Ver código fonte

feat: add the worker-thread code runtime (dsh-code-runtime-worker)

The shipped backend of the code-execution seam, per the Code Mode RFC's
worker-thread section: one fresh Node worker per run, executing the
model's TypeScript after a host-side type-strip (wrapped in an
async-function shell so top-level return/await parse, sliced back out
position-preserved), bindings bridged over the message port under
hostile-peer rules (own-property name lookup, at-most-once replies,
post-settlement drops, null-prototype namespaces), logs streamed eagerly
with an in-band truncation marker, and two independent budgets — measured
event-loop busy time (computeMs) plus a never-pausing wall ceiling
(maxWallMs) — funneling into worker.terminate(). env: {} and execArgv: []
keep the isolate hermetic; disposal aborts in-flight runs and awaits
worker exits.

The worker entry loads unbuilt via Node's native type stripping
(src/worker.ts, erasable-only) and ships built as a sibling tsdown bundle
(lib/worker.js); tests/built-lib.e2e.ts pins the built load path under
plain node and joins the built-artifact smoke gate. Unit suites cover the
bootstrap in-process (fake port) and the runtime over real workers,
per-file 100%.
Tianyi Cui 2 meses atrás
pai
commit
583704ac1d

+ 2 - 2
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.

+ 3 - 1
docs/capability-seams.md

@@ -44,6 +44,7 @@ flowchart LR
   pkg_hooks_codex["hooks-codex"]
   pkg_code_runtime["code-runtime"]
   svc_codeRuntime["ctx.codeRuntime<br/>Code-execution seam"]
+  pkg_code_runtime_worker["code-runtime-worker"]
   pkg_fs["fs"]
   svc_fs["ctx.fs<br/>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. |

+ 32 - 0
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`

+ 3 - 0
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) |

+ 1 - 1
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

+ 4 - 0
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"]

+ 1 - 1
packages/README.md

@@ -11,7 +11,7 @@ Packages are grouped by modular role at `packages/<group>/<pkg>/`. 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 |

+ 3 - 2
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.

+ 32 - 0
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).

+ 36 - 0
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"
+  }
+}

+ 257 - 0
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<number, PendingCall>): 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<WorkerBootData, 'namespaces'>,
+  port: BootstrapPort,
+  pending: Map<number, PendingCall>,
+  nextId: { value: number },
+): Record<string, unknown>[] {
+  return data.namespaces.map(({ global, names }) => {
+    const namespace = Object.create(null) as Record<string, unknown>
+    for (const name of names) {
+      Object.defineProperty(namespace, name, {
+        enumerable: true,
+        value: (args: unknown): Promise<unknown> => 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<void> {
+  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<number, PendingCall>()
+  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<unknown>
+    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)
+}

+ 350 - 0
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<Config>
+
+/**
+ * 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<void>
+}
+
+/**
+ * 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<Config> = 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<LiveRun>()
+  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<void> {
+    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<CodeRunResult> {
+    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<string, Record<string, CodeBindingFunction>> {
+    const bindings = new Map<string, Record<string, CodeBindingFunction>>()
+    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<string, Record<string, CodeBindingFunction>>,
+  ): Promise<CodeRunResult> {
+    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<CodeRunResult>((resolve) => {
+      let settled = false
+      const answered = new Set<number>()
+      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<void>((done) => { finishResolve = done })
+      const finish = (result: Omit<CodeRunResult, 'logs'>): 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

+ 65 - 0
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 }

+ 20 - 0
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 })

+ 214 - 0
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<number, PendingCall>()
+    wireReplies(port, pending)
+    const [tools] = makeNamespaces({ namespaces: [{ global: 'tools', names: ['__proto__', 'constructor', 'toString'] }] }, port, pending, { value: 1 }) as [Record<string, (args: unknown) => Promise<unknown>>]
+    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<number, PendingCall>()
+    const [tools] = makeNamespaces({ namespaces: [{ global: 'tools', names: ['x'] }] }, throwingPort, pending, { value: 1 }) as [Record<string, (args: unknown) => Promise<unknown>>]
+    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' })
+  })
+})

+ 55 - 0
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<number | null>(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' })
+  })
+})

+ 328 - 0
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<string, (args: unknown) => Promise<unknown>>) {
+  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<void>
+    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<CodeRunResult> = 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()
+  })
+})

+ 24 - 0
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"
+    }
+  ]
+}

+ 18 - 0
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,
+})

+ 13 - 0
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':

+ 15 - 0
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
 }
 

+ 1 - 1
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).',
   },

+ 1 - 0
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'],

+ 1 - 0
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" },

+ 1 - 0
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" },

+ 6 - 1
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