dsh-plugin-surgery
给 DSH 插件一把手术刀:看得见影响面的安全卸载、一键回滚、重启确认与插件体检。
DeepSeek Harness 的插件"装容易卸难":pnpm remove 只删依赖,dsh.profile.bundles 里
的插件层条目、node_modules 里的孤儿包、pnpm 元数据残留全都要手工收拾;卸错了没有撤销;
卸完还要记得重启。dsh-plugin-surgery 把"卸载"变成一台有麻醉、有缝合的手术:
- 麻醉(dry-run):动手前先算清楚影响面——直接依赖、传递依赖、将被清除的孤儿、是否插件层;
- 缝合(快照/回滚):每次操作前自动快照,任何一步失败自动还原,卸错了随时一键回滚;
- 对账(三方校验):卸载后核对 清单(package.json)/ 锁文件(pnpm-lock.yaml)/ node_modules, 不一致自动回滚;
- 查房(restart-pending):操作结果落盘,重启后
pm_status确认生效; - 体检(pm_doctor):专治"卸不干净"留下的悬空 bundle 条目、孤儿包、锁文件漂移。
安装
# 推荐:npm 发布版
dsh plugin --profile web add dsh-plugin-surgery
# 或 GitHub 直装
dsh plugin --profile web add github:ruimin251204/dsh-plugin-surgery
# 本地开发
dsh plugin --profile web add file:/path/to/dsh-plugin-surgery
重启 dsh 后生效。工具名以 pm_ 为前缀(Plugin Manager 的手术刀)。
工具
| 工具 | 参数 | 作用 |
|---|---|---|
pm_list |
— | 列出 profile 插件现状:每个依赖是否激活为插件层、是否声明 dsh.bundle、孤儿包、待重启操作、快照数 |
pm_uninstall |
package(必填)、dryRun(必填)、confirm、force |
安全卸载。先 dryRun=true 预览影响面,再 dryRun=false, confirm=true 执行:快照 → pnpm remove → 同步 bundles 清单 → 三方对账,失败自动回滚 |
pm_rollback |
snapshot、confirm |
回滚依赖状态。不带参数列出快照清单(含 sha256 校验状态);带 snapshot=ID, confirm=true 还原文件并 pnpm install |
pm_status |
— | 待重启确认的操作、快照清单与完整性 |
pm_doctor |
fix |
体检:悬空 bundle、未激活的 bundle 依赖、锁文件不一致、孤儿包、待重启项;fix=true 自动对账 bundles 清单 |
标准用法
1. pm_doctor # 先体检,看有没有历史欠账
2. pm_uninstall(package=X, dryRun=true) # 预览影响面
3. pm_uninstall(package=X, dryRun=false, confirm=true) # 执行
4. pm_list / pm_status # 确认干净 + 记住要重启
5. (重启 dsh)
6. pm_status # 确认已生效;若异常 → pm_rollback
斜杠命令
除了对话指挥 agent,也可以直接敲命令(人机两用):
/pm list 列出插件与依赖现状
/pm status 状态:待重启操作、快照清单
/pm doctor [--fix] 体检(可选自动修复 bundles 清单)
/pm uninstall <包名> [--dry-run] 卸载(默认直接执行;--dry-run 仅预览影响面)
/pm rollback [<快照ID>] 不带 ID 列出快照;带 ID 回滚
/pm help 显示帮助
示例:
/pm doctor
/pm uninstall dsh-web-ui-all --dry-run
/pm uninstall dsh-web-ui-all
/pm rollback
/pm rollback 2026-08-16T14-22-47-200Z__fake-bundle-alpha
使用示例(直接对 agent 说人话)
安装后不需要任何配置——pm_* 工具会自动出现在 agent 的工具集里,你只需要在对话里说:
体检:
用 pm_doctor 给我的插件做个体检,有问题就修一下
卸载(推荐先预览):
把 dsh-web-ui-all 卸载掉,先 dry-run 看看会带走哪些依赖 (看完影响面)→ 确认卸载吧
回滚:
刚才卸载好像不对,回滚到最新快照
查状态:
pm_status 看看有没有待重启生效的操作
工具描述里已经写明了安全流程(dry-run → confirm),agent 会按流程执行,不会跳过确认直接动手。
安全模型
- 确认门:所有破坏性操作必须显式
confirm=true,且建议先 dry-run——不依赖宿主审批策略; - 保护名单:官方模板核心层(
@deepseek-ai/dsh-base/dsh-web-app/dsh-headless) 拒绝卸载(除非force=true),对账与体检也绝不把它们当残留处理; - 自动回滚:
pnpm remove失败或三方对账不过,自动从快照还原,不留下半吊子状态; - 快照校验:回滚前对快照文件做 sha256 校验,损坏的快照拒绝恢复;
- 只动 profile:所有操作局限于当前 profile 目录与
~/.dsh/surgery/状态目录, 不碰其他 profile、不碰会话、不碰凭据。
工作原理
pm_uninstall(X)
├─ 影响面:读 pnpm-lock.yaml → 计算 X 的传递闭包 − 剩余依赖闭包 = 孤儿集
├─ 快照:package.json + pnpm-lock.yaml + pnpm-workspace.yaml → ~/.dsh/surgery/snapshots/<id>/
├─ 执行:pnpm remove X(经 dsh subprocess,尊重沙箱)
├─ 对账:同步 dsh.profile.bundles(镜像 dsh 核心 reconcile 语义,跳过模板内置层)
├─ 校验:清单 / 锁文件 / node_modules 三方核对,失败 → 自动回滚
└─ 状态:写 ~/.dsh/surgery/pending.json,重启后 pm_status 确认
测试
node test/run-tests.js — 34 项集成测试,运行在隔离的临时 DSH_HOME + scratch profile 上,
覆盖:安装与自动对账、dry-run 影响面、确认门、保护名单、正式卸载(依赖/bundles/node_modules/
锁文件四清)、doctor 发现与修复、模板内置层保护、回滚、pnpm 失败自动回滚、三方对账、残留发现、
snapshots 段依赖图解析(pnpm 10+/11 锁文件格式)。
node test/run-real-drill.js — 真实包演练(需联网):在隔离 profile 里安装真实的
@linxin666/dsh-web-ui-all 全家桶(12 子包),验证影响面展开(12 直接 / 32 传递 / 32 孤儿)、
真实卸载(四清 + pending)、doctor 复检、快照回滚、二次卸载。17 项全过。
真实运行时验证(本机 dsh 0.1.0-rc.6 headless profile):
- 装入 bundle 后由 dsh 官方 CLI 自动纳入 bundles ✅
pm_doctor/pm_uninstall(dry-run + confirm)/pm_list/pm_status全链路 ✅pm_rollback快照恢复,插件层完整还原 ✅
已知兼容性处理:pnpm 10+/11 把依赖图放在锁文件的 snapshots: 段(旧版在 packages: 段),
解析器两者都支持;pnpm 11 的 ERR_PNPM_IGNORED_BUILDS(build 脚本未批准)是软失败,
包已装好,引擎视为成功不中断。
已知边界
- 影响面计算基于 pnpm-lock.yaml;
nodeLinker非 hoisted 布局下孤儿判定可能偏保守; - 卸载只对"已声明为依赖"的包生效;模板内置层从安装位置解析,天然不在卸载范围;
- 卸载/回滚后需重启 dsh 生效(插件无法重启宿主,这是平台约束,
pm_status会提示)。
路线图
- 核心引擎 + 5 个工具(纯本地,零外部服务)
- 集成测试 + 真实运行时验证
- Web 设置页(快照浏览 / 一键回滚按钮)
- 卸载前"将带走 N 个子包"的可视化确认弹层
- npm 发布
No comments yet. Be the first to write one.