diff --git a/apps/web/tests/ptc-escalation.e2e.ts b/apps/web/tests/ptc-escalation.e2e.ts index f9dbd31eae..9f71da0890 100644 --- a/apps/web/tests/ptc-escalation.e2e.ts +++ b/apps/web/tests/ptc-escalation.e2e.ts @@ -19,6 +19,7 @@ const UI_EXPECTED = join(SNAPSHOT_DIR, 'approval.expected.md') const MODE = webSnapshotMode() const PROMPT = 'Use run_code with timeoutMs 120000 and direct Node filesystem access to create approved.txt in the working directory containing exactly "approved\\n". ' + 'Use await import("node:fs/promises") and writeFile; do not call nested tools. First attempt the write under the current read-only sandbox without escalation. ' + + 'In that first program, catch only filesystem errors with code EPERM, EACCES or EROFS and return exactly "EXPECTED_SANDBOX_DENIAL"; rethrow any other error. ' + 'If the sandbox denies it, explicitly retry the program with sandbox_permissions "workspace-write" and justification "Create the file requested by the user". ' + 'I will answer the approval prompt. After the file is written, reply DONE and stop.' @@ -67,6 +68,8 @@ describe('web e2e: PTC program sandbox escalation', () => { expect(calls.every(event => event.data.name === 'run_code')).toBe(true) expect(JSON.stringify(calls[0]?.data)).toContain('node:fs/promises') expect(JSON.stringify(calls.at(-1)?.data)).toContain('workspace-write') + const results = events.filter(event => event.type === 'tool/result') + expect(JSON.stringify(results[0]?.data)).toContain('EXPECTED_SANDBOX_DENIAL') const file = join(scaffold.workspaceCwd, 'workspace', 'approved.txt') await expect(readFile(file, 'utf8')).rejects.toMatchObject({ code: 'ENOENT' }) if (MODE !== 'record') { diff --git a/docs/subsystems/code-runtime.i18n.yaml b/docs/subsystems/code-runtime.i18n.yaml index 2d8b472beb..e19e14e90e 100644 --- a/docs/subsystems/code-runtime.i18n.yaml +++ b/docs/subsystems/code-runtime.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 docs/subsystems/code-runtime.md -code-runtime.md: 085d51e1c51fefe37b9b5913d1a2d1e69fbf5fc8 -code-runtime.zh.md: 887e0dabc6c1c5cf9faa0a9c47813cc0681ccbfa +code-runtime.md: 19eb28750cddc6b2660d74f24927f65888ba175e +code-runtime.zh.md: a8fa809846f5ecb57c1bf29fc30f53b42c5b4fdf diff --git a/docs/subsystems/code-runtime.md b/docs/subsystems/code-runtime.md index 085d51e1c5..19eb28750c 100644 --- a/docs/subsystems/code-runtime.md +++ b/docs/subsystems/code-runtime.md @@ -192,7 +192,7 @@ interface CodeRunFailure { ## The service -`CodeRuntime` is defined in [`src/index.ts`](../../packages/code-runtime/code-runtime/src/index.ts). `resolve(request)` returns complete execution inputs, and `run(spec)` executes them. `executionInstructions` supplies provider-owned usage guidance for consumer presentation. `language` selects supported program presentation; `isolation` describes the substrate without claiming security. `sandboxMode` advertises file-policy support, with `undefined` for a provider that does not supply confinement. Each implementation keeps program state separate between runs and terminates and awaits active executions during disposal. +`CodeRuntime` is defined in [`src/index.ts`](../../packages/code-runtime/code-runtime/src/index.ts). `resolve(request)` returns complete execution inputs, and `run(spec)` executes them. `executionInstructions` supplies provider-owned usage guidance for consumer presentation. `timeout` reports the configured elapsed-time default and maximum when per-call overrides are supported; `resolve` still validates and caps each request. `language` selects supported program presentation; `isolation` describes the substrate without claiming security. `sandboxMode` advertises file-policy support, with `undefined` for a provider that does not supply confinement. Each implementation keeps program state separate between runs and terminates and awaits active executions during disposal. diff --git a/docs/subsystems/code-runtime.zh.md b/docs/subsystems/code-runtime.zh.md index 887e0dabc6..a8fa809846 100644 --- a/docs/subsystems/code-runtime.zh.md +++ b/docs/subsystems/code-runtime.zh.md @@ -192,7 +192,7 @@ interface CodeRunFailure { ## 服务 -`CodeRuntime` 定义于 [`src/index.ts`](../../packages/code-runtime/code-runtime/src/index.ts)。`resolve(request)` 返回完整执行输入,`run(spec)` 执行它们。`executionInstructions` 提供由运行时拥有的使用说明,供消费方呈现。`language` 选择支持的程序呈现;`isolation` 描述执行基底,不作安全声明。`sandboxMode` 声明文件策略支持,不提供约束的提供方返回 `undefined`。每个实现将各次运行的程序状态分离,并在资源释放期间终止且等待活跃执行。 +`CodeRuntime` 定义于 [`src/index.ts`](../../packages/code-runtime/code-runtime/src/index.ts)。`resolve(request)` 返回完整执行输入,`run(spec)` 执行它们。`executionInstructions` 提供由运行时拥有的使用说明,供消费方呈现。支持逐次覆盖时,`timeout` 报告配置的经过时间默认值和上限;每次请求仍由 `resolve` 验证并截断。`language` 选择支持的程序呈现;`isolation` 描述执行基底,不作安全声明。`sandboxMode` 声明文件策略支持,不提供约束的提供方返回 `undefined`。每个实现将各次运行的程序状态分离,并在资源释放期间终止且等待活跃执行。 diff --git a/packages/code-runtime/code-runtime-node/tests/runtime.spec.ts b/packages/code-runtime/code-runtime-node/tests/runtime.spec.ts index 24dadf3bb3..d74a41ea6e 100644 --- a/packages/code-runtime/code-runtime-node/tests/runtime.spec.ts +++ b/packages/code-runtime/code-runtime-node/tests/runtime.spec.ts @@ -132,6 +132,7 @@ describe('Node program process', () => { it('uses the default deadline and caps explicit requests', async () => { const { runtime } = await setup() + expect(runtime.timeout).toEqual({ defaultMs: 120_000, maxMs: 600_000 }) expect(runtime.executionInstructions).toBe('Each call runs in a fresh Node process. Node APIs are available through await import(...). Relative paths use the supplied working directory; process.env starts empty. Direct filesystem access follows this execution\'s sandbox policy.') expect(runtime.resolve({ program: '', bindings: [] }).timeoutMs).toBe(120_000) expect(runtime.resolve({ program: '', bindings: [], timeoutMs: 900_000 }).timeoutMs).toBe(600_000) @@ -139,6 +140,12 @@ describe('Node program process', () => { await expect(runtime.run({ program: '', bindings: [], cwd: process.cwd(), timeoutMs: 1000 })).rejects.toThrow('sandbox policy') }) + it('advertises the capped default when the deployment maximum is lower', async () => { + const { runtime } = await setup({ timeoutMs: 2000, maxTimeoutMs: 1000 }) + expect(runtime.timeout).toEqual({ defaultMs: 1000, maxMs: 1000 }) + expect(runtime.timeout.defaultMs).toBe(runtime.resolve({ program: '', bindings: [] }).timeoutMs) + }) + it.each(['for (;;) {}', 'await new Promise(() => {})'])('ends an unfinished program at its elapsed deadline: %s', async (program) => { const { run } = await setup({ timeoutMs: 600, graceMs: 50 }) expect((await run({ program, bindings: [] })).error?.kind).toBe('timeout') diff --git a/packages/core/tools/src/ptc.ts b/packages/core/tools/src/ptc.ts index 97736b8c33..e36d2653cc 100644 --- a/packages/core/tools/src/ptc.ts +++ b/packages/core/tools/src/ptc.ts @@ -741,7 +741,8 @@ export function createRunCodeTool(registry: ToolRuntime, options: RunCodeBridgeO const runtime = peekRuntime() const instructions = runtime?.executionInstructions return resolveFlavor(peekRuntime).description - + (instructions ? ` ${instructions} The working directory is the Session's current directory.` : '') + + (instructions ? ` ${instructions}` : '') + + (runtime === undefined ? '' : " The working directory is the Session's current directory.") + escalationGuidance(runtime) }, }) diff --git a/packages/core/tools/tests/ptc.spec.ts b/packages/core/tools/tests/ptc.spec.ts index c3ef02bb53..ddc1ac9cb3 100644 --- a/packages/core/tools/tests/ptc.spec.ts +++ b/packages/core/tools/tests/ptc.spec.ts @@ -2034,6 +2034,7 @@ describe('per-program execution controls', () => { expect(JSON.stringify(schema.parameters)).not.toContain('timeoutMs') expect(JSON.stringify(schema.parameters)).not.toContain('sandbox_permissions') expect(schema.description).not.toContain('Programs start with an empty environment.') + expect(schema.description).toContain("The working directory is the Session's current directory.") const rejected = await python.tools.execute({ callId: ToolCallId('hidden-timeout'), name: RUN_CODE_NAME, signal: testToolSignal, arguments: { code: 'pass', description: 'Try unsupported timeout', timeoutMs: 5 },