DSH 插件调试避坑:client.js 设置面板、热重载与高频报错排查表

|-------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| CFzjy AI Engineer | ▍深耕领域 AI Agent 架构设计 Harness 插件开发 Context Engineering 多智能体协同 & RAG ▍技术底座 Python TypeScript · Cordis 插件 MCP & Skills · FastAPI Docker ▍工程能力 专注 Agent 运行时与 Harness 插件开发、长上下文与流式交互优化、LLM 全链路工程化落地 |
| 「 让 AI 交互更智能,让技术落地更高效 」 | 欢迎技术探讨 / 项目合作,关注解锁大模型与智能交互的无限可能! ||


DSH 插件调试避坑:client.js 设置面板、热重载与高频报错排查表

📚 《DeepSeek Harness 插件架构》连载专栏 · 第 09 篇

开发实战篇到这里收尾:07 搭工程骨架、08 写工具契约,这一篇解决最憋屈的问题------插件写对了,却"装不活" :不报错、不输出、界面也不显示。DSH 的调试和传统框架最大的不同,是它默认安静:依赖没就绪的插件停在 PENDING 状态,既不报错也不提示;热重载缺一个辅助服务就静默停摆;客户端卡片写错了只回退、不报错。这一篇把三条"装死"链路全部拆开,给你一套从"猜"到"查"的排查方法。

痛点一:插件装进 profile 不生效,日志里干干净净。 没有报错、没有异常、没有提示------因为它根本停在 PENDING,压根没进 apply。

痛点二:改代码保存了,热重载"没反应"。 你以为 HMR 坏了,其实它缺个 timer 服务,早就静默停在 PENDING 等死。

痛点三:客户端 UI 面板 / 工具卡片写好了,界面上就是不显示。 没有报错,只回退到通用卡片------因为你踩了 keyed slot 的注册约定。

网上讲 DSH 的教程大多只讲"怎么写",不讲"写坏了怎么查"。本文专注后者:把官方文档里散落的诊断手段(FiberState 枚举、--dump-config、HMR 依赖链、keyed slot 回退规则)收拢成一条可复用的排查链。读完你会拿到:三个反直觉的调试前提、客户端插件的两种形态与四条硬规则、HMR 的完整链路与两大静默陷阱、一张高频报错速查表。想追完整系列(下一篇:解剖官方真实插件),可以先关注我的 CSDN 主页。

