1
0
Эх сурвалжийг харах

Merge remote-tracking branch 'origin/master' into codex/simp-drop-assembled-section-order

Tianyi Cui 1 сар өмнө
parent
commit
dbae83d573
24 өөрчлөгдсөн 106 нэмэгдсэн , 173 устгасан
  1. 1 1
      docs/config-catalog.md
  2. 1 1
      docs/cordis-catalog/services.md
  3. 3 14
      docs/core-data-structures/code-runtime.md
  4. 1 1
      examples/acp-agent/tests/snapshots/advanced-toolchain/session.jsonl
  5. 1 1
      examples/acp-agent/tests/snapshots/both-mode-turn/session.jsonl
  6. 1 1
      examples/acp-agent/tests/snapshots/code-mode-turn/session.jsonl
  7. 3 1
      packages/code-runtime/code-runtime-worker/README.md
  8. 0 1
      packages/code-runtime/code-runtime-worker/package.json
  9. 19 21
      packages/code-runtime/code-runtime-worker/src/bootstrap.ts
  10. 14 33
      packages/code-runtime/code-runtime-worker/src/index.ts
  11. 4 6
      packages/code-runtime/code-runtime-worker/src/protocol.ts
  12. 23 27
      packages/code-runtime/code-runtime-worker/tests/bootstrap.spec.ts
  13. 2 2
      packages/code-runtime/code-runtime-worker/tests/built-lib.e2e.ts
  14. 14 22
      packages/code-runtime/code-runtime-worker/tests/runtime.spec.ts
  15. 1 1
      packages/code-runtime/code-runtime/README.md
  16. 0 1
      packages/code-runtime/code-runtime/src/index.ts
  17. 2 16
      packages/code-runtime/code-runtime/src/types.ts
  18. 1 1
      packages/code-runtime/code-runtime/tests/service.spec.ts
  19. 1 5
      packages/cordis/tool-cordis/src/api-catalog.ts
  20. 5 6
      packages/core/tools/src/code-mode.ts
  21. 6 5
      packages/core/tools/tests/code-mode.spec.ts
  22. 2 4
      scripts/snapshots/python-sdk-single-exe/advanced/result.json
  23. 1 1
      scripts/snapshots/python-sdk-single-exe/advanced/session.jsonl
  24. 0 1
      scripts/type-equiv.manifest.json

+ 1 - 1
docs/config-catalog.md

