← 返回列表
⚠ 装前注意
一个 DeepSeek Harness 插件,当调用会话无法授予沙箱升级字段时,阻止这些字段被提供给模型。
基本兼容但装前注意:未发布到 npm registry,仅可从源码安装 · 最近上游提交 2026/9/19 · 已提供中文文档
DeepSeek Harness 插件:当调用会话无法授予沙箱升级字段时,阻止这些字段被通告给模型,从而修复已知模型在 danger-full-access 下陷入的重试循环。
综合分
30.5
GitHub 分
30.5
用户评分
—
★ Stars
1
周下载量
—
安装插件(需先安装 dsh CLI 引擎:npm install -g @deepseek-ai/dsh)
dsh plugin --profile web add iimaguest/dsh-sandbox-escalation-guard未发布到 npm registry,仅可从源码安装,改用 GitHub 源安装
信任档位:已验证本站已于 2 天前真实安装成功
- 是什么
- dsh 原生插件 · ads
- 装得上吗
- 本站已真实安装成功(非静态推断)
- 安全吗
- 本站尚未对该插件做风险分级(暂未覆盖,不等同于无风险)
- 还在维护吗
- 活跃:最近一次提交在 6 天前
档位由下列信号合成:本站实装验证(真实安装,当前最高到 L4)· 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。下方各区块是它的证据明细。 验证判据与等级说明 →
🟢实装验证通过· 2026/9/24
由本站实装验证器在真实 dsh 环境安装成功,非静态推断。
数据截至 2026/9/22(元数据每日更新 · 实装验证按队列轮转,单条结论的验证时间见上方)
安装兼容性检查⚠ 装前注意
以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。
✗npm 包dsh-sandbox-escalation-guard(未发布到 npm,仅可源码安装)
✓Node 引擎未声明 engines.node
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明
未发布到 npm registry,仅可从源码安装
验证方式:npm registry 存在性 + package.json 静态校验 · 最后验证 2026/9/22 00:10:21
依赖的 DSH / Cordis 模块
@deepseek-ai/cordis@deepseek-ai/dsh-sandbox@deepseek-ai/dsh-system-prompt用户评分
还没有人投票,来当第一个
订阅周报,不错过优质插件更新
每周一封 · 高评分插件 + 新用户活动
README
由 DeepSeek 最新模型翻译生成dsh-sandbox-escalation-guard
一个 DeepSeek Harness 插件,当调用会话无法授予沙箱升级字段时,阻止这些字段被提供给模型。
它修复的是一个 GPT 特有的故障。 GPT 系列模型会填满工具 schema 中的每一个可选属性,而 bash、write 和 edit 上的两个可选属性恰好是全权限会话永远无法使用的两个。于是 GPT 在每次调用时都会把它们填上,每次调用都在运行前被拒绝,而错误文本从不说明解决办法就是省略它们。这一轮就这样死掉,什么都没执行。
一个只发送调用所需内容的模型永远不会遇到这个问题——这就是为什么它看起来像是“GPT 坏了”,而其他模型在同一会话中却一切正常。解决办法不是更好的提示词:而是停止发布那些无法被填写的字段。参见为什么这表现为 GPT 特有。
这个 bug
DSH 升级字段——sandbox_permissions 和 justification——是根据加载时的能力事实对外公布的,但在执行时却是根据每会话的策略事实来判定的:
| | 来源 | 回答的问题 |
|---|---|---|
| 公布 | ctx.shell.sandboxMode | 是否挂载了限制性执行器? |
| 执行 | ctx.sandboxPolicy.resolve().mode | 这个会话实际处于什么模式? |
它们恰好会在最要命的情况下产生分歧:会话正处在上限。
一个有效模式为 danger-full-access 的会话会收到:
"sandbox_permissions": {
"type": "string",
"enum": ["workspace-write", "danger-full-access"]
}
两个值都不可用。workspace-write 是一种降级,而 danger-full-access 是已经生效的模式——并且检查要求严格更宽,所以两者都永远无法被授予。系统提示词同时却指示模型使用该字段来“立即升级”。
一个会填满所看到的每一个可选属性的模型——GPT 系列就是这样——于是既无法成功,也无法知道原因:
Error: invalid justification: expected a non-empty sentence
Error: sandbox escalation to "danger-full-access" is not strictly wider than this call's current "danger-full-access" mode
Error: sandbox escalation to "workspace-write" is not strictly wider than this call's current "danger-full-access" mode
两段文本都没有指出唯一真正的修复方法,即省略这两个字段。观察到的结果是同一条命令被反复重试,直到这一轮死掉。
为什么这表现为“GPT 坏了”
这里没有任何东西是 harness 中特定于模型的。区别在于一种习惯,而它与这个缺陷完美地撞在一起:
GPT 系列模型会填充工具 schema 中的每一个可选属性。 给定 command(必需)加上 sandbox_permissions 和 justification(可选),它们不会发送调用所需的最小内容——它们会发送所看到 schema 的一个完整实例。被要求给一个字符串,它们却填了三个。
这个习惯通常无害。但在这里却是致命的,因为这两个可选属性恰恰是全访问会话永远无法使用的两个属性。于是模型会在每一次调用时贴心地补全它们,每次调用都会在运行任何东西之前被拒绝,而错误文本指向的是 justification 字段,而不是真正的指令——即省略这些字段。带着同样的习惯重试,会得到同样的拒绝。这一轮结束时,没有任何命令被执行,也没有任何关于原因的可用信号。
一个只发送调用所需内容的模型永远不会碰这些字段,也永远不会遇到这个 bug。从外部看,这就像是某个模型坏了——同一个会话、同一个 harness、同一个仓库、不同的模型,只有一个失败。
修复方案直接来自诊断:如果模型会补全展示给它的任何内容,那就不要再展示它无法使用的东西。 一旦这两个字段从 schema 中消失,这个习惯就没有什么可补全的了,而那个最容易出现此问题的模型的失败模式也随之消失——模型无需知道它曾经存在,也无需通过提示词告诉它要小心,因为 schema 补全型模型最不可能可靠地遵循这类指令。
这就是为什么主要修复是抑制,而不是更清晰的错误消息。更好的消息是在要求模型改变行为;而移除字段则完全不依赖模型的配合。tools/pre-execute 修正只针对残余情况——缓存的 schema,或者忽略所给 schema 的模型。
证据
来自一个实时会话(有效模式 danger-full-access,审批策略 never),比较一个会补全可选属性的模型和一个不会的模型:
| 模型 | 工具调用 | 发出的 sandbox_permissions | bash 调用 | bash + 可选字段 |
|---|---|---|---|---|
| zai/glm-5.3 | 211 | 0 | 86 | 48 |
| oauth-codex/gpt-6-astra | 10 | 8 | 8 | 8 |
GLM 这一列并不是“一个尝试过并成功的模型”——它在 211 次调用中发出该字段的次数是零。它从未进入失败模式,因为它只发送调用所需的属性。GPT 这一列在每一次 bash 调用中都发出了该字段,并因同样的拒绝而丢掉了全部八次。
同一个会话、同一个 schema、同一个仓库、同一个有效模式。唯一的变量是模型是否会补全展示给它的可选属性——这就是为什么从外部看,这像是“GPT 坏了”,而不是一个被某个模型习惯暴露出来的 schema 缺陷。
在线路层面也得到确认:该会话的 request/header 事件显示,bash、write 和 edit 都声明了 sandbox_permissions,其 enum: ["workspace-write","danger-full-access"],因此该字段确实被发布给了模型,而不是模型自己发明的。
这个插件做什么
1. 它停止发布不可能的内容(真正的修复)
工具集在 system-prompt/assemble 处仍然是可变的,这是一个
受认可的扩展点,并按请求运行。是否重写由会话事实决定——即升级是否可能被批准——而绝不取决于当前访问级别:
- 当会话的批准策略为 never,以及对于任何无法识别的模式时,完全移除这两个字段,并从工具描述中删除那段否则会不断敦促模型使用它们的升级段落;
- 当升级确实可能被批准时,保持表面与 harness 构建时完全一致。
更早的一个修订版只保留严格宽于当前模式的枚举值。这种做法已被移除,原因有二:
- 它会移动缓存前缀。 工具块位于缓存的前端,因此在访问级别变化时重写它会丢弃其后的整个缓存对话。
- 访问级别不会自行移动。 随附的权限预设将这两个旋钮捆绑在一起,切换其中一个会同时写入两者:
| 预设 | 沙箱 | 批准 |
|---|---|---|
| read-only | read-only | ask |
| workspace-write | workspace-write | ask |
| danger-full-access | danger-full-access | never |
因此,只要策略保持不变,访问级别就永远不会改变。所以策略才是那个在整个预设生命周期内保持恒定的关键,而这正是缓存前缀必须存活的整个生命周期。
由于决定性事实是批准策略而非模式,所发布的表面在每个请求上都字节级一致,并且在就地更改访问级别时不会移动。这正是保护缓存的属性,test/mount.test.mjs 和 test/real-services.test.mjs 直接固定了这一点。
在任何随附预设中都没有覆盖损失,这一点值得说明,因为本文件的早期版本暗示了相反的情况:
| 预设 | 提供给模型的内容 | 正确吗? |
|---|---|---|
| read-only | ["workspace-write", "danger-full-access"] | 是——两者都确实可授予 |
| workspace-write | ["workspace-write", "danger-full-access"] | danger-full-access 可授予;workspace-write 是向下方向,而升级只会向更宽方向进行 |
| danger-full-access | (字段已移除) | 是——没有更宽的了,因此每个值都会被拒绝。这就是所报告的那次失败。 |
升级严格是加宽,因此模型永远不会请求收窄自身。因此,上述表面对于 harness 随附的每个预设都是正确的。
它仍然依据 @deepseek-ai/dsh-sandbox 导出的、并由 approveEscalation 强制执行的同一条严格更宽的阶梯来判断可授予性,因此两者不可能就可授予的内容产生分歧。该阶梯在 lib/wider-modes.js 中是镜像的,而不是导入的,这就是为什么此包没有运行时依赖——它仅接入宿主契约(apply + inject)。一个 test/wider-modes.test.mjs 漂移防护会将镜像与 harness 自身的导出进行比较,如果 DSH 更改了该阶梯,它就会失败。
对于处于 never 策略下的会话,模型现在收到的 bash 工具只包含
command 和 description,并且完全没有升级相关的说明文字——在所有模式下都是如此。
2. 它会纠正仍然携带这些字段的调用
持有缓存 schema 的客户端,或者忽略自身 schema 的模型,仍然可能
发送不可用的请求。此时 tools/pre-execute 会拒绝该请求,其文本会指明
生效模式、声明没有任何值可被授予,并指明修复方式——而不是那两条
晦涩的校验信息。
这是 (1) 适用时的回退机制,也是 (1) 不适用时的唯一机制。
在处于 ask 策略下的会话中,接口表面被有意保持原样,因此
正是这个监听器防止了不可用的调用以晦涩的方式失败。
该插件从不扩大任何权限
它只是停止提供那些无法被授予的模式。它从不授予 harness 本会拒绝的
模式,而且它也无法做到:它从公布的接口表面移除能力,而不添加任何能力。
抑制矩阵
核心防护是在所有模式下统一的——这正是让缓存的
工具前缀在会话期间保持字节稳定的原因。
| 会话能否获得升级批准? | 公布的枚举 | 描述文字 |
|---|---|---|
| 否——批准策略为 never、无批准服务,或模式无法识别 | (字段被移除) | 升级段落被移除 |
| 是——存在批准通道且策略不是 never | (完全保持 harness 构建时的原样) | 保留 |
这两个条件是会话事实,而非当前访问级别的属性。
当生效策略为 never 时,ApprovalService.decide 会在咨询任何批准者之前
直接返回 "rejected",因此在该策略下,升级在所有模式下都不可能——
而正确的公布接口表面在所有模式下也是相同的。
无法识别的模式不授予任何权限,而不是授予一切:SandboxMode 是一个
经过校验的封闭联合类型,因此这不可能来自健康的主机,而提供
升级选项将是不安全的猜测。
残留情况,以及逐调用纠正
当升级确实可以被批准时,该防护完全不编辑接口表面。此时,
仍然携带不可用请求的调用会在 tools/pre-execute 处被纠正,该处会
逐调用重新求值,且重新运行没有任何成本。这涵盖了:
- 处于 ask 下、其当前模式恰好没有留下任何更宽权限的会话——例如
danger-full-access,从该模式永远无法授予任何升级;
- 持有缓存 schema 的客户端;
- 完全忽略自身 schema 的模型。
该纠正会指明生效模式、声明没有任何值可被授予,并
指明修复方式——不同于它所取代的那两条晦涩的校验信息。关于为何不按模式
裁剪枚举,请参见该插件涵盖与不涵盖的内容。
安装
dsh plugin --profile web add github:iimaguest/dsh-sandbox-escalation-guard
这会从 GitHub 安装、写入依赖,并添加该配置文件的
bundles 条目,因此此包中的 cordis.patch.yml 会自动挂载。确认该行:
grep -A 14 '"bundles"' ~/.dsh/profiles/web/package.json
激活是配置文件加载,而不是文件写入。 该插件在配置文件下次加载时生效。
管理安装
pull new commits that have landed on GitHub
dsh plugin --profile web update dsh-sandbox-escalation-guard
remove completely
dsh plugin --profile web remove dsh-sandbox-escalation-guard
要在不卸载的情况下禁用它,请从配置文件的 bundles 列表中移除 dsh-sandbox-escalation-guard。要针对本地检出而不是 GitHub 进行开发,请使用 dsh plugin --profile web add /path/to/dsh-sandbox-escalation-guard。
配置
- id: dsh-sandbox-escalation-guard
name: dsh-sandbox-escalation-guard
config:
warn: true # log each intervention to the host console
resolveMode: ... # testing seam only; omit in production
escalationPossible: ... # testing seam only; omit in production
| 字段 | 默认值 | 含义 |
|---|---|---|
| warn | true | 当 schema 被收窄或调用被纠正时记录日志。 |
| resolveMode | (未设置) | 为每次调用的纠正覆盖有效模式解析。测试接缝;生产环境通过 sandboxPolicy 读取会话自身的 sandbox/mode 折叠结果。 |
| escalationPossible | (未设置) | 覆盖已发布表面是否保留升级字段。测试接缝;生产环境读取审批策略。 |
此插件涵盖和不涵盖的内容
这里明确说明,是因为覆盖范围在开发过程中发生了变化,而诚实的版本比本文件中早先的声明更窄。
它涵盖升级永远无法被批准的情况。 当部署的审批服务存在且会话策略为 never 时,ApprovalService.decide 会在咨询任何审批者之前返回 "rejected",因此升级在每种模式下都不可能。此时该防护会从已发布表面中移除升级字段,在所有模式下统一执行。这就是所报告的配置(失败的会话记录了 approval/policy: {policy: never}),并且已修复。
它还会针对每次调用,纠正任何仍然到达的不可用请求——缓存的 schema、忽略其 schema 的模型,或策略为 ask 的会话。该检查在 tools/pre-execute 运行,重新评估不产生任何成本,并指出确切的修复方式。
它不会将公布的枚举收窄为当前模式下可授予的值。 早先的修订版这样做过,而那是双重错误:
1. 它破坏了缓存。 每当访问级别变化时,枚举都会移动,而工具块是缓存前缀的开头,因此模式切换会丢弃其后的整个缓存对话。
2. 它发布了一个无论如何都会被拒绝的值。 一个 workspace-write 会话公布了 ["danger-full-access"]。省略一个
字段始终是安全的;发布一个随后被拒绝的值才会破坏模型。在会话可以切换到的每一种模式下都安全的集合,是这些模式的交集——而一旦范围包含 danger-full-access,它就是空集:
| 模式 | 可从其授予 |
|---|---|
| read-only | workspace-write、danger-full-access |
| workspace-write | danger-full-access |
| danger-full-access | (无) |
没有任何值可以从这三种模式中全部授予,因此一个从不提供被拒绝值的界面无法提供任何值。
唯一残留的情况,直白说明。 剩下的缺口需要一种没有任何已发布预设会产生的组合:审批策略为 ask 与 danger-full-access 配对。在这种情况下,这些字段会保留下来(策略说升级可以被批准),而模式说没有更宽的值,因此 GPT 系列模型仍然可以填充它们并收集到一次被拒绝的调用——由 tools/pre-execute 监听器纠正,而不是被阻止。该监听器会指出有效模式和确切的修复方法,因此失败是一次清晰的纠正,而不是促成这个插件的那个不透明的循环。
如果你构建了这样的组合并想要更严格的界面,请设置 escalationPossible: false 以统一抑制。这是关于你的部署的陈述,由你的部署做出——而不是这个插件根据一个它无法知道是否稳定的模式所做的猜测。
截图
交给模型的内容,之前和之后
按会话模式的可授予性
守卫所在的位置
已针对测试框架验证
这些是测试框架自身契约的示意图,而不是 UI 的截图,其中的每个值都复制自已验证的输出——生成它们的是 npm run screenshots,而最后一张图中的模式表正是 npm run verify 打印的内容。
复现该主张
npm install
npm run verify
将插件挂载到真实的 Cordis 上下文和真实的 systemPrompt 注册表上,并为每种会话模式打印面向模型的 bash schema——这是插件的可观察主张,可复现而非仅描述。
测试
npm test
48 个测试:
- test/guard.test.mjs —— 抑制矩阵、对照 approveEscalation 判断的严格更宽表,以及散文剥离。
- test/wider-modes.test.mjs —— 漂移守卫:只要测试框架可解析,就将镜像的阶梯与 @deepseek-ai/dsh-sandbox 自己的导出进行比较;当不可解析时则跳过(而非失败)。
- test/mount.test.mjs —— 将插件应用于真实的 Cordis 上下文和真实的 systemPrompt 注册表,断言模型将会收到什么、一个能够升级的会话保持测试框架自身的字节不变、已发布的界面在访问级别变化时不会移动,以及
卸载会精确恢复原始表面(因此这是一个没有残留的普通插件行),并且预执行修正在不可用请求上才会触发。其中一些在策略服务存在之前挂载插件,然后再提供它,这才是真正的启动顺序——见下面的说明。另一些在守卫两侧挂载相邻的监听器,并在每种模式下重新组装五十次,以证明输出字节从不移动。
- test/real-services.test.mjs —— 同一契约挂载在测试框架的真实 ApprovalService 和 SandboxPolicy 上,而不是测试接缝。正是这个文件证明了门控读取的是测试框架自身的审批事实:一个伪造的 { effectivePolicy: () => 'never' } 会与任何实现一致,包括错误的实现。当测试框架无法解析时跳过(而非失败)。
瀑布流必须被链式串联
ctx.waterfall 通过一个链式 thunk 进行分发——(cbs.shift() ?? inner)——因此一个返回时没有调用 next() 的监听器会为所有在其之后注册的监听器终止链条。这个插件的第一个版本正是这样做的,从而在整个进程范围内静默地抑制了 dsh-session-reference 和 dsh-agent 的 system-prompt/assemble 监听器。它们的贡献从未到达任何请求,也没有任何东西报告这一损失;它是通过用两个监听器探测分发器发现的,而不是通过任何单监听器测试。该监听器现在会 await next() 并收窄结果,这也是正确的顺序——模型必须接收到最终的组装结果。
服务顺序陷阱
ctx.get('sandboxPolicy') 不能在 apply 内部读取一次然后闭包捕获。一个 bundle 补丁被追加到组合中,而 sandbox-policy 位于基础树中更早的位置,并且是异步提供的,因此该行可能在服务存在之前加载。闭包捕获结果会绑定 undefined,然后插件在整个会话期间什么都不收窄——静默地,只打印了一条看起来像缺少依赖而非竞态的警告。
在 inject 中声明 sandboxPolicy 是另一个错误答案:它会把 apply 推迟到服务出现之后,因此在真正缺少该服务的组合上,插件会等待整个会话,没有任何钩子注册,也没有任何诊断信息。守卫改为按请求解析服务,无条件挂载,并在第一个无法服务的请求处报告一次真正缺失的服务。mount.test.mjs 固定了这两半:服务到达之前不受影响,到达之后立即收窄。
guard.test.mjs 中的升级措辞夹具逐字复制自已发布的 dsh-tool-bash 和 dsh-tool-fs 描述。如果 DSH 升级重写了该措辞,措辞测试会大声失败,而不是让这个包静默地停止剥离措辞。
已知限制
它无法修复 schema 的来源。 这个插件在通往模型的途中重写 schema;它不会改变 dsh-tool-bash 或 dsh-tool-fs。那个
底层谓词——从 ctx.shell.sandboxMode 进行广告,同时针对 ctx.sandboxPolicy.resolve() 进行验证——仍然属于测试框架自身,正确的上游修复是根据会话的已解析模式来对这些字段进行门控(并接受当前模式作为无操作)。此插件是针对不得修补 node_modules 的安装的持久性变通方案。
散文剥离是锚定的,而非结构性的。 升级段落通过匹配其前导子句来移除。如果未来的 DSH 重写了该文本,模式修复仍然适用,只会留下散文——模型保留其指令,但丢失了它们所引用的字段。固定的测试使这一点可见。
预执行回退是拒绝而非执行。 通过剥离参数来让调用继续,将需要重新实现工具分发,因为 exec.arguments 在任何插件观察到它之前就被深度冻结了。指明修复方式的拒绝是安全的选择;在 (1) 激活的情况下,它应该永远不会触发。
在此之后注册的监听器仍然可以重新添加这些字段。 该守卫收窄了 next() 返回的组装结果,而该结果携带了链中位于其上游的每个监听器的工作。一个追加工具模式并且恰好在此之后注册的插件,会把无法收窄的字段重新放回去。中间件顺序是绝对的,无法从瀑布内部纠正。在随附的集合中不会出现这种情况:dsh-session-reference 和 dsh-agent 都调用 next(),并且只读取 assembly.variables,不添加任何工具。
关于提示缓存
该守卫会重写请求内容,而这正是可能悄悄破坏提供方前缀缓存的那种变更形态。因此,这是对照提供方实际文档来衡量的,而不是假设的。
提供方的说法
| 提供方 | 规则 |
|---|---|
| OpenAI | 更改 tools 会改变“名称、描述、模式、顺序或工具特定指令”。缓存复用“要求整个渲染后的前缀匹配”。缓存路由哈希取自初始 token,“包括工具定义(如果存在)”。 |
| Anthropic | 层级为 tools → system → messages;“修改工具定义”会使每个层级的整个缓存失效。 |
| DeepSeek | 缓存是自动的且基于磁盘;请求只有完全匹配持久化的缓存前缀单元才会命中。 |
三者都在关键点上一致:工具定义位于前缀的最前面,因此它们必须字节稳定,复用才可能发生。
为什么此插件满足该要求
在一个会话期间,字节是恒定的。 有效模式是一个会话事实,因此 narrowTools 在每次组装时都产生相同的输出。该
模式实际上只被收窄一次,之后每一轮都发送相同的字节。
缓存在第一次请求时写入,此后读取——在稳态下不受影响。
没有引入任何变化的内容。 没有时间戳、计数器、标识符、令牌或重新生成进入输出,因此路由哈希也是稳定的。
名称和顺序都不变。 只有 properties 条目和描述文本被过滤;工具列表保持其顺序,每个工具保持其名称。属性插入顺序从输入中保留,因此重写永远不会重新排列键,也不会在不改变含义的情况下改变字节。
结果从不依赖于请求历史。 渲染给定模式产生的字节是相同的,无论是第一次请求还是第一百次请求。
它使缓存前缀更小,而不是不同。 升级字段被移除而非重写,因此发布的定义是原始定义的严格子集。
测试所固定的内容
test/mount.test.mjs 直接断言该契约:
- 每种模式连续组装 50 次,恰好产生一个不同的工具表面 JSON.stringify;
- 同一模式无论落在不同模式序列中的哪个位置,渲染结果都相同;
- 工具名称和列表顺序在收窄后保持不变;
- 属性键顺序在收窄后保持不变。
会话中途访问变更:诚实的代价
本节较早的一个版本声称,harness 已经通过 sandbox:policy 提示上下文在模式变更时使前缀失效。那是错误的,而这一更正很重要,因为它改变了由谁付出代价。
策略文本是一条追加的消息,而不是前缀的一部分。
dsh-sandbox-policy 注册了一个 sandbox:policy 上下文,其文本是已解析模式的函数,但 dsh-agent-loop 渲染整个上下文快照并将其追加到消息列表:
const context = this.runtimeContext.project(joinContextSections(sections), sections)
// ...
{ kind: "enter", messages: context === void 0 ? claimed : [...claimed, context] }
该快照甚至自我声明:“此快照取代先前的运行时上下文快照。” 追加在对话末尾的内容无法使任何缓存于其之前的内容失效,因此模式变更本身不产生任何代价。
tools 是承载代价的通道。 assembly.tools 同时供给 buildRequest 和 toolsChanged(),后者驱动请求序列重启;headerEquals 通过规范 JSON 比较模式。该守卫的重写是一次工具重写,因此它正是推动缓存前缀的东西。
而且如果没有这个插件,工具块确实不会移动。 所公布的升级枚举派生自一个挂载时捕获——dsh-tool-bash 在 apply 中读取一次 const defaultMode = ctx.shell.sandboxMode,而 ESCALATION_TARGETS 由此而来——而 resolveSandboxPolicy 则按调用解析。这种加载时/执行时的分裂正是本插件存在的根本 bug,
修复,其副作用是发布的 schema 在模式切换前后保持静态。因此,harness 中没有任何东西会使前缀失效,而 guard 的重写是唯一原因。
所以 guard 确实会在模式切换时引入一次缓存写入——一次本来不会发生的写入。精确说明其规模:
| | 会话中途一次模式切换的成本 |
|---|---|
| 仅 harness | 无——快照被追加,工具块是静态的 |
| 使用此 guard | 一次缓存写入,随后读取恢复正常 |
这仍然是正确的取舍,原因明确。 如果不重写,模型会保留一个提供不再有效的升级字段的 schema——而这正是此插件旨在防止的故障,在会话中途被重新引入。每次模式切换一次缓存写入,换来一个始终与会话真实权限匹配的接口。模式切换是用户操作,而非每轮事件,因此其频率从设计上就很低。
在此设计内部无法避免这一点。 不修改已发布的 schema 就无法纠正它,而这些字段位于工具块中。替代方案是恢复而非预防——在调用被拒绝后纠正它,而不是从一开始就不宣传它——这不会产生缓存写入,但每个受影响的轮次会产生一次失败的工具调用。相关的 apex-mochen/dsh-sandbox-arg-guard 采用了那条路线。两者互补,而这正是它们所不同的具体维度。
稳态不受影响,这是日常最重要的特性:在模式不变的情况下,每次请求的工具字节完全相同,因此 toolsChanged() 为 false,没有序列重启,也没有重复成本。test/mount.test.mjs 固定了全部三个事实。
许可证
Apache-2.0