Cordis:dsh 的插件框架基座

Cordis 是 dsh 底层的 vendored 插件框架(TypeScript,ESM-first,自称 “Meta-Framework for Modern JavaScript Applications”)。dsh 的”一切皆插件”(见 architecture)完全构建在它之上。设计参见论文 A Programming Paradigm for Spatiotemporal Composability(github.com/cordiverse/paper)。

五个核心思想(primer)

  1. A plugin is an object that implements Service——函数(带可选 inject/apply(ctx))或 Service 子类。
  2. A context is a repository of services——服务占据稳定的 ctx.<key>(如 ctx.tools、ctx.llm、ctx.sessions),按 key 找服务而不是 import 实现。
  3. Declare service dependency via inject——加载顺序由服务依赖表达,而非手工 boot 排序。
  4. Typed Events for communication——TypeScript declaration merging 声明事件名,按 mode 分发。
  5. Registrations are reversible effects——prompt sections、tool schemas、adapter、listener 都通过 ctx.effect()/ctx.on() 安装,reload/teardown 可预测回卷。

核心对象

Context

root/child dependency container,是一个 proxy:普通属性读走 service resolver。关键 API:

  • ctx.extend(meta?) / ctx.isolate(name, label?) / ctx.intercept(name, config):建 scoped child context,不 mutate 父级。isolate 建立 isolation scope——子树可提供同名服务的不同实现;同 label 合并 scope。
  • ctx.get(name, strict?) / ctx.set(name, value) / ctx.provide(name, value)(返回 disposer)/ ctx.accessor() / ctx.mixin()。
  • ctx.root、ctx.events(混入 ctx.on/emit/...)、ctx.registry(混入 ctx.plugin/inject)、ctx.logger、ctx.fiber。
  • Context.is(value) 用全局 symbol 品牌判定,跨 realm、跨多份 cordis 拷贝有效。

Service

super(ctx, name) 构造即注册为 ctx.<name>;注册是 effect,随 owning fiber 卸载自动移除。静态 symbol keys:Service.init、Service.check(可用性谓词)、Service.config、Service.invoke(可调用)、Service.extend、Service.resolveConfig。类型侧用 declaration merging 让 ctx.greeter 通过检查。

Events:四种 dispatch mode

ModeAwaitedOrder返回值
emitNo注册序No
waterfallNo注册序包装链Yes(around-middleware,listener 收到 (...args, next),不调 next() 即 veto)
parallelYes全部并发No
serialYes注册序Yes(首个 bail 值)

mode 是事件公开契约的一部分(@mode JSDoc tag)。注册:ctx.on(name, listener, options?) 返回 disposer;EventOptions: prepend / global。

Fiber(一个已加载插件实例)

ctx.plugin() 的返回值。关键成员:uid、config(验证过的,update() 更新)、state、dispose()(await 全部清理,递归卸载子插件)、restart()、effect(execute, label?)、await()。ctx.effect() 的 execute 立即执行,产出的 disposers 在卸载时逆注册序运行;多个 async disposers 并发运行,需顺序拆除的步骤放同一个 disposer。

Registry

  • ctx.plugin(plugin, ...args):加载插件(函数/类/{apply} 三形状),config 按 standard-schema 验证;返回 PromiseLike fiber。
  • ctx.inject(deps, callback):所需服务可用时运行回调;任一服务变化时回调被卸载并重跑。⚠️ 实战要点(经插件开发实测):它是 plugin-apply 简写,callback 收到的是声明了该 inject 的 context,不是服务值;在未声明 inject 的 fiber 里直接访问 ctx.<service> 会抛 cannot get property "<service>" without inject。因此”可选服务”不能用硬 inject + 事后判空,而要用 ctx.inject 回调(服务永不出现则回调不触发,fiber 保持 ACTIVE)——这就是软依赖模式。
  • Plugin.Base 元数据:name、Config、inject、provide、intercept。
  • boot assert 差异:headless profile 的 app-boot 会 assert 所有 entry 激活(有 entry 停在 PENDING 即拒绝 boot);可选插件若硬依赖某 profile 不存在的服务会把宿主 boot 搞挂——做通用插件要按软依赖设计。

