dsh-plugin-console
August 28, 2026 · View on GitHub
研讨立项(2026-08-27,工作台项目 pmtbcss1x2ggr,模式①+②完成,当前③最小设计)
核心痛点:Cordis 插件树重启后静默不加载,且没有任何机制发现和恢复。
问题定义(一句话)
DSH/Cordis 插件加载依赖 5 道手工易碎的关卡(bundles 层栈 / patch insert / node_modules 落盘 / inject 声明 / 动态-静态同步),任一关卡在重启前悄悄失败(最常见:
dsh plugin add时 pnpm exit≠0 导致 reconcilePlugins 被跳过),重启后插件静默不加载——本项目做一个插件期望状态对账器,让插件加载从"手工装配、静默失败"变成"声明期望、自动对账、失败可诊断"。
5 道加载关卡(源码实证)
| # | 关卡 | 失败模式 | 源码依据 |
|---|---|---|---|
| ① | dsh.profile.bundles 层栈 | dsh plugin add 时 pnpm exit≠0 → reconcilePlugins 被跳过(if (exitCode === 0) reconcilePlugins(...)),包装了但没写层栈 | dsh/lib/plugin-9h8shc4d.js |
| ② | bundle 的 cordis.patch.yml - insert: 条目 | 手清/错位/丢失;home 层 $DSH_HOME/cordis.patch.yml 与 profile 层冲突(home 层优先级更高) | dsh-app-boot composeProfile |
| ③ | node_modules 落盘 | link: 残留、半安装、ESM 锁目录 | 本机卸载残留经验 |
| ④ | inject 声明 | ctx 严格代理:探测式访问未声明属性 → cannot get property without inject → 整棵树 boot 失败(非单个插件挂掉) | cordis/lib/index.js ReflectService.get trap |
| ⑤ | 动态/静态双轨同步 | dev 动态插件重启即失(vibecoding-workbench 教训:静态 bundle 滞后,重启回退) | 本机实战 |
架构设计(③最小设计)
plugins.intent.json(期望状态,~/.dsh/,profile 外)
├── dsh-plugin-console sync → 生成/核对 dsh.profile.bundles 层栈(M1)
├── boot 对账器(host 插件) → boot 后实测 loader 树 vs intent,缺失自动补写(M2)
├── 加载巡检 → fiber.state 巡检 0挂起/3错误/5卸载(M3)
└── heal 自愈 → 确定性修复 / 不确定给命令(M4)
组件划分
| 组件 | 形态 | 职责 |
|---|---|---|
plugins.intent.json | 状态文件(~/.dsh/) | 声明期望插件集合:{name, version-line, source, dynamic?} |
sync CLI | 独立 CLI(dsh-plugin-console bin) | 从 intent 生成/核对 dsh.profile.bundles;--dump-config 交叉验证 |
| boot 对账器 | host 插件(ctx.inject(['loader'])) | boot 后 loader.entries() 实测 vs intent;缺失 → 写回层栈(幂等);注入失败 → fail-loud 报告 |
| 巡检器 | host 插件服务 | 周期/按需巡检 fiber.state;标记依赖缺失(fiber.inject vs fiber.store) |
heal | CLI 子命令 | 确定性修复(补 bundles / 清 link 残留 / 重跑 pnpm add);不确定的(inject 缺服务、config 校验失败)只输出精确命令 |
关键设计决策
- 意图文件放 profile 外(
~/.dsh/plugins.intent.json):profile 目录本身是易碎区,不能把"修复清单"放在"被修对象"里。 - 对账发生在 boot 后、HMR 前:loader 树已 settle,用
ctx.get('loader')实测而非猜配置。 - 自愈只做确定性修复(幂等操作:写 bundles、重跑 reconcile、清 link 残留);不确定的只报告 + 给命令,避免自动改坏。
- 兼容动态/静态双轨:intent 里
dynamic: true的插件,sync 时提示同步静态 bundle(把 vibecoding-workbench 的坑变成流程护栏)。 - 纯函数决策层:intent 解析 / 层栈 diff / 修复计划生成 = 纯函数(可单测);文件写、pnpm 调用 = IO 边界。
技术约束(坑位预埋)
- ctx 严格代理:只声明注入的
loader;取路径用process.env.DSH_HOME,绝不探测式访问 ctx 属性。 - harness 工具 render 必须返回数组
[{type:'text',text}]。 dsh plugin add在 pnpm exit≠0 时跳过 reconcile —— 对账器必须独立于 CLI reconcile 自证。- 插件文件被 ESM 锁目录:改 bundle 前先移除 patch 条目再重启。
- 动态插件迭代后必须同步静态 bundle(update-bundle.ps1 流程)。
里程碑(已挂工作台③)
| # | 里程碑 | 状态 | 验收 |
|---|---|---|---|
| M1 | intent 声明 + sync CLI | ✅ 完成 | 生成层栈与 --dump-config 合成树一致 |
| M2 | boot 对账器(host 插件) | ✅ 完成 | 模拟 reconcile 被跳过场景,重启后自动补写层栈 |
| M3 | 加载巡检 + 状态报告 | ✅ 完成 | 故意制造 inject 缺失/挂起,巡检正确标记并指出缺哪个服务 |
| M4 | heal 自愈 + 五关诊断 | ✅ 完成 | 一键修复且幂等;修不了的输出可执行命令 |
| M5 | 真机验证(本机 web profile) | ✅ 完成(隔离环境) | 装/卸/改坏各场景重启实测通过 |
| M6 | 发布(publish=true) | ✅ 完成(2026-08-28) | GitHub 仓库 + Release v0.1.0 + BWH issue #147 + 官方 Discussion #4825 |
实现状态(2026-08-28,47 单测全绿)
真机验证记录(M5,隔离环境 DSH_HOME 实测)
- 最小 profile 独立 boot:
dsh plugin add link:...→ reconcile 正常写入层栈 →--dump-config合成树含 plugin-console 层 → 启动 web 成功(3080 监听)。 - 自愈闭环(核心验收):装 dsh-mnemon → 从层栈手动移除(模拟 pnpm exit≠0 跳过 reconcile)→ 启动 web → boot 后对账器自动把 dsh-mnemon 补写回层栈末尾,
.bak备份证明写回前后差异(写回前无 mnemon)。 - 幂等性:二次启动层栈不变、无新增写回。
- 真实环境还原:测试后已从真实 web profile 移除插件并清 link 残留,
--dump-config确认无残留。
真机踩坑(已入工作台教训栏)
- cordis 插件
Config必须是 standard-schema 对象:导出普通对象{type:'array'}时resolveConfig调Config["~standard"].validate抛 TypeError → 该 entry 失败 → AggregateError 拖垮整棵 loader tree(plugin tree failed to load: loader entries failed to apply),且 fail-loud 只跟 cause 链、不打印 AggregateError.errors,排查极难。解法:不导出 Config,配置走 cordis.patch.yml 的 config 字段。 - PATH 里的
dsh可能是 Desktop launcher(DSH Desktop\host-commands\desktop\bin\dsh.cmd),它忽略DSH_HOME且用打包版 cordis;真机隔离验证要用全局 npm 版:node <npm-global>/@deepseek-ai/dsh/lib/bin.js。
已实现
plugins.intent.json(~/.dsh/):v1 格式,{version, profiles: {web: {plugins: [{name, spec, dynamic}]}}}dsh-plugin-syncCLI(bin/sync.js):- 默认 dry-run 对账(期望 vs 实际层栈)
--apply幂等写回dsh.profile.bundles(.bak 备份,保留核心 bundle,缺失追加末尾)diagnose子命令:五关诊断 + 修复命令
- host 插件(plugin/host.js,boot 对账器):
- boot 后自动对账:
ctx.inject(['loader','tools']),实测 loader 树 vs intent,缺失自动补写 - agent 工具:
dsh_plugin_reconcile(对账+自愈)、dsh_plugin_inspect(fiber.state 巡检)、dsh_plugin_diagnose(五关诊断)
- boot 后自动对账:
- 纯函数决策层(lib/,全部可单测):intent.js / reconcile.js / reconciler.js / inspect.js / heal.js
五关诊断(M4)
| 关 | 检查 | 修复 |
|---|---|---|
| ① | bundles 层栈 vs intent | dsh-plugin-sync --apply 自动补写 |
| ② | bundle cordis.patch.yml 的 - insert: | 提示补回条目 |
| ③ | node_modules 落盘 + link: 残留 | pnpm add 命令 |
| ④ | inject 依赖缺失(fiber.state 0 挂起) | 巡检指出缺哪个服务 |
| ⑤ | 动态/静态双轨同步 | update-bundle.ps1 提示 |
目录规划(待④实现)
D:\HarnessSpace\dsh-plugin-console\
├── README.md ← 本文档
├── intent/ ← plugins.intent.json 定义与样例
├── cli/ ← sync / heal CLI(node:test 单测)
├── plugin/ ← boot 对账器 host 插件(bundle 结构,dsh 可装)
│ ├── host.js
│ ├── cordis.patch.yml
│ └── package.json
└── test/ ← 单测 + 真机场景脚本