ARTICLE · 1336689
0.2.2 升级:插件作者先跑这 4 条命令
本文看点
01版本坐标:五个包同时换版
02官方明说的 7 处迁移
034 条命令自查 + 该不该跟
约 3000 字 · 阅读 7 分钟 · 版本评估
某天 CI 一跑,插件全绿。
编译过、启动过、日志干干净净。可用户点开设置,改完保存,什么都没发生。
我们查了两天。代码没坏,配置也没坏——是内核换代了。

图 1 · 0.2.2 升级:插件作者先跑这 4 条命令
这件事最难受的地方不是报错,是它不报错。
它发生在 latest悄悄指向 0.2.2-rc.1 之后。
也就是说,你什么都没做,npm i @deepseek-ai/dsh装到的就是新线。
新机器、重跑的 CI、同事 clone 下来配的环境,都走这个入口。
所以这篇不是「要不要升」,是「升之前先把哪些地方摸清楚」。
01五个包一起换版,Node 门槛抬到 24.9
判断一个版本要不要认真对待,先看它动了几个包,而不是看版本号第二位有没有进。
@deepseek-ai/dsh从 0.2.1-rc.5 走到 0.2.2-rc.1。
Cordis 家族四个包同期换代:
▸cordis4.0.5 → 4.1.0;
▸cordis-plugin-loader1.0.6 → 1.1.0;
▸cordis-plugin-include1.0.10 → 1.1.0;
▸cordis-plugin-timer1.1.7 → 1.2.0。
四个包一起动,加上 dsh 本体,一共五个。
版本号看着只进了第三位,实际是一次横向换代。

图 2 · 版本坐标:dsh 加 Cordis 家族四个包同期换版
为什么「同时」值得单独说?因为这四层不是并列关系。
cordis是底座,管 Fiber、事件、配置;loader管条目怎么装进来、什么时候写回;include管包怎么被引入。
三者叠在一起,就是「插件与内核之间的那段契约」。底座挪位,上面跟着挪,插件恰好骑在缝上。
单个包升次版本,你只测那个包;三个一起升,等于契约整体重排了一遍。
这里有个反直觉的判断:次版本位不一定是安全位。
4.0.5 → 4.1.0、1.0.6 → 1.1.0,全是次版本位。但回调少了一个参数、写盘方法改了名——这些都在次版本里发生。
还有两个坐标:rc.5 是 10 月 29 日,rc.1 是 11 月 4 日;cordis4.1.0 比上层 rc.1 早 1 小时 9 分落地。
时间线上,alpha.2(10-28)→ alpha.3(11-03)→ rc.1(11-04),北京时间 7 天。
发布时间在 npm 上都有记录,随手可回查。

图 3 · 时间线:alpha 到 rc,北京时间 7 天
门槛也抬了:0.2.2-rc.1的 engines.node要求 >= 24.9.0。
Node 太低会直接装不上或者起不来——这类失败反而好认,它报错,不静默。
所以「升级后起不来」先核 Node 版本,别一上来就怀疑插件。
顺带把 engines.dsh说清楚:写 >=0.1.0-rc.6 <0.3.0。
在 includePrerelease: true的口径下,它对 0.2.2-rc.1 判 true。
但默认 semver 会把预发布排除在外,默认口径下这条判 false。测试时别拿错口径。
结论:版本范围拦不住 0.2.2。semver 只做算术,不做接口检查。真正拦你的是接口形状。
02官方点到要动的 7 处,我先挑代价高的说
官方 release notes 的「其他变更」一共 29 条。
我按「要不要动代码或配置」筛出 10 条:7 条放正文,3 条属于自部署和集成侧,单列。
先讲最贵的三条。
第一条:工具接口迁移。
原文里直接写「插件需迁移」的只有这条:tools.resolve/ executable和提示词组装接口。
它同时动了三处:工具能不能被选中、选中能不能跑、模型看到的工具描述长什么样。
一个工具从「被声明」到「被模型看见」再到「被执行」,每一段都可能换形状。
这不是改个函数名能解决的:三处一起动,回归面是整条工具链。
第二条:两个旧开关作废。
配置项 tools.mode和环境变量 DSH_TOOLS_MODE都要迁移。
配置项好搜,环境变量难搜——它可能躺在启动脚本、CI 变量、桌面端快捷方式里。
官方说的是迁移到「配置 overlay 启用」:从「一个全局开关」变成「一份作用域配置」。
差别在:全局开关只回答开不开,overlay 要回答「在哪个 Agent 作用域开」。
如果你的插件靠读全局开关推断自己该不该提供工具,升级后它得先知道自己在哪个作用域——这是新增的上下文,不是换了字段名。
第三条:取消不等于失败。
PTC 里用户主动取消的 run_code,返回从 CODE_RUN_FAILED改成 ABORTED。
这不是文案变了,是分类变了。
如果你的统计、重试、告警按 CODE_RUN_FAILED判「代码跑挂了」,升级后用户点一次取消就记一次失败——数据脏了,但没有任何报错。
更麻烦的是重试:取消是用户的意图,重试是对抗用户意图。分类没更新,用户按了取消,程序默默再跑一遍。
剩下四条,一条一句:
▸Session Projection 注册改成异步,要「启动时持有」,同步写不会编译失败,只在时间窗里出事;
▸模型选择由 agent.model决定,agent/request不能再改路由;
▸工作树插件已合入主功能,要从 profile 的 dsh.profile.bundles手动摘掉;
▸Cordis / Loader / Include 三包同步,统一了配置访问、条目变更和持久化。
工作树这条最隐蔽:它不会自动清掉。
升级脚本跑完、看起来一切正常,残留还在。
dsh-experimental-tool-worktree还在 npm 上(latest 0.2.1-alpha.2),但功能已经合进主功能。
包还在,不等于还该装着。

