中文EN

dsh-compat-vet

dev已测通过npmMIT

dsh 插件兼容性体检:把已装插件的 peerDependencies 与当前 dsh 运行时比对,提前指出哪些会在下次启动被预检拒绝加载,并给出可直接执行的中文修复命令;dsh web 顶部横幅可按键隔离、卸载或升级,改动前自动备份、复检不过自动回滚。

下载量 / Downloads
947
依赖体积 / node_modules
0.28 MB
测试时间 / Tested
2026-11-06
DSH 版本 / Version
0.2.2-rc.1

安装

dsh plugin --profile web add dsh-compat-vet

沙箱测试结果

阶段结果
1 安装✓
2 列装✓
3 挂载✓
4 启动—

四阶段在隔离沙箱里跑:安装(有网)→ 列装 → 挂载 → 启动(后三步断网)。

README

dsh-compat-vet

DSH 插件兼容性体检 + 页面上的一键修复 —— 检查已安装插件与当前 dsh 运行时是否冲突,

中文预警并给出可直接执行的命令;装了本插件之后,dsh web 的页面顶部会直接弹出横幅,

照着按键就能当场修掉(不提供「版本豁免」这个选项,理由见 §4④)。

它跟着 dsh 版本自己变,不需要你为任何 dsh 版本改一行代码。(见 §2)


1. 它解决什么问题

dsh 升级后,老插件常常因为 peer 依赖范围不匹配而被启动预检拒绝加载。此时的表现很有迷惑性:

  • 插件还在 dsh plugin list 里、文件也还在 node_modules 里,看起来没坏;
  • 但功能整块消失,或者 dsh 直接起不来;
  • dsh 只会往 stderr 打一行英文告警,而且只在恰好安装那一个插件时才出现 ——

等到某天重启 dsh 才发现问题,已经很难回溯。

本插件把这件事变成主动、集中、中文、可操作的体检 —— 而且在 dsh web 上

直接变成页面顶部的一条横幅,按键就能当场修掉(§4④)。

本机实测(这就是它的目标场景)

当前环境 dsh 0.2.0-rc.2,profile web。dsh 自己的启动日志:

dsh: skipping profile bundle "dsh-cost-meter": Error: Plugin dsh-cost-meter@1.7.35 is
incompatible with dsh 0.2.0-rc.2: peerDependencies {"@deepseek-ai/dsh-credentials":
"^0.1.0-rc.6 || ^0.1.1-0 || ^0.1.2-0 || ^0.1.3-0 || ^0.1.5-0", ...}

本插件对同一环境的结论(dsh-compat-vet / plugin_compat_check 工具):

[阻断] dsh-cost-meter @ 1.7.35
  [阻断] 与 dsh 0.2.0-rc.2 不兼容,启动时会被拒绝加载
         处理办法:
           dsh plugin --profile web add dsh-cost-meter@latest
           dsh plugin --profile web remove dsh-cost-meter
           dsh plugin --profile web allow-version dsh-cost-meter@1.7.35 \
             --dsh-version 0.2.0-rc.2 --accept-risk

两者结论一致;差别在于本插件是集中报告 + 中文 + 带修复命令。


2. 怎么做到「适配任意 dsh 版本」——实测,不是猜

2.1 先说不做什么:不写版本号 if

最省事、也最不靠谱的做法是维护一张表:

// ❌ 本插件刻意不这么写
if (semver.gte(version, '0.1.7-rc.1') && semver.lt(version, '0.3.0')) { … }

这种代码每个 dsh 新版本都要人来改一次 —— 正是要避免的事。而且它基于一个错误假设:

「dsh 版本号 → 能力」这个映射是稳定的、可以事先写死的。实际上不是。

2.2 实际做法:把 dsh 自己的判定函数拿过来跑一遍

dsh 的兼容性预检在 @deepseek-ai/dsh-app-boot 里。插件在启动时:

1. 动态 import() 本机 dsh 安装目录下的 dsh-app-boot;

2. 看它有没有导出 evaluatePluginCompatibility —— 有没有这件事不靠版本号假设;

