DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

liujianqiao701 /

liujianqiao701/dsh-compat-vet

Verified

DSH(DeepSeek Harness)插件兼容性体检:预警下次启动会被拒绝加载的插件,并在页面上按键一键隔离/卸载修掉它(改前自动备份、失败自动回滚)。Plugin compatibility vet for DeepSeek Harness.

★ 0 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHubProject homepage
READMESource: main@926be60e

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 字段,就直接判定为兼容 (源码里那句 if (!Object.hasOwn(fields, "peerDependencies")) return void 0)。

所以下面这些情况 dsh 全程不看(「会不会发现」一列是逐版本实测的,见 §2.4):

检查项 说明 dsh 是否会发现
peerDependencies 不满足 peer 范围与运行时不符 ✅ 会发现(仅 0.1.7-rc.1 及以后;老版本连这个都不看)
非标准位置的 dsh 版本声明 dsh.engines.dsh / dsh.compatibility.dsh / dshhub.compatibility.dsh —— 三种野生写法实测都存在 ❌ 4 个版本实测全部不会
完全没有 peerDependencies 的插件 无法从元数据判断兼容性,升 dsh 时最需要人工回归 ❌ 4 个版本实测全部不会
engines.node 不匹配 Node 版本不符可能导致语法/API 不可用 ❌ 不会
bundles 里引用了但没装的包 dsh 加载器会打印 skipping profile bundle 并跳过 ⚠️ 加载器会打日志,但那不是兼容性预检,本插件只标「未能实测」不敢替 dsh 下断言
「快要不适配」(可预测的版本失配) 声明范围现在能满足,但 dsh 的下一个小版本 / 大版本就会掉出去 —— 也就是「下一个 dsh-cost-meter」 ❌ 不会(dsh 只判当前版本)。本插件按声明范围推算,并告诉你坏在哪个版本(例:dshmarket@1.66.6 会在 dsh 0.3.0 失配)
装了但不在 bundles 里 装了却不激活,白占依赖 ❌ 不会
profile 声明范围 vs 实际安装版本不一致 lock 与 manifest 漂移 ❌ 不会
读不到插件清单 包损坏 / 安装不完整 ❌ 不会
版本豁免状态 区分「已知不兼容但接受风险」与「真的没问题」 ⚠️ 部分(且老版本没有豁免机制)

报告里每条发现都会标出「dsh 自己会不会发现」,三态且带依据:

[dsh 内置检查:同样会发现|依据:dsh 兼容性预检(实测校准)]
[dsh 内置检查:不会发现|依据:dsh 兼容性预检(实测:该版本没有这个检查)]
[dsh 内置检查:会不会发现未实测,不下断言|依据:…]

实测例子:@linxin666/dsh-perf@0.3.14 声明了 dsh.engines.dsh = ">=0.1.2-rc.1", 但没有任何 dsh peer 依赖 —— 所以 dsh 侧从未校验过它。本插件会读这个字段。


4. 四种用法

① 装成插件:启动自动预警 + 会话内工具(推荐)

装进 profile 后,每次 dsh 启动会自动体检,有问题就往 stderr 打一段中文告警:

dsh-compat-vet: 检测到 1 个插件与 dsh 0.2.0-rc.2 存在兼容性问题:
  [阻断] dsh-cost-meter@1.7.35 — 与 dsh 0.2.0-rc.2 不兼容,启动时会被拒绝加载
      建议:dsh plugin --profile web add dsh-cost-meter@latest
  完整报告:运行 `dsh-compat-vet`,或在会话里用 plugin_compat_check 工具。

同时注册一个 plugin_compat_check 工具,会话里随时可以复查:

「帮我看看插件兼容性」→ agent 调用 plugin_compat_check → 返回完整中文报告与修复命令。

还有一个系统提示词段落:只要存在未解决的兼容性问题,它就会被注入到模型的上下文里, 模型会在你提到插件/报错/功能异常时主动提醒,不需要你想到去问。

② 独立 CLI:dsh 起不来时唯一能用的入口

插件只在 dsh 成功启动之后才跑得起来;而版本冲突最坏的后果恰恰是 dsh 起不来。 所以同一个引擎同时提供独立 CLI —— 它不依赖 dsh 的任何模块:

