[DeepSeek Harness深度拆解-09]基于Scope的隔离机制是如何实现的?

DeepSeek Harness深度拆解-08:如何实现在自定义的逻辑范围内的隔离通过实例演示的方式体现了@deepseek-ai/dsh-scope提供的基于Scope的隔离机制。通过提供的createScope函数,我们可用构建一个逻辑层面的Scope树,并将节点动态绑定到Context树的某个节点上,再利用scopeTarget函数提供的对象作为触发事件的主体,该对象会利用提供的过滤函数让基于某个Scope发出的事件只能在限定的Scope内传播。结合使用ScopedLayers,我们可以实现基于Scope的可继承注册表。接下来我们就来看看这一切都是如何实现的。

1. ScopeKey和Scope树的构建

Scope树的节点通过如下这个Scope接口表示,每个Scope通过一个ScopeKey对象作为唯一标识。ScopeKey本质上就是object类型的别名。

exporttypeScopeKey=object

Scope树通过如下这个类型为WeakMap<ScopeKey, ScopeKey>的全局弱引用字段来维护,每个KV表示一个具有父子关系的ScopeKey对,父子是一对多的关系,所以这里Key和Value分别是Child和Parent。

constscopeParents=newWeakMap<ScopeKey,ScopeKey>()

如下所示的linkScopeParent函数会根据指定的两个ScopeKey,构建一个KV并添加在scopeParents全局字典中。在添加之前会进行环路检验。我们可调用的bindScopeParent这个导出的函数,它除了调用linkScopeParent方法完成指定的这对父子关系的构建外,还利用返回对象提供的rebind方法实现针对另一个Parent的重新绑定。bindScopeParent函数返回类型被声明为ScopeParentBinding接口。

exportfunctionbindScopeParent(key:ScopeKey,parent:ScopeKey):ScopeParentBinding{if(scopeParents.has(key)){thrownewError('dsh-scope: scope key is already bound to a parent; re-linking requires the binding returned by the original bind')}linkScopeParent(key,parent)return{rebind(next:ScopeKey):void{linkScopeParent(key,next)},}}functionlinkScopeParent(key:ScopeKey,parent:ScopeKey):void{for(letcursor:ScopeKey|undefined=parent;cursor!==undefined;cursor=scopeParents.get(cursor)){if(cursor===key)thrownewError('dsh-scope: scope parent link would form a cycle')}scopeParents.set(key,parent)}exportinterfaceScopeParentBinding{rebind(parent:ScopeKey):void}

2. Scope的创建

Scope树的节点通过如下这个Scope接口表示,它本质上是对一个Context的封装。Scope同时是一个Disposable对象,可以调用其dispose方法将其终结释放。

exportinterfaceScope{ctx:ContextrawDispose:()=>Promise<void>|voiddispose():Promise<void>}

定义在Scope接口的三个成员说明如下:

  • ctx:该Scope绑定的Context;
  • rawDispose:原始释放函数。它是真正执行清理的逻辑,可能是同步或异步。以raw作为命名前缀是因为它通常不包含框架的包装逻辑(幂等、并发控制、事件触发、子Scope清理等),一般不直接对外暴露或调用;
  • dispose:对外释放方法,统一返回Promise<void>。

Scope对象最终通过如下这个createScope函数根据指定Conext、ScopeKey和可选的CreateScopeOptions创建而成。CreateScopeOptions利用parent提供作为Parent标识的ScopeKey,这是一个可选的参数。如果利用这个配置选项提供了Parent节点,会调用上述的bindScopeParent函数建立父子关系。

exportfunctioncreateScope(ctx:Context,key:ScopeKey,options?:CreateScopeOptions):Scope{if(options?.parent!==undefined)bindScopeParent(key,options.parent)constfiber=ctx.plugin(scope)constscoped:Context=fiber.ctx.extend({[kScope]:key})letdisposing:Promise<void>|undefinedreturn{ctx:scoped,rawDispose:fiber.dispose,dispose:()=>(disposing??=quiesceFiber(fiber)),}}functionscope():void{}constkScope=Symbol('dsh.scope')

由于Scope同时是一个Disposable对象,需要一个生命周期管理器。在Cordis的世界里,说到管理生命周期,自然会想到Fiber。从createScope函数的定义可用看出,Scope对象也采用Fiber来管理自己的生命周期。由于Fiber随着插件的注册而创建,所以虽然不需要一个具体的插件,这里不得不调用指定Context的plugin方法注册了一个什么都没做的插件函数,其目的就是为了得到这个Fiber对象。我们知道插件是与创建的子Context绑定在一起的,这个子Context通过调用extend方法作进一步扩展,其目的是将当前Scope的Key利用kScope常量表示的Symbol附加到Context上,Socpe的ctx字段最终返回的就是这个Context。

