DSH 插件冲突事件复盘

事件窗口 2026-09-05 ~ 09-06 三次连续启动崩溃 环境 Windows / DSH (Cordis) / pnpm profile web 改进 E1–E6 已全部落地

一句话摘要

三天内 DSH 连续三次启动闪退,表象各异(缺包 → API 导出缺失 → 同名服务重复注册),是三层不同根因叠加:一次依赖树损坏、一次 dsh 生态 API 版本漂移、一次第三方全家桶插件与官方内置插件的服务冲突。每次都以「报错原文 → 本机实证 → 最小修复」闭环解决;最后一次冲突与 插件冲突管家(P2M) 要治理的领域直接相关,暴露了 P2M 可落地的改进方向——该方向已逐条实施为 E1–E6(见文末落地记录)。

事件时间线

09-05 16:20 · 依赖树损坏(事故)
profile 的 node_modules/.pnpm 被清空,顶层 @deepseek-ai/ 变空壳;lockfile 同步丢失 dsh-settings 实体解析。
09-06 上午 · 第 1 次崩溃
pnpm install 两次「成功」但崩点不变 → 揭穿 pnpm 状态文件信任快路径。
09-06 · 第 2 次崩溃
强制重建 231 包后仍崩 → 真因浮出:dsh-settings API 版本漂移(settingsNamespace 导出被新版移除)。
09-06 · 第 3 次崩溃(本次核心)
钉回兼容版 dsh-settings@0.1.1-rc.2 后,@morlay/session-rdb 终于能加载 → 与官方 JSONL 后端抢注同名服务 sessionPersistence → Cordis 拒绝。

第一幕 · 依赖树损坏(已修复)

崩因①
node_modules 实体被抹除,只剩「装过的记忆」

证据:.pnpm 仅剩 lock.yaml(mtime 09-05 16:20);顶层 @deepseek-ai/ 空壳;而 pnpm-lock.yamlpackage.json、全局 store(758MB)均完好。

为什么越修越糟:pnpm 以 .modules.yaml + lockfile 判断「是否 up to date」,不 stat 磁盘实体 → 普通 pnpm install 永远走快路径跳过,实体永不补齐。

修复:ren node_modules node_modules.bak-…(改名备份而非删除)→ pnpm install 强制重建,231 包从 store 硬链,7.4s 完成。

第二幕 · dsh-settings API 版本漂移(已修复)

崩因②
模块「找到了」但版本不对 → 报 export 缺失而非 MODULE_NOT_FOUND

崩溃点:@morlay/session-rdb@0.0.11 按旧 API 写:import { settingsNamespace } from "@deepseek-ai/dsh-settings"

  • 它要的版本:peer ^0.1.1-rc.2 —— 该版本确有 settingsNamespace(下载 tarball 实证);
  • 实际解析到的:profile 本地实体丢失后,Node 一路爬升命中全局 CLI 内置dsh-settings@0.1.2-alpha.3 —— 该版本已移除该导出(改为 SettingsProvider 等);
  • 诱因:9-05 那次损坏把 lockfile 中 dsh-settings 的 resolution 条目弄丢(只剩 peer 声明)→ 重装永不安装它。
修复:profile 显式 pnpm add @deepseek-ai/dsh-settings@0.1.1-rc.2 → 本地实体 + lockfile resolution 齐备 → Node 解析改命中本地兼容版。经验:报「does not provide an export」≠ 缺包,是解析目标版本错;用 createRequire(...).resolve() 实证解析路径。
上游注:morlay 在 session-rdb@0.0.13 已适配新 API(peer 改 ^0.1.2-rc.1)——pre-1.0 生态 API 破坏性变更频繁,是此坑的温床。

第三幕 · sessionPersistence 服务重名冲突(本次核心,已修复)

崩因③
两个会话持久化后端同时注册同名 Cordis 服务

报错:service "sessionPersistence" has been registered at <JsonlSessionPersistence>

冲突方身份提供者注册服务
官方 session-persistence-jsonldsh-base 内置默认后端JsonlSessionPersistencesessionPersistence(同名 → Cordis 拒绝重复注册)
web-ui-session-rdbweb-all 全家桶经 @morlay/better-session 引入的 RDB/SQLite 后端SessionPersistenceRdb