3. 有的话,用几个合成的假插件清单真调它一次,看它到底会不会报、报什么:

  • 造一个 peer 范围是 >=999.0.0 的清单(任何真实 dsh 都不可能满足)→ 看它拦不拦;
  • 造一个完全没有 peerDependencies 的清单 → 看它管不管(这是它的盲区);
  • 造一个只有 dsh.engines.dsh 的清单 → 看它看不看字段(也是盲区);
  • 找找有没有 compatibility 文件常量 / 读写函数 → 判断有没有版本豁免机制。

于是「本机这个 dsh 有没有预检、会不会拦、有没有豁免」全是实测结论,不是版本推断。

报告里会明确标出结论依据:

适配信息(结论依据,不是假设)
  dsh 版本      : 0.2.0-rc.2|来源:anchor-manifest
  能力探测      : 成功 —— 已就本机这个 dsh 实测校准
  兼容性预检    : 存在且实测有效
    · 会因 peer 不匹配拦下插件 : 会
    · 会检查没有 peer 的插件   : 不会(盲区)
    · 会看非标准版本声明       : 不会(盲区)
    · 版本豁免机制             : 有(compatibility.json)

探测不成功时不下结论:报告改说「本次未能实测」,措辞退到最保守的说法

(会不会拦下它:未能实测),并提示用 --anchor <dsh 的 package.json> 重跑一次。

三种状态是明确的 covered / not-covered / unknown,不存在「猜一个」。

2.3 顺带解决了一个更隐蔽的问题:措辞不能错

老版本 dsh 根本没有兼容性预检。那里插件会照常加载,兼容性风险直接暴露在运行时。

如果在那种版本上还打「启动时会被拒绝加载」,就是说反了 —— 用户会以为自己安全了。

所以告警措辞跟着实测走:

实测结论 · 告警措辞

该版本会拦 · — 与 dsh X 不兼容,启动时会被拒绝加载

该版本不拦 · — 与 dsh X 不兼容(本机 dsh 不会拦下它,会照常加载)

没测出来 · — 与 dsh X 不兼容(本次未能实测本机 dsh 会不会拦下它)

同理,allow-version 这条修复建议只在真的有豁免机制时才给 ——

在没有豁免机制的老版本上给这条命令,等于给一条根本跑不通的命令。

2.4 实测矩阵(本机真的装了 4 个 dsh 版本,逐个跑出来的)

dsh 版本 · 兼容性预检函数 · 会因 peer 不匹配拦下 · 版本豁免机制 · 会看 dsh.engines.dsh 等野生声明 · 会看没有 peerDependencies 的插件

0.1.1-rc.2 · 没有 · 不会 · 没有 · 不会 · 不会

0.1.5-rc.3 · 没有 · 不会 · 没有 · 不会 · 不会

0.1.7-rc.1 · 有且实测有效 · 会 · 有(compatibility.json) · 不会 · 不会

0.2.0-rc.2 · 有且实测有效 · 会 · 有 · 不会 · 不会

这张表由 npm test 每次自动重测(test/versions.test.mjs),不是文档里抄下来的死数据:

将来你机器上多一个新版本 dsh,测试会自己把它加进来跑。遇到完全不认识的未来版本时,

测试只做自洽性检查并打一个 ⚠️ 提示,不会因此失败、也不需要谁来更新代码。

2.5 真机启动验证:光有结论不够,要真启动一次

单元测试里的 cordis 上下文是捏出来的假货。**「插件在真 dsh 启动流程里到底会不会被调用、

输出长什么样」只能真启动一次才算数**。所以还有一个真机验证脚本:

npm run verify:boot        # 把本机所有 dsh 版本各真启动一次

它在一个临时的 DSH_HOME 里、用 dsh 自带的 headless 模板起一个最小 profile,

塞进本插件 + 一个「对任何版本都不兼容」的金丝雀插件,然后真的把这个 dsh 跑起来,

核对四件事:

dsh 版本 · 插件真被调用 · 告警措辞 · dsh 实际是否拦下金丝雀 · 探测结论 · 一致

0.1.1-rc.2 · ✅ · 会照常加载 · 否 · 不会拦 · ✅

0.1.5-rc.3 · ✅ · 会照常加载 · 否 · 不会拦 · ✅

0.1.7-rc.1 · ✅ · 会被拒绝加载 · 是 · 会拦 · ✅

