摘要:“AI 能给自己装插件”——翻完源码发现仓库里根本没有市场。真实机制更炸:模型现场写一段插件代码,登记、审批、沙箱里跑,跑挂了错误信息自动喂回,自己修自己升级。
“AI 能给自己装插件。”
这句话听起来,像是有个插件市场,模型在里头挑挑拣拣,然后点一下安装。我翻完源码后第一反应是:仓库里根本没有市场。没有 marketplace,没有 npm install,没有能力货架。
真实机制比“装”更炸:AI 自己写一段插件代码,现场登记、审批、沙箱里跑起来,跑挂了还能自己修。不是逛市场,是现场手搓一个。
这篇是 dsh 系列第五篇,拆的就是这个“自举插件”系统。
一、没有市场:AI 手写插件的三步流程
1.1 先摸清自己家:cordis_inspect_*
模型想给自己加能力,第一步不是去外面找,而是先读自己家底。
源码里提供了一对接口:cordis_inspect_list 和 cordis_inspect_query。前者列出当前运行时里有哪些 Service、Event、Slot,后者按名字查详情。
这不像逛应用商店,更像是翻自家工具箱。模型得先知道:我现在能注入什么、能监听什么、能往哪个 Slot 插东西。
探测完,模型才会决定:我要写一个什么样的插件,需要注入哪些服务。
1.2 define:只登记,不执行
第二步是 cordis_define。
这个名字容易误解成“定义并启用”。其实不是。define 只做登记,代码一次都没跑。它先把代码字符串收进来,做一次语法预检:new Script(code)(来自 node:vm)只编译不执行,语法错误直接挡在登记阶段。
更妙的是错误信息不是裸堆栈,而是会识别模型常见错误:TS 注解没去掉、括号不配、顶层 await 乱用之类。报错里带着教学提示,告诉模型怎么改。
这对应了《拆解 dsh(四):多智能体委派光谱》里说的“报错里带着出路”。不过那次是运行时错误,这里是登记阶段就把错误变成教学材料。
一旦 define 成功,这个 Package 就被冻结。源码注释写得很死:Package 不可变。你不能改老版本代码,只能追加新版本。
1.3 run 与审批:单勾 / 双勾
第三步是 cordis_plugin_run,这才是真正激活。
但激活前有一道审批分叉:
纯 Host 插件,只操作后端服务,直接跑。 含浏览器 UI 的插件,必须弹窗让用户批准。
审批界面有两个勾。源码里叫单勾和双勾:
- 单勾
:只授权当前这个版本。 - 双勾
:授权当前版本,也授权这个 Package 的未来版本。
这是审批粒度的分层。用户不是信“这个 AI”,而是信“这段代码”或“这个代码系列”。双勾相当于给了一个持续授权,后续自修升级时不用再弹窗。
1.4 版本双指针:current 与 next
每个 Package 有两个版本指针:current 和 next。
current 是正在运行的版本。next 是登记成功、等待激活的版本。
cordis_plugin_update 会把 next 切到 current。如果激活失败,current 保持不动,旧版本继续跑。
外部引用插件时用 @pluginId。系统注入的是当前 current 指针,不是固定版本号。所以模型只要更新指针,所有引用方自动跟随。
二、场景走一遍:做一个实时倒计时小组件
光说流程抽象,我举一个源码注释里反复出现的例子:“帮我做一个实时倒计时小组件”。
完整走一遍是六步:
- 探测
:模型调用 cordis_inspect_list,发现有一个timer服务和一个 UI Slot。 - 写代码
:模型生成一段插件代码,暴露一个倒计时组件,注入 timer和ui。 - define
:调用 cordis_plugin_define,语法预检通过,Package 登记为 v1。 - 审批
:因为涉及浏览器 UI,弹窗让用户勾选。用户单勾通过 v1。 - run
:沙箱里激活,UI 上出现倒计时。 - 失败自修
:如果倒计时渲染炸了,模型收到 steer 指令,用 cordis_inspect_self读诊断,在同一 Plugin 上修,追加 v2。如果用户之前双勾授权,v2 直接替换;单勾则再弹一次窗。
重启进程后,这个插件会消失。它不是永久安装,只活在当前进程里。
这个“六步流程”本身就是 dsh 世界观的一个缩影:AI 不是用户,不是管理员,它是运行时的另一个开发者,在自己的上下文里写代码。
三、沙箱不是安全边界
3.1 node:vm + guard façade 长什么样
代码要在哪跑?源码用的是 node:vm。
但 dsh 没有天真地认为 node:vm 就是安全隔离。它在上头套了一层 guard façade。
传给插件的全局对象被大幅裁剪:
console是打过 tag 的,日志会进入宿主审计流。 几个 harness助手,比如编码/解码工具。其他常见全局?大部分被替换成重定向陷阱。
3.2 重定向陷阱:require / setTimeout / fetch 调用即报错
这是我最喜欢的细节。
插件代码里如果写 require('fs'),不会静默失败,也不会真的加载 fs。它会立刻抛错,并告诉你该用哪个 Cordis 服务——require 被拒绝时提示改用 inject: ['fs'],fetch 指向 ctx.web,setTimeout 指向 Cordis 的 timer 服务。
不是封死,而是把错误变成 API 教学。模型写错一次,下次就知道该 inject 什么。
另外,插件执行有超时:vmTimeoutMs 默认 5000 毫秒。死循环会被直接掐掉。
3.3 Proxy façade:只放行 CTX_VERBS 与 inject
上下文对象 ctx 也不是原样给的。它包在一个 Proxy 里。
Proxy 的白名单只有两类:
CTX_VERBS:Cordis 上下文里的合法动词(effect/on/provide/timer 等); inject声明过的服务:插件 manifest 里没写的服务, ctx上根本访问不到。
两个专门的防御点:ctx.tools.get 只返回 schema 视图不给 execute 函数(防绕过工具执行管线直接调内部实现);某个服务的返回值里如果包含 Cordis Context,会被 denyContext 拒绝(防插件拿到未防护的宿主句柄层层突围)。
3.4 诚实声明:可审查、可回收,但不是隔离
但源码注释非常诚实:
这不是安全隔离,而是可审查、可回收的协作约束。
意思是:node:vm 拦不住 host realm 的闭包逃逸。如果插件真的想搞事,同进程内总有办法。所以 dsh 的信任模型不是“沙箱万能”,而是前置审批 + 运行时可审计 + 失败可回滚。
这让我想起《拆解 dsh(三):guard 包,两种权力形态》里那句判断:诚实的缺席比表演性的控制值钱。这里同理:诚实的沙箱比假装的安全值钱。
承认“我不可能绝对安全”,反而让设计更可信。
3.5 fiber 挂载:一次 dispose,全部回收
插件注册的所有 effect,最终都挂在一个 fiber 上。
fiber.dispose() 一调,插件注册的 service、event、slot、timer 全部回收。这个设计呼应了《拆解 DeepSeek Harness:热插拔的数学》里讲的热插拔的数学:插件不是零散的资源,而是一个有生命周期的整体,可以整体创建、整体销毁。
四、失败自修:把报错喂回模型
4.1 steerRunOutcome 给模型递条子
插件跑起来之后,成功或失败都会通过 agent.steer() 把结果写回模型上下文。
这就是《拆解 dsh(三)》(就是权限姊妹篇那篇)里说的“递条子”。宿主给模型递条子,告诉它刚才那步怎么样了。失败时的条子大意是:用 cordis_inspect_self 读诊断信息,在同一个 Plugin 上修复,用 update 追加新版本自主重试。
不是简单返回一个布尔值,而是一段带修复指引的结构化提示。
4.2 四类失败,四类 steer
源码把失败分了四类,每类有专门 steer:
- 渲染失败
:UI 组件抛错,给组件堆栈 + 建议检查 manifest 的 slot 声明。 - Host handler 抛错
:后端 handler 异常,给 handler 名 + 输入参数 + 建议加日志。 - guard 拒绝
:触发了 Proxy 或 guard 的禁止项,给禁止原因 + 对应合法 API。 - 运行失败
:vm 里跑挂了,给堆栈 + 建议用 cordis_inspect_self读运行时状态。
这四类 steer 都是机器写给机器看的,但格式很克制,信息密度高,没有废话。
4.3 去重:同一错误只 steer 一次
还有个 claimRuntimeFailure 函数,按错误内容去重——同一类失败只在第一次发生时递条子,后面不再重复。
否则模型可能陷入“报错—重试—同样报错”的循环,条子反而成了噪声。
4.4 闭环:写 → 挂 → 修 → 追加版本
整个自修循环是这样的:
模型写 v1。 v1 跑挂。 宿主 steer 回具体诊断。 模型读 cordis_inspect_self,定位问题。模型在同一 Plugin 上修,生成 v2。 cordis_plugin_update把 v2 提到 current。
如果用户之前双勾授权,v2 直接替换。单勾则再审批一次。
这就是 self-modification 的灵魂:AI 修自己写的代码,修的还是给自己用的插件。
五、为什么“写”比“装”更好
5.1 审批的是代码,不是包
传统插件模型里,用户安装的是一个包。包里有什么、会不会变、能不能联网,都很难一眼看清。
dsh 里用户审批的是一段具体代码。代码就躺在那儿,用户可以审。沙箱里的行为也被 Proxy 和 guard 约束。
攻击面从“一个未知包 + 任意权限”缩小成“一段可见代码 + 声明过的 inject + 超时回收”。
5.2 不可变 + 双指针 = 审计 + 回滚
Package 不可变,意味着每次修改都留下一个版本。配合 current/next 双指针,你天然有一条审计轨迹。
出问题了?把 current 指回上一个版本就行。这种回滚不需要 git,不需要 npm,就是指针一动。
5.3 报错里带着出路
从 define 的语法预检,到 guard 的“请改用 inject”,再到 steer 的“请用 cordis_inspect_self 诊断”,整个系统都在把错误变成下一步指令。
《拆解 dsh(四)》里说过“报错里带着出路”。这里不是一句口号,而是一套方法论:每次模型写错,系统都给出可执行的修正方向。
5.4 局限也要诚实说
设计虽好,源码注释没有吹。
它列出的局限包括:
- 进程内即失
:重启后插件消失,没有积累,下次要重写。 - vm 只护同步段
:异步逃逸仍有可能。 - 沙箱不是安全边界
:信任模型仍是前提。
这种诚实很重要。说穿了,这不是一个“AI 想干嘛就干嘛”的系统,而是一个“AI 在人的审批和可审计约束下,给自己写临时工具”的系统。
六、结语
dsh 的“万物皆插件”在系列第一篇《拆解 DeepSeek Harness:万物皆插件》里是一个静态架构。走到第五篇,它变成了动态终局:
插件本身,也成了 AI 的工具。
AI 不再只是调用别人写好的能力。它在运行时给自己手搓能力,写砸了再修,修好了再跑,跑完了还能一键回收。
“不是逛市场,是现场手搓一个。”这就是我看到的最像“自举”的插件系统。
对了,上一篇结尾预告的 compaction + spill 后勤篇还在路上,下周补。这篇先插播一个更炸的。
你在自己的项目里,会让 AI 在运行时给自己写扩展吗?如果会,你最担心哪一环?
觉得有用?点个关注,持续看我把 dsh 源码拆完。
夜雨聆风