dsh-compat-vet                  # 检查默认 profile($DSH_PROFILE 或 web)
dsh-compat-vet --profile web    # 指定 profile(会压过环境里的 DSH_PROFILE_DIR)
dsh-compat-vet --home <目录>     # 指定 ~/.dsh 所在目录
dsh-compat-vet --anchor <路径>   # 指定 dsh 的 package.json(校准要用的那个)
dsh-compat-vet --json           # 输出结构化 JSON
dsh-compat-vet --all            # 连没有问题的插件也列出来
dsh-compat-vet --quiet          # 只输出一行结论(给脚本用)

退出码:0 没问题 / 1 发现问题 / 2 体检未完成。可以拿来做启动前的守门脚本。 探测失败时会把「每个字段是从哪个来源推出来的」一并打印,方便定位。

③ 直接引用引擎

import { auditProfile } from 'dsh-compat-vet/audit'
import { auditProfileProbed } from 'dsh-compat-vet/audit'   // 会先做实测校准
const report = auditProfile({ profileDir, profileName: 'web', installAnchor })

④ 页面上的一键修复(web profile 专属)

装了插件、用 dsh web 打开界面就会看到顶部横幅 —— 不用敲命令,直接按键修:

┌────────────────────────────────────────────────────────────────────────────┐
│ [插件冲突] dsh 0.2.0-rc.2 与 dsh-cost-meter@1.7.35 冲突,它下次启动会被 dsh   │
│            拒绝加载。                                                  [×] │
│ 与运行时不兼容 —— 要求 ^0.1.0-rc.6 || ^0.1.1-0 || … ,当前 0.2.0-rc.2       │
│ [1] 隔离   [2] 卸载   [3] 升级   [R] 重新体检                              │
│ 不在输入框时可直接按数字键;正在输入时按 Alt+数字                          │
└────────────────────────────────────────────────────────────────────────────┘
按键 动作 可逆性 能保证消除冲突吗
1 隔离(默认) 用 dsh 自己的服务把它从 dsh.profile.bundles 摘掉,包和依赖都留着 随时加回去 ✅ 能
2 卸载` 走 dsh plugin remove,彻底删掉 要重装 ✅ 能
3 升级` 装 latest(需要联网) —— ❌ 不能保证
R 立刻重新体检 —— ——
Esc 收起(同一批问题不再重复弹;问题集合变了会重新弹) —— ——

补充几点:

  • 按键不会抢你的输入:正在输入框里打字时裸数字键原样放过,只有 Alt+数字 才触发。
  • 「快要不适配」不会被「已经冲突」盖住:真机上两者会同时存在 (dsh-cost-meter@1.7.35 已阻断 + dshmarket@1.66.6 将在 dsh 0.3.0 失配), 所以阻断视图底部会多一行预警摘要 + 一个「查看 / 预防性处理」入口,点进去是预警视图, 可以「预防性处理」(把它提升成可操作的问题行,数字键继续有效)。 想先看会显示成什么样:npm run preview(只读,用你真实 profile 的数据渲染一遍横幅,不发修复请求)。
  • 结果直接显示在横幅上(含每一步做了什么、是否已落盘、要不要重启、以及失败时的「已自动回滚」); 修完之后横幅不会立刻消失,免得你以为"什么都没发生"。
  • 改动前自动备份 profile 的 package.json / cordis.patch.yml / compatibility.json 到 <profile>/.compat-vet-backup/<时间戳>/;改完自动复检,保证类动作复检不过就自动回滚。
  • 刻意不提供「豁免(allow-version)」这个选项:豁免只是"接受风险继续加载", 它不会让 dsh 下次启动不被拦下 —— 与本插件的目标正好相反。

⚠️ 接口只认本机回环(127.0.0.1/::1 + 同源校验)。所以从局域网地址打开的 GUI 会被拒绝(403),修复功能不可用但页面其余部分照常。这是刻意取舍:这条接口会改 profile 配置, 宁可不可用也不能给跨站请求留门。


5. 安装 / 卸载

从 npm 安装(给使用者的那条路)

已发布到 npm:dsh-compat-vet(latest = 0.3.1)

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

源码仓库:liujianqiao701/dsh-compat-vet

