---
description: "面向用户与维护者的文件型设置提供方：选择、配置或排查 YAML/JSON 设置文档及其热重载。"
kind: "package-reference"
---

# @deepseek-ai/dsh-settings-file

[English](README.md) | 中文

## 概述

`dsh-settings-file` 把所有 namespace 的用户设置保存在一个 YAML 或 JSON 文档中，默认是 harness home 下的 `settings.yaml`：用户可以直接编辑文档——变更实时生效——也可以经服务写入，后者会安全合并并发编辑。YAML 写入保留每个未触碰节点上的注释、锚点与排版，未加载插件所拥有的分节也绝不会被丢弃。启动时非法文档直接报错；运行中失败的热重载保留最后可用分节并告警，而不是拖垮进程。

## 目录

- [使用本包](#use-this-package)
- [理解实现](#understand-the-implementation)
- [进一步探索](#further-exploration)
- [模型体验](#model-experience)
- [已知限制与延期工作](#known-limitations-and-deferred-work)
- [开发备注](#dev-note)

-----

<a id="use-this-package"></a>
## 使用本包

当组合想要一份用户可编辑的设置文档时，挂载此提供方。常用路径是显式的：挂载提供方、经 `ctx.settings` 注册 namespace，然后让用户编辑文档或让配置界面经服务写入。

### 何时选择

把它当作默认的用户设置存储：一份用户可以在任意编辑器中打开的人类可读文档，变更无需重启即可生效。当文档中的注释与排版很重要时也选它，因为写入会保留它们。非文件存储（例如远程设置后端）不随本包提供；那需要另一个提供方。

### 最小配置

```yaml
- 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 写入稳定窗口（毫秒） |

生成的[配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-settings-file)是每个受支持字段及其 JSDoc 的穷尽式真源。

### 编辑文档

文档是 namespace 到用户分节的 YAML 或 JSON 映射。用户可以直接编辑：任何变更都会自动生效，删除文件则让所有 namespace 回到默认值与 `base`。存在但非法的文档在启动时使插件加载失败——提供方绝不会静默忽略或覆盖它。运行中不可读或不可解析的编辑只告警并保留最后可用分节，因此手改出错不会拖垮进程。

### 经服务写入

经 `ctx.settings` 的写入绝不会丢失并发变更：仍在途中的外部编辑、watcher 漏掉的变更或另一个进程的写入，都会在写入落地前并入文档。YAML 编辑是叶子级 diff：只设置变化的值、只删除被移除的键，因此每个未触碰节点以及每个被改键值对的键上的注释、锚点与排版都得以保留；被改的数组或其他非 map 值整体替换。JSON 文档重新序列化，无注释。若磁盘上的文档已变为非法，写入会明确报错，而不是覆盖用户的手工编辑。

锁有 2 秒的获取期限，带指数退避；超时的竞争者不会移除现有锁，因为锁龄无法区分崩溃的所有者与被暂停但仍存活的写入方——遗留锁恢复须由操作者执行。文档以 `0600` 权限创建在仅属主可访问的 `0700` 目录下，并通过一个绝不跟随预埋符号链接的随机后缀临时文件原子替换。

### 失败与恢复

- 不支持的扩展名在加载时报错——格式由扩展名决定（`.yaml`、`.yml`、`.json`）。
- 文档缺失即空存储；删除文件即回到该状态。
- 运行中磁盘文档非法不会阻塞任何操作，但保留最后可用分节；写入拒绝覆盖它。
- `prepareDocument()` 在原生编辑器打开前，把缺失的文档物化为空的仅属主可访问文件。

-----

<a id="understand-the-implementation"></a>
## 理解实现

<details>
<summary>实现细节——点击展开</summary>

本节解释提供方背后的设计决策并指出实现它们的代码位置；可观察行为已在[使用本包](#use-this-package)中完整说明。

### 设计理念

- **一步显式默认化。** `resolveSpec(config)` 在一步内解析文件名、格式、watch 标志与防抖窗口，因此绕过 Schemastery 规范化的程序化构造也得到同样的默认值。
- **启动明确报错，重载保留最后可用值。** 存在但非法的文档使插件加载失败；运行中不可读或不可解析的编辑只告警并保留最后可用分节。
- **每次写入都是读-改-写。** persist 先从磁盘对账并把任何差异发布进 seam，再基于这份新鲜文本渲染，因此写入绝不会复活陈旧文档或丢掉未观察到的同级分节。
- **写入持有跨进程写锁。** 读-渲染-rename 流程在以 `wx` 创建的同级 `<file>.lock` 保护下运行，带指数退避与 2 秒的获取期限；读取方从不取锁，因为 rename 提交是原子的。
- **YAML 编辑是叶子级 diff。** 只设置变化的值、只删除被移除的键，保留未触碰节点上的注释、锚点与排版。
- **重载与写入共享一条操作链。** watcher 刷新与来自各 namespace 队列的 persist 按队列顺序逐个执行；每次渲染都基于上一次操作提交后的文本。
- **按内容抑制自写。** 提供方缓存最后可用文本；watcher 事件内容与缓存相同（含自己的写入）即为 no-op。

### 源码地图

| 文件 | 职责 |
|---|---|
| [`src/index.ts`](src/index.ts) | 提供方：spec 解析、加载/解析、写锁下的读-改-写、watcher 生命周期、YAML/JSON 渲染 |
| [`src/invariant.ts`](src/invariant.ts) | 不变式伴生插件（无运行时不变式；文件往返、watcher 时序与原子写入行为由包测试证明，进程内提交关系归 `dsh-settings` 所有） |

### 文档生命周期

基类服务 init 在服务可注入前加载并发布文档；随后提供方启动 watcher，并在 ready 时对账一次，补上「初始读取与 watcher 生效之间写入的变更永不触发事件」的启动缺口。每个 watcher 事件与每次 persist 都排上同一条独占操作链。`reconcileFromDisk` 把磁盘文本与缓存比较，发布任何差异（缺失即空文档），只在解析失败时抛出，让每个调用方自行选择策略——重载告警并保留最后可用文档，写入明确报错。卸载先把提供方标记为已关闭，关闭 watcher，再等待所有已排队或进行中的操作完成，之后不再有任何发布。

### 渲染路径

YAML 渲染把缓存文本解析成可变的保留注释树，再对一个 namespace 施加叶子级编辑；JSON 渲染替换一个 namespace 键后以两个空格缩进重新序列化。在 Chokidar 打开目标之前，提供方对层级最深的现有祖先路径执行 realpath 解析，再拼回缺失的后缀，从而避免 Windows 在 libuv 内部混用 8.3 别名与长格式事件路径。

</details>

-----

<a id="further-exploration"></a>
## 进一步探索

当提供方级约定不够用时阅读以下页面。它们从 seam 约定逐步进入原子写入原语与穷尽式配置面。

- [用户设置服务](../settings/README.zh.md)——namespace 注册、分层解析、写入与本提供方所供的事件。
- [设置子系统参考](../../../docs/subsystems/settings.zh.md)——namespace、解析顺序、descriptor 与变更提交。
- [设置包映射](../README.zh.md)——用户设置能力的两个包。
- [原子写入](../../util/atomic-write/README.zh.md)——每次写入都使用的写锁与原子替换。
- [主目录路径](../../util/home-paths/README.zh.md)——`$DSH_HOME` 解析与规范化监听路径。
- [生成配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-settings-file)——每个受支持配置字段及其源声明。

-----

<a id="model-experience"></a>
## 模型体验

间接生效：经由 `ctx.settings` 的消费方，它们拥有存储值所喂给的任何模型面行为；本文件提供方只存储并发布 namespace 分节，自身不注册任何模型面内容。

#### KV Cache 影响

无直接失效；请求前缀的任何变更均由消费方插件负责。

## 已知限制与延期工作

<a id="known-limitations-and-deferred-work"></a>


这些限制说明本提供方何时不合适或需要特别的运维注意。它们是当前包约束，不是任务积压。

- **同 namespace 冲突仍是后写胜出**——写锁加读-改-写让并发写入者不会丢掉彼此的 namespace，但两个写入者编辑同一个 namespace 时仍以较后的写入为准；没有按值合并，也没有修订检查。
- **漏掉的 watcher 事件在下一个信号前保持不可见**——读取从不重新 stat 文件，因此 watcher 漏报的变更只会在下一个事件、下一次写入或重启时被并入。
- **注释保留仅限 YAML 且仅限 map 形状**——JSON 文档重新序列化，无注释，且被改数组内部的注释（或行内附着在被改标量值上的注释）随其所描述的值一同被换掉。
- **无值间接引用**——分节存字面值；面向密钥的 `${env:VAR}` 式引用是暂缓实现的 seam 层功能。

<a id="dev-note"></a>
### 开发备注

<details>
<summary>维护者的工作上下文——点击展开</summary>

本开发备注是维护者的工作上下文：尚未决定的暂缓方向。它明确非权威——已发布的行为、限制与已接受的理由见上文各节与包代码。暂缓方向：`${env:VAR}` 式值间接引用是 seam 层功能——落地时应归属设置服务约定，而非本提供方。遗留锁恢复按设计仍是操作者动作，因为锁龄无法区分崩溃的所有者与被暂停但仍存活的写入方。

</details>