@@ -203,7 +203,7 @@ export interface Config {
 }
 ```
 
-Source: [`packages/code-runtime/code-runtime-worker/src/index.ts:24`](../packages/code-runtime/code-runtime-worker/src/index.ts)
+Source: [`packages/code-runtime/code-runtime-worker/src/index.ts:21`](../packages/code-runtime/code-runtime-worker/src/index.ts)
 
 ## `@deepseek-ai/dsh-compact-basic`
 

+ 1 - 1
docs/cordis-catalog/services.md

@@ -82,7 +82,7 @@ abstract run(request: CodeRunRequest): Promise<CodeRunResult>
 
 Types: [CodeRunRequest](../core-data-structures/code-runtime.md) · [CodeRunResult](../core-data-structures/code-runtime.md)
 
-Source: [`packages/code-runtime/code-runtime/src/index.ts:31`](../../packages/code-runtime/code-runtime/src/index.ts)
+Source: [`packages/code-runtime/code-runtime/src/index.ts:30`](../../packages/code-runtime/code-runtime/src/index.ts)
 
 ## `ctx.compact` — `CompactService` (abstract seam)
 

+ 3 - 14
docs/core-data-structures/code-runtime.md

@@ -39,8 +39,8 @@ interface CodeRunResult {
    * or value-less run leaves this absent.
    */
   value?: unknown
-  /** Everything the program emitted, in order (capped by the implementation). */
-  logs: CodeLogEntry[]
+  /** Text the program emitted, in order (capped by the implementation). */
+  logs: string[]
   /** Present iff the run failed; see {@link CodeRunFailure} for the taxonomy. */
   error?: CodeRunFailure
 }
@@ -65,18 +65,7 @@ type CodeBindingFunction = (args: unknown) => Promise<unknown>
 
 ## Captured output and the failure taxonomy
 
-Logs arrive in emission order, attributed to their channel (the runtime's `console` shim, or stray writes to the underlying streams):
-
-```ts type-equiv
-interface CodeLogEntry {
-  /** Which channel produced the text. */
-  source: 'console' | 'stdout' | 'stderr'
-  /** The console method used; present only when `source` is `'console'`. */
-  level?: 'log' | 'info' | 'warn' | 'error' | 'debug'
-  /** The captured text (possibly truncated by the implementation's caps, marked in-band). */
-  text: string
-}
-```
+Logs are plain strings in emission order. The runtime captures the program's console and stream output, but channel and console-method metadata are not part of the seam because consumers render only the text. Implementations cap the aggregate output and mark truncation in-band.
 
 Failure kinds are **orthogonal outcomes reported independently** (per [defensive-patterns](../defensive-patterns.md)): a budget expiry is not an exception, an abort is not a timeout, and a substrate death (e.g. OOM) is neither:
 

+ 1 - 1
examples/acp-agent/tests/snapshots/advanced-toolchain/session.jsonl

@@ -21,7 +21,7 @@
 {"type":"assistant/message","seq":19,"time":1783957884490,"data":{"turn":1,"step":2,"content":[{"type":"tool-call","id":"advanced-code","name":"run_code","arguments":"{\"code\":\"return await tools.cordis_inspect({ what: 'dynamic' })\"}"}],"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[14,15,16,17,18],"surfaceOp":"append"}
 {"type":"tool/call","seq":20,"time":1783957884490,"data":{"turn":1,"step":2,"callId":"advanced-code","name":"run_code","arguments":"{\"code\":\"return await tools.cordis_inspect({ what: 'dynamic' })\"}"}}
 {"type":"tool/code-dispatch","seq":21,"time":1783957884560,"data":{"parentCallId":"advanced-code","subCallId":"advanced-code:code:1","name":"cordis_inspect","arguments":{"what":"dynamic"},"isError":false,"resultSummary":"## dynamic\n- dyn-1: snapshot-marker [active]"}}
-{"type":"tool/result","seq":22,"time":1783957884561,"data":{"turn":1,"step":2,"callId":"advanced-code","content":[{"type":"text","text":"## dynamic\n- dyn-1: snapshot-marker [active]"}],"isError":false,"meta":{"logs":[],"dispatches":1}},"sourceEventSeqs":[20],"surfaceOp":"append"}
+{"type":"tool/result","seq":22,"time":1783957884561,"data":{"turn":1,"step":2,"callId":"advanced-code","content":[{"type":"text","text":"## dynamic\n- dyn-1: snapshot-marker [active]"}],"isError":false,"meta":{"logs":[]}},"sourceEventSeqs":[20],"surfaceOp":"append"}
 {"type":"step/end","seq":23,"time":1783957884561,"data":{"turn":1,"step":2}}
 {"type":"step/start","seq":24,"time":1783957884562,"data":{"turn":1,"step":3}}
 {"type":"assistant/chunk","seq":25,"time":1783950000026,"data":{"turn":1,"step":3,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}

+ 1 - 1
examples/acp-agent/tests/snapshots/both-mode-turn/session.jsonl

@@ -87,7 +87,7 @@
 {"type":"assistant/message","seq":85,"time":1783611775503,"data":{"turn":1,"step":1,"content":[{"type":"reasoning","text":"The user wants me to use the `run_code` tool to execute a program that calls `tools.bash` with the command `echo BOTH_OK` and returns its output."},{"type":"tool-call","id":"call_00_AZFzvUwuC4vAUoICrfke5147","name":"run_code","arguments":"{\"code\": \"const result = await tools.bash({ command: \\\"echo BOTH_OK\\\", description: \\\"Echo BOTH_OK\\\" });\\nreturn result;\"}"}],"usage":{"inputTokens":5530,"outputTokens":108,"cacheReadTokens":0,"reasoningTokens":37}},"sourceEventSeqs":[4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84],"surfaceOp":"append"}
 {"type":"tool/call","seq":86,"time":1783611775504,"data":{"turn":1,"step":1,"callId":"call_00_AZFzvUwuC4vAUoICrfke5147","name":"run_code","arguments":"{\"code\": \"const result = await tools.bash({ command: \\\"echo BOTH_OK\\\", description: \\\"Echo BOTH_OK\\\" });\\nreturn result;\"}"}}
 {"type":"tool/code-dispatch","seq":87,"time":1783611775590,"data":{"parentCallId":"call_00_AZFzvUwuC4vAUoICrfke5147","subCallId":"call_00_AZFzvUwuC4vAUoICrfke5147:code:1","name":"bash","arguments":{"command":"echo BOTH_OK","description":"Echo BOTH_OK"},"isError":false,"resultSummary":"BOTH_OK\n"}}
-{"type":"tool/result","seq":88,"time":1783611775592,"data":{"turn":1,"step":1,"callId":"call_00_AZFzvUwuC4vAUoICrfke5147","content":[{"type":"text","text":"BOTH_OK\n"}],"isError":false,"meta":{"logs":[],"dispatches":1}},"sourceEventSeqs":[86],"surfaceOp":"append"}
+{"type":"tool/result","seq":88,"time":1783611775592,"data":{"turn":1,"step":1,"callId":"call_00_AZFzvUwuC4vAUoICrfke5147","content":[{"type":"text","text":"BOTH_OK\n"}],"isError":false,"meta":{"logs":[]}},"sourceEventSeqs":[86],"surfaceOp":"append"}
 {"type":"step/end","seq":89,"time":1783611775592,"data":{"turn":1,"step":1}}
 {"type":"step/start","seq":90,"time":1783611775592,"data":{"turn":1,"step":2}}
 {"type":"assistant/chunk","seq":91,"time":1783611776183,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}}}

+ 1 - 1
examples/acp-agent/tests/snapshots/code-mode-turn/session.jsonl

@@ -112,7 +112,7 @@
 {"type":"tool/call","seq":110,"time":1783611772840,"data":{"turn":1,"step":1,"callId":"call_00_DRxnM6R1TThfDwcudW0f2050","name":"run_code","arguments":"{\"code\": \"const out1 = await tools.bash({ command: \\\"echo CODE_ONE\\\", description: \\\"First echo\\\" });\\nconst out2 = await tools.bash({ command: \\\"echo CODE_TWO\\\", description: \\\"Second echo\\\" });\\nreturn out1.trim() + \\\"+\\\" + out2.trim();\"}"}}
 {"type":"tool/code-dispatch","seq":111,"time":1783611772933,"data":{"parentCallId":"call_00_DRxnM6R1TThfDwcudW0f2050","subCallId":"call_00_DRxnM6R1TThfDwcudW0f2050:code:1","name":"bash","arguments":{"command":"echo CODE_ONE","description":"First echo"},"isError":false,"resultSummary":"CODE_ONE\n"}}
 {"type":"tool/code-dispatch","seq":112,"time":1783611772936,"data":{"parentCallId":"call_00_DRxnM6R1TThfDwcudW0f2050","subCallId":"call_00_DRxnM6R1TThfDwcudW0f2050:code:2","name":"bash","arguments":{"command":"echo CODE_TWO","description":"Second echo"},"isError":false,"resultSummary":"CODE_TWO\n"}}
-{"type":"tool/result","seq":113,"time":1783611772937,"data":{"turn":1,"step":1,"callId":"call_00_DRxnM6R1TThfDwcudW0f2050","content":[{"type":"text","text":"CODE_ONE+CODE_TWO"}],"isError":false,"meta":{"logs":[],"dispatches":2}},"sourceEventSeqs":[110],"surfaceOp":"append"}
+{"type":"tool/result","seq":113,"time":1783611772937,"data":{"turn":1,"step":1,"callId":"call_00_DRxnM6R1TThfDwcudW0f2050","content":[{"type":"text","text":"CODE_ONE+CODE_TWO"}],"isError":false,"meta":{"logs":[]}},"sourceEventSeqs":[110],"surfaceOp":"append"}
 {"type":"step/end","seq":114,"time":1783611772938,"data":{"turn":1,"step":1}}
 {"type":"step/start","seq":115,"time":1783611772938,"data":{"turn":1,"step":2}}
 {"type":"assistant/chunk","seq":116,"time":1783611773376,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}}}

+ 3 - 1
packages/code-runtime/code-runtime-worker/README.md

@@ -23,7 +23,7 @@ Every field is validated (positive numbers) and defaulted; there are no other tu
 - **Type-strip host-side, in execution context** — the program is wrapped in an async-function shell, stripped with `node:module`'s `stripTypeScriptTypes` (erasable syntax only — `enum`/namespaces are rejected as a program `exception` and no worker spawns), and sliced back out byte-positioned; it then executes as the body of an `AsyncFunction`, so top-level `await`/`return` work.
 - **The port assumes a hostile peer** — model code can reach `parentPort` and forge traffic, so every inbound message is shape-validated and REBUILT before anything reads it (`null`, primitives, junk types, and malformed payloads drop without a throw; forged extra fields never ride along), the host answers each call id at most once, resolves binding names as OWN properties only (a forged `constructor` cannot walk a prototype chain), drops post-settlement replies, and converts a non-cloneable binding resolution into an error reply. Forged `log`/`done` messages cannot bypass the caps: one host-side ledger bounds everything that lands in `logs`, and the completion value is re-capped host-side. 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. ONE shared `maxLogBytes` ledger bounds everything: streamed entries, forged port traffic, and pipe bytes that bypass the patched streams (appended after), with the overflow marked in-band once.
+- **Logs stream eagerly** — console/stdout/stderr text crosses the port in emission order, so a timed-out or killed program still shows what it printed. ONE shared `maxLogBytes` ledger bounds everything: streamed text, forged port traffic, and pipe bytes that bypass the patched streams (appended after), with the overflow marked in-band once.
 - **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.
 
@@ -31,6 +31,8 @@ Every field is validated (positive numbers) and defaulted; there are no other tu
 
 Source mode loads erasable-only `src/worker.ts` through Node's native type stripping. Built mode passes the sibling `lib/worker.cjs` as a filesystem path because pkg's VFS Worker hook expects CommonJS; the same path works under ordinary Node. `tests/built-lib.e2e.ts` pins the real load path required by [docs/testing.md](../../../docs/testing.md).
 
+The SDK surface is the default/named `WorkerCodeRuntime` class plus `Config`. The operational `./worker` subpath exists only as the packaged spawn entry; the wire protocol and bootstrap helpers are source-private implementation details.
+
 ## Model Experience
 
 Indirectly, through Code Mode in [`dsh-tools`](../../core/tools/README.md), which renders this worker's capped printed or returned data and exact `[dsh-code-runtime-worker] log capture truncated at <maxLogBytes> bytes` and `… [truncated]` markers into a retained `run_code` result. Binding traffic and worker internals stay outside context.

+ 0 - 1
packages/code-runtime/code-runtime-worker/package.json

@@ -15,7 +15,6 @@
       "types": "./lib/types/worker.d.ts",
       "default": "./lib/worker.cjs"
     },
-    "./src/*": "./src/*",
     "./package.json": "./package.json"
   },
   "files": [

+ 19 - 21
packages/code-runtime/code-runtime-worker/src/bootstrap.ts

@@ -7,7 +7,6 @@
 
 import { inspect } from 'node:util'
 import { serialize } from 'node:v8'
-import type { CodeLogEntry } from '@deepseek-ai/dsh-code-runtime'
 import { logTruncationMarker } from './protocol.ts'
 import type { DoneMessage, ReplyMessage, WorkerBootData, WorkerToHost } from './protocol.ts'
 
@@ -28,12 +27,12 @@ export interface PatchableStream {
 }
 
 /**
- * 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
+ * Ordered text capture under one shared byte budget, delivered to a sink as
+ * each item lands (the real sink streams text 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.
+ * exhausted it emits exactly one in-band marker 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
@@ -42,28 +41,28 @@ export class LogBuffer {
   // 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
+  private readonly sink: (text: string) => void
 
-  constructor(maxBytes: number, sink: (entry: CodeLogEntry) => void) {
+  constructor(maxBytes: number, sink: (text: string) => 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.
+   * Emit text to the sink, charging it against the budget (drops + marks once exhausted).
+   * @param text - the captured text to deliver.
    */
-  push(entry: CodeLogEntry): void {
+  push(text: string): void {
     if (this.truncated) return
-    const cost = Buffer.byteLength(entry.text, 'utf8')
+    const cost = Buffer.byteLength(text, 'utf8')
     if (cost > this.remaining) {
       this.truncated = true
-      this.sink({ source: 'stderr', text: logTruncationMarker(this.maxBytes) })
+      this.sink(logTruncationMarker(this.maxBytes))
       return
     }
     this.remaining -= cost
-    this.sink(entry)
+    this.sink(text)
   }
 }
 
@@ -84,7 +83,7 @@ export function makeConsoleShim(logs: LogBuffer): Record<(typeof CONSOLE_LEVELS)
     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) }) }
+    shim[level] = (...args: unknown[]) => { logs.push(render(args)) }
   }
   return shim
 }
@@ -98,17 +97,16 @@ export function makeConsoleShim(logs: LogBuffer): Record<(typeof CONSOLE_LEVELS)
  *
  * @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 {
+export function captureStreamWrites(logs: LogBuffer, stream: PatchableStream): () => 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, ...rest: unknown[]): boolean => {
-    logs.push({ source, text: typeof chunk === 'string' ? chunk : String(chunk) })
+    logs.push(typeof chunk === 'string' ? chunk : String(chunk))
     // Node's optional-encoding shape: the callback is whichever of the next
     // two positions holds a function (a non-function there is the encoding).
     const callback = [rest[0], rest[1]].find(
@@ -256,9 +254,9 @@ export async function runWorkerMain(
   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 logs = new LogBuffer(data.maxLogBytes, (text) => { port.postMessage({ type: 'log', text }) })
+  captureStreamWrites(logs, streams.stdout)
+  captureStreamWrites(logs, streams.stderr)
 
   const pending = new Map<number, PendingCall>()
   wireReplies(port, pending)

+ 14 - 33
packages/code-runtime/code-runtime-worker/src/index.ts

@@ -12,14 +12,11 @@ import { fileURLToPath } from 'node:url'
 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 { CodeBindingFunction, CodeRunFailure, CodeRunRequest, CodeRunResult } from '@deepseek-ai/dsh-code-runtime'
 import { prepareValue, truncateUtf8Bytes } from './bootstrap.ts'
 import { logTruncationMarker } from './protocol.ts'
 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 {
   /**
@@ -112,10 +109,6 @@ function messageOf(error: unknown): string {
   return error instanceof Error ? error.message : String(error)
 }
 
-/** The log sources / console levels the seam vocabulary admits, as runtime sets for inbound-message validation. */
-const LOG_SOURCES = new Set<string>(['console', 'stdout', 'stderr'])
-const LOG_LEVELS = new Set<string>(['log', 'info', 'warn', 'error', 'debug'])
-
 /**
  * Runtime shape gate for inbound port traffic. The peer runs MODEL CODE and
  * can post anything — `null`, primitives, objects with poisoned fields — so
@@ -134,20 +127,8 @@ function parseWorkerMessage(raw: unknown): WorkerToHost | undefined {
       return { type: 'call', id: m.id, global: m.global, name: m.name, args: m.args }
     }
     case 'log': {
-      const entry = m.entry
-      if (typeof entry !== 'object' || entry === null) return undefined
-      const e = entry as Record<string, unknown>
-      if (typeof e.text !== 'string') return undefined
-      if (typeof e.source !== 'string' || !LOG_SOURCES.has(e.source)) return undefined
-      if (e.level !== undefined && (typeof e.level !== 'string' || !LOG_LEVELS.has(e.level))) return undefined
-      return {
-        type: 'log',
-        entry: {
-          source: e.source as CodeLogEntry['source'],
-          ...e.level !== undefined ? { level: e.level as Exclude<CodeLogEntry['level'], undefined> } : {},
-          text: e.text,
-        },
-      }
+      if (typeof m.text !== 'string') return undefined
+      return { type: 'log', text: m.text }
     }
     case 'done': {
       if (m.error === undefined) return { type: 'done', ...m.value !== undefined ? { value: m.value } : {} }
@@ -291,33 +272,33 @@ export class WorkerCodeRuntime extends CodeRuntime {
     return new Promise<CodeRunResult>((resolve) => {
       let settled = false
       const answered = new Set<number>()
-      const logs: CodeLogEntry[] = []
-      const strayLogs: CodeLogEntry[] = []
+      const logs: string[] = []
+      const strayLogs: string[] = []
 
       // One host-side budget covers normal, forged, and stray-pipe log entries. The first
       // overflow emits the shared in-band marker and drops everything after it.
       let logBudget = this.config.maxLogBytes
       let logsTruncated = false
-      const admit = (entry: CodeLogEntry, sink: CodeLogEntry[]): void => {
+      const admit = (text: string, sink: string[]): void => {
         if (logsTruncated) return
-        const cost = Buffer.byteLength(entry.text, 'utf8')
+        const cost = Buffer.byteLength(text, 'utf8')
         if (cost > logBudget) {
           logsTruncated = true
-          sink.push({ source: 'stderr', text: logTruncationMarker(this.config.maxLogBytes) })
+          sink.push(logTruncationMarker(this.config.maxLogBytes))
           return
         }
         logBudget -= cost
-        sink.push(entry)
+        sink.push(text)
       }
 
       // No settled guard: `finish` snapshots the arrays when it resolves, so
       // a chunk flushing after settlement mutates only the discarded buffers,
       // and the ledger bounds that growth until the pipes close.
-      const captureStray = (source: 'stdout' | 'stderr') => (chunk: Buffer) => {
-        admit({ source, text: chunk.toString('utf8') }, strayLogs)
+      const captureStray = (chunk: Buffer): void => {
+        admit(chunk.toString('utf8'), strayLogs)
       }
-      worker.stdout.on('data', captureStray('stdout'))
-      worker.stderr.on('data', captureStray('stderr'))
+      worker.stdout.on('data', captureStray)
+      worker.stderr.on('data', captureStray)
 
       // Exactly one outcome wins. Every path cleans up, terminates, and awaits the worker;
       // logs captured before timeout, abort, or failure remain in the result.
@@ -386,7 +367,7 @@ export class WorkerCodeRuntime extends CodeRuntime {
         // this listener would crash the host process. Junk drops silently.
         const message = parseWorkerMessage(raw)
         if (!message) return
-        if (message.type === 'log' && !settled) admit(message.entry, logs)
+        if (message.type === 'log' && !settled) admit(message.text, logs)
         onCall(message)
         onDone(message)
       })

+ 4 - 6
packages/code-runtime/code-runtime-worker/src/protocol.ts

@@ -5,8 +5,6 @@
  * @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. */
@@ -20,7 +18,7 @@ export interface WorkerBootData {
 }
 
 /** Worker → host: one bridged binding call. */
-export interface CallMessage {
+interface CallMessage {
   type: 'call'
   /** Worker-issued correlation id; the host answers each id at most once and ignores duplicates. */
   id: number
@@ -32,10 +30,10 @@ export interface CallMessage {
   args: unknown
 }
 
-/** Worker → host: one captured log entry, streamed eagerly so output survives a mid-run termination (timeout, abort, OOM). */
-export interface LogMessage {
+/** Worker → host: captured text, streamed eagerly so output survives a mid-run termination (timeout, abort, OOM). */
+interface LogMessage {
   type: 'log'
-  entry: CodeLogEntry
+  text: string
 }
 
 /**

+ 23 - 27
packages/code-runtime/code-runtime-worker/tests/bootstrap.spec.ts

@@ -1,9 +1,8 @@
 import { describe, expect, it } from 'vitest'
 import { EventEmitter } from 'node:events'
-import { LogBuffer, makeConsoleShim, makeNamespaces, captureStreamWrites, prepareValue, runWorkerMain, truncateUtf8Bytes, 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'
+import { LogBuffer, makeConsoleShim, makeNamespaces, captureStreamWrites, prepareValue, runWorkerMain, truncateUtf8Bytes, wireReplies } from '../src/bootstrap.ts'
+import type { BootstrapPort, PatchableStream, PendingCall } from '../src/bootstrap.ts'
+import type { ReplyMessage, WorkerToHost } from '../src/protocol.ts'
 
 /**
  * An in-process stand-in for the worker's parentPort: the test plays the
@@ -31,8 +30,8 @@ class FakePort implements BootstrapPort {
     this.emitter.emit('message', message)
   }
 
-  logs(): CodeLogEntry[] {
-    return this.sent.filter(message => message.type === 'log').map(message => message.entry)
+  logs(): string[] {
+    return this.sent.filter(message => message.type === 'log').map(message => message.text)
   }
 
   done(): WorkerToHost | undefined {
@@ -48,12 +47,12 @@ 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([
+    const seen: string[] = []
+    const buffer = new LogBuffer(10, text => seen.push(text))
+    buffer.push('12345')
+    buffer.push('123456')
+    buffer.push('dropped')
+    expect(seen).toEqual([
       '12345',
       '[dsh-code-runtime-worker] log capture truncated at 10 bytes',
     ])
@@ -61,40 +60,37 @@ describe('LogBuffer', () => {
 })
 
 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)))
+  it('captures the five methods and renders non-strings inspect-style', () => {
+    const seen: string[] = []
+    const shim = makeConsoleShim(new LogBuffer(1_000, text => seen.push(text)))
     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)
+    expect(seen).toEqual(['plain { a: 1 }', 'i', 'w', 'e', 'd'])
   })
 })
 
 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))
+    const seen: string[] = []
+    const buffer = new LogBuffer(1_000, text => seen.push(text))
     let underlying = ''
     const stream: PatchableStream = { write: (chunk: unknown) => { underlying += String(chunk); return true } }
-    const restore = captureStreamWrites(buffer, stream, 'stdout')
+    const restore = captureStreamWrites(buffer, stream)
     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(seen).toEqual(['captured', 'bytes'])
     expect(underlying).toBe('after')
   })
 
   it('invokes the write callback asynchronously, in both optional-encoding shapes', async () => {
     const buffer = new LogBuffer(1_000, () => {})
     const stream: PatchableStream = { write: () => true }
-    captureStreamWrites(buffer, stream, 'stdout')
+    captureStreamWrites(buffer, stream)
     const calls: (Error | null | undefined)[] = []
     stream.write('two-arg', (error?: Error | null) => calls.push(error))
     stream.write('three-arg', 'utf8', (error?: Error | null) => calls.push(error))
@@ -107,7 +103,7 @@ describe('captureStreamWrites', () => {
   it('still fires the callback for a write the exhausted budget drops', async () => {
     const buffer = new LogBuffer(4, () => {})
     const stream: PatchableStream = { write: () => true }
-    captureStreamWrites(buffer, stream, 'stdout')
+    captureStreamWrites(buffer, stream)
     stream.write('this write overflows the budget and is dropped')
     await new Promise<void>(resolve => stream.write('also dropped', resolve))
   })
@@ -210,7 +206,7 @@ describe('runWorkerMain', () => {
       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.logs()).toEqual(['got 42'])
     expect(port.done()).toEqual({ type: 'done', value: { doubled: 42 } })
   })
 
@@ -268,6 +264,6 @@ describe('runWorkerMain', () => {
     // 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' })
+    expect(port.logs().at(-1)).toBe('never seen — already restored? no: patch persists in worker')
   })
 })

+ 2 - 2
packages/code-runtime/code-runtime-worker/tests/built-lib.e2e.ts

@@ -38,9 +38,9 @@ describe.skipIf(!built)('built lib real load path (plain node)', () => {
 
     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 }
+    const result = JSON.parse(lastLine) as { value?: unknown; logs: string[]; error?: unknown }
     expect(result.error).toBeUndefined()
     expect(result.value).toBe(42)
-    expect(result.logs).toContainEqual({ source: 'console', level: 'log', text: 'halfway 42' })
+    expect(result.logs).toContain('halfway 42')
   })
 })

+ 14 - 22
packages/code-runtime/code-runtime-worker/tests/runtime.spec.ts

@@ -28,7 +28,7 @@ describe('WorkerCodeRuntime — programs and bindings (real workers)', () => {
     expect(runtime.isolation).toBe('worker-thread')
   })
 
-  it('runs TypeScript (erasable syntax), captures console/stdout in order, returns the value', async () => {
+  it('runs TypeScript (erasable syntax), captures output in order, returns the value', async () => {
     const { runtime } = await setup()
     const result = await runtime.run({
       program: `