至于为创建的Scope对象定义的其他两个方法,rawDispose直接对应Fiber对象的dispose方法,dispose方法返回的Promise<void>是如下这个quiesceFiber函数的返回值,该方法在调用Fiber对象的dispose方法之余,还会利用一个循环内持续调用Fiber的inertia方法,其目的在于确保整个释放过程完全结束。

asyncfunctionquiesceFiber(fiber:Fiber):Promise<void>{awaitPromise.resolve(fiber.dispose())while(fiber.inertia!==undefined)awaitfiber.inertia}

3. 根据Scope过滤事件处理器

DeepSeek Harness插件内核-15:再谈Cordis的事件总线对Cordis事件总线的事件分发机制进行了详细介绍,我们知道除了internal/update这种特殊的事件会将作为事件函数至于Fiber自身的_hooks字段外,其余的事件处理函数都会被封装成Hook被注册到EventsService这个单例服务对象的_hooks字典中。所以在不进行任何过滤的前提下,只要指定了正确的事件名称,不论当前身在何处,都能监听到来自当前Context树任何一个地方触发的事件。

但是如果在分发事件的时候指定了一个主体对象,该对象除了会作为事件处理函数的执行上下文(this)之外,定义其中的symbols.filter方法还会用来过滤获选的Hook对象。这个过滤方法的签名(Context)=>boolean,实施过滤时将会使用Hook的ctx字段作为参数。scopeTarget函数正是利用了这一原理创建了一个对象,并利用当前Scope定义了这个过滤函数实现了事件向上传递的特性。

如下面的代码所示,scopeTarget函数提供了两个参数。base参数提供的对象可以利用自身的symbols.filter方法实施前置过滤,key参数则提供一个ScopeKey表示触发事件所在的Scope。scopeTarget函数最终返回一个只包含symbols.filter方法的对象。

exportfunctionscopeTarget<Textendsobject>(base:T,key:ScopeKey|undefined):Scoped<T>{constbaseFilter=(baseas{[CordisContext.filter]?:(ctx:Context)=>boolean})[CordisContext.filter]constcarrier={[CordisContext.filter](ctx:Context):boolean{if(baseFilter!==undefined&&!baseFilter.call(base,ctx))returnfalseconsttag=scopeOf(ctx)if(tag===undefined)returntruefor(letcursor=key;cursor!==undefined;cursor=scopeParents.get(cursor)){if(cursor===tag)returntrue}returnfalse},}carrierKeys.set(carrier,key)returncarrierasunknownasScoped<T>}exportfunctionscopeOf(ctx:Context):ScopeKey|undefined{return(ctxasContext&{[kScope]?:ScopeKey})[kScope]}constcarrierKeys=newWeakMap<object,ScopeKey|undefined>()

返回对象的过滤方法具有这样的逻辑:

  • 如果base参数指定的对象定义了过滤方法,并且返回false,则直接返回false;
  • 调用scopeOf函数提取Context携带的Scope标识,如果不存在,意味Context并没有被限定为某个Scope,直接返回true;
  • 根据参数key指定的Scope作为起点在Scope树(通过scopeParents表示)中不断向上追溯,只要遇到一个Scope与当前Context携带的Scope一致,这意味着Hook对应的Context在触发事件源Scope的向上继承链路上,符合事件向上传递的法则,返回true;
  • 不满足以上条件,即表明Hook并不在监听的目标范围内,返回false阻止它的执行。

scopeTarget函数返回的对象被视为一个携带过滤方法的对象会被添加到carrierKeys这个全局字典中,用来存储这样的对象与对应ScopeKey的映射。scopeTarget函数的返回类型被声明为Scoped<T>,定义如下。

exporttypeScoped<Textendsobject>=object&{readonly[ScopedBrand]:T}declareconstScopedBrand:uniquesymbol

4. 基于ScopedLayers的注册表

Scope树具有两种消费形式,一种就是利用scopeTarget函数构建一个主体对象来过滤事件监听器,另一种就是利用ScopedLayers<L>构建一个具有层级继承关系的注册表。我们可以将ScopedLayers<L>视为多层注册表的集合,而作为泛型参数的L表示具体某个单层的注册表。L派生于接口ScopeLayer,后者仅仅定义了唯一的isEmpty方法来确定注册表是否为空。