关键证据(定位到根因的决定性一步):

  • web-all 聚合 patch(cordis.patch.yml,AUTO-GENERATED)末尾自带注释与默认值:
    # inactive by default: the rows above ship disabled, so the stock persistence
    # backend keeps serving sessions until you opt in.
    - id: web-ui-session-branch
      disabled: true
    - id: web-ui-session-rdb
      disabled: true
    - id: web-ui-conversation-message-actions
      disabled: true
    morlay RDB 后端出厂即禁用——官方 JSONL 继续服务会话,要换 RDB 需显式 opt-in;
  • 但 profile 层 cordis.patch.yml 为了开启全家桶 UI,把所有 web-ui-* 整批设为 disabled: false,把这三个休眠条目也唤醒了 → 双后端抢注同名服务。
修复(已执行,备份 cordis.patch.yml.bak-20260906-113229):web-ui-session-branch / web-ui-session-rdb / web-ui-conversation-message-actions 三行改回 disabled: true,其余 20 项全家桶 UI 一概不动。
若确实要用 RDB 会话存储:正确姿势是「二选一」——三件套置 false 并追加 - id: session-persistence-jsonl / disabled: true 停用官方 JSONL 后端,而非两个都开。

三幕小结:为什么每次都「看似无解」

表象误导方向破局手段
缺包报错「再 pnpm install 一次」.modules.yaml,识破状态文件信任快路径
export 缺失「包没装全」createRequire.resolve() 实证解析目标 = 全局 CLI 旧版
服务重复注册「依赖又坏了」读三方 patch 层,发现「出厂禁用被用户层覆盖」
共同教训:DSH pre-1.0 的 loader 补丁层 + pnpm 状态文件 + API 快速漂移三者叠加,任何「凭经验重试」都会踩空;每次都要回到文件系统与源码实证

对插件冲突管家(P2M)的改进建议 → E1–E6 已全部落地 (v0.1.3 ~ v0.1.6)

本次事件中两幕(② 版本漂移、③ 同名服务)正是 P2M 声称治理的「插件冲突」范畴,但现有检测面覆盖不到。以下改进点已逐条实施、配套单测与真实冒烟验证,并按「GitHub main + 市场留痕」双端同步,见文末实施记录表。

E1同名服务注册预检(新冲突类 C7)已实施
lib/conflicts.js · lib/policy.js · lib/index.js · 对应本次第三幕

静态扫描每个 entry 目标包入口的 Cordis 服务注册面(super(ctx, "…") / provide("…"),连带一跳依赖包),检测「不同 entry 提供同名 service」并提前报告——本次 sessionPersistence 双注册在启动前即可被识别;命中官方 known-core 服务(sessionPersistence)时给出「二选一」修复建议。7 个单测全绿;真实冒烟:对 web-ui-session-rdb 扫出注册 sessionPersistence 撞 core 默认后端。

E2依赖解析路径留痕(resolve 快照)已实施
lib/state.js · lib/index.js · 对应本次第二幕

boot 与每次对账记录各 entry 的 require.resolve 实际解析路径与版本(含是否命中 profile 本地);一旦出现「本地缺失 → 爬升到全局 CLI」这类解析漂移,写 resolve-drift incident 并给出钉版本建议,快照持久化到 state.json#lastResolve。5 个单测全绿;真实 profile 4 个 spec 全部 local=true 且版本正确。

E3peer 版本合规预检(启动前体检 preflight)已实施
新增 lib/preflight.js · launcher/dsh-safe.mjs · 对应本次第二幕

启动器拉起 DSH 前调用静态体检:核对每个 entry 声明的 peerDependencies 区间与实际解析版本(内置零依赖 semver 子集,prerelease 按同 [major.minor.patch] 元组规则判定),版本越界即报告/给出钉版本命令,DSH_P2M_PREFLIGHT=block 可拒启;同时暴露 ctx.p2m.preflight()。7 个单测全绿;真实 profile 首跑即抓出 2 条现存漂移(dsh-settings 的 peer brand/invariants 爬到全局 0.1.2-alpha.3)。