@@ -43,12 +43,7 @@ describe('WorkerCodeRuntime — programs and bindings (real workers)', () => {
     })
     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 }')
+    expect(result.logs).toEqual(['point { x: 1, y: 2 }', 'raw-out\n', 'careful'])
   })
 
   it('bridges binding calls both ways and rejects the program-side call on a host rejection', async () => {
@@ -115,7 +110,7 @@ describe('WorkerCodeRuntime — programs and bindings (real workers)', () => {
       bindings: [],
     })
     expect(result.error?.kind).toBe('exception')
-    expect(result.logs.map(entry => entry.text)).toContain('before')
+    expect(result.logs).toContain('before')
   })
 })
 
@@ -210,8 +205,8 @@ describe('WorkerCodeRuntime — budgets and containment (real workers)', () => {
       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(result.logs.at(-1)).toContain('truncated at 300 bytes')
+    const total = result.logs.reduce((sum, text) => sum + Buffer.byteLength(text, 'utf8'), 0)
     expect(total).toBeLessThan(1_000)
   })
 
@@ -241,7 +236,7 @@ describe('WorkerCodeRuntime — budgets and containment (real workers)', () => {
     })
     expect(result.error).toBeUndefined()
     expect(result.value).toBe('done')
-    expect(result.logs).toContainEqual({ source: 'stdout', text: 'flushed' })
+    expect(result.logs).toContain('flushed')
   })
 
   it('caps a huge container whose bounded rendering is small (wire size, not rendering, is what counts)', async () => {
@@ -268,8 +263,8 @@ describe('WorkerCodeRuntime — budgets and containment (real workers)', () => {
       bindings: [],
     })
     expect(result.error).toBeUndefined()
-    expect(result.logs).toContainEqual({ source: 'stdout', text: 'abcd' })
-    expect(result.logs.map(entry => entry.text)).not.toContain('ef')
+    expect(result.logs).toContain('abcd')
+    expect(result.logs).not.toContain('ef')
   }, 15_000)
 })
 