exportclassScopedLayers<LextendsScopeLayer>{readonlyglobal:Lprivatereadonlyscoped=newMap<ScopeKey,L>()constructor(privatereadonlycreateLayer:(scope:ScopeKey|undefined)=>L,privatereadonlyonChange:()=>void){this.global=createLayer(undefined)}}exportinterfaceScopeLayer{isEmpty():boolean}

如ScopedLayers<L>的定义可知,它利用global字段返回的ScopeLayer来表示全局共享的注册表,而scoped字段返回的字典则用来表示基于某个Scope的注册表。构造函数提供如下两个参数(同时绑定为同名的字段):

  • createLayer: 用来创建ScopeLayer的工厂函数;
  • onChange: 注册表发生更新后的回调函数。

当ScopedLayers被构造的时候,由参数createLayer指定的工厂函数会被用来创建一个ScopeLayer对象,并将其作为全局注册表赋值给global字段。如下定义的effect方法是ScopedLayers最为核心的方法,因为我们会调用此方法利用作为参数的action函数来完成具体的注册操作。除了action参数,该方法提供的ctx参数体现了注册操作针对的Scope。如果没有绑定具体的Scope,意味着直接注册到全局注册表中。该方法最终返回Fiber同名方法的调用,options参数的label最终会作为调用该方法的同名参数,notify字段表示注册成功或者解除注册时是否调用onChange函数来发送通知。

exportclassScopedLayers<LextendsScopeLayer>{effect(ctx:Context,action:(layer:L)=>()=>void,options:{label:string;notify?:boolean},):()=>void{constscope=scopeOf(ctx)constnotify=options.notify??trueconstdispose=ctx.effect(function*(this:ScopedLayers<L>){letlayer:Lletcreated=falseif(scope===undefined){layer=this.global}else{constexisting=this.scoped.get(scope)if(existing===undefined){layer=this.createLayer(scope)this.scoped.set(scope,layer)created=true}else{layer=existing}}letundo:()=>voidtry{undo=action(layer)}catch(error){if(scope!==undefined&&created&&layer.isEmpty())this.scoped.delete(scope)throwerror}yield()=>{undo()if(scope!==undefined&&layer.isEmpty())this.scoped.delete(scope)if(notify)this.onChange()}if(notify)this.onChange()}.bind(this),options.label)returndispose}}

如上述的代码所示,在将指定Context绑定的Scope提取出来后,方法调用Context对象effect方法(其实就是Fiber的effect方法),并利用设置的生成器函数执行如下的操作:

  • 确定操作的ScopeLayer:
    • 如果当前Context没有绑定一个具体的Scope,直接使用global字段返回的作为全局注册表的ScopeLayer对象;
    • 否则从scoped字典提出对应的ScopeLayer对象,如果不存在就调用createLayer函数创建一个新的ScopeLayer对象并添加到scoped字典中。
  • 将上面解析出来的ScopeLayer作为参数调用action函数实施注册操作,并得到返回的清理函数(比如解除注册操作就可以实现在这里);
  • 如果开启通知,则调用onChange函数;
  • yield语句会保证执行返回的函数会:
    • 执行action函数返回的清理函数;
    • 如果ScopeLayer为空,且不是全局注册表,则将其从scoped字典中移除。

ScopedLayers还定义了如下三个方法:

  • peek:从scoped字典中提取指定Scope的ScopeLayer;
  • chainLayers:提取指定Scope及其上级的所有ScopeLayer(不包含global),按照Scope从高到低排序;
  • merge<V>: 将指定Scope及其上级的所有ScopeLayer(包含global)注册的条目合并成字典(Map<string, V>)输出。
exportclassScopedLayers<LextendsScopeLayer>{peek(scope:ScopeKey|undefined):L|undefined{if(scope===undefined)returnundefinedreturnthis.scoped.get(scope)}chainLayers(scope:ScopeKey|undefined):L[]{constlayers:L[]=[]for(constkeyofscopeChainOf(scope).reverse()){constlayer=this.scoped.get(key)if(layer!==undefined)layers.push(layer)}returnlayers}merge<V>(scope:ScopeKey|undefined,pick:(layer:L)=>NamedEntries<V>):Map<string,V>{constmerged=newMap(pick(this.global).entries())for(constlayerofthis.chainLayers(scope)){for(const[name,value]ofpick(layer).entries())merged.set(name,value)}returnmerged}}exportfunctionscopeChainOf(key:ScopeKey|undefined):ScopeKey[]{constchain:ScopeKey[]=[]for(letcursor=key;cursor!==undefined;cursor=scopeParents.get(cursor))chain.push(cursor)returnchain}