Fiber 状态机与生命周期

PENDING → LOADING → ACTIVE → UNLOADING → DISPOSED
                 ↘ FAILED
  • PENDING 是合法状态:某 required service 尚不可用(provider 可能稍后挂载)——这是”为什么我的插件什么都不打印”的常见答案。诊断:遍历 ctx.registry.values() 检查 fiber.state === FiberState.PENDING。
  • 卸载触发:config 编辑、HMR、显式 disposal、依赖丢失。
  • 已是 effect 的内建注册:ctx.on、ctx.plugin(child)、服务注册;harness registry 的 register() 也返回 disposer。非 Cordis 管理资源(timer/连接/watcher)需手工包进 ctx.effect()。

HMR

@deepseek-ai/cordis-plugin-hmr 监听文件,保存时卸载旧实例(所有 effect 回卷)并加载新代码重跑 apply——“因为卸载释放 effects、加载跟随依赖,HMR 可以用 unload+load 替换运行中的插件”。编辑 cordis.yml 也被捕获:loader 按 id diff 条目只 mount/unmount/reconfigure 变化部分;无 id 的条目每次生成新 id,任何编辑都重挂载。相关事件:hmr/change、hmr/reload。

cordis-tutorial 七章

  1. first plugin——launcher 建 root Context 挂 Loader,读 cordis.yml 逐条挂载;顺序由 inject 决定。
  2. lifecycle and effects——可逆 effect 与 fiber 状态机。
  3. services——Service 子类 + inject + declaration merging。
  4. events——typed events 与 waterfall short-circuit。
  5. config——config 块按 schema 在 apply 前验证,坏配置 fail loud。
  6. composition and HMR——配置文件即插件树(id/disabled/groups/isolate),诊断 PENDING。
  7. into the harness——defineTool + ctx.tools.register() 接入真实 harness。

Vendoring 方式

  • 源码级 vendor(拷贝进 monorepo 而非 npm 依赖):“让 harness 完全拥有框架层(auditable, patchable, pinned)”。
  • 全部 rescope 到 @deepseek-ai(cordis → @deepseek-ai/cordis);目录名与上游版本号刻意不变;verify-vendored-links gate 断言 link: 解析。
  • 9 个 vendored 包:cordis 4.0.0-rc.7(上游 commit 56b3d4f)、cosmokit 1.8.1、schemastery 3.18.0、loader 1.0.0-rc.5、include 1.0.4、group 1.0.0、timer 1.1.2、hmr 1.0.15、logger-console 1.0.0。注:vendor manifest 记 cordis 4.0.0-rc.7,而 vendor/cordis/package.json 已是 4.0.1——上游自身版本号不一致,本文从 manifest 口径。
  • Local modifications log(18 条)穷尽记录与上游分歧:fiber.ts 生命周期加固(reentrant disposal 缺口、UNLOADING 拒绝建 effect)、Loader/Include 事务性配置对账(失败回滚)、lazy loader config resolution(port cordiverse/cordis#41)、Include patch 语义导出(供 dsh --dump-config 复用)等。
  • 同步流程:记上游 commit → 覆盖 src/ → 重放 local modifications → 更新 manifest → install && test && build。

dsh 对 cordis 的封装(core 子系统)

packages/core = 每个组合都会 boot 的包,全部按 cordis 模式暴露 service(见 architecture 核心包表)。封装模式:

  • AgentFactory 解耦:loop 经 ctx.agents.setFactory() 注册,消费者只依赖 ctx.agents,loop 保持可替换。
  • 类型模式:…Map → derived-union(discriminant interface + keyof union + declaration merging 扩展);branded IDs(SessionId/CallId 编译期不可互换)。
  • Session 为 single source of truth:append-only log,LLM 历史由 deriveMessages() 派生。
  • 框架级内部事件:internal/plugin、internal/status、internal/service、internal/update|get|set(waterfall)、internal/listener、internal/dispatch。

相关链接

  • architecture — profile/bundle 组合与 capability seams
  • extension-development — 插件开发实操(tool/adapter/发布)
  • ship-of-theseus-rsi — Cordis 论文《时空可组合性编程范式》的通俗解读(时间/空间可组合性、Confluence 合流性)