Files
dsh_shenxian/dsh-server-docs/04-调整方案/34-功能插件启用探活与逐插件隔离.md
T
admin 5ad755116e chore(docs): 文档库并入代码仓(R4 选 a)+ 索引/台账跟进
1) dsh-server-docs/ 从工作区(原 E:\...\aliyun-dsh-server\dsh-server-docs)**整体并入本仓**,
   保留目录名 ⇒ 仓库内 dsh-server-docs/... 的相对引用天然继续有效;旧目录(含其 .git)已归档到
   工作区 _中间产物_待清理/,未随本提交带入。
2) .gitattributes:新增 `dsh-server-docs/** -text` —— 原文档库是 `* -text` + autocrlf=false,
   必须保持纯 LF,否则会被本仓的 CRLF 规则翻掉。
3) 活引用里的绝对路径已全部改到新位置(docs 的 INDEX / README / scripts / skills + 用户级 skills
   + ~/.workbuddy/settings.json 的 hooks);历史档案(04-调整方案/、archive/)按「只增不改」未动。
   ⚠️ hooks 路径改动需「完全重启会话」才生效(配置是会话启动快照)。
4) 交接单/T08:新增 §16「生产整体切换执行记录」(形态 / 落地动作 / **4 个只有真上线才暴露的真 bug** /
   验收证据 / 回滚命令 / 残留项);台账 T08 行 → 已完成并归档;03-路线图 §二 登记 T08 收尾项。
5) 统一称谓:**「本机」只指跑 WorkBuddy 的开发机**,47 / 106 一律写「远程服务器」。
2026-09-15 18:47:13 +08:00

4.6 KiB
Raw Blame History

34 · 功能插件启用:探活 + 快照回滚 + 逐插件隔离标记

  • 日期:2026-09-11
  • 触发:用户确认「批量 + 统一重启 + 刷新 + 死循环风险」,并明确「标记错误插件禁用并标记(让 admin 方便知晓),其余正常插件不需要回滚」
  • 状态:✅ 已实施(commit 0283c1b)并端到端验证通过(含「好插件保留」真实插件复核)

一、暴露的既有缺陷

/api/plugins/mine/apply 的异步任务原本是:

pnpm add 各插件 → reconcileBundles → restartMain → 直接标 success

全程没有任何探活 → 插件把实例搞崩(崩溃循环)时,任务照样报「✓ 已应用」,用户以为成功、实际实例起不来。档案 25 的事故(duplicate loader entry id)正是这一类。排查时还发现第二个更严重的既有缺陷 → 见档案 35。

二、设计(采纳用户方案)

① 快照 profile(package.json / pnpm-lock.yaml / cordis.patch.yml)
② 禁用项 → pnpm remove(不引入新代码,永远安全,不需探活)
③ 启用项 → 先「整批试一次」(正常路径只花 1 次重启,与旧行为一致)
     探活通过 → 完成
     探活失败 → ④
④ 回滚快照 → 逐插件隔离定位:能起来的保留,起不来的单独摘掉 + 记事故
     结束状态 = 快照 + good 集合   ← 好插件不被坏插件连累
⑤ 事故写入 audit_log(action='plugin_incident')
⑥ 任务结果新增 rejected[],stage/error 写明「哪些已自动禁用」

三、实现

文件 改动
orchestrator.ts 新增 restartAndProbe(userId, budgetMs=60000)
spawner.ts 接口加该方法
k8s-spawner.ts 兜底实现(k8s 未启用;返回 ok:true 保持旧行为)
business-plugins.ts apply 任务重写 + snapshotProfile/restoreProfile

探活判定的两个关键点(都是实测踩出来的):

  1. 不能只判 restartMain 的返回值 —— 编排器里没有该用户实例时(portal 重启清了内存态、或实例被空闲回收)restartMain 返回 undefined,会把无辜插件误判为不兼容。→ 改为:无实例时先按 /api/dsh/enter 同路径 launch 一个。
  2. 固定短等待会把好插件判死 —— 初版用「固定 2.5s 稳定窗口」,实测好插件也报「未产出 launch token」。→ 改为轮询(500ms 间隔、总预算 60s),拿到 token 且 status==='running' 后再过 2.5s 确认没闪崩。

四、验证(2026-09-11,端到端)

用两个自建 fixture 插件:good-test(合法 bundle)、bad-test(cordis.patch.yml 插入已存在的 loader id permission → 必然启动失败)。

检查项 结果
坏插件被准确诊断 ✅ 回传真实错误 TypeError: duplicate loader entry id: permission(cordis-plugin-loader/lib/index.js:91),非泛化文案
隔离循环执行 ✅ 阶段依次 整批试→定位(1/2)→定位(2/2)→恢复实例,耗时 3s→30s→33s→42s(真实重启+探活)
实例未进崩溃循环 ✅ 全程 instance 存活,最终回到干净状态
快照回滚生效 ✅ final bundles = 原始 5 个;deps 无残留
事故可追溯 ✅ audit_log.action='plugin_incident' 记录 pluginId + 真实 reason
好插件被保留 ✅ 已用真实插件复核通过(2026-09-11):从官方白名单导入真实 npm 插件 dsh-status-rotator,与 bad2(npm-pack 布局、仅 patch 有差异)一起启用 → 任务返回 完成:1 个已启用,1 个已自动禁用;bundles/deps 均含 dsh-status-rotator 且不含 bad2,plugin_incident 记录 bad2 的完整真实错误,实例存活。→ 「好插件不被坏插件连累」成立。此前两轮 fixture 失败纯属 fixture 自身问题(平铺 tar 布局 pnpm 不接受 / 包名与 patch 引用不一致)。

五、后续

  1. 用真实插件复核「好插件保留」 ✅ 已完成(2026-09-11):白名单导入 dsh-status-rotator + bad2 一起启用 → 好插件保留、坏插件被摘并标记。
  2. admin 可见性:事故已入 audit_log,但读取/聚合并未实现 —— 门户「插件管理」页应显示「⚠ N 个用户启用失败」。需要新增一个 audit 读取方法(adapter 目前只有写)。
  3. 重启前预检:应用后、重启前跑 dsh --profile web --dump-config,可 0 重启拦住配置类错误(未做)。
  4. 前端:location.reload() 与被禁用插件的展示,随批次 3(client bundle 改造)一起做。