0.2.0-rc.2 · ✅ · 会被拒绝加载 · 是 · 会拦 · ✅

最后两列是重点:「探测说会拦」必须和「dsh 真的把金丝雀跳过了」对得上。

这是「实测校准」这四个字唯一的硬证据 —— 校准自己错了,上面所有结论都是假的。

这个脚本不碰你的真实环境:全程在临时 DSH_HOME 里跑、不监听端口、

不动 ~/.dsh、不影响正在运行的 Web GUI。

2.6 真机验证抓出来的 6 个 bug(这就是它存在的理由)

这几个都是单元测试全绿也照样漏掉的,只有真启动才暴露:

# · 问题 · 后果 · 修法

1 · ctx.profileContext 这种服务属性读取会抛异常(cordis 在插件没 inject 该服务时抛 cannot get property "profileContext" without inject;0.1.5-rc.3 实测如此) · 整个启动自检炸掉,又被 apply() 的兜底吞掉 —— 插件在那个版本上什么都不做,且一声不吭 · 改走 serviceOf(),逐个来源 try/catch,读不到就换下一个来源

2 · 0.1.1-rc.2 上根本没有 profileContext 服务,插件退回继承来的 DSH_PROFILE_DIR · 张冠李戴:在一个 web 会话里 dsh --profile tui,子进程继承的环境变量仍指向 web,插件就去体检 web 了,还理直气壮地报警 · 从进程自己的命令行反推 --profile,并让「显式指定 / 运行时事实」压过 DSH_PROFILE_DIR

3 · 定位 dsh 安装目录只扫 PATH · 本机 npx 缓存里躺着 4 个 dsh,可能定位到不是正在运行的那个 —— 「实测校准」测错了对象,结论看着像实测,其实测的是别人 · 新增 anchorFromProcessEntry():从当前进程入口脚本反推,权威性最高

4 · CLI 的 --home / --profile 被环境里的 DSH_PROFILE_DIR 悄悄盖掉 · 命令行参数看着生效了、其实没生效,检查的是另一个 profile · 「显式指定」的优先级提到环境变量之前

5 · 「复检不过就回滚」把一次正确的修复判成了失败:setBundleEnabled() 返回 restart-required,紧接着问 listBundles(),拿到的还是本进程的旧结论 · 于是一次已经正确落盘的隔离被自动回滚 —— 表面上"很安全",实际上这个功能在真机上永远修不成功。单测全绿也发现不了(假 service 不会"慢半拍") · 判定依据改成「改动真的落盘 + 本插件复检干净」,dsh 本进程的即时结论只作为信息如实上报;并且 listBundles() 改为轮询若干秒,不采样一次就下结论

6 · 有阻断问题时,「快要不适配」的预警整块看不见 —— 横幅只渲染 problems[0],"其余问题"也只列 problems,从不带 upcoming · 用户最想提前知道的那条消息("dsh 升到 0.3.0 时 dshmarket 会被拦下")被当前那条更紧急的盖住 —— 真机上两者是同时存在的 · 阻断视图底部加一行预警摘要 + 「查看 / 预防性处理」切换视图,两者同屏可达

第 5 个是修复功能的真机验证抓出来的(npm run verify:repair);

第 6 个是用真实数据预览横幅抓出来的(npm run preview)——

单测用的假报文里 upcoming 一直是空的,所以"两种情况同屏"这件事从来没被测到过。

这两个例子说明为什么这个项目的验证要分四层:单元测试 → 真机启动 → 真机改造 + 再启动一次 →

用真实数据渲染一遍界面。

顺带把「一个坏来源拖垮整条链路」这件事也堵了:所有可选来源都各自 try/catch 降级

(safe()),任何一处出错只会退到下一个来源,不会让自检整体失能。


3. 相对 dsh 内置检查,多查了什么

dsh 的兼容性预检只检查 peerDependencies 中以 @deepseek-ai/dsh / @deepseek-ai/dsh-

开头的键,而且**插件只要没有 peerDependencies 字段,就直接判定为兼容*

…(truncated)

以上内容来自 npm 上的 README,我们只做格式化,不改内容。

版本

npm 上共发布:3,最后更新 2026-09-30

链接

GitHub · Homepage · npm

同类插件