deepseek-harness
deepseek-ai
DeepSeek Harness: Everything is a Plugin.
PROJECT TOPICS
INSTALL REFERENCE
dsh plugin --profile web add github:liujianqiao701/dsh-compat-vet
该命令指向仓库当前默认分支;尚无绑定当前 commit 的完整验证结果。
PROJECT README
DSH 插件兼容性体检 + 页面上的一键修复 —— 检查已安装插件与当前 dsh 运行时是否冲突,
中文预警并给出可直接执行的命令;装了本插件之后,dsh web 的页面顶部会直接弹出横幅,
照着按键就能当场修掉(不提供「版本豁免」这个选项,理由见 §4④)。
它跟着 dsh 版本自己变,不需要你为任何 dsh 版本改一行代码。(见 §2)
dsh 升级后,老插件常常因为 peer 依赖范围不匹配而被启动预检拒绝加载。此时的表现很有迷惑性:
dsh plugin list 里、文件也还在 node_modules 里,看起来没坏;本插件把这件事变成主动、集中、中文、可操作的体检 —— 而且在 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
两者结论一致;差别在于本插件是集中报告 + 中文 + 带修复命令。
if最省事、也最不靠谱的做法是维护一张表:
// ❌ 本插件刻意不这么写
if (semver.gte(version, '0.1.7-rc.1') && semver.lt(version, '0.3.0')) { … }
这种代码每个 dsh 新版本都要人来改一次 —— 正是要避免的事。而且它基于一个错误假设: 「dsh 版本号 → 能力」这个映射是稳定的、可以事先写死的。实际上不是。
dsh 的兼容性预检在 @deepseek-ai/dsh-app-boot 里。插件在启动时:
import() 本机 dsh 安装目录下的 dsh-app-boot;evaluatePluginCompatibility —— 有没有这件事不靠版本号假设;>=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,不存在「猜一个」。
老版本 dsh 根本没有兼容性预检。那里插件会照常加载,兼容性风险直接暴露在运行时。 如果在那种版本上还打「启动时会被拒绝加载」,就是说反了 —— 用户会以为自己安全了。
所以告警措辞跟着实测走:
| 实测结论 | 告警措辞 |
|---|---|
| 该版本会拦 | — 与 dsh X 不兼容,启动时会被拒绝加载 |
| 该版本不拦 | — 与 dsh X 不兼容(本机 dsh 不会拦下它,会照常加载) |
| 没测出来 | — 与 dsh X 不兼容(本次未能实测本机 dsh 会不会拦下它) |
同理,allow-version 这条修复建议只在真的有豁免机制时才给 ——
在没有豁免机制的老版本上给这条命令,等于给一条根本跑不通的命令。
| 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,测试会自己把它加进来跑。遇到完全不认识的未来版本时,
测试只做自洽性检查并打一个 ⚠️ 提示,不会因此失败、也不需要谁来更新代码。
单元测试里的 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。
这几个都是单元测试全绿也照样漏掉的,只有真启动才暴露:
| # | 问题 | 后果 | 修法 |
|---|---|---|---|
| 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()),任何一处出错只会退到下一个来源,不会让自检整体失能。
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 侧从未校验过它。本插件会读这个字段。
装进 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→ 返回完整中文报告与修复命令。
还有一个系统提示词段落:只要存在未解决的兼容性问题,它就会被注入到模型的上下文里, 模型会在你提到插件/报错/功能异常时主动提醒,不需要你想到去问。
插件只在 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 })
装了插件、用 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 的数据渲染一遍横幅,不发修复请求)。package.json / cordis.patch.yml / compatibility.json 到
<profile>/.compat-vet-backup/<时间戳>/;改完自动复检,保证类动作复检不过就自动回滚。allow-version)」这个选项:豁免只是"接受风险继续加载",
它不会让 dsh 下次启动不被拦下 —— 与本插件的目标正好相反。⚠️ 接口只认本机回环(
127.0.0.1/::1+ 同源校验)。所以从局域网地址打开的 GUI 会被拒绝(403),修复功能不可用但页面其余部分照常。这是刻意取舍:这条接口会改 profile 配置, 宁可不可用也不能给跨站请求留门。
已发布到 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-doctor3 条、dsh-plugin-doctor3 条、dsh-plugin-guard2 条,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。)
@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 全都不在。
所以:
defineTool(它只是把 spec 转成 JSON Schema);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、
不联网;修复命令只打印,由你决定执不执行。
唯一的写路径是你在页面横幅上明确按键选择之后的那一次修复,而且:
pluginManager 服务,不自己写文件(它加 profile 文件锁、原子写、还会协调 HMR;
而 dsh 内部那个 writeProfileManifest 是无锁非原子的 writeFileSync——
自己写就是绕过锁,万一插件页同时在写就会互相覆盖);pluginManager 的老宿主才退回自己原子写,并在结果里明确说明这一点。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 的长 || 串、@linxin666/dsh-perf 的 >=)断言分级;固定版本 / 只允许补丁 / 通配 / 垃圾输入各自的分级 |
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-compat-vet/client.js(真实 rev 从页面里取),按该 URL 取回 HTTP 200、 41 万字节,内容就是本插件的横幅代码;npm run verify:tarball,21 项)→ npm pack 出真 tarball、
用 dsh 自己的安装命令装进空白 profile、真启动、取到横幅代码、再卸载干净;--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 一个触发预警,不是噪音;dsh-cost-meter@1.7.35)、退出码 1;plugin_compat_check 工具实际调用成功,与「DSH 插件兼容性问题」提示词段落
均已在该会话生效;skipping profile bundle "dsh-cost-meter" 日志逐条一致。阶段性验证报告:report/页面预警与一键修复-验证报告-20260930.md(含逐条要求对照、真机输出、诚实边界)。
本插件在开发过程中被装进了用户的在用 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 原始日志)。本插件只是把它报了出来,
没有修改、也没有尝试修复它。
0.1.7-rc.1 之前没有兼容性预检,也没有版本豁免机制。 那些版本上不兼容的插件
会照常加载,风险直接暴露在运行时;allow-version 这条路也不适用。
本插件会如实这么说,但它无法替你拦下任何东西 —— 它只负责告诉你。^ ~ >= > <= < =、||、空格分隔、
x/X/* 通配、- 连字符区间、预发布优先级),且已与真 semver 对拍 1300+ 组
零差异;但 npm 还有一些极冷门写法(如复杂嵌套、npm: 别名里的范围)未逐一对拍。allow-version)」这个选项:豁免是"接受风险继续加载",
它不会让启动不被拦下,与目标相反。403),
页面其余部分照常。这是刻意取舍(该接口会改 profile 配置)。pluginManager:完全没有这个服务的老宿主上,
隔离退化为自己原子写 profile 清单(结果里会明确标注「未走 dsh 服务」),
而卸载/升级会直接报错让你改用命令行 —— 不会假装成功。pnpm 与网络,且装完之前无法预知新版兼不兼容;
它会如实报告结果,必要时你可以接着按「1」隔离。的 bundle 被当作 dsh 安装自带**(依据实测的DEFAULT_PROFILE_BUNDLES`
与安装目录里是否真的存在该包,不是写死的名单)。若第三方占用了该 scope,会漏报。dsh-app-boot 被改动过(少见),结论就以那次实测为准。DSH_INSTALL_ANCHOR / DSH_ANCHOR →
PATH 上的 dsh → npx 缓存 → 常见 npm 全局目录;都不中时用 --anchor 显式指定。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) CLASSIFICATION EVIDENCE
系统优先读取 GitHub Topics,再与站内分类词典和词根规则比对。当前命中: 无有效分类标签。