mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-08-29 04:26:38 +00:00
171 lines
12 KiB
Markdown
171 lines
12 KiB
Markdown
---
|
||
description: "共享远程沙箱内的 shell 命令与终端:agent 可以在那里运行什么、输出如何处理,以及可以期待什么——面向 E2B 家族的部署方与维护者。"
|
||
kind: "package-reference"
|
||
---
|
||
|
||
# @deepseek-ai/dsh-subprocess-e2b
|
||
|
||
[English](README.md) | 中文
|
||
|
||
## 概述
|
||
|
||
`dsh-subprocess-e2b` 让 agent(智能体)的 shell 命令与终端在远程沙箱内运行:agent 可以执行 Bash、打开交互式终端并读取其输出,体验与本地执行完全一致,而宿主机器上什么都不会运行。现有的命令、终端与语言服务器功能无需任何改动即可继续工作——不需要 E2B 专用工具。密钥与宿主环境变量绝不会泄漏进沙箱:只有 agent 显式请求的环境条目才会传入。请与 `dsh-e2b`、`dsh-fs-e2b` 一起使用,让命令、终端与文件共享同一个远程世界。主要代价是远程延迟——每条命令都要经过一段短暂异步初始化,而不是立即启动。
|
||
|
||
## 目录
|
||
|
||
- [使用本包](#use-this-package)
|
||
- [理解实现](#understand-the-implementation)
|
||
- [进一步探索](#further-exploration)
|
||
- [模型体验](#model-experience)
|
||
- [已知限制与延期工作](#known-limitations-and-deferred-work)
|
||
- [开发备注](#dev-note)
|
||
|
||
-----
|
||
|
||
<a id="use-this-package"></a>
|
||
## 使用本包
|
||
|
||
当 agent 的 shell 命令与终端应在远程沙箱内而非你的机器上运行时,使用本包。它是 E2B 家族的命令半边:命令、终端与文件共享同一个远程世界。
|
||
|
||
### 何时选择
|
||
|
||
当组合已经使用 E2B 沙箱且希望命令与终端在其中运行时,选择本包。宿主执行请选择本地子进程包。需要立即获得进程 ID 的工具——例如 ACP(Agent Client Protocol)子进程后端——无法使用本包。
|
||
|
||
### 配置
|
||
|
||
唯一设置是包检查运行中命令状态的频率;默认值适合大多数部署,调大它可以减少远程请求,代价是退出检测略慢。
|
||
|
||
| 字段 | 默认值 | 含义 |
|
||
|---|---|---|
|
||
| `pollMs` | `20` | 包检查运行中命令状态的频率(毫秒) |
|
||
|
||
生成的[配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-subprocess-e2b)是每个受支持字段及其 JSDoc 的穷尽式真源。
|
||
|
||
### 运行命令
|
||
|
||
agent 可以在沙箱中按指定的工作目录与环境运行命令,选择输出的交付方式(实时流式、在大小上限内捕获,或路由到应用自身的输出),并在命令卡住时停止它——停止会先礼貌地请求命令退出,短暂宽限期后再强制终止,因此卡住的命令不会残留。非常大的输出可以保存到沙箱中的文件里,供 agent 稍后读取。命令的退出码会正常报告;如果命令运行期间沙箱消失,该命令会被视为已结束而不是报错。
|
||
|
||
### 使用终端
|
||
|
||
agent 可以在沙箱中打开交互式终端、发送输入、读取输出,并向其中运行的程序发送信号——提示符、交互式工具与全屏程序的行为与本地完全一致。scrollback 与就绪检测等终端功能由终端工具提供,无需改动即可工作。
|
||
|
||
### 保持环境干净
|
||
|
||
命令在干净、沙箱原生的环境中运行:宿主变量与形似凭据的值不会被隐式传入,只有 agent 显式请求的条目才会被设置。这使密钥不会进入沙箱。
|
||
|
||
### 如果沙箱消失
|
||
|
||
沙箱是短暂的:如果命令或终端运行期间沙箱被删除——无论是到期、关闭还是被别处移除——受影响的命令会被视为干净地结束。请不要依赖能在沙箱中存续的工作。
|
||
|
||
默认沙箱镜像自带命令工作所需的运行时与工具:`node`、`bash`、`setsid`、`ps`、`awk`、`tr`、`env`、`base64`、`chmod`、`tee`、`head`、`rm`、`kill`、`id` 与 `getent`。
|
||
|
||
-----
|
||
|
||
<a id="understand-the-implementation"></a>
|
||
## 理解实现
|
||
|
||
<details>
|
||
<summary>实现细节——点击展开</summary>
|
||
|
||
本节解释提供方背后的设计决策,并指出实现它们的代码位置;可观察行为已在[使用本包](#use-this-package)中完整说明。
|
||
|
||
### 设计理念
|
||
|
||
- **延后的远程身份。** 同步 seam 从不阻塞在网络请求上:句柄异步发布真实进程组 ID,包装层的私有文件是 pid、退出码与 spill 有效性的权威来源。
|
||
- **单一终止阶梯。** 终止、回滚与资源释放共享同一条进程组信号路径——先 `SIGTERM`,再 `SIGKILL` 加 SDK kill 回退——并把已证明的完全停稳视为最终状态。
|
||
- **环境必须显式。** 宿主内容与形似凭据的内容都不会隐式进入沙箱;每个环境值都会被清理,每个 `spec.env` 条目都是显式选择。
|
||
|
||
### 源码地图
|
||
|
||
| 文件 | 职责 |
|
||
|---|---|
|
||
| [`src/index.ts`](src/index.ts) | 插件入口:`E2BSubprocessRuntime`、`Config`、spawn 与 spawnTerminal、资源释放 |
|
||
| [`src/process.ts`](src/process.ts) | `E2BSubprocessHandle`:远程包装层、发布、终止、输出投影 |
|
||
| [`src/terminal.ts`](src/terminal.ts) | `E2BTerminalHandle`:PTY 分配、会话拆除 |
|
||
| [`src/environment.ts`](src/environment.ts) | 远程环境探测、清理、序列化 |
|
||
| [`src/output.ts`](src/output.ts) | base64 解码器与有界输出读取器 |
|
||
| [`src/remote.ts`](src/remote.ts) | 共享控制 shell 辅助:选项构造、轮询 tick、进程组信号 |
|
||
| [`src/invariant.ts`](src/invariant.ts) | 不变式伴生插件(无运行时不变式;存活远程句柄是私有的拆除所有权) |
|
||
|
||
### 远程包装层
|
||
|
||
引导脚本会从沙箱 PATH 解析自身所需的工具,拒绝任何缺失或不可执行的路径,通过 `env -i` 与 `setsid --wait` 执行 exec,把进程组 ID 与退出码发布到 `ctx.e2b.runtimeRoot/processes` 下的私有文件,并把 stdout 与 stderr 重定向到带保留完成帧的 base64 编码器;`tee` 与 `head -c` 约束可选 spill 文件的大小。
|
||
|
||
### 进程身份与发布
|
||
|
||
同步 seam 会立即返回句柄,同时命令异步启动;`pid` 在包装层发布进程组 ID 且适配器验证通过之前保持 `-1`,stdin 与常规观察都等待该发布。启动信号会在分配前中止环境与私有状态准备;分配开始后,取消会等待可清理的临时 SDK 句柄。
|
||
|
||
### 环境边界
|
||
|
||
一次受信任的控制 shell 探测会从 passwd 条目解析沙箱用户的登录主目录,以 base64 ASCII 传输沙箱环境,再进行一次严格 UTF-8 解码;随后包装层移除环境中的 `DSH_*` 与形似凭据的名称(`*KEY*`、`*SECRET*`、`*TOKEN*`),并把每个有效的 `spec.env` 条目恢复为调用方显式选择。空名称、`=` 与违反 NUL 分帧规则的条目会在启动前被拒绝;在用户 profile 脚本运行前,此后的命令与 PTY 登录 shell 会获得位于根目录下、全新随机生成的 `HOME`,并为每个被清理的环境变量名设置空值覆盖。私有环境文件在使用后会被删除。
|
||
|
||
### 输出处理
|
||
|
||
远程包装层先把原始字节分流到可选的有界 spill 文件,再把每个实时分片编码为换行分隔的 base64 ASCII 帧;宿主会跨任意 SDK 回调边界增量恢复字节。pipe 模式把字节写入宿主 Node 流,inherit 模式写入 harness 进程流,collect 模式保留有界的宿主尾部并支持偏移读取。对于 collect 或 inherit 输出,超过 `graceMs` 后适配器会断开未完成的 SDK 流并扣留其不完整的 spill;原始 pipe 自然完成时则会等待无损传输并保留背压。批量与流式 stdin 都使用 SDK 句柄。
|
||
|
||
### 终止阶梯
|
||
|
||
终止与回滚共享同一条容错信号路径(`signalRemoteGroups`),在宽限期满时从 `SIGTERM` 升级到 `SIGKILL`,以 SDK kill 作为回退,并在报告成功前用有界进程表探测证明完全停稳;仅含僵尸进程的进程组视为空,`SandboxNotFoundError` 视为完全停稳。
|
||
|
||
</details>
|
||
|
||
-----
|
||
|
||
<a id="further-exploration"></a>
|
||
## 进一步探索
|
||
|
||
当包级约定不够用时阅读以下页面。它们从家族组合逐步进入子进程 seam 表面,以及渲染它的消费方。
|
||
|
||
- [E2B 提供方家族地图](../README.zh.md)——沙箱所有者与三包组合。
|
||
- [子进程子系统](../../../docs/subsystems/subprocess.zh.md)——子进程 seam 约定与生成的 Cordis 表面。
|
||
- [子进程 seam 包](../../subprocess/subprocess/README.zh.md)——本提供方实现的抽象约定。
|
||
- [Bash 执行器](../../shell/bash-local/README.zh.md)——向模型渲染所启动命令的消费方。
|
||
- [PTY 终端后端](../../terminal/terminal-bash/README.zh.md)——渲染终端会话的消费方。
|
||
- [生成配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-subprocess-e2b)——每个受支持配置字段及其源声明。
|
||
|
||
-----
|
||
|
||
<a id="model-experience"></a>
|
||
## 模型体验
|
||
|
||
通过消费方 seam 间接影响模型,例如 bash 执行器家族;它们渲染远程输出、退出事实、后台增量与 spill 路径。
|
||
|
||
#### KV Cache 影响
|
||
|
||
不会直接失效:请求前缀变更由消费方 seam 负责;本后端的传输永远不会进入请求。
|
||
|
||
## 已知限制与延期工作
|
||
|
||
<a id="known-limitations-and-deferred-work"></a>
|
||
|
||
|
||
这些限制说明本提供方何时不合适,或何时需要特别的运维注意。它们是当前包约束,不是任务积压。
|
||
|
||
- **SDK 仍会在宿主内存中保留完整命令输出**:即使本适配器公开的是有界原始字节尾部,E2B `CommandHandle.stdout` 与 `.stderr` 仍会累积 base64 传输内容,因此无法达到子进程 seam 通常提供的宿主内存边界,而且传输保留量大于源数据流。
|
||
- **不支持需要同步 PID 的消费方**:远程启动期间 `pid` 保持 `-1`;包括 ACP 子进程后端在内,要求立即获得正 PID 的消费方无法原样使用本提供方。
|
||
- **私有状态随沙箱生命周期存在**:进程目录与有效的 spill 文件会留在 `.dsh-e2b` 下,直到所有者删除沙箱;本 POC 不提供沙箱内清理。
|
||
- **控制状态与沙箱用户同 UID**:E2B 以同一默认用户运行每条命令,因此 `0700`/`0600` 权限无法把 `.dsh-e2b` 控制文件与并发运行的沙箱进程隔离开;真正的隔离需要 E2B 提供按命令用户或带外控制通道。
|
||
- **数值进程身份没有复用围栏**:E2B 公开基于数值 PID/PGID 的输入、信号发送与清理操作,却没有与身份原子绑定的替代方案;在 E2B 新增身份原语,或实际故障证明需要更窄的协议之前,替代方案继续延后。
|
||
- **初始环境探测会继承沙箱默认值**:E2B 会把命令覆盖与默认环境条目合并,因此探测无法在枚举未知且形似凭据的名称之前将它们置空;因此,该 POC 不支持把 secret 放入沙箱默认环境变量。
|
||
- **E2B 不公开信号事实**:适配器请求的 `SIGTERM` 或 `SIGKILL` 只有在包装层发布的直接退出码没有胜出时才报告为信号;其他未请求的 SDK 退出始终保留为退出码,包括等于 `128 + signal` 的值。
|
||
- **无法精确检查终端 stdin 等待状态**:E2B 会公开前台进程组,但不提供证明其正在等待 fd 0 所需的 syscall 证据,因此通用 PTY 后端会回退到受控提示符标记与有界静默机制。
|
||
- **依赖 Linux 工具与 E2B 传输语义**:没有 Windows、逃逸会话恢复或网络分区的保真层。
|
||
|
||
<a id="dev-note"></a>
|
||
### 开发备注
|
||
|
||
<details>
|
||
<summary>维护者的工作上下文——点击展开</summary>
|
||
|
||
本开发备注是维护者的工作上下文:开放问题与尚未决定的探索方向。它明确不具权威性——已交付的行为、限制与既定理由以上文和包代码为准。
|
||
|
||
#### 开放:数值进程身份
|
||
|
||
E2B 公开基于数值 PID/PGID 的输入、信号发送与清理操作,却没有与身份原子绑定的替代方案。适配器会尽量减少宿主往返,并在 E2B 新增身份原语或实际故障证明需要更窄的协议之前,继续延后替代方案(TODO(e2b-pgid-identity))。
|
||
|
||
#### 开放:替换环境与状态观察
|
||
|
||
由于 E2B 会合并命令覆盖,初始环境探测会继承沙箱默认值;又因为 E2B 无法独立于后代持有的输出观察直接命令的退出,collect/inherit 命令状态需要控制面轮询。两者都只能靠 E2B 的新原语来弥合(TODO(e2b-replace-environment)、TODO(e2b-status-watch))。
|
||
|
||
</details>
|