上周我的 DeepSeek Harness 桌面版崩了。不是闪退,是每次启动都崩,最后连欢迎页都打不开。查了半天,根因竟然是一个第三方插件的依赖声明方式。这件事让我重新理解了「插件生态」和「宿主运行时」之间的边界有多脆弱。

崩溃现场

崩溃日志很一致:

Error: desktop welcome: Web RPC failed
    at lib/main.js:16875
    at settingsAndReference (16880)
    at read (16903)
    at openInitialWindow (21232)

翻译一下:桌面启动时要打开欢迎页,欢迎页要调 settings/describe 读取设置,而设置服务不存在,于是抛错,整个应用崩掉。

根因

当时我装了一个 QQ 机器人插件 dsh-qqbot@1.0.2。它的 package.json 里是这样写的:

"dependencies": {
  "@deepseek-ai/dsh-settings": "^0.1.0-rc.5",
  "@deepseek-ai/dsh-credentials": "^0.1.0-rc.5"
}

注意:它把 DSH 官方的设置和凭证服务声明成了普通依赖,而不是 peer 依赖。

而 DSH 的 profile 使用 nodeLinker: hoisted,pnpm 会把这些包提升到 profile/node_modules/@deepseek-ai 下。结果就是:插件自带的 0.1.0-rc.8 版本,遮蔽了运行时自带的 0.2.2-rc.1 版本。

加载器做版本校验时发现:0.1.0-rc.8 要求 dsh-brand / dsh-invariants 为 ^0.1.0-rc.8,而运行时是 0.2.2-rc.1 → 判定不兼容 → 直接禁用了 settings 插件行。

于是欢迎页调不到设置服务,崩溃。

更狠的一层

崩溃之后,DSH 的「致命恢复机制」启动了:DesktopFatalRecovery.disableAllPlugins()。它做了两件事:

  1. 把 cordis.patch.yml 改名成 cordis.patch.yml.bak-<时间戳>
  2. 把 package.json 里的 dsh.profile.bundles 重置为只保留 2 条官方 bundle

也就是说,所有额外插件被一锅端禁用了。这时候光修依赖还不够,还得把插件列表恢复回来。

修复

两步:

  1. 在 pnpm-workspace.yaml 里加 overrides,把被提升的官方包钉回运行时版本:
overrides:
  '@deepseek-ai/dsh-settings': 0.2.2-rc.1
  '@deepseek-ai/dsh-credentials': 0.2.2-rc.1
  '@deepseek-ai/schemastery': 3.19.0
  '@deepseek-ai/cosmokit': 1.9.0
  1. 用运行时自带的 pnpm 重装,并把 dsh.profile.bundles 恢复成完整列表。

修完启动,25 秒后进程还活着,崩溃日志没新增。

教训

  • 插件的依赖声明方式,决定了它会不会搞崩宿主。 凡是宿主已经提供的服务,插件必须写成 peer 依赖,不能写成普通依赖。
  • 版本兼容性是双向的。 不是插件单方面适配宿主,宿主升级也要考虑老插件的声明范围。
  • 崩溃恢复机制是「核弹级」的。 它一键禁用所有插件,虽然能救急,但也会让你误以为「所有插件都坏了」。
  • 改动前先备份。 cordis.patch.yml 和 package.json 各留一份,出问题能立刻回滚。

如果你也在做 DSH 插件,请一定检查自己的 dependencies 里有没有 @deepseek-ai/*。这个坑,我替你踩过了。