merge<V>方法除了传入标识Scope的ScopeKey之外,还需要提供一个函数将ScopeLayer转换成NamedEntries<V>。我们自定义的ScopeLayer一般使用NamedEntries<V>来存储注册的需要命名的条目。与之对应的AnonymousEntries<V>,它们都派生自EntryValues<V>接口。

interfaceEntryValues<V>{values():IterableIterator<V>isEmpty():boolean}exportclassNamedEntries<V>implementsEntryValues<V>{privatedata=newMap<string,V>()constructor(privatereadonlyduplicateError:(name:string)=>Error)insert(name:string,value:V):()=>voidget(name:string):V|undefinedhas(name:string):booleankeys():IterableIterator<string>entries():IterableIterator<[string,V]>values():IterableIterator<V>isEmpty():boolean}exportclassAnonymousEntries<V>implementsEntryValues<V>{privatedata=newMap<symbol,V>()append(value:V):()=>voidvalues():IterableIterator<V>isEmpty():boolean}

相关新闻

2slides 演示文稿生成技能实战指南:从文本、参考图与文档到 AI 配音 PPT 的完整工作流

2slides 演示文稿生成技能实战指南:从文本、参考图与文档到 AI 配音 PPT 的完整工作流

2slides 演示文稿生成技能实战指南&#xff1a;从文本、参考图与文档到 AI 配音 PPT 的完整工作流 【免费下载链接】agentic-awesome-skills AAS Core is the local, agent-first control plane for complete catalog discovery, agent-owned selection, stack validation, and…

2026/10/21 2:17:50 阅读更多 →
RAG系统节点级评测完整方案

RAG系统节点级评测完整方案

RAG系统节点级评测完整方案 节点级评测是RAG分层质量体系的最小单元&#xff0c;对应软件工程的单元测试。核心逻辑是单一变量控制 独立输入输出验证&#xff1a;为每个功能节点构造标准化输入&#xff0c;单独校验其输出质量与性能&#xff0c;彻底排除上下游节点的干扰&…

2026/10/21 2:17:50 阅读更多 →
Gauge Python 运行器安装配置与避坑指南

Gauge Python 运行器安装配置与避坑指南

简介&#xff1a;gauge-python 是 Gauge 自动化测试框架的 Python 语言运行器插件&#xff0c;面向使用 Python 编写验收测试、行为驱动测试的开发者与测试工程师&#xff0c;解决 Gauge 在 Python 环境下执行 specs 用例、解析步骤实现与结果校验的问题。资源包共 56 个文件&a…

2026/10/21 2:17:50 阅读更多 →

最新新闻

无人机航拍小目标检测数据集构建:从场景设计到智慧交通与应急救援落地

无人机航拍小目标检测数据集构建:从场景设计到智慧交通与应急救援落地

我做无人机视觉落地也有几年了&#xff0c;今年被问得最多的问题不是“模型怎么调”&#xff0c;而是“模型怎么到了现场就不灵”。很多朋友拿着公开的航拍检测模型&#xff0c;在桌面演示上mAP能到七八十个点&#xff0c;可一部署到国内的十字路口、老小区、城乡结合部&#x…

2026/10/21 3:07:07 阅读更多 →
Seay源代码审计系统实战:PHP漏洞扫描与规则扩展

Seay源代码审计系统实战:PHP漏洞扫描与规则扩展

简介&#xff1a;Seay源代码审计系统是一款基于C#开发的PHP代码安全审计工具&#xff0c;主要运行于Windows平台&#xff0c;面向网络安全初学者、CTF/AWD参赛选手及渗透测试人员&#xff0c;用于快速定位PHP项目中的安全隐患。它能够识别SQL注入、代码执行、命令执行、文件包含…

2026/10/21 3:07:07 阅读更多 →
Seay源代码审计系统实战:PHP静态代码审计与AWD快速排查指南

Seay源代码审计系统实战:PHP静态代码审计与AWD快速排查指南