图 4 · 七个迁移点:接口、开关、语义、注册、路由、配置
另外 3 条单列,因为它们落在自部署和集成侧,不是写插件的人每天碰的:
▸组合包改由 @deepseek-ai/dsh-official-bundles导出(latest 0.2.2-alpha.2),自定义集成要改 app-boot 的导入;
▸自部署 PTC 通道改 birpc,SSH 协议从 2 升到 3,官方用的是「必须同步升级」;
▸Web Fetch 不再拒绝非公网地址,网络边界交给部署方自己收。
03官方没说的第 8 件:源码把内部契约拆了
第 8 件是我读公开源码补的,不是官方宣布的 breaking 范围。
对比 dsh-v0.2.1-rc.5→ dsh-v0.2.2-rc.1。
在 vendor/cordis/src/和 vendor/loader/src/里,看到三个面。
面 1:事件签名变了。
internal/update的回调从 (config, noSave, next)变成 (config, next)。
noSave没了,持久化拆成新事件 internal/commit。
JavaScript 不校验回调参数个数,所以老代码照样能跑进来,然后分两种结局。
调第三个参数 next()的,新签名下是 undefined,当场 TypeError——这种还好。
只拿 noSave做判断的才要命:noSave位置上拿到的是 next函数,真值恒为真,分支悄悄走错。
表现是什么?该保存的时候没保存,或者保存时机整体偏移。
第二种才是真正的静默:本地大概率复现不出来,只在特定条目变更顺序下露头。
面 2:配置分层。
Fiber 现在区分 raw config 和 effective config,新增 resolveConfig()、effectiveConfig。
以前「你拿到的配置」和「实际生效的配置」是同一个对象,现在不是。
启动早期缓存了配置对象的插件,升级后缓存的可能只是 raw 层,而 effective 层已经变了。
update()也变成可等待,不等就等于把「已提交」当成「已生效」。
配置分成两层之后,「写回哪一层」才需要一个专门的提交动作。
面 3:Loader 写盘换名字。
EntryTree.write()改成 commit(change),参数是 EntryChange。
patch-context的 next也从 () => void变成 () => Awaitable<void>。
只改方法名不够,调用侧还得先学会构造 EntryChange。
凡是自己调 Loader 写盘、或者自己实现 patch context 的集成,这一整条链都要过一遍。
规模上也不是小修。同一段 tag 区间:
events.ts+81/-62,fiber.ts+124/-58。
reflect.ts+37/-19,utils.ts+28/-21。
一句话总结:官方说「统一」,源码里看到的是「拆开」——持久化拆成事件、配置拆成两层、写盘拆成 commit。
隐含约定变成显式接口,所有依赖旧约定的代码,都会在拆开那一刻失去支撑。
04先跑这 4 条命令
8 件事拆完,落到动作只有一个:先确认自己碰没碰。
在插件仓库根目录跑这四条:
grep -rn "tools\.mode\|DSH_TOOLS_MODE\|tools\.resolve\|tools\.executable" .
grep -rn "internal/update\|internal/commit\|internal/config" .
grep -rn "EntryTree\|patch-context\|\.write()" .
grep -rn "agent/request\|CODE_RUN_FAILED\|ABORTED" .--include按你仓库的语言补:ESM 加 .mts,有构建产物别漏 .cjs。
漏了后缀,grep 会安静地返回空——最像「没问题」的那种结果。
四条判读方式不一样,别一把梭:
▸第 1 条命中,按 release notes 迁移,属于「好认」那类;
▸第 2 条命中,必须在新版上跑一遍真实条目变更,重点看配置有没有写回;
▸第 3 条会误报,大量写文件都叫 write(),要确认是不是 EntryTree;
▸第 4 条要分清 agent/request是读还是改路由。
还有三类 grep 扫不到,必须人工看:环境变量 DSH_TOOLS_MODE、profile 里的 dsh.profile.bundles、提示词模板。
这三类恰好是「代码全绿但行为不对」的高发区。

