DSH plugin 生命周期:Fiber 状态机、嵌套上下文与 dispose 释放语义
DSH plugin 的生命周期不是「加载完就一直在」,而是被一个六态 Fiber 状态机管着:依赖没齐就停在 PENDING、apply 抛错进 FAILED、卸载要经过 UNLOADING 才到 DISPOSED。 理解这条状态机,能解释插件开发里最常见的一类困惑——「我的插件明明装上了,为什么不执行」:多数情况是 inject 依赖没就绪,Fiber 根本没走到 apply。另一半困惑来自卸载:通过 ctx 注册的东西会被自动清理,但异步清理并不保证顺序,有依赖关系的收尾必须自己串起来。
DSH plugin 的 Fiber 状态机:六个状态与三类转移
一句话结论:Fiber 的六个状态只描述一件事——这个插件现在能不能执行、出错停在哪、卸载走到哪一步。
| 状态 | 含义 | 典型成因 |
|---|---|---|
| PENDING | 已声明,但所需依赖未就绪 | inject 里的服务还没提供 |
| LOADING | 依赖就绪,正在执行 apply | 正常加载中 |
| ACTIVE | 插件运行中 | 加载成功 |
| FAILED | apply 抛出异常 | 同步错误、初始化失败 |
| UNLOADING | 正在卸载并释放资源 | 主动 dispose、依赖消失、热重载 |
| DISPOSED | 已完全卸载 | 卸载完成 |
- 正常路径:PENDING → LOADING → ACTIVE。
- 失败路径:LOADING → FAILED,
apply抛出异常即停在这里。 - 卸载路径:ACTIVE → UNLOADING → DISPOSED,清理完成后才真正消失。
关键判据:看到「插件没反应」先看 Fiber 停在哪一态——停在 PENDING 是依赖问题,停在 FAILED 是代码问题,两者修法完全不同。
DSH plugin 依赖驱动的加载:inject 决定何时执行 apply
一句话结论:声明了 inject 的 DSH plugin 会等所有必需服务就绪才执行 apply,所以「没执行」往往不是没装,而是在排队等依赖。
export const inject = ['tools', 'llm']
export function apply(ctx: Context) {
// ctx.tools 与 ctx.llm 在这里一定已就绪
}
- 列出真实依赖:只声明
apply里实际用到的服务,多写一个无关服务就会白白多等一个依赖。 - 确认提供方在树里:依赖的服务由别的插件提供,那个插件没加载,你就永远停在 PENDING。
- 看依赖消失的行为:如果依赖的服务消失(例如提供方被替换),DSH plugin 会被自动卸载(ACTIVE → DISPOSED),等服务恢复后再重新加载——这是设计行为,不是异常。
- 验证:把 inject 临时去掉跑一次,若插件能执行,说明问题在依赖而不是代码。
DSH plugin 的嵌套上下文:ctx.plugin 与子 Fiber
一句话结论:ctx.plugin() 创建的子插件有独立 Fiber、共享父上下文,并随父插件一起卸载——这是拆分内部模块最轻的方式。
- 注册子插件:
export function apply(ctx: Context) {
ctx.plugin(childPlugin) // 子插件拥有自己的 Fiber
}
- 理解继承关系:子插件继承父上下文,但生命周期独立——父被卸载时子会被递归卸载,子自己也可以提前 dispose。
- 用它拆分内部职责:一个插件内部有多块能力时,用子插件分离状态机,比在一个
apply里堆逻辑更容易诊断。 - 注意边界:子插件不是「另一个包」,共享同一进程与上下文;真正的隔离(独立依赖树)要用 profile 或独立插件包。
DSH plugin 的 dispose 语义:清理顺序与并发陷阱
一句话结论:dispose 保证注册全清、子插件递归卸载、Promise 等异步清理完成;但多个异步处置器并发执行,有顺序依赖的收尾必须合并进同一个 effect。
- 自动清理覆盖什么:
ctx.on事件监听、ctx.tools.register工具注册、ctx.llm.registerAdapter适配器注册、ctx.effect登记的资源——卸载时全部撤销。 - 顺序规则:处置器按注册顺序的逆序开始调用。
- 并发陷阱:多个异步处置器并发执行,不保证逐个完成。有顺序依赖的清理步骤必须放进同一个
ctx.effect()返回的处置器,由该处置器串行等待。 - 手动终止一个实例:
const fiber = ctx.plugin(myPlugin)
await fiber.dispose() // 等清理全部完成
- 验证清理是否彻底:卸载后再触发一次原事件、再调一次原工具,应无任何反应;有反应说明有注册绕过了 ctx。
DSH plugin 生命周期排查:FAILED、PENDING 与热重载
一句话结论:三类现象对应三个状态——不执行看 PENDING、报错看 FAILED、改代码不生效看 HMR 是否真的卸载了旧实例。
- 不执行:查 inject 依赖与提供方,别怀疑代码。
- 报错:查 FAILED 的直接原因,优先看
apply里第一个await之前的同步错误——它们不会给你堆栈上下文。 - 改代码不生效:启用
@deepseek-ai/dsh-hmr后,改源文件会走「卸载旧插件 → 重新加载 → 执行新 apply」三步;没启用就只有重启进程一条路。 - 推断清理是否发生:注册被自动清理是热替换能生效的前提,如果旧注册还在,热重载会表现为「两份逻辑同时生效」。
- 一个最小验证:
export function apply(ctx: Context) {
console.log('plugin loading')
ctx.effect(() => {
console.log('effect registered')
return () => console.log('effect cleaned up')
})
}
加载时输出前两行,卸载时输出第三行——这条最小链路能证明状态机与清理都在正常工作。
DSH plugin 排查注意事项
一句话结论:生命周期问题先定位状态,再谈代码——状态机已经把答案写在状态里了。
- PENDING 不是故障,是依赖未就绪;先核对服务名与提供方。
- FAILED 不会自动重试,修完要重载或重启。
- 依赖消失导致的自动卸载是设计行为,不要当成崩溃处理。
- 异步清理不保证顺序,有依赖的收尾合并进同一个 effect。
- 热重载不是万能的:只在加载了 HMR 插件时生效,且依赖「注册可自动撤销」这一前提。
- 修复状态如实:本文状态机、清理语义与 dispose 三条保证均取自官方「插件与生命周期」页面;
ctx的具体方法签名以官方 Cordis API 页面与你所用版本的类型定义为准。
常见问题
DSH plugin 停在 PENDING 表示它声明的 inject 依赖还没全部就绪,而不是插件代码有错。检查 inject 里列出的服务名是否拼对、提供该服务的插件是否已加载;依赖一旦齐全,Fiber 会自动进入 LOADING 并执行 apply。
DSH plugin 的 apply 抛异常会让 Fiber 进入 FAILED,后续不会再自动重试。修复代码并触发热重载或重启进程才会重新走一遍加载;排查时优先看 apply 里第一个 await 之前的同步错误。
DSH plugin 的处置器按注册顺序逆序调用,但多个异步处置器是并发执行的,不保证逐个完成。存在顺序依赖的清理必须放进同一个 ctx.effect 返回的处置器里,由它自己串行等待。
会,DSH plugin 的 dispose 会移除该插件拥有的全部注册,并递归卸载它的子插件。返回的 Promise 会在所有异步清理完成后兑现,所以可以直接 await 它再继续后续动作。
DSH plugin 的热重载顺序是卸载旧插件、重新加载新代码、再执行新的 apply。因为注册会被自动清理,热替换不会保留旧实例的注册,也不需要你手动 removeListener。
相关术语
- Fiber(插件作用域)
- Fiber 是 Cordis 为每个已加载插件创建的运行作用域,承载该插件的状态机与全部注册。它决定插件何时执行 apply、何时被卸载,以及卸载时清理哪些副作用。— https://deepseek-harness.github.io/deepseek-harness/develop/framework/
- PENDING(待加载)
- PENDING 是 Fiber 的初始状态,表示插件已声明但 inject 依赖尚未全部就绪。它是最容易被误判成故障的状态,实质上是框架在等待依赖而不是插件出错。— https://deepseek-harness.github.io/deepseek-harness/develop/framework/
- dispose(资源释放)
- dispose 是提前终止某个插件实例的操作,会移除其全部注册、递归卸载子插件,并返回在所有异步清理完成后兑现的 Promise。— https://deepseek-harness.github.io/deepseek-harness/reference/cordis-api/context
- ctx.effect
- ctx.effect 是登记自定义资源清理函数的方法:传入一个返回处置器的函数,插件卸载时框架会调用该处置器。它用于管理框架无法自动追踪的资源,例如网络连接与定时器。— https://deepseek-harness.github.io/deepseek-harness/develop/basic/
来源
- DeepSeek Harness 文档 — 插件与生命周期(Fiber 状态机、dispose 语义、HMR)· deepseek-harness.github.io
- DeepSeek Harness 文档 — Cordis 核心 API:Context· deepseek-harness.github.io
- DeepSeek Harness 文档 — 第一个插件(apply、ctx 与自动清理)· deepseek-harness.github.io