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)
- A plugin is an object that implements Service——函数(带可选
inject/apply(ctx))或Service子类。 - A context is a repository of services——服务占据稳定的
ctx.<key>(如ctx.tools、ctx.llm、ctx.sessions),按 key 找服务而不是 import 实现。 - Declare service dependency via
inject——加载顺序由服务依赖表达,而非手工 boot 排序。 - Typed Events for communication——TypeScript declaration merging 声明事件名,按 mode 分发。
- 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
| Mode | Awaited | Order | 返回值 |
|---|---|---|---|
emit | No | 注册序 | No |
waterfall | No | 注册序包装链 | Yes(around-middleware,listener 收到 (...args, next),不调 next() 即 veto) |
parallel | Yes | 全部并发 | No |
serial | Yes | 注册序 | 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 七章
- first plugin——launcher 建 root Context 挂 Loader,读
cordis.yml逐条挂载;顺序由inject决定。 - lifecycle and effects——可逆 effect 与 fiber 状态机。
- services——
Service子类 + inject + declaration merging。 - events——typed events 与 waterfall short-circuit。
- config——
config块按 schema 在apply前验证,坏配置 fail loud。 - composition and HMR——配置文件即插件树(
id/disabled/groups/isolate),诊断 PENDING。 - 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-linksgate 断言link:解析。 - 9 个 vendored 包:
cordis4.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 +keyofunion + 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 合流性)