设置系统
一句话版:
ctx.settings是运行时用户配置服务:用户在 UI/Settings 里改的东西存进~/.dsh/settings.yaml(由dsh-settings-file文件提供者热发布),settings/document-updated事件让 UI 和插件同步。
这一篇说明白"patch 静态配置之外、用户运行时可改的那一层"。
一、这是哪一层
| patch 层 | settings 层 | |
|---|---|---|
| 谁写 | 部署方(cordis.patch.yml) | 用户(UI / 文件) |
| 内容 | 静态配置/挂载 | 运行时用户设置 |
| 载体 | cordis.patch.yml | ~/.dsh/settings.yaml |
| 变更 | 改文件重启动 | 热发布,settings/document-updated |
patch 和 settings 是两条配置缝,互补(见 配置)。
二、文件提供者:settings-file
dsh-settings-file 是默认实现,一个 YAML/JSON 文档承载每个 namespace 的 section:
| 字段 | 默认 | 说明 |
|---|---|---|
path | settings.yaml(harness home 下) | 文档路径;扩展名选格式(.yaml/.yml/.json) |
dshHome | $DSH_HOME 或 ~/.dsh | path 缺省时的 harness home |
watch | true | 监听文档,热发布外部编辑 |
debounceMs | 100 | 写 settle 窗口(ms) |
行为要点:
- 启动 fail-loud,reload 保 last-good:存在的非法文档 → 插件加载失败;直播中的不可读编辑 → warn 并保留最后好的 section;缺失文档 → 每个 namespace 从 defaults+base 解析
- 每次写都是 read-modify-write:先重读文档、发布差异,再写:不会复活陈旧文档或丢未观察的兄弟 section
- 写持有跨进程写锁(
*.locksibling,wx创建,2s 获取上限) - 原子、owner-only、防 symlink:渲染用随机后缀 temp(0600)再 rename 覆盖
- YAML 编辑是叶级 diff:只改变化的值/删移除的键,保留注释锚点格式
- documentPath 是解析后的绝对路径:
ctx.settings.documentPath是resolveSpec()文件名;浏览器只拿到可用性标志,永不重建$DSH_HOME、永不提交文件系统目标 - 自身写靠内容去重:provider 缓存最后一份好文本,watcher 事件内容等于缓存(含自己的写)时是 no-op
- 无值间接引用:section 存字面值,
${env:VAR}式密钥引用是 deferred 的 seam 层特性
三、surface:插件怎么用
import { settingsNamespace } from '@deepseek-ai/dsh-settings'
import z from '@deepseek-ai/schemastery'
import { Context } from '@deepseek-ai/cordis'
const ns = settingsNamespace('my-plugin')
const schema = z.object({
greeting: z.string().default('你好'),
maxItems: z.number().step(1).min(1).default(10),
})
export function apply(ctx: Context) {
const scope = ctx.settings.register(ns, schema, { /* base? */ })
// 解析顺序: schema默认 → composition base → 用户层
// scope.get() / scope.update({...}) / scope.watch(...)
}
register(ns, schema, {base?})返回 ownerSettingsScope(get/watch/update)- 注册是 effect:dispose 该 fiber 会移除 namespace 与观察者
- 存储的 section 被 schema 拒绝 → 注册失败;重复 namespace → fail loud
- 完整示例见 配置与发布 的"运行时用户配置"
四、内置 namespace:agent-default-model
agent-default-model 是 DSH 自带的设置 section,由 dsh-agent-default-model 服务(ctx.agentDefaultModel)提供:
currentSelection()返回一个 detached{ provider, model, reasoningEffort? }选择,给新建 Agent 用saveSelection(selection)保存完整用户选择;没有 settings provider 时是 no-op,组合里的部署默认保持生效- 部署默认写在插件 config(必填
{ provider, model }),是 settings section 的 base;挂载 settings provider 后用户选择覆盖其上,下次currentSelection()读取即生效 reasoningEffort只属于 settings section、不进插件 config:完整保存的选择在下一个模型没有 effort 时可以清掉它,而 composition 值会被再次继承- 服务不校验目录成员资格:provider route 可能服务一个未登记的模型,真正开模型请求的消费方负责可用性诊断
# ~/.dsh/settings.yaml
agent-default-model:
provider: deepseek-official
model: deepseek-v4-flash
直接入口(如 dsh --profile headless)与 Host 侧入口(如 ApiProxy)读同一个服务,不各自维护 provider/model 默认值。
五、事件
settings/document-updated:ctx.settings值变化- 第三方插件/UI 监听它刷新;也触发
credentials/updated(若含凭据引用)
六、验证
# 看设置文档(用户可改的那层)
cat ~/.dsh/settings.yaml
# 组合里看 settings 服务
dsh web --dump-config | grep -i settings