图 5 · 四条 grep 的示意输出:命中不等于坏,但每条都要给结论
一句提醒:命中不代表一定坏。这四条是缩小范围,不是判决。
把 grep 当结论,会得到两种错误:一种是把能跑的插件按住不敢升,一种是把静默失效的插件放过去。
真正的判据是升级后跑一遍,看行为有没有变。
05跟不跟,三个判断题
第一题:你是主动跟,还是被动跟?
latest已经是 0.2.2-rc.1,走 latest 的安装路径都在新线上。
「被动跟版」的入口比想象的多:CI 里的 npm i、容器镜像重建、同事新配的环境。
想真正不跟,只有一个办法:把版本号写死到你验证过的那一条。
第二题:你碰没碰那些面?
工具接口、PTC 与 tools.mode、取消分类、Session Projection、组合包导入、模型选择、工作树残留,再加源码那三面。
碰了任意一个,发版前就要回归;一个都没碰,风险主要落在 Node 24.9,属于「装不上就报错」。
按代价排:源码内部事件最贵(静默),agent/request次之(静默但不影响启动)。
tools.mode再次(不生效),Session Projection 再次(窗口期),工作树代价低(改配置)。
贵的先查。
第三题:你是不是自部署?
是的话,SSH 协议 2→3 要先看:版本错配的表现是连不上,而不是一条清晰的版本错误。
排查时先核两边协议版本,别先去查网络。
什么时候先别跟:手上没有一条可回滚的具体版本号时,别跟。
这不是保守,是基本盘——出问题时你得能退回到跑过的版本,而不是在事故里现找。

图 6 · 跟版决策:三个判断题,一条可回滚的退路
06三条能直接用的建议
一、按风险分三层,别停在两类。
会当场报错的:Node 版本不够,装不上或者起不来。
不生效但不报错的:tools.mode失效、agent/request改不了路由、bundles 里残留工作树插件。
完全静默的:internal/update参数错位、internal/commit没监听、配置缓存停在 raw 层、取消被记成失败。
回归清单按这个分层排,不是按 release notes 的顺序排——官方顺序按改动面排,不是按你的风险排。
二、四条 grep 进 CI,再加一次真实加载。
grep 负责「有没有碰」,真实加载负责「碰了有没有坏」。
在 0.2.2-rc.1 上起一次,故意触发一次配置条目变更,看有没有按预期写回;再故意取消一次 PTC 执行,看日志里记的是取消还是失败。
注意是「进 CI」而不是「写进文档」:文档里的清单三个月后没人跑,CI 里的检查每次提交都跑。
**三、版本范围写上下界,internal/*别当稳定 API 用。**
engines.dsh两侧都写,比如 >=0.1.0-rc.6 <0.3.0(实测对 0.2.2-rc.1 判定为 true)。
internal/update、internal/commit这类带 internal前缀的事件,Cordis 4.1 已经证明它们会改签名。
非用不可,就在一个地方集中包一层,写清依赖的版本与行为,让下次升级只改那一处。
升级不怕 breaking,怕的是不知道自己踩了哪条。
完整迁移清单见 CSDN,点阅读原文。
部分内容由 AI 辅助生成,事实与数据已人工核验。















































































