description: "面向插件作者与维护者的用户设置服务:注册可配置 namespace、读取解析值或接入配置界面。"
English | 中文
当用户需要在运行时修改插件配置,而无需重启或重新读取 cordis.yml 时,请使用本包。每个 namespace 合并 schema 默认值、部署配置与用户覆盖;读取方会得到深冻结的解析值快照,并可观察已提交的变更。写入只影响用户覆盖、按 namespace 串行执行,并可拒绝陈旧 revision,避免覆盖较新的变更。持久化运行时编辑需要先配置设置存储;否则插件仍可继续使用组合配置。
插件与配置界面通过 ctx.settings 在运行时读取并修改配置。常用路径:挂载提供方、用 schema 注册 namespace、读取并观察解析值,并通过 owner scope 写入。
当插件的配置需要在运行时可变——用户编辑文档或配置界面修改——且无需重启或重读 cordis.yml 时,选择设置服务。它适合多个插件各拥有一个配置 namespace、以及配置界面需要渲染 schema、标记用户覆盖字段并持久化编辑的场景。当配置在加载时固定则没有必要:没有挂载提供方时一切照旧,配置保持组合原样。
服务本身不存储任何内容;请挂载一个提供方,例如随附的文件型提供方:
- name: '@deepseek-ai/dsh-settings-file'
config:
path: /absolute/path/to/settings.yaml
提供方上线后 ctx.settings 即出现。完整配置面由提供方 README 负责;生成的配置目录列出每个受支持字段。
插件用 schemastery schema 注册自己的 namespace,并可选地把组合配置作为 base 层传入,让解析值从部署已配置的内容起步:
const scope = ctx.settings.register('ui-theme', ThemeSchema, {
base: config, // composition entry config; the user layer resolves above it
})
const theme = scope.get() // deep-frozen resolved snapshot
scope.update({ density: 'compact' }) // merges into the user section and persists
TypeScript 会按小写字母、数字与连字符文法检查字面量 namespace 参数;运行时动态传入的字符串接受相同校验。ctx.settings.installSection(owner, ns, schema, entry, hooks) 为消费方插件封装可选服务接线:只要设置服务存在,它就用插件的组合配置作为 base 注册 namespace;服务消失时插件回退到组合配置,行为与原先完全一致。
get(ns) 以深冻结快照返回解析值,namespace 未注册时为 undefined。watch(callback) 在每次已提交变更后以 (next, prev) 调用回调:同一回调的调用按提交顺序逐个执行,异常被隔离并记入日志,因此慢或抛错的观察者绝不会阻塞或破坏其他观察者。
update(ns, patch) 把普通对象 patch 深合并进用户分节——绝不进 base——校验解析候选值、经提供方持久化后提交。replace(ns, section) 整体替换用户分节,是删除/重置路径:replace({}) 重新继承 base 与 schema 默认值。mutate(ns, ops) 在写入排到队首那一刻的分节上按序施加 { op: 'set' | 'unset', path } 编辑——这是持有不完整(例如脱敏后)视图的调用方的删除路径,因为按协议接口返回的内容重建分节再整体替换,会删掉协议从未回传的每个字段。
每次写入都会拒绝与 JSON 不兼容的数据(Date、Map、BigInt、非有限数或循环引用会在任何内容持久化前以 $ 为根的路径报错)、拒绝只读提供方上的写入,并可接受可选的 expectedRevision:把 descriptor 中的 revision 传回,namespace 已越过该值时写入会被 SettingsConflictError 拒绝,而不是覆盖先完成写入的一方。
describe() 为每个已注册 namespace 返回一条 descriptor:序列化 schema、解析值、分离的 base 与 user 层(字段出现在 user 中即标记为用户覆盖)、生效时机与 namespace 的 revision。每个协议接口都必须传入 redactSecrets: true:它从每一层剥离 role('secret') 字段,并把它们枚举为 { path, set } slot,让页面可以渲染只写输入而不接触任何机密。documentPath 与 prepareDocument() 在提供方拥有用户可编辑文件时把它暴露给原生编辑器。
settings/updated (ns, next, prev, source) 在每次已提交变更后触发——进程内写入(source: 'update')或外部观察到的编辑(source: 'provider')——解析值深相等时绝不触发。settings/document-updated (ns, revision) 在原始用户分节发生变化时触发,即使解析值没有变——已打开的编辑器正需要它来得知字段从继承变为覆盖。schema 拒绝的存量分节在重载时保留该 namespace 的最后可用值并告警;注册时同样的失败会直接拒绝注册。
当服务级约定不够用时阅读以下页面。它们从共享子系统词汇逐步进入随附提供方与能力架构。
间接生效:由设置值提供的所有面向模型的内容均由消费方插件负责;本服务只存储并解析用户设置,自身不注册任何面向模型的内容。
无直接失效;把设置值纳入请求前缀的消费方负责该变更。
这些限制说明本服务何时不合适或需要特别注意。它们是当前包约束,不是任务积压。
base 与一个用户文档;它不记录每个解析值由哪一层提供。redactSecrets 并非一条可被证明的协议边界——遍历器只跟随 object/dict/array 容器,因此只能经由 union、intersection 或 transform 抵达的 role('secret') 字段会被原样返回,且 secrets 列表为空;序列化 schema 还会把 secret 字段的默认值带给每个客户端。两种情况都不会被拒绝;机密无法经由被遍历的容器抵达的 schema,绝不可注册到暴露于协议的 namespace 上。fail-closed 的 describeForWire()——拒绝自己无法证明安全的 schema,并对序列化封装与错误文本做净化——是暂缓的答案。