Skip to content
My Blog
Go back

第 11 课 · 自扩展:skill 与 extensions

前置:第 08、09 课 | 预计时长:45–60 分钟 | 动手环节:必须完成 | 与第 12 课可互换顺序

你将学会

问题引入

「一切皆插件」再往前一步:让模型在运行时自己写插件。dsh 靠两套机制做到——skill(内容注入)与 extensions(代码求值 + 插件挂载)。这一课讲清两套机制的边界,顺带精读四篇 postmortem,看这个系统怎么被现实教育。

正文

11.1 skill:注册表 + 模型侧工作流

对应 packages/skill/skill/src/index.tspackages/skill/tool-skill/

// provider 接口(skill/src/index.ts:248-260)
interface SkillProvider {
  name
  list(options) → SkillCandidate[]   // 列目录
  get(candidate, options) → SkillDefinition
}
registerProvider(create)   // :391 注册是 effect,fiber 拆除即反注册 + 目录缓存失效
register(skill)            // :440 任何插件可在运行时注入技能(保留名 'runtime')

模型侧工作流(tool-skill)三步:

  1. agent/pre-step 注入 <available_skills> 目录——内容寻址:目录摘要按 sha256 比对,变化时才发布替换(:213-251),避免每步重发全文;
  2. 模型调用 skill 工具 → 校验 modelInvocable → ctx.skills.get() → 渲染 <skill_content>
  3. 用户消息里的 /skill-name 手势触发确定性注入——只认 source.kind === 'user',外部文本无法伪造用户手势。

SkillRegistry 是 scoped 分层注册表:全局层 + 每个 agent preset 的 scope 层,近层同名遮蔽。本地磁盘 provider skill-filesystem 按根分级:项目 .dsh(100) / .agents(200) / 自定义(300) / 用户(400/500) / bundled(600),chokidar 监视 + 失效。

11.2 extensions:模型写 JS,运行即插件

四包:tool-cordis(七个模型工具)、cordis-host-runner(宿主半边)、cordis-client-runner(浏览器半边)、ui-cordis(面板)。七个工具 = 三个只读检视(cordis_inspect_list/query/self)+ 四个写操作(cordis_define/run/stop/undefine)。

// 宿主半边:node:vm 新 realm 中作为 async 函数体求值(cordis-host-runner/src/sandbox.ts:254)
evaluateHostCode(code) {
  // require/fetch/setTimeout 被"教学陷阱"拦截,指向 ctx 服务(:96-108)
  vm.runInNewRealm(`(async () => { ${code} })()`)
}
// 产物作为 'cordis-dynamic' group 的子 fiber 挂载,启动失败即 dispose

// 浏览器半边:闭包求值,参数即符号面(cordis-client-runner/src/client/evaluator.ts:173-180)
parameters = ['React', 'console', 'styles', 'host', 'harness', ...traps]
factory = new Function(...parameters, `return (async () => { ${clientCode} })()`)

关键约束:

边界声明(自己读原文):“host-realm helper functions remain an escape route”——沙箱是可检视、可丢弃的遏制(containment),不是安全边界。

勘误背景:根 AGENTS.md 仍列 self-modification/,但磁盘上该职责由 packages/extensions/ 承接(组 README 自称 “Agent runtime self-modification”)。读文档遇到对不上时以磁盘为准。

11.3 postmortem:四篇必须精读的事故复盘

docs/postmortem/ 收录「已触达真实用户」的事故,写作三条件:subtle(机制非显然)、systemic(逃逸原因是测试/工具/规范缺口)、costly to rediscover

#事故教训 → 固化
0001ACP 连接即崩:export default apply 使 Loader 丢弃命名空间插件的 inject;178 个单测全绿(都手工挂载)命名空间插件禁加 default export;可选服务用 ctx.get;必须有走真实 Loader 的无键 e2e
0002!!js 表达式写在条目元数据里恒真 → 文件系统工具被永久禁用;快照刷新把 UNKNOWN_TOOL 接受为新期望!!js 仅限 config/disabled;催生 verify-cordis-config 门禁
0003模型把裸 Vite HTTP 200(白屏)当成功,验证了替身服务器GUI 规范 URL 必须 model-visible;验收要在确切 origin 上外部观察
0004landlock 良性 partial enforcement 与致命行共享前缀被误判致命证据须精确行匹配且与 exit 125 联结;正交结果独立报告

配套 docs/defensive-patterns.md 七条防御规则(正交结果独立上报、公共契约两侧都守、异步状态不是同步状态、Dispose 必须抵达静止、派发器包含回调异常、不把环境交给不可信输出、链接形态路径用 unlink 删)——生命周期/并发/子进程/拆除工作的常备必读。

动手环节

  1. 解剖一个真实 skill:打开 deepseek-harness/.agents/skills/ 里任意一个(如 dsh-pre-push-checks),找出它的名字、描述、触发约定;再确认本课程第 10 课让你写的 Agent Note 骨架与 .agents/notes/README.md 的制度一致。
  2. 精读一篇 postmortem:完整读 0001,回答:事故的机制根因是什么?为什么 178 个单测全都没拦住?最终固化了哪三层防线?
  3. 推演题:为什么目录注入用 sha256 比对而不是每步重发?为什么 /skill-name 手势必须校验 source.kind === 'user'?各举一个没有这条约束时会发生的事故场景。

自检清单

常见误解

延伸阅读


Share this post:

Previous Post
第 10 课 · 毕业项目:写一个完整插件
Next Post
第 12 课 · Web 客户端:Slots、模块图与三层分层