DeepSeek Harness 插件装上了不生效:重名注册、realm pending 与重载报错
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 三步定位
- 确认「工具与系统提示注入段一起消失」:只挂一个插件时消失的可能是一个工具;挂多个同类插件时可能整批消失。「整批」是关键信号,因为单点失败不会整批消失(#9308)。
- 把被吞掉的错误逼出来:从终端启动宿主(而不是从桌面图标),让 stderr 可见;预期能看到
… is already registered一类的注册期错误(#9308)。 - 用诊断插件确认真实原因:社区插件
@argszero/dsh-activation-doctor会检测激活失败并报告原因(#9308):
npm i @argszero/dsh-activation-doctor
DeepSeek Harness 处置
- 找出重名的那两个插件:逐个禁用可疑插件、每次只重开一个,观察工具是否回来——二分法比读日志快(#9308)。
- 给其中一个改工具名,或只保留一个。
- 验证:重开后确认工具与注入段同时回来,才算真正修好——只看到「插件已启用」不算(#9308)。
DeepSeek Harness 重复注册:Agent resolver is already registered
一句话结论:注册函数在销毁回调里做引用相等判断,但被比较的对象经过代理包装、每次读取都返回新包装,于是这个判断永远是假、销毁回调等于死代码;结果就是每次重载都重复注册,抛错后新会话永久建不出来。 触发条件很宽——对 cordis.patch.yml 的任意改写(甚至字节相同的重写)都会触发一次重载(#8968)。
DeepSeek Harness 四步处置
- 确认判据:报错文本是
file-upload: Agent resolver is already registered,现象是新会话永久建不出来(已有会话可能仍可用);重启进程后恢复(#8968)。 - 应急:重启宿主进程——这是唯一能立刻恢复的手段(#8968)。
- 别在「顺序 dispose / 重建看起来正常」上放心:讨论里强调那只是假象,销毁回调虽然会执行,但引用比对恒定失败,所以注册不会被撤销(#8968)。
- 长期规避:把注册身份绑到一个普通对象(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 三步定位与处置
- 判据一:看命令列表而不是服务。如果
commands.list()里有压缩命令(说明后端在运行),而ctx.get('compaction')取不到服务,那就是 realm 边界问题(#9425)。 - 判据二:注意属性读取的陷阱。直接读
ctx.compaction有时会返回字符串而不是undefined,容易让人误判「服务在,只是用不了」。这也是为什么渠道 pending 与 disabled 两种外观不同、表现却都是「服务不存在」(#9425)。 - 处置一(推荐):改用命令方式执行,而不是依赖服务依赖注入:
// 插件里改用命令通道,而不是声明 inject 服务
await ctx.commands.execute(agent, '/compact', [], signal)
- 处置二:把压缩后端装到与插件相同的层级;讨论中确认,把后端移到同层后
ctx.compaction立即可用(#9425)。 - 验证:重开后确认条目不再是
pending,且ctx.get('compaction')能拿到服务对象(#9425)。
DeepSeek Harness 插件注册的 skill 加载失败:写入路径缺默认值
一句话结论:ctx.skills.register() 允许省略 source,但运行时校验又要求它必须是字符串——写入路径校验的形状与读取路径要求的不一致,于是「注册成功、加载失败」。 报错是 loaded skill "<name>" source must be a string(#9550)。
- 确认:skill 出现在列表里,但一被调用就报上述错误;说明注册成功、加载失败(#9550)。
- 插件侧规避(最省事):注册时显式传
source:
ctx.skills.register({ name, description, content, invocation, source: 'runtime' })
DeepSeek Harness 热重载丢排队消息:事件有回执,界面没订阅
一句话结论:热重载走 dispose 路径时以 {kind:'disposed'} 取消回合,而取消会先清空收件箱再中止,于是排队消息无声消失;事件里其实已经发了「被丢弃」的回执,只是 UI 没有订阅它,所以看起来像是你主动取消。 触发窗口很具体:改 dsh.profile.bundles 或从界面装卸插件后的约 2.8 秒内(#9711)。
- 确认判据:回合被中止、排队消息消失,而事件里出现的是
outcome: "canceled"——措辞误导是这条的典型特征(#9711)。 - 规避:不要在回合运行中改
bundles相关配置或装卸插件;等回合结束再动(#9711)。 - 最小修法(核心侧):让 dispose 路径也传
keepInbox: true——用户主动停止那条路径本来就传了它,热重载这条漏了(#9711)。 - 验证:改配置重载后,预期排队消息仍在队列中;若 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)
- 确认:控制台出现「合法关闭却打 error」的噪声,且伴随另一个插件打不开标签页(#9843)。
- 处置:把 veto 与真失败分开——veto 不应记 error;
place()的替换分支补上 catch(#9843)。 - 注意:调整日志级别会改动测试契约,改动前先看相关测试(#9843)。
DSH插件 Windows 上 CLI 启动就静默失败
一句话结论:安装树里同一原生模块有多个副本时,两份先后被加载,第二份在 koffi 的进程级类型登记簿上撞名,于是条目初始化失败、整棵插件树报「无法解析」,而错误又不带真实异常。 报错文本类似 plugin tree failed to load … could not be resolved(#9553)。
- 确认:检查是否有多份同一原生模块副本(常见于多次安装叠加的旧环境)(#9553)。
- 处置(最直接):清掉冗余副本后重装;全新安装的环境通常只有一份,不中招(#9553)。
- 把真实异常挖出来(可复用):挂一个诊断插件,在加载上下文里直接调用加载器并
try/catch写文件,就能拿到被吞掉的原始异常;也可以从日志服务的输出端回捞条目级错误(#9553)。
DSH插件 排查注意事项
这一族的排查主线是:先判断失败发生在「激活期」还是「调用期」——激活期失败表现为「装了但完全没作用」,调用期失败才有具体报错。 九条要点:
- 「整批工具一起消失」是激活期失败的指纹,别按单个工具去查(#9308)。
- 从终端启动能看到被吞掉的错误,这是成本最低的一步(#9308)。
- 「市场显示已启用」不构成证据:它反映的是安装状态,不是激活状态(#9308)。
- 重载类故障先重启再分析:
already registered那一类,重启是唯一即时恢复手段(#8968)。 pending (waiting for service)先看 realm 边界,再看服务到底装没装(#9425)。- 属性读取会骗人:
ctx.compaction可能返回字符串而不是 undefined,判据用ctx.get()(#9425)。 - 注册接口要「写入即校验」:skill 的
source就是一个典型反例(#9550)。 - 别在回合运行中装卸插件:热重载那 2.8 秒窗口会吃掉你的排队消息(#9711)。
- 修复状态如实:重名注册的可见性、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 出现「显示已启用但工具调不到」,优先怀疑工具重名注册。注册表对重名工具会抛 already registered,而插件在 apply 阶段抛错会导致整条 fiber 被卸载——也就是全有或全无;更麻烦的是这个错误只写进日志服务,出厂 profile 没挂日志输出端,桌面端的 stderr 又只留在内存环形缓冲里,于是表现成彻底静默。可行的做法是从终端启动宿主以便看到 stderr,或使用能检测激活失败的诊断插件。
DeepSeek Harness 插件条目永久 pending,是因为那个服务与插件处在不同的 realm 里。压缩后端在 agent preset 的分组 realm 内,而插件是 profile 层的条目,跨 realm 不可达是设计使然;判据是 commands.list() 里能看到 compact 命令但 ctx.get('compaction') 取不到服务。规避办法是改用命令方式执行压缩,或把压缩后端装到与插件相同的层级。
DeepSeek Harness 的 skill 注册后加载失败,是因为注册接口不会给 source 填默认值,而运行时校验又要求它必须是字符串——写入路径与读取路径对形状的要求不一致。插件侧最省事的规避是显式传 source: 'runtime';根治方向是让注册接口给出默认值,或在注册时就拒绝非法形状而不是延后到加载时报错。
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
来源
- #9308 — 插件因工具重名注册失败时用户完全不可感知:市场仍显示已启用、无 UI 提示、无任何日志· deepseek-ai(GitHub Discussions)
- #8968 — file-upload: Agent resolver is already registered — any cordis.patch.yml reload permanently kills sessionController· deepseek-ai(GitHub Discussions)
- #9425 — 插件无法触达 compaction 服务:声明 inject 后条目永久 pending· deepseek-ai(GitHub Discussions)
- #9550 — ctx.skills.register() does not default source, so plugin-registered skills fail to load· deepseek-ai(GitHub Discussions)
- #9711 — 回合被 dispose(配置热重载)时排队消息被静默丢弃,且事件标记 canceled 会误导成用户主动取消· deepseek-ai(GitHub Discussions)
- #9843 — Legitimate close-handler veto is logged with console.error; the replaceTab path calls close handlers without a catch· deepseek-ai(GitHub Discussions)
- #9553 — dsh CLI on Windows: entry import failures — invisible on 0.1.5-rc.2, downgraded to a warning later· deepseek-ai(GitHub Discussions)