From 8a8c2164fdeba9ce96897a4121814b3957a33202 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Thu, 23 Jul 2026 03:32:24 +0800 Subject: [PATCH 1/5] fix(code-runtime): harden captured JSON boundary --- .../code-runtime-worker/src/output-json.ts | 26 +++++--- .../code-runtime-worker/src/worker-json.ts | 61 ++++++++++--------- .../code-runtime-worker/tests/runtime.spec.ts | 2 + 3 files changed, 52 insertions(+), 37 deletions(-) diff --git a/packages/code-runtime/code-runtime-worker/src/output-json.ts b/packages/code-runtime/code-runtime-worker/src/output-json.ts index cc668e5d99..06d56292bf 100644 --- a/packages/code-runtime/code-runtime-worker/src/output-json.ts +++ b/packages/code-runtime/code-runtime-worker/src/output-json.ts @@ -12,6 +12,7 @@ const intrinsicReflectApply = Reflect.apply as ( const intrinsicArrayIsArray = Array.isArray const IntrinsicBuffer = Buffer const intrinsicBufferByteLength = Reflect.get(Buffer, 'byteLength') as IntrinsicCallable +const intrinsicObjectCreate = Object.create const intrinsicObjectDefineProperty = Object.defineProperty const intrinsicObjectKeys = Object.keys const intrinsicString = String @@ -19,6 +20,22 @@ const intrinsicStringCharCodeAt = Reflect.get(String.prototype, 'charCodeAt') as const intrinsicStringCodePointAt = Reflect.get(String.prototype, 'codePointAt') as IntrinsicCallable const intrinsicStringSlice = Reflect.get(String.prototype, 'slice') as IntrinsicCallable +/** Build a data descriptor that cannot inherit model-defined accessor fields. */ +function dataDescriptor(value: unknown): PropertyDescriptor { + const descriptor = intrinsicObjectCreate(null) as PropertyDescriptor + descriptor.value = value + return descriptor +} + +/** Define an ordinary enumerable data slot without a prototype-bearing descriptor. */ +function defineEnumerableDataProperty(target: object, key: PropertyKey, value: unknown): void { + const descriptor = dataDescriptor(value) + descriptor.enumerable = true + descriptor.configurable = true + descriptor.writable = true + intrinsicObjectDefineProperty(target, key, descriptor) +} + /** UTF-8 byte length through the module-captured Node intrinsic. */ function byteLength(text: string): number { return intrinsicReflectApply(intrinsicBufferByteLength, IntrinsicBuffer, [text, 'utf8']) as number @@ -26,12 +43,7 @@ function byteLength(text: string): number { /** Append without consulting a model-mutated `Array.prototype`. */ function append(target: T[], value: T): void { - intrinsicObjectDefineProperty(target, target.length, { - value, - enumerable: true, - configurable: true, - writable: true, - }) + defineEnumerableDataProperty(target, target.length, value) } /** Pop without consulting a model-mutated `Array.prototype`. */ @@ -39,7 +51,7 @@ function takeLast(target: T[]): T | undefined { if (target.length === 0) return undefined const index = target.length - 1 const value = target[index] - intrinsicObjectDefineProperty(target, 'length', { value: index }) + intrinsicObjectDefineProperty(target, 'length', dataDescriptor(index)) return value } diff --git a/packages/code-runtime/code-runtime-worker/src/worker-json.ts b/packages/code-runtime/code-runtime-worker/src/worker-json.ts index ac61d4eaab..b91005bb68 100644 --- a/packages/code-runtime/code-runtime-worker/src/worker-json.ts +++ b/packages/code-runtime/code-runtime-worker/src/worker-json.ts @@ -14,28 +14,42 @@ const intrinsicReflectApply = Reflect.get(Reflect, 'apply') as ( const IntrinsicError = Error const IntrinsicSet = Set const intrinsicArrayIsArray = Array.isArray +const intrinsicArrayPrototype = Array.prototype const intrinsicNumberIsFinite = Number.isFinite const intrinsicNumberIsSafeInteger = Number.isSafeInteger +const intrinsicObjectCreate = Object.create const intrinsicObjectDefineProperty = Object.defineProperty const intrinsicObjectGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor const intrinsicObjectGetPrototypeOf = Object.getPrototypeOf const intrinsicObjectHasOwn = Object.hasOwn const intrinsicObjectIs = Object.is const intrinsicObjectKeys = Object.keys -const intrinsicObjectPropertyIsEnumerable = Reflect.get(Object.prototype, 'propertyIsEnumerable') as IntrinsicCallable +const intrinsicObjectPrototype = Object.prototype +const intrinsicObjectPropertyIsEnumerable = Reflect.get(intrinsicObjectPrototype, 'propertyIsEnumerable') as IntrinsicCallable const intrinsicReflectOwnKeys = Reflect.ownKeys const intrinsicSetAdd = Reflect.get(Set.prototype, 'add') as IntrinsicCallable const intrinsicSetDelete = Reflect.get(Set.prototype, 'delete') as IntrinsicCallable const intrinsicSetHas = Reflect.get(Set.prototype, 'has') as IntrinsicCallable +/** Build a data descriptor that cannot inherit model-defined accessor fields. */ +function dataDescriptor(value: unknown): PropertyDescriptor { + const descriptor = intrinsicObjectCreate(null) as PropertyDescriptor + descriptor.value = value + return descriptor +} + +/** Define an ordinary enumerable data slot without a prototype-bearing descriptor. */ +function defineEnumerableDataProperty(target: object, key: PropertyKey, value: unknown): void { + const descriptor = dataDescriptor(value) + descriptor.enumerable = true + descriptor.configurable = true + descriptor.writable = true + intrinsicObjectDefineProperty(target, key, descriptor) +} + /** Append without consulting a model-mutated `Array.prototype`. */ function append(target: T[], value: T): void { - intrinsicObjectDefineProperty(target, target.length, { - value, - enumerable: true, - configurable: true, - writable: true, - }) + defineEnumerableDataProperty(target, target.length, value) } /** Pop without consulting a model-mutated `Array.prototype`. */ @@ -43,7 +57,7 @@ function takeLast(target: T[]): T | undefined { if (target.length === 0) return undefined const index = target.length - 1 const value = target[index] - intrinsicObjectDefineProperty(target, 'length', { value: index }) + intrinsicObjectDefineProperty(target, 'length', dataDescriptor(index)) return value } @@ -76,26 +90,28 @@ function hasIntrinsicConstructor(prototype: object, name: 'Array' | 'Object'): b } } -/** Whether a candidate is one realm's intrinsic `Object.prototype`. */ -function isIntrinsicObjectPrototype(value: object): boolean { +/** Whether a candidate is a foreign realm's intrinsic `Object.prototype`. */ +function isForeignIntrinsicObjectPrototype(value: object): boolean { return intrinsicObjectGetPrototypeOf(value) === null && hasIntrinsicConstructor(value, 'Object') } /** Whether an array uses one realm's intrinsic `Array.prototype`, not a subclass or forged prototype. */ function hasPlainArrayPrototype(value: unknown[]): boolean { const prototype: unknown = intrinsicObjectGetPrototypeOf(value) + if (prototype === intrinsicArrayPrototype) return true if (!intrinsicArrayIsArray(prototype) || !hasIntrinsicConstructor(prototype, 'Array')) return false const objectPrototype: unknown = intrinsicObjectGetPrototypeOf(prototype) return typeof objectPrototype === 'object' && objectPrototype !== null - && isIntrinsicObjectPrototype(objectPrototype) + && isForeignIntrinsicObjectPrototype(objectPrototype) } /** Whether an object is a plain or null-prototype record from any JavaScript realm. */ function hasPlainObjectPrototype(value: object): boolean { const prototype: unknown = intrinsicObjectGetPrototypeOf(value) return prototype === null - || typeof prototype === 'object' && isIntrinsicObjectPrototype(prototype) + || prototype === intrinsicObjectPrototype + || typeof prototype === 'object' && isForeignIntrinsicObjectPrototype(prototype) } /** Return every JSON-visible object key, or reject own data JSON would discard. */ @@ -135,19 +151,9 @@ export function snapshotCodeJsonValue(value: unknown): CodeJsonValue | undefined if (destination.kind === 'root') { root = item } else if (destination.kind === 'array') { - intrinsicObjectDefineProperty(destination.target, destination.index, { - value: item, - enumerable: true, - configurable: true, - writable: true, - }) + defineEnumerableDataProperty(destination.target, destination.index, item) } else { - intrinsicObjectDefineProperty(destination.target, destination.key, { - value: item, - enumerable: true, - configurable: true, - writable: true, - }) + defineEnumerableDataProperty(destination.target, destination.key, item) } } @@ -361,12 +367,7 @@ export function decodeWorkerJson(input: unknown): CodeJsonValue | undefined { const key = parent.keys[parent.index] /* v8 ignore next -- object frames are built from validated keys and their exact length. */ if (key === undefined) return false - intrinsicObjectDefineProperty(parent.target, key, { - value, - enumerable: true, - configurable: true, - writable: true, - }) + defineEnumerableDataProperty(parent.target, key, value) } parent.index += 1 return true diff --git a/packages/code-runtime/code-runtime-worker/tests/runtime.spec.ts b/packages/code-runtime/code-runtime-worker/tests/runtime.spec.ts index 715dbb4121..ff75dc65b7 100644 --- a/packages/code-runtime/code-runtime-worker/tests/runtime.spec.ts +++ b/packages/code-runtime/code-runtime-worker/tests/runtime.spec.ts @@ -677,6 +677,8 @@ describe('WorkerCodeRuntime — hostile programs (real workers)', () => { stringPrototype.charCodeAt = stringPrototype.codePointAt = stringPrototype.slice = () => { throw new Error('mutated string method') }; Buffer.byteLength = () => 0; Function.prototype.toString = () => 'mutated'; + objectPrototype.get = () => undefined; + objectPrototype.constructor = arrayPrototype.constructor = null; globalThis.Array = globalThis.Buffer = globalThis.Function = globalThis.Number = globalThis.Object = globalThis.Reflect = globalThis.Set = globalThis.String = undefined; const echoed = await tools.echo({ request: ['€', 1] }); return { echoed, completion: { ok: true, amount: 42 } }; From a19c80bf6e0124eef2332e0a206ae86f95b4ed2f Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Thu, 23 Jul 2026 03:34:42 +0800 Subject: [PATCH 2/5] fix(code-runtime): preserve typed failures after mutation --- .../code-runtime-worker/src/bootstrap.ts | 15 +++++++++++++-- .../code-runtime-worker/tests/runtime.spec.ts | 9 +++++++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/packages/code-runtime/code-runtime-worker/src/bootstrap.ts b/packages/code-runtime/code-runtime-worker/src/bootstrap.ts index 3b0db6b59c..b654b04fec 100644 --- a/packages/code-runtime/code-runtime-worker/src/bootstrap.ts +++ b/packages/code-runtime/code-runtime-worker/src/bootstrap.ts @@ -10,6 +10,17 @@ import type { DoneMessage, ReplyMessage, WorkerBootData, WorkerToHost } from './ import { jsonStringBytesUpTo, jsonValueBytesUpTo, truncateJsonStringBytes } from './output-json.ts' import { decodeWorkerJson, encodeWorkerJson, snapshotCodeJsonValue } from './worker-json.ts' +const capturedObjectCreate = Object.create +const capturedObjectDefineProperty = Object.defineProperty + +/** Define one public binding-error field without consulting mutable globals or descriptor prototypes. */ +function defineBindingErrorField(error: Error, key: string, value: string): void { + const attributes = capturedObjectCreate(null) as PropertyDescriptor + attributes.enumerable = true + attributes.value = value + capturedObjectDefineProperty(error, key, attributes) +} + /** The port surface the bootstrap needs — satisfied by `parentPort` and by the tests' fake. */ export interface BootstrapPort { postMessage(message: WorkerToHost): void @@ -236,8 +247,8 @@ function makeBindingErrorClass( return class BindingCallError extends Error { constructor(memberName: string, message: string) { super(message) - Object.defineProperty(this, 'name', { enumerable: true, value: descriptor.name }) - Object.defineProperty(this, descriptor.memberNameProperty, { enumerable: true, value: memberName }) + defineBindingErrorField(this, 'name', descriptor.name) + defineBindingErrorField(this, descriptor.memberNameProperty, memberName) } } } diff --git a/packages/code-runtime/code-runtime-worker/tests/runtime.spec.ts b/packages/code-runtime/code-runtime-worker/tests/runtime.spec.ts index ff75dc65b7..f938a6802a 100644 --- a/packages/code-runtime/code-runtime-worker/tests/runtime.spec.ts +++ b/packages/code-runtime/code-runtime-worker/tests/runtime.spec.ts @@ -681,14 +681,19 @@ describe('WorkerCodeRuntime — hostile programs (real workers)', () => { objectPrototype.constructor = arrayPrototype.constructor = null; globalThis.Array = globalThis.Buffer = globalThis.Function = globalThis.Number = globalThis.Object = globalThis.Reflect = globalThis.Set = globalThis.String = undefined; const echoed = await tools.echo({ request: ['€', 1] }); - return { echoed, completion: { ok: true, amount: 42 } }; + let failure; + try { await tools.fail({}) } catch (error) { + failure = { typed: error instanceof ToolCallError, name: error.name, toolName: error.toolName, message: error.message }; + } + return { echoed, failure, completion: { ok: true, amount: 42 } }; `, - bindings: tools({ echo: async args => args }), + bindings: tools({ echo: async args => args, fail: async () => { throw new Error('nope') } }), }) expect(result).toEqual({ logs: [], value: { echoed: { request: ['€', 1] }, + failure: { typed: true, name: 'ToolCallError', toolName: 'fail', message: 'nope' }, completion: { ok: true, amount: 42 }, }, }) From ffbdabf39c94511b13bb6d89451ce0643bfa1f2e Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Thu, 23 Jul 2026 03:38:34 +0800 Subject: [PATCH 3/5] docs(code-runtime): specify mutation-safe boundaries --- .../2026-07-20-code-mode-typed-tool-returns.i18n.yaml | 4 ++-- .../feature/2026-07-20-code-mode-typed-tool-returns.md | 6 +++--- .../feature/2026-07-20-code-mode-typed-tool-returns.zh.md | 6 +++--- packages/code-runtime/code-runtime-worker/README.md | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.agents/notes/implemented/feature/2026-07-20-code-mode-typed-tool-returns.i18n.yaml b/.agents/notes/implemented/feature/2026-07-20-code-mode-typed-tool-returns.i18n.yaml index f3606667b5..76740443cb 100644 --- a/.agents/notes/implemented/feature/2026-07-20-code-mode-typed-tool-returns.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-20-code-mode-typed-tool-returns.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write -2026-07-20-code-mode-typed-tool-returns.md: 2446f1ac87d992a7d393485796c86ee7d7efcb1e -2026-07-20-code-mode-typed-tool-returns.zh.md: daac287671f9b231855d9af0d992d8e131126c52 +2026-07-20-code-mode-typed-tool-returns.md: 1f3baa076115b848fcde107f4ba4f7b3eb779d4e +2026-07-20-code-mode-typed-tool-returns.zh.md: e7d0fe6f7259cae2c51a25c1e6b5b5669b8f5f88 diff --git a/.agents/notes/implemented/feature/2026-07-20-code-mode-typed-tool-returns.md b/.agents/notes/implemented/feature/2026-07-20-code-mode-typed-tool-returns.md index 2446f1ac87..1f3baa0761 100644 --- a/.agents/notes/implemented/feature/2026-07-20-code-mode-typed-tool-returns.md +++ b/.agents/notes/implemented/feature/2026-07-20-code-mode-typed-tool-returns.md @@ -51,9 +51,9 @@ declare const tools: { Before dispatch the bridge snapshots binding arguments as lossless JSON and snapshots the detached value again for an independent durable summary event. Host-side detachment, immutable execution, and output-schema projection all use iterative traversals rather than nested structured clone or recursive freezing. `undefined`, non-finite numbers, `-0`, sparse arrays, cycles, functions, and exotic objects reject that call before the tool runs. Successful dispatch returns `ToolExecutionResult.value`; Native `content`, metadata, and internal error information do not cross to the program. -Code Mode declares its rejection capability on the runtime request as `{ name: "ToolCallError", memberNameProperty: "toolName" }`. The runtime seam treats those names as data: the worker materializes and injects the actual constructor used for `tools` binding failures, so `error instanceof ToolCallError` works without making a generic runtime know about tools. The error has the standard `Error` message plus the exact `toolName`; it deliberately omits `ToolFailure.info`, error codes, and Native content. This is an exception contract for control flow, not a failure union for programmatic classification. +Code Mode declares its rejection capability on the runtime request as `{ name: "ToolCallError", memberNameProperty: "toolName" }`. The runtime seam treats those names as data: the worker materializes and injects the actual constructor used for `tools` binding failures, so `error instanceof ToolCallError` works without making a generic runtime know about tools. The worker defines the error's public fields through module-captured property-definition intrinsics and null-prototype descriptors, so model mutations cannot replace the promised rejection with a worker failure. The error has the standard `Error` message plus the exact `toolName`; it deliberately omits `ToolFailure.info`, error codes, and Native content. This is an exception contract for control flow, not a failure union for programmatic classification. -Binding arguments and resolutions are revalidated as lossless JSON on both sides of the hostile worker protocol and have no byte cap. Before crossing through structured clone, each detached value is encoded as a flat pre-order token stream whose transport nesting is bounded; the receiver rebuilds it iteratively. Valid application nesting therefore has neither a JavaScript call-stack depth cap nor a platform-specific nested structured-clone limit. At module initialization the worker captures the native function-source intrinsic plus every structural and metering intrinsic used by the JSON boundary; private array and set operations invoke those captures without consulting mutable global or prototype slots. Model code can therefore replace helpers such as `Object.keys`, `Array.isArray`, collection methods, string methods, or `Buffer.byteLength` without changing validation, wire transport, or byte accounting. The native function-source capture distinguishes realm-owned plain-container prototypes from user-authored constructors that imitate `Object` or `Array`. The dependency-light runtime seam names its structural equivalent `CodeJsonValue` so it need not depend on the session-owned canonical type; the generated SDK and tool API use `JsonValue`. Intermediate values are not prompt-truncated, context-spilled, or persisted. This preserves full acquired search, workflow, task, filesystem, and MCP values for programmatic filtering while leaving provider and executor acquisition limits truthful. +Binding arguments and resolutions are revalidated as lossless JSON on both sides of the hostile worker protocol and have no byte cap. Before crossing through structured clone, each detached value is encoded as a flat pre-order token stream whose transport nesting is bounded; the receiver rebuilds it iteratively. Valid application nesting therefore has neither a JavaScript call-stack depth cap nor a platform-specific nested structured-clone limit. At module initialization the worker captures its own realm's `Array.prototype` and `Object.prototype` identities, the native function-source intrinsic used only to recognize foreign-realm plain-container prototypes, and every structural and metering intrinsic used by the JSON boundary. Property writes use null-prototype descriptors, while private array and set operations invoke captured methods without consulting mutable global or prototype slots. Model code can therefore replace helpers such as `Object.keys`, `Array.isArray`, collection methods, string methods, or `Buffer.byteLength`, rewrite intrinsic-prototype constructor slots, or add descriptor-shaped fields to `Object.prototype` without changing validation, wire transport, or byte accounting. The foreign-realm native function-source check still rejects user-authored constructors that imitate `Object` or `Array`. The dependency-light runtime seam names its structural equivalent `CodeJsonValue` so it need not depend on the session-owned canonical type; the generated SDK and tool API use `JsonValue`. Intermediate values are not prompt-truncated, context-spilled, or persisted. This preserves full acquired search, workflow, task, filesystem, and MCP values for programmatic filtering while leaving provider and executor acquisition limits truthful. ### Outer result and output ledger @@ -79,7 +79,7 @@ The opaque `exec.parent` token marks nested calls. Presentation metadata and gen ## Testing -Compile-time and snapshot tests pin exact `ToolArgsMap`, `ToolOutputMap`, `ToolName`, schema-to-TypeScript coverage, and exotic names. Registry and real-worker tests cover scalar, array, object, and null values; raw string rendering; absent `undefined`; consumer-declared real rejection classes, including `ToolCallError`; invalid arguments and completions, including intrinsic-looking forged prototypes; model-mutated JSON-boundary globals and prototypes; large uncapped intermediate bindings; nested spill suppression; exact and over-limit 64 MiB accounting; combined logs/value/diagnostic accounting; giant thrown stacks; bounded failure spill; hostile forged traffic; and built-package execution. +Compile-time and snapshot tests pin exact `ToolArgsMap`, `ToolOutputMap`, `ToolName`, schema-to-TypeScript coverage, and exotic names. Registry and real-worker tests cover scalar, array, object, and null values; raw string rendering; absent `undefined`; consumer-declared real rejection classes, including `ToolCallError`; invalid arguments and completions, including intrinsic-looking forged prototypes; model-mutated JSON-boundary globals, prototype methods, constructor slots, and inherited descriptor fields; typed binding failures after those mutations; large uncapped intermediate bindings; nested spill suppression; exact and over-limit 64 MiB accounting; combined logs/value/diagnostic accounting; giant thrown stacks; bounded failure spill; hostile forged traffic; and built-package execution. Keyless real-worker integration tests pin the two handle workflows that prose results could not safely support. A background bash call returns its task id, the outer run settles, and a later run polls that id to completion; separate cases prove pre-abort creates no task, post-publication call abort preserves the task, foreground execution stays signal-coupled, and `task_kill` owns cancellation. A Cordis program reads an active or pending mount's id and `waitingFor` fields directly, unmounts by that id, and confirms removal without parsing rendered text. diff --git a/.agents/notes/implemented/feature/2026-07-20-code-mode-typed-tool-returns.zh.md b/.agents/notes/implemented/feature/2026-07-20-code-mode-typed-tool-returns.zh.md index daac287671..e7d0fe6f72 100644 --- a/.agents/notes/implemented/feature/2026-07-20-code-mode-typed-tool-returns.zh.md +++ b/.agents/notes/implemented/feature/2026-07-20-code-mode-typed-tool-returns.zh.md @@ -51,9 +51,9 @@ declare const tools: { 分发前,桥接层会把绑定参数快照为无损 JSON,再对分离后的值生成一次快照,供独立的持久摘要事件使用。宿主侧的值分离、执行数据的不可变处理与输出 schema 投影均采用迭代遍历,而不使用嵌套结构化克隆或递归冻结。`undefined`、非有限数、`-0`、稀疏数组、循环引用、函数和非普通对象都会使该调用在工具运行前 reject。成功分发会返回 `ToolExecutionResult.value`;Native `content`、元数据和内部错误信息不会传入程序。 -Code Mode 通过运行时请求中的 `{ name: "ToolCallError", memberNameProperty: "toolName" }` 声明其 reject 异常能力。运行时 seam 只把这些名称视为数据:worker 会动态生成并注入真正用于 `tools` 绑定失败的构造函数,因此无需让通用运行时了解工具,`error instanceof ToolCallError` 也能成立。该错误包含标准的 `Error` 消息和确切的 `toolName`,并有意省略 `ToolFailure.info`、错误代码与 Native 内容。这是一项用于控制流的异常契约,而不是供程序分类的失败联合。 +Code Mode 通过运行时请求中的 `{ name: "ToolCallError", memberNameProperty: "toolName" }` 声明其 reject 异常能力。运行时 seam 只把这些名称视为数据:worker 会动态生成并注入真正用于 `tools` 绑定失败的构造函数,因此无需让通用运行时了解工具,`error instanceof ToolCallError` 也能成立。worker 会通过模块初始化时捕获的属性定义内建方法和原型为 null 的属性描述符来定义该错误的公开字段,因此模型代码的修改不会把契约承诺的 reject 变成 worker 失败。该错误包含标准的 `Error` 消息和确切的 `toolName`,并有意省略 `ToolFailure.info`、错误代码与 Native 内容。这是一项用于控制流的异常契约,而不是供程序分类的失败联合。 -绑定参数与绑定返回值会在不可信 worker 协议的两端重新校验为无损 JSON,且不设字节上限。每个分离后的值在通过结构化克隆跨越边界前,都会编码为扁平的前序 token 流,其传输结构的嵌套深度有界;接收方再以迭代方式重建该值。因此,有效应用数据的嵌套深度既不受 JavaScript 调用栈深度上限限制,也不受特定平台对嵌套结构化克隆施加的上限限制。模块初始化时,worker 会捕获用于读取函数源码的原生内建方法,以及 JSON 边界用于结构处理和计量的全部内建方法;内部的数组与集合操作直接调用这些捕获值,不会访问可变的全局或原型槽位。因此,即使模型代码替换 `Object.keys`、`Array.isArray`、集合方法、字符串方法或 `Buffer.byteLength` 等辅助方法,也不会改变校验、协议传输或字节计量。用于读取函数源码的捕获值会区分每个 JavaScript 运行域原生的普通容器原型与由用户编写、冒充 `Object` 或 `Array` 的构造函数伪造的原型。为保持依赖轻量,运行时 seam 将结构等价类型命名为 `CodeJsonValue`,从而无需依赖会话侧拥有的规范类型;生成的 SDK 和工具 API 则使用 `JsonValue`。这些值不会经过提示词截断、上下文输出落盘或持久化。因此,程序可以完整筛选已经采集的搜索、工作流、任务、文件系统与 MCP 值,同时提供方和执行器的采集上限仍会实际生效。 +绑定参数与绑定返回值会在不可信 worker 协议的两端重新校验为无损 JSON,且不设字节上限。每个分离后的值在通过结构化克隆跨越边界前,都会编码为扁平的前序 token 流,其传输结构的嵌套深度有界;接收方再以迭代方式重建该值。因此,有效应用数据的嵌套深度既不受 JavaScript 调用栈深度上限限制,也不受特定平台对嵌套结构化克隆施加的上限限制。模块初始化时,worker 会捕获自身 JavaScript 运行域中 `Array.prototype` 和 `Object.prototype` 的引用、仅用于识别其他运行域普通容器原型的原生函数源码内建方法,以及 JSON 边界用于结构处理和计量的全部内建方法。属性写入使用原型为 null 的属性描述符;内部的数组与集合操作直接调用捕获的方法,不会访问可变的全局或原型槽位。因此,即使模型代码替换 `Object.keys`、`Array.isArray`、集合方法、字符串方法或 `Buffer.byteLength` 等辅助方法,重写内建原型的构造函数槽位,或向 `Object.prototype` 添加形如属性描述符的字段,也不会改变校验、协议传输或字节计量。面向其他运行域的原生函数源码检查仍会拒绝由用户编写、冒充 `Object` 或 `Array` 的构造函数。为保持依赖轻量,运行时 seam 将结构等价类型命名为 `CodeJsonValue`,从而无需依赖会话侧拥有的规范类型;生成的 SDK 和工具 API 则使用 `JsonValue`。这些值不会经过提示词截断、上下文输出落盘或持久化。因此,程序可以完整筛选已经采集的搜索、工作流、任务、文件系统与 MCP 值,同时提供方和执行器的采集上限仍会实际生效。 ### 外层结果与输出账本 @@ -79,7 +79,7 @@ Code Mode 通过运行时请求中的 `{ name: "ToolCallError", memberNameProper ## 测试 -编译期测试与快照测试锁定了精确的 `ToolArgsMap`、`ToolOutputMap`、`ToolName`、schema 到 TypeScript 的覆盖范围以及特殊名称。注册表与真实 worker 测试覆盖标量、数组、对象和 null 值;字符串原文渲染;缺席的 `undefined`;消费方声明并用于 reject 的真实异常类,包括 `ToolCallError`;无效参数与完成值,包括伪装为内建原型的伪造原型;模型代码修改过的 JSON 边界全局对象与原型;不设上限的大型中间绑定值;嵌套输出落盘抑制;64 MiB 上限内外的精确计量;日志、值与诊断的组合计量;抛出的超大堆栈;有界失败的输出落盘;不可信对端伪造的流量;以及构建后包的执行。 +编译期测试与快照测试锁定了精确的 `ToolArgsMap`、`ToolOutputMap`、`ToolName`、schema 到 TypeScript 的覆盖范围以及特殊名称。注册表与真实 worker 测试覆盖标量、数组、对象和 null 值;字符串原文渲染;缺席的 `undefined`;消费方声明并用于 reject 的真实异常类,包括 `ToolCallError`;无效参数与完成值,包括伪装为内建原型的伪造原型;模型代码修改过的 JSON 边界全局对象、原型方法、构造函数槽位,以及继承而来的属性描述符字段;上述修改后的类型化绑定失败;不设上限的大型中间绑定值;嵌套输出落盘抑制;64 MiB 上限内外的精确计量;日志、值与诊断的组合计量;抛出的超大堆栈;有界失败的输出落盘;不可信对端伪造的流量;以及构建后包的执行。 无密钥的真实 worker 集成测试锁定了自然语言结果无法安全支持的两种句柄工作流。后台 bash 调用返回 task id,外层运行结束,之后的运行再根据该 id 轮询直至任务完成;其他用例分别证明,预先中止不会创建任务、发布后的调用取消会保留任务、前台执行仍与信号耦合,并且取消归 `task_kill` 所有。Cordis 程序会直接读取 active 或 pending 挂载的 id 和 `waitingFor` 字段,按该 id 卸载,并在不解析渲染文本的情况下确认挂载已移除。 diff --git a/packages/code-runtime/code-runtime-worker/README.md b/packages/code-runtime/code-runtime-worker/README.md index ac63c0a0b5..34a2f6fc2c 100644 --- a/packages/code-runtime/code-runtime-worker/README.md +++ b/packages/code-runtime/code-runtime-worker/README.md @@ -21,9 +21,9 @@ Every field is validated and defaulted; `maxOutputBytes` is a safe integer of at - **One fresh worker per run, no pooling** — a program's world dies with its worker: no cross-run state to log, state bleed unrepresentable, runs reconstructable from the session log alone. - **Type-strip host-side, in execution context** — the program is wrapped in an async-function shell, stripped with `node:module`'s `stripTypeScriptTypes` (erasable syntax only — `enum`/namespaces are rejected as a program `exception` and no worker spawns), and sliced back out byte-positioned; it then executes as the body of an `AsyncFunction`, so top-level `await`/`return` work. - **The port assumes a hostile peer** — model code can reach `parentPort` and forge traffic, so every inbound message is shape-validated and REBUILT before anything reads it (`null`, primitives, junk types, and malformed payloads drop without a throw; forged extra fields never ride along), the host answers each call id at most once, resolves binding names as OWN properties only (a forged `constructor` cannot walk a prototype chain), drops post-settlement replies, and validates every binding resolution and completion as lossless JSON. Forged `log`/`done` messages cannot bypass the outer cap: the host repeats validation and accounts every admitted log plus the completion or diagnostic. Worker-side namespaces are null-prototype with `defineProperty`, so `__proto__`-shaped binding names are ordinary keys. -- **Binding rejection classes are request data** — an optional namespace descriptor names the constructor global and the own property that receives the failed member name. The worker materializes and injects that real class, so `instanceof` works without hardcoding `tools` or `ToolCallError`; declarations with invalid or colliding globals fail before a worker spawns. +- **Binding rejection classes are request data** — an optional namespace descriptor names the constructor global and the own property that receives the failed member name. The worker materializes and injects that real class, so `instanceof` works without hardcoding `tools` or `ToolCallError`; declarations with invalid or colliding globals fail before a worker spawns. Error fields use module-captured property-definition intrinsics and null-prototype descriptors, so later model mutations cannot turn a rejected binding into a worker crash. - **Two independent budgets, because the peer is hostile** — `computeMs` meters the worker's MEASURED busy time (`worker.performance.eventLoopUtilization()` polling): a hot loop cannot hide behind a pending decoy dispatch, and a program awaiting a slow tool accrues nothing. `maxWallMs` backstops what busy time cannot see (awaiting a promise nobody resolves). Both funnel into `worker.terminate()`, which ends hot synchronous loops too; heap overflow surfaces as the worker's OOM exit (`kind: 'worker-exit'`). -- **Intermediate binding values are complete JSON** — binding arguments and resolutions undergo iterative lossless-JSON validation, including a native-constructor identity check captured before program execution so user-authored functions cannot impersonate plain-container prototypes. The worker also captures every structural and metering intrinsic used by this JSON boundary and bypasses mutable collection prototypes for private traversal state, so model mutations of global helpers cannot alter validation, wire transport, or byte accounting. Values flatten into a bounded-depth pre-order wire value for structured clone and rebuild iteratively on the other side. They have no byte, JavaScript call-stack, or nested structured-clone depth cap. They never enter the outer-output ledger or model context; provider/executor acquisition bounds and process/worker memory remain the limits. +- **Intermediate binding values are complete JSON** — binding arguments and resolutions undergo iterative lossless-JSON validation. Before program execution, the worker captures its own realm's plain-container prototype identities plus the native function-source check used only for foreign realms, so constructor-slot mutation and user-authored impostors cannot change container classification. It also captures every structural and metering intrinsic used by this JSON boundary, creates property descriptors without a prototype, and bypasses mutable collection prototypes for private traversal state; model mutations of globals, prototype methods, or descriptor-shaped `Object.prototype` fields therefore cannot alter validation, wire transport, or byte accounting. Values flatten into a bounded-depth pre-order wire value for structured clone and rebuild iteratively on the other side. They have no byte, JavaScript call-stack, or nested structured-clone depth cap. They never enter the outer-output ledger or model context; provider/executor acquisition bounds and process/worker memory remain the limits. - **Logs stream eagerly into one outer ledger** — console/stdout/stderr text crosses the port in emission order, so a timed-out or killed program still shows what it printed. The worker charges exact JSON-string bytes and preflights completion values and exception diagnostics against the remaining combined budget before posting them; a thrown million-byte stack therefore becomes the fixed `output-limit` diagnostic at the worker boundary. Native writes that bypass the patched stream slots arrive on pipes independent of the completion port, so the host repeats the ledger for those bytes and hostile forged traffic; settlement continues bounded pipe capture until worker termination completes before materializing the result. `maxOutputBytes` accounts the JSON serialization of the outer `logs` array plus the completion value or failure-message payload; fixed `CodeRunResult` field names, braces, the bounded error-kind tag, and later presentation whitespace are outside that variable-payload ledger. At or below the cap the exact value returns; a lossy completion is `invalid-output`, and a combined overflow is `output-limit` rather than a substituted inspected string. The failure retains the fitting captured prefix and later follows the normal outer `run_code` spill policy. - **Empty environment** — the worker gets `env: {}` and `execArgv: []`: no ambient credentials (stronger than the scrubbed-env rule for spawned commands) and no inherited loader flags. - **Dispose to quiescence** — teardown fails in-flight runs as `abort` and AWAITS each worker's exit before resolving. From 7809236236580edfea2e70a1f02a5c5104ee4222 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Thu, 23 Jul 2026 03:51:15 +0800 Subject: [PATCH 4/5] fix(code-runtime): capture worker error intrinsic --- .../code-runtime-worker/src/bootstrap.ts | 17 +++++++++-------- .../code-runtime-worker/tests/runtime.spec.ts | 2 +- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/packages/code-runtime/code-runtime-worker/src/bootstrap.ts b/packages/code-runtime/code-runtime-worker/src/bootstrap.ts index b654b04fec..aad4b7b2e6 100644 --- a/packages/code-runtime/code-runtime-worker/src/bootstrap.ts +++ b/packages/code-runtime/code-runtime-worker/src/bootstrap.ts @@ -10,6 +10,7 @@ import type { DoneMessage, ReplyMessage, WorkerBootData, WorkerToHost } from './ import { jsonStringBytesUpTo, jsonValueBytesUpTo, truncateJsonStringBytes } from './output-json.ts' import { decodeWorkerJson, encodeWorkerJson, snapshotCodeJsonValue } from './worker-json.ts' +const CapturedError = Error const capturedObjectCreate = Object.create const capturedObjectDefineProperty = Object.defineProperty @@ -77,7 +78,7 @@ export class LogBuffer { if (prefix.length > 0) { const prefixBytes = jsonStringBytesUpTo(prefix, availableBytes) /* v8 ignore next -- truncateJsonStringBytes guarantees the returned prefix fits. */ - if (prefixBytes === undefined) throw new Error('worker output ledger produced an oversized log prefix') + if (prefixBytes === undefined) throw new CapturedError('worker output ledger produced an oversized log prefix') this.bytes += prefixBytes + separatorBytes this.entries += 1 this.sink(prefix) @@ -219,7 +220,7 @@ export function prepareException( ): Omit { let message: string try { - const detail: unknown = error instanceof Error ? error.stack ?? error.message : error + const detail: unknown = error instanceof CapturedError ? error.stack ?? error.message : error message = typeof detail === 'string' ? detail : String(detail) } catch { message = 'program threw an unrenderable value' @@ -244,7 +245,7 @@ export type BindingErrorConstructor = new (memberName: string, message: string) function makeBindingErrorClass( descriptor: { name: string; memberNameProperty: string }, ): BindingErrorConstructor { - return class BindingCallError extends Error { + return class BindingCallError extends CapturedError { constructor(memberName: string, message: string) { super(message) defineBindingErrorField(this, 'name', descriptor.name) @@ -255,7 +256,7 @@ function makeBindingErrorClass( /** Create the namespace-specific rejection for one failed binding call. */ function bindingFailure(errorClass: BindingErrorConstructor | undefined, memberName: string, message: string): Error { - return errorClass ? new errorClass(memberName, message) : new Error(message) + return errorClass ? new errorClass(memberName, message) : new CapturedError(message) } /** @@ -289,10 +290,10 @@ export function wireReplies(port: BootstrapPort, pending: Map { Function.prototype.toString = () => 'mutated'; objectPrototype.get = () => undefined; objectPrototype.constructor = arrayPrototype.constructor = null; - globalThis.Array = globalThis.Buffer = globalThis.Function = globalThis.Number = globalThis.Object = globalThis.Reflect = globalThis.Set = globalThis.String = undefined; + globalThis.Array = globalThis.Buffer = globalThis.Error = globalThis.Function = globalThis.Number = globalThis.Object = globalThis.Reflect = globalThis.Set = globalThis.String = undefined; const echoed = await tools.echo({ request: ['€', 1] }); let failure; try { await tools.fail({}) } catch (error) { From 4aac1514e4e6e008feab8005b863d7b6a8e2fa12 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Thu, 23 Jul 2026 03:51:25 +0800 Subject: [PATCH 5/5] docs(code-runtime): cover captured error construction --- .../feature/2026-07-20-code-mode-typed-tool-returns.i18n.yaml | 4 ++-- .../feature/2026-07-20-code-mode-typed-tool-returns.md | 2 +- .../feature/2026-07-20-code-mode-typed-tool-returns.zh.md | 2 +- packages/code-runtime/code-runtime-worker/README.md | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.agents/notes/implemented/feature/2026-07-20-code-mode-typed-tool-returns.i18n.yaml b/.agents/notes/implemented/feature/2026-07-20-code-mode-typed-tool-returns.i18n.yaml index 76740443cb..cb91dbf5e9 100644 --- a/.agents/notes/implemented/feature/2026-07-20-code-mode-typed-tool-returns.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-20-code-mode-typed-tool-returns.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write -2026-07-20-code-mode-typed-tool-returns.md: 1f3baa076115b848fcde107f4ba4f7b3eb779d4e -2026-07-20-code-mode-typed-tool-returns.zh.md: e7d0fe6f7259cae2c51a25c1e6b5b5669b8f5f88 +2026-07-20-code-mode-typed-tool-returns.md: 5768ed69011cd4b0ee319fdd8bb3cf5b583e47ac +2026-07-20-code-mode-typed-tool-returns.zh.md: 12db374a66ee427e19895d109ee262b5b3e8a69f diff --git a/.agents/notes/implemented/feature/2026-07-20-code-mode-typed-tool-returns.md b/.agents/notes/implemented/feature/2026-07-20-code-mode-typed-tool-returns.md index 1f3baa0761..5768ed6901 100644 --- a/.agents/notes/implemented/feature/2026-07-20-code-mode-typed-tool-returns.md +++ b/.agents/notes/implemented/feature/2026-07-20-code-mode-typed-tool-returns.md @@ -51,7 +51,7 @@ declare const tools: { Before dispatch the bridge snapshots binding arguments as lossless JSON and snapshots the detached value again for an independent durable summary event. Host-side detachment, immutable execution, and output-schema projection all use iterative traversals rather than nested structured clone or recursive freezing. `undefined`, non-finite numbers, `-0`, sparse arrays, cycles, functions, and exotic objects reject that call before the tool runs. Successful dispatch returns `ToolExecutionResult.value`; Native `content`, metadata, and internal error information do not cross to the program. -Code Mode declares its rejection capability on the runtime request as `{ name: "ToolCallError", memberNameProperty: "toolName" }`. The runtime seam treats those names as data: the worker materializes and injects the actual constructor used for `tools` binding failures, so `error instanceof ToolCallError` works without making a generic runtime know about tools. The worker defines the error's public fields through module-captured property-definition intrinsics and null-prototype descriptors, so model mutations cannot replace the promised rejection with a worker failure. The error has the standard `Error` message plus the exact `toolName`; it deliberately omits `ToolFailure.info`, error codes, and Native content. This is an exception contract for control flow, not a failure union for programmatic classification. +Code Mode declares its rejection capability on the runtime request as `{ name: "ToolCallError", memberNameProperty: "toolName" }`. The runtime seam treats those names as data: the worker materializes and injects the actual constructor used for `tools` binding failures, so `error instanceof ToolCallError` works without making a generic runtime know about tools. The worker constructs failures and defines their public fields through module-captured error and property-definition intrinsics plus null-prototype descriptors, so model mutations cannot replace the promised rejection with a worker failure. The error has the standard `Error` message plus the exact `toolName`; it deliberately omits `ToolFailure.info`, error codes, and Native content. This is an exception contract for control flow, not a failure union for programmatic classification. Binding arguments and resolutions are revalidated as lossless JSON on both sides of the hostile worker protocol and have no byte cap. Before crossing through structured clone, each detached value is encoded as a flat pre-order token stream whose transport nesting is bounded; the receiver rebuilds it iteratively. Valid application nesting therefore has neither a JavaScript call-stack depth cap nor a platform-specific nested structured-clone limit. At module initialization the worker captures its own realm's `Array.prototype` and `Object.prototype` identities, the native function-source intrinsic used only to recognize foreign-realm plain-container prototypes, and every structural and metering intrinsic used by the JSON boundary. Property writes use null-prototype descriptors, while private array and set operations invoke captured methods without consulting mutable global or prototype slots. Model code can therefore replace helpers such as `Object.keys`, `Array.isArray`, collection methods, string methods, or `Buffer.byteLength`, rewrite intrinsic-prototype constructor slots, or add descriptor-shaped fields to `Object.prototype` without changing validation, wire transport, or byte accounting. The foreign-realm native function-source check still rejects user-authored constructors that imitate `Object` or `Array`. The dependency-light runtime seam names its structural equivalent `CodeJsonValue` so it need not depend on the session-owned canonical type; the generated SDK and tool API use `JsonValue`. Intermediate values are not prompt-truncated, context-spilled, or persisted. This preserves full acquired search, workflow, task, filesystem, and MCP values for programmatic filtering while leaving provider and executor acquisition limits truthful. diff --git a/.agents/notes/implemented/feature/2026-07-20-code-mode-typed-tool-returns.zh.md b/.agents/notes/implemented/feature/2026-07-20-code-mode-typed-tool-returns.zh.md index e7d0fe6f72..12db374a66 100644 --- a/.agents/notes/implemented/feature/2026-07-20-code-mode-typed-tool-returns.zh.md +++ b/.agents/notes/implemented/feature/2026-07-20-code-mode-typed-tool-returns.zh.md @@ -51,7 +51,7 @@ declare const tools: { 分发前,桥接层会把绑定参数快照为无损 JSON,再对分离后的值生成一次快照,供独立的持久摘要事件使用。宿主侧的值分离、执行数据的不可变处理与输出 schema 投影均采用迭代遍历,而不使用嵌套结构化克隆或递归冻结。`undefined`、非有限数、`-0`、稀疏数组、循环引用、函数和非普通对象都会使该调用在工具运行前 reject。成功分发会返回 `ToolExecutionResult.value`;Native `content`、元数据和内部错误信息不会传入程序。 -Code Mode 通过运行时请求中的 `{ name: "ToolCallError", memberNameProperty: "toolName" }` 声明其 reject 异常能力。运行时 seam 只把这些名称视为数据:worker 会动态生成并注入真正用于 `tools` 绑定失败的构造函数,因此无需让通用运行时了解工具,`error instanceof ToolCallError` 也能成立。worker 会通过模块初始化时捕获的属性定义内建方法和原型为 null 的属性描述符来定义该错误的公开字段,因此模型代码的修改不会把契约承诺的 reject 变成 worker 失败。该错误包含标准的 `Error` 消息和确切的 `toolName`,并有意省略 `ToolFailure.info`、错误代码与 Native 内容。这是一项用于控制流的异常契约,而不是供程序分类的失败联合。 +Code Mode 通过运行时请求中的 `{ name: "ToolCallError", memberNameProperty: "toolName" }` 声明其 reject 异常能力。运行时 seam 只把这些名称视为数据:worker 会动态生成并注入真正用于 `tools` 绑定失败的构造函数,因此无需让通用运行时了解工具,`error instanceof ToolCallError` 也能成立。worker 使用模块初始化时捕获的 Error 构造函数与属性定义内建方法,配合原型为 null 的属性描述符,构造失败对象并定义其公开字段,因此模型代码的修改不会把契约承诺的 reject 变成 worker 失败。该错误包含标准的 `Error` 消息和确切的 `toolName`,并有意省略 `ToolFailure.info`、错误代码与 Native 内容。这是一项用于控制流的异常契约,而不是供程序分类的失败联合。 绑定参数与绑定返回值会在不可信 worker 协议的两端重新校验为无损 JSON,且不设字节上限。每个分离后的值在通过结构化克隆跨越边界前,都会编码为扁平的前序 token 流,其传输结构的嵌套深度有界;接收方再以迭代方式重建该值。因此,有效应用数据的嵌套深度既不受 JavaScript 调用栈深度上限限制,也不受特定平台对嵌套结构化克隆施加的上限限制。模块初始化时,worker 会捕获自身 JavaScript 运行域中 `Array.prototype` 和 `Object.prototype` 的引用、仅用于识别其他运行域普通容器原型的原生函数源码内建方法,以及 JSON 边界用于结构处理和计量的全部内建方法。属性写入使用原型为 null 的属性描述符;内部的数组与集合操作直接调用捕获的方法,不会访问可变的全局或原型槽位。因此,即使模型代码替换 `Object.keys`、`Array.isArray`、集合方法、字符串方法或 `Buffer.byteLength` 等辅助方法,重写内建原型的构造函数槽位,或向 `Object.prototype` 添加形如属性描述符的字段,也不会改变校验、协议传输或字节计量。面向其他运行域的原生函数源码检查仍会拒绝由用户编写、冒充 `Object` 或 `Array` 的构造函数。为保持依赖轻量,运行时 seam 将结构等价类型命名为 `CodeJsonValue`,从而无需依赖会话侧拥有的规范类型;生成的 SDK 和工具 API 则使用 `JsonValue`。这些值不会经过提示词截断、上下文输出落盘或持久化。因此,程序可以完整筛选已经采集的搜索、工作流、任务、文件系统与 MCP 值,同时提供方和执行器的采集上限仍会实际生效。 diff --git a/packages/code-runtime/code-runtime-worker/README.md b/packages/code-runtime/code-runtime-worker/README.md index 34a2f6fc2c..1838919112 100644 --- a/packages/code-runtime/code-runtime-worker/README.md +++ b/packages/code-runtime/code-runtime-worker/README.md @@ -21,7 +21,7 @@ Every field is validated and defaulted; `maxOutputBytes` is a safe integer of at - **One fresh worker per run, no pooling** — a program's world dies with its worker: no cross-run state to log, state bleed unrepresentable, runs reconstructable from the session log alone. - **Type-strip host-side, in execution context** — the program is wrapped in an async-function shell, stripped with `node:module`'s `stripTypeScriptTypes` (erasable syntax only — `enum`/namespaces are rejected as a program `exception` and no worker spawns), and sliced back out byte-positioned; it then executes as the body of an `AsyncFunction`, so top-level `await`/`return` work. - **The port assumes a hostile peer** — model code can reach `parentPort` and forge traffic, so every inbound message is shape-validated and REBUILT before anything reads it (`null`, primitives, junk types, and malformed payloads drop without a throw; forged extra fields never ride along), the host answers each call id at most once, resolves binding names as OWN properties only (a forged `constructor` cannot walk a prototype chain), drops post-settlement replies, and validates every binding resolution and completion as lossless JSON. Forged `log`/`done` messages cannot bypass the outer cap: the host repeats validation and accounts every admitted log plus the completion or diagnostic. Worker-side namespaces are null-prototype with `defineProperty`, so `__proto__`-shaped binding names are ordinary keys. -- **Binding rejection classes are request data** — an optional namespace descriptor names the constructor global and the own property that receives the failed member name. The worker materializes and injects that real class, so `instanceof` works without hardcoding `tools` or `ToolCallError`; declarations with invalid or colliding globals fail before a worker spawns. Error fields use module-captured property-definition intrinsics and null-prototype descriptors, so later model mutations cannot turn a rejected binding into a worker crash. +- **Binding rejection classes are request data** — an optional namespace descriptor names the constructor global and the own property that receives the failed member name. The worker materializes and injects that real class, so `instanceof` works without hardcoding `tools` or `ToolCallError`; declarations with invalid or colliding globals fail before a worker spawns. Failures use module-captured error and property-definition intrinsics plus null-prototype descriptors, so later model mutations cannot turn a rejected binding into a worker crash. - **Two independent budgets, because the peer is hostile** — `computeMs` meters the worker's MEASURED busy time (`worker.performance.eventLoopUtilization()` polling): a hot loop cannot hide behind a pending decoy dispatch, and a program awaiting a slow tool accrues nothing. `maxWallMs` backstops what busy time cannot see (awaiting a promise nobody resolves). Both funnel into `worker.terminate()`, which ends hot synchronous loops too; heap overflow surfaces as the worker's OOM exit (`kind: 'worker-exit'`). - **Intermediate binding values are complete JSON** — binding arguments and resolutions undergo iterative lossless-JSON validation. Before program execution, the worker captures its own realm's plain-container prototype identities plus the native function-source check used only for foreign realms, so constructor-slot mutation and user-authored impostors cannot change container classification. It also captures every structural and metering intrinsic used by this JSON boundary, creates property descriptors without a prototype, and bypasses mutable collection prototypes for private traversal state; model mutations of globals, prototype methods, or descriptor-shaped `Object.prototype` fields therefore cannot alter validation, wire transport, or byte accounting. Values flatten into a bounded-depth pre-order wire value for structured clone and rebuild iteratively on the other side. They have no byte, JavaScript call-stack, or nested structured-clone depth cap. They never enter the outer-output ledger or model context; provider/executor acquisition bounds and process/worker memory remain the limits. - **Logs stream eagerly into one outer ledger** — console/stdout/stderr text crosses the port in emission order, so a timed-out or killed program still shows what it printed. The worker charges exact JSON-string bytes and preflights completion values and exception diagnostics against the remaining combined budget before posting them; a thrown million-byte stack therefore becomes the fixed `output-limit` diagnostic at the worker boundary. Native writes that bypass the patched stream slots arrive on pipes independent of the completion port, so the host repeats the ledger for those bytes and hostile forged traffic; settlement continues bounded pipe capture until worker termination completes before materializing the result. `maxOutputBytes` accounts the JSON serialization of the outer `logs` array plus the completion value or failure-message payload; fixed `CodeRunResult` field names, braces, the bounded error-kind tag, and later presentation whitespace are outside that variable-payload ledger. At or below the cap the exact value returns; a lossy completion is `invalid-output`, and a combined overflow is `output-limit` rather than a substituted inspected string. The failure retains the fitting captured prefix and later follows the normal outer `run_code` spill policy.