「陌生人装上能不能用」是实测过的,不是推断:node scripts/verify-tarball-install.mjs --from-npm dsh-compat-vet 会在空白 profile 里用 dsh 自己的安装命令装registry 上那个包、真启动、取回页面横幅代码、再卸载干净(16 项)。 单测跑的是源码目录,而别人装到的是 npm 包 —— 两者不是一回事(files 少一项就够让单测全绿而别人装完是坏的)。

关于名字(改过两次): 最初叫 dsh-plugin-doctor —— 但这个名字在 npm 上已经被别人占用(作者 Xrainsmile,0.1.1), 生态里另有两个同名插件,所以第一次改成了 dsh-compat-doctor(并以此名发布过一次)。 随后发现 doctor / guard / health / sentinel 这一族名字在插件目录里已经用滥 (dsh-doctor 3 条、dsh-plugin-doctor 3 条、dsh-plugin-guard 2 条,dsh-plugin-sentinel、 dsh-boot-guard、dsh-startup-guard、dsh-conflict-guardian 等均已占用), 而 compat 在 4392 条目录里只被用过 2 次且都不是这个用途 —— 因此最终定名 dsh-compat-vet。

名字牵连的地方(客户端的模块 id 必须等于包名,否则 dsh 路由不到横幅)都已对齐: package.json 的 name/bin、lib/client.js 的模块 id、cordis.patch.yml 的 name、 lib/index.js 的 name 导出、页面接口前缀、横幅 DOM id、存储键、备份目录名、全部测试与文档。

上架进度与投稿文件见 publish/:收录到插件市场(dshmarket 的清单来自 awesome-dsh-plugin) 需要往那边提一个 PR,加一个 YAML 条目;npm 发布与收录是独立的, 发不发 npm 都不影响收录,收录也不依赖 npm。

从源码安装(本机开发就是这样装的)

# link: 指向源码目录,改完代码重启 dsh 即生效
dsh plugin --profile web add link:D:/playwright-AI/playwright/dsh-compat-vet

# 卸载(两种安装方式都用同一条命令)
dsh plugin --profile web remove dsh-compat-vet

dsh plugin add 会自动把包名同时写进 package.json 的 dependencies 与 dsh.profile.bundles 两处(本机实测:两处都已按预期更新,且原有 8 个插件未受影响); remove 由同一套命令包装负责,会同步清理 bundles 条目。

本插件自己的 dsh.engines.dsh 写的是 "*" —— 一个声称「适配任意 dsh 版本」的插件 不该反过来给自己设版本门槛。

⚠️ Windows 上有个 pnpm 自己的坑(与插件无关,但会撞到): 同一个目录如果被长短两种路径写法混用(...\liujianqiao\... 与 ...\LIUJIA~1\...), pnpm 会报 ERR_PNPM_UNEXPECTED_VIRTUAL_STORE 并拒绝 add/remove。 照它提示先在 profile 目录里跑一次 pnpm install 即可恢复。 (本插件的验证脚本因此先把临时目录解析成长路径再用——见 scripts/verify-tarball-install.mjs。)


6. 设计取舍(为什么代码长这样)

