DeepSeek Harness101 打包与安装 返回首页 官方文档 ↗

Fiber、effect 与确定性清理

让插件知道何时开始,也知道如何结束。

依赖驱动加载,作用域追踪注册,dispose 等待全部清理,HMR 不保留旧实例。

阅读位置完成打包教程后阅读。这里解释此前自动清理、依赖等待和热替换背后的运行时模型。
plugin.ts
export function apply(ctx: Context) {
  ctx.on('some-event', handler)

  ctx.effect(() => {
    const connection = createConnection()

    return () => connection.close()
  })
}

每个插件实例拥有一个 Fiber

Fiber 是运行时作用域,也是生命周期句柄。状态反映依赖、apply 和清理当前进行到哪里。

PENDINGLOADINGACTIVEUNLOADINGDISPOSEDFAILED
PENDING

已声明,所需服务尚未就绪。

LOADING / ACTIVE

apply 正在运行,或已经完成。

FAILED

apply 或配置校验抛出异常。

UNLOADING / DISPOSED

disposer 正在运行,或清理已经完成。

inject 决定插件何时能进入 LOADING

插件会等待全部必需服务就绪。如果提供方被替换、服务暂时消失,消费者会自动卸载,并在服务恢复后重新加载。

TypeScript
export const inject = ['tools', 'llm']

export function apply(ctx: Context) {
  // ctx.tools and ctx.llm are ready here.
}
服务就绪ACTIVE服务消失DISPOSED恢复后重载

多数注册本身已经是 effect

通过 ctx 建立的注册会附着到当前 Fiber。只有 Cordis 不管理的资源才需要手写 ctx.effect()。

ctx.on(event, handler)卸载时移除监听器
ctx.tools.register(tool)撤销工具注册
ctx.llm.registerAdapter(...)撤销模型适配器
ctx.plugin(child)随父 Fiber 递归卸载
ctx.effect(() => cleanup)释放连接、watcher 等外部资源
不要假设异步清理串行完成

disposer 按注册顺序的逆序开始调用,但多个异步 disposer 会并发运行。存在顺序依赖时,把全部步骤放在同一个 disposer 中,并在内部依次 await。

子插件继承上下文,但拥有独立生命周期

ctx.plugin() 返回子 Fiber。它可以提前 dispose,也会在父插件卸载时递归清理。

parent Fiber
child Fiber独立状态,同父级卸载
TypeScript
const fiber = ctx.plugin(myPlugin)

// Dispose it manually later.
await fiber.dispose()
移除拥有的注册
递归卸载子插件
等待全部异步清理

HMR 是完整卸载再加载

cordis-plugin-hmr 先清理旧实例的所有注册,再加载新代码并执行新的 apply。它不是在旧实例上打补丁。

卸载旧实例清理全部注册加载新代码运行新 apply

下一步,让插件向其他插件提供服务