@deepseek-ai/dsh-settings-file
September 6, 2026 · View on GitHub
English | 中文
概述
dsh-settings-file 把所有 namespace 的用户设置保存在一个 YAML 或 JSON 文档中,默认是 harness home 下的 settings.yaml:用户可以直接编辑文档——变更实时生效——也可以经服务写入,后者会安全合并并发编辑。YAML 写入保留每个未触碰节点上的注释、锚点与排版,未加载插件所拥有的分节也绝不会被丢弃。启动时非法文档直接报错;运行中失败的热重载保留最后可用分节并告警,而不是拖垮进程。
目录
使用本包
当组合想要一份用户可编辑的设置文档时,挂载此提供方。常用路径是显式的:挂载提供方、经 ctx.settings 注册 namespace,然后让用户编辑文档或让配置界面经服务写入。
何时选择
把它当作默认的用户设置存储:一份用户可以在任意编辑器中打开的人类可读文档,变更无需重启即可生效。当文档中的注释与排版很重要时也选它,因为写入会保留它们。非文件存储(例如远程设置后端)不随本包提供;那需要另一个提供方。
最小配置
- name: '@deepseek-ai/dsh-settings-file'
config:
path: /absolute/path/to/settings.yaml
| 字段 | 默认值 | 含义 |
|---|---|---|
path | <harness home>/settings.yaml | 设置文档路径;扩展名决定格式(.yaml、.yml 或 .json) |
dshHome | $DSH_HOME 或 ~/.dsh | path 省略时使用的 harness home |
watch | true | 监听文档并热发布外部编辑 |
debounceMs | 100 | watcher 写入稳定窗口(毫秒) |
生成的配置目录是每个受支持字段及其 JSDoc 的穷尽式真源。
编辑文档
文档是 namespace 到用户分节的 YAML 或 JSON 映射。用户可以直接编辑:任何变更都会自动生效,删除文件则让所有 namespace 回到默认值与 base。存在但非法的文档在启动时使插件加载失败——提供方绝不会静默忽略或覆盖它。运行中不可读或不可解析的编辑只告警并保留最后可用分节,因此手改出错不会拖垮进程。
经服务写入
经 ctx.settings 的写入绝不会丢失并发变更:仍在途中的外部编辑、watcher 漏掉的变更或另一个进程的写入,都会在写入落地前并入文档。YAML 编辑是叶子级 diff:只设置变化的值、只删除被移除的键,因此每个未触碰节点以及每个被改键值对的键上的注释、锚点与排版都得以保留;被改的数组或其他非 map 值整体替换。JSON 文档重新序列化,无注释。若磁盘上的文档已变为非法,写入会明确报错,而不是覆盖用户的手工编辑。
锁有 2 秒的获取期限,带指数退避;超时的竞争者不会移除现有锁,因为锁龄无法区分崩溃的所有者与被暂停但仍存活的写入方——遗留锁恢复须由操作者执行。文档以 0600 权限创建在仅属主可访问的 0700 目录下,并通过一个绝不跟随预埋符号链接的随机后缀临时文件原子替换。
失败与恢复
- 不支持的扩展名在加载时报错——格式由扩展名决定(
.yaml、.yml、.json)。 - 文档缺失即空存储;删除文件即回到该状态。
- 运行中磁盘文档非法不会阻塞任何操作,但保留最后可用分节;写入拒绝覆盖它。
prepareDocument()在原生编辑器打开前,把缺失的文档物化为空的仅属主可访问文件。
理解实现
实现细节——点击展开
本节解释提供方背后的设计决策并指出实现它们的代码位置;可观察行为已在使用本包中完整说明。
设计理念
- 一步显式默认化。
resolveSpec(config)在一步内解析文件名、格式、watch 标志与防抖窗口,因此绕过 Schemastery 规范化的程序化构造也得到同样的默认值。 - 启动明确报错,重载保留最后可用值。 存在但非法的文档使插件加载失败;运行中不可读或不可解析的编辑只告警并保留最后可用分节。
- 每次写入都是读-改-写。 persist 先从磁盘对账并把任何差异发布进 seam,再基于这份新鲜文本渲染,因此写入绝不会复活陈旧文档或丢掉未观察到的同级分节。
- 写入持有跨进程写锁。 读-渲染-rename 流程在以
wx创建的同级<file>.lock保护下运行,带指数退避与 2 秒的获取期限;读取方从不取锁,因为 rename 提交是原子的。 - YAML 编辑是叶子级 diff。 只设置变化的值、只删除被移除的键,保留未触碰节点上的注释、锚点与排版。
- 重载与写入共享一条操作链。 watcher 刷新与来自各 namespace 队列的 persist 按队列顺序逐个执行;每次渲染都基于上一次操作提交后的文本。
- 按内容抑制自写。 提供方缓存最后可用文本;watcher 事件内容与缓存相同(含自己的写入)即为 no-op。
源码地图
| 文件 | 职责 |
|---|---|
src/index.ts | 提供方:spec 解析、加载/解析、写锁下的读-改-写、watcher 生命周期、YAML/JSON 渲染 |
| — | 不发布运行时不变式伴生入口;文件往返、watcher 时序与原子写入行为由包测试证明,进程内提交关系归 dsh-settings 所有。 |
文档生命周期
基类服务 init 在服务可注入前加载并发布文档;随后提供方启动 watcher,并在 ready 时对账一次,补上「初始读取与 watcher 生效之间写入的变更永不触发事件」的启动缺口。每个 watcher 事件与每次 persist 都排上同一条独占操作链。reconcileFromDisk 把磁盘文本与缓存比较,发布任何差异(缺失即空文档),只在解析失败时抛出,让每个调用方自行选择策略——重载告警并保留最后可用文档,写入明确报错。卸载先把提供方标记为已关闭,关闭 watcher,再等待所有已排队或进行中的操作完成,之后不再有任何发布。
渲染路径
YAML 渲染把缓存文本解析成可变的保留注释树,再对一个 namespace 施加叶子级编辑;JSON 渲染替换一个 namespace 键后以两个空格缩进重新序列化。在 Chokidar 打开目标之前,提供方对层级最深的现有祖先路径执行 realpath 解析,再拼回缺失的后缀,从而避免 Windows 在 libuv 内部混用 8.3 别名与长格式事件路径。
进一步探索
当提供方级约定不够用时阅读以下页面。它们从 seam 约定逐步进入原子写入原语与穷尽式配置面。
- 用户设置服务——namespace 注册、分层解析、写入与本提供方所供的事件。
- 设置子系统参考——namespace、解析顺序、descriptor 与变更提交。
- 设置包映射——用户设置能力的两个包。
- 原子写入——每次写入都使用的写锁与原子替换。
- 主目录路径——
$DSH_HOME解析与规范化监听路径。 - 生成配置目录——每个受支持配置字段及其源声明。
模型体验
间接生效:经由 ctx.settings 的消费方,它们拥有存储值所喂给的任何模型面行为;本文件提供方只存储并发布 namespace 分节,自身不注册任何模型面内容。
KV Cache 影响
无直接失效;请求前缀的任何变更均由消费方插件负责。
已知限制与延期工作
这些限制说明本提供方何时不合适或需要特别的运维注意。它们是当前包约束,不是任务积压。
- 同 namespace 冲突仍是后写胜出——写锁加读-改-写让并发写入者不会丢掉彼此的 namespace,但两个写入者编辑同一个 namespace 时仍以较后的写入为准;没有按值合并,也没有修订检查。
- 漏掉的 watcher 事件在下一个信号前保持不可见——读取从不重新 stat 文件,因此 watcher 漏报的变更只会在下一个事件、下一次写入或重启时被并入。
- 注释保留仅限 YAML 且仅限 map 形状——JSON 文档重新序列化,无注释,且被改数组内部的注释(或行内附着在被改标量值上的注释)随其所描述的值一同被换掉。
- 无值间接引用——分节存字面值;面向密钥的
${env:VAR}式引用是暂缓实现的 seam 层功能。
开发备注
维护者的工作上下文——点击展开
本开发备注是维护者的工作上下文:尚未决定的暂缓方向。它明确非权威——已发布的行为、限制与已接受的理由见上文各节与包代码。暂缓方向:${env:VAR} 式值间接引用是 seam 层功能——落地时应归属设置服务约定,而非本提供方。遗留锁恢复按设计仍是操作者动作,因为锁龄无法区分崩溃的所有者与被暂停但仍存活的写入方。