feat(code-runtime): complete sandboxed Node execution and Session fixtures
This commit is contained in:
1 parent
f6904f4615
commit
8e19ec4962
82 files changed
+2580
-348
No files matched your search
@@ -1,5 +1,5 @@
|
||||
---
|
||||
description: "Worker 线程代码执行,面向组装、容量规划或调试已发布 TypeScript 后端的用户与维护者;该后端在全新的 Node worker 中运行每个程序。"
|
||||
description: "在全新 Node 进程中运行 TypeScript 程序,使用会话文件系统沙箱、受管清理以及可配置的执行与输出限制。"
|
||||
kind: "package-reference"
|
||||
---
|
||||
|
||||
@@ -9,7 +9,7 @@ kind: "package-reference"
|
||||
|
||||
## 概述
|
||||
|
||||
本包让 PTC 组合能够使用宿主提供的绑定执行模型编写的 TypeScript,并取得完成值、顺序日志或结构化失败。每次请求都不继承先前运行的状态;语法错误、预算到期、中止、内存耗尽和输出溢出等失败会作为结果返回,而不是抛出。应将执行的代码视为与 bash 拥有同等权限:本包限制环境暴露和资源使用,但不将代码与宿主隔离。可配置的计算时间、墙钟时间、堆和输出上限会终止运行并限制其结果大小。
|
||||
在与 Bash 相同的平台沙箱策略下执行模型编写的 TypeScript,并通过异步绑定调用 Host 提供的函数。每次调用启动一个全新的 Node 进程,返回捕获日志、精确 JSON 值或结构化失败。直接 Node API 在所选限制内仍可使用。经过时间截止、输出上限和 V8 堆限制约束执行;取消和完成都会终止受管进程范围。请求受限模式但沙箱后端不可用时,执行失败。
|
||||
|
||||
## 目录
|
||||
|
||||
@@ -17,7 +17,7 @@ kind: "package-reference"
|
||||
- [理解实现](#understand-the-implementation)
|
||||
- [进一步探索](#further-exploration)
|
||||
- [模型体验](#model-experience)
|
||||
- [已知限制与延期工作](#known-limitations-and-deferred-work)
|
||||
- [已知限制与延后工作](#known-limitations-and-deferred-work)
|
||||
- [开发备注](#dev-note)
|
||||
|
||||
-----
|
||||
@@ -25,40 +25,51 @@ kind: "package-reference"
|
||||
<a id="use-this-package"></a>
|
||||
## 使用本包
|
||||
|
||||
当组合需要执行模型编写的 TypeScript 程序时,连同 code-runtime seam 一起挂载此后端;只要模型调用 `run_code`,`dsh-tools` 中的 PTC mode 就会通过 `ctx.codeRuntime` 驱动它。每个执行上限都是已验证的配置,因此你可以从 `cordis.yml` 为部署调整运行时规模。
|
||||
在提供 `fs`、`subprocess`、`sandbox` 与 `sandboxPolicy` 的组合中挂载本提供方。`dsh-tools` 的 PTC 模式传入调用 Session 的目录和常设策略;直接运行时消费方在执行前解析这些选项。
|
||||
|
||||
### 最小配置
|
||||
### 配置
|
||||
|
||||
在所需服务可用后,配置提供方条目:
|
||||
|
||||
```yaml
|
||||
- name: '@deepseek-ai/dsh-code-runtime'
|
||||
- name: '@deepseek-ai/dsh-code-runtime-node'
|
||||
config:
|
||||
computeMs: 60000 # busy-time budget (measured event-loop active time)
|
||||
maxWallMs: 600000 # wall-clock ceiling; never pauses for anything
|
||||
maxOutputBytes: 67108864 # combined serialized outer-output cap (64 MiB)
|
||||
maxOldGenerationSizeMb: 512 # worker heap cap
|
||||
timeoutMs: 120000
|
||||
maxTimeoutMs: 600000
|
||||
maxOutputBytes: 67108864
|
||||
maxOldGenerationSizeMb: 512
|
||||
maxMessageBytes: 134217728
|
||||
maxPendingCalls: 128
|
||||
graceMs: 3000
|
||||
```
|
||||
|
||||
| 字段 | 默认值 | 含义 |
|
||||
|---|---|---|
|
||||
| `computeMs` | `60,000` | 忙碌时间预算:worker 实测事件循环活跃时间超过该值时,运行以 `timeout` 失败 |
|
||||
| `maxWallMs` | `600,000` | 墙钟上限,为忙碌时间无法观测的等待兜底;最大 `2_147_483_647` |
|
||||
| `maxOutputBytes` | `67,108,864` | 序列化日志加完成值或失败消息的硬上限;至少 `4` |
|
||||
| `maxOldGenerationSizeMb` | `512` | worker 堆上限;溢出会杀死 worker,并以 `worker-exit` 呈现 |
|
||||
| `timeoutMs` | `120,000` | 默认经过时间截止,包括嵌套工具与审批等待 |
|
||||
| `maxTimeoutMs` | `600,000` | 解析器应用的经过时间截止上限 |
|
||||
| `maxOutputBytes` | `67,108,864` | 序列化日志与完成值或诊断的合计预算 |
|
||||
| `maxOldGenerationSizeMb` | `512` | V8 老生代堆上限,单位 MiB |
|
||||
| `maxMessageBytes` | `134,217,728` | 控制帧、未完成参数字节和排队控制写入的上限 |
|
||||
| `maxPendingCalls` | `128` | 同时进行的 Host 绑定调用数量上限 |
|
||||
| `graceMs` | `3,000` | 受管终止与输出排空宽限时间 |
|
||||
| `nodeExecutable` | 当前 Node 可执行文件 | 在子进程执行世界中解析的可执行文件 |
|
||||
| `bootstrapPath` | 包内 bootstrap | 该执行世界中预先安装的构建后 bootstrap 的可选绝对路径 |
|
||||
|
||||
每个字段在加载时都会验证并提供默认值;没有其他可调项。生成的[配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-code-runtime-node)是所有受支持字段的完整参考。
|
||||
[配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-code-runtime-node)定义可接受的配置字段。`resolve(request)` 补全 cwd、封顶后的 timeout 与执行策略;`run(spec)` 接受这些已解析输入,不补缺省值。
|
||||
|
||||
### 运行返回什么
|
||||
### 执行与结果
|
||||
|
||||
成功的运行把程序的无损 JSON 完成值作为 `result.value` 返回,把程序打印的文本按顺序作为 `result.logs` 返回。顶层 `await`/`return` 可用,程序可以把宿主提供的绑定函数(PTC mode 暴露一个 `tools` 对象)当作普通异步调用。
|
||||
程序是异步函数体:支持顶层 `await` 与 `return`,且只接受可擦除 TypeScript。成功调用以 `result.value` 返回无损 JSON 值,以 `result.logs` 返回捕获文本。`result.sandbox` 独立于程序结果报告所选模式、观察到的拒绝,以及后端完整或部分的强制能力。
|
||||
|
||||
### 隔离措施,而非安全边界
|
||||
直接文件系统、网络与子进程操作仍是 Node 操作,受所选 OS 沙箱约束。嵌套 Host 绑定通过控制通道调用;PTC 工具调用保留注册表的可见性、排序、日志和审批规则。运行程序不会改变 Session 的常设策略,也不会在拒绝后自动重放程序。
|
||||
|
||||
程序运行时的权限与 bash 工具相当:它可以访问 Node API,后端也刻意不承诺与宿主的隔离。它提供的是隔离措施:独立 isolate、空环境(没有环境变量凭据,也不继承 loader 标志)、可配置堆上限,以及也能终止同步热循环的强制终止。程序派生的 OS 进程在 `terminate()` 后仍然存活,需要部署层面的清理。
|
||||
### 截止时间与取消
|
||||
|
||||
### 可能出什么问题
|
||||
经过时间截止覆盖运行时准备和执行,包括等待嵌套工具或审批的时间。它不是 CPU 计量器。超时或取消通过 Host 的受管进程所有者停止同步循环;成功完成也会清理该受管范围。选择结果后、清理前停止计时器,因此调用可能要在执行截止之后等待清理结算才返回。
|
||||
|
||||
每次程序运行都会通过 resolve 返回结果,因此运行失败会体现在 `result.error` 中,而不是触发 rejection:语法错误或不可擦除的 TypeScript(`enum`、namespace)在任何 worker 启动前就以 `exception` 失败;预算到期是 `timeout`;中止信号是 `abort`;堆溢出或其他 worker 终止是 `worker-exit`;不是无损 JSON 的完成值是 `invalid-output`;超出上限的序列化输出是 `output-limit`——并保留能容纳的已捕获日志前缀。只有调用方误用才会触发 rejection,例如在 dispose(资源释放)后提交运行。
|
||||
### 失败
|
||||
|
||||
程序解析错误与抛出异常为 `exception`;截止到期为 `timeout`;取消为 `abort`;畸形或超量控制通信为 `protocol`;约束不可用为 `sandbox-unavailable`;进程提前退出或受管清理失败为 `worker-exit`。进程提供方保留与执行基底无关的失败名 `worker-exit`。有损完成值为 `invalid-output`,外层结果超限为 `output-limit`,并保留能容纳的日志前缀。无效或不支持的选项,以及资源释放后的调用,以调用方误用拒绝。
|
||||
|
||||
-----
|
||||
|
||||
@@ -66,45 +77,31 @@ kind: "package-reference"
|
||||
## 理解实现
|
||||
|
||||
<details>
|
||||
<summary>实现细节——点击展开</summary>
|
||||
<summary>实现内部——点击展开</summary>
|
||||
|
||||
本节解释后端背后的设计;可观察行为已在[使用本包](#use-this-package)中完整说明。
|
||||
Host 负责策略、截止时间、绑定查找和进程清理。子进程负责程序求值与绑定代理;即使使用预期的控制描述符,模型编写的代码仍是不可信对端。
|
||||
|
||||
### 设计理念
|
||||
### 启动与控制
|
||||
|
||||
后端基于一项明确区分:**这是隔离措施,而非安全边界**。模型代码按与 bash 等价的信任等级处理([PTC mode Agent Note](../../../.agents/notes/implemented/feature/2026-06-15-ptc.zh.md) 的 Trust posture),因此设计追求可重建性与有界资源使用,而非硬性的多租户边界——那需要等待容器级后端。每次运行使用一个全新的 worker,程序的世界随 worker 一同终止:不存在可泄漏、也无需记录的跨运行状态,仅凭会话日志即可重建一次运行。
|
||||
Host 擦除可擦除类型,在配置的执行世界中解析可执行文件与 bootstrap,通过 `ctx.sandbox` 包装 argv,并通过 `ctx.subprocess` 启动。子进程接管继承的控制通道,在求值程序前清空环境。显式 Node 参数避免继承 Host 的加载器或调试器标志。
|
||||
|
||||
### 执行流程
|
||||
带长度分帧的 JSON 与 stdout/stderr 分开传输。Host 限制帧与排队写入,在分派前验证调用身份和已声明的绑定名,并拒绝无效通信。输出捕获计量序列化日志加完成值或诊断;固定结果信封字段与沙箱元数据不计入该账本。
|
||||
|
||||
一次运行在宿主侧剥离类型(`node:module` 的 `stripTypeScriptTypes`,保持字节位置不变),包裹为异步函数的函数体使顶层 `await`/`return` 可用,然后发送给全新的 worker,由 bootstrap 物化绑定命名空间。绑定调用以无损 JSON 跨消息端口传递,每个调用 id 至多应答一次。日志文本主动流向宿主,因此被终止的程序仍会显示已打印的内容。恰好一个结果结算运行——`done` 帧、预算到期、中止或 worker 终止——之后宿主终止 worker 并等待其退出。
|
||||
### 源代码与构建后 bootstrap
|
||||
|
||||
### 把对端视为不可信
|
||||
源代码执行加载仅含可擦除语法的 bootstrap 依赖,不依赖同级包的构建后导出。构建后执行使用包内 `process.js` 入口。无法映射 Host bootstrap 的执行世界需要预先安装兼容的 `bootstrapPath`;不会假设 Host 路径对应同一个远程文件。
|
||||
|
||||
模型代码能够访问 `parentPort` 并伪造通信,因此任何代码读取入站消息前,系统都会逐字段验证并重建:伪造的额外字段绝不随行,非数字的 call id 绝不会被回显进 reply,绑定名称只解析为自有属性(伪造的 `constructor` 无法沿原型链访问),垃圾被静默丢弃。worker 侧命名空间使用 null-prototype,因此形似 `__proto__` 的绑定名称只是普通键。
|
||||
|
||||
### 预算
|
||||
|
||||
存在两个独立预算,因为对端不可信:`computeMs` 计量 worker 的实测忙碌时间(每 25 ms 轮询一次 `eventLoopUtilization()`),因此热循环无论是否有诱饵 dispatch 在途都会到期,而等待慢绑定的程序不累计;`maxWallMs` 为忙碌时间无法观测的情况兜底,例如永远不会 resolve 的 promise。二者最终都会调用 `worker.terminate()`。`maxWallMs` 在加载时对照 `MAX_TIMER_DELAY_MS` 做范围校验,因为 `setTimeout` 会把更长的延迟限制为 1 ms。
|
||||
|
||||
### 输出账本
|
||||
|
||||
`maxOutputBytes` 统计外层 `logs` 数组加完成值或失败消息载荷的 JSON 序列化;固定的 `CodeRunResult` 字段名与外层封装语法不计入这份账本。未超过上限时返回精确值;有损完成值属于 `invalid-output`,组合溢出属于 `output-limit`,不会用 inspected string 代替。失败会保留日志中能容纳的已捕获前缀。
|
||||
|
||||
### 源码地图
|
||||
### 源码索引
|
||||
|
||||
| 文件 | 职责 |
|
||||
|---|---|
|
||||
| [`src/index.ts`](src/index.ts) | 插件入口:`Config` schema、`NodeCodeRuntime`、运行编排、输出账本 |
|
||||
| [`src/worker.ts`](src/worker.ts) | 源码模式 worker 入口(可擦除 TypeScript,不依赖 `lib/`) |
|
||||
| [`src/bootstrap.ts`](src/bootstrap.ts) | worker 侧 bootstrap:命名空间物化、console shim、日志捕获 |
|
||||
| [`src/protocol.ts`](src/protocol.ts) | host 与 worker 之间的端口消息词汇 |
|
||||
| [`src/worker-json.ts`](src/worker-json.ts) | worker 侧无损 JSON 编解码 |
|
||||
| [`src/output-json.ts`](src/output-json.ts) | 外层账本的字节计量与截断 |
|
||||
| — | 不发布运行时不变式伴生入口;这个进程边界实现不暴露同进程事件关系,worker 协议测试与构建后 worker 测试负责覆盖。 |
|
||||
|
||||
### 未构建与已构建的 worker 入口
|
||||
|
||||
源代码模式通过 Node 原生类型剥离加载只包含可擦除语法的 `src/worker.ts`;其传递运行时闭包只包含 Node 内置模块和相对源模块,因此全新 checkout 绝不需要兄弟工作区包尚未构建的 `lib/` 导出。构建模式会把兄弟文件 `lib/worker.cjs` 作为文件系统路径传入,因为 pkg 的虚拟文件系统(VFS)Worker hook 要求 CommonJS;同一路径也可在普通 Node 下使用。
|
||||
| [`src/index.ts`](src/index.ts) | 配置、解析、策略、绑定与受管执行 |
|
||||
| [`src/launch.ts`](src/launch.ts) | 可执行文件/bootstrap 参数与执行世界资源映射 |
|
||||
| [`src/process.ts`](src/process.ts) | 子进程握手、环境清空与程序生命周期 |
|
||||
| [`src/bootstrap.ts`](src/bootstrap.ts) | 程序求值、绑定代理与输出捕获 |
|
||||
| [`src/channel.ts`](src/channel.ts) | 分帧、有界写入与协议失败 |
|
||||
| [`src/output-ledger.ts`](src/output-ledger.ts) | Host 外层结果计量 |
|
||||
| — | 不发布运行时不变式配套模块;分帧与进程清理跨进程边界强制执行,不依靠同进程中的独立观测。 |
|
||||
|
||||
</details>
|
||||
|
||||
@@ -113,44 +110,44 @@ kind: "package-reference"
|
||||
<a id="further-exploration"></a>
|
||||
## 进一步探索
|
||||
|
||||
当后端约定不够用时阅读以下内容。它们从 seam 定义进入消费方与配置面。
|
||||
直接使用提供方前先读服务约定;决策记录解释策略与消费方职责。
|
||||
|
||||
- [代码运行时 seam](../code-runtime/README.zh.md)——此后端实现的抽象约定。
|
||||
- [PTC mode Agent Note](../../../.agents/notes/implemented/feature/2026-06-15-ptc.zh.md)——`dsh-tools` 如何消费 `ctx.codeRuntime` 并呈现 `run_code`。
|
||||
- [代码运行时子系统参考](../../../docs/subsystems/code-runtime.zh.md)——请求/结果词汇、绑定与失败分类体系。
|
||||
- [生成配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-code-runtime-node)——每个受支持配置字段及其源声明。
|
||||
- [代码运行时服务](../code-runtime/README.zh.md)——请求、已解析 spec 与结果。
|
||||
- [沙箱 Node 决策](../../../.agents/notes/implemented/architecture/2026-09-11-sandboxed-node-code-runtime.zh.md)——安全、生命周期与 timeout 取舍。
|
||||
- [PTC 基础](../../../.agents/notes/implemented/feature/2026-06-15-ptc.zh.md)——注册表呈现与嵌套工具分派。
|
||||
- [子进程提供方](../../subprocess/subprocess-local/README.zh.md)——受管进程范围与平台限制。
|
||||
|
||||
-----
|
||||
|
||||
<a id="model-experience"></a>
|
||||
## 模型体验
|
||||
|
||||
通过 `dsh-tools` 中的 PTC mode 间接提供,如果外层值能容纳则原样渲染,否则返回明确的 `invalid-output`/`output-limit` 失败,且只有外层 `run_code` 结果在其普通 spill 策略下进入模型上下文,绑定通信与中间值始终只存在于执行环境中。
|
||||
通过 `dsh-tools` 的 PTC 模式间接提供,返回捕获日志与完成值,或带沙箱事实的失败。中间绑定通信不进入模型历史;外层结果遵循普通工具溢出策略。
|
||||
|
||||
#### KV Cache 影响
|
||||
#### KV Cache effect
|
||||
|
||||
不会直接失效;由上述消费方负责请求前缀变更。
|
||||
不直接失效;具名消费方负责请求前缀的任何变更。
|
||||
|
||||
## 已知限制与延期工作
|
||||
## 已知限制与延后工作
|
||||
|
||||
<a id="known-limitations-and-deferred-work"></a>
|
||||
|
||||
这些限制界定执行保证与保留的输出。
|
||||
|
||||
这些限制说明此后端何时不合适,或何时需要特别的运维注意。它们是当前包约束,不是任务积压。
|
||||
|
||||
- **程序派生的 OS 进程在程序终止后仍会存活**——`worker.terminate()` 只结束线程,比 bash-local 的进程组终止更弱;在容器后端出现前,孤儿进程清理属于部署职责。
|
||||
- **类型剥离依赖 Node 的实验性 `stripTypeScriptTypes` API**——如依赖的行为发生变化,amaro 或 sucrase 是已经点名的直接替代品。
|
||||
- **`computeMs` 到期最多可能超过一个轮询间隔**——系统每 25 ms 采样一次忙碌时间(内部常量,有意不做成配置)。
|
||||
- **程序获得一个含 5 个方法的 `console` shim**(`log`/`info`/`warn`/`error`/`debug`)——有意不提供 Node 的完整 console 接口。
|
||||
- **中间绑定值没有字节上限**——程序可以用永远不会成为外层输出的值耗尽进程或 worker 内存。
|
||||
- **默认 64 MiB 上限是拒绝边界,不是可恢复存储**——外层 spill 机制只能保存发生 `output-limit` 后返回的有界日志和诊断;在运行时上限之外被拒绝的字节永远不会到达 spill 层。
|
||||
- **约束继承所选后端的限制**——完整与部分强制能力分开报告;沙箱策略与受管进程约束是不同保证。
|
||||
- **堆上限不是进程树内存限制**——原生分配与后代进程内存不属于 V8 老生代上限。不提供进程树 CPU 计量器。
|
||||
- **清理继承子进程的可观测范围**——使用 fallback 的平台上,逃逸的后代可能仍在受管范围之外;参见子进程提供方声明的限制。
|
||||
- **执行是一次性的**——没有 yield/wait API、实时结果流或跨调用保留的程序状态。
|
||||
- **输出上限拒绝超量内容,而不保留每个字节**——溢出只能保存本提供方交付的有界结果。
|
||||
- **绑定在传输接纳时受限**——控制限制不约束 Host 绑定生成结果期间分配的内存。
|
||||
- **console shim 有五个方法**——`log`、`info`、`warn`、`error` 与 `debug`。
|
||||
|
||||
<a id="dev-note"></a>
|
||||
### 开发备注
|
||||
|
||||
<details>
|
||||
<summary>维护者的工作上下文——点击展开</summary>
|
||||
<summary>维护者工作上下文——点击展开</summary>
|
||||
|
||||
无。
|
||||
[timeout 讨论](../../../.agents/notes/implemented/architecture/2026-09-11-sandboxed-node-code-runtime.zh.md#deferred-timeout-design)记录 yield、总生命周期、审批等待计时和进程树 CPU/RSS 上限的开放选择。这些选择不改变已配置的经过时间截止。
|
||||
|
||||
</details>
|
||||
Reference in new issue
Block a user