DeepSeek Harness 插件装上了不生效:重名注册、realm pending 与重载报错

故障排查发布于 2026-10-17作者: DeepSeek Plugin 插件市场
DeepSeek Harness插件不加载already registeredwaiting for servicecordis.patch.ymlskill 注册koffi
DeepSeek Harness 里「插件装了但没反应」有几类固定形态:工具重名注册让整条插件静默卸载、跨 realm 的服务依赖让条目永久 pending、配置重载后 resolver 已注册与热重载静默丢掉排队消息让新会话建不出来。本文给出判据与用户侧规避。

DeepSeek Harness(DSH)里「插件装了但没生效」是一类特别费时间的故障,因为它往往同时缺三样东西:没有报错、没有日志、也没有界面提示。 本批讨论把这一族拆成六种固定形态,共同点是失败发生在插件激活(apply / 注册)阶段,而不是插件被调用时:工具重名注册让注册表抛错、整条 fiber 被卸载,错误只写进日志服务而出厂 profile 没挂日志输出端,于是彻底静默;插件声明的服务依赖跨了 realm,条目永久停在 pending (waiting for service: …);ctx.skills.register() 不给 source 填默认值,而读取端要求它是字符串,于是注册的 skill「出现在列表里却加载失败」;配置重载时 file-upload 的 resolver 引用比对失效、抛出 already registered,此后新会话再也建不出来;热重载走 dispose 路径时取消了回合又清空了收件箱,把排队消息静默丢掉;Windows 上 npm 安装的 CLI 还会因为 koffi 进程级类型重名而启动就静默失败(#9308、#8968、#9425、#9550、#9711、#9843、#9553)。

DeepSeek Harness 工具重名注册:插件整体失效,而且彻底不报错

一句话结论:重名注册会让注册表抛错,插件的 apply 抛错会导致整条 fiber 被卸载(全有或全无),而这个错误只进了日志服务——出厂 profile 没挂日志输出端,桌面端 stderr 也只留在内存环形缓冲里,所以「市场显示已启用、工具一个都调不到、日志 0 命中」。(#9308)

DeepSeek Harness 三步定位

  1. 确认「工具与系统提示注入段一起消失」:只挂一个插件时消失的可能是一个工具;挂多个同类插件时可能整批消失。「整批」是关键信号,因为单点失败不会整批消失(#9308)。
  2. 把被吞掉的错误逼出来:从终端启动宿主(而不是从桌面图标),让 stderr 可见;预期能看到 … is already registered 一类的注册期错误(#9308)。
  3. 用诊断插件确认真实原因:社区插件 @argszero/dsh-activation-doctor 会检测激活失败并报告原因(#9308):
bash
npm i @argszero/dsh-activation-doctor

DeepSeek Harness 处置

  1. 找出重名的那两个插件:逐个禁用可疑插件、每次只重开一个,观察工具是否回来——二分法比读日志快(#9308)。
  2. 给其中一个改工具名,或只保留一个。
  3. 验证:重开后确认工具与注入段同时回来,才算真正修好——只看到「插件已启用」不算(#9308)。

DeepSeek Harness 重复注册:Agent resolver is already registered

一句话结论:注册函数在销毁回调里做引用相等判断,但被比较的对象经过代理包装、每次读取都返回新包装,于是这个判断永远是假、销毁回调等于死代码;结果就是每次重载都重复注册,抛错后新会话永久建不出来。 触发条件很宽——对 cordis.patch.yml 的任意改写(甚至字节相同的重写)都会触发一次重载(#8968)。

DeepSeek Harness 四步处置

  1. 确认判据:报错文本是 file-upload: Agent resolver is already registered,现象是新会话永久建不出来(已有会话可能仍可用);重启进程后恢复(#8968)。
  2. 应急:重启宿主进程——这是唯一能立刻恢复的手段(#8968)。
  3. 别在「顺序 dispose / 重建看起来正常」上放心:讨论里强调那只是假象,销毁回调虽然会执行,但引用比对恒定失败,所以注册不会被撤销(#8968)。
  4. 长期规避:把注册身份绑到一个普通对象(token)而不是函数上,这样引用比较才成立;社区已有实测补丁(#8968)。

DeepSeek Harness 跨 realm 的服务依赖:条目永久 pending

一句话结论:插件声明 inject: ['compaction'] 之后条目永久停在 pending (waiting for service: compaction),不是因为服务没装,而是因为 provider 与 consumer 不在同一个 realm——provider 在 agent preset 的分组里,插件在 profile 层,跨 realm 不可达是设计使然。(#9425) 不过条目停在 pending、看起来像「装上了却不生效」时,也值得回头确认安装侧有没有真正收尾,这一步可以对照 DeepSeek Harness 插件装不上与卸载事故:add 挂起、旧版不兼容与 junction 删源码。

DeepSeek Harness 三步定位与处置

  1. 判据一:看命令列表而不是服务。如果 commands.list() 里有压缩命令(说明后端在运行),而 ctx.get('compaction') 取不到服务,那就是 realm 边界问题(#9425)。
  2. 判据二:注意属性读取的陷阱。直接读 ctx.compaction 有时会返回字符串而不是 undefined,容易让人误判「服务在,只是用不了」。这也是为什么渠道 pending 与 disabled 两种外观不同、表现却都是「服务不存在」(#9425)。
  3. 处置一(推荐):改用命令方式执行,而不是依赖服务依赖注入:
js
// 插件里改用命令通道,而不是声明 inject 服务
await ctx.commands.execute(agent, '/compact', [], signal)
  1. 处置二:把压缩后端装到与插件相同的层级;讨论中确认,把后端移到同层后 ctx.compaction 立即可用(#9425)。
  2. 验证:重开后确认条目不再是 pending,且 ctx.get('compaction') 能拿到服务对象(#9425)。

DeepSeek Harness 插件注册的 skill 加载失败:写入路径缺默认值

一句话结论:ctx.skills.register() 允许省略 source,但运行时校验又要求它必须是字符串——写入路径校验的形状与读取路径要求的不一致,于是「注册成功、加载失败」。 报错是 loaded skill "<name>" source must be a string(#9550)。

  1. 确认:skill 出现在列表里,但一被调用就报上述错误;说明注册成功、加载失败(#9550)。
  2. 插件侧规避(最省事):注册时显式传 source:
js
ctx.skills.register({ name, description, content, invocation, source: 'runtime' })
  1. 验证:重开后调用该 skill,预期不再报 source must be a string(#9550)。
  2. 上游两条修法(择一):给 source 一个默认值(如 'runtime'),或在注册时就拒绝缺 source 的调用——让校验发生在写入时而不是读取时,是讨论里指出的根治方向(#9550)。

DeepSeek Harness 热重载丢排队消息:事件有回执,界面没订阅

一句话结论:热重载走 dispose 路径时以 {kind:'disposed'} 取消回合,而取消会先清空收件箱再中止,于是排队消息无声消失;事件里其实已经发了「被丢弃」的回执,只是 UI 没有订阅它,所以看起来像是你主动取消。 触发窗口很具体:改 dsh.profile.bundles 或从界面装卸插件后的约 2.8 秒内(#9711)。

  1. 确认判据:回合被中止、排队消息消失,而事件里出现的是 outcome: "canceled"——措辞误导是这条的典型特征(#9711)。
  2. 规避:不要在回合运行中改 bundles 相关配置或装卸插件;等回合结束再动(#9711)。
  3. 最小修法(核心侧):让 dispose 路径也传 keepInbox: true——用户主动停止那条路径本来就传了它,热重载这条漏了(#9711)。
  4. 验证:改配置重载后,预期排队消息仍在队列中;若 UI 仍不说清楚,可以把「事件已发、UI 未接」这条记下来作为判断依据(#9711)。

DeepSeek Harness 两个「看起来无关」但同族的坑

DSH插件 这两处失败同属一族:都发生在「插件与宿主」的交互边界上——一处把合法的关闭 veto 当成错误、还让相邻插件的 openTab 静默失效,另一处让同一原生模块的两份副本在进程级类型登记簿上撞名,共同点是报错都不指向真正的原因。

DSH插件 合法的 close handler veto 被当成错误刷屏

一句话结论:插件用「抛错以保留标签页」是文档化的 veto 通道,但关闭路径把它包进了 try/catch { console.error(...) },于是每次合法 veto 都打一条 error;更严重的是 place() 的替换分支没有 try/catch,一个插件的 veto 会让另一个插件的 openTab 静默变成 no-op。(#9843)

  1. 确认:控制台出现「合法关闭却打 error」的噪声,且伴随另一个插件打不开标签页(#9843)。
  2. 处置:把 veto 与真失败分开——veto 不应记 error;place() 的替换分支补上 catch(#9843)。
  3. 注意:调整日志级别会改动测试契约,改动前先看相关测试(#9843)。

DSH插件 Windows 上 CLI 启动就静默失败

一句话结论:安装树里同一原生模块有多个副本时,两份先后被加载,第二份在 koffi 的进程级类型登记簿上撞名,于是条目初始化失败、整棵插件树报「无法解析」,而错误又不带真实异常。 报错文本类似 plugin tree failed to load … could not be resolved(#9553)。

  1. 确认:检查是否有多份同一原生模块副本(常见于多次安装叠加的旧环境)(#9553)。
  2. 处置(最直接):清掉冗余副本后重装;全新安装的环境通常只有一份,不中招(#9553)。
  3. 把真实异常挖出来(可复用):挂一个诊断插件,在加载上下文里直接调用加载器并 try/catch 写文件,就能拿到被吞掉的原始异常;也可以从日志服务的输出端回捞条目级错误(#9553)。

DSH插件 排查注意事项

这一族的排查主线是:先判断失败发生在「激活期」还是「调用期」——激活期失败表现为「装了但完全没作用」,调用期失败才有具体报错。 九条要点:

  1. 「整批工具一起消失」是激活期失败的指纹,别按单个工具去查(#9308)。
  2. 从终端启动能看到被吞掉的错误,这是成本最低的一步(#9308)。
  3. 「市场显示已启用」不构成证据:它反映的是安装状态,不是激活状态(#9308)。
  4. 重载类故障先重启再分析:already registered 那一类,重启是唯一即时恢复手段(#8968)。
  5. pending (waiting for service) 先看 realm 边界,再看服务到底装没装(#9425)。
  6. 属性读取会骗人:ctx.compaction 可能返回字符串而不是 undefined,判据用 ctx.get()(#9425)。
  7. 注册接口要「写入即校验」:skill 的 source 就是一个典型反例(#9550)。
  8. 别在回合运行中装卸插件:热重载那 2.8 秒窗口会吃掉你的排队消息(#9711)。
  9. 修复状态如实:重名注册的可见性、resolver 引用比对、realm 可达性、source 默认值、dispose 的 keepInbox 在讨论所指版本里都未修;本文给出的均为用户侧规避与社区插件,其中只有「显式传 source」「改名/只留一个插件」「重启宿主」「避免运行中改配置」是可立即执行的。

排查「插件装了不生效」时,最耗时间的其实是确认环境里到底挂了多少东西。与其在各处手敲命令,不如用 DSH Plugin Hub 这类可视化插件市场:已安装列表带来源标记、装卸历史与系统日志集中在一个面板里,先看清「装了什么、什么时候装的、有没有报错」,再动手会快很多。

已安装插件列表

来源:Discussion #9308、Discussion #8968、Discussion #9425、Discussion #9550、Discussion #9711、Discussion #9843、Discussion #9553。

常见问题

DeepSeek Harness 插件市场里显示某插件已启用,但工具一个都调不到、日志也空白,怎么查?

DeepSeek Harness 出现「显示已启用但工具调不到」,优先怀疑工具重名注册。注册表对重名工具会抛 already registered,而插件在 apply 阶段抛错会导致整条 fiber 被卸载——也就是全有或全无;更麻烦的是这个错误只写进日志服务,出厂 profile 没挂日志输出端,桌面端的 stderr 又只留在内存环形缓冲里,于是表现成彻底静默。可行的做法是从终端启动宿主以便看到 stderr,或使用能检测激活失败的诊断插件。

DeepSeek Harness 插件条目一直显示 pending (waiting for service: compaction),但服务明明装了,为什么?

DeepSeek Harness 插件条目永久 pending,是因为那个服务与插件处在不同的 realm 里。压缩后端在 agent preset 的分组 realm 内,而插件是 profile 层的条目,跨 realm 不可达是设计使然;判据是 commands.list() 里能看到 compact 命令但 ctx.get('compaction') 取不到服务。规避办法是改用命令方式执行压缩,或把压缩后端装到与插件相同的层级。

DeepSeek Harness 插件用 ctx.skills.register() 注册的 skill 出现在列表里却报 source must be a string,为什么?

DeepSeek Harness 的 skill 注册后加载失败,是因为注册接口不会给 source 填默认值,而运行时校验又要求它必须是字符串——写入路径与读取路径对形状的要求不一致。插件侧最省事的规避是显式传 source: 'runtime';根治方向是让注册接口给出默认值,或在注册时就拒绝非法形状而不是延后到加载时报错。

DeepSeek Harness 只是改了 cordis.patch.yml,之后新会话全建不出来并报 Agent resolver is already registered,怎么办?

DeepSeek Harness 新会话建不出来,是引用比对失效导致的重复注册。注册函数在销毁回调里做引用相等判断,但那边的服务是经过代理包装的,每次读函数属性都会返回新包装,于是这个判断永远是假、销毁回调等于死代码。绕过办法是重启宿主进程;长期修法是把注册身份绑到一个普通对象而不是函数上,这样引用比较才成立。

DeepSeek Harness 改配置重载后正在跑的回合被中止、排队消息不见了,事件还说是主动取消,怎么避免?

DeepSeek Harness 热重载丢排队消息,是因为热重载走 dispose 路径时取消了回合却清空了收件箱。事件里其实已经有被丢弃的回执,只是界面没有订阅它,所以看起来像用户主动取消。最小修法是在 dispose 路径也传 keepInbox,让排队消息保留;在那之前,避免在回合运行中改 bundles 相关的配置。

相关术语

realm
realm 是 DeepSeek Harness 里服务注册与解析的作用域边界,provider 与 consumer 只有在同一个 realm 内才能互相看见。agent preset 的分组与 profile 层属于不同 realm,因此在 preset 内注册的服务默认对 profile 层插件不可达。— https://github.com/deepseek-ai/deepseek-harness/discussions/9425
fiber
fiber 是 cordis 插件系统里的一个插件实例运行单元,插件的 apply 抛错会导致整条 fiber 被卸载,且其注册的工具与系统提示注入段一起消失,表现为「插件已启用但完全没作用」。— https://github.com/deepseek-ai/deepseek-harness/discussions/9308
Agent resolver is already registered
Agent resolver is already registered 是文件上传模块在重复注册同一 resolver 时抛出的错误。它出现在配置重载后会话控制器重建的过程中,一旦抛出,新会话就再也建不出来,只能重启宿主恢复。— https://github.com/deepseek-ai/deepseek-harness/discussions/8968
keepInbox
keepInbox 是取消回合时的一个选项,决定这次取消是否保留收件箱里仍未处理的消息。热重载的 dispose 路径默认不带它,因此排队消息会随取消一起被清空。— https://github.com/deepseek-ai/deepseek-harness/discussions/9711

来源