作用域:isolate 与上下文继承
专栏:Cordis 插件框架 · 第 10 / 12 篇:::info 学习目标 完成本篇后你能够:用 isolate 让不同上下文拥有同名服务的不同实现;解释 dsh 的”按 agent 过滤事件”机制链路;设计多租户的插件作用域方案。 前置:第 3、5、7 篇完成。预计时长:50 分钟。 :::
默认情况下,ctx.tools 在整个进程里是同一个实例。但 dsh 需要每个 agent 拥有独立的工具集、不同分组走不同的 shell 实现。这靠 Cordis 的作用域三件套:isolate(服务隔离)、intercept(配置拦截)、Context.filter(事件过滤)。
isolate:用原型链实现的作用域
回看 context.ts
的实现:// vendor/cordis/src/context.ts:121-125
isolate(name: string, label?: symbol) {
const shadow = Object.create(this[symbols.isolate]) // 原型指向父的隔离映射
shadow[name] = label ?? Symbol(name) // 为 name 绑定新标签
return this.extend({ [symbols.isolate]: shadow })
}
每个 ctx 携带一张 服务名 → 标签 的隔离映射,映射本身用原型链串联。解析服务时,从 ctx 出发沿原型链查找该服务的标签,找到最近的(最作用域化的)标签,在该标签下解析实现。效果:
两个子 ctx 各自 ctx.shell 解析到不同实现,互不可见;根 ctx 仍然能解析到自己作用域的(或报不存在)。isolate 传同一个 label 可以把多个 ctx 加入同一作用域——它们共享同名服务。
Service 的作用域判定
第 4 篇提过 Service 的两行过滤器(service.ts
):比较请求方与服务提供方的 isolate 标签——相同(同一作用域)才可见。事件分发(第 7 篇的Context.filter)用同样的比较:无标签的监听器全局收事件;带标签的只收本作用域与子孙作用域的事件(标签沿原型链向上查找,天然实现”子孙可见”)。
intercept:作用域内的配置拦截
intercept(name, config) 与 isolate 同构——沿原型链的配置映射,resolveConfig 合并时越靠近使用处的配置优先级越高(第 4 篇)。dsh 的 preset(agent 平面组装)就靠它:同一个 tools 服务,在不同 agent 的作用域里拿到不同的配置。
dsh 的实战:每个 agent 一个世界
把三件套拼起来,就是 dsh 的按 agent 隔离(packages/core/scope 的 createScope + dsh 专栏第 4 篇的引用):
与 dsh 的对照:第 10 篇(dsh 专栏)的能力 seam 说”换一个提供方换掉半个产品”——而作用域让这件事可以按 agent 粒度发生:A agent 用本地 shell,B agent 同时用远程沙箱 shell,互不干扰。
常见踩坑
- isolate 后忘了重新 provide——新作用域里服务不存在,注入它的插件会 PENDING;
- 以为子作用域会继承父的实现——isolate 是”换标签”不是”继承”,子作用域必须自己 provide(或在父作用域提供且不做 isolate 才共享);
- 在根 ctx provide 了被 isolate 的服务——根作用域的实现仍然存在,只是子作用域看不见它;清理时注意两份都要收。
随堂练习(带验收标准)
- 创建根 ctx + 两个 isolate 子 ctx,各 provide 不同的
greet。验收:三个 ctx 各自解析到不同的实现(根的可能报不存在); - 用相同的 label 调两次 isolate,验证两个 ctx 共享同一实现;
- 在子 ctx 上注册一个监听器,从兄弟子 ctx emit 同名事件。验收:监听器收不到——作用域过滤生效。