diff --git a/AGENTS.md b/AGENTS.md
index 2cd72aff60..c466a20f77 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -1,6 +1,6 @@
# AGENTS.md
-This is the monorepo of the DeepSeek Harness group; it hosts **DeepSeek Harness SDK**, a plugin-based SDK for building agent harnesses. The codebase is built on the vendored Cordis framework, microkernel-style: **everything is a plugin**. Read [docs/architecture.md](docs/architecture.md) before changing `packages/`; the documentation standard is [docs/AGENTS.md](docs/AGENTS.md).
+This is the DeepSeek Harness group's monorepo; it hosts **DeepSeek Harness SDK**, a plugin-based SDK for building agent harnesses. The codebase is built on the vendored Cordis framework, microkernel-style: **everything is a plugin**. Read [docs/architecture.md](docs/architecture.md) before changing `packages/`; the documentation standard is [docs/AGENTS.md](docs/AGENTS.md).
## Pre-release stance: foundation over blast radius
@@ -69,7 +69,7 @@ printf '%s\n' "$out" | grep -q '\[tool call\] echo({"text":"ci smoke"})'
printf '%s\n' "$out" | grep -q '\[tool result\] ECHO: CI SMOKE'
ls .sessions/_no-cwd/main-session-*.jsonl >/dev/null
rm -rf .sessions
-pnpm exec vitest run --config vitest.e2e.config.ts packages/ui/stdio-agent/tests/built-bin.e2e.ts packages/ui/acp-agent/tests/built-bin.e2e.ts
+pnpm exec vitest run --config vitest.e2e.config.ts packages/ui/stdio-agent/tests/built-bin.e2e.ts packages/ui/acp-agent/tests/built-bin.e2e.ts packages/code-runtime/code-runtime-worker/tests/built-lib.e2e.ts
```
`test:coverage`, not `test`, is the gating run ([why](docs/testing.md)); a sign-off counts only for commands actually run.
diff --git a/docs/capability-seams.md b/docs/capability-seams.md
index 339954c00f..12ce8033b9 100644
--- a/docs/capability-seams.md
+++ b/docs/capability-seams.md
@@ -44,6 +44,7 @@ flowchart LR
pkg_hooks_codex["hooks-codex"]
pkg_code_runtime["code-runtime"]
svc_codeRuntime["ctx.codeRuntime
Code-execution seam"]
+ pkg_code_runtime_worker["code-runtime-worker"]
pkg_fs["fs"]
svc_fs["ctx.fs
Filesystem provider seam"]
pkg_fs_local["fs-local"]
@@ -67,6 +68,7 @@ flowchart LR
pkg_bash --> svc_bash
pkg_bash_local --> svc_bash
pkg_code_runtime --> svc_codeRuntime
+ pkg_code_runtime_worker --> svc_codeRuntime
pkg_compact --> svc_compact
pkg_compact_basic --> svc_compact
pkg_fs --> svc_fs
@@ -137,7 +139,7 @@ flowchart LR
| `ctx.agents` | `core` | [`agent`](../packages/core/agent) | - | [`agent-loop`](../packages/core/agent-loop), [`acp`](../packages/ui/acp), [`subagent-inprocess`](../packages/subagent/subagent-inprocess), [`stdio-agent`](../packages/ui/stdio-agent), [`invariants`](../packages/support/invariants) | - | Owns live Agent handles and the create/resume factory seam. |
| `ctx.agentLoop` | `bundle` | [`agent-loop`](../packages/core/agent-loop) | - | [`agent-core`](../packages/core/agent-core) | - | The one concrete loop plugin; extension packages depend on dsh-agent events and services, not on this package. |
| `ctx.bash` | `seam` | [`bash`](../packages/bash/bash) | [`bash-local`](../packages/bash/bash-local) | [`tool-bash`](../packages/bash/tool-bash), [`hooks-claude`](../packages/hooks/hooks-claude), [`hooks-codex`](../packages/hooks/hooks-codex) | - | The model-facing bash tools and hook bridges consume this seam; sandboxed or remote executors can replace bash-local. |
-| `ctx.codeRuntime` | `seam` | [`code-runtime`](../packages/code-runtime/code-runtime) | - | - | - | Runs one model-written program against host-provided async bindings; backends differ by substrate and language (the Code Mode RFC specifies the worker-thread backend and the tool-registry consumer). |
+| `ctx.codeRuntime` | `seam` | [`code-runtime`](../packages/code-runtime/code-runtime) | [`code-runtime-worker`](../packages/code-runtime/code-runtime-worker) | - | - | Runs one model-written program against host-provided async bindings; backends differ by substrate and language (the Code Mode RFC specifies the worker-thread backend and the tool-registry consumer). |
| `ctx.fs` | `seam` | [`fs`](../packages/fs/fs) | [`fs-local`](../packages/fs/fs-local) | [`tool-fs`](../packages/fs/tool-fs) | [`fs-policy`](../packages/fs/fs-policy) | tool-fs executes read/write/edit through ctx.fs; fs-policy contributes observed-state checks through the fs/* event gate. |
| `ctx.compact` | `seam` | [`compact`](../packages/compact/compact) | [`compact-basic`](../packages/compact/compact-basic) | [`compact-basic`](../packages/compact/compact-basic) | - | The basic backend currently consumes the pre-step event directly; a model-facing compact tool remains deferred. |
| `ctx.subagents` | `seam` | [`subagent`](../packages/subagent/subagent) | [`subagent-spawn`](../packages/subagent/subagent-spawn), [`subagent-fork`](../packages/subagent/subagent-fork), [`subagent-acp`](../packages/subagent/subagent-acp), [`subagent-mock`](../packages/support/subagent-mock) | [`tool-subagent`](../packages/subagent/tool-subagent) | - | Providers implement transports; tool-subagent exposes one configured provider as a model-facing tool name. |
diff --git a/docs/config-catalog.md b/docs/config-catalog.md
index 73ab7d45dd..6510aea849 100644
--- a/docs/config-catalog.md
+++ b/docs/config-catalog.md
@@ -141,6 +141,38 @@ export interface Config {
Source: [`packages/bash/bash-local/src/index.ts:28`](../packages/bash/bash-local/src/index.ts)
+## `@deepseek-ai/dsh-code-runtime-worker`
+
+```ts config-catalog
+/** Plugin config: every execution cap, changeable from `cordis.yml` (no hardcoded tunables). */
+export interface Config {
+ /**
+ * Busy-time budget in milliseconds: the run fails with kind `'timeout'`
+ * once the worker's MEASURED event-loop active time
+ * (`worker.performance.eventLoopUtilization()`) exceeds this. Metering
+ * measured busy time — not wall time, not host-side pending-call
+ * bookkeeping — is what makes the budget both fair (a program awaiting a
+ * slow tool accrues nothing) and ungameable (a hot loop accrues whether
+ * or not a decoy dispatch is in flight).
+ */
+ computeMs?: number
+ /**
+ * Wall-clock ceiling in milliseconds; never pauses for anything. The
+ * backstop for what busy-time cannot see (a program awaiting a promise
+ * nobody will resolve).
+ */
+ maxWallMs?: number
+ /** Shared byte budget for captured log text (console + raw stream writes), truncation marked in-band. */
+ maxLogBytes?: number
+ /** Byte cap for the rendered completion value; an oversized or non-cloneable value crosses as a capped string rendering. */
+ maxValueBytes?: number
+ /** The worker's max old-generation heap in MiB (`resourceLimits`); overflow kills the worker, surfacing as kind `'worker-exit'`. */
+ maxOldGenerationSizeMb?: number
+}
+```
+
+Source: [`packages/code-runtime/code-runtime-worker/src/index.ts:27`](../packages/code-runtime/code-runtime-worker/src/index.ts)
+
## `@deepseek-ai/dsh-compact-basic`
Requires: `llm`
diff --git a/docs/module-graph.md b/docs/module-graph.md
index b12250d484..fa02da2430 100644
--- a/docs/module-graph.md
+++ b/docs/module-graph.md
@@ -80,9 +80,11 @@ flowchart TD
end
subgraph group_code_runtime["packages/code-runtime"]
pkg_code_runtime["code-runtime"]
+ pkg_code_runtime_worker["code-runtime-worker"]
end
pkg_llm --> pkg_brand
pkg_bash --> pkg_brand
+ pkg_code_runtime_worker --> pkg_code_runtime
pkg_llm_deepseek --> pkg_llm
pkg_llm_pi_ai --> pkg_llm
pkg_session --> pkg_brand
@@ -214,6 +216,7 @@ flowchart TD
| [`code-runtime`](../packages/code-runtime/code-runtime) | `code-runtime` | — |
| [`llm`](../packages/llm/llm) | `llm` | [`brand`](../packages/util/brand) |
| [`bash`](../packages/bash/bash) | `bash` | [`brand`](../packages/util/brand) |
+| [`code-runtime-worker`](../packages/code-runtime/code-runtime-worker) | `code-runtime` | [`code-runtime`](../packages/code-runtime/code-runtime) |
| [`llm-deepseek`](../packages/llm/llm-deepseek) | `llm` | [`llm`](../packages/llm/llm) |
| [`llm-pi-ai`](../packages/llm/llm-pi-ai) | `llm` | [`llm`](../packages/llm/llm) |
| [`session`](../packages/core/session) | `core` | [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm) |
diff --git a/docs/testing.md b/docs/testing.md
index d7ed14ecb9..5ec94b7502 100644
--- a/docs/testing.md
+++ b/docs/testing.md
@@ -25,7 +25,7 @@ An e2e assertion re-runs the command or re-reads the file externally; a keyword
- A plugin shipped via `cordis.yml` needs at least one test through the REAL Loader path: hand-built `ctx.plugin({...})` mounts bypass `unwrapExports` and cannot catch a broken export shape ([postmortem 0001](postmortem/0001-acp-default-export-drops-inject.md); export-shape rules in [packages/AGENTS.md](../packages/AGENTS.md)).
- A guard only guards if the regression actually fails it. For a plugin without `inject` (bundle/composition plugins), a Loader smoke stays green under a broken export shape — add an explicit `expect('default' in mod).toBe(false)` plus an `unwrapExports` round-trip assertion, and prove it: introduce the regression, watch red, revert.
-- "Real entry path" means the published artifact: the package `bin` points at built `lib/bin.js` under plain `node`, which tsx masks (settle races, module resolution, a swallowed load failure exiting 0). Keep the built-bin smokes green (`packages/ui/*/tests/built-bin.e2e.ts`), and assert a genuinely-missing config exits non-zero.
+- "Real entry path" means the published artifact: the package `bin` points at built `lib/bin.js` under plain `node`, which tsx masks (settle races, module resolution, a swallowed load failure exiting 0). The same applies to any non-index runtime entry the built package resolves at run time (the worker-thread runtime's sibling `lib/worker.js`). Keep the built-artifact smokes green (`packages/ui/*/tests/built-bin.e2e.ts`, `packages/code-runtime/code-runtime-worker/tests/built-lib.e2e.ts`), and assert a genuinely-missing config exits non-zero.
- An e2e that spawns an example from a temp cwd sets `TSX_TSCONFIG_PATH` to the repo-root tsconfig, or it silently falls back to stale built `lib/` ([examples/AGENTS.md](../examples/AGENTS.md)).
## When a snapshot test is required
diff --git a/knip.json b/knip.json
index 89cd2fffe7..b9dbd9bbff 100644
--- a/knip.json
+++ b/knip.json
@@ -25,6 +25,10 @@
"entry": ["tests/**/*.spec.ts", "tests/**/*.e2e.ts"],
"project": ["src/**/*.ts", "tests/**/*.ts"]
},
+ "packages/code-runtime/code-runtime-worker": {
+ "entry": ["tests/**/*.spec.ts", "tests/**/*.e2e.ts"],
+ "project": ["src/**/*.ts", "tests/**/*.ts"]
+ },
"packages/llm/llm-deepseek": {
"entry": ["tests/**/*.spec.ts", "tests/**/*.e2e.ts"],
"project": ["src/**/*.ts", "tests/**/*.ts"]
diff --git a/packages/README.md b/packages/README.md
index da75f740e8..8e918c4c15 100644
--- a/packages/README.md
+++ b/packages/README.md
@@ -11,7 +11,7 @@ Packages are grouped by modular role at `packages///`. The group dir
| [`core/`](core/README.md) | Product API spine: session, system-prompt, tools, agent, and the concrete loop | Product — stable surface |
| [`llm/`](llm/README.md) | LLM capability family: the abstract service + provider adapters | Product — stable surface |
| [`bash/`](bash/README.md) | Bash capability family: the executor seam, a local impl, and the model-facing tool | Product — stable surface |
-| [`code-runtime/`](code-runtime/README.md) | Code-execution capability family: the abstract runtime seam for model-written programs | Product — stable surface |
+| [`code-runtime/`](code-runtime/README.md) | Code-execution capability family: the abstract runtime seam for model-written programs + a worker-thread backend | Product — stable surface |
| [`fs/`](fs/README.md) | Filesystem capability family: the abstract seam, a local impl, and the model-facing file tools | Product — stable surface |
| [`compact/`](compact/README.md) | Compaction capability family: the abstract seam + a basic backend (tool deferred) | Product — stable surface |
| [`subagent/`](subagent/README.md) | Subagent capability family: the provider-registry seam and the model-facing delegation tool | Product — stable surface |
diff --git a/packages/code-runtime/README.md b/packages/code-runtime/README.md
index 578f3179c1..b98baa43e6 100644
--- a/packages/code-runtime/README.md
+++ b/packages/code-runtime/README.md
@@ -1,9 +1,10 @@
# code-runtime/ — code-execution capability family
-The code-execution capability seam (see [capability seams](../../docs/rfc/implemented/architecture/2026-06-13-capability-seams.md)): an abstract runtime interface for executing one model-written program against host-provided async bindings, capturing what it printed and returned. The consumer is the tool registry's Code Mode, and the first implementation (a Node worker-thread backend) is specified alongside it in the [Code Mode RFC](../../docs/rfc/proposed/feature/2026-06-15-code-mode.md). **Product** packages.
+The code-execution capability seam (see [capability seams](../../docs/rfc/implemented/architecture/2026-06-13-capability-seams.md)): an abstract runtime interface for executing one model-written program against host-provided async bindings, capturing what it printed and returned. The consumer is the tool registry's Code Mode, specified alongside the seam in the [Code Mode RFC](../../docs/rfc/proposed/feature/2026-06-15-code-mode.md). **Product** packages.
| Package | Role | ctx key |
|---|---|---|
| `code-runtime/` | Abstract code-execution seam (interface + vocabulary) | `ctx.codeRuntime` |
+| [`code-runtime-worker/`](code-runtime-worker/README.md) | Worker-thread backend: fresh worker per run, TypeScript via host-side type-strip, port-bridged bindings, budget/heap containment | registers `ctx.codeRuntime` |
-The interface lives at `code-runtime/code-runtime/`. Backends differ by execution substrate (worker thread, process, container) and by source language — both readonly descriptors on the service — and register `ctx.codeRuntime` without touching the interface or its consumer; that split is what makes a hardened backend a drop-in later.
+The interface lives at `code-runtime/code-runtime/`; the shipped backend at `code-runtime/code-runtime-worker/`. Backends differ by execution substrate (worker thread, process, container) and by source language — both readonly descriptors on the service — and register `ctx.codeRuntime` without touching the interface or its consumer; that split is what makes a hardened backend a drop-in later.
diff --git a/packages/code-runtime/code-runtime-worker/README.md b/packages/code-runtime/code-runtime-worker/README.md
new file mode 100644
index 0000000000..13bf992bb5
--- /dev/null
+++ b/packages/code-runtime/code-runtime-worker/README.md
@@ -0,0 +1,32 @@
+# @deepseek-ai/dsh-code-runtime-worker
+
+Worker-thread implementation of the [`@deepseek-ai/dsh-code-runtime`](../code-runtime/README.md) seam: `WorkerCodeRuntime` runs each program in ONE fresh Node `worker_threads.Worker` — TypeScript in, type-stripped host-side, bindings bridged over the message port, `{ value, logs, error? }` out. **Containment, not a security boundary**: trust posture is bash-equivalent by design (the [Code Mode RFC](../../../docs/rfc/proposed/feature/2026-06-15-code-mode.md) § Trust posture), with containment bash does not have — separate isolate, empty environment, heap cap, hard termination.
+
+## Config
+
+```yaml
+- id: code-runtime
+ name: '@deepseek-ai/dsh-code-runtime-worker'
+ config:
+ computeMs: 60000 # busy-time budget (measured event-loop active time)
+ maxWallMs: 600000 # wall-clock ceiling; never pauses for anything
+ maxLogBytes: 65536 # shared byte budget for captured log text
+ maxValueBytes: 32768 # rendered-completion-value cap
+ maxOldGenerationSizeMb: 512 # worker heap cap (resourceLimits)
+```
+
+Every field is validated (positive numbers) and defaulted; there are no other tunables.
+
+## Design
+
+- **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 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 converts a non-cloneable binding resolution into an error reply. Worker-side namespaces are null-prototype with `defineProperty`, so `__proto__`-shaped binding names are ordinary keys.
+- **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'`).
+- **Logs stream eagerly** — console/stdout/stderr entries cross the port as they happen, so a timed-out or killed program still shows what it printed; pipe bytes that bypass the patched streams are appended after, under the same byte budget.
+- **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.
+
+## The worker entry, unbuilt and built
+
+`worker.ts` is deliberately erasable-only TypeScript with type-only cross-package imports: unbuilt (vitest/tsx), the host spawns `src/worker.ts` directly and Node's native type stripping loads it; built, the entry ships as the sibling bundle `lib/worker.js` (its own tsdown entry). The built path is pinned by `tests/built-lib.e2e.ts`, the real-load-path guard from [docs/testing.md](../../../docs/testing.md).
diff --git a/packages/code-runtime/code-runtime-worker/package.json b/packages/code-runtime/code-runtime-worker/package.json
new file mode 100644
index 0000000000..85df6a0d78
--- /dev/null
+++ b/packages/code-runtime/code-runtime-worker/package.json
@@ -0,0 +1,36 @@
+{
+ "name": "@deepseek-ai/dsh-code-runtime-worker",
+ "description": "Worker-thread implementation of the DeepSeek Harness code-execution seam",
+ "version": "0.0.1",
+ "private": true,
+ "type": "module",
+ "main": "lib/index.js",
+ "types": "lib/types/index.d.ts",
+ "exports": {
+ ".": {
+ "types": "./lib/types/index.d.ts",
+ "default": "./lib/index.js"
+ },
+ "./src/*": "./src/*",
+ "./package.json": "./package.json"
+ },
+ "files": [
+ "lib/index.js",
+ "lib/worker.js",
+ "lib/types/**/*.d.ts",
+ "lib/types/**/*.d.ts.map",
+ "src"
+ ],
+ "license": "BSD-3-Clause",
+ "peerDependencies": {
+ "@deepseek-ai/dsh-code-runtime": "^0.0.1",
+ "cordis": "^4.0.0-rc.6"
+ },
+ "dependencies": {
+ "schemastery": "^3.18.0"
+ },
+ "devDependencies": {
+ "@deepseek-ai/dsh-code-runtime": "workspace:^",
+ "cordis": "^4.0.0-rc.6"
+ }
+}
diff --git a/packages/code-runtime/code-runtime-worker/src/bootstrap.ts b/packages/code-runtime/code-runtime-worker/src/bootstrap.ts
new file mode 100644
index 0000000000..06cef02c7b
--- /dev/null
+++ b/packages/code-runtime/code-runtime-worker/src/bootstrap.ts
@@ -0,0 +1,257 @@
+/**
+ * Worker-side execution logic, written as plain functions over an injected
+ * port so the unit suite can run every line IN-PROCESS against a fake port
+ * (a real worker thread is a separate V8 isolate the coverage provider
+ * cannot observe). The real worker entry (`worker.ts`) is a thin
+ * self-executing glue file over {@link runWorkerMain}, excluded from
+ * coverage the same way `bin.ts` entrypoints are, and exercised end-to-end
+ * by the integration tests that spawn real workers.
+ *
+ * @module @deepseek-ai/dsh-code-runtime-worker/src/bootstrap
+ */
+
+import { inspect } from 'node:util'
+import type { CodeLogEntry } from '@deepseek-ai/dsh-code-runtime'
+import type { DoneMessage, ReplyMessage, WorkerBootData, WorkerToHost } from './protocol.ts'
+
+/** The port surface the bootstrap needs — satisfied by `parentPort` and by the tests' fake. */
+export interface BootstrapPort {
+ postMessage(message: WorkerToHost): void
+ on(event: 'message', listener: (message: ReplyMessage) => void): void
+}
+
+/**
+ * A writable stream's `write` slot, as the bootstrap patches it (see
+ * {@link captureStreamWrites}). Method-typed so the real
+ * `process.stdout`/`process.stderr` (narrower chunk parameters) remain
+ * assignable.
+ */
+export interface PatchableStream {
+ write(chunk: unknown, ...rest: unknown[]): boolean
+}
+
+/**
+ * Ordered log capture under one shared byte budget, delivered to a sink as
+ * each entry lands (the real sink streams entries over the port eagerly, so
+ * captured output survives a mid-run termination). Once the budget is
+ * exhausted it emits exactly one in-band marker entry (on the `stderr`
+ * diagnostics channel) and silently drops everything after — the cap is a
+ * blast-radius bound, so "how much was lost" intentionally stays unmeasured.
+ */
+export class LogBuffer {
+ private remaining: number
+ private truncated = false
+ // Explicit fields, not constructor parameter properties: this module loads
+ // under Node's native strip-only mode, which rejects non-erasable syntax —
+ // and parameter properties are non-erasable.
+ private readonly maxBytes: number
+ private readonly sink: (entry: CodeLogEntry) => void
+
+ constructor(maxBytes: number, sink: (entry: CodeLogEntry) => void) {
+ this.maxBytes = maxBytes
+ this.sink = sink
+ this.remaining = maxBytes
+ }
+
+ /**
+ * Emit one entry to the sink, charging its text against the budget (drops + marks once exhausted).
+ * @param entry - the log entry to deliver.
+ */
+ push(entry: CodeLogEntry): void {
+ if (this.truncated) return
+ const cost = Buffer.byteLength(entry.text, 'utf8')
+ if (cost > this.remaining) {
+ this.truncated = true
+ this.sink({ source: 'stderr', text: `[dsh-code-runtime-worker] log capture truncated at ${this.maxBytes} bytes` })
+ return
+ }
+ this.remaining -= cost
+ this.sink(entry)
+ }
+}
+
+/** The five console methods the shim captures, in the seam's level vocabulary. */
+const CONSOLE_LEVELS = ['log', 'info', 'warn', 'error', 'debug'] as const
+
+/**
+ * A `console` replacement whose five leveled methods render their arguments
+ * `util.inspect`-style (matching real console formatting closely enough for
+ * a model to recognize its own output) into the buffer. Only these five
+ * exist — the program gets a deliberately small console, not Node's full
+ * surface.
+ * @param logs - the buffer every rendered line is pushed into.
+ * @returns the five-method console object handed to the program.
+ */
+export function makeConsoleShim(logs: LogBuffer): Record<(typeof CONSOLE_LEVELS)[number], (...args: unknown[]) => void> {
+ const render = (args: unknown[]): string =>
+ args.map(arg => typeof arg === 'string' ? arg : inspect(arg, INSPECT_OPTIONS)).join(' ')
+ const shim = Object.create(null) as Record<(typeof CONSOLE_LEVELS)[number], (...args: unknown[]) => void>
+ for (const level of CONSOLE_LEVELS) {
+ shim[level] = (...args: unknown[]) => { logs.push({ source: 'console', level, text: render(args) }) }
+ }
+ return shim
+}
+
+/**
+ * Redirect a stream's `write` into the log buffer (the program-visible
+ * `process.stdout`/`process.stderr` in the real worker), so raw writes land
+ * in emission order alongside console output instead of racing down a pipe.
+ * @param logs - the buffer captured writes are pushed into.
+ * @param stream - the stream whose `write` slot is patched.
+ * @param source - the log source the captured writes are attributed to.
+ * @returns the restore function (the in-process tests un-patch; the real
+ * worker never needs to).
+ */
+export function captureStreamWrites(logs: LogBuffer, stream: PatchableStream, source: 'stdout' | 'stderr'): () => void {
+ // The slot's VALUE is stored for restore and reassigned — never invoked
+ // detached, so the unbound-method concern does not apply.
+ // eslint-disable-next-line @typescript-eslint/unbound-method
+ const original = stream.write
+ stream.write = (chunk: unknown): boolean => {
+ logs.push({ source, text: typeof chunk === 'string' ? chunk : String(chunk) })
+ return true
+ }
+ return () => { stream.write = original }
+}
+
+/** Bounded inspect options: deep enough to be useful, bounded so a pathological value cannot explode the rendering. */
+const INSPECT_OPTIONS = { depth: 4, maxArrayLength: 100, maxStringLength: 10_000 } as const
+
+/**
+ * Prepare the program's completion value for the done message: a
+ * structured-clone-safe value whose rendering fits `maxValueBytes` crosses
+ * raw; anything else (non-cloneable, or oversized) is REPLACED by its
+ * bounded `util.inspect` rendering, truncated with an in-band marker — the
+ * seam contract's "a non-transferable value is replaced by a string
+ * rendering", extended to oversized ones so a huge return cannot flood the
+ * host.
+ * @param value - the program's completion value.
+ * @param maxValueBytes - the byte cap for the rendered value.
+ * @returns the done-message fragment: `{}` for `undefined`, else `{ value }`.
+ */
+export function prepareValue(value: unknown, maxValueBytes: number): { value?: unknown } {
+ if (value === undefined) return {}
+ const rendered = typeof value === 'string' ? value : inspect(value, INSPECT_OPTIONS)
+ let cloneable = true
+ try {
+ structuredClone(value)
+ } catch {
+ // Only the verdict matters: the value has parts structured clone rejects
+ // (functions, classes, …) and must cross as its rendering instead.
+ cloneable = false
+ }
+ if (cloneable && Buffer.byteLength(rendered, 'utf8') <= maxValueBytes) return { value }
+ const capped = rendered.length > maxValueBytes ? `${rendered.slice(0, maxValueBytes)}… [truncated]` : rendered
+ return { value: capped }
+}
+
+/** One awaited binding call's settlement handles, keyed by call id in the pending map. */
+export interface PendingCall {
+ resolve(value: unknown): void
+ reject(error: Error): void
+}
+
+/**
+ * Route host replies into the pending-call map: each reply settles its call
+ * at most once, and a reply for an unknown id (stray, or a duplicate answer
+ * to an id already settled) is ignored. Shared wiring between
+ * {@link runWorkerMain} and the tests that exercise {@link makeNamespaces}
+ * standalone.
+ * @param port - the port whose `message` events carry the replies.
+ * @param pending - the id-keyed map of unsettled binding calls.
+ */
+export function wireReplies(port: BootstrapPort, pending: Map): void {
+ port.on('message', (message: ReplyMessage) => {
+ const entry = pending.get(message.id)
+ if (!entry) return
+ pending.delete(message.id)
+ if (message.ok) entry.resolve(message.value)
+ else entry.reject(new Error(message.message))
+ })
+}
+
+/**
+ * Build the binding namespace objects the program sees: one null-prototype
+ * global per namespace, each declared name an own enumerable async function
+ * that bridges over the port (`__proto__`/`constructor`/`toString` are
+ * ordinary keys, never prototype collisions). A non-cloneable argument
+ * rejects that one call with a descriptive error; the host's reply (`ok`
+ * false) rejects it likewise, so a failed tool call surfaces in the program
+ * as an ordinary promise rejection.
+ * @param data - the boot payload's namespace declarations (globals + names).
+ * @param port - the port binding calls are posted to.
+ * @param pending - the id-keyed map each posted call parks its handles in.
+ * @param nextId - the shared mutable id counter (worker-issued correlation ids).
+ * @returns one namespace object per declaration, in declaration order.
+ */
+export function makeNamespaces(
+ data: Pick,
+ port: BootstrapPort,
+ pending: Map,
+ nextId: { value: number },
+): Record[] {
+ return data.namespaces.map(({ global, names }) => {
+ const namespace = Object.create(null) as Record
+ for (const name of names) {
+ Object.defineProperty(namespace, name, {
+ enumerable: true,
+ value: (args: unknown): Promise => new Promise((resolve, reject) => {
+ const id = nextId.value++
+ pending.set(id, { resolve, reject })
+ try {
+ port.postMessage({ type: 'call', id, global, name, args })
+ } catch (error: unknown) {
+ pending.delete(id)
+ reject(new Error(`binding arguments must be structured-cloneable: ${error instanceof Error ? error.message : String(error)}`))
+ }
+ }),
+ })
+ }
+ return namespace
+ })
+}
+
+/**
+ * Run one program to settlement and post the {@link DoneMessage}: wires the
+ * reply handler, materializes the namespaces and console shim, compiles the
+ * type-stripped body as an async function (top-level `await`/`return`
+ * work), and reports a thrown program error as the done message's `error`
+ * field. Exactly one done message is ever posted.
+ * @param port - the message port to the host (the real `parentPort`, or the tests' fake).
+ * @param data - the boot payload the host sent.
+ * @param streams - the stream objects whose `write` is captured (the real
+ * `process.stdout`/`process.stderr` in the worker; fakes in tests).
+ * @returns resolves after the done message is posted (the tests await it;
+ * the real entry lets the worker exit naturally).
+ */
+export async function runWorkerMain(
+ port: BootstrapPort,
+ data: WorkerBootData,
+ streams: { stdout: PatchableStream; stderr: PatchableStream },
+): Promise {
+ const logs = new LogBuffer(data.maxLogBytes, (entry) => { port.postMessage({ type: 'log', entry }) })
+ captureStreamWrites(logs, streams.stdout, 'stdout')
+ captureStreamWrites(logs, streams.stderr, 'stderr')
+
+ const pending = new Map()
+ wireReplies(port, pending)
+
+ const nextId = { value: 1 }
+ const namespaces = makeNamespaces(data, port, pending, nextId)
+ const consoleShim = makeConsoleShim(logs)
+
+ let done: DoneMessage
+ try {
+ // The async function constructor, reached through an instance because
+ // `AsyncFunction` is not a global. The program body is strict-mode.
+ /* v8 ignore next -- the arrow exists only to reach the AsyncFunction constructor; it is never invoked. */
+ const AsyncFunction = (async () => {}).constructor as new (...args: string[]) => (...fnArgs: unknown[]) => Promise
+ const fn = new AsyncFunction(...data.namespaces.map(namespace => namespace.global), 'console', `'use strict';\n${data.code}`)
+ const value = await fn(...namespaces, consoleShim)
+ done = { type: 'done', ...prepareValue(value, data.maxValueBytes) }
+ } catch (error: unknown) {
+ const message = error instanceof Error ? error.stack ?? error.message : String(error)
+ done = { type: 'done', error: { message } }
+ }
+ port.postMessage(done)
+}
diff --git a/packages/code-runtime/code-runtime-worker/src/index.ts b/packages/code-runtime/code-runtime-worker/src/index.ts
new file mode 100644
index 0000000000..dbd0a44966
--- /dev/null
+++ b/packages/code-runtime/code-runtime-worker/src/index.ts
@@ -0,0 +1,350 @@
+/**
+ * Worker-thread implementation of the code-execution seam: one fresh Node
+ * worker per run, executing the model's TypeScript after a host-side
+ * type-strip, with bindings bridged over the message port. Containment, not
+ * a security boundary (bash-equivalent trust — see the Code Mode RFC's
+ * trust-posture section): the worker gets an EMPTY environment, a heap cap,
+ * and two independent budgets — `computeMs` metered on the worker's
+ * measured event-loop busy time (a hot loop cannot hide behind a pending
+ * binding call) and a never-pausing `maxWallMs` ceiling — all funneling
+ * into `worker.terminate()`, which ends hot synchronous loops too.
+ *
+ * @module @deepseek-ai/dsh-code-runtime-worker
+ */
+
+import { Worker } from 'node:worker_threads'
+import { stripTypeScriptTypes } from 'node:module'
+import { Context } from 'cordis'
+import z from 'schemastery'
+import { CodeRuntime } from '@deepseek-ai/dsh-code-runtime'
+import type { CodeBindingFunction, CodeLogEntry, CodeRunFailure, CodeRunRequest, CodeRunResult } from '@deepseek-ai/dsh-code-runtime'
+import type { ReplyMessage, WorkerBootData, WorkerToHost } from './protocol.ts'
+
+export type { BootstrapPort, PatchableStream } from './bootstrap.ts'
+export type { CallMessage, DoneMessage, ReplyMessage, WorkerBootData, WorkerToHost } from './protocol.ts'
+
+/** Plugin config: every execution cap, changeable from `cordis.yml` (no hardcoded tunables). */
+export interface Config {
+ /**
+ * Busy-time budget in milliseconds: the run fails with kind `'timeout'`
+ * once the worker's MEASURED event-loop active time
+ * (`worker.performance.eventLoopUtilization()`) exceeds this. Metering
+ * measured busy time — not wall time, not host-side pending-call
+ * bookkeeping — is what makes the budget both fair (a program awaiting a
+ * slow tool accrues nothing) and ungameable (a hot loop accrues whether
+ * or not a decoy dispatch is in flight).
+ */
+ computeMs?: number
+ /**
+ * Wall-clock ceiling in milliseconds; never pauses for anything. The
+ * backstop for what busy-time cannot see (a program awaiting a promise
+ * nobody will resolve).
+ */
+ maxWallMs?: number
+ /** Shared byte budget for captured log text (console + raw stream writes), truncation marked in-band. */
+ maxLogBytes?: number
+ /** Byte cap for the rendered completion value; an oversized or non-cloneable value crosses as a capped string rendering. */
+ maxValueBytes?: number
+ /** The worker's max old-generation heap in MiB (`resourceLimits`); overflow kills the worker, surfacing as kind `'worker-exit'`. */
+ maxOldGenerationSizeMb?: number
+}
+
+/** {@link Config} after schemastery fills the defaults (every field present). */
+type ResolvedConfig = Required
+
+/**
+ * How often the host samples the worker's event-loop utilization for the
+ * `computeMs` budget. An internal cadence, not config: the only effect of
+ * the interval is budget-expiry granularity (a run can overshoot by up to
+ * one interval), and nothing a deployment could tune here improves that
+ * without burning host CPU.
+ */
+const ELU_POLL_INTERVAL_MS = 25
+
+/** ECMAScript reserved words that cannot be async-function parameter names — rejected as binding globals. */
+const RESERVED_WORDS = new Set([
+ 'await', 'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger', 'default', 'delete', 'do',
+ 'else', 'enum', 'export', 'extends', 'false', 'finally', 'for', 'function', 'if', 'import', 'in',
+ 'instanceof', 'new', 'null', 'return', 'super', 'switch', 'this', 'throw', 'true', 'try', 'typeof',
+ 'var', 'void', 'while', 'with', 'yield', 'let', 'static', 'implements', 'interface', 'package',
+ 'private', 'protected', 'public', 'arguments', 'eval',
+])
+
+/** Valid async-function parameter name (the binding global becomes one). */
+const IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/
+
+/**
+ * The shell a program is wrapped in for the type-strip, matching the
+ * grammatical context it will execute in (an async function body, where
+ * top-level `return` and `await` are legal — a bare module parse would
+ * reject the `return`). Strip mode is position-preserving (removed syntax
+ * becomes whitespace, nothing shifts), so the wrapper survives the strip
+ * byte-identical and the body slices back out with the model's own
+ * line/column positions intact.
+ */
+const STRIP_WRAP = { prefix: 'async function __dsh_program__() {\n', suffix: '\n}' } as const
+
+/** One in-flight run's host-side state, tracked for disposal. */
+interface LiveRun {
+ worker: Worker
+ settle(failure: CodeRunFailure): void
+ finished: Promise
+}
+
+/**
+ * The worker entry module. Source runs unbuilt (`src/worker.ts`, loadable
+ * directly on this repo's Node range via native type stripping — the file
+ * is erasable-only with type-only relative imports); the built package
+ * ships it as a sibling bundle (`lib/worker.js`, its own tsdown entry).
+ * The URL *pathname*'s extension says which world this module is in —
+ * pathname, because dev-time module runners (vitest) may suffix
+ * `import.meta.url` with a query string; relative resolution drops it.
+ */
+/* v8 ignore next -- the './worker.js' arm is the built-lib world, unreachable unbuilt by construction; the built-lib e2e pins it. */
+const WORKER_URL = new URL(new URL(import.meta.url).pathname.endsWith('.ts') ? './worker.ts' : './worker.js', import.meta.url)
+
+/** Render an unknown thrown value as a message, `Error` or not. */
+function messageOf(error: unknown): string {
+ return error instanceof Error ? error.message : String(error)
+}
+
+/**
+ * The shipped {@link CodeRuntime} backend (`ctx.codeRuntime`). Registers as
+ * the `codeRuntime` service; every cap comes from validated config. See the
+ * module doc for the containment model and the class JSDoc on the seam for
+ * the contract this implements (error-as-field, hostile-peer port,
+ * no cross-run state, dispose to quiescence).
+ */
+export class WorkerCodeRuntime extends CodeRuntime {
+ static Config: z = z.object({
+ computeMs: z.number().default(60_000),
+ maxWallMs: z.number().default(600_000),
+ maxLogBytes: z.number().default(65_536),
+ maxValueBytes: z.number().default(32_768),
+ maxOldGenerationSizeMb: z.number().default(512),
+ })
+
+ readonly language = 'typescript'
+ readonly isolation = 'worker-thread'
+
+ private readonly config: ResolvedConfig
+ private readonly live = new Set()
+ private disposed = false
+
+ constructor(ctx: Context, config: Config) {
+ super(ctx)
+ // Schemastery filled the defaults; the cast records that. Positivity is a
+ // semantic check the schema's plain number type does not carry.
+ this.config = config as ResolvedConfig
+ for (const [key, value] of Object.entries(this.config)) {
+ if (!(Number.isFinite(value) && value > 0)) throw new Error(`dsh-code-runtime-worker: config.${key} must be a positive number, got ${String(value)}`)
+ }
+ ctx.effect(() => () => this.teardown(), 'worker code-runtime teardown')
+ }
+
+ /**
+ * Dispose to quiescence: mark the service unusable, fail every in-flight
+ * run as aborted, and AWAIT each worker's exit so no worker outlives the
+ * fiber.
+ */
+ private async teardown(): Promise {
+ this.disposed = true
+ const runs = [...this.live]
+ for (const run of runs) run.settle({ kind: 'abort', message: 'runtime disposed' })
+ await Promise.all(runs.map(run => run.finished))
+ }
+
+ /**
+ * Execute one program in a fresh worker. Program outcomes — including a
+ * type-strip syntax error, which never spawns a worker — resolve with
+ * `result.error`; the method rejects only for seam misuse (a disposed
+ * runtime, an invalid binding namespace).
+ * @param request - the program, its bindings, and the abort signal.
+ * @returns the run's outcome per the seam contract.
+ */
+ async run(request: CodeRunRequest): Promise {
+ if (this.disposed) throw new Error('dsh-code-runtime-worker: run() after disposal')
+ const bindings = this.validateBindings(request)
+ if (request.signal?.aborted) {
+ return { logs: [], error: { kind: 'abort', message: String(request.signal.reason) } }
+ }
+
+ let code: string
+ try {
+ const stripped = stripTypeScriptTypes(STRIP_WRAP.prefix + request.program + STRIP_WRAP.suffix)
+ code = stripped.slice(STRIP_WRAP.prefix.length, stripped.length - STRIP_WRAP.suffix.length)
+ } catch (error: unknown) {
+ // A program that does not survive the type-strip (syntax error,
+ // non-erasable syntax like `enum`) is a program failure, reported the
+ // same way a thrown exception would be — and no worker ever spawns.
+ return { logs: [], error: { kind: 'exception', message: messageOf(error) } }
+ }
+
+ return await this.execute(request, code, bindings)
+ }
+
+ /** Reject (seam misuse) malformed binding namespaces: non-identifier or reserved globals, duplicates, and the `console` collision. */
+ private validateBindings(request: CodeRunRequest): Map> {
+ const bindings = new Map>()
+ for (const namespace of request.bindings) {
+ if (!IDENTIFIER.test(namespace.global) || RESERVED_WORDS.has(namespace.global)) {
+ throw new Error(`dsh-code-runtime-worker: binding global ${JSON.stringify(namespace.global)} is not a usable identifier`)
+ }
+ if (namespace.global === 'console' || bindings.has(namespace.global)) {
+ throw new Error(`dsh-code-runtime-worker: duplicate binding global ${JSON.stringify(namespace.global)}`)
+ }
+ bindings.set(namespace.global, namespace.functions)
+ }
+ return bindings
+ }
+
+ /** Spawn the worker for one validated, type-stripped run and drive it to settlement. */
+ private execute(
+ request: CodeRunRequest,
+ code: string,
+ bindings: Map>,
+ ): Promise {
+ const bootData: WorkerBootData = {
+ code,
+ namespaces: [...bindings].map(([global, functions]) => ({ global, names: Object.keys(functions) })),
+ maxLogBytes: this.config.maxLogBytes,
+ maxValueBytes: this.config.maxValueBytes,
+ }
+ const worker = new Worker(WORKER_URL, {
+ workerData: bootData,
+ // Model code gets NO ambient environment — stronger than the scrubbed
+ // env the defensive-patterns rule requires for spawned commands.
+ env: {},
+ // Hermetic flags too: without this the worker inherits the host
+ // process's execArgv (a test runner's or tsx's loader hooks), which a
+ // bare isolate with an empty environment cannot satisfy. The entry
+ // needs nothing beyond native type stripping, on this repo's whole
+ // Node range.
+ execArgv: [],
+ resourceLimits: { maxOldGenerationSizeMb: this.config.maxOldGenerationSizeMb },
+ // Backstop capture: the bootstrap patches JS-level writes into its own
+ // ordered buffer, so these pipes normally stay silent; anything that
+ // still arrives (native-level writes) is appended after the done logs.
+ stdout: true,
+ stderr: true,
+ })
+
+ return new Promise((resolve) => {
+ let settled = false
+ const answered = new Set()
+ const logs: CodeLogEntry[] = []
+ const strayLogs: CodeLogEntry[] = []
+ let strayBudget = this.config.maxLogBytes
+
+ const captureStray = (source: 'stdout' | 'stderr') => (chunk: Buffer) => {
+ if (settled || strayBudget <= 0) return
+ const text = chunk.toString('utf8').slice(0, strayBudget)
+ strayBudget -= Buffer.byteLength(text, 'utf8')
+ strayLogs.push({ source, text })
+ }
+ worker.stdout.on('data', captureStray('stdout'))
+ worker.stderr.on('data', captureStray('stderr'))
+
+ // Settlement: exactly one outcome wins; every path funnels through
+ // here, cleans up the timers/listeners, terminates the worker, and
+ // resolves only after the worker actually exited (quiescence). Logs
+ // streamed eagerly before the settlement are kept — a timed-out or
+ // killed program still shows the model what it printed.
+ let finishResolve!: () => void
+ const finished = new Promise((done) => { finishResolve = done })
+ const finish = (result: Omit): void => {
+ if (settled) return
+ settled = true
+ clearInterval(eluTimer)
+ clearTimeout(wallTimer)
+ request.signal?.removeEventListener('abort', onAbort)
+ this.live.delete(live)
+ void worker.terminate().then(() => {
+ finishResolve()
+ resolve({ ...result, logs: [...logs, ...strayLogs] })
+ })
+ }
+
+ const onDone = (message: WorkerToHost): void => {
+ if (message.type !== 'done') return
+ finish({
+ ...message.value !== undefined ? { value: message.value } : {},
+ ...message.error ? { error: { kind: 'exception' as const, message: message.error.message } } : {},
+ })
+ }
+
+ const onCall = (message: WorkerToHost): void => {
+ if (message.type !== 'call' || settled) return
+ // Hostile-peer rules: a duplicate id is ignored, an unknown name is
+ // answered with a failure, and a binding throw/reject becomes the
+ // program-side rejection — contained here, never a host crash.
+ if (answered.has(message.id)) return
+ answered.add(message.id)
+ const reply = (payload: ReplyMessage): void => {
+ if (settled) return
+ try {
+ worker.postMessage(payload)
+ } catch {
+ // The reply value failed structured clone; renegotiate as an error
+ // reply, which is always clone-plain. Nothing else throws here.
+ worker.postMessage({ type: 'reply', id: message.id, ok: false, message: 'binding resolution is not structured-cloneable' })
+ }
+ }
+ const record = bindings.get(message.global)
+ // Own-property lookup only: a forged name like 'constructor' or
+ // 'hasOwnProperty' must not walk the record's prototype chain and
+ // reach a callable the consumer never declared.
+ const fn = record && Object.hasOwn(record, message.name) ? record[message.name] : undefined
+ if (typeof fn !== 'function') {
+ reply({ type: 'reply', id: message.id, ok: false, message: `unknown binding ${JSON.stringify(`${message.global}.${message.name}`)}` })
+ return
+ }
+ void (async () => {
+ try {
+ reply({ type: 'reply', id: message.id, ok: true, value: await fn(message.args) })
+ } catch (error: unknown) {
+ reply({ type: 'reply', id: message.id, ok: false, message: messageOf(error) })
+ }
+ })()
+ }
+
+ worker.on('message', (message: WorkerToHost) => {
+ if (message.type === 'log' && !settled) logs.push(message.entry)
+ onCall(message)
+ onDone(message)
+ })
+ worker.on('error', (error: Error) => {
+ finish({ error: { kind: 'worker-exit', message: `worker error: ${error.message}` } })
+ })
+ worker.on('exit', (exitCode: number) => {
+ finish({ error: { kind: 'worker-exit', message: `worker exited with code ${exitCode} before completing` } })
+ })
+
+ // The compute budget reads the worker's own measured busy time, so a
+ // hot loop expires it no matter what dispatches are in flight, while a
+ // program idling on a slow binding accrues nothing.
+ const eluTimer = setInterval(() => {
+ const elu = worker.performance.eventLoopUtilization()
+ if (elu.active > this.config.computeMs) {
+ finish({ error: { kind: 'timeout', message: `compute budget exhausted (${this.config.computeMs}ms busy)` } })
+ }
+ }, ELU_POLL_INTERVAL_MS)
+ const wallTimer = setTimeout(() => {
+ finish({ error: { kind: 'timeout', message: `wall-clock ceiling reached (${this.config.maxWallMs}ms)` } })
+ }, this.config.maxWallMs)
+ const onAbort = (): void => {
+ finish({ error: { kind: 'abort', message: String(request.signal?.reason) } })
+ }
+ request.signal?.addEventListener('abort', onAbort, { once: true })
+
+ const live: LiveRun = {
+ worker,
+ finished,
+ settle: (failure: CodeRunFailure) => { finish({ error: failure }) },
+ }
+ this.live.add(live)
+ })
+ }
+}
+
+export default WorkerCodeRuntime
diff --git a/packages/code-runtime/code-runtime-worker/src/protocol.ts b/packages/code-runtime/code-runtime-worker/src/protocol.ts
new file mode 100644
index 0000000000..85d5113b82
--- /dev/null
+++ b/packages/code-runtime/code-runtime-worker/src/protocol.ts
@@ -0,0 +1,65 @@
+/**
+ * Wire protocol between the host runtime and the worker bootstrap. Everything
+ * crossing the message port is structured-clone-plain and versionless — both
+ * ends ship in this package, always at the same version. The host treats
+ * inbound traffic as HOSTILE (the worker runs model code, which can reach
+ * `parentPort` via `import('node:worker_threads')` and forge any of these
+ * shapes); the worker treats inbound traffic as trusted.
+ *
+ * @module @deepseek-ai/dsh-code-runtime-worker/src/protocol
+ */
+
+import type { CodeLogEntry } from '@deepseek-ai/dsh-code-runtime'
+
+/** What the host hands the worker at spawn, via `workerData`. */
+export interface WorkerBootData {
+ /** The type-stripped (plain JS) program body. */
+ code: string
+ /** Binding namespaces to materialize: the global name plus the function names (functions themselves stay host-side). */
+ namespaces: { global: string; names: string[] }[]
+ /** Shared byte budget for captured log text; exceeding it drops further entries after one in-band marker. */
+ maxLogBytes: number
+ /** Byte cap for the rendered completion value (see the value-preparation contract in bootstrap.ts). */
+ maxValueBytes: number
+}
+
+/** Worker → host: one bridged binding call. */
+export interface CallMessage {
+ type: 'call'
+ /** Worker-issued correlation id; the host answers each id at most once and ignores duplicates. */
+ id: number
+ /** The namespace global the call targets. */
+ global: string
+ /** The function name within the namespace. */
+ name: string
+ /** The single argument, structured-clone-plain. */
+ args: unknown
+}
+
+/** Worker → host: one captured log entry, streamed eagerly so output survives a mid-run termination (timeout, abort, OOM). */
+export interface LogMessage {
+ type: 'log'
+ entry: CodeLogEntry
+}
+
+/**
+ * Worker → host: the program settled. `error` carries a program exception
+ * (the only failure the bootstrap itself can report — budgets, aborts, and
+ * substrate death are observed host-side). `value` is present only on a
+ * clean completion that produced one (already size-capped and
+ * clone-safe per the bootstrap's value preparation). Logs are NOT carried
+ * here — they streamed eagerly as {@link LogMessage}s.
+ */
+export interface DoneMessage {
+ type: 'done'
+ value?: unknown
+ error?: { message: string }
+}
+
+/** Every message the worker sends. */
+export type WorkerToHost = CallMessage | LogMessage | DoneMessage
+
+/** Host → worker: the answer to one {@link CallMessage}. */
+export type ReplyMessage =
+ | { type: 'reply'; id: number; ok: true; value: unknown }
+ | { type: 'reply'; id: number; ok: false; message: string }
diff --git a/packages/code-runtime/code-runtime-worker/src/worker.ts b/packages/code-runtime/code-runtime-worker/src/worker.ts
new file mode 100644
index 0000000000..efaafdb038
--- /dev/null
+++ b/packages/code-runtime/code-runtime-worker/src/worker.ts
@@ -0,0 +1,20 @@
+/**
+ * The worker-thread entrypoint: self-executing glue over
+ * `bootstrap.ts`'s {@link runWorkerMain}, kept to the spawn wiring alone.
+ * Like `bin.ts` CLI entrypoints, this file executes only inside a spawned
+ * worker isolate — a place the coverage provider cannot observe — so it is
+ * excluded from the coverage gate while every line of actual logic lives in
+ * `bootstrap.ts`, unit-tested in-process; the real spawn path is pinned by
+ * the integration tests that run genuine workers.
+ *
+ * @module @deepseek-ai/dsh-code-runtime-worker/src/worker
+ */
+
+import { parentPort, workerData } from 'node:worker_threads'
+import { runWorkerMain } from './bootstrap.ts'
+import type { WorkerBootData } from './protocol.ts'
+
+// A worker always has a parent port; guard loudly rather than run detached.
+if (!parentPort) throw new Error('dsh-code-runtime-worker: worker entry loaded outside a worker thread')
+
+await runWorkerMain(parentPort, workerData as WorkerBootData, { stdout: process.stdout, stderr: process.stderr })
diff --git a/packages/code-runtime/code-runtime-worker/tests/bootstrap.spec.ts b/packages/code-runtime/code-runtime-worker/tests/bootstrap.spec.ts
new file mode 100644
index 0000000000..c4e5393634
--- /dev/null
+++ b/packages/code-runtime/code-runtime-worker/tests/bootstrap.spec.ts
@@ -0,0 +1,214 @@
+import { describe, expect, it } from 'vitest'
+import { EventEmitter } from 'node:events'
+import { LogBuffer, makeConsoleShim, makeNamespaces, captureStreamWrites, prepareValue, runWorkerMain, wireReplies } from '@deepseek-ai/dsh-code-runtime-worker/src/bootstrap.ts'
+import type { BootstrapPort, PatchableStream, PendingCall } from '@deepseek-ai/dsh-code-runtime-worker/src/bootstrap.ts'
+import type { ReplyMessage, WorkerToHost } from '@deepseek-ai/dsh-code-runtime-worker/src/protocol.ts'
+import type { CodeLogEntry } from '@deepseek-ai/dsh-code-runtime'
+
+/**
+ * An in-process stand-in for the worker's parentPort: the test plays the
+ * HOST side — inspect what the bootstrap posted, feed replies back — so
+ * every line of worker-side logic runs under coverage without spawning an
+ * isolate (real-worker behavior is pinned by runtime.spec.ts).
+ */
+class FakePort implements BootstrapPort {
+ sent: WorkerToHost[] = []
+ private readonly emitter = new EventEmitter()
+ /** Host-scripted responder; return undefined to leave the call pending. */
+ respond: (message: WorkerToHost) => ReplyMessage | undefined = () => undefined
+
+ postMessage(message: WorkerToHost): void {
+ this.sent.push(message)
+ const reply = this.respond(message)
+ if (reply) queueMicrotask(() => this.emitter.emit('message', reply))
+ }
+
+ on(event: 'message', listener: (message: ReplyMessage) => void): void {
+ this.emitter.on(event, listener)
+ }
+
+ deliver(message: ReplyMessage): void {
+ this.emitter.emit('message', message)
+ }
+
+ logs(): CodeLogEntry[] {
+ return this.sent.filter(message => message.type === 'log').map(message => message.entry)
+ }
+
+ done(): WorkerToHost | undefined {
+ return this.sent.find(message => message.type === 'done')
+ }
+}
+
+function fakeStreams(): { stdout: PatchableStream; stderr: PatchableStream } {
+ return { stdout: { write: () => true }, stderr: { write: () => true } }
+}
+
+const BOOT = { maxLogBytes: 65_536, maxValueBytes: 32_768 }
+
+describe('LogBuffer', () => {
+ it('streams entries to the sink until the byte budget, then emits one marker and drops the rest', () => {
+ const seen: CodeLogEntry[] = []
+ const buffer = new LogBuffer(10, entry => seen.push(entry))
+ buffer.push({ source: 'console', level: 'log', text: '12345' })
+ buffer.push({ source: 'console', level: 'log', text: '123456' })
+ buffer.push({ source: 'console', level: 'log', text: 'dropped' })
+ expect(seen.map(entry => entry.text)).toEqual([
+ '12345',
+ '[dsh-code-runtime-worker] log capture truncated at 10 bytes',
+ ])
+ })
+})
+
+describe('makeConsoleShim', () => {
+ it('captures the five levels and renders non-strings inspect-style', () => {
+ const seen: CodeLogEntry[] = []
+ const shim = makeConsoleShim(new LogBuffer(1_000, entry => seen.push(entry)))
+ shim.log('plain', { a: 1 })
+ shim.info('i')
+ shim.warn('w')
+ shim.error('e')
+ shim.debug('d')
+ expect(seen.map(entry => entry.level)).toEqual(['log', 'info', 'warn', 'error', 'debug'])
+ expect(seen[0]?.text).toBe('plain { a: 1 }')
+ expect(seen.every(entry => entry.source === 'console')).toBe(true)
+ })
+})
+
+describe('captureStreamWrites', () => {
+ it('redirects writes into the buffer and restores on request', () => {
+ const seen: CodeLogEntry[] = []
+ const buffer = new LogBuffer(1_000, entry => seen.push(entry))
+ let underlying = ''
+ const stream: PatchableStream = { write: (chunk: unknown) => { underlying += String(chunk); return true } }
+ const restore = captureStreamWrites(buffer, stream, 'stdout')
+ stream.write('captured', 'utf8')
+ stream.write(Buffer.from('bytes'))
+ restore()
+ stream.write('after')
+ expect(seen.map(entry => entry.text)).toEqual(['captured', 'bytes'])
+ expect(seen[0]).toMatchObject({ source: 'stdout' })
+ expect(underlying).toBe('after')
+ })
+})
+
+describe('prepareValue', () => {
+ it('omits undefined, passes small cloneable values raw', () => {
+ expect(prepareValue(undefined, 100)).toEqual({})
+ expect(prepareValue({ a: [1, 'two'] }, 100)).toEqual({ value: { a: [1, 'two'] } })
+ })
+
+ it('replaces a non-cloneable value with its rendering', () => {
+ const { value } = prepareValue({ fn: () => 1 }, 1_000)
+ expect(typeof value).toBe('string')
+ expect(value).toContain('fn')
+ })
+
+ it('replaces an oversized value with a truncation-marked capped rendering', () => {
+ const { value } = prepareValue('x'.repeat(50), 10)
+ expect(value).toBe(`${'x'.repeat(10)}… [truncated]`)
+ })
+})
+
+describe('makeNamespaces', () => {
+ it('exposes prototype-colliding names as ordinary own properties', async () => {
+ const port = new FakePort()
+ port.respond = message => message.type === 'call' ? { type: 'reply', id: message.id, ok: true, value: `${message.name}-ok` } : undefined
+ const pending = new Map()
+ wireReplies(port, pending)
+ const [tools] = makeNamespaces({ namespaces: [{ global: 'tools', names: ['__proto__', 'constructor', 'toString'] }] }, port, pending, { value: 1 }) as [Record Promise>]
+ expect(Object.getPrototypeOf(tools)).toBeNull()
+ await expect(tools['__proto__']?.({})).resolves.toBe('__proto__-ok')
+ await expect(tools['constructor']?.({})).resolves.toBe('constructor-ok')
+ await expect(tools['toString']?.({})).resolves.toBe('toString-ok')
+ })
+
+ it('rejects a non-cloneable argument without leaking the pending entry', async () => {
+ let firstCall = true
+ const throwingPort: BootstrapPort = {
+ // First call throws an Error (the real DataCloneError shape), the
+ // second a bare string — the rejection renders both.
+ postMessage: () => {
+ if (firstCall) { firstCall = false; throw new Error('DataCloneError-ish') }
+ throw 'raw-clone-failure'
+ },
+ on: () => {},
+ }
+ const pending = new Map()
+ const [tools] = makeNamespaces({ namespaces: [{ global: 'tools', names: ['x'] }] }, throwingPort, pending, { value: 1 }) as [Record Promise>]
+ await expect(tools.x?.(() => 1)).rejects.toThrow(/structured-cloneable: DataCloneError-ish/)
+ await expect(tools.x?.(() => 1)).rejects.toThrow(/structured-cloneable: raw-clone-failure/)
+ expect(pending.size).toBe(0)
+ })
+})
+
+describe('runWorkerMain', () => {
+ it('runs a program end-to-end: bindings, console, return value', async () => {
+ const port = new FakePort()
+ port.respond = message => message.type === 'call' ? { type: 'reply', id: message.id, ok: true, value: (message.args as { n: number }).n * 2 } : undefined
+ await runWorkerMain(port, {
+ ...BOOT,
+ code: 'const doubled = await tools.double({ n: 21 }); console.log("got", doubled); return { doubled };',
+ namespaces: [{ global: 'tools', names: ['double'] }],
+ }, fakeStreams())
+ expect(port.logs()).toEqual([{ source: 'console', level: 'log', text: 'got 42' }])
+ expect(port.done()).toEqual({ type: 'done', value: { doubled: 42 } })
+ })
+
+ it('reports a thrown program error on the done message', async () => {
+ const port = new FakePort()
+ await runWorkerMain(port, { ...BOOT, code: 'throw new Error("boom")', namespaces: [] }, fakeStreams())
+ const done = port.done()
+ expect(done?.type).toBe('done')
+ expect(done?.type === 'done' ? done.error?.message : undefined).toContain('boom')
+ expect(done?.type === 'done' ? done.value : undefined).toBeUndefined()
+ })
+
+ it('renders non-Error throws and stack-less Errors on the done message', async () => {
+ const rawPort = new FakePort()
+ await runWorkerMain(rawPort, { ...BOOT, code: 'throw "raw-throw"', namespaces: [] }, fakeStreams())
+ expect(rawPort.done()).toEqual({ type: 'done', error: { message: 'raw-throw' } })
+
+ const barePort = new FakePort()
+ await runWorkerMain(barePort, { ...BOOT, code: 'const e = new Error("bare"); e.stack = undefined; throw e', namespaces: [] }, fakeStreams())
+ expect(barePort.done()).toEqual({ type: 'done', error: { message: 'bare' } })
+ })
+
+ it('surfaces a host failure reply as a program-side rejection it can catch', async () => {
+ const port = new FakePort()
+ port.respond = message => message.type === 'call' ? { type: 'reply', id: message.id, ok: false, message: 'denied by host' } : undefined
+ await runWorkerMain(port, {
+ ...BOOT,
+ code: 'try { await tools.x({}) } catch (error) { return `caught: ${error.message}` }',
+ namespaces: [{ global: 'tools', names: ['x'] }],
+ }, fakeStreams())
+ expect(port.done()).toEqual({ type: 'done', value: 'caught: denied by host' })
+ })
+
+ it('ignores replies for unknown pending ids', async () => {
+ const port = new FakePort()
+ port.respond = (message) => {
+ if (message.type !== 'call') return undefined
+ // Deliver a stray reply first; the real one follows.
+ port.deliver({ type: 'reply', id: 9_999, ok: true, value: 'stray' })
+ return { type: 'reply', id: message.id, ok: true, value: 'real' }
+ }
+ await runWorkerMain(port, {
+ ...BOOT,
+ code: 'return await tools.x({})',
+ namespaces: [{ global: 'tools', names: ['x'] }],
+ }, fakeStreams())
+ expect(port.done()).toEqual({ type: 'done', value: 'real' })
+ })
+
+ it('captures raw stream writes through the patched process streams', async () => {
+ const port = new FakePort()
+ const streams = fakeStreams()
+ await runWorkerMain(port, { ...BOOT, code: 'return 1', namespaces: [] }, streams)
+ streams.stdout.write('never seen — already restored? no: patch persists in worker')
+ // The patch stays installed for the worker's lifetime; writes during the
+ // program landed in order. Here the program wrote nothing via streams, so
+ // only the post-run write above went through the patched slot.
+ expect(port.logs().at(-1)).toMatchObject({ source: 'stdout' })
+ })
+})
diff --git a/packages/code-runtime/code-runtime-worker/tests/built-lib.e2e.ts b/packages/code-runtime/code-runtime-worker/tests/built-lib.e2e.ts
new file mode 100644
index 0000000000..66ce1830b6
--- /dev/null
+++ b/packages/code-runtime/code-runtime-worker/tests/built-lib.e2e.ts
@@ -0,0 +1,55 @@
+import { spawn } from 'node:child_process'
+import { existsSync } from 'node:fs'
+import { join } from 'node:path'
+import { fileURLToPath } from 'node:url'
+import { describe, expect, it } from 'vitest'
+
+/**
+ * BUILT-ARTIFACT smoke for the published package (the real-load-path guard
+ * from docs/testing.md): the unit suite runs `src/` under vitest, where the
+ * worker entry resolves to `src/worker.ts` — a consumer runs `lib/index.js`
+ * under plain `node`, where it must resolve the sibling `lib/worker.js`
+ * bundle instead. This spawns plain `node` (NOT tsx) from inside the package
+ * directory and imports the package BY NAME, so resolution flows through the
+ * real `exports` map exactly as it would from a downstream install; the
+ * program exercises the type-strip, the worker spawn, the binding bridge,
+ * and log capture end-to-end through the built bundles.
+ *
+ * It build-gates: SKIPS when the built artifacts are absent (suite run
+ * without `pnpm run build`); CI runs it after the build step. KEYLESS — no
+ * model is involved.
+ */
+
+const pkgDir = fileURLToPath(new URL('..', import.meta.url))
+const built = ['lib/index.js', 'lib/worker.js'].every(file => existsSync(join(pkgDir, file)))
+ && existsSync(join(pkgDir, '../code-runtime/lib/index.js'))
+
+describe.skipIf(!built)('built lib real load path (plain node)', () => {
+ it('runs a TypeScript program with a binding through lib/index.js and its lib/worker.js entry', async () => {
+ const script = `
+ const { Context } = await import('cordis')
+ const { WorkerCodeRuntime } = await import('@deepseek-ai/dsh-code-runtime-worker')
+ const ctx = new Context()
+ await ctx.plugin(WorkerCodeRuntime, {})
+ const result = await ctx.codeRuntime.run({
+ program: 'const doubled: number = await tools.double({ n: 21 }); console.log("halfway", doubled); return doubled;',
+ bindings: [{ global: 'tools', functions: { double: async args => args.n * 2 } }],
+ })
+ console.log(JSON.stringify(result))
+ process.exit(0)
+ `
+ const child = spawn(process.execPath, ['--input-type=module', '-e', script], { cwd: pkgDir, stdio: ['ignore', 'pipe', 'pipe'] })
+ let stdout = ''
+ let stderr = ''
+ child.stdout.on('data', (chunk: Buffer) => { stdout += chunk.toString('utf8') })
+ child.stderr.on('data', (chunk: Buffer) => { stderr += chunk.toString('utf8') })
+ const exitCode = await new Promise(resolve => child.on('close', resolve))
+
+ expect(exitCode, `stderr:\n${stderr}`).toBe(0)
+ const lastLine = stdout.trim().split('\n').at(-1) ?? ''
+ const result = JSON.parse(lastLine) as { value?: unknown; logs: { source: string; level?: string; text: string }[]; error?: unknown }
+ expect(result.error).toBeUndefined()
+ expect(result.value).toBe(42)
+ expect(result.logs).toContainEqual({ source: 'console', level: 'log', text: 'halfway 42' })
+ })
+})
diff --git a/packages/code-runtime/code-runtime-worker/tests/runtime.spec.ts b/packages/code-runtime/code-runtime-worker/tests/runtime.spec.ts
new file mode 100644
index 0000000000..8edfdc6a41
--- /dev/null
+++ b/packages/code-runtime/code-runtime-worker/tests/runtime.spec.ts
@@ -0,0 +1,328 @@
+import { describe, expect, it } from 'vitest'
+import { Context } from 'cordis'
+import { WorkerCodeRuntime } from '@deepseek-ai/dsh-code-runtime-worker'
+import type { Config } from '@deepseek-ai/dsh-code-runtime-worker'
+import type { CodeRunResult } from '@deepseek-ai/dsh-code-runtime'
+
+/**
+ * Integration suite over REAL worker threads (no mocks — workers are cheap
+ * and local, per docs/testing.md's real-over-mock policy). Each test builds
+ * a fresh context so budgets can be tuned per case.
+ */
+async function setup(config: Config = {}) {
+ const ctx = new Context()
+ await ctx.plugin(WorkerCodeRuntime, config)
+ const runtime = ctx.codeRuntime as WorkerCodeRuntime
+ return { ctx, runtime }
+}
+
+/** Convenience: one namespace `tools` with the given functions. */
+function tools(functions: Record Promise>) {
+ return [{ global: 'tools', functions }]
+}
+
+describe('WorkerCodeRuntime — programs and bindings (real workers)', () => {
+ it('registers with the seam descriptors', async () => {
+ const { runtime } = await setup()
+ expect(runtime.language).toBe('typescript')
+ expect(runtime.isolation).toBe('worker-thread')
+ })
+
+ it('runs TypeScript (erasable syntax), captures console/stdout in order, returns the value', async () => {
+ const { runtime } = await setup()
+ const result = await runtime.run({
+ program: `
+ interface Point { x: number; y: number }
+ const p: Point = { x: 1, y: 2 } as Point;
+ console.log('point', p);
+ process.stdout.write('raw-out\\n');
+ console.warn('careful');
+ return p.x + p.y;
+ `,
+ bindings: [],
+ })
+ expect(result.error).toBeUndefined()
+ expect(result.value).toBe(3)
+ expect(result.logs.map(entry => [entry.source, entry.level ?? null])).toEqual([
+ ['console', 'log'],
+ ['stdout', null],
+ ['console', 'warn'],
+ ])
+ expect(result.logs[0]?.text).toBe('point { x: 1, y: 2 }')
+ })
+
+ it('bridges binding calls both ways and rejects the program-side call on a host rejection', async () => {
+ const { runtime } = await setup()
+ const calls: unknown[] = []
+ const result = await runtime.run({
+ program: `
+ const first = await tools.echo({ n: 1 });
+ let caught = '';
+ try { await tools.fail({}) } catch (error) { caught = error.message }
+ let caughtRaw = '';
+ try { await tools.failRaw({}) } catch (error) { caughtRaw = error.message }
+ return { first, caught, caughtRaw };
+ `,
+ bindings: tools({
+ echo: async (args) => { calls.push(args); return { echoed: args } },
+ fail: async () => { throw new Error('nope') },
+ // A non-Error throw: the host renders it, the program still catches.
+ failRaw: async () => { throw 'raw-nope' },
+ }),
+ })
+ expect(result.error).toBeUndefined()
+ expect(result.value).toEqual({ first: { echoed: { n: 1 } }, caught: 'nope', caughtRaw: 'raw-nope' })
+ expect(calls).toEqual([{ n: 1 }])
+ })
+
+ it('reports non-erasable syntax as an exception without spawning a worker', async () => {
+ const { runtime } = await setup()
+ const result = await runtime.run({ program: 'enum E { A }\nreturn 1', bindings: [] })
+ expect(result.error?.kind).toBe('exception')
+ expect(result.error?.message).toMatch(/enum|strip/i)
+ })
+
+ it('reports a runtime throw as an exception with the message', async () => {
+ const { runtime } = await setup()
+ const result = await runtime.run({ program: 'throw new Error("kaboom")', bindings: [] })
+ expect(result.error?.kind).toBe('exception')
+ expect(result.error?.message).toContain('kaboom')
+ })
+
+ it('gives the program an EMPTY environment', async () => {
+ const { runtime } = await setup()
+ const result = await runtime.run({ program: 'return JSON.stringify(process.env)', bindings: [] })
+ expect(result.value).toBe('{}')
+ })
+
+ it('replaces a non-cloneable return value with a string rendering', async () => {
+ const { runtime } = await setup()
+ const result = await runtime.run({ program: 'return { f: () => 1 }', bindings: [] })
+ expect(typeof result.value).toBe('string')
+ })
+
+ it('keeps logs streamed before a failure', async () => {
+ const { runtime } = await setup()
+ const result = await runtime.run({
+ program: 'console.log("before"); throw new Error("after-log")',
+ bindings: [],
+ })
+ expect(result.error?.kind).toBe('exception')
+ expect(result.logs.map(entry => entry.text)).toContain('before')
+ })
+})
+
+describe('WorkerCodeRuntime — budgets and containment (real workers)', () => {
+ it('ends a hot loop at the compute budget — including behind a pending decoy dispatch', async () => {
+ const { runtime } = await setup({ computeMs: 300, maxWallMs: 30_000 })
+ const result = await runtime.run({
+ // The decoy: fire a call at a never-resolving binding WITHOUT awaiting,
+ // then spin. Host-side pending-call bookkeeping would pause a naive
+ // budget here; measured busy time cannot be fooled.
+ program: 'void tools.slow({}); for (;;) {}',
+ bindings: tools({ slow: () => new Promise(() => {}) }),
+ })
+ expect(result.error?.kind).toBe('timeout')
+ expect(result.error?.message).toContain('compute budget')
+ }, 15_000)
+
+ it('does not charge time spent awaiting a slow binding against the compute budget', async () => {
+ const { runtime } = await setup({ computeMs: 250, maxWallMs: 30_000 })
+ const result = await runtime.run({
+ program: 'return await tools.slow({})',
+ bindings: tools({ slow: () => new Promise(resolve => setTimeout(() => { resolve('slow-done') }, 700)) }),
+ })
+ expect(result.error).toBeUndefined()
+ expect(result.value).toBe('slow-done')
+ }, 15_000)
+
+ it('ends an idle-forever run at the wall-clock ceiling', async () => {
+ const { runtime } = await setup({ computeMs: 30_000, maxWallMs: 400 })
+ const result = await runtime.run({
+ program: 'await tools.never({}); return 1',
+ bindings: tools({ never: () => new Promise(() => {}) }),
+ })
+ expect(result.error?.kind).toBe('timeout')
+ expect(result.error?.message).toContain('wall-clock ceiling')
+ }, 15_000)
+
+ it('reports an abort mid-run and stops the worker', async () => {
+ const { runtime } = await setup()
+ const controller = new AbortController()
+ setTimeout(() => { controller.abort('user-cancel') }, 150)
+ const result = await runtime.run({ program: 'for (;;) {}', bindings: [], signal: controller.signal })
+ expect(result.error).toEqual({ kind: 'abort', message: 'user-cancel' })
+ }, 15_000)
+
+ it('reports a pre-aborted signal without spawning', async () => {
+ const { runtime } = await setup()
+ const controller = new AbortController()
+ controller.abort('too-late')
+ const result = await runtime.run({ program: 'return 1', bindings: [], signal: controller.signal })
+ expect(result.error).toEqual({ kind: 'abort', message: 'too-late' })
+ })
+
+ it('drops a binding resolution that lands after the run settled', async () => {
+ const { runtime } = await setup()
+ const controller = new AbortController()
+ let replyDelivered!: Promise
+ const result = await runtime.run({
+ program: 'void tools.late({}); for (;;) {}',
+ bindings: tools({
+ // Anchored on invocation: abort 100ms after the call reaches the
+ // host, resolve 400ms after — by then the run has settled, so the
+ // resolution's reply hits the post-settlement drop.
+ late: () => new Promise((resolve) => {
+ setTimeout(() => { controller.abort('cancel-now') }, 100)
+ replyDelivered = new Promise(done => setTimeout(() => { resolve('too-late'); done() }, 400))
+ }),
+ }),
+ signal: controller.signal,
+ })
+ expect(result.error).toEqual({ kind: 'abort', message: 'cancel-now' })
+ // Let the late resolution actually fire so its reply executes instead of
+ // being cancelled with the test.
+ await replyDelivered
+ }, 15_000)
+
+ it('contains an OOM under resourceLimits as worker-exit, host process healthy', async () => {
+ const { runtime } = await setup({ maxOldGenerationSizeMb: 32 })
+ const result = await runtime.run({
+ program: 'const hog = []; for (;;) hog.push(new Array(1e6).fill(1));',
+ bindings: [],
+ })
+ expect(result.error?.kind).toBe('worker-exit')
+ // And the host is fine: run something else.
+ const after = await runtime.run({ program: 'return "alive"', bindings: [] })
+ expect(after.value).toBe('alive')
+ }, 30_000)
+
+ it('truncates runaway log output at the byte budget with an in-band marker', async () => {
+ const { runtime } = await setup({ maxLogBytes: 300 })
+ const result = await runtime.run({
+ program: 'for (let i = 0; i < 1000; i++) console.log("spam line", i); return 1',
+ bindings: [],
+ })
+ expect(result.logs.at(-1)?.text).toContain('truncated at 300 bytes')
+ const total = result.logs.reduce((sum, entry) => sum + Buffer.byteLength(entry.text, 'utf8'), 0)
+ expect(total).toBeLessThan(1_000)
+ })
+
+ it('caps an oversized return value with a truncation marker', async () => {
+ const { runtime } = await setup({ maxValueBytes: 64 })
+ const result = await runtime.run({ program: 'return "y".repeat(10_000)', bindings: [] })
+ expect(result.value).toBe(`${'y'.repeat(64)}… [truncated]`)
+ })
+
+ it('captures pipe writes that bypass the patched write slot as stray logs, capped by the same budget', async () => {
+ const { runtime } = await setup({ maxLogBytes: 4 })
+ const result = await runtime.run({
+ // The bootstrap patches the stream instance's own `write`; going
+ // through the prototype's slot reaches the real pipe underneath, so
+ // the bytes arrive host-side as stray data. The pauses keep the two
+ // writes in separate pipe chunks and let them land before settlement.
+ program: `
+ const write = (text) => Object.getPrototypeOf(process.stdout).write.call(process.stdout, text);
+ write('abcd');
+ await new Promise(resolve => setTimeout(resolve, 150));
+ write('ef');
+ await new Promise(resolve => setTimeout(resolve, 100));
+ return 1;
+ `,
+ bindings: [],
+ })
+ expect(result.error).toBeUndefined()
+ expect(result.logs).toContainEqual({ source: 'stdout', text: 'abcd' })
+ expect(result.logs.map(entry => entry.text)).not.toContain('ef')
+ }, 15_000)
+})
+
+describe('WorkerCodeRuntime — hostile programs (real workers)', () => {
+ it('survives forged port traffic: unknown binding names, duplicate ids, junk shapes', async () => {
+ const { runtime } = await setup()
+ const result = await runtime.run({
+ program: `
+ const { parentPort } = await import('node:worker_threads');
+ parentPort.postMessage({ type: 'call', id: 7777, global: 'tools', name: 'missing', args: {} });
+ parentPort.postMessage({ type: 'call', id: 7777, global: 'tools', name: 'missing', args: {} });
+ parentPort.postMessage({ type: 'call', id: 7778, global: 'tools', name: 'constructor', args: {} });
+ parentPort.postMessage({ type: 'junk' });
+ return await tools.real({});
+ `,
+ bindings: tools({ real: async () => 'still-works' }),
+ })
+ expect(result.error).toBeUndefined()
+ expect(result.value).toBe('still-works')
+ })
+
+ it('answers a binding whose resolution cannot be cloned with a failure reply', async () => {
+ const { runtime } = await setup()
+ const result = await runtime.run({
+ program: 'try { await tools.bad({}) } catch (error) { return error.message }',
+ bindings: tools({ bad: async () => (() => 1) }),
+ })
+ expect(result.value).toContain('not structured-cloneable')
+ })
+
+ it('exposes binding names that collide with Object.prototype as ordinary functions', async () => {
+ const { runtime } = await setup()
+ const result = await runtime.run({
+ program: 'return [await tools["__proto__"]({}), await tools["constructor"]({}), typeof tools["hasOwnProperty"]]',
+ // Computed keys: a literal `'__proto__': …` entry would SET the record's
+ // prototype instead of declaring a binding of that name.
+ bindings: tools({ ['__proto__']: async () => 'proto-ok', ['constructor']: async () => 'ctor-ok' }),
+ })
+ expect(result.value).toEqual(['proto-ok', 'ctor-ok', 'undefined'])
+ })
+})
+
+describe('WorkerCodeRuntime — seam misuse and lifecycle', () => {
+ it('rejects invalid binding globals loudly (identifier, reserved word, duplicate, console)', async () => {
+ const { runtime } = await setup()
+ const cases: [string, RegExp][] = [
+ ['not valid!', /not a usable identifier/],
+ ['await', /not a usable identifier/],
+ ['console', /duplicate binding global/],
+ ]
+ for (const [global, message] of cases) {
+ await expect(runtime.run({ program: 'return 1', bindings: [{ global, functions: {} }] })).rejects.toThrow(message)
+ }
+ await expect(runtime.run({
+ program: 'return 1',
+ bindings: [{ global: 'tools', functions: {} }, { global: 'tools', functions: {} }],
+ })).rejects.toThrow(/duplicate binding global/)
+ })
+
+ it('rejects config values that are not positive numbers', async () => {
+ const ctx = new Context()
+ await expect(ctx.plugin(WorkerCodeRuntime, { computeMs: -1 })).rejects.toThrow(/positive number/)
+ })
+
+ it('keeps runs isolated: no state survives from one run to the next', async () => {
+ const { runtime } = await setup()
+ await runtime.run({ program: 'globalThis.leak = "value"; return 1', bindings: [] })
+ const second = await runtime.run({ program: 'return typeof globalThis.leak', bindings: [] })
+ expect(second.value).toBe('undefined')
+ })
+
+ it('disposal aborts in-flight runs, awaits worker exit, and rejects later runs', async () => {
+ const ctx = new Context()
+ const fiber = await ctx.plugin(WorkerCodeRuntime)
+ const runtime = ctx.codeRuntime as WorkerCodeRuntime
+ const inflight: Promise = runtime.run({ program: 'for (;;) {}', bindings: [] })
+ // Give the worker a moment to actually start spinning.
+ await new Promise(resolve => setTimeout(resolve, 200))
+ await fiber.dispose()
+ const result = await inflight
+ expect(result.error).toEqual({ kind: 'abort', message: 'runtime disposed' })
+ await expect(runtime.run({ program: 'return 1', bindings: [] })).rejects.toThrow(/after disposal/)
+ }, 15_000)
+
+ it('removes ctx.codeRuntime when the providing fiber disposes (HMR safety)', async () => {
+ const ctx = new Context()
+ const fiber = await ctx.plugin(WorkerCodeRuntime)
+ expect(ctx.get('codeRuntime')).toBeInstanceOf(WorkerCodeRuntime)
+ await fiber.dispose()
+ expect(ctx.get('codeRuntime')).toBeUndefined()
+ })
+})
diff --git a/packages/code-runtime/code-runtime-worker/tsconfig.json b/packages/code-runtime/code-runtime-worker/tsconfig.json
new file mode 100644
index 0000000000..af962eda4f
--- /dev/null
+++ b/packages/code-runtime/code-runtime-worker/tsconfig.json
@@ -0,0 +1,24 @@
+{
+ "extends": "../../../tsconfig.base.json",
+ "compilerOptions": {
+ "rootDir": "src",
+ "outDir": "lib/types"
+ },
+ "include": [
+ "src"
+ ],
+ "references": [
+ {
+ "path": "../../../vendor/cosmokit"
+ },
+ {
+ "path": "../../../vendor/cordis"
+ },
+ {
+ "path": "../../../vendor/schemastery"
+ },
+ {
+ "path": "../code-runtime"
+ }
+ ]
+}
diff --git a/packages/code-runtime/code-runtime-worker/tsdown.config.ts b/packages/code-runtime/code-runtime-worker/tsdown.config.ts
new file mode 100644
index 0000000000..234ec4c6b9
--- /dev/null
+++ b/packages/code-runtime/code-runtime-worker/tsdown.config.ts
@@ -0,0 +1,18 @@
+import { defineConfig } from 'tsdown'
+
+/**
+ * Package-shape override (see the root tsdown.config.ts): besides the
+ * default lib/index.js bundle, the worker BOOTSTRAP ships as its own
+ * sibling entry — `new Worker(new URL('./worker.js', import.meta.url))`
+ * loads it as a file, so it cannot be part of the index bundle.
+ */
+export default defineConfig({
+ entry: ['lib/types/index.js', 'lib/types/worker.js'],
+ outDir: 'lib',
+ format: ['esm'],
+ platform: 'node',
+ target: 'es2024',
+ fixedExtension: false,
+ dts: false,
+ clean: false,
+})
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index ccf2f6978d..03a47d6866 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -133,6 +133,19 @@ importers:
specifier: ^4.0.0-rc.6
version: 4.0.0-rc.6(@cordisjs/plugin-include@1.0.4)(@cordisjs/plugin-loader@1.0.0-rc.4)
+ packages/code-runtime/code-runtime-worker:
+ dependencies:
+ schemastery:
+ specifier: ^3.18.0
+ version: 3.18.0
+ devDependencies:
+ '@deepseek-ai/dsh-code-runtime':
+ specifier: workspace:^
+ version: link:../code-runtime
+ cordis:
+ specifier: ^4.0.0-rc.6
+ version: 4.0.0-rc.6(@cordisjs/plugin-include@1.0.4)(@cordisjs/plugin-loader@1.0.0-rc.4)
+
packages/compact/compact:
devDependencies:
'@deepseek-ai/dsh-llm':
diff --git a/scripts/check-workspace-constraints.ts b/scripts/check-workspace-constraints.ts
index f579169ab0..4d7d0802c7 100644
--- a/scripts/check-workspace-constraints.ts
+++ b/scripts/check-workspace-constraints.ts
@@ -105,11 +105,26 @@ const dshBinPackageFiles = [
'src',
] as const
+// Packages that ship a worker-thread entry as a sibling runtime bundle
+// (lib/worker.js, its own tsdown entry): the bootstrap is loaded via
+// `new Worker(new URL('./worker.js', import.meta.url))`, so it cannot live
+// inside the index bundle and must be published alongside it.
+const workerEntryPackages = new Set(['@deepseek-ai/dsh-code-runtime-worker'])
+
+const dshWorkerPackageFiles = [
+ 'lib/index.js',
+ 'lib/worker.js',
+ 'lib/types/**/*.d.ts',
+ 'lib/types/**/*.d.ts.map',
+ 'src',
+] as const
+
function sameStringList(actual: readonly string[] | undefined, expected: readonly string[]): boolean {
return !!actual && actual.length === expected.length && actual.every((value, index) => value === expected[index])
}
function expectedDshPackageFiles(manifest: PackageManifest): readonly string[] {
+ if (manifest.name && workerEntryPackages.has(manifest.name)) return dshWorkerPackageFiles
return manifest.bin ? dshBinPackageFiles : dshPackageFiles
}
diff --git a/scripts/gen-doc-graphs.ts b/scripts/gen-doc-graphs.ts
index 6440a672be..2848578f49 100644
--- a/scripts/gen-doc-graphs.ts
+++ b/scripts/gen-doc-graphs.ts
@@ -154,7 +154,7 @@ const SERVICE_ROLES: ServiceRole[] = [
pkg: 'code-runtime',
title: 'Code-execution seam',
mode: 'seam',
- implementations: [],
+ implementations: ['code-runtime-worker'],
consumers: [],
note: 'Runs one model-written program against host-provided async bindings; backends differ by substrate and language (the Code Mode RFC specifies the worker-thread backend and the tool-registry consumer).',
},
diff --git a/scripts/run-gates.ts b/scripts/run-gates.ts
index 6c844f842a..9ab71c6b61 100644
--- a/scripts/run-gates.ts
+++ b/scripts/run-gates.ts
@@ -310,6 +310,7 @@ function builtBinSmokeGate(): Gate {
'vitest.e2e.config.ts',
'packages/ui/stdio-agent/tests/built-bin.e2e.ts',
'packages/ui/acp-agent/tests/built-bin.e2e.ts',
+ 'packages/code-runtime/code-runtime-worker/tests/built-lib.e2e.ts',
], {
label: 'built-bin smoke',
needs: ['build'],
diff --git a/tsconfig.build.json b/tsconfig.build.json
index 00739a361d..9040d83f2b 100644
--- a/tsconfig.build.json
+++ b/tsconfig.build.json
@@ -23,6 +23,7 @@
{ "path": "./packages/core/agent-core" },
{ "path": "./packages/bash/bash" },
{ "path": "./packages/code-runtime/code-runtime" },
+ { "path": "./packages/code-runtime/code-runtime-worker" },
{ "path": "./packages/compact/compact" },
{ "path": "./packages/compact/compact-basic" },
{ "path": "./packages/llm/llm-deepseek" },
diff --git a/tsconfig.json b/tsconfig.json
index 5d90943c1a..22b2a65a39 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -34,6 +34,7 @@
{ "path": "./packages/core/agent-core" },
{ "path": "./packages/bash/bash" },
{ "path": "./packages/code-runtime/code-runtime" },
+ { "path": "./packages/code-runtime/code-runtime-worker" },
{ "path": "./packages/llm/llm-deepseek" },
{ "path": "./packages/llm/llm-pi-ai" },
{ "path": "./packages/bash/bash-local" },
diff --git a/vitest.config.ts b/vitest.config.ts
index 4200b5c7f4..11d1454b08 100644
--- a/vitest.config.ts
+++ b/vitest.config.ts
@@ -31,7 +31,12 @@ export default defineConfig({
// can't import one without booting it, so they are driven by the keyless
// Loader-path smoke (a real subprocess) instead of the in-process unit
// suite — the same reason `examples/start.ts` sat out of coverage scope.
- exclude: ['packages/*/*/src/types.ts', 'packages/*/*/src/bin.ts'],
+ // `worker.ts` files are the same class as bin.ts: self-executing
+ // worker-thread entrypoints that only ever run inside a spawned isolate
+ // the v8 provider cannot observe. They stay thin glue over in-process-
+ // tested logic (bootstrap.ts) and are pinned by real-worker integration
+ // tests.
+ exclude: ['packages/*/*/src/types.ts', 'packages/*/*/src/bin.ts', 'packages/*/*/src/worker.ts'],
// 100% or it doesn't merge (docs/testing.md: excessive tests are welcome).
// Per-file so a well-covered big file can't subsidize a bare one.
// Every v8 ignore comment must carry a reason — see the quality-gates RFC