E4profile 层覆盖感知已实施
lib/conflicts.js · lib/index.js · 对应本次第三幕

boot 自动发现 profile 与各 bundle 的 cordis.patch.yml 分层扫描:检测到「出厂 disabled:true 被用户层 disabled:false 覆盖」时直接输出「二选一」建议(本次就是「全开清单覆盖了出厂禁用」);双 overlay 分歧同样告警;C4 收紧为「同形态重复」,insert+overlay 合法习语(web-all)不再误报。5 个单测全绿;真实扫描 26 → 23 命中且无 error。

E5冲突速查表与文档同步扩展已实施
DESIGN.md · README.md / README.en.md · AGENTS.md

C7 类入冲突矩阵(§6.1)与 README 冲突速查表(补「同名服务 / 版本漂移」两行);配置表补 knownCoreServices / c7ScanIntervalMs / preflightOnBoot / scanBootLayers;API 表补 preflight();AGENTS.md 沉淀本次「pnpm 状态文件信任」「export 缺失 ≠ 缺包」「service registered = 出厂禁用被覆盖」「cordis 注册字面量两类」四条排障心法。全仓单测 74 个全绿。

E6(非代码)上游反馈已反馈
dsh 生态协作

web-all 聚合脚本把 better-session 的 insert 行与「出厂禁用」行拆开生成、导致 opt-in 语义割裂;dsh-settings 破坏性改名无迁移提示。已以 issue / discussion 形式反馈:zhu1090093659/dsh-web#1394(经本机 package.json 元数据锁定上游实为 zhu1090093659/dsh-web,非 @linxin666 个人仓库)、deepseek-ai/deepseek-harness Discussion #5771(该仓库关闭 Issues,改走 Discussion)。

E1–E6 实施与同步记录

条目版本 / 提交核心改动文件验证同步方式
E1 · C7 同名服务预检v0.1.3 · 9aa2273conflicts.js / policy.js / index.js + test/c7-service-clash.test.mjs(7 例)真实扫出 web-ui-session-rdb 注册 sessionPersistence 撞 coreGitHub:push main;市场:2BingLing/dsh-market#127 评论留痕(catalog 每日 06:00 自动刷新)
E2 · resolve 快照v0.1.4 · dd90b28state.js / index.js + test/e2-resolve-snapshot.test.mjs(5 例)真实 profile 4 spec 全 local
E3 · preflight 体检v0.1.5 · 8faf4bf新增 preflight.js / index.js / launcher/dsh-safe.mjs + test/e3-preflight.test.mjs(7 例)真实首跑抓出 2 条现存漂移
E4 · profile 覆盖感知v0.1.6 · e9420c3conflicts.js / index.js + test/e4-profile-override.test.mjs(5 例)真实 patch 层 26 → 23 命中、无 error
E5 · 文档同步309d3f0DESIGN.md / README.md / README.en.md / AGENTS.md全仓 74 单测全绿
E6 · 上游反馈本页终稿docs/incident-2026-09-06.htmlissue / discussion 均已建dsh-web#1394 + deepseek-harness Discussion #5771
同步说明:插件市场(dsh-market)收录本插件后由每日 06:00 自动管道从仓库 HEAD 重扫刷新 catalog(收录 issue #127 已 closed,bot 明言「仓库本身无需改动」),因此市场同步 = 推送 GitHub main + 在 #127 评论留痕,两处保持一致。

附录 · 关键文件与备份

对象路径状态
修复后的 profile 补丁~/.dsh/profiles/web/cordis.patch.yml三件套已置 disabled:true
改动前备份~/.dsh/profiles/web/cordis.patch.yml.bak-20260906-113229可回滚
依赖损坏备份~/.dsh/profiles/web/node_modules.bak-20260906-1108/重建后可删(删除需确认)
钉回的兼容版本dsh-settings@0.1.1-rc.2(已入 profile deps + lockfile)勿用 pnpm install 冲掉
P2M 状态目录~/.dsh/p2m/(guard / usage / incidents / state)正常运行

复盘依据:本机文件系统实证 + registry tarball 导出面对比 + 三方 cordis.patch.yml 原文;所有结论均有命令级证据,非推测。