DeepSeek Harnessdsh的插件体检 + 修复工具,带 DSH 内的原生设置页。
36/100
综合分
4
GitHub 星标
3,372
npm 周下载
—/5
用户评分
数据截至 2026/11/3 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add dsh-plugin-healthnpm 源 · dsh-plugin-health 的发布者账号已校验与本仓库一致
状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:已验证
是什么
dsh 原生插件 · market
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 3 天前
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查✓ 自动检查通过展开查看检查明细 ↓
以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。
✓npm 包dsh-plugin-health @ 0.5.0
✓Node 引擎要求 >=20 · 基线 Node 22.19 满足
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明
验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/29 11:41:09
安全扫描自动 · 静态分析源码v2.7.0🔵 扫描:含敏感能力尚无人工评估展开查看扫描说明与证据 ↓
安全扫描自动 · 静态分析源码
v2.7.0静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。
可执行外部命令读写/删除本地文件写入剪贴板(不计风险)读取剪贴板或密钥环境变量发起外部网络请求
查看扫描证据(共 137 处,展示前 95 条)
- 读取本地文件src/plugin.js:25
return JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version - 读取本地文件src/plugin.js:41
if (existsSync(manifestPath)) { - 读取本地文件src/plugin.js:43
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8')) - 发起网络请求src/ui/client.js:430
const response = await fetch(STATUS_PATH, { - 发起网络请求src/ui/client.js:480
const response = await fetch(`${RESCUE_PATH}/${action}`, { - 写入剪贴板src/ui/client.js:546
await navigator.clipboard.writeText(handoffResult?.prompt ?? '') - 发起网络请求src/ui/client.js:887
const response = await fetch(REPAIR_PATH, { - 发起网络请求src/ui/client.js:1319
fetch(STATUS_PATH, { headers: { accept: 'application/json' }, credentials: 'same-origin' }) - 读取本地文件src/repair/inspect.js:32
if (!existsSync(directory)) return [] - 读取本地文件src/repair/inspect.js:33
return readdirSync(directory, { withFileTypes: true }) - 读取本地文件src/repair/inspect.js:44
return JSON.parse(readFileSync(filename, 'utf8')) - 读取本地文件src/repair/inspect.js:70
if (!existsSync(resolvedHome)) { - 读取本地文件src/repair/inspect.js:85
if (!existsSync(profileDir)) { - 读取本地文件src/repair/inspect.js:92
if (!existsSync(manifestPath)) { - 读取本地文件src/ui/status.js:58
return JSON.parse(readFileSync(join(result.profileDir, 'node_modules', name, 'package.json'), 'utf8')) - 读取本地文件src/ui/rescue-route.js:312
return JSON.parse(readFileSync(join(profileDir, 'node_modules', name, 'package.json'), 'utf8')) - 写入本地文件src/compat/report.js:86
mkdirSync(directory, { recursive: true }) - 写入本地文件src/compat/report.js:94
writeFileSync(file, `${badgeSvg({ label: 'dsh compat', value: `${compatible}/${total} hosts`, color })}\n`) - 写入本地文件src/compat/report.js:100
writeFileSync(file, `${badgeSvg({ label: `dsh ${summary.host}`, value: `${summary.compatible}/${summary.total} ok`, color })}\n`) - 读取本地文件src/index/catalog.js:20
: JSON.parse(await readFile(source, 'utf8')) - 写入本地文件src/index/build.js:287
mkdirSync(outDir, { recursive: true }) - 写入本地文件src/index/build.js:291
writeFileSync(index, `${JSON.stringify(build.doc, null, 0)}\n`) - 写入本地文件src/index/build.js:292
writeFileSync(report, buildReport(build)) - 写入本地文件src/index/build.js:293
writeFileSync(homepage, landingPage(build)) - 写入本地文件src/repair/apply.js:54
if (!dryRun && auto.length > 0) mkdirSync(root, { recursive: true }) - 读取本地文件src/repair/apply.js:60
if (typeof target !== 'string' || !existsSync(target)) { - 写入本地文件src/repair/apply.js:67
mkdirSync(dirname(destination), { recursive: true }) - 写入本地文件src/repair/apply.js:69
renameSync(target, destination) - 删除本地文件递归src/repair/apply.js:72
rmSync(target, { recursive: true, force: true }) - 读取本地文件src/repair/apply.js:80
if (typeof target !== 'string' || !existsSync(target)) { - 读取本地文件src/repair/apply.js:84
const original = readFileSync(target, 'utf8') - 写入本地文件src/repair/apply.js:93
mkdirSync(dirname(backup), { recursive: true }) - 写入本地文件src/repair/apply.js:94
copyFileSync(target, backup) - 写入本地文件src/repair/apply.js:96
writeFileSync(temporary, edited) - 读取本地文件src/repair/apply.js:116
while (existsSync(candidate)) { - 读取本地文件src/cli.js:24
const VERSION = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version - 读取本地文件src/cli.js:91
return readFileSync(filename, 'utf8').split(/ ? /u).map((line) => line.trim()).filter((line) => line !== '' && !line.startsWith('#')) - 读取本地文件src/cli.js:96
return statSync(path).isFile() - 读取本地文件src/cli.js:227
const exemptions = exemptionsPath === undefined ? {} : JSON.parse(readFileSync(exemptionsPath, 'utf8')) - 写入本地文件src/cli.js:230
mkdirSync(out, { recursive: true }) - 写入本地文件src/cli.js:231
writeFileSync(join(out, 'matrix.json'), `${JSON.stringify(matrix, null, 2)}\n`) - 写入本地文件src/cli.js:232
writeFileSync(join(out, 'matrix.md'), matrixToMarkdown(matrix)) - 读取本地文件src/cli.js:271
const matrix = matrixPath === undefined ? undefined : JSON.parse(readFileSync(matrixPath, 'utf8')) - 读取本地文件src/cli.js:304
calls: callsPath === undefined ? [] : JSON.parse(readFileSync(callsPath, 'utf8')), - 读取密钥环境变量src/cli.js:546
token: process.env.GITHUB_TOKEN, - 执行外部命令来自配置/变量src/runtime/smoke.js:14
const child = spawn(process.execPath, args, { - 执行外部命令固定命令src/runtime/smoke.js:36
const killer = spawn('taskkill', ['/pid', String(child.pid), '/T', '/F'], { stdio: 'ignore' }) - 读取本地文件src/runtime/smoke.js:102
if (existsSync(spec) && statSync(spec).isDirectory()) { - 读取本地文件src/runtime/smoke.js:102
if (existsSync(spec) && statSync(spec).isDirectory()) { - 读取本地文件src/runtime/smoke.js:104
if (!existsSync(manifestPath)) { - 读取本地文件src/runtime/smoke.js:107
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8')) - 读取本地文件src/runtime/smoke.js:113
if (existsSync(spec)) { - 读取本地文件src/runtime/smoke.js:169
const manifest = JSON.parse(readFileSync(join(hostDir, 'package.json'), 'utf8')) - 写入本地文件src/runtime/smoke.js:183
mkdirSync(parent, { recursive: true }) - 写入本地文件src/runtime/smoke.js:196
const save = () => { writeFileSync(reportPath, `${JSON.stringify(report, null, 2)}\n`) } - 写入本地文件src/runtime/smoke.js:216
writeFileSync(configPath, JSON.stringify({ hostDir, profile, tools, calls, resultPath: workerResult })) - 读取本地文件src/rescue/rescue.js:42
const parsed = JSON.parse(readFileSync(path, 'utf8')) - 读取本地文件src/rescue/rescue.js:62
const parsed = JSON.parse(readFileSync(path, 'utf8')) - 写入本地文件src/rescue/rescue.js:78
mkdirSync(dirname(path), { recursive: true }) - 读取本地文件src/rescue/rescue.js:79
if (existsSync(path)) writeFileSync(`${path}.bak`, readFileSync(path)) - 读取本地文件src/rescue/rescue.js:79
if (existsSync(path)) writeFileSync(`${path}.bak`, readFileSync(path)) - 写入本地文件src/rescue/rescue.js:79
if (existsSync(path)) writeFileSync(`${path}.bak`, readFileSync(path)) - 写入本地文件src/rescue/rescue.js:80
writeFileSync(path, `${JSON.stringify(state, null, 2)}\n`) - 写入本地文件src/rescue/rescue.js:137
writeFileSync(join(profileDir, EXEMPTION_FILE), `${JSON.stringify(updated, null, 2)}\n`) - 写入本地文件src/rescue/rescue.js:159
writeFileSync(join(profileDir, EXEMPTION_FILE), `${JSON.stringify(exemptions, null, 2)}\n`) - 读取本地文件src/rescue/upstream.js:93
try { cache = JSON.parse(readFileSync(cachePath, 'utf8')) } catch { cache = {} } - 写入本地文件src/rescue/upstream.js:117
try { writeFileSync(cachePath, `${JSON.stringify(cache, null, 2)}\n`) } catch { } - 读取本地文件src/rescue/environment.js:34
const version = JSON.parse(readFileSync(path, 'utf8')).version - 读取本地文件src/rescue/environment.js:42
const lock = readFileSync(join(profileDir, 'pnpm-lock.yaml'), 'utf8') - 读取本地文件src/rescue/environment.js:81
if (existsSync(join(candidate, 'package.json'))) return candidate - 读取本地文件src/rescue/environment.js:131
if (existsSync(join(directory, 'package.json'))) return { directory, installed: false } - 写入本地文件src/rescue/environment.js:133
mkdirSync(root, { recursive: true }) - 读取本地文件src/rescue/environment.js:147
if (!existsSync(join(directory, 'package.json'))) { - 执行外部命令来自配置/变量src/rescue/environment.js:156
const child = spawn(process.platform === 'win32' ? 'npm.cmd' : 'npm', args, { - 读取本地文件src/rescue/boot.js:39
if (!existsSync(path)) return [] - 读取本地文件src/rescue/boot.js:42
parsed = parseYaml(readFileSync(path, 'utf8')) - 读取本地文件src/rescue/boot.js:71
if (existsSync(path)) { - 写入本地文件src/rescue/boot.js:72
try { copyFileSync(path, `${path}.bak-plugin-health`) } catch { } - 写入本地文件src/rescue/boot.js:75
writeFileSync(temporary, stringifyYaml(entries)) - 写入本地文件src/rescue/boot.js:76
renameSync(temporary, path) - 读取本地文件src/rescue/boot.js:129
return existsSync(directory) ? directory : null - 读取本地文件src/rescue/boot.js:139
if (dir === null || !existsSync(dir)) return [] - 读取本地文件src/rescue/boot.js:142
files = readdirSync(dir, { withFileTypes: true }) - 写入本地文件src/rescue/boot.js:452
mkdirSync(directory, { recursive: true }) - 写入本地文件src/rescue/boot.js:454
writeFileSync(path, `${JSON.stringify(diagnosis, null, 2)}\n`) - 读取本地文件src/rescue/reporting.js:265
try { state = JSON.parse(readFileSync(path, 'utf8')) } catch { state = {} } - 写入本地文件src/rescue/reporting.js:267
try { writeFileSync(path, `${JSON.stringify(state, null, 2)}\n`) } catch { } - 读取本地文件src/rescue/reporting.js:276
return JSON.parse(readFileSync(join(profileDir, 'plugin-health-reports.json'), 'utf8')) - 写入本地文件src/rescue/handoff.js:244
mkdirSync(directory, { recursive: true }) - 写入本地文件src/rescue/handoff.js:248
writeFileSync(path, contents) - 读取本地文件src/rescue/handoff.js:268
prompt: readFileSync(join(directory, 'prompt.md'), 'utf8'), - 读取本地文件src/rescue/handoff.js:269
brief: JSON.parse(readFileSync(join(directory, 'brief.json'), 'utf8')), - 发起网络请求src/util/http.js:30
const response = await fetch(url, { - 读取本地文件src/runtime/worker.js:58
const config = JSON.parse(readFileSync(configPath, 'utf8')) - 写入本地文件src/runtime/worker.js:60
const save = () => { writeFileSync(config.resultPath, `${JSON.stringify(report, null, 2)}\n`) }
扫描于 2026/11/3(1 天前) · 范围 入口闭包 + 全包扫描 · 来源 npm 发布包
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
依赖的 DSH / Cordis 模块
@deepseek-ai/dsh-plugin-market-index@deepseek-ai/dsh-app-boot@deepseek-ai/dsh-tools给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
DSH 插件升级检查与多主机兼容性矩阵、官方 format-1 市场索引、Windows/WSL 安装诊断工具,以及一个可复用的 GitHub Action,在插件 PR 上评论兼容性矩阵。
最近更新
在 GitHub 查看全部 →v1.1.02026-10-31
· 1.1.0 — 安全版本
1.0.2 的安全测试发现七个问题,全部复现并修复,每个都配了回归测试。
如果你在跑 1.0.3 或更早版本,请升级。
GHSA-vj8q-543v-9hw5
· 哪些能被触达
远程和跨源攻击者够不到这些路由——host 头校验和强制预检的自定义头都守住了。能走通的链路需要一个本地进程,起点是自己挖的坑:写路由的控制密钥由未认证的 /status 端点返回,所以这把密钥对本地进程毫无防护作用,而它本来就是为了挡住这些进程。
由此往下:配置被清空(工具解析不了的补丁层被读成空,随后被覆盖)、任意路径写入(原始插件版本被拼进交接路径)、代码执行(调用方提供的宿主目录决定了跑哪个 bin 脚本;非 semver 的宿主规格经 shell: true 进了 npm install)。
· 修复
| 问题 | 修复 |
| --- | --- |
| /status 返回控制密钥 | 改为下发 HttpOnly; SameSite=Strict cookie |
| 无法解析的补丁层被覆盖 | 以 PATCH_NOT_SEQUENCE 拒绝;每次写入保留 .bak 并原子替换 |
| 经插件版本做路径穿越 | 两个字段都做净化,解析后的路径校验在交接根目录内 |
| 经宿主规格做命令注入 | 不走 shell、加 --ignore-scripts、只接受精确 semver——顺带堵上 npm: 别名路径 |
| body.dshDirectory 决定执行的 bin | 限制在可信根目录内 |
| 密钥比较非常量时间、认证前先返回 404 | 常量时间比较,先认证 |
· 说清楚
回环接口就是边界。同一用户账号下的本地进程能调这些路由;cookie 挡住其他源,也让页面脚本拿不到值,但它分不开合法 UI 和机器上的另一个进程。这一点现在写进了 SECURITY.md,不再靠「有密钥」暗示相反的意思。
176 个测试(12 个安全回归),176 通过,1 个平台跳过。CI 4/4 全绿。
v1.0.02026-10-29
· 1.0.0 — DSH 插件检查器,能真正修好它发现的问题
首个稳定版。四层流程已完整,并在真实 DSH 中端到端验证过,不只是单元测试。
· 四层
| # | 层 | 做什么 |
| --- | --- | --- |
| 1 | 检测 | 扫描每个已安装插件——包、依赖、cordis.patch.yml 插入项(含嵌套)、node_modules |
| 2 | 验证 | 在一次性 DSH home 里试跑被拒的插件:在那里授予豁免、安装、启动、检查每个入口是否激活 |
| 3 | 修复 | 仅当试跑启动成功:向你的配置档写入一条官方 allow-version 豁免,有记录、可撤销 |
| 4 | 上报 | 若未启动:什么都不改,提供「关掉它」或一份备好的 AI 简报,并先查上游是否已有报告 |
· 为什么要有第 2 层
在 0.2.1-rc.4 上实测——三个插件因声明被拒:
· @nanmicoder/dsh-agent-teams@0.1.27 → 完整启动,94 个入口激活
· dsh-find-plugin@0.4.0 → 运行正常
· @feng0531/dsh-vcs@0.4.10 → 失败:等待宿主已不再提供的服务
静态检查器会把三个都判为坏掉。只有第三个真的坏了。给它豁免,恰恰会造出这道关卡要防的东西:一个能加载、随后行为异常、且任何地方都不再留警告的插件。
· 发布前通过真实 UI 验证
每一层都在真实 DSH 的设置页里跑过。这一轮发现四个单元测试漏掉的缺陷,均已在此修复:桌面版安装没有磁盘上的宿主(它在 app.asar 里),所以试跑总是失败;自动下载的宿主用的是 npm latest 而非运行中的版本;确认步骤可能落到折叠线以下;没有宿主版本的救援会写入 [null]。详见 CHANGELOG。
· DSH 起不来时如何恢复
boot diagnose 读取桌面应用的崩溃报告,报告会直接点名出问题的包。boot safe 禁用第三方入口,让 DSH 重新启动;boot restore 把一切还原。两者都不会禁用这个工具本身。
155 个测试,154 通过,1 个平台跳过。CI:7/7 全绿,含两个真实宿主版本上的运行时验收。
npm:(SLSA 来源证明)
v0.5.02026-10-26
· 0.5.0
· 新增
· smoke — 隔离配置档运行时验收。静态矩阵只比对声明的版本范围,抓不到这种情况:清单声明兼容,但打包产物仍在等宿主已不再提供的服务。新命令需手动开启,通过官方命令行把包或发布 tarball 装进全新的 DSH_HOME,用公开的 profile-boot 导出启动,检查生效条目和具名工具,还可选执行调用并核对预期返回值。
sh
dsh-plugin-health smoke \
--dsh-dir /path/to/node_modules/@deepseek-ai/dsh \
--plugin ./my-plugin-1.0.0.tgz \
--tool my_plugin_tool \
--calls ./smoke-calls.json
由 @po-et 在 #3 贡献。
先说清限制:全新配置档不是安全沙箱;Windows 上尚未实现自动检测正常退出的子进程;浏览器行为不在范围内。
· 修复
· 矩阵工作流不再因来自 fork 的拉取请求而失败。fork 的 PR 无论 permissions: 块怎么写,拿到的 GITHUB_TOKEN 都是只读的,于是评论步骤报 HttpError: Resource not accessible by integration 挂掉,把整个任务变红,尽管矩阵在 3/3 台宿主上都通过了。
· 变更
· rc.3 的 5 个回归问题里已有 3 个带上修复——受影响数量 5 → 2。用本工具对 npm 验证:@anionex/dsh-vision-toolkit@0.1.48、dsh-plugin-subscriptions@0.9.11 和 @nanmicoder/dsh-agent-teams@0.1.27 在 0.2.1-rc.2 和 0.2.1-rc.3 上都通过。剩余:dsh-workbuddy-connect@0.7.9(仓库无法访问)和 @goodandready/dsh-clinebot@0.6.15(issue 无人回复)。
· docs/upgrade-0.2.1-rc3.md 更正了修复建议:「用范围代替枚举」并非总是适用——自己跑宿主版本枚举检查的仓库必须枚举,因为范围过不了那道关。
· 两个 README 都展示了各插件的修复状态表和更正后的 0.5.0 状态行。
npm:(已发布,带 SLSA 来源证明)
数据来自仓库的 GitHub Release,可能与插件实际版本不一致。
README
由 DeepSeek 最新模型翻译生成dsh-plugin-health
DeepSeek Harness(dsh)的插件体检 + 修复工具,带 DSH 内的原生设置页。
唯一一个"修得动"的 DSH 插件检查器——而且在动你的配置之前,先证明修复真的有效。
别的工具到「这个插件不兼容」就停了。这个工具回答下一个问题:它救不救得回来,怎么救? 它把插件丢进一个一次性环境里试装试启动,真跑一遍,然后才决定:修、交给你自己的 AI、还是干脆关掉让 DSH 能继续用。
状态:1.1.0。兼容判定完全对齐官方 DSH 0.2.1-rc.2 的规则,索引由官方发布的 @deepseek-ai/dsh-plugin-market-index 校验器验证。
线上官方 format-1 索引: <https://easonmws.github.io/dsh-plugin-health/index.json> (300 个插件,每晚自动重建) · npm: npm install -g dsh-plugin-health · 仓库: <https://github.com/EasonMWS/dsh-plugin-health>
已收录:awesome-dsh-plugin 与 DSH Plugin Store。
特色:四个层次,不是一层
静态检查只是第一层——而且单独用它会经常判断错误,错到危险的程度。
展开完整文档(README 剩余 1 节)
dsh-plugin-health
DeepSeek Harness(dsh)的插件体检 + 修复工具,带 DSH 内的原生设置页。
唯一一个"修得动"的 DSH 插件检查器——而且在动你的配置之前,先证明修复真的有效。
别的工具到「这个插件不兼容」就停了。这个工具回答下一个问题:它救不救得回来,怎么救? 它把插件丢进一个一次性环境里试装试启动,真跑一遍,然后才决定:修、交给你自己的 AI、还是干脆关掉让 DSH 能继续用。
状态:1.1.0。兼容判定完全对齐官方 DSH 0.2.1-rc.2 的规则,索引由官方发布的 @deepseek-ai/dsh-plugin-market-index 校验器验证。
线上官方 format-1 索引: <https://easonmws.github.io/dsh-plugin-health/index.json> (300 个插件,每晚自动重建) · npm: npm install -g dsh-plugin-health · 仓库: <https://github.com/EasonMWS/dsh-plugin-health>
已收录:awesome-dsh-plugin 与 DSH Plugin Store。
特色:四个层次,不是一层
静态检查只是第一层——而且单独用它会经常判断错误,错到危险的程度。完整路径是这样的:
| # | 层次 | 做什么 | 在哪跑 |
| 1 | 检测 | 扫每个已安装插件(bundles、dependencies、cordis.patch.yml 的 insert 含嵌套、node_modules),把声明和你实际运行的版本对比 | 你的机器 |
| 2 | 验证 | 在一次性 DSH home 里试:先放行门禁,再真装上、真启动,检查每个 entry 是否激活 | 隔离、用完即弃 |
| 3 | 修复 | 预演能启动,才往你的 profile 写一条官方 allow-version 豁免——有记录,一键撤销 | 你的 profile |
| 4 | 兜底 | 预演起不来,就什么都不改,给两个真实选择:关掉它,或拿一份准备好的提示词交给你自己的 AI。上报前先查上游有没有人报过 | 你决定 |
第 2 层是这个工具存在的理由。 没有它,第 3 层就是在赌。
为什么中间这层重要
大部分被拒的插件代码是好的——作者把版本号写死了,没跟上新宿主。0.2.1-rc.4 实测:
| 插件 | 声明状态 | 隔离环境里真跑 |
| @nanmicoder/dsh-agent-teams@0.1.27 | ❌ 被拒 | ✅ 完整启动,94 个 entry 全部激活 |
| dsh-find-plugin@0.4.0 | ❌ 被拒 | ✅ 真的能跑 |
| @feng0531/dsh-vcs@0.4.10 | ❌ 被拒 | ❌ 失败:在等宿主已不再提供的服务 |
前两个是假警报——只做静态检查的工具会把它们报成坏插件。第三个正是预演必须存在的理由:放行它,恰好制造出门禁想避免的结果——一个能加载、但会出问题的插件,而且再没有任何警告留着。
这意味着一件事:只做静态检查的工具,会把这些还能用的插件误判成坏插件。
所以我们做的是“先验证,再动手”
扫描发现插件被拒 ↓ 在一次性临时环境里「假装放行 + 真的启动一次」 ← 不碰你的 profile ├─ 起来了 → 代码没问题,只是声明过时 │ → 在你的 profile 里放行(可一键撤销) │ → 记录状态,盯上游有没有发新版 └─ 起不来 → 真的不兼容,放行也没用 → 什么都不改,明确告诉你 → 需要的话,帮你准备好给 AI 的完整上下文
顺序很重要:先放行再验证,会让你带着一个「门禁不拦但会崩」的插件;我们是先在隔离环境里证明能用,才动你的配置。
在设置页里长这样
安装后在 设置 → 插件健康 里:
- 每个不兼容插件下方有「检查能不能救」按钮
- 点了会在临时环境试装试启动(30~60 秒),不修改任何东西
- 如果能用,出现「临时放行」按钮;不能用则明确告诉你救不了
- 放行过的插件会记录在案,等上游发版后提示你「可以恢复原样了」
全部操作都可撤销:放行只写一个官方支持的豁免项,撤销会精确恢复到放行前的状态。
安全边界(请务必读一遍)
安全提示: 1.1.0 修复了安全测试在 1.0.2 里发现的 7 个问题,其中一个评为 high ——写路由的控制密钥被无鉴权的 status 接口下发了。如果你在用 1.0.3 或更早,请升级。
详情见 GHSA-vj8q-543v-9hw5 与 SECURITY.md。那里也把信任边界写清楚了:写路由对任何本地进程可达,真正的边界是 loopback 接口本身。
| 我们做什么 | 我们不做什么 |
| 在隔离的临时 home 里试装试启动 | ❌ 不自动调用你的 AI,不花你的 token |
| 使用 DSH 官方 allow-version 豁免机制 | ❌ 不修改插件源码,不改 npm 上的包 |
| 记录每一次改动,支持精确撤销 | ❌ 验证失败时绝不放行 |
| 帮你检查上游是否已有人报过 issue | ❌ 不替你提交 issue |
| 准备好完整的 AI 上下文包 | ❌ 不替你按下发送键 |
临时放行 = 门禁放行,不等于官方支持。 我们只保证它在隔离环境里能启动;插件的具体功能是否完全正常,仍需你自己观察。等作者发布正式支持版本后,请撤销临时放行并升级。
1.0 承诺什么
大版本号是对稳定性的承诺,所以这里把冻结的和还没冻结的都说清楚,而不是让你升级时才发现。
冻结——1.x 内不会破坏:
| 面 | 保证 |
| CLI 命令与参数(matrix、smoke、doctor、repair、rescue、handoff、report、boot、index) | 现有参数含义不变。可以新增参数,但不会删除或改变含义 |
| plugin_health agent 工具的名字与输入 schema | 只增字段,不改已有字段 |
| 退出码(0 成功 / 1 有问题或失败 / 2 用法错误) | 不变 |
| 本工具写出的文件(compatibility.json、plugin-health-rescue.json、交接包) | 格式有文档且向前兼容:不认识的键会保留,不丢弃 |
| 状态路由结构(/plugins/dsh-plugin-health/status) | 只增字段,已有字段名与类型不变 |
未冻结——可能在 minor 版本变化,因为它们属于展示层或内部实现:
- UI 文案、布局、配色、设置面板的结构;
- 生成的 AI 提示词和 issue 草稿里的建议措辞;
- src/ 下的内部模块划分(除 ./api 和 ./client 两个导出,src/ 不是公开导入路径);
- 已文档化之外的 finding id——新增检查会带来新 id。
宿主升级时的版本策略。 engines.dsh 写的是范围(>=0.1.0-rc.6 <0.3.0-0)而不是枚举,所以新的 DSH nightly 不会像打断那些写死版本的插件那样打断本插件。等 DSH 0.3.0 发布时,本包需要发一版扩展这个范围——这是唯一已知的过期点,会提前写进 changelog。
DSH 起不来的时候
插件坏了导致 DSH 根本启动不了时,设置页自然也打不开——那些功能都跑在 DSH 里面。
所以这三个命令完全独立于 DSH,只要能开终端就能用:
1. 先看为什么起不来(读宿主留下的日志和静态检查) dsh-plugin-health boot diagnose --profile desktop
2. 实在查不出,就先把所有第三方插件停掉,让 DSH 能启动 dsh-plugin-health boot safe --profile desktop --apply
3. 找到元凶、修好之后,恢复原状 dsh-plugin-health boot restore --profile desktop --apply
boot safe 只往 profile 自己的补丁层追加 disabled: true: 不删文件、不动插件、宿主自带条目一律不碰。boot restore 只移除它自己加的条目,你自己的配置原样保留。两者默认都是 dry-run,加 --apply 才真正写。
你在找“插件管理器 / 插件检查 / 插件冲突检测”吗?
如果你在搜下面任何一个,那你要的就是这个:
| 你在找… | 这个包能做到 |
| DSH 的插件管理器 | 一页看全所有已安装插件及状态 |
| 插件检查 / 体检 / 扫描工具 | 扫描全部已安装插件,不是抽样 |
| 插件诊断 / doctor | 说清哪里坏了、该怎么处理 |
| 插件冲突检测 | 指出哪些插件在当前 DSH 版本上加载不了 |
| 想修复坏掉的插件 | 一键修复(只隔离不删除,随时可找回) |
| DSH 更新后插件突然不能用了 | 对比插件声明与新宿主要求 |
| plugin manager / plugin doctor / plugin checker / plugin conflict | 英文界面同样支持 |
不适合你:如果你想“逛市场、发现并安装新插件”——那是插件市场的活,这个包管的是你已经装了什么。
(不过官方 format-1 索引正是由它生成的,市场可以消费。)
为什么需要它:DSH 管住了安装,但没管升级
DSH 会拒绝新安装一个声明范围不匹配当前版本的插件,这块做得很扎实:
$ dsh plugin --profile test add dsh-session-recall
dsh: installation rejected: Plugin dsh-session-recall@0.7.9 is incompatible with dsh 0.2.1-rc.3
dsh: nothing was installed.
但它不会在宿主升级后重新检查已经装好的插件。一个声明 engines.dsh: 0.2.1-rc.2 的插件依旧留在 bundles 里、依旧在 0.2.1-rc.3 上加载,UI 里没有任何提示。这就是本工具要补的缺口:读出每个已安装插件自己声明的范围,和你实际在跑的版本做对比。
它已经抓到过真实问题
在一次宿主小版本升级中扫描 Top 120 插件,发现 5 个在 0.2.1-rc.2 上正常、到 0.2.1-rc.3 就被拒。
逐个提报后,作者们动了手——5 个里已有 4 个发布修复(2026-10-26 复测),受影响数 5 → 1:
已用发布的 0.1.47 manifest 和官方 rc.3 宿主兼容性评估器复核确认:19 个 DSH peer 被拒,而 rc.2 仍然通过。已在 #179 合入 main 修复……九个 PR CI 全部通过,其中新增了必需的 rc.3 Profile 检查。
— Anionex/dsh-vision-toolkit#178 (906★),因本次扫描而修复,已随 0.1.48 发布
| 插件 | 修复版本 | 状态 |
| @anionex/dsh-vision-toolkit | 0.1.48 | ✅ 已发布,rc.2 + rc.3 双版本复测通过 |
| dsh-plugin-subscriptions | 0.9.11 | ✅ 已发布,rc.2 + rc.3 双版本复测通过 |
| @nanmicoder/dsh-agent-teams | 0.1.27 | ✅ 已发布,rc.2 + rc.3 双版本复测通过 |
| @goodandready/dsh-clinebot | 0.6.16 | ✅ 已发布,rc.2 + rc.3 双版本复测通过 |
| dsh-workbuddy-connect | — | ⚠️ 仍为 0.7.9(仓库不可访问,是唯一连 issue 都提不了的一个) |
完整报告:docs/upgrade-0.2.1-rc3.md。
有 UI,但只在你需要时出现
把它作为 bundle 装进 profile,DSH 设置里会多出一个原生分区 设置 → 插件健康:
dsh plugin --profile web add dsh-plugin-health
- 它检查的是你机器上真实安装的每一个插件,不是抽样,也不只看 bundle 列表: bundle、package.json 依赖、cordis.patch.yml 的 insert(含嵌套)、node_modules 里所有插件形态的包。
页面顶部会直接告诉你这次检查了多少个。
- 每条问题都用大白话解释,并给出「怎么做」,第一次用的人不用读文档也能处理;
- 顶部一行结论告诉你该不该担心:可以放心用 或 有问题建议处理;旁边有 重新扫描 按钮随时重跑;
- 一切正常时什么都不显示:没有徽章、没有横幅、没有侧栏条目;
- 真的有问题时,账号菜单上方会出现一个 插件有 N 个问题 的小入口,点一下直接跳到设置分区;
- host 半身只提供 loopback 只读快照,不会修改你的 profile。
| 问题列表 + 结论行 + 一键修复 | 执行前先给你看修复计划 |
| 设置页里的问题与修复按钮 | 执行前展示的修复计划 |
准备升级 DSH?先跑一遍体检。
0.2.1-rc.3(2026-10-23)让 Top 120 里的兼容数从 118 掉到 113: 5 个在 rc.2 上正常的插件,到 rc.3 就加载不了,原因都是把 @deepseek-ai/ 版本写成了精确枚举。
截至 2026-10-26,其中 4 个已修复发版(受影响数 5 → 1)。
更早的 rc.2 也曾打崩另外 3 个。最新报告: docs/upgrade-0.2.1-rc3.md · docs/upgrade-0.2.1.md。
dsh-plugin-health matrix --plugins plugins.txt --versions 0.2.1-rc.2,0.2.1-rc.3
为什么做
DSH 没有官方插件市场索引;发行节奏约 2.1 天一个版本且带破坏性变更;官方插件管理器没有版本选择/升级动作;Windows/WSL 安装失败会留下残缺目录和残留锁文件。现有 doctor/市场工具只验证“本机当前安装的那一个 DSH 版本”,也不处理 Windows 文件锁和跨 profile 问题。
这个工具补三个缺口:
1. 多宿主兼容矩阵:插件能不能在 0.1.7-rc.2 / 0.2.0-rc.2 / 0.2.1-alpha.4 / 0.2.1-rc.2 上加载?使用官方语义精确判定(engines.dsh 优先,否则检查 @deepseek-ai/dsh peers;预发布参与 range;workspace: 映射到宿主版本)。
2. 官方 format-1 市场索引:把社区目录变成内置 Discover 能直接吃的 JSON 索引,并用 DSH Host 同款解析器验证。
3. Windows/WSL 安装诊断:默认只读;修复动作只隔离不删除,改 patch 保留用户注释。
安装
npm install -g dsh-plugin-health
或首发前本地运行:
git clone https://github.com/EasonMWS/dsh-plugin-health.git && cd dsh-plugin-health && npm ci
node bin/dsh-plugin-health.js --help
需要 Node.js 20+。
作为 DSH bundle 安装(可选)
这个包同时是一个可安装的 DSH bundle。装进 profile 后会提供一个只读的 plugin_health agent 工具,报告:声明但未安装的第三方 bundle、残缺的包目录、cordis.patch.yml 残留、代理/registry 配置、profile 差异,以及已装插件与当前 DSH 版本的不兼容。
dsh plugin --profile web add dsh-plugin-health
bundle 声明了 engines.dsh,不带 UI,自身不改变任何东西。
1. 兼容矩阵
dsh-plugin-health matrix \
--plugins examples/plugins.txt \
--versions latest,next,alpha,0.1.7-rc.2 \
--out dsh-health-out/matrix \
--fail-on any
- --plugins:文件(一行一个,支持 # 注释)、逗号列表,或重复参数;每项是 name、 name@version,或本地插件目录。
- --versions:精确 SemVer,或 @deepseek-ai/dsh 的 npm dist-tag(latest/next/alpha)。
- 输出:matrix.json、matrix.md、静态 SVG 徽章(badges/)。
- --fail-on any|none|<host>:直接用作 CI 门禁。
判定规则与官方 packages/boot/app-boot/src/plugin-compatibility.ts 一致:
| 插件声明 | 检查 |
| engines.dsh | 只看这个 range,忽略 peers |
| 无 engines.dsh、有 peers | 所有 @deepseek-ai/dsh / @deepseek-ai/dsh- peer range |
| peer 为 workspace:^ / ~ / | 视为当前宿主版本 |
| 宿主为预发布 | 参与 range 比较(includePrerelease: true) |
矩阵是静态判定(manifest + 官方兼容规则)。声明兼容不代表插件已激活、工具能执行;可以用下面独立、显式执行的 smoke 命令补充运行时验收。Git 插件仍可从本地目录做静态检查。
在全新 profile 中做运行时验收
先把目标宿主版本安装到独立目录,再验收用户实际安装的包,包括发布用的 tarball:
npm install --prefix /tmp/dsh-rc3 @deepseek-ai/dsh@0.2.1-rc.3
dsh-plugin-health smoke \
--dsh-dir /tmp/dsh-rc3/node_modules/@deepseek-ai/dsh \
--plugin ./my-plugin-1.0.0.tgz \
--tool my_plugin_tool \
--calls ./smoke-calls.json \
--out dsh-health-out/runtime
smoke-calls.json 是工具调用数组。调用必须成功;提供 expectedValue 时还会精确比较完整的结构化返回值:
[{"name":"my_plugin_tool","arguments":{"input":"example"},"expectedValue":{"answer":42}}]
命令使用全新的 DSH_HOME,通过官方 dsh plugin CLI 安装,经宿主公开的 @deepseek-ai/dsh/profile-boot 启动,然后检查工具注册、执行调用、释放 profile。
至少提供一个 --tool 或 --calls 断言。只有 --tool 时仅检查注册,不代表功能或浏览器 UI 已经验收。每个宿主版本分别运行一次。
安装默认最多 120 秒,启动、调用与退出合计最多 30 秒,可用 --install-timeout、--timeout 调整(毫秒)。超时会停止整个子进程树,包括一直不结束初始化的插件。报告保留失败阶段(install、boot、assertions、shutdown)与宿主错误,即使超时后的信号处理以 0 退出,这次验收仍然失败。
POSIX 下,父进程以 0 退出却留下后台子进程时,同样判定失败并停止其进程组。Windows 超时使用 taskkill /T;目前不能在父进程已经退出后检测其遗留子进程。
每次运行都保留独立目录中的 home、smoke.json、runtime.json 与有长度上限的输出日志,不会修改现有 profile,也不自动清理。分享前请检查日志。目标宿主需要自己的 Node.js、pnpm 运行要求,并需导出 profile-boot(已在 0.2.1-rc.2、0.2.1-rc.3 验证)。
此命令会以当前系统账号权限执行插件代码。 临时 DSH home 只隔离正常 profile,不是安全沙箱;只测试可信代码,不可信代码请放在容器或虚拟机中。运行时冒烟也不保证所有功能或 UI 都兼容。
实跑示例(2026-10-17,7 个插件 × 4 个宿主版本)
dsh 0.1.7-rc.2: 6/7 compatible, 1 incompatible
dsh 0.2.0-rc.2: 5/7 compatible, 2 incompatible
dsh 0.2.1-alpha.4: 5/7 compatible, 2 incompatible
dsh 0.2.1-rc.2: 5/7 compatible, 2 incompatible
✗ dsh-win32@0.17.14:四个宿主全不兼容(@deepseek-ai/dsh-subprocess-local 的 peer 上界过窄) ✗ dsh-find-plugin@0.4.0:从 0.2.0-rc.2 起不兼容(@deepseek-ai/dsh-tools peer)
第一条正好复现了 dsh-win32 issue #89(peer 上界过窄导致新宿主线被拒绝)的真实问题,而且不需要安装任何东西。
GitHub Action:每个 PR 自动贴兼容矩阵
仓库内置可复用 workflow。把 examples/dsh-compat.yml 放进插件仓库的 .github/workflows/dsh-compat.yml,之后每个 PR 都会收到一条兼容矩阵评论 + job summary,并且矩阵产物会自动上传为 artifact:
name: dsh plugin compatibility
on: pull_request
permissions:
contents: read
pull-requests: write
jobs:
matrix:
uses: EasonMWS/dsh-plugin-health/.github/workflows/matrix-review.yml@v0.1.5
with:
plugins: '.'
versions: 'latest,next,alpha'
comment: true
2. format-1 市场索引
dsh-plugin-health index \
--catalog https://awesome-dsh-plugin.com/plugins.json \
--host 0.2.1-rc.2 \
--index-url https://easonmws.github.io/dsh-plugin-health/index.json \
--matrix dsh-health-out/matrix/matrix.json \
--limit 300 --out site
命令生成 site/index.json(format: 1)和 site/build-report.md,然后调用 @deepseek-ai/dsh-plugin-market-index(DSH Host 实际使用的解析器)做严格校验。把目录部署到任意静态主机(GitHub Pages 即可,不能有重定向),再把 index.json 的 URL 填进 插件 → 自定义插件市场 → 发现 → 添加索引源。
索引特性:
- 市场与每个插件的标题/描述都带本地化(必须有 en,同时保留 zh);
- 分类来自社区目录,另有标签(npm、compatible、incompatible、unchecked、 capability risk)和排序键(下载量、Star、健康分);
- 健康分是确定性公式:热度(log 下载 55 分 + log Star 25 分)加减兼容性与能力风险,0–100;
- 每个插件默认列 3 个版本(--max-versions):dist-tag latest 优先,然后最高版本,带 npm 发布时间与 engines.dsh。
当前范围:只收社区目录里有 npm 包的条目(2026-10 快照里 2,607 / 5,704);GitHub/tarball-only 条目会跳过并在报告里计数,git 源(commit 固定)在路线图里。
3. Windows/WSL 诊断与修复
dsh-plugin-health doctor --home C:\Users\me\.dsh --profile desktop --runtime 0.2.1-rc.2
dsh-plugin-health repair --home C:\Users\me\.dsh --profile desktop --apply --yes
检查项:profile 声明但未安装的第三方 bundle(DSH 安装自带的 @deepseek-ai/ 视为已提供)、不可读/残缺的包目录、缺失的 profile 依赖、损坏或空的 cordis.patch.yml 条目、 .bak- / .lock / .lock.takeover-* 残留、过大的插件管理器日志、配了代理但没设 NODE_USE_ENV_PROXY=1、WSL 与 Windows 长路径、按 --runtime 检查已装插件是否兼容、以及 desktop/web 等 profile 之间的插件集差异。
修复必须显式 --apply,默认 dry-run,且只隔离不删除:
- 文件和目录移到 <home>/.plugin-health/quarantine/<时间戳>/,repair-log.json 记录每一步;
- cordis.patch.yml 只做文本级删除(顶层 - [] 条目),先备份,保留用户注释与格式。
路线图
- 浏览器插件的运行时验收(当前 smoke 探测 agent 工具);
- 支持 GitHub/tarball 目录条目的 commit-pinned git 源;
- 全目录矩阵 + 每晚构建并发布索引;
- DSH 内的插件面板(渲染矩阵与诊断结果);
- 可复用的 GitHub Action 与公开兼容性看板。
开发
npm ci
npm test
另外可针对已安装的宿主执行真实 tarball 验收(需要 pnpm):
npm run test:runtime -- /tmp/dsh-rc3/node_modules/@deepseek-ai/dsh
会打包并安装正常、等待缺失服务、初始化抛错、初始化挂起四类受控插件,保留 profile 与报告。
23 个单元测试覆盖官方兼容语义、format-1 构建(用官方解析器验证)以及基于 fixture home 的诊断/修复流程,不依赖网络。
免责声明
DSH 插件以用户完整权限运行。健康分、兼容标签、目录收录都不是安全审计。第三方索引未签名,只添加你信任的来源。
License
MIT预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
EasonMWS/dsh-plugin-health
DeepSeek Harnessdsh的插件体检 + 修复工具,带 DSH 内的原生设置页。
相关插件 · 市场 / 管理
插件精选列表awesome-dsh-plugin/awesome-dsh-plugin
DeepSeek Harness(dsh)插件精选列表
★ 22,225未汉化DSH
73.6
新词雷达zhuyansen/new-term-radar
new-term-radar新词雷达
★ 2,049已汉化MCP
72.8
插件生态导航0xsline/awesome-deepseek-harness
安装 贡献指南 DeepSeek Docs 公开插件目录 Issues
★ 1,188已汉化DSH
72.6
技能注册中心iflytek/skillhub
企业级开源智能体技能注册中心——发布、发现并管理组织内可复用的技能包。
★ 5,370已汉化DSH
71.9
插件市场目录imsai-sh/awesome-deepseek-harness-plugins
面向 DeepSeek Harnessdsh生态的社区插件目录,共收录 13941 个插件含 PR 收录与…
★ 355已汉化DSH
71.8
插件精选清单Anil-matcha/awesome-dsh-plugin
DeepSeek Harnessdsh精选指南——DeepSeek…
★ 1,013已汉化DSH
71.7
这是你的仓库?认领条目