依赖解析与顺序
Cordis 的"加载顺序"不是拓扑排序,而是 service-availability 驱动的反应式系统——fiber 在 inject 声明的服务全部 active 时才 LOADING→ACTIVE,否则永远 PENDING(无报错,因为 provider 可能后到)。
不是拓扑排序
这是理解 Cordis 插件加载的关键认知转换:没有"先加载 A 再加载 B"的显式顺序。声明 inject: ['tools', 'systemPrompt'] 的插件会一直 PENDING,直到两个 service 都 active;若 tools 自身又 inject: ['systemPrompt'],Cordis 自动传递解析。
_checkImpl:服务是否可注入
vendor/cordis/src/fiber.ts:597-609 的 _checkImpl(name):_checkImpl(name) {
// 查 ctx.reflect._getImpl(name, true)
// 若 impl 存在且 check() 通过则写入 _store
// 否则删除
}这是"服务是否可注入"的判定。
_refresh:epoch 字符串
vendor/cordis/src/fiber.ts:611-623 的 _refresh():_refresh() {
// 遍历 Object.keys(this.inject)
// 任一 _store[name] 缺失就把 epoch 设为 INACTIVE (PENDING)
// 否则拼 ':uid:uid' 字符串作为新 epoch
}vendor/cordis/src/fiber.ts:625-639 的 _setEpoch(epoch):epoch 变化即 _updateState——- 从 INACTIVE→非 INACTIVE 触发
_reload()(LOADING) - 反向触发
_unload()(UNLOADING)
为什么用 uid 拼接而非布尔
_setEpoch 用 uid 拼接而非布尔:provider 被 reload 时 uid 变,所有下游 fiber 的 epoch 字符串变,自动级联 reload——这是 HMR 无需手动簿记的根因。
inject 的两种写法
plugin() 内部解析 inject
vendor/cordis/src/registry.ts:316-336 的 plugin(plugin, config):plugin(plugin, config) {
// 新建 runtime → new Fiber(ctx, config, Inject.resolve(plugin.inject), runtime, …)
// inject 在 fiber 构造时就被解析进 ctx.intercept 覆盖
}inject() 语法糖
vendor/cordis/src/registry.ts:300-302 的 inject(inject, callback) 其实是 plugin({ inject, apply: callback, name: callback.name }) 的语法糖:callback 在服务可用时执行、服务消失时卸载并重跑。动态插件的启动:guardedPlugin
startHostHalf
packages/extensions/cordis-host-runner/src/lifecycle.ts:22-45 的 startHostHalf(group, plugin, reportGuardFailure):async function startHostHalf(group, plugin, reportGuardFailure) {
await group.await() // 等组就绪
const fiber = group.ctx.plugin(guardedPlugin(plugin)) // 包装后挂载
await fiber.await()
// 失败即 fiber.dispose() 不留 FAILED fiber 残留
}group.await() 确保动态插件所挂的 cordis-dynamic 组 fiber 就绪后再起子 fiber,避免在组未 ACTIVE 时 mount 导致孤儿 fiber。
guardedPlugin 的沙箱
guardedPlugin 包装:动态插件的 apply 收到的是 sandboxContext(ctx, reportFailure) 而非真实 ctx,只暴露白名单 verb(ctx.get/ctx.on/ctx.provide/ctx.effect),框架内部与 context-valued 返回被拒。
Sandbox 不是安全边界:
host-sandbox.ts注释明说 "is not containment: host-realm helper functions remain an escape route"。动态插件用于 cooperative package(可观察可处置),不是防恶意代码。
诊断:为什么我的插件不加载
packages/extensions/cordis-host-runner/src/lifecycle.ts:55-57 的 missingServices(ctx, fiber):function missingServices(ctx, fiber) {
// 用 ctx.get(service) === undefined 列出 fiber 还在等的 service
}这是"为什么我的插件不加载"的诊断答案。cordis_inspect_self 工具的"settled-but-pending"报告就来自此函数,告诉 agent:"你的插件在等 fs service"。
反应式模型总结
整个系统没有"调度器"在排顺序,只有每个 fiber 各自观察自己关心的服务,服务可用性变化时自动反应。