# dsh-sandbox-escalation-fix(已支持 DSH 0.1.2-alpha.1 与 Desktop 2.0.3)
[English](README.md) | 中文
> [!IMPORTANT]
> 这是独立开发的社区插件,不是 DeepSeek 官方发布、维护或背书的插件。它不会修改 DeepSeek Harness 的核心代码。
> [!CAUTION]
> DSH `0.1.0-rc8`、`0.1.1-rc.1`、`0.1.1-rc.2` 和 `0.1.2-alpha.1` 已做部分改善,但仍使用注册表全局升级 Schema 和执行期校验。**建议用户先观察原生行为,仅在实际遇到本文所列的同模式升级、空 justification 或反复重试问题后再安装本插件。**
> *如果该插件帮上你的忙了,还请点颗星让更多人看见~非常感谢 Thanks♪(・ω・)ノ*
**dsh-sandbox-escalation-fix** 是一个零配置兼容插件,直接解决 GPT 等第三方模型在 DSH All Access 下调用 `bash`、`pwsh`、`write`、`edit` 等工具时,因为错误的沙箱升级参数提示而调用失败、反复重试的问题。
如果你遇到过下面这些错误,这个插件就是针对它们的:
```text
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
```
一些次要的说明
> DSH `0.1.1-rc.2` 的官方更新集中在图像处理:DeepSeek 适配器优先使用 Files API 上传图像、复用已上传文件,并根据模型要求自动缩放和转换图像格式。与本插件相关的 Sandbox 升级、Bash、Pwsh、ToolRuntime 和审批实现均与 `0.1.1-rc.1` 相同,因此 rc.2 没有修复本文问题,也不需要调整插件核心逻辑。
DSH 从 `0.1.0-rc8` 到 `0.1.2-alpha.1` 改善了部署组合级广告:没有受限沙箱后端时,Bash、Pwsh、Write、Edit 不再公开升级字段。但它没有实现按 Session 投影 Schema。有沙箱后端时,工具仍会全局注册 `workspace-write` 和 `danger-full-access`;当前 Session 模式只在执行期读取,严格变宽仍在执行期校验,`approval=never` 只增加模型提示而不会删除字段。Native Tool Call 和 PTC Mode 又使用同一注册定义,因此本插件针对的问题仍可能出现
本次兼容基于官方标签提交 `cd5ef8148158c3a752a658978873241fdf8e2bbc` 的源码契约审计。验证时 alpha.1 核心包尚未发布到公共 npm,因此本 Release 使用标签源码契约核对和 alpha.1 工具形状回归测试,不会把尚无法执行的 npm 包级测试描述为已完成
插件 `0.1.1-desktop.2` 包含对 DSH Desktop `2.0.3` 的兼容。Desktop 2.0.3 会把 CommonJS 包清单覆盖解析限制在 Profile 的直接锚点,因此第三方插件自身无法读取宿主的 `@deepseek-ai/dsh-*/package.json`。当所有受检清单都被宿主统一隐藏时,本插件改用现有的严格运行时工具契约校验;部分清单可读、版本混装、清单损坏或工具定义不兼容时仍会拒绝运行
插件也支持 `link:`、工作区软链接和外部插件目录等加载方式。当插件物理目录位于宿主依赖树之外时,兼容门禁可以从宿主当前工作目录读取完整的 DSH 包清单集合。每个候选解析根必须独立提供全部受检包;部分包集、跨解析根拼接、清单损坏和非模块缺失类加载错误仍会拒绝运行。`DSH_HOME` 只存放 Harness 配置和 Profile 数据,不是稳定的 Node.js 依赖根,因此不会被用于依赖解析。
## 目录
- [这是什么?](#这是什么)
- [它能解决什么问题?](#它能解决什么问题)
- [安装前后对比](#安装前后对比)
- [为什么选择它?](#为什么选择它)
- [安装与升级](#安装与升级)
- [卸载](#卸载)
- [手动覆盖安装(Windows)](#手动覆盖安装windows)
- [验证、行为与插件协作](#验证行为与插件协作)
- [故障排查](#故障排查)
- [支持范围](#支持范围)
- [贡献者](#贡献者)
- [开发验证](#开发验证)
## 这是什么?
一句话说明:**这个插件让 DeepSeek Harness 只把“当前会话真正能用”的沙箱升级选项展示给模型。**
在 `danger-full-access + approval=never` 这类 All Access 会话里,DSH 原版仍然会在 `bash`、`pwsh`、`write`、`edit` 工具中向模型展示 `sandbox_permissions` 和 `justification` 两个参数。但此时:
- 当前权限已经是最高级,没有更宽的权限可以升;
- 审批策略又是 `never`,所有审批请求都会被自动拒绝。
于是模型一旦填写这两个参数,调用就会在执行前失败,然后模型可能反复重试,陷入“报错 → 换参数 → 再报错”的循环。
本插件会根据**每个 Session 当前的 Sandbox Mode 和 Approval Policy**,动态调整模型看到的工具参数和说明;同时在执行前做一层最小兜底,把同模式的冗余请求安全地处理掉。
## 它能解决什么问题?
- 模型在 All Access 下仍能看到并填写 `sandbox_permissions` / `justification`,导致工具还没执行就报错。
- 模型在 `workspace-write` 下看到两个升级目标,但其实只有 `danger-full-access` 是真正更宽的选项。
- 模型在 `approval=never` 下仍然被提示“可以升级”,但所有升级都会被拒绝。
- 工具描述和错误结果里继续出现“升级可用”的提示,诱导模型反复重试。
- Native Tool Call 和 PTC Mode SDK(旧称 Code Mode)展示不一致,一边修好了、另一边还在误导模型。
### 问题成因
DSH 工具注册时会公开静态的升级字段,但真正可以请求的升级目标取决于每个 Session 当前的 Sandbox Mode 和 Approval Policy。原始模型可见 Schema 在构造请求前没有根据实时会话状态重新投影,因此即使当前权限已经最高或审批已被禁止,模型仍可能收到无法成功执行的升级参数。工具随后会在执行前拒绝这些请求,模型便可能进入反复修改参数并重试的循环。
## 安装前后对比
未安装插件时,受影响的 All Access Session 可能在实际操作开始前反复失败。模型会在空白 `justification`、同模式 `danger-full-access` 请求,甚至会被 DSH 正确拒绝的降级请求之间循环。
### 安装前:参数校验与升级错误反复出现


### 安装后:工具可以连续完成工作流
安装插件后,同一模型可以连续执行 Edit、Read、Pwsh、格式化、测试、Lint 和 Type Check,不再进入无效升级循环。

## 为什么选择它?
### 它解决的是根因,而不是只让报错消失
很多修复方案只在模型已经拿到错误 Schema 之后,于执行前把参数删掉。这样做调用虽然不报错了,但模型仍然会继续看到、继续发送这些无效参数。
本插件会按每个 Session 的实时权限状态,动态投影模型真正可见的工具参数:
| 当前模式 | 审批策略 | 模型看到的结果 |
|---|---|---|
| `read-only` | `ask` | `workspace-write`、`danger-full-access` |
| `workspace-write` | `ask` | 仅 `danger-full-access` |
| `danger-full-access` | `ask` | 不展示升级参数 |
| 任意模式 | `never` | 不展示升级参数 |
模型看不到当前不可能成功的参数,自然就不会去使用它。
### 它不会只修 Native Tool Call,而漏掉 PTC Mode
插件的参数投影发生在 Agent 精确作用域的工具定义上。原生工具调用和 PTC Mode SDK 读取的是同一份投影结果:
- 原生工具 Schema 里不会出现无效升级字段;
- PTC Mode 生成的 TypeScript/Python SDK 里同样不会出现;
- 两种模式的行为始终一致。
不会出现“原生调用已经修好,模型走 `run_code` 仍然看到并误用旧参数”的半成品状态。
### 它不会静默吞掉降级请求
执行兜底只做一件非常克制的事:当且仅当 `requestedMode === effectiveMode` 时,把冗余参数视为幂等请求并移除,然后按普通调用执行。
对于降级请求或非法值,插件会原样保留,交给 DSH 原有的安全逻辑处理:
- `danger-full-access` 下请求 `danger-full-access` → 忽略,正常执行;
- `danger-full-access` 下请求 `workspace-write` → 不会被擅自当成 Full access 执行;
- `read-only` 下请求更宽模式 → 原样进入审批流程。
它不会为了“少报一个错”,就把调用者显式要求的更窄权限静默替换成更宽权限。
### 它不会自动填充一个虚假的审批理由
对于真正需要升级的请求,插件不会自动编造或填充占位 justification。缺失、空白或非法的理由仍然由 DSH 原有校验逻辑处理,保证审批流程看到的信息真实、可审计。
### 它不会一边说“不要升级”,一边又在结果里提示“升级可用”
当会话没有合法升级目标时,插件除了隐藏参数,还会同步处理模型看到的自然语言:
- 工具描述中的升级引导段落会被裁剪;
- Shell、文件工具和 `job_output` 结果中已不成立的 `escalation available` 提示会被清理。
模型看到的参数、描述和执行结果不会再互相矛盾。
### 它不会修改或绕过 DSH 的安全核心
`approveEscalation()` 的严格变宽检查、审批流程、一次性授权语义都保持原样。插件只做模型可见面的投影和调用前的最小兼容处理:
- 不删除严格变宽检查;
- 不在 `approval=never` 时自动批准升级;
- 不授予任何额外权限;
- 不修改 DSH 安装目录或核心包。
### 它不会把同一进程里的不同 Session 一刀切
插件按 Agent/Session 独立包装,不修改全局工具注册表。因此同一个进程中:
```text
Session A = read-only + ask → 看到两个升级目标
Session B = danger-full-access + never → 看不到升级字段
```
各自互不影响。Session 中途切换权限后,下一次模型请求的工具 Schema 也会立即按新状态重新计算。
### 它不会在 Agent 销毁或工具替换后留下残留包装
插件完整监听 Agent 创建、销毁、Preset 切换、动态限制和工具变更事件。动态 Preset 调用 `agent.ctx.tools.restrict()` 时,对应的 Agent Exact Scope 包装器会与被限制的父工具同步退出;限制解除后自动恢复投影包装。Agent 创建时不可见的目标工具以后重新出现,也会自动接入。每个 Agent 独立协调,Agent 销毁或插件卸载后恢复原始定义。
### 它不会只支持单一 DSH 版本
插件同时支持 DSH `0.1.0-rc.5`、`0.1.0-rc.6`、`0.1.0-rc.7`、`0.1.0-rc.8`、`0.1.1-rc.1`、`0.1.1-rc.2` 和 `0.1.2-alpha.1`,并在加载时校验 DSH 各包版本是否一致且受支持。遇到不兼容的工具定义会主动拒绝安装,而不是在运行中产生难以排查的诡异行为。
### 它不会给你增加配置负担
零配置,安装到实际使用的 Profile 后即可生效。测试覆盖 36 项,包含当前公共 npm 可获得的 rc.2 真实包运行时集成验证,以及 alpha.1 标签源码契约和 PTC Mode 元数据回归验证,覆盖 Schema 投影、动态限制、多 Agent 隔离、Delegate 与协作包装器替换、内部超时预算透传、失败提示清理和插件卸载等关键路径。
### 与“仅执行期止血”方案的区别
| 能力 | 本插件 | 仅执行期参数正规化 |
|---|---|---|
| 从 Native Tool Schema 隐藏不可执行升级字段 | 支持,按 Session 动态投影 | 不支持 |
| 从 PTC Mode SDK 隐藏相同字段 | 支持,与 Native 读取同一 Agent Exact Scope 定义 | 不支持 |
| 只删除精确同模式的冗余请求 | 支持 | 取决于实现 |
| 保留显式降级和非法请求给 DSH 校验 | 支持 | 不保证 |
| 保留缺失或空白 `justification` 给 DSH 校验 | 支持 | 不保证 |
| 清理描述与结果中的无效升级建议 | 覆盖 Shell、FS、PTC Mode、`job_output` | 不支持 |
| 响应 Agent、Preset 和工具生命周期变化 | 支持 | 取决于实现 |
## 安装与升级
插件为零配置修复。推荐下载 Release ZIP,通过脚本安装到实际使用的 Profile;安装后按原方式启动 DSH:
```sh
dsh --profile
```
无需修改模型配置、Sandbox Mode、Approval Policy 或 Agent Preset。插件会按每个 Session 的当前权限状态动态决定模型可见参数。
### Release ZIP 一键安装
`0.1.2-alpha1.1` Release 保留 DSH `0.1.2-alpha.1`、Desktop 2.0.3 兼容和 PR #5 的软链接/外部插件目录解析增强,并加入 PR #8 的 Git 安装修复。Git 安装会直接使用仓库内预构建的 `lib`,不再要求配置 `allowBuilds`。包括 `v0.1.2-alpha1` 在内的旧 Release 均保留。新版 Release ZIP 解压后包含以下四个文件:
```text
dsh-sandbox-escalation-fix-0.1.2-alpha1.1.tgz
install-release.ps1
uninstall-release.ps1
RELEASE-USAGE.zh.md
```
安装或升级前先完全关闭 DSH。执行脚本前,请确认系统已将 `dsh` 命令加入 PATH,且当前 DSH 使用的是 rc5、rc6、rc7、rc8、`0.1.1-rc.1`、`0.1.1-rc.2` 或 `0.1.2-alpha.1`。建议先实际复现同类错误,再决定是否安装。
#### 安装到默认 Web Profile
在 Release 目录打开 PowerShell,执行:
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File ".\install-release.ps1"
```
脚本会定位同目录中唯一的 `.tgz` 文件,然后执行:
```powershell
dsh plugin --profile web add
```
DSH CLI 会将插件安装到 `web` Profile,并在 pnpm 成功后自动把插件的 `cordis.patch.yml` 加入 Profile Bundle 层。安装完成后重启 DSH。
#### 安装到其他 Profile
例如安装到 `headless`:
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File ".\install-release.ps1" -Profile headless
```
#### 发布者构建 Release 目录
在源码根目录执行:
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File ".\build-release.ps1"
```
该脚本会先构建 `lib`,再执行 `npm pack` 生成 `.tgz`,最后在 `release` 目录生成 `dsh-sandbox-escalation-fix-0.1.2-alpha1.1-release.zip`。ZIP 内含 tarball、两个一键脚本和简明中文使用说明;上传 GitHub Release 时只需上传该 ZIP。
### 升级已有安装
升级前必须关闭 DSH。插件包名、Bundle ID 和 Profile Patch 配置行均未改变,已经安装旧版的用户不需要再次修改 `cordis.patch.yml`。
#### 通过 GitHub Commit 安装的用户
将原安装命令中的 Commit SHA 换成新的、已经审核过的 SHA,再执行同一条命令:
```sh
dsh plugin --profile add github:/dsh-sandbox-escalation-fix#
```
该命令会更新 Profile 依赖。仓库内已提交预构建的 `lib`,本包不再声明任何安装期构建脚本,pnpm 不会要求 `allowBuilds` 白名单。安装完成后检查 `--dump-config`,然后重新启动 DSH。
#### 手动安装到 Web Profile 的用户
准备包含新版 `lib` 的仓库或发布包,在插件目录中打开 Windows PowerShell,然后执行:
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File ".\deploy-web-profile.ps1"
```
脚本优先使用 `DSH_HOME`;未设置时使用 `%USERPROFILE%\.dsh`。它只覆盖 8 个发布用 `lib` 文件并逐一比较 SHA-256;只有部署目录与新版构建完全一致时,最后一行才会显示:
```text
Deployment verified.
```
脚本不会修改 Profile Patch,也不会复制 `node_modules`。验证成功后重新启动 DSH。
### 通过命令行安装
建议从可信仓库锁定 Commit SHA 安装到指定 Profile:
```sh
dsh plugin --profile add github:/dsh-sandbox-escalation-fix#
```
也可以从本地目录安装:
```sh
dsh plugin --profile add D:/deepseek-harness/plugins/dsh-sandbox-escalation-fix
```
本包已移除 `prepare` 等安装期构建脚本:Git 安装直接使用仓库内提交的预构建 `lib`,pnpm 不会要求 `allowBuilds` 白名单。旧版本安装时曾需要允许构建;如果 Profile 的 `pnpm-workspace.yaml` 里还残留 `dsh-sandbox-escalation-fix@https://codeload.github.com/...` 条目,升级到新版后可以删除。
重新执行安装命令后检查最终组合:
```sh
dsh --profile --dump-config
```
输出中应包含 `dsh-sandbox-escalation-fix` 层和 `sandbox-escalation-fix` 行。
## 卸载
使用 Release ZIP 时,可在解压目录执行:
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File ".\uninstall-release.ps1"
```
其他 Profile 通过 `-Profile` 指定,例如:
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File ".\uninstall-release.ps1" -Profile headless
```
等效的 DSH CLI 命令为:
```sh
dsh plugin --profile remove dsh-sandbox-escalation-fix
```
卸载会移除插件创建的包装 Host、包装层和结果过滤器。完成后重启 DSH,并可通过 `dsh --profile --dump-config` 确认输出中不再出现插件层。
## 手动覆盖安装(Windows)
这是 Windows 下不使用插件安装命令的推荐方式。它只修改指定 Profile,不修改 DSH 安装目录或核心包。
需要更短的逐文件教程时,可查看 [奶龙也能看懂的食用说明.txt](奶龙也能看懂的食用说明.txt);其英文对应版为 [Tutorials that even Peppa Pig can understand](Tutorials%20that%20even%20Peppa%20Pig%20can%20understand)。
### 开始前先确认
- 本教程中的“Profile 根目录”不是 `.dsh`,而是当前实际启动的 Profile 目录。
- 使用 Web Profile 时,Profile 根目录就是 `%USERPROFILE%\.dsh\profiles\web`。
- 要修改的是 `profiles\web\cordis.patch.yml`;`.dsh` 根目录下没有该文件并不异常。
- 可以直接复制整个 `dsh-sandbox-escalation-fix` 文件夹,但复制后必须删除插件目录内部的 `node_modules`。
- Profile Patch 使用 YAML 块列表;不要给配置加引号,也不要自行添加 `[` 或 `]`。
### 1. 关闭 DSH
退出正在运行的 Web、桌面端或 Headless 进程,避免旧插件文件仍被 Node.js 占用。
### 2. 打开 Profile 目录
默认 Harness Home 是:
```text
C:\Users\<你的用户名>\.dsh
```
目标 Profile 目录是:
```text
C:\Users\<你的用户名>\.dsh\profiles\
```
例如 Web Profile 通常是:
```text
C:\Users\<你的用户名>\.dsh\profiles\web
```
可直接在 Windows 资源管理器地址栏输入:
```text
%USERPROFILE%\.dsh\profiles\web
```
如果配置过 `DSH_HOME`,请把上面的 `C:\Users\<你的用户名>\.dsh` 换成实际目录。若 `profiles\web` 尚不存在,先正常启动一次 DSH Web,让它初始化 Profile。
注意:这里说的是 Profile 根目录 `%USERPROFILE%\.dsh\profiles\web`,**不是** `%USERPROFILE%\.dsh`。看到 `.dsh` 根目录没有 `cordis.patch.yml` 是正常的,不需要创建;继续进入 `profiles\web` 修改其中已有的文件即可。
### 3. 覆盖插件目录
在目标 Profile 中创建以下目录:
```text
\node_modules\dsh-sandbox-escalation-fix
```
Web Profile 的默认目标目录是:
```text
%USERPROFILE%\.dsh\profiles\web\node_modules\dsh-sandbox-escalation-fix
```
如果该目录已经存在,先将整个目录改名为 `dsh-sandbox-escalation-fix.backup`。
然后从本项目目录复制以下内容到新的 `dsh-sandbox-escalation-fix` 目录:
```text
package.json
cordis.patch.yml
README.md
lib\
```
也可以直接把整个 `dsh-sandbox-escalation-fix` 文件夹复制到 Profile 的 `node_modules`,但复制完成后必须删除:
```text
\node_modules\dsh-sandbox-escalation-fix\node_modules
```
嵌套的 `node_modules` 可能携带另一套 Cordis 和 DSH 包,导致 Scope、Service 或错误类型来自不同实例。`src`、`tests`、`package-lock.json` 和 TypeScript 配置留在目标目录通常不会影响运行,只是没有必要。
复制完成后的结构应为:
```text
\
├── cordis.patch.yml
├── package.json
└── node_modules\
└── dsh-sandbox-escalation-fix\
├── package.json
├── cordis.patch.yml
├── README.md
└── lib\
├── index.mjs
├── index.d.mts
├── wrapper-protocol.mjs
└── wrapper-protocol.d.mts
```
注意:
- 不要复制本项目的 `node_modules`。
- 不需要复制 `src`、`tests`、`tsconfig.json` 或构建工具配置。
- 手动安装包必须已经包含 `lib\index.mjs`;只有源码而没有 `lib` 的 ZIP 不能直接使用此方法。
- 不要把插件自己的 `cordis.patch.yml` 直接覆盖到 Profile 根目录;Profile 根目录中的同名文件可能包含其他用户配置,必须按下一步手动合并。
- 确认没有多套一层同名目录。
正确入口是:
```text
\node_modules\dsh-sandbox-escalation-fix\package.json
\node_modules\dsh-sandbox-escalation-fix\lib\index.mjs
```
以下结构是错误的:
```text
\node_modules\dsh-sandbox-escalation-fix\dsh-sandbox-escalation-fix\package.json
```
更新已经安装的版本时,请直接按照[安装与升级](#安装与升级)操作,不要重复添加 Profile Patch。
### 4. 修改 Profile Patch
用记事本或其他纯文本编辑器打开:
```text
\cordis.patch.yml
```
如果文件内容只有 `[]`,将其完整替换为:
```yaml
- insert:
- id: sandbox-escalation-fix
name: dsh-sandbox-escalation-fix
```
例如,初始文件为:
```yaml
# Your patch layer for this dsh profile, applied after every bundle layer:
# a top-level YAML array of loader patch entries (id-targeted config
# overrides, disables, and insert lists; `!!js` expressions allowed).
[]
```
修改后的完整文件应为:
```yaml
# Your patch layer for this dsh profile, applied after every bundle layer:
# a top-level YAML array of loader patch entries (id-targeted config
# overrides, disables, and insert lists; `!!js` expressions allowed).
- insert:
- id: sandbox-escalation-fix
name: dsh-sandbox-escalation-fix
```
顶部注释可以保留,只替换最后一行 `[]`。
以下两种写法都**不要**使用:
```yaml
[- insert:
- id: sandbox-escalation-fix
name: dsh-sandbox-escalation-fix
```
```yaml
- insert:
- id: sandbox-escalation-fix
name: dsh-sandbox-escalation-fix]
```
正确写法不带方括号、不带引号,且 `- insert:` 必须顶格。请使用空格缩进,不要使用 Tab。
如果文件已有其他配置,不要覆盖原内容。在文件末尾追加同一个顶层 Patch:
```yaml
- insert:
- id: sandbox-escalation-fix
name: dsh-sandbox-escalation-fix
```
同一个文件中只能保留一个 `id: sandbox-escalation-fix`,不要重复追加。此手动方式不要求修改 Profile 的 `package.json`,因为插件由 Profile Patch 直接加载。
### 5. 重启并验证
按原来的方式启动该 Profile,新建 Session,然后在 All Access 下要求模型执行简单 Shell 命令或写入文件。
修复生效时,OAI 系列模型不再因为重复发送 `danger-full-access` 升级参数而导致工具调用失败。若仍看到旧行为,请确认修改的是当前实际启动的 Profile,并彻底退出 DSH 后重新启动。
### 安装后检查表
- [ ] `profiles\web\cordis.patch.yml` 中只有一个 `id: sandbox-escalation-fix`。
- [ ] Patch 中没有 `[`、`]` 或包住整段配置的引号。
- [ ] 插件的 `package.json` 直接位于 `node_modules\dsh-sandbox-escalation-fix` 下。
- [ ] 插件目录包含 `lib\index.mjs`。
- [ ] 插件目录内部不存在第二个 `node_modules`。
- [ ] DSH 已完全退出并重启。
- [ ] 测试使用的是重启后新建的 Session。
### 手动更新
关闭 DSH,将新的 `package.json`、`cordis.patch.yml`、`README.md` 和整个 `lib` 目录覆盖到 Profile 的插件目录即可。Profile Patch 已存在时不需要再次修改。
### 手动回退
1. 关闭 DSH。
2. 从 `\cordis.patch.yml` 删除 `sandbox-escalation-fix` 的整个 `- insert:` 块。
3. 删除 `\node_modules\dsh-sandbox-escalation-fix`。
4. 如果保留了 `.backup`,将其改回原名;否则直接重启 DSH。
## 验证、行为与插件协作
### 验证修复
Web 正常启动只能证明 Profile 组合成功、插件加载时没有导致进程退出;动态限制行为仍需按本节后续步骤验证。使用 DSH 源码仓库时,可在仓库根目录运行:
```powershell
node --import tsx/esm apps/cli/src/bin.ts web
```
请先确保受支持的 Node.js 已加入 PATH。看到以下输出后再继续测试:
```text
dsh web: http://127.0.0.1:3080
```
1. 使用安装插件的 Profile 启动 DSH。
2. 选择原先在 All Access 下受影响的 OAI 系列模型。
3. 将权限设置为 All Access,对应 `danger-full-access` 与 Approval Policy `never`。
4. 新建 Session,让 Agent Scope 在插件已加载的状态下创建。
5. 要求模型执行一条简单 Shell 命令或写入工作区文件。
6. 如果 Preset 使用 `agent.ctx.tools.restrict()`,进入限制状态并确认对应工具消失;解除限制后确认工具无需重建 Agent 即可恢复。
修复生效时:
- 模型可见的 `bash`、`pwsh`、`write`、`edit` Schema 不再包含 `sandbox_permissions` 和 `justification`。
- 模型即使发送与当前模式相同的冗余升级参数,工具也会删除这对参数后正常委托。
- Sandbox 拒绝结果不会继续提示一个当前策略无法执行的升级动作。
修改插件安装状态、Profile 或 Preset 后,建议新建 Session 验证,避免把旧 Agent Scope 的行为误认为当前配置。
### 验证证据
自动测试直接使用真实 DSH `SessionStore`、`ToolRuntime`、`AgentRegistry`、`SandboxPolicyService`、`ApprovalService` 和 `SystemPrompt` 包,而不是只测试隔离 Mock。36 项测试覆盖:
- 权限矩阵;
- 精确同模式正规化;
- Native Schema 投影;
- PTC Mode SDK 生成;
- 动态 Preset 限制、解除和初始受限后自动发现;
- 多 Agent 限制隔离;
- Delegate 替换;
- 普通包装器与协作协议包装器动态切换;
- 运行期不兼容定义的单工具隔离与恢复;
- 包装协作与冲突拒绝;
- Shell / FS / `job_output` 提示过滤;
- 版本检查;
- 插件卸载失效。
其中 alpha.1 核心包尚未发布到公共 npm,alpha.1 兼容部分采用官方标签源码契约审计和工具形状回归测试;当前公共 npm 可获得的 rc.2 包继续用于运行时集成验证。自动测试不能完全替代真实模型 Provider 的 E2E 验证。
### 插件行为一览
| 场景 | 插件行为 |
|---|---|
| `danger-full-access` 或 Approval Policy 为 `never` | 不向模型广告 `sandbox_permissions` 与 `justification` |
| `workspace-write` 且允许审批 | 只广告 `danger-full-access` |
| `read-only` 且允许审批 | 广告 `workspace-write` 与 `danger-full-access` |
| 模型发送与当前模式完全相同的冗余升级参数 | 包装器删除这一对参数后委托原工具 |
| 降级、未知目标、缺少配对参数和真实升级请求 | 仍交给原工具严格验证 |
| 无合法升级目标 | 删除 Shell 描述尾部的升级说明,并清理与 Sandbox Denial Marker 同时出现的无效提示 |
| 动态 Preset 限制目标工具 | 对应 Exact Scope 包装器在同一次同步变更中退出 |
| 限制解除或 Provider 恢复 | 自动重新建立投影包装,无需重建 Agent |
| 运行期替换为不兼容定义 | 只让对应 Agent 的对应工具暂时不包装,记录诊断并等待兼容定义恢复 |
### 与其他包装插件协作
插件会占用每个 Agent Exact Scope 中的 `bash`、`pwsh`、`write`、`edit` 名称。
另一个包装插件只有在同名工具暴露 `Symbol.for('dsh.tool-wrapper.v1')` 协议时才能与本插件链式协作。包装层按 `priority`、`owner` 稳定排序。如果同名工具没有实现协作协议,Agent 注册会明确失败,避免静默改变包装顺序或产生错误语义;此时必须由用户选择保留其中一个插件。
协议类型可从以下入口导入:
```ts
import {
TOOL_WRAPPER_PROTOCOL,
type WrapperLayer,
type ToolWrapperProtocolV1,
} from 'dsh-sandbox-escalation-fix/wrapper-protocol'
```
## 故障排查
| 问题 | 处理方式 |
|---|---|
| 安装后没有生效 | 确认安装和启动使用同一个 `--profile`,并通过 `--dump-config` 检查插件层 |
| `.dsh` 根目录没有 `cordis.patch.yml` | 这是正常的;Web Profile 应修改 `.dsh\profiles\web\cordis.patch.yml` |
| 不知道 `[]` 怎么改 | 保留注释,只把独占一行的 `[]` 替换为不带方括号的 `- insert:` YAML 块 |
| 复制了整个开发文件夹 | 可以保留,但必须删除目标插件目录内部的 `node_modules`,并确认没有多套一层同名目录 |
| YAML 启动报错 | 检查 `- insert:` 是否顶格、是否误加了方括号或引号,以及缩进是否只使用空格 |
| Git 安装被 `allowBuilds` 拦截 | 说明装到的是仍带 `prepare` 脚本的旧提交;固定到已移除 `prepare`、随仓库提交预构建 `lib` 的新提交 SHA 后重新安装 |
| 启动时报版本错误 | 不要混装 rc.5、rc.6、rc.7、rc.8、0.1.1-rc.1、0.1.1-rc.2 与 0.1.2-alpha.1 包;让 Profile 中关键 `@deepseek-ai/dsh-*` 包保持同一版本 |
| Agent 注册时报同名工具冲突 | 另一个插件已在 Agent Exact Scope 注册 `bash`、`pwsh`、`write` 或 `edit`,且未实现协作协议;只能卸载其中一个 |
| 动态 Preset 隐藏了部分工具 | 这是正常行为;插件会镜像 `tools.restrict()`,限制解除后自动恢复包装 |
| 日志出现动态协调警告 | 检查警告中目标工具的替换定义;其他工具和 Agent 会继续工作,兼容定义出现后自动恢复 |
| 升级字段仍然出现 | 确认查看的是安装后新建 Session 的 Schema,并确认没有后加载的插件替换同名工具 |
## 支持范围
- Node.js `^22.19.0` 或 `>=24.0.0`
- `@deepseek-ai/dsh-*` `0.1.0-rc.5`、`0.1.0-rc.6`、`0.1.0-rc.7`、`0.1.0-rc.8`、`0.1.1-rc.1`、`0.1.1-rc.2`、`0.1.2-alpha.1`
- `@deepseek-ai/cordis` `4.0.1`
插件针对这些版本的公开 Scope、ToolRuntime、Sandbox Policy 与 Approval Service 契约构建。Agent 初次创建时,已可见的目标工具定义或同 Scope 包装协议不兼容会严格拒绝该 Agent 注册。
启动时会读取关键 `@deepseek-ai/dsh-*` 包的实际版本;rc.5/rc.6/rc.7/rc.8/0.1.1-rc.1/0.1.1-rc.2/0.1.2-alpha.1 混装或未知版本会拒绝启动。目标工具同时省略两个升级字段时视为已经安全。运行期的 Preset 限制或 Provider 稳定删除会让包装器进入休眠;运行期替换为字段残缺或输出定义不兼容的工具时,只隔离对应 Agent 的对应工具并记录警告,不会终止 Host 进程,后续兼容定义出现时自动恢复。
## 贡献者
- [sprainJinyu](https://github.com/sprainJinyu) / 张金雨:在 PR #5 中提出了软链接和外部插件目录的包清单解析回退方案。
- [tappat225](https://github.com/tappat225) / tappat:在 PR #8 中移除了 Git 依赖不必要的 `prepare` 步骤,使插件无需配置 `allowBuilds` 即可安装。
## 开发验证
```sh
npm install
npm test
npm run build
```