@@ -304,11 +299,9 @@ describe('WorkerCodeRuntime — hostile programs (real workers)', () => {
           { type: 'call', id: 1e9, global: 7, name: 'real', args: {} },
           { type: 'call', id: 1e9, global: 'tools', name: 7, args: {} },
           { type: 'log' },
-          { type: 'log', entry: null },
-          { type: 'log', entry: { source: 'stdout', text: 7 } },
-          { type: 'log', entry: { source: 'nope', text: 'x' } },
-          { type: 'log', entry: { source: 'console', level: 'nope', text: 'x' } },
-          { type: 'log', entry: { source: 'console', level: 7, text: 'x' } },
+          { type: 'log', text: null },
+          { type: 'log', text: 7 },
+          { type: 'log', text: {} },
           { type: 'done', error: 5 },
           { type: 'done', error: { message: 5 } },
         ]) parentPort.postMessage(junk);
@@ -329,7 +322,7 @@ describe('WorkerCodeRuntime — hostile programs (real workers)', () => {
       // code and an unbounded result.
       program: `
         const { parentPort } = await import('node:worker_threads');
-        for (let i = 0; i < 50; i++) parentPort.postMessage({ type: 'log', entry: { source: 'stdout', text: 'F'.repeat(100), forged: true } });
+        for (let i = 0; i < 50; i++) parentPort.postMessage({ type: 'log', text: 'F'.repeat(100), forged: true });
         parentPort.postMessage({ type: 'done', value: 'V'.repeat(100000) });
         for (;;) {}
       `,
@@ -341,10 +334,9 @@ describe('WorkerCodeRuntime — hostile programs (real workers)', () => {
     expect(value.endsWith('… [truncated]')).toBe(true)
     expect(value.length).toBeLessThan(120)
     const marker = '[dsh-code-runtime-worker] log capture truncated at 200 bytes'
-    const total = result.logs.reduce((sum, entry) => sum + Buffer.byteLength(entry.text, 'utf8'), 0)
+    const total = result.logs.reduce((sum, text) => sum + Buffer.byteLength(text, 'utf8'), 0)
     expect(total).toBeLessThanOrEqual(200 + Buffer.byteLength(marker, 'utf8'))
-    expect(result.logs.at(-1)?.text).toBe(marker)
-    expect(result.logs.every(entry => !('forged' in entry))).toBe(true)
+    expect(result.logs.at(-1)).toBe(marker)
   })
 
   it('accepts a forged done carrying both value and error (self-sabotage, contained)', async () => {

+ 1 - 1
packages/code-runtime/code-runtime/README.md

@@ -16,7 +16,7 @@ Semantics every implementation must honor (contract details in the class JSDoc):
 
 ## Vocabulary
 
-`CodeRunRequest` (`program`, `bindings`, `signal?`) carries everything the runtime acts on — defaulting (time budgets, output caps) is the implementation's validated config, never a hidden `??` inside `run()`. `bindings` is a list of `CodeBindingNamespace`s (`global` + `functions`), each exposed to the program as one global object of async callables. `CodeRunResult` reports the completion `value?`, the ordered `logs` (`CodeLogEntry`: `console`/`stdout`/`stderr` source, console `level`, capped text), and the `error?` (`CodeRunFailure`: `kind` + model-feedable `message`). See `src/types.ts` for the full contracts.
+`CodeRunRequest` (`program`, `bindings`, `signal?`) carries everything the runtime acts on — defaulting (time budgets, output caps) is the implementation's validated config, never a hidden `??` inside `run()`. `bindings` is a list of `CodeBindingNamespace`s (`global` + `functions`), each exposed to the program as one global object of async callables. `CodeRunResult` reports the completion `value?`, ordered capped `logs: string[]`, and the `error?` (`CodeRunFailure`: `kind` + model-feedable `message`). See `src/types.ts` for the full contracts.
 
 ## Model Experience
 

+ 0 - 1
packages/code-runtime/code-runtime/src/index.ts

@@ -10,7 +10,6 @@ import type { CodeRunRequest, CodeRunResult } from './types.ts'
 export type {
   CodeBindingFunction,
   CodeBindingNamespace,
-  CodeLogEntry,
   CodeRunFailure,
   CodeRunRequest,
   CodeRunResult,

+ 2 - 16
packages/code-runtime/code-runtime/src/types.ts

@@ -54,20 +54,6 @@ export interface CodeRunRequest {
   signal?: AbortSignal
 }
 
-/**
- * One captured output entry, in emission order. `source` says which channel
- * produced it: the program's `console` (shimmed by the runtime), or a stray
- * write to the underlying stdout/stderr streams.
- */
-export interface CodeLogEntry {
-  /** Which channel produced the text. */
-  source: 'console' | 'stdout' | 'stderr'
-  /** The console method used; present only when `source` is `'console'`. */
-  level?: 'log' | 'info' | 'warn' | 'error' | 'debug'
-  /** The captured text (possibly truncated by the implementation's caps, marked in-band). */
-  text: string
-}
-
 /**
  * Why a run failed. The kinds are orthogonal outcomes reported independently
  * (per docs/defensive-patterns.md): a budget expiry is not an exception, an
@@ -98,8 +84,8 @@ export interface CodeRunResult {
    * or value-less run leaves this absent.
    */
   value?: unknown
-  /** Everything the program emitted, in order (capped by the implementation). */
-  logs: CodeLogEntry[]
+  /** Text the program emitted, in order (capped by the implementation). */
+  logs: string[]
   /** Present iff the run failed; see {@link CodeRunFailure} for the taxonomy. */
   error?: CodeRunFailure
 }

+ 1 - 1
packages/code-runtime/code-runtime/tests/service.spec.ts

@@ -55,7 +55,7 @@ describe('CodeRuntime service seam', () => {
   it('reports a failed run as an error field on a resolved result, never a rejection', async () => {
     const { runtime } = await setup()
     runtime.nextResult = {
-      logs: [{ source: 'console', level: 'error', text: 'boom' }],
+      logs: ['boom'],
       error: { kind: 'exception', message: 'boom' },
     }
     const result = await runtime.run({ program: 'throw new Error("boom")', bindings: [] })

+ 1 - 5
packages/cordis/tool-cordis/src/api-catalog.ts

@@ -609,10 +609,6 @@ export const TYPE_API: readonly TypeApiEntry[] = [
     name: 'CodeBindingNamespace',
     declaration: 'export interface CodeBindingNamespace {\n    global: string;\n    functions: Record<string, CodeBindingFunction>;\n}',
   },
-  {
-    name: 'CodeLogEntry',
-    declaration: 'export interface CodeLogEntry {\n    source: \'console\' | \'stdout\' | \'stderr\';\n    level?: \'log\' | \'info\' | \'warn\' | \'error\' | \'debug\';\n    text: string;\n}',
-  },
   {
     name: 'CodeRunFailure',
     declaration: 'export interface CodeRunFailure {\n    kind: \'exception\' | \'timeout\' | \'abort\' | \'worker-exit\';\n    message: string;\n}',
@@ -623,7 +619,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [
   },
   {
     name: 'CodeRunResult',
-    declaration: 'export interface CodeRunResult {\n    value?: unknown;\n    logs: CodeLogEntry[];\n    error?: CodeRunFailure;\n}',
+    declaration: 'export interface CodeRunResult {\n    value?: unknown;\n    logs: string[];\n    error?: CodeRunFailure;\n}',
   },
   {
     name: 'CollectedOutput',

+ 5 - 6
packages/core/tools/src/code-mode.ts

@@ -108,14 +108,13 @@ function renderValue(value: unknown): string {
 /** The run_code result's `meta` payload (JSON-serializable; `presentResult` narrows it back). */
 interface RunCodeMeta {
   logs: CodeRunResult['logs']
-  dispatches: number
 }
 
 /** Soft-narrow a result `meta` back to {@link RunCodeMeta} (replay may carry older shapes; presentation must not throw). */
 function asRunCodeMeta(meta: unknown): RunCodeMeta | undefined {
   if (typeof meta !== 'object' || meta === null) return undefined
   const m = meta as Record<string, unknown>
-  if (!Array.isArray(m.logs) || typeof m.dispatches !== 'number') return undefined
+  if (!Array.isArray(m.logs) || !m.logs.every(log => typeof log === 'string')) return undefined
   return m as unknown as RunCodeMeta
 }
 
@@ -251,12 +250,12 @@ export function createRunCodeTool(registry: ToolRegistry, requireRuntime: () =>
         }
 
         if (result.error) {
-          const logsText = result.logs.length > 0 ? `\nCaptured output:\n${result.logs.map(entry => entry.text).join('\n')}` : ''
+          const logsText = result.logs.length > 0 ? `\nCaptured output:\n${result.logs.join('\n')}` : ''
           throw new CodeRunFailedError(`code run failed (${result.error.kind}): ${result.error.message}${logsText}`)
         }
         const rendered = renderValue(result.value)
-        const parts = [result.logs.map(entry => entry.text).join('\n'), rendered].filter(part => part.length > 0)
-        const meta: RunCodeMeta = { logs: result.logs, dispatches }
+        const parts = [result.logs.join('\n'), rendered].filter(part => part.length > 0)
+        const meta: RunCodeMeta = { logs: result.logs }
         return {
           content: [{ type: 'text', text: parts.length > 0 ? parts.join('\n') : '(run_code completed with no output)' }],
           meta,
@@ -278,7 +277,7 @@ export function createRunCodeTool(registry: ToolRegistry, requireRuntime: () =>
     presentResult: (_args, result) => {
       const meta = asRunCodeMeta(result.meta)
       if (!meta) return undefined
-      const output = meta.logs.map(entry => entry.text).join('\n')
+      const output = meta.logs.join('\n')
       return {
         card: 'generic',
         ...output.length > 0 ? { content: [{ type: 'text' as const, text: output }] } : {},

+ 6 - 5
packages/core/tools/tests/code-mode.spec.ts

@@ -328,7 +328,7 @@ describe('the run_code dispatch bridge', () => {
       const tools = request.bindings[0]!.functions
       const first = await tools.echo!({ value: 'one' })
       const second = await tools.echo!({ value: 'two' })
-      return { logs: [{ source: 'console', level: 'log', text: `saw ${String(first)}` }], value: second }
+      return { logs: [`saw ${String(first)}`], value: second }
     }
     const result = await runCode(ctx, 'const …: string = …', { agent })
     expect(result.isError).toBe(false)
@@ -339,7 +339,7 @@ describe('the run_code dispatch bridge', () => {
       { parentCallId: 'call-1', subCallId: 'call-1:code:1', name: 'echo', arguments: { value: 'one' }, isError: false, resultSummary: 'echo:one' },
       { parentCallId: 'call-1', subCallId: 'call-1:code:2', name: 'echo', arguments: { value: 'two' }, isError: false, resultSummary: 'echo:two' },
     ])
-    expect(result.meta).toEqual({ logs: [{ source: 'console', level: 'log', text: 'saw echo:one' }], dispatches: 2 })
+    expect(result.meta).toEqual({ logs: ['saw echo:one'] })
   })
 
   it('exposes only an opaque parent token to nested result observers', async () => {
@@ -503,7 +503,7 @@ describe('the run_code dispatch bridge', () => {
   it('converts a failed run into a structured isError result carrying kind, message, and captured logs', async () => {
     const { ctx, runtime } = await setup({ mode: 'code' })
     runtime.behavior = () => Promise.resolve({
-      logs: [{ source: 'console', level: 'log', text: 'got this far' }],
+      logs: ['got this far'],
       error: { kind: 'timeout', message: 'compute budget exhausted (300ms busy)' },
     })
     const result = await runCode(ctx, 'program')
@@ -627,7 +627,7 @@ describe('the run_code dispatch bridge', () => {
     const view = tool.presentResult?.({ code: 'return 1' }, {
       content: [{ type: 'text', text: 'model-facing' }],
       isError: false,
-      meta: { logs: [{ source: 'console', level: 'log', text: 'printed' }], dispatches: 1 },
+      meta: { logs: ['printed'] },
     })
     // The result omits the title — an update replaces only provided fields,
     // so the pending card's program title persists through completion.
@@ -636,9 +636,10 @@ describe('the run_code dispatch bridge', () => {
       content: [{ type: 'text', text: 'printed' }],
     })
     // No captured output → no content either; everything pending persists.
-    expect(tool.presentResult?.({ code: 'x' }, { content: [], isError: false, meta: { logs: [], dispatches: 2 } }))
+    expect(tool.presentResult?.({ code: 'x' }, { content: [], isError: false, meta: { logs: [] } }))
       .toEqual({ card: 'generic' })
     // Replay with an unrecognizable meta falls back to the generic rendering.
+    expect(tool.presentResult?.({ code: 'x' }, { content: [], isError: false, meta: { logs: [{ text: 'legacy' }], dispatches: 1 } })).toBeUndefined()
     expect(tool.presentResult?.({ code: 'x' }, { content: [], isError: false, meta: { other: true } })).toBeUndefined()
     expect(tool.presentResult?.({ code: 'x' }, { content: [], isError: false })).toBeUndefined()
   })

+ 2 - 4
scripts/snapshots/python-sdk-single-exe/advanced/result.json

@@ -408,8 +408,7 @@
         ],
         "isError": false,
         "meta": {
-          "logs": [],
-          "dispatches": 1
+          "logs": []
         }
       },
       "sourceEventSeqs": [
@@ -1606,8 +1605,7 @@
             ],
             "isError": false,
             "meta": {
-              "logs": [],
-              "dispatches": 1
+              "logs": []
             }
           },
           "sourceEventSeqs": [

+ 1 - 1
scripts/snapshots/python-sdk-single-exe/advanced/session.jsonl

@@ -22,7 +22,7 @@
 {"type":"assistant/message","seq":20,"time":0,"data":{"turn":1,"step":2,"content":[{"type":"tool-call","id":"advanced-code","name":"run_code","arguments":"{\"code\": \"return await tools.snapshot_double({ value: 21 })\"}"}],"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[15,16,17,18,19],"surfaceOp":"append"}
 {"type":"tool/call","seq":21,"time":0,"data":{"turn":1,"step":2,"callId":"advanced-code","name":"run_code","arguments":"{\"code\": \"return await tools.snapshot_double({ value: 21 })\"}"}}
 {"type":"tool/code-dispatch","seq":22,"time":0,"data":{"parentCallId":"advanced-code","subCallId":"advanced-code:code:1","name":"snapshot_double","arguments":{"value":21},"isError":false,"resultSummary":"42"}}
-{"type":"tool/result","seq":23,"time":0,"data":{"turn":1,"step":2,"callId":"advanced-code","content":[{"type":"text","text":"42"}],"isError":false,"meta":{"logs":[],"dispatches":1}},"sourceEventSeqs":[21],"surfaceOp":"append"}
+{"type":"tool/result","seq":23,"time":0,"data":{"turn":1,"step":2,"callId":"advanced-code","content":[{"type":"text","text":"42"}],"isError":false,"meta":{"logs":[]}},"sourceEventSeqs":[21],"surfaceOp":"append"}
 {"type":"step/end","seq":24,"time":0,"data":{"turn":1,"step":2}}
 {"type":"step/start","seq":25,"time":0,"data":{"turn":1,"step":3}}
 {"type":"assistant/chunk","seq":26,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}

+ 0 - 1
scripts/type-equiv.manifest.json

@@ -101,7 +101,6 @@
     { "doc": "docs/core-data-structures/code-runtime.md", "symbol": "CodeRunResult", "source": "packages/code-runtime/code-runtime/src/types.ts" },
     { "doc": "docs/core-data-structures/code-runtime.md", "symbol": "CodeBindingNamespace", "source": "packages/code-runtime/code-runtime/src/types.ts" },
     { "doc": "docs/core-data-structures/code-runtime.md", "symbol": "CodeBindingFunction", "source": "packages/code-runtime/code-runtime/src/types.ts" },
-    { "doc": "docs/core-data-structures/code-runtime.md", "symbol": "CodeLogEntry", "source": "packages/code-runtime/code-runtime/src/types.ts" },
     { "doc": "docs/core-data-structures/code-runtime.md", "symbol": "CodeRunFailure", "source": "packages/code-runtime/code-runtime/src/types.ts" },
 
     { "doc": "docs/core-data-structures/filesystem.md", "symbol": "FsTarget", "source": "packages/fs/fs/src/types.ts" },