零 @deepseek-ai/* 依赖

这不是洁癖,是被逼的。link: 安装的插件,其真实路径在 profile 之外, Node 的 ESM 解析从真实路径向上找 node_modules —— 找不到 dsh 自带的包。 实测 profile 的 node_modules/@deepseek-ai/ 下只有 cosmokit 和 schemastery, dsh-tools / dsh-app-boot / dsh-system-prompt / semver 全都不在。

所以:

  • 工具定义手写 JSON Schema,不走 defineTool(它只是把 spec 转成 JSON Schema);
  • SemVer 范围求值是自己实现的(lib/semver.js),不用 semver 包;
  • 服务用 ctx.get() 机会性获取并降级,inject 留空 —— 保证在任何残缺环境下插件都能加载起来,而不是「因为缺个服务就连带不加载」。
  • 探测 dsh-app-boot 用的是动态 import() + 绝对路径,不是静态 import: 静态 import 在本机根本解析不了。

体检工具不能自己成为故障源

apply() 全程 try/catch,启动自检、提示词段落、工具注册各自独立兜底, 任何一步失败只写一行 stderr 日志,绝不向上抛。系统提示词段落的 text() 失败时返回空串(空段落会被 dsh 丢弃,不占上下文)。

启动自检是同步做的:模块加载时(ESM 顶层 await)就把校准做完, apply() 里不再等待任何异步 —— 否则像 dsh web --help 这种「挂载完就退」的路径 可能在告警写出来之前就退出了。

运行时版本未知时不下结论

拿不到 dsh 版本就不做版本比对 —— 拿空串去比会把每个插件都误报成不兼容。 这种情况如实报「本次未判定版本兼容性」,并提示用 --anchor 恢复完整检查。 同理,定位不到 dsh 安装目录时,@deepseek-ai/* 的 bundle 只报「无法验证」, 不误报成「缺包」。

只读(默认)/要写就必须是你点的

体检、命令行、会话内工具全都是只读的:不改 profile 配置、不装不卸、不写 compatibility.json、 不联网;修复命令只打印,由你决定执不执行。

唯一的写路径是你在页面横幅上明确按键选择之后的那一次修复,而且:

  1. 走 dsh 自己的 pluginManager 服务,不自己写文件(它加 profile 文件锁、原子写、还会协调 HMR; 而 dsh 内部那个 writeProfileManifest 是无锁非原子的 writeFileSync—— 自己写就是绕过锁,万一插件页同时在写就会互相覆盖);
  2. 改前自动备份,改后自动复检,保证类动作复检不过自动回滚;
  3. 接口只认本机回环 + 同源,请求体上限 4 KiB;
  4. 只有完全没有 pluginManager 的老宿主才退回自己原子写,并在结果里明确说明这一点。

7. 验证情况

npm test               # 134 个用例(含 4 个真实 dsh 版本的实测对拍)
npm run test:fast      # 跳过版本对拍,只跑纯逻辑
npm run verify:boot    # 真机启动验证(把本机每个 dsh 版本各真启动一次)
npm run verify:repair  # 真机「一键修复」验证(隔离环境里真修一次 + 再启动一次)
npm run verify:tarball # 打包安装验证:npm pack 出来 → dsh 自己装 → 真启动(21 项)
npm run preview        # 只读:用真实 profile 的数据把页面横幅渲染出来给你看

只想验线上那个包能不能被陌生人装上(不走本地源码):

node scripts/verify-tarball-install.mjs --from-npm dsh-compat-vet@beta
# 用 dsh 自己的 `dsh plugin add` 装 npm 上已发布的版本 → 空白 profile → 真启动 → 取横幅代码 → 卸载
测试文件 覆盖
test/semver.test.mjs 与本机 dsh 自带的真 semver 穷举对拍 1312 组 satisfies + 41 组 validRange,零差异(两种 includePrerelease 模式都测);SemVer §11 优先级;反直觉语义的回归断言
test/adapt.test.mjs 定位与上下文推导的全部来源与优先级;服务属性读取抛异常时不能失能(§2.6 的 bug 1);命令行压过环境变量(bug 4);从进程入口反推 dsh(bug 3);从命令行反推 profile(bug 2)
test/audit.test.mjs 每种发现各一个用例;豁免文件的真实格式(裸 map,不是 {exemptions:…} 包装);同一份插件代码在不同能力下给出相反结论(自适应核心);未实测就不下断言;损坏输入不抛
test/plugin.test.mjs 工具定义形状、execute/render、提示词段落内容;残缺上下文下 apply() 不抛异常;启动告警是同步的;配置开关;拿不到 profile 时报可读错误;页面路由挂载 / 释放 / 关掉 / 老 cordis 安静降级 / pluginManager 延迟可取
test/upgrade.test.mjs 「快要不适配」的预测:用两个真实插件声明的范围(dsh-cost-meter 的长 `
test/repair.test.mjs 三个动作打到 dsh 的对的方法上;备份/还原往返;兜底直写是 2 空格 + 结尾换行;拒绝码与人话翻译;「已落盘但 dsh 还没重估」必须判成功(§2.6 的 bug 5);复检不干净 / 没落盘 / 服务拒绝 → 回滚
test/routes.test.mjs 回环与同源围栏(跨站、伪造 Origin、非法 Host 全拒);请求体超限;状态报文里插件版本号与 dsh 版本号都在;修复请求的参数校验与作业生命周期
test/client.test.mjs 用最小假 DOM 跑真的 client.js:横幅同时写明插件版本号与 dsh 版本号;1 映射到隔离;在输入框里裸数字键不被抢走、Alt+数字 有效;Esc 记住指纹不重复打扰;接口挂了安静退场;客户端产物没有 import
test/versions.test.mjs 对上表 4 个真实 dsh 版本逐个实测:预检有无、会不会拦、豁免机制、三个盲区;与 dsh 自己的豁免文件读取器对比 18 组样本,结论全部一致;未来新版本只做自洽检查、自动纳入

真实环境端到端已确认:

  • dsh --profile web --dump-config → 组合树里出现 - id: compat-vet / name: dsh-compat-vet;
  • 真机启动(npm run verify:boot)→ 4 个 dsh 版本上插件都被真正调用、措辞全部正确、 且「探测结论」与「dsh 真实是否拦下金丝雀」逐版本一致(见 §2.5);
  • 真机「一键修复」(npm run verify:repair)→ 在一个临时 DSH_HOME 里造一个必然不兼容的插件、 真启动 dsh web、走 HTTP 接口修一次、再真启动一次,全部通过: 状态报文里带着插件版本号与 dsh 版本号 → 修复走的是 dsh 自己的 pluginManager → 改动真的落盘 → 第二次启动不再跳过它、启动告警消失、体检 0 冲突;
  • 客户端横幅真的会被浏览器加载:dsh 的启动注入里带着 dsh-compat-vet/client.js(真实 rev 从页面里取),按该 URL 取回 HTTP 200、 41 万字节,内容就是本插件的横幅代码;
  • 打包产物装得上、装完能用(npm run verify:tarball,21 项)→ npm pack 出真 tarball、 用 dsh 自己的安装命令装进空白 profile、真启动、取到横幅代码、再卸载干净;
  • npm 上已发布的那一个包也一样能用(--from-npm dsh-compat-vet@beta,16 项)→ 这条路就是陌生人点「一键安装」走的路:装出来的是 registry 上的真拷贝 (node_modules/.pnpm/dsh-compat-vet@0.3.1/…,不是指回本机源码目录), 启动后体检 0 冲突、横幅代码 HTTP 200、卸载干净;
  • 用真实数据把界面渲染了一遍(npm run preview)→ 你的 profile 上横幅会同时报出 dsh-cost-meter@1.7.35(已阻断)与 dshmarket@1.66.6(将在 dsh 0.3.0 失配), 并给出了 [1] 隔离 [2] 卸载 [3] 升级;30 个依赖里只有 dshmarket 一个触发预警,不是噪音;
  • CLI 在真实 profile 上 → 9 条、阻断 1(dsh-cost-meter@1.7.35)、退出码 1;
  • 会话内 plugin_compat_check 工具实际调用成功,与「DSH 插件兼容性问题」提示词段落 均已在该会话生效;
  • 结论与 dsh 自身的 skipping profile bundle "dsh-cost-meter" 日志逐条一致。

阶段性验证报告:report/页面预警与一键修复-验证报告-20260930.md(含逐条要求对照、真机输出、诚实边界)。


8. 本次改过什么 / 如何回滚

本插件在开发过程中被装进了用户的在用 profile ~/.dsh/profiles/web。改动与回滚:

改了什么(2026-09-30):

文件 改动
~/.dsh/profiles/web/package.json dependencies 增加 dsh-compat-vet: link:D:/playwright-AI/playwright/dsh-compat-vet;dsh.profile.bundles 末尾增加 dsh-compat-vet
~/.dsh/profiles/web/pnpm-lock.yaml 增加该 link 依赖的 lock 条目
~/.dsh/profiles/web/node_modules/dsh-compat-vet 新建 Junction,指向源码目录

同日两次改名同步(dsh-plugin-doctor → dsh-compat-doctor → 最终 dsh-compat-vet,原因见 §5):

文件 改动
~/.dsh/profiles/web/package.json 依赖键与 bundles 条目同步改名;最后一次同时把 link: 目标路径改成改名后的源码目录
~/.dsh/profiles/web/pnpm-lock.yaml 同一条 lock 条目的键名与 link 路径同步
~/.dsh/profiles/web/node_modules/ 旧 Junction 删除,新建 dsh-compat-vet Junction
源码目录 D:\playwright-AI\playwright\dsh-plugin-doctor → …\dsh-compat-vet(目录名与包名、仓库名彻底一致)

每次同步后都用只读方式复核:dsh --profile web --dump-config 里出现 - id: compat-vet / name: dsh-compat-vet,唯一的启动告警仍是本来就存在的 dsh-cost-meter,没有新增问题。

没有改:原有 8 个插件一个没动(版本、解析路径全部不变)、cordis.yml、 cordis.patch.yml、pnpm-workspace.yaml 均未改动;compatibility.json 未创建。

⚠️ 这一轮新增的「一键修复」会在你按键之后改 profile 配置(这正是它的用途): 按 1 隔离会动 ~/.dsh/profiles/web/package.json 的 dsh.profile.bundles, 按 2 卸载会走 dsh 的 remove。每次改动前都会自动把 package.json / cordis.patch.yml / compatibility.json 备份到 ~/.dsh/profiles/web/.compat-vet-backup/<时间戳>/;复检不过会自动回滚。 本轮开发过程中没有对用户的真实 profile 执行过任何修复动作 —— 真机修复验证全程跑在临时 DSH_HOME 里(npm run verify:repair)。

改前快照:_profile-backup/20260930-152606/(安装前,含 package.json / pnpm-lock.yaml / pnpm-workspace.yaml / cordis.yml / cordis.patch.yml)与 _profile-backup/20260930-rename/(改名同步前)。

回滚(二选一):

# 方式一:正规卸载(推荐)
dsh plugin --profile web remove dsh-compat-vet

# 方式二:直接用快照还原(连 lock 一起回到改前状态)
copy /Y _profile-backup\20260930-152606\package.json       %USERPROFILE%\.dsh\profiles\web\package.json
copy /Y _profile-backup\20260930-152606\pnpm-lock.yaml     %USERPROFILE%\.dsh\profiles\web\pnpm-lock.yaml
cd %USERPROFILE%\.dsh\profiles\web && pnpm install

_profile-backup/ 已被 .gitignore 排除:它里面是本机真实 profile 的配置快照, 不进公开仓库(report/ 同理,含本机路径与已装插件清单)。

dsh-cost-meter@1.7.35 的不兼容不是本次改动造成的 —— 它在安装本插件之前 就已经被 dsh 启动预检拒绝加载了(见 §1 的 dsh 原始日志)。本插件只是把它报了出来, 没有修改、也没有尝试修复它。


9. 已知限制

  • 0.1.7-rc.1 之前没有兼容性预检,也没有版本豁免机制。 那些版本上不兼容的插件 会照常加载,风险直接暴露在运行时;allow-version 这条路也不适用。 本插件会如实这么说,但它无法替你拦下任何东西 —— 它只负责告诉你。
  • SemVer 求值器覆盖常见语法(^ ~ >= > <= < =、||、空格分隔、 x/X/* 通配、- 连字符区间、预发布优先级),且已与真 semver 对拍 1300+ 组 零差异;但 npm 还有一些极冷门写法(如复杂嵌套、npm: 别名里的范围)未逐一对拍。
  • 不联网(只有你按键选「升级」那一次会联网,因为那要装包)。因此常规体检只能告诉你 「去升级」,不会自动算出哪个版本才是兼容的 —— 那需要查 registry 的版本元数据。
  • 「100%」的准确边界(§4④,这里必须说实话):
    • 能保证的是:隔离 / 卸载之后,dsh 启动时的兼容性预检一定不会再因为这个插件拦下它 —— 依据是 profile 清单真的改了(下次启动读的就是它),并且再启动一次已经实测验证过;
    • 不能保证的是:插件升级到新版后就一定兼容(要装完才能验), 也不能保证某个插件的运行时问题不会把 dsh 弄崩 —— 本插件管的是版本冲突导致启动失败这一件事;
    • 因此不提供「豁免(allow-version)」这个选项:豁免是"接受风险继续加载", 它不会让启动不被拦下,与目标相反。
  • 修复接口只认本机回环:从局域网地址打开的 GUI 拿不到修复功能(返回 403), 页面其余部分照常。这是刻意取舍(该接口会改 profile 配置)。
  • 修改能力依赖 dsh 自己的 pluginManager:完全没有这个服务的老宿主上, 隔离退化为自己原子写 profile 清单(结果里会明确标注「未走 dsh 服务」), 而卸载/升级会直接报错让你改用命令行 —— 不会假装成功。
  • 升级不受「保证」保护:它需要 pnpm 与网络,且装完之前无法预知新版兼不兼容; 它会如实报告结果,必要时你可以接着按「1」隔离。
  • @deepseek-ai/* 的 bundle 被当作 dsh 安装自带(依据实测的 DEFAULT_PROFILE_BUNDLES 与安装目录里是否真的存在该包,不是写死的名单)。若第三方占用了该 scope,会漏报。
  • 校准是「本机这个 dsh」的实测结论。 换一台机器、换一个 dsh 版本,结论会重测; 但同一台机器上如果 dsh-app-boot 被改动过(少见),结论就以那次实测为准。
  • 定位 dsh 安装目录的顺序:当前进程入口 → DSH_INSTALL_ANCHOR / DSH_ANCHOR → PATH 上的 dsh → npx 缓存 → 常见 npm 全局目录;都不中时用 --anchor 显式指定。

10. 目录结构

dsh-compat-vet/                # 目录名 = 包名 = GitHub 仓库名(三者彻底一致)
├── package.json                  # dsh.bundle.patch + dsh.client 声明 + bin 入口 + npm 元数据
├── cordis.patch.yml              # 把插件插进 profile 的 bundle 层
├── LICENSE                       # MIT
├── .gitignore                    # 排除私有快照与报告(见下)
├── .gitattributes                # 一律 LF 入库
├── lib/
│   ├── index.js                  # cordis 宿主插件:启动自检 + 工具 + 提示词段落 + 页面接口挂载
│   ├── adapt.js                  # 适配层:定位 + 上下文来源推导 + 运行时能力实测校准
│   ├── audit.js                  # 审计引擎(纯函数 + 文件读取,永不抛)
│   ├── upgrade.js                # 「快要不适配」预测:按声明范围推算何时会坏
│   ├── repair.js                 # 修复层:备份 → 走 dsh 服务改 → 落盘校验 + 复检 → 不过就回滚
│   ├── routes.js                 # 宿主 HTTP 接口:状态 / 发起修复 / 查询作业(回环 + 同源围栏)
│   ├── client.js                 # 页面横幅(手写 lazy-CJS,零依赖、无构建步骤)
│   ├── semver.js                 # 自包含 SemVer 范围求值器(与真 semver 对拍过)
│   ├── report.js                 # 中文报告渲染 + 修复命令生成(措辞随实测能力变)
│   ├── locate.js                 # 定位 dsh 安装目录(含从进程入口反推)
│   └── cli.js                    # 独立 CLI(dsh 起不来时用)
├── scripts/
│   ├── verify-real-boot.mjs      # 真机启动验证:每个 dsh 版本各真启动一次
│   ├── verify-real-repair.mjs    # 真机修复验证:真改一次 + 再启动一次(见 §2.6 bug 5)
│   ├── verify-tarball-install.mjs# 打包安装验证;--from-npm 可直接验线上那个包
│   ├── preview-banner.mjs        # 只读:用真实 profile 的数据把横幅渲染出来看
│   └── helpers/
│       ├── dsh-harness.mjs       # 真机验证公共件(临时 home / 启停 dsh / 建 profile)
│       └── fake-dom.mjs          # 最小假 DOM(测试与 preview 工具共用一份)
├── test/                         # 134 个用例(含 4 个真实 dsh 版本的实测对拍)
├── publish/                      # 投稿插件市场的文件与步骤(含那条 YAML 条目)
├── report/                       # 阶段性验证报告(含本机路径,故 gitignore)
└── _profile-backup/              # 本机 profile 配置快照(含插件清单,故 gitignore)
—/ 5

No ratings yet

Verified DSH bundle

Commit 926be60ea0a7

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout