← 核心插件深度解析索引 / 整体架构解析
@deepseek-ai/dsh-scope · packages/core/scope

dsh-scope — 作用域注册原语

scope 是地基:system-prompt 的 ScopedLayers、tools 的 restrict、agent 的 agent.ctx 全部建立在它上面。 一个 agent 的注册既 scope-可见又 scope-生命周期 —— 一个事实驱动两者。

ctx key: 无(纯函数库) 4 个 src 文件 · 598 行 非插件:createScope / scopeTarget / ScopedLayers + scope-invariant 伴侣 消费方:agent · tools · system-prompt · preset

01包定位与入口

非插件(无 name/inject/apply):纯函数库 + 可选 invariant 伴随插件(@deepseek-ai/dsh-scope/invariant)。 导出(index.ts:11-12):createScope / bindScopeParent / scopeParentOf / scopeChainOf / scopeOf / scopeTarget / isScopeCarrier / carrierKeyOf + { AnonymousEntries, NamedEntries, ScopedLayers }ScopeLayer

◆ 核心语义ScopeKey = object,按对象同一性比较(引用相等)。惯例:活体 agent 就是它自己 scope 的 key。 scope 是 trusted same-process 插件路由机制,不是 sandbox / 权威边界

02核心接口

createScope(ctx, key, options?): Scope      // 铸带 tag 的 Cordis context;backing fiber 拥有经它做出的每次注册
bindScopeParent(key, parent): void        // 一次性;已绑定再 bind 抛错
scopeChainOf(key): ScopeKey[]            // [key, parent, …] 最近优先;环检测抛错
scopeOf(ctx): ScopeKey | undefined       // 读最近 tag;派生 context 继承 tag
scopeTarget(base, key): Scoped<T>        // scope 过滤的只路由事件载体
isScopeCarrier(x) / carrierKeyOf(x)     // 供 invariant 用(WeakMap 打标)

// 存储层:
ScopedLayers<L>.effect(ctx, action, opts)   // 可见性与所有权的同一来源,返回精确 disposer
ScopedLayers<L>.peek(scope)                // chain-blind:只取该 scope 自己层的贡献
ScopedLayers<L>.chainLayers(scope)         // 最远祖先在前,精确 scope 最后
ScopedLayers<L>.merge(scope, pick)         // 全局打底 + 链上各层按名覆盖(shadowing 在 store 层)

03关键机制深度解析

createScope:tag 与 context 铸造(index.ts:137-147)

createScope(ctx, key, options?) {
  if (options?.parent !== undefined) bindScopeParent(key, options.parent)
  const fiber = ctx.plugin(scope)          // 共享 no-op 插件作 backing fiber
  const scoped: Context = fiber.ctx.extend({ [kScope]: key })
  ...
  return { ctx: scoped, rawDispose: fiber.dispose,
           dispose: () => (disposing ??= quiesceFiber(fiber)) }
}

rawDispose精确 Cordis disposer(合成 effect 可 yield 它以嵌套在指定 yield 位置);dispose 幂等共享静默边界, 即使 rawDispose 已被单次调用也等 fiber 惯性结束。嵌套 scope 的 context shadow 为单一最近 tag(层级在 key 的父链,不在 context tag)。

scopeTarget:事件准入的过滤语义(index.ts:170-185)

filter(ctx)
与 base 的既有 Cordis filter 组合(先 base 后 scope 谓词)
tag = scopeOf(ctx)
tag === undefined → 全局放行(无 tag 监听者收全部)
沿父链匹配
tag === key 或是 key 的祖先 → 放行;key === undefined → 只收无 tag 监听者
▼ 事件向上延伸,永不向下流
祖先 scope 的监听者收到每个后代 scope 的事件
一个 standing preset 组合可观察其下每个 agent;{ global: true } 监听者绕过过滤

载体只含路由状态;真实 subject 通过事件参数传递(Scoped<T> 不暴露属性)。Scoped<T> 是编译期不透明品牌 —— 裸 subject 作 thisArg 是编译错误。

ScopedLayers.effect:store 层的生命周期(store.ts:226-266)

effect(ctx, action, options) {
  const scope = scopeOf(ctx)               // 调用方 ctx 决定可见性与生命周期
  const dispose = ctx.effect(function* () {
    // 无 scope → global 层;有 scope → 惰性创建/复用
    try { undo = action(layer) }
    catch (error) {
      // 失败回滚:若该层是本次新建且完全空,立即从 scoped Map 删除(不留空壳层)
      throw error
    }
    yield () => { undo(); // 层全空则回收;notify onChange }
    if (notify) onChange()
  })
  return dispose                       // 精确 disposer
}

invariant(scope/src/invariant.ts)

internal/dispatch(global),对每个声明为 scope-filtered 的事件(scoped-events.generated.ts,由 pnpm run gen-scoped-events 生成): ① 必须带载体分派(thisArg 必须是 scopeTarget 产物,否则 fail); ② 载体 key 必须等于 payload 命名的主体(如 agent/* 的 agent 参数、system-prompt/assemble 的 args[1].scope)。 session/*、subagent/* 解析器为 null(只查载体存在)。

04自定制插件指南

为一个 agent 注册 scoped 工具 / 区块 / 监听者

  1. 拿到 agent.ctx:loop 挂载于 agent 属性;在 agent/created 或 preset mount 等时机经 agent 参数获得
  2. 在 agent.ctx 上调用 scope-aware 注册 API:agent.ctx.systemPrompt.section/tools/variable(...)agent.ctx.tools.restrict(...)` 等 —— ScopedLayers.effect 从 scopeOf(ctx) 提取 scope,注册既 scope-可见又 scope-生命周期
  3. 监听事件:agent.ctx.on('system-prompt/assemble', ...) 只收到该 agent 的组装;agent.ctx.on('tools/execute', ...) 只收到该 agent 的工具执行
  4. 释放:disposer 随调用 fiber;或 scope.dispose() 统一清场(幂等、共享静默)

restrict 与 scope-local 注册的区别

构建自己的 scope-aware registry

  1. 实现 ScopeLayer(聚合各表 + isEmpty());用 ScopedLayers 存层;effect() 挂注册 API
  2. scopeTarget(base, scope) 分派自己的 scope-filtered 事件,事件声明用 Scoped<...> this 类型
  3. 把自己的 scope-filtered 事件名加入 gen-scoped-events 生成源,让 invariant 强检生效

05关键陷阱与约定