文章目录

    • [DSH 插件调试避坑:client.js 设置面板、热重载与高频报错排查表](#DSH 插件调试避坑:client.js 设置面板、热重载与高频报错排查表)
      • [一、为什么 DSH 插件"装死"是常态:三个反直觉的调试前提](#一、为什么 DSH 插件"装死"是常态:三个反直觉的调试前提)
        • [1.1 PENDING 是特性,不是 bug](#1.1 PENDING 是特性,不是 bug)
        • [1.2 静默是常态,报错是例外](#1.2 静默是常态,报错是例外)
        • [1.3 先问"它在哪个状态",再问"它为什么报错"](#1.3 先问"它在哪个状态",再问"它为什么报错")
      • [二、client.js 客户端插件:两种形态与调试盲区](#二、client.js 客户端插件:两种形态与调试盲区)
        • [2.1 client.js 是什么:Client 端沙箱里的"第二套代码"](#2.1 client.js 是什么:Client 端沙箱里的"第二套代码")
        • [2.2 形态一:UI 插件(会话渲染 + 输入驱动)](#2.2 形态一:UI 插件(会话渲染 + 输入驱动))
        • [2.3 形态二:工具卡片展示(tool.call.toolview keyed slot)](#2.3 形态二:工具卡片展示(tool.call.toolview keyed slot))
        • [2.4 四条硬规则:客户端展示的"红线"](#2.4 四条硬规则:客户端展示的"红线")
      • [三、HMR 热重载:完整链路与静默 PENDING 陷阱](#三、HMR 热重载:完整链路与静默 PENDING 陷阱)
        • [3.1 HMR 三件套:hmr + logger + timer](#3.1 HMR 三件套:hmr + logger + timer)
        • [3.2 卸载 → 加载的完整链路](#3.2 卸载 → 加载的完整链路)
        • [3.3 id 稳定器与配置变更触发](#3.3 id 稳定器与配置变更触发)
      • 四、装不活的三大诊断工具
        • [4.1 FiberState 枚举:一眼看穿"装死"](#4.1 FiberState 枚举:一眼看穿"装死")
        • [4.2 --dump-config:验证层到底有没有生效](#4.2 --dump-config:验证层到底有没有生效)
        • [4.3 日志服务与 console:HMR 的"眼睛"](#4.3 日志服务与 console:HMR 的"眼睛")
      • 五、实战避坑指南:高频报错与一键解决速查表
      • 结语

一、为什么 DSH 插件"装死"是常态:三个反直觉的调试前提

1.1 PENDING 是特性,不是 bug

传统框架的惯性是"启动即报错"------组件少个依赖,进程直接抛异常,红字告诉你缺什么。DSH 走的是依赖驱动加载 :一个插件声明了 inject,它不会在启动时急着运行,而是等所有必需服务就绪才进入 apply。

官方 Cordis 教程第六章的原话点破了这个反差:"如果插件的 inject 指定了无人提供的服务,它就会一直等待,不输出任何内容。这不是错误,因为 PENDING 是合法状态,提供方可能稍后才挂载。"

用一个生活化类比:它像一个在站台等公交的人,车没来就安静站着,不哭不闹 。你不能因为这个人"没上车"就断定他"出了错"------他只是还没等到那班车。同样,一个 inject: ['timer'] 的插件,只要 timer 服务没被任何插件提供,它就永远站在 PENDING 站台,安静得让你以为它坏了。

1.2 静默是常态,报错是例外

DSH 的哲学里,只有真正的异常才会"响亮" 。配置 Schema 校验失败会加载失败并给出明确错误(对应第 02 篇的 FAILED 状态);但依赖缺失、HMR 缺服务、客户端卡片格式不对,这三类高频问题全部走"静默"路径。所以调试 DSH 的第一课是:不要等报错,要学会主动查状态。

1.3 先问"它在哪个状态",再问"它为什么报错"

Fiber 状态机(第 02 篇讲过)给了每个插件六个状态:PENDING → LOADING → ACTIVE → UNLOADING → DISPOSED,外加 FAILED 分支。排查的起点不是看报错日志,而是枚举当前注册表里每个 fiber 停在哪个状态------一个停在 PENDING 的插件和"报错的插件"是两码事,前者查依赖、后者查异常。这一套在第四章展开成完整工具。

二、client.js 客户端插件:两种形态与调试盲区

2.1 client.js 是什么:Client 端沙箱里的"第二套代码"

第 07 篇的工程结构里,client.js 是可选的可视化设置面板入口 ,通过 package.json 的 exports["./client"] 暴露。它的关键特性是跑在 Client 端沙箱 ------与宿主端的 index.js 是两套隔离的运行环境。这意味着:你在宿主端能用的 ctx 能力,客户端插件未必能用,反之亦然。调试客户端插件时,第一步永远是确认"这段代码到底跑在哪一端"。

2.2 形态一:UI 插件(会话渲染 + 输入驱动)

官方《实操手册》把 UI 插件的职责拆成两半:把持久化的事件流渲染出来,把用户的输入喂回去 。它组合两类数据源------持久的 session/event record(助手结算、轮次/步骤边界、工具活动)和瞬态的 agent/assistant-stream frame(实时 token 呈现),再通过 agent.followup() / agent.steer() 把输入驱动回会话:

ts 复制代码
import type { Context } from '@deepseek-ai/cordis'
import { brandString } from '@deepseek-ai/dsh-brand'
import { createUserMessage } from '@deepseek-ai/dsh-llm'
import type { SessionId } from '@deepseek-ai/dsh-session'

declare function render(text: string): void
declare function onUserInput(handler: (text: string) => void): void

export const name = 'my-ui'
export const inject = ['agents']

export function apply(ctx: Context) {
  // 监听瞬态流:把模型的实时文本增量画到界面上
  ctx.on('agent/assistant-stream', ({ frame }) => {
    if (frame.type === 'chunk' && frame.chunk.type === 'text-delta') {
      render(frame.chunk.text)
    }
  })
  // 监听输入:把用户打的话驱动回会话
  onUserInput(text => ctx.agents.get(brandString<SessionId>('client-session'))?.followup(createUserMessage({
    content: [{ type: 'text', text }],
    source: { kind: 'user' },
  })))
}

这段代码的调试要点有两条:inject: ['agents'] 是客户端插件能拿到会话能力的前提 ,少了它 ctx.agents 就是空;输入回驱动靠 followup 而不是自己构造事件 ,绕过它会导致会话状态不一致。官方还提醒,如果浏览器插件要向内建 Web Client 贡献业务行,应注册 ConversationNodeDefinition 与 keyed Chat renderer------这是 UI 插件的"进阶版",但约定更严。

2.3 形态二:工具卡片展示(tool.call.toolview keyed slot)

第 08 篇讲过宿主端用 presentCall / presentResult 定义工具卡片,但官方明确点了一句:内置 Web Client 不消费这两个方法 。客户端要渲染工具卡片,走的是另一条路------在 keyed slot tool.call.toolview 里注册自己的 wire 工具名称,从 ToolCallBlock 的参数、内容、错误、metadata、父调用 id 与 Session 路径事实里派生组件 props。

这条路的调试盲区集中在注册约定 上:slot 是 keyed 的,注册名必须精确匹配 wire 工具名;派生 props 的来源是 ToolCallBlock 而不是宿主端的 args/result 结构。写错了不会报错,只会回退到 generic 行。

2.4 四条硬规则:客户端展示的"红线"

官方给客户端派生展示立了四条硬规则,违反任何一条都会静默回退或崩溃:

规则 含义 违反后果
本地校验 wire 值 格式错误或不受支持的输入要回退 generic 不校验 → 组件拿到脏数据崩溃
不在 metadata 存 React props 不把 UI 实现细节序列化进持久数据 回放时 props 失效或报错
不把 Host 工具实现导入浏览器 bundle 客户端不 import 宿主端实现 bundle 体积爆炸或运行时报错
不建第二套 Client presenter registry 只定义 Host 展示方法,不另起炉灶 两端展示逻辑漂移

这四条的共同心法是第 08 篇那句:展示层要"软"------客户端卡片写错只能回退,绝不能影响核心会话。调试时如果发现客户端卡片"不显示但会话正常",先查这四条,别急着改渲染代码。

三、HMR 热重载:完整链路与静默 PENDING 陷阱

3.1 HMR 三件套:hmr + logger + timer

热重载不是 DSH 开箱即用的默认能力,它依赖三个插件协同。官方教程第六章的 cordis.yml 是标准配置:

yaml 复制代码
- id: logger
  name: '@deepseek-ai/cordis-plugin-logger-console'
- id: timer
  name: '@deepseek-ai/cordis-plugin-timer'
- id: hmr
  name: '@deepseek-ai/dsh-hmr'
  config:
    root: ['.']
- id: hello
  name: './hello.ts'

这里藏着两个极易踩的静默陷阱,官方原话写得很细:

陷阱一:缺 logger,看不到 HMR 日志。 "HMR 通过 Cordis logger 服务记录日志,因此没有控制台导出器时看不到其消息"------HMR 还在工作,但它的 reload 日志你看不见,于是你以为它没生效。

陷阱二:缺 timer,HMR 直接停摆。 "它还会 inject timer 服务来实现去抖,如果没有 timer,它就会永远停在 PENDING,而且不发出任何提示"------这才是真正的"装死":HMR 插件自己变成了一个 PENDING 的 fiber,安静地等一个永远不来的服务。这就是第一章说的"静默是常态"最典型的现场。

3.2 卸载 → 加载的完整链路

第 02 篇讲过:卸载会释放 effect,加载遵循依赖。HMR 就是这套机制的复用------先卸载旧插件(回卷它所有 effect),再加载新代码,再执行新的 apply 。因为注册都属于 effect 并会自动清理,热替换后不会残留旧实例的任何注册。这条链路的含义是:你的 apply 必须对"随时被重载"友好,不要假设它只跑一次。
#mermaid-svg-ZdrMSTmIkdra6Axm{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ZdrMSTmIkdra6Axm .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ZdrMSTmIkdra6Axm .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ZdrMSTmIkdra6Axm .error-icon{fill:#552222;}#mermaid-svg-ZdrMSTmIkdra6Axm .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ZdrMSTmIkdra6Axm .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ZdrMSTmIkdra6Axm .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ZdrMSTmIkdra6Axm .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ZdrMSTmIkdra6Axm .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ZdrMSTmIkdra6Axm .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ZdrMSTmIkdra6Axm .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ZdrMSTmIkdra6Axm .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ZdrMSTmIkdra6Axm .marker.cross{stroke:#333333;}#mermaid-svg-ZdrMSTmIkdra6Axm svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ZdrMSTmIkdra6Axm p{margin:0;}#mermaid-svg-ZdrMSTmIkdra6Axm .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ZdrMSTmIkdra6Axm .cluster-label text{fill:#333;}#mermaid-svg-ZdrMSTmIkdra6Axm .cluster-label span{color:#333;}#mermaid-svg-ZdrMSTmIkdra6Axm .cluster-label span p{background-color:transparent;}#mermaid-svg-ZdrMSTmIkdra6Axm .label text,#mermaid-svg-ZdrMSTmIkdra6Axm span{fill:#333;color:#333;}#mermaid-svg-ZdrMSTmIkdra6Axm .node rect,#mermaid-svg-ZdrMSTmIkdra6Axm .node circle,#mermaid-svg-ZdrMSTmIkdra6Axm .node ellipse,#mermaid-svg-ZdrMSTmIkdra6Axm .node polygon,#mermaid-svg-ZdrMSTmIkdra6Axm .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ZdrMSTmIkdra6Axm .rough-node .label text,#mermaid-svg-ZdrMSTmIkdra6Axm .node .label text,#mermaid-svg-ZdrMSTmIkdra6Axm .image-shape .label,#mermaid-svg-ZdrMSTmIkdra6Axm .icon-shape .label{text-anchor:middle;}#mermaid-svg-ZdrMSTmIkdra6Axm .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ZdrMSTmIkdra6Axm .rough-node .label,#mermaid-svg-ZdrMSTmIkdra6Axm .node .label,#mermaid-svg-ZdrMSTmIkdra6Axm .image-shape .label,#mermaid-svg-ZdrMSTmIkdra6Axm .icon-shape .label{text-align:center;}#mermaid-svg-ZdrMSTmIkdra6Axm .node.clickable{cursor:pointer;}#mermaid-svg-ZdrMSTmIkdra6Axm .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ZdrMSTmIkdra6Axm .arrowheadPath{fill:#333333;}#mermaid-svg-ZdrMSTmIkdra6Axm .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ZdrMSTmIkdra6Axm .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ZdrMSTmIkdra6Axm .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ZdrMSTmIkdra6Axm .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ZdrMSTmIkdra6Axm .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ZdrMSTmIkdra6Axm .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ZdrMSTmIkdra6Axm .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ZdrMSTmIkdra6Axm .cluster text{fill:#333;}#mermaid-svg-ZdrMSTmIkdra6Axm .cluster span{color:#333;}#mermaid-svg-ZdrMSTmIkdra6Axm div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ZdrMSTmIkdra6Axm .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ZdrMSTmIkdra6Axm rect.text{fill:none;stroke-width:0;}#mermaid-svg-ZdrMSTmIkdra6Axm .icon-shape,#mermaid-svg-ZdrMSTmIkdra6Axm .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ZdrMSTmIkdra6Axm .icon-shape p,#mermaid-svg-ZdrMSTmIkdra6Axm .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ZdrMSTmIkdra6Axm .icon-shape .label rect,#mermaid-svg-ZdrMSTmIkdra6Axm .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ZdrMSTmIkdra6Axm .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ZdrMSTmIkdra6Axm .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ZdrMSTmIkdra6Axm :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否(缺 timer)
是
保存源文件
dsh-hmr

是否 ACTIVE?
静默停摆

无任何提示
卸载旧实例

回卷 effect
重新加载新代码
执行新的 apply

3.3 id 稳定器与配置变更触发

HMR 不仅盯插件源文件,编辑 cordis.yml 本身也会触发更新 :loader 按 id 比较 Cordis 配置项,只挂载、卸载或重新配置发生变化的部分。这就是为什么配置项要显式带 id------官方原话点破:"不带该字段的配置项在每次读取时都会获得一个新生成的 id,所以只要配置文件发生任何编辑,即使自身文本未变,它也会被视为先删除再添加并重新挂载。"

id 就是配置项的稳定身份证 。此外还有两个相关的配置语义值得记住:disabled: true 会卸载插件但不删除其配置项,改回原值后插件连同依赖它的 PENDING 插件都会再次加载;配置变更触发热替换(第 07 篇 3.3 讲过),旧实例卸载、新实例加载、无残留。

四、装不活的三大诊断工具

4.1 FiberState 枚举:一眼看穿"装死"

官方教程第六章给出的诊断代码,是整个排查链的核心。每个上下文都能枚举插件注册表,逐个检查 fiber 状态:

ts 复制代码
import { FiberState, type Context } from '@deepseek-ai/cordis'

export const name = 'diagnose'

export function apply(ctx: Context) {
  setTimeout(() => {
    for (const runtime of ctx.registry.values()) {
      for (const fiber of runtime.fibers) {
        if (fiber.state === FiberState.PENDING) {
          console.log(`${fiber.name} is PENDING --- a required service is missing`)
        }
      }
    }
  }, 500)
}

这段代码回答的正是第一章那个问题:"它在哪个状态" 。FiberState.PENDING 命中,说明依赖缺失(回到第 03 篇查 inject);FiberState.FAILED 命中,说明 apply 抛异常(回到第 02 篇查 FAILED 分支)。官方还提醒,不加 PENDING 过滤条件迭代时,你会看到 loader 自身的插件处于 ACTIVE------因为配置文件本身也是通过插件挂载的,别把它们误判成问题。

4.2 --dump-config:验证层到底有没有生效

第 05 篇讲过四层层序(组合包层 → profile 层 → home 层 → overlay 层)。当插件"装了不生效",第一步不是读代码,而是先确认你的层到底有没有被应用:

sh 复制代码
dsh --profile demo --dump-config   # 输出里应出现 "# == dsh-hello-plugin" 这一层

如果 --dump-config 里看不到你的 bundle 层,问题在装配层(第 05 篇的 dsh.bundle.patch 声明、层序、整行替换语义);如果能看到层但插件还是 PENDING,问题在依赖层(第 03 篇的 inject)。先定位到"层"还是"依赖",再动手,省一半时间。

4.3 日志服务与 console:HMR 的"眼睛"

第三章的陷阱一已经埋了伏笔:HMR 的日志走 logger 服务,不是裸 console。所以调试热重载时,先确保 cordis-plugin-logger-console 已挂载,否则 reload 日志对你是隐形的。挂上 logger 后,编辑源文件你会看到两行关键日志:

复制代码
hmr watching [ '.' ]
hmr reload plugin at hello.ts

第一行证明 HMR 在监视、第二行证明触发了重载。看到这两行,HMR 链路才真正打通;看不到,回头查第三章的 timer 陷阱。

五、实战避坑指南:高频报错与一键解决速查表

序号 遇到的报错或现象 背后的真正原因 一句话解决办法
1 插件装进 profile 不生效,零报错 停在 PENDING,inject 的依赖没人提供 用 FiberState 枚举查,补上提供方插件
2 保存源文件热重载没反应 HMR 缺 timer 服务,自己停在 PENDING cordis.yml 加 @deepseek-ai/cordis-plugin-timer
3 热重载在干活但看不到日志 缺 logger 服务,HMR 消息走 logger 不走 console 挂 cordis-plugin-logger-console
4 客户端工具卡片不显示但会话正常 没在 keyed slot 注册 wire 名,或 props 派生错了 在 tool.call.toolview 按 wire 名注册,校验回退
5 客户端组件拿到脏数据崩溃 没本地校验 wire 值 格式错/不支持一律回退 generic
6 配置改了不生效 配置值读进了模块级常量,或整行替换语义 从 apply 的 config 参数读;覆盖需重述全部键
7 git 直装的 TS 插件加载失败 没有构建产物,无 lib/ 输出 作者加 prepare 脚本,用户配 allowBuilds

这张表的心法和前几篇一致:DSH 的"正确用法"都写成了契约,踩坑的根因几乎永远是"凭直觉绕过契约",解法永远是一句话"回到官方约定"。区别只在于------调试篇的契约不在功能上,而在"状态与生命周期"上:先查状态、再查层、再查依赖,顺序对了,装死问题基本三分钟定位。

结语

这一篇把"插件装不活"拆成了三条链路:客户端插件 (client.js 双形态 + 四条展示红线)、热重载 (HMR 三件套 + 两个静默陷阱)、诊断工具 (FiberState 枚举 + --dump-config + 日志服务)。核心心法一句话:DSH 默认安静,调试的第一动作不是看报错,而是主动查状态------先问"它在哪个 Fiber 状态",再决定查依赖、查层还是查异常。开发实战篇到此收尾,下一篇开始"解剖官方真实插件",从官方已经交付的插件里学生产级写法。

知识要点文字回顾:① PENDING 是合法状态,装死≠报错,先查 fiber 状态;② 客户端插件双形态 = UI 插件(会话渲染+followup 驱动) + 工具卡片(keyed slot 注册),四条红线里"本地校验回退"最关键;③ HMR 三件套 = hmr+logger+timer,缺 logger 瞎、缺 timer 死;④ 编辑 cordis.yml 也触发热替换,id 是稳定身份证;⑤ 三大诊断 = FiberState 枚举、--dump-config 验层、logger 服务看 HMR 日志。

下一篇连载预告:《解剖官方插件:从 tool-bash / tool-fs 学生产级插件怎么写》 ------把官方真实交付的插件逐行拆开,关注 CSDN 主页 不错过更新。

你在调 DSH 插件时,最常卡在哪个"静默装死"的现场?是 HMR 没反应,还是客户端卡片不显示?评论区聊聊,我挨个回。 本文事实基于 DeepSeek Harness 官方文档(Cordis 教程第六章、实操手册、工具编写参考,2026-10 版本),API 迭代快,有出入评论区提醒我。

参考资料(正文数据来源):DeepSeek Harness 官方文档《插件与生命周期》《组合与 HMR》《插件配置》《打包与安装插件》《实操手册:扩展插件形态》《工具编写参考》。

相关推荐
秋饼1 小时前
Computer Use Agent Java 落地实战:感知-行动循环、执行器与安全围栏生产级实现
java·ai·技术分享·后端开发
tellmewhoisi2 小时前
深度学习0:数学知识:正态分布4(坐标变换的缩放比例(面积微元变换))
人工智能·深度学习
布吉岛的石头2 小时前
Java 程序员第 50 阶段14:灾难性遗忘与回放
人工智能·深度学习·transformer
不吃鱼的羊2 小时前
诊断27解锁dll文件制作(Visual Stdio无法安装版)
人工智能
后端小肥肠3 小时前
漫画工坊上线用户破百:公众号漫画从需求到变现全复盘
人工智能·aigc·agent
赛博山人3 小时前
字节跳动下线扣子企业版,团队已并入豆包
人工智能
isboyjc3 小时前
Lomo Agent:面向创作型、持续协作 Agent 的架构设计与工程实践
人工智能·架构·agent
冻柠檬飞冰走茶3 小时前
字母移动游戏题解:贪心求最长可保留连续子串(附 C 实现)
c语言·开发语言·数据结构·算法·动态规划