简介&#xff1a;Seay源代码审计系统是一款基于C#开发的PHP代码安全审计工具&#xff0c;主要运行于Windows平台&#xff0c;面向网络安全初学者、CTF/AWD参赛选手及渗透测试人员&#xff0c;用于快速定位PHP项目中的安全隐患。它可检测SQL注入、代码执行、命令执行、文件包含、…

2026/10/21 3:07:07 阅读更多 →
程序员 AI 出图避坑:椒图 AI 与 Nano Banana Pro 实战指南

程序员 AI 出图避坑:椒图 AI 与 Nano Banana Pro 实战指南

如果你也是那种被拉去“顺带做个图”就浑身难受的程序员&#xff0c;这篇文章应该能帮你省下不少救心丸。我自己的本职是写后端&#xff0c;大部分时间对着终端和接口文档&#xff0c;但只要涉及方案评审、产品汇报、开源项目宣传&#xff0c;就逃不开一个尴尬任务&#xff1a;…

2026/10/21 3:07:07 阅读更多 →
从能生成到有质感:Seedance 2.0镜头语言全解析

从能生成到有质感:Seedance 2.0镜头语言全解析

先说一个我自己的真实感受&#xff1a;第一次用 Seedance 2.0 生成视频时&#xff0c;我很兴奋&#xff0c;因为画面真的“能动了”。但等我把五六段素材拼到一起&#xff0c;越看越不对劲——画面在动&#xff0c;情绪却没动&#xff1b;每个镜头单拎出来都不差&#xff0c;连…

2026/10/21 3:07:07 阅读更多 →
RAG效果差?问题多半出在知识库管理:清洗、分块与治理实战

RAG效果差?问题多半出在知识库管理:清洗、分块与治理实战

先说明一点&#xff1a;这个标题我深有同感。做了大半年RAG相关的东西&#xff0c;见过不少团队把精力全压在召回链路调参上&#xff0c;结果效果上不去&#xff0c;最后排查一圈&#xff0c;问题全出在知识库本身——脏数据、错分块、无版本、无评测&#xff0c;源头一乱&…

2026/10/21 3:06:07 阅读更多 →

日新闻

统一内存+量化:锐龙AI Max PRO 400如何本地跑320B大模型

统一内存+量化:锐龙AI Max PRO 400如何本地跑320B大模型

这段时间硬件圈和AI圈都在传一个事&#xff1a;AMD锐龙AI Max PRO 400系列把最高192GB统一内存搬进了PC形态的产品里&#xff0c;按宣传口径&#xff0c;本地跑320B参数的大家伙不再是纸面概念。我第一反应倒不是“内存真大”&#xff0c;而是这话背后的含金量——它把过去只能…

2026/10/21 0:00:11 阅读更多 →
AI助手深度使用体验:对话质量、长文本处理与指令遵循如何留住用户

AI助手深度使用体验:对话质量、长文本处理与指令遵循如何留住用户

1. 从“封号重开”这个行为说起&#xff1a;用户到底在追求什么“封号重开”这四个字&#xff0c;放在任何产品语境里都带着一股决绝的味道。一个账号从零开始积累使用记录、调教偏好、沉淀工作流&#xff0c;背后是大量的时间和精力成本。正常情况下&#xff0c;没有人愿意主动…

2026/10/21 0:00:11 阅读更多 →
ROS中YOLOv3工程落地:通信同步、坐标变换与性能调优

ROS中YOLOv3工程落地:通信同步、坐标变换与性能调优

1. 为什么在ROS里硬刚YOLOv3不是“炫技”&#xff0c;而是工程落地的必经之路我第一次在某高校机器人实验室看到那台搭载Jetson TX2的移动底盘原地打转、反复识别错垃圾桶时&#xff0c;就意识到&#xff1a;单纯跑通darknet_ros demo和真正让YOLOv3在ROS里“干活”&#xff0c…

2026/10/21 0:00:11 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型&#xff08;GMM&#xff09;的语音识别系统&#xff0c;很多人第一反应就是一堆数学公式&#xff0c;觉得门槛很高。但如果你真的在MATLAB里把它落地跑通&#xff0c;会发现核心其实就是三件事&#xff1a;把声音变成特征向量&#xff0c;用GMM给每个类别…

2026/10/20 10:02:13 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人&#xff0c;但落到具体操作上&#xff0c;无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程&#xff1a;对图像做反白、改调色板、彩色转灰度&#xff0c;最后把像素数据导出成txt文件。单看每一…

2026/10/20 20:43:59 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 10:02:08 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:45 阅读更多 →