feat: Code Mode — the registry's mode config, the SDK codegen, and the run_code bridge
The dsh-tools half of the Code Mode RFC (its fourth, final change): the
registry gains its first config — mode: native | code | both — and OWNS how
its tools reach the model. 'code' contributes exactly one wire tool,
run_code, plus a lazy tools:sdk prompt section declaring every other tool
as a generated TypeScript API (jsonSchemaToTs: total over the defineTool
subset, unknown degradation, lexicographic byte-identical rendering);
'both' ships both representations; 'native' is byte-for-byte the old
behavior. Non-native modes fail every assembly loudly without a
typescript-language ctx.codeRuntime.
run_code's dispatch bridge: JSON-normalizes each binding argument before
dispatch (what dispatches is what the tool/code-dispatch event logs — the
append can never fail on payload shape; BigInt/circulars reject that one
call), serializes all program tool calls through a per-run queue (even
Promise.all — no concurrency-safety metadata yet), routes every sub-call
through tools/pre-execute → tools/post-execute (a deny rejects the
program-side promise), drops sub-call additionalContext (no safe outlet
mid-run; pinned), owns a run-scoped abort that follows the outer signal in
and fires on settlement (in-flight sub-dispatch aborted, queued abandoned,
queue drained before returning), and converts a failed run into
CodeRunFailedError → a structured isError carrying kind + captured logs.
tool/code-dispatch joins SessionEventMap by declaration merging (log-only;
deriveMessages ignores it).
The composed surface: the tools config forwards through agent-core and
both app packages; examples/code-agent + demo:code run the worker runtime
under mode code (keyless boot smoke + a with-key e2e proving the collapsed
[run_code] header, the dispatch events, and the file the program wrote);
two new snapshot scenarios (code-mode-turn, both-mode-turn) record the SDK
section, collapsed header, dispatch events, and result card — each its own
header-pinning class (the harness gains per-scenario config overlays and
per-class pins). Catalogs, graphs, cookbook, hooks-bridge notes, and the
RFC (moved to implemented/, restructured to decision-era headings) updated
in the same change.
2026-07-08 12:58:23 +08:00
|
|
|
|
/**
|
2026-07-13 23:27:00 +08:00
|
|
|
|
* Code Mode `run_code` transport. Programs call the registry's agent-visible
|
2026-07-26 06:02:36 +08:00
|
|
|
|
* tools through nested executions scheduled under the native concurrency
|
|
|
|
|
|
* contract; each sub-dispatch is logged for reconstruction, while only the
|
|
|
|
|
|
* outer curated result enters model history.
|
feat: Code Mode — the registry's mode config, the SDK codegen, and the run_code bridge
The dsh-tools half of the Code Mode RFC (its fourth, final change): the
registry gains its first config — mode: native | code | both — and OWNS how
its tools reach the model. 'code' contributes exactly one wire tool,
run_code, plus a lazy tools:sdk prompt section declaring every other tool
as a generated TypeScript API (jsonSchemaToTs: total over the defineTool
subset, unknown degradation, lexicographic byte-identical rendering);
'both' ships both representations; 'native' is byte-for-byte the old
behavior. Non-native modes fail every assembly loudly without a
typescript-language ctx.codeRuntime.
run_code's dispatch bridge: JSON-normalizes each binding argument before
dispatch (what dispatches is what the tool/code-dispatch event logs — the
append can never fail on payload shape; BigInt/circulars reject that one
call), serializes all program tool calls through a per-run queue (even
Promise.all — no concurrency-safety metadata yet), routes every sub-call
through tools/pre-execute → tools/post-execute (a deny rejects the
program-side promise), drops sub-call additionalContext (no safe outlet
mid-run; pinned), owns a run-scoped abort that follows the outer signal in
and fires on settlement (in-flight sub-dispatch aborted, queued abandoned,
queue drained before returning), and converts a failed run into
CodeRunFailedError → a structured isError carrying kind + captured logs.
tool/code-dispatch joins SessionEventMap by declaration merging (log-only;
deriveMessages ignores it).
The composed surface: the tools config forwards through agent-core and
both app packages; examples/code-agent + demo:code run the worker runtime
under mode code (keyless boot smoke + a with-key e2e proving the collapsed
[run_code] header, the dispatch events, and the file the program wrote);
two new snapshot scenarios (code-mode-turn, both-mode-turn) record the SDK
section, collapsed header, dispatch events, and result card — each its own
header-pinning class (the harness gains per-scenario config overlays and
per-class pins). Catalogs, graphs, cookbook, hooks-bridge notes, and the
RFC (moved to implemented/, restructured to decision-era headings) updated
in the same change.
2026-07-08 12:58:23 +08:00
|
|
|
|
* @module @deepseek-ai/dsh-tools/src/code-mode
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
import { CallId, HarnessError } from '@deepseek-ai/dsh-llm'
|
|
|
|
|
|
import type { ContentBlock } from '@deepseek-ai/dsh-llm'
|
|
|
|
|
|
import type { CodeBindingFunction, CodeRunResult, CodeRuntime } from '@deepseek-ai/dsh-code-runtime'
|
2026-07-21 04:34:14 +08:00
|
|
|
|
import { snapshotJsonValue } from '@deepseek-ai/dsh-session'
|
2026-07-21 03:08:35 +08:00
|
|
|
|
import type { JsonValue } from '@deepseek-ai/dsh-session'
|
feat: Code Mode — the registry's mode config, the SDK codegen, and the run_code bridge
The dsh-tools half of the Code Mode RFC (its fourth, final change): the
registry gains its first config — mode: native | code | both — and OWNS how
its tools reach the model. 'code' contributes exactly one wire tool,
run_code, plus a lazy tools:sdk prompt section declaring every other tool
as a generated TypeScript API (jsonSchemaToTs: total over the defineTool
subset, unknown degradation, lexicographic byte-identical rendering);
'both' ships both representations; 'native' is byte-for-byte the old
behavior. Non-native modes fail every assembly loudly without a
typescript-language ctx.codeRuntime.
run_code's dispatch bridge: JSON-normalizes each binding argument before
dispatch (what dispatches is what the tool/code-dispatch event logs — the
append can never fail on payload shape; BigInt/circulars reject that one
call), serializes all program tool calls through a per-run queue (even
Promise.all — no concurrency-safety metadata yet), routes every sub-call
through tools/pre-execute → tools/post-execute (a deny rejects the
program-side promise), drops sub-call additionalContext (no safe outlet
mid-run; pinned), owns a run-scoped abort that follows the outer signal in
and fires on settlement (in-flight sub-dispatch aborted, queued abandoned,
queue drained before returning), and converts a failed run into
CodeRunFailedError → a structured isError carrying kind + captured logs.
tool/code-dispatch joins SessionEventMap by declaration merging (log-only;
deriveMessages ignores it).
The composed surface: the tools config forwards through agent-core and
both app packages; examples/code-agent + demo:code run the worker runtime
under mode code (keyless boot smoke + a with-key e2e proving the collapsed
[run_code] header, the dispatch events, and the file the program wrote);
two new snapshot scenarios (code-mode-turn, both-mode-turn) record the SDK
section, collapsed header, dispatch events, and result card — each its own
header-pinning class (the harness gains per-scenario config overlays and
per-class pins). Catalogs, graphs, cookbook, hooks-bridge notes, and the
RFC (moved to implemented/, restructured to decision-era headings) updated
in the same change.
2026-07-08 12:58:23 +08:00
|
|
|
|
import { defineTool } from './schema.ts'
|
2026-07-26 10:33:48 +08:00
|
|
|
|
import { TOOL_REGISTRY_SCHEDULER } from './index.ts'
|
|
|
|
|
|
import type { ToolDefinition, ToolExecutionResult, ToolRegistry, ToolRunContext } from './index.ts'
|
feat: Code Mode — the registry's mode config, the SDK codegen, and the run_code bridge
The dsh-tools half of the Code Mode RFC (its fourth, final change): the
registry gains its first config — mode: native | code | both — and OWNS how
its tools reach the model. 'code' contributes exactly one wire tool,
run_code, plus a lazy tools:sdk prompt section declaring every other tool
as a generated TypeScript API (jsonSchemaToTs: total over the defineTool
subset, unknown degradation, lexicographic byte-identical rendering);
'both' ships both representations; 'native' is byte-for-byte the old
behavior. Non-native modes fail every assembly loudly without a
typescript-language ctx.codeRuntime.
run_code's dispatch bridge: JSON-normalizes each binding argument before
dispatch (what dispatches is what the tool/code-dispatch event logs — the
append can never fail on payload shape; BigInt/circulars reject that one
call), serializes all program tool calls through a per-run queue (even
Promise.all — no concurrency-safety metadata yet), routes every sub-call
through tools/pre-execute → tools/post-execute (a deny rejects the
program-side promise), drops sub-call additionalContext (no safe outlet
mid-run; pinned), owns a run-scoped abort that follows the outer signal in
and fires on settlement (in-flight sub-dispatch aborted, queued abandoned,
queue drained before returning), and converts a failed run into
CodeRunFailedError → a structured isError carrying kind + captured logs.
tool/code-dispatch joins SessionEventMap by declaration merging (log-only;
deriveMessages ignores it).
The composed surface: the tools config forwards through agent-core and
both app packages; examples/code-agent + demo:code run the worker runtime
under mode code (keyless boot smoke + a with-key e2e proving the collapsed
[run_code] header, the dispatch events, and the file the program wrote);
two new snapshot scenarios (code-mode-turn, both-mode-turn) record the SDK
section, collapsed header, dispatch events, and result card — each its own
header-pinning class (the harness gains per-scenario config overlays and
per-class pins). Catalogs, graphs, cookbook, hooks-bridge notes, and the
RFC (moved to implemented/, restructured to decision-era headings) updated
in the same change.
2026-07-08 12:58:23 +08:00
|
|
|
|
|
|
|
|
|
|
declare module '@deepseek-ai/dsh-session' {
|
|
|
|
|
|
interface SessionEventMap {
|
|
|
|
|
|
/**
|
2026-07-26 06:02:36 +08:00
|
|
|
|
* One sub-dispatch STARTING inside a `run_code` program: the parent
|
|
|
|
|
|
* `run_code` call id, the deterministic sub-call id (`<parent>:code:<n>`,
|
|
|
|
|
|
* numbered in submission order), and the tool `name` with its
|
|
|
|
|
|
* JSON-normalized `arguments` — the exact value dispatched, normalized
|
|
|
|
|
|
* BEFORE dispatch, so this append can never fail on payload shape.
|
|
|
|
|
|
* Appended when the scheduler actually starts the call (not at
|
|
|
|
|
|
* submission), so a start means the tool body pipeline was entered; a
|
|
|
|
|
|
* call abandoned in the queue logs nothing. Log-only: `deriveMessages()`
|
|
|
|
|
|
* ignores it; UIs use it for live per-sub-call running state and pair it
|
|
|
|
|
|
* with `tool/code-dispatch` by `subCallId` (timing = the two events'
|
|
|
|
|
|
* `time` fields).
|
|
|
|
|
|
*/
|
|
|
|
|
|
'tool/code-dispatch-start': { parentCallId: CallId; subCallId: CallId; name: string; arguments: unknown }
|
|
|
|
|
|
/**
|
|
|
|
|
|
* One bridged sub-dispatch SETTLING: the pairing ids (matching the
|
|
|
|
|
|
* `tool/code-dispatch-start` with the same `subCallId`), the tool `name`
|
|
|
|
|
|
* with the same JSON-normalized `arguments`, and the sub-call's complete
|
|
|
|
|
|
* model-facing outcome in `tool/result`'s own vocabulary
|
2026-07-26 02:43:34 +08:00
|
|
|
|
* (`content` + `isError`), so UIs render a sub-call through the exact
|
2026-07-26 06:02:36 +08:00
|
|
|
|
* code path that renders a native call. Every started sub-call settles
|
|
|
|
|
|
* with exactly one of these (abort included: the aborted pipeline result
|
|
|
|
|
|
* is an `isError` outcome).
|
feat: Code Mode — the registry's mode config, the SDK codegen, and the run_code bridge
The dsh-tools half of the Code Mode RFC (its fourth, final change): the
registry gains its first config — mode: native | code | both — and OWNS how
its tools reach the model. 'code' contributes exactly one wire tool,
run_code, plus a lazy tools:sdk prompt section declaring every other tool
as a generated TypeScript API (jsonSchemaToTs: total over the defineTool
subset, unknown degradation, lexicographic byte-identical rendering);
'both' ships both representations; 'native' is byte-for-byte the old
behavior. Non-native modes fail every assembly loudly without a
typescript-language ctx.codeRuntime.
run_code's dispatch bridge: JSON-normalizes each binding argument before
dispatch (what dispatches is what the tool/code-dispatch event logs — the
append can never fail on payload shape; BigInt/circulars reject that one
call), serializes all program tool calls through a per-run queue (even
Promise.all — no concurrency-safety metadata yet), routes every sub-call
through tools/pre-execute → tools/post-execute (a deny rejects the
program-side promise), drops sub-call additionalContext (no safe outlet
mid-run; pinned), owns a run-scoped abort that follows the outer signal in
and fires on settlement (in-flight sub-dispatch aborted, queued abandoned,
queue drained before returning), and converts a failed run into
CodeRunFailedError → a structured isError carrying kind + captured logs.
tool/code-dispatch joins SessionEventMap by declaration merging (log-only;
deriveMessages ignores it).
The composed surface: the tools config forwards through agent-core and
both app packages; examples/code-agent + demo:code run the worker runtime
under mode code (keyless boot smoke + a with-key e2e proving the collapsed
[run_code] header, the dispatch events, and the file the program wrote);
two new snapshot scenarios (code-mode-turn, both-mode-turn) record the SDK
section, collapsed header, dispatch events, and result card — each its own
header-pinning class (the harness gains per-scenario config overlays and
per-class pins). Catalogs, graphs, cookbook, hooks-bridge notes, and the
RFC (moved to implemented/, restructured to decision-era headings) updated
in the same change.
2026-07-08 12:58:23 +08:00
|
|
|
|
* Log-only: `deriveMessages()` ignores it, so sub-calls never re-enter
|
|
|
|
|
|
* model context; persistence and UIs get every call. Appended inside the
|
2026-07-26 06:02:36 +08:00
|
|
|
|
* parent `run_code`'s execution (the bridge drains in-flight dispatches
|
|
|
|
|
|
* before returning), so the turn-enclosure invariant holds by
|
|
|
|
|
|
* construction.
|
feat: Code Mode — the registry's mode config, the SDK codegen, and the run_code bridge
The dsh-tools half of the Code Mode RFC (its fourth, final change): the
registry gains its first config — mode: native | code | both — and OWNS how
its tools reach the model. 'code' contributes exactly one wire tool,
run_code, plus a lazy tools:sdk prompt section declaring every other tool
as a generated TypeScript API (jsonSchemaToTs: total over the defineTool
subset, unknown degradation, lexicographic byte-identical rendering);
'both' ships both representations; 'native' is byte-for-byte the old
behavior. Non-native modes fail every assembly loudly without a
typescript-language ctx.codeRuntime.
run_code's dispatch bridge: JSON-normalizes each binding argument before
dispatch (what dispatches is what the tool/code-dispatch event logs — the
append can never fail on payload shape; BigInt/circulars reject that one
call), serializes all program tool calls through a per-run queue (even
Promise.all — no concurrency-safety metadata yet), routes every sub-call
through tools/pre-execute → tools/post-execute (a deny rejects the
program-side promise), drops sub-call additionalContext (no safe outlet
mid-run; pinned), owns a run-scoped abort that follows the outer signal in
and fires on settlement (in-flight sub-dispatch aborted, queued abandoned,
queue drained before returning), and converts a failed run into
CodeRunFailedError → a structured isError carrying kind + captured logs.
tool/code-dispatch joins SessionEventMap by declaration merging (log-only;
deriveMessages ignores it).
The composed surface: the tools config forwards through agent-core and
both app packages; examples/code-agent + demo:code run the worker runtime
under mode code (keyless boot smoke + a with-key e2e proving the collapsed
[run_code] header, the dispatch events, and the file the program wrote);
two new snapshot scenarios (code-mode-turn, both-mode-turn) record the SDK
section, collapsed header, dispatch events, and result card — each its own
header-pinning class (the harness gains per-scenario config overlays and
per-class pins). Catalogs, graphs, cookbook, hooks-bridge notes, and the
RFC (moved to implemented/, restructured to decision-era headings) updated
in the same change.
2026-07-08 12:58:23 +08:00
|
|
|
|
*/
|
2026-07-26 02:43:34 +08:00
|
|
|
|
'tool/code-dispatch': { parentCallId: CallId; subCallId: CallId; name: string; arguments: unknown; isError: boolean; content: ContentBlock[] }
|
feat: Code Mode — the registry's mode config, the SDK codegen, and the run_code bridge
The dsh-tools half of the Code Mode RFC (its fourth, final change): the
registry gains its first config — mode: native | code | both — and OWNS how
its tools reach the model. 'code' contributes exactly one wire tool,
run_code, plus a lazy tools:sdk prompt section declaring every other tool
as a generated TypeScript API (jsonSchemaToTs: total over the defineTool
subset, unknown degradation, lexicographic byte-identical rendering);
'both' ships both representations; 'native' is byte-for-byte the old
behavior. Non-native modes fail every assembly loudly without a
typescript-language ctx.codeRuntime.
run_code's dispatch bridge: JSON-normalizes each binding argument before
dispatch (what dispatches is what the tool/code-dispatch event logs — the
append can never fail on payload shape; BigInt/circulars reject that one
call), serializes all program tool calls through a per-run queue (even
Promise.all — no concurrency-safety metadata yet), routes every sub-call
through tools/pre-execute → tools/post-execute (a deny rejects the
program-side promise), drops sub-call additionalContext (no safe outlet
mid-run; pinned), owns a run-scoped abort that follows the outer signal in
and fires on settlement (in-flight sub-dispatch aborted, queued abandoned,
queue drained before returning), and converts a failed run into
CodeRunFailedError → a structured isError carrying kind + captured logs.
tool/code-dispatch joins SessionEventMap by declaration merging (log-only;
deriveMessages ignores it).
The composed surface: the tools config forwards through agent-core and
both app packages; examples/code-agent + demo:code run the worker runtime
under mode code (keyless boot smoke + a with-key e2e proving the collapsed
[run_code] header, the dispatch events, and the file the program wrote);
two new snapshot scenarios (code-mode-turn, both-mode-turn) record the SDK
section, collapsed header, dispatch events, and result card — each its own
header-pinning class (the harness gains per-scenario config overlays and
per-class pins). Catalogs, graphs, cookbook, hooks-bridge notes, and the
RFC (moved to implemented/, restructured to decision-era headings) updated
in the same change.
2026-07-08 12:58:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** The model-facing name of the Code Mode tool. */
|
|
|
|
|
|
export const RUN_CODE_NAME = 'run_code'
|
|
|
|
|
|
|
|
|
|
|
|
/** The `tools:sdk` section order: inside the 100–199 tool-guidance band, after per-tool guidance sections. */
|
|
|
|
|
|
export const SDK_SECTION_ORDER = 150
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Thrown by `run_code` when the program run itself failed — a program
|
|
|
|
|
|
* exception, a budget expiry, an abort, or substrate death. Extends
|
|
|
|
|
|
* {@link HarnessError} (`code: 'CODE_RUN_FAILED'`); the registry's execution
|
|
|
|
|
|
* pipeline converts it into a structured `isError` result whose text carries
|
|
|
|
|
|
* the failure kind plus the captured logs, so the model can self-correct.
|
|
|
|
|
|
*/
|
|
|
|
|
|
export class CodeRunFailedError extends HarnessError {
|
|
|
|
|
|
constructor(message: string) {
|
|
|
|
|
|
super(message, 'CODE_RUN_FAILED')
|
|
|
|
|
|
this.name = 'CodeRunFailedError'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2026-07-23 00:30:36 +08:00
|
|
|
|
* Snapshot one binding call's argument as lossless JSON, then snapshot that
|
|
|
|
|
|
* detached value again so dispatch and logging stay independent without
|
|
|
|
|
|
* reintroducing structured-clone's platform-specific nesting limit.
|
feat: Code Mode — the registry's mode config, the SDK codegen, and the run_code bridge
The dsh-tools half of the Code Mode RFC (its fourth, final change): the
registry gains its first config — mode: native | code | both — and OWNS how
its tools reach the model. 'code' contributes exactly one wire tool,
run_code, plus a lazy tools:sdk prompt section declaring every other tool
as a generated TypeScript API (jsonSchemaToTs: total over the defineTool
subset, unknown degradation, lexicographic byte-identical rendering);
'both' ships both representations; 'native' is byte-for-byte the old
behavior. Non-native modes fail every assembly loudly without a
typescript-language ctx.codeRuntime.
run_code's dispatch bridge: JSON-normalizes each binding argument before
dispatch (what dispatches is what the tool/code-dispatch event logs — the
append can never fail on payload shape; BigInt/circulars reject that one
call), serializes all program tool calls through a per-run queue (even
Promise.all — no concurrency-safety metadata yet), routes every sub-call
through tools/pre-execute → tools/post-execute (a deny rejects the
program-side promise), drops sub-call additionalContext (no safe outlet
mid-run; pinned), owns a run-scoped abort that follows the outer signal in
and fires on settlement (in-flight sub-dispatch aborted, queued abandoned,
queue drained before returning), and converts a failed run into
CodeRunFailedError → a structured isError carrying kind + captured logs.
tool/code-dispatch joins SessionEventMap by declaration merging (log-only;
deriveMessages ignores it).
The composed surface: the tools config forwards through agent-core and
both app packages; examples/code-agent + demo:code run the worker runtime
under mode code (keyless boot smoke + a with-key e2e proving the collapsed
[run_code] header, the dispatch events, and the file the program wrote);
two new snapshot scenarios (code-mode-turn, both-mode-turn) record the SDK
section, collapsed header, dispatch events, and result card — each its own
header-pinning class (the harness gains per-scenario config overlays and
per-class pins). Catalogs, graphs, cookbook, hooks-bridge notes, and the
RFC (moved to implemented/, restructured to decision-era headings) updated
in the same change.
2026-07-08 12:58:23 +08:00
|
|
|
|
*/
|
fix: pre-dispatch rejection of unloggable args, mutation-proof event copies, proto-safe bindings (Codex round 1)
Three findings from the PR-4 convergence round:
(A) A root-undefined binding argument passed normalization untouched, so
the sub-call DISPATCHED and only then failed the tool/code-dispatch append
(Session.append rejects undefined event data) — a sub-call executed with
no log record, violating the nothing-executes-unlogged contract. And the
tool received the SAME object later handed to the append, so a tool
mutating its args desynced the logged record from what was dispatched (or
re-poisoned the append). jsonNormalizeArgs now rejects undefined up front
with a model-correctable message and returns TWO independent parses of the
canonical JSON text: the tool gets one, the event logs the sibling —
identical by construction, mutation-proof.
(B) The bridge built its bindings record with plain-object assignment, so
a registered tool named __proto__ hit the prototype setter and silently
vanished (the runtime host resolves binding names as own properties). The
record is now null-prototype with defineProperty, mirroring the
worker-side namespace build.
(B) The header-pin sanity assertions ran only inside NON-pinning
scenarios, so a class consisting solely of its pinning scenario (the two
Code Mode classes) would accept a re-recorded pin carrying several headers
or a header-delta. A fixtures meta-test now asserts every pinning fixture
directly.
2026-07-08 13:39:51 +08:00
|
|
|
|
function jsonNormalizeArgs(value: unknown): { dispatched: unknown; logged: unknown } {
|
2026-07-21 04:34:14 +08:00
|
|
|
|
let snapshot: JsonValue | undefined
|
feat: Code Mode — the registry's mode config, the SDK codegen, and the run_code bridge
The dsh-tools half of the Code Mode RFC (its fourth, final change): the
registry gains its first config — mode: native | code | both — and OWNS how
its tools reach the model. 'code' contributes exactly one wire tool,
run_code, plus a lazy tools:sdk prompt section declaring every other tool
as a generated TypeScript API (jsonSchemaToTs: total over the defineTool
subset, unknown degradation, lexicographic byte-identical rendering);
'both' ships both representations; 'native' is byte-for-byte the old
behavior. Non-native modes fail every assembly loudly without a
typescript-language ctx.codeRuntime.
run_code's dispatch bridge: JSON-normalizes each binding argument before
dispatch (what dispatches is what the tool/code-dispatch event logs — the
append can never fail on payload shape; BigInt/circulars reject that one
call), serializes all program tool calls through a per-run queue (even
Promise.all — no concurrency-safety metadata yet), routes every sub-call
through tools/pre-execute → tools/post-execute (a deny rejects the
program-side promise), drops sub-call additionalContext (no safe outlet
mid-run; pinned), owns a run-scoped abort that follows the outer signal in
and fires on settlement (in-flight sub-dispatch aborted, queued abandoned,
queue drained before returning), and converts a failed run into
CodeRunFailedError → a structured isError carrying kind + captured logs.
tool/code-dispatch joins SessionEventMap by declaration merging (log-only;
deriveMessages ignores it).
The composed surface: the tools config forwards through agent-core and
both app packages; examples/code-agent + demo:code run the worker runtime
under mode code (keyless boot smoke + a with-key e2e proving the collapsed
[run_code] header, the dispatch events, and the file the program wrote);
two new snapshot scenarios (code-mode-turn, both-mode-turn) record the SDK
section, collapsed header, dispatch events, and result card — each its own
header-pinning class (the harness gains per-scenario config overlays and
per-class pins). Catalogs, graphs, cookbook, hooks-bridge notes, and the
RFC (moved to implemented/, restructured to decision-era headings) updated
in the same change.
2026-07-08 12:58:23 +08:00
|
|
|
|
try {
|
2026-07-21 04:34:14 +08:00
|
|
|
|
snapshot = snapshotJsonValue(value) as JsonValue | undefined
|
feat: Code Mode — the registry's mode config, the SDK codegen, and the run_code bridge
The dsh-tools half of the Code Mode RFC (its fourth, final change): the
registry gains its first config — mode: native | code | both — and OWNS how
its tools reach the model. 'code' contributes exactly one wire tool,
run_code, plus a lazy tools:sdk prompt section declaring every other tool
as a generated TypeScript API (jsonSchemaToTs: total over the defineTool
subset, unknown degradation, lexicographic byte-identical rendering);
'both' ships both representations; 'native' is byte-for-byte the old
behavior. Non-native modes fail every assembly loudly without a
typescript-language ctx.codeRuntime.
run_code's dispatch bridge: JSON-normalizes each binding argument before
dispatch (what dispatches is what the tool/code-dispatch event logs — the
append can never fail on payload shape; BigInt/circulars reject that one
call), serializes all program tool calls through a per-run queue (even
Promise.all — no concurrency-safety metadata yet), routes every sub-call
through tools/pre-execute → tools/post-execute (a deny rejects the
program-side promise), drops sub-call additionalContext (no safe outlet
mid-run; pinned), owns a run-scoped abort that follows the outer signal in
and fires on settlement (in-flight sub-dispatch aborted, queued abandoned,
queue drained before returning), and converts a failed run into
CodeRunFailedError → a structured isError carrying kind + captured logs.
tool/code-dispatch joins SessionEventMap by declaration merging (log-only;
deriveMessages ignores it).
The composed surface: the tools config forwards through agent-core and
both app packages; examples/code-agent + demo:code run the worker runtime
under mode code (keyless boot smoke + a with-key e2e proving the collapsed
[run_code] header, the dispatch events, and the file the program wrote);
two new snapshot scenarios (code-mode-turn, both-mode-turn) record the SDK
section, collapsed header, dispatch events, and result card — each its own
header-pinning class (the harness gains per-scenario config overlays and
per-class pins). Catalogs, graphs, cookbook, hooks-bridge notes, and the
RFC (moved to implemented/, restructured to decision-era headings) updated
in the same change.
2026-07-08 12:58:23 +08:00
|
|
|
|
} catch (error: unknown) {
|
2026-07-21 04:34:14 +08:00
|
|
|
|
throw new Error(`tool arguments must be lossless JSON: ${error instanceof Error ? error.message : String(error)}`)
|
|
|
|
|
|
}
|
|
|
|
|
|
if (snapshot === undefined) {
|
|
|
|
|
|
throw new Error('tool arguments must be lossless JSON (call the tool with an arguments object, e.g. `{}`)')
|
feat: Code Mode — the registry's mode config, the SDK codegen, and the run_code bridge
The dsh-tools half of the Code Mode RFC (its fourth, final change): the
registry gains its first config — mode: native | code | both — and OWNS how
its tools reach the model. 'code' contributes exactly one wire tool,
run_code, plus a lazy tools:sdk prompt section declaring every other tool
as a generated TypeScript API (jsonSchemaToTs: total over the defineTool
subset, unknown degradation, lexicographic byte-identical rendering);
'both' ships both representations; 'native' is byte-for-byte the old
behavior. Non-native modes fail every assembly loudly without a
typescript-language ctx.codeRuntime.
run_code's dispatch bridge: JSON-normalizes each binding argument before
dispatch (what dispatches is what the tool/code-dispatch event logs — the
append can never fail on payload shape; BigInt/circulars reject that one
call), serializes all program tool calls through a per-run queue (even
Promise.all — no concurrency-safety metadata yet), routes every sub-call
through tools/pre-execute → tools/post-execute (a deny rejects the
program-side promise), drops sub-call additionalContext (no safe outlet
mid-run; pinned), owns a run-scoped abort that follows the outer signal in
and fires on settlement (in-flight sub-dispatch aborted, queued abandoned,
queue drained before returning), and converts a failed run into
CodeRunFailedError → a structured isError carrying kind + captured logs.
tool/code-dispatch joins SessionEventMap by declaration merging (log-only;
deriveMessages ignores it).
The composed surface: the tools config forwards through agent-core and
both app packages; examples/code-agent + demo:code run the worker runtime
under mode code (keyless boot smoke + a with-key e2e proving the collapsed
[run_code] header, the dispatch events, and the file the program wrote);
two new snapshot scenarios (code-mode-turn, both-mode-turn) record the SDK
section, collapsed header, dispatch events, and result card — each its own
header-pinning class (the harness gains per-scenario config overlays and
per-class pins). Catalogs, graphs, cookbook, hooks-bridge notes, and the
RFC (moved to implemented/, restructured to decision-era headings) updated
in the same change.
2026-07-08 12:58:23 +08:00
|
|
|
|
}
|
2026-07-23 00:30:36 +08:00
|
|
|
|
const logged = snapshotJsonValue(snapshot)
|
|
|
|
|
|
/* v8 ignore next -- snapshot is already a detached lossless JSON value. */
|
|
|
|
|
|
if (logged === undefined) {
|
|
|
|
|
|
throw new Error('tool arguments could not be detached for durable logging')
|
|
|
|
|
|
}
|
|
|
|
|
|
return { dispatched: snapshot, logged }
|
feat: Code Mode — the registry's mode config, the SDK codegen, and the run_code bridge
The dsh-tools half of the Code Mode RFC (its fourth, final change): the
registry gains its first config — mode: native | code | both — and OWNS how
its tools reach the model. 'code' contributes exactly one wire tool,
run_code, plus a lazy tools:sdk prompt section declaring every other tool
as a generated TypeScript API (jsonSchemaToTs: total over the defineTool
subset, unknown degradation, lexicographic byte-identical rendering);
'both' ships both representations; 'native' is byte-for-byte the old
behavior. Non-native modes fail every assembly loudly without a
typescript-language ctx.codeRuntime.
run_code's dispatch bridge: JSON-normalizes each binding argument before
dispatch (what dispatches is what the tool/code-dispatch event logs — the
append can never fail on payload shape; BigInt/circulars reject that one
call), serializes all program tool calls through a per-run queue (even
Promise.all — no concurrency-safety metadata yet), routes every sub-call
through tools/pre-execute → tools/post-execute (a deny rejects the
program-side promise), drops sub-call additionalContext (no safe outlet
mid-run; pinned), owns a run-scoped abort that follows the outer signal in
and fires on settlement (in-flight sub-dispatch aborted, queued abandoned,
queue drained before returning), and converts a failed run into
CodeRunFailedError → a structured isError carrying kind + captured logs.
tool/code-dispatch joins SessionEventMap by declaration merging (log-only;
deriveMessages ignores it).
The composed surface: the tools config forwards through agent-core and
both app packages; examples/code-agent + demo:code run the worker runtime
under mode code (keyless boot smoke + a with-key e2e proving the collapsed
[run_code] header, the dispatch events, and the file the program wrote);
two new snapshot scenarios (code-mode-turn, both-mode-turn) record the SDK
section, collapsed header, dispatch events, and result card — each its own
header-pinning class (the harness gains per-scenario config overlays and
per-class pins). Catalogs, graphs, cookbook, hooks-bridge notes, and the
RFC (moved to implemented/, restructured to decision-era headings) updated
in the same change.
2026-07-08 12:58:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-22 19:57:44 +08:00
|
|
|
|
/** Two-space JSON presentation, matching the existing shallow `run_code` text contract. */
|
|
|
|
|
|
const JSON_INDENT = ' '
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ECMAScript caps `JSON.stringify`'s `space` string at ten characters. The
|
|
|
|
|
|
* renderer also caps TOTAL indentation there, compacting deeper subtrees, so
|
|
|
|
|
|
* formatted output remains linear in the canonical JSON size.
|
|
|
|
|
|
*/
|
|
|
|
|
|
const MAX_JSON_INDENT_CHARS = 10
|
|
|
|
|
|
|
|
|
|
|
|
/** A pending fragment in the iterative JSON presentation traversal. */
|
|
|
|
|
|
type JsonRenderTask =
|
|
|
|
|
|
| { kind: 'text'; text: string }
|
|
|
|
|
|
| { kind: 'value'; value: JsonValue; depth: number; compact: boolean }
|
|
|
|
|
|
|
|
|
|
|
|
/** Render one non-string JSON root without recursive traversal or unbounded indentation growth. */
|
|
|
|
|
|
function renderJsonValue(value: Exclude<JsonValue, string>): string {
|
|
|
|
|
|
const chunks: string[] = []
|
|
|
|
|
|
const tasks: JsonRenderTask[] = [{ kind: 'value', value, depth: 0, compact: false }]
|
|
|
|
|
|
for (let task = tasks.pop(); task !== undefined; task = tasks.pop()) {
|
|
|
|
|
|
if (task.kind === 'text') {
|
|
|
|
|
|
chunks.push(task.text)
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const current = task.value
|
|
|
|
|
|
if (current === null || typeof current === 'boolean' || typeof current === 'number') {
|
|
|
|
|
|
chunks.push(String(current))
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
if (typeof current === 'string') {
|
|
|
|
|
|
chunks.push(JSON.stringify(current))
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const compact = task.compact || (task.depth + 1) * JSON_INDENT.length > MAX_JSON_INDENT_CHARS
|
|
|
|
|
|
const childDepth = task.depth + 1
|
|
|
|
|
|
if (Array.isArray(current)) {
|
|
|
|
|
|
chunks.push('[')
|
|
|
|
|
|
if (current.length === 0) {
|
|
|
|
|
|
chunks.push(']')
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
tasks.push({ kind: 'text', text: compact ? ']' : `\n${JSON_INDENT.repeat(task.depth)}]` })
|
|
|
|
|
|
for (let index = current.length - 1; index >= 0; index--) {
|
|
|
|
|
|
const item = current[index]
|
|
|
|
|
|
/* v8 ignore next -- canonical JsonValue arrays are dense. */
|
|
|
|
|
|
if (item === undefined) throw new Error('cannot render a sparse JSON array')
|
|
|
|
|
|
tasks.push({ kind: 'value', value: item, depth: childDepth, compact })
|
|
|
|
|
|
tasks.push({
|
|
|
|
|
|
kind: 'text',
|
|
|
|
|
|
text: compact
|
|
|
|
|
|
? index === 0 ? '' : ','
|
|
|
|
|
|
: `${index === 0 ? '\n' : ',\n'}${JSON_INDENT.repeat(childDepth)}`,
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const keys = Object.keys(current)
|
|
|
|
|
|
chunks.push('{')
|
|
|
|
|
|
if (keys.length === 0) {
|
|
|
|
|
|
chunks.push('}')
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
tasks.push({ kind: 'text', text: compact ? '}' : `\n${JSON_INDENT.repeat(task.depth)}}` })
|
|
|
|
|
|
for (let index = keys.length - 1; index >= 0; index--) {
|
|
|
|
|
|
const key = keys[index]
|
|
|
|
|
|
/* v8 ignore next -- the loop is bounded by the captured key count. */
|
|
|
|
|
|
if (key === undefined) throw new Error('cannot render a missing JSON object key')
|
|
|
|
|
|
const item = current[key]
|
|
|
|
|
|
/* v8 ignore next -- canonical JsonValue records contain no undefined properties. */
|
|
|
|
|
|
if (item === undefined) throw new Error('cannot render an undefined JSON object property')
|
|
|
|
|
|
tasks.push({ kind: 'value', value: item, depth: childDepth, compact })
|
|
|
|
|
|
tasks.push({
|
|
|
|
|
|
kind: 'text',
|
|
|
|
|
|
text: compact
|
|
|
|
|
|
? `${index === 0 ? '' : ','}${JSON.stringify(key)}:`
|
|
|
|
|
|
: `${index === 0 ? '\n' : ',\n'}${JSON_INDENT.repeat(childDepth)}${JSON.stringify(key)}: `,
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return chunks.join('')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-21 03:08:35 +08:00
|
|
|
|
/** Render one present program completion value for the model-facing result text. */
|
|
|
|
|
|
function renderValue(value: JsonValue): string {
|
2026-07-22 19:57:44 +08:00
|
|
|
|
return typeof value === 'string' ? value : renderJsonValue(value)
|
feat: Code Mode — the registry's mode config, the SDK codegen, and the run_code bridge
The dsh-tools half of the Code Mode RFC (its fourth, final change): the
registry gains its first config — mode: native | code | both — and OWNS how
its tools reach the model. 'code' contributes exactly one wire tool,
run_code, plus a lazy tools:sdk prompt section declaring every other tool
as a generated TypeScript API (jsonSchemaToTs: total over the defineTool
subset, unknown degradation, lexicographic byte-identical rendering);
'both' ships both representations; 'native' is byte-for-byte the old
behavior. Non-native modes fail every assembly loudly without a
typescript-language ctx.codeRuntime.
run_code's dispatch bridge: JSON-normalizes each binding argument before
dispatch (what dispatches is what the tool/code-dispatch event logs — the
append can never fail on payload shape; BigInt/circulars reject that one
call), serializes all program tool calls through a per-run queue (even
Promise.all — no concurrency-safety metadata yet), routes every sub-call
through tools/pre-execute → tools/post-execute (a deny rejects the
program-side promise), drops sub-call additionalContext (no safe outlet
mid-run; pinned), owns a run-scoped abort that follows the outer signal in
and fires on settlement (in-flight sub-dispatch aborted, queued abandoned,
queue drained before returning), and converts a failed run into
CodeRunFailedError → a structured isError carrying kind + captured logs.
tool/code-dispatch joins SessionEventMap by declaration merging (log-only;
deriveMessages ignores it).
The composed surface: the tools config forwards through agent-core and
both app packages; examples/code-agent + demo:code run the worker runtime
under mode code (keyless boot smoke + a with-key e2e proving the collapsed
[run_code] header, the dispatch events, and the file the program wrote);
two new snapshot scenarios (code-mode-turn, both-mode-turn) record the SDK
section, collapsed header, dispatch events, and result card — each its own
header-pinning class (the harness gains per-scenario config overlays and
per-class pins). Catalogs, graphs, cookbook, hooks-bridge notes, and the
RFC (moved to implemented/, restructured to decision-era headings) updated
in the same change.
2026-07-08 12:58:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-21 03:08:35 +08:00
|
|
|
|
/** Canonical value returned by the outer Code Mode transport. */
|
|
|
|
|
|
type RunCodeOutput = { logs: string[]; result?: JsonValue }
|
|
|
|
|
|
|
feat: Code Mode — the registry's mode config, the SDK codegen, and the run_code bridge
The dsh-tools half of the Code Mode RFC (its fourth, final change): the
registry gains its first config — mode: native | code | both — and OWNS how
its tools reach the model. 'code' contributes exactly one wire tool,
run_code, plus a lazy tools:sdk prompt section declaring every other tool
as a generated TypeScript API (jsonSchemaToTs: total over the defineTool
subset, unknown degradation, lexicographic byte-identical rendering);
'both' ships both representations; 'native' is byte-for-byte the old
behavior. Non-native modes fail every assembly loudly without a
typescript-language ctx.codeRuntime.
run_code's dispatch bridge: JSON-normalizes each binding argument before
dispatch (what dispatches is what the tool/code-dispatch event logs — the
append can never fail on payload shape; BigInt/circulars reject that one
call), serializes all program tool calls through a per-run queue (even
Promise.all — no concurrency-safety metadata yet), routes every sub-call
through tools/pre-execute → tools/post-execute (a deny rejects the
program-side promise), drops sub-call additionalContext (no safe outlet
mid-run; pinned), owns a run-scoped abort that follows the outer signal in
and fires on settlement (in-flight sub-dispatch aborted, queued abandoned,
queue drained before returning), and converts a failed run into
CodeRunFailedError → a structured isError carrying kind + captured logs.
tool/code-dispatch joins SessionEventMap by declaration merging (log-only;
deriveMessages ignores it).
The composed surface: the tools config forwards through agent-core and
both app packages; examples/code-agent + demo:code run the worker runtime
under mode code (keyless boot smoke + a with-key e2e proving the collapsed
[run_code] header, the dispatch events, and the file the program wrote);
two new snapshot scenarios (code-mode-turn, both-mode-turn) record the SDK
section, collapsed header, dispatch events, and result card — each its own
header-pinning class (the harness gains per-scenario config overlays and
per-class pins). Catalogs, graphs, cookbook, hooks-bridge notes, and the
RFC (moved to implemented/, restructured to decision-era headings) updated
in the same change.
2026-07-08 12:58:23 +08:00
|
|
|
|
/**
|
2026-07-26 10:18:53 +08:00
|
|
|
|
* Build the `run_code` {@link ToolDefinition}: required `code` and
|
|
|
|
|
|
* `description` parameters, executed through the dispatch bridge described
|
|
|
|
|
|
* above. The
|
2026-07-11 20:47:45 +08:00
|
|
|
|
* registry reserves it as presentation infrastructure under non-native modes,
|
|
|
|
|
|
* outside the filterable global/scoped capability layers.
|
feat: Code Mode — the registry's mode config, the SDK codegen, and the run_code bridge
The dsh-tools half of the Code Mode RFC (its fourth, final change): the
registry gains its first config — mode: native | code | both — and OWNS how
its tools reach the model. 'code' contributes exactly one wire tool,
run_code, plus a lazy tools:sdk prompt section declaring every other tool
as a generated TypeScript API (jsonSchemaToTs: total over the defineTool
subset, unknown degradation, lexicographic byte-identical rendering);
'both' ships both representations; 'native' is byte-for-byte the old
behavior. Non-native modes fail every assembly loudly without a
typescript-language ctx.codeRuntime.
run_code's dispatch bridge: JSON-normalizes each binding argument before
dispatch (what dispatches is what the tool/code-dispatch event logs — the
append can never fail on payload shape; BigInt/circulars reject that one
call), serializes all program tool calls through a per-run queue (even
Promise.all — no concurrency-safety metadata yet), routes every sub-call
through tools/pre-execute → tools/post-execute (a deny rejects the
program-side promise), drops sub-call additionalContext (no safe outlet
mid-run; pinned), owns a run-scoped abort that follows the outer signal in
and fires on settlement (in-flight sub-dispatch aborted, queued abandoned,
queue drained before returning), and converts a failed run into
CodeRunFailedError → a structured isError carrying kind + captured logs.
tool/code-dispatch joins SessionEventMap by declaration merging (log-only;
deriveMessages ignores it).
The composed surface: the tools config forwards through agent-core and
both app packages; examples/code-agent + demo:code run the worker runtime
under mode code (keyless boot smoke + a with-key e2e proving the collapsed
[run_code] header, the dispatch events, and the file the program wrote);
two new snapshot scenarios (code-mode-turn, both-mode-turn) record the SDK
section, collapsed header, dispatch events, and result card — each its own
header-pinning class (the harness gains per-scenario config overlays and
per-class pins). Catalogs, graphs, cookbook, hooks-bridge notes, and the
RFC (moved to implemented/, restructured to decision-era headings) updated
in the same change.
2026-07-08 12:58:23 +08:00
|
|
|
|
* @param registry - the owning registry (sub-calls go through its `execute`,
|
|
|
|
|
|
* bindings cover its registered tools).
|
|
|
|
|
|
* @param requireRuntime - resolves `ctx.codeRuntime` or throws the loud
|
|
|
|
|
|
* misconfiguration error (shared with the registry's assembly-time checks).
|
2026-07-26 06:02:36 +08:00
|
|
|
|
* @param maxParallel - the run's overlap cap for parallel-classified
|
|
|
|
|
|
* sub-calls (the registry passes its validated `maxParallelSubCalls`).
|
feat: Code Mode — the registry's mode config, the SDK codegen, and the run_code bridge
The dsh-tools half of the Code Mode RFC (its fourth, final change): the
registry gains its first config — mode: native | code | both — and OWNS how
its tools reach the model. 'code' contributes exactly one wire tool,
run_code, plus a lazy tools:sdk prompt section declaring every other tool
as a generated TypeScript API (jsonSchemaToTs: total over the defineTool
subset, unknown degradation, lexicographic byte-identical rendering);
'both' ships both representations; 'native' is byte-for-byte the old
behavior. Non-native modes fail every assembly loudly without a
typescript-language ctx.codeRuntime.
run_code's dispatch bridge: JSON-normalizes each binding argument before
dispatch (what dispatches is what the tool/code-dispatch event logs — the
append can never fail on payload shape; BigInt/circulars reject that one
call), serializes all program tool calls through a per-run queue (even
Promise.all — no concurrency-safety metadata yet), routes every sub-call
through tools/pre-execute → tools/post-execute (a deny rejects the
program-side promise), drops sub-call additionalContext (no safe outlet
mid-run; pinned), owns a run-scoped abort that follows the outer signal in
and fires on settlement (in-flight sub-dispatch aborted, queued abandoned,
queue drained before returning), and converts a failed run into
CodeRunFailedError → a structured isError carrying kind + captured logs.
tool/code-dispatch joins SessionEventMap by declaration merging (log-only;
deriveMessages ignores it).
The composed surface: the tools config forwards through agent-core and
both app packages; examples/code-agent + demo:code run the worker runtime
under mode code (keyless boot smoke + a with-key e2e proving the collapsed
[run_code] header, the dispatch events, and the file the program wrote);
two new snapshot scenarios (code-mode-turn, both-mode-turn) record the SDK
section, collapsed header, dispatch events, and result card — each its own
header-pinning class (the harness gains per-scenario config overlays and
per-class pins). Catalogs, graphs, cookbook, hooks-bridge notes, and the
RFC (moved to implemented/, restructured to decision-era headings) updated
in the same change.
2026-07-08 12:58:23 +08:00
|
|
|
|
* @returns the registry-ready definition.
|
|
|
|
|
|
*/
|
2026-07-26 06:02:36 +08:00
|
|
|
|
export function createRunCodeTool(registry: ToolRegistry, requireRuntime: () => CodeRuntime, maxParallel: number): ToolDefinition {
|
feat: Code Mode — the registry's mode config, the SDK codegen, and the run_code bridge
The dsh-tools half of the Code Mode RFC (its fourth, final change): the
registry gains its first config — mode: native | code | both — and OWNS how
its tools reach the model. 'code' contributes exactly one wire tool,
run_code, plus a lazy tools:sdk prompt section declaring every other tool
as a generated TypeScript API (jsonSchemaToTs: total over the defineTool
subset, unknown degradation, lexicographic byte-identical rendering);
'both' ships both representations; 'native' is byte-for-byte the old
behavior. Non-native modes fail every assembly loudly without a
typescript-language ctx.codeRuntime.
run_code's dispatch bridge: JSON-normalizes each binding argument before
dispatch (what dispatches is what the tool/code-dispatch event logs — the
append can never fail on payload shape; BigInt/circulars reject that one
call), serializes all program tool calls through a per-run queue (even
Promise.all — no concurrency-safety metadata yet), routes every sub-call
through tools/pre-execute → tools/post-execute (a deny rejects the
program-side promise), drops sub-call additionalContext (no safe outlet
mid-run; pinned), owns a run-scoped abort that follows the outer signal in
and fires on settlement (in-flight sub-dispatch aborted, queued abandoned,
queue drained before returning), and converts a failed run into
CodeRunFailedError → a structured isError carrying kind + captured logs.
tool/code-dispatch joins SessionEventMap by declaration merging (log-only;
deriveMessages ignores it).
The composed surface: the tools config forwards through agent-core and
both app packages; examples/code-agent + demo:code run the worker runtime
under mode code (keyless boot smoke + a with-key e2e proving the collapsed
[run_code] header, the dispatch events, and the file the program wrote);
two new snapshot scenarios (code-mode-turn, both-mode-turn) record the SDK
section, collapsed header, dispatch events, and result card — each its own
header-pinning class (the harness gains per-scenario config overlays and
per-class pins). Catalogs, graphs, cookbook, hooks-bridge notes, and the
RFC (moved to implemented/, restructured to decision-era headings) updated
in the same change.
2026-07-08 12:58:23 +08:00
|
|
|
|
return defineTool({
|
|
|
|
|
|
name: RUN_CODE_NAME,
|
|
|
|
|
|
description:
|
|
|
|
|
|
'Execute a TypeScript program against the available tools. Write the BODY of an '
|
|
|
|
|
|
+ 'async function (erasable syntax only; top-level `await` and `return` work) and '
|
|
|
|
|
|
+ 'call tools as `await tools.name(args)` per the declarations in the system prompt. '
|
|
|
|
|
|
+ 'Only what you print or return comes back — curate it.',
|
|
|
|
|
|
parameters: {
|
|
|
|
|
|
code: { type: 'string', required: true, description: 'The program: the body of an async TypeScript function.' },
|
2026-07-26 02:43:34 +08:00
|
|
|
|
description: {
|
|
|
|
|
|
type: 'string',
|
|
|
|
|
|
required: true,
|
|
|
|
|
|
description: 'Clear, concise description of what this program does in active voice, '
|
|
|
|
|
|
+ '5-10 words (shown in the UI). Examples: "Count TODO markers across packages"; '
|
|
|
|
|
|
+ '"Read failing test and its fixture"; "Rename config key in every cordis.yml".',
|
|
|
|
|
|
},
|
feat: Code Mode — the registry's mode config, the SDK codegen, and the run_code bridge
The dsh-tools half of the Code Mode RFC (its fourth, final change): the
registry gains its first config — mode: native | code | both — and OWNS how
its tools reach the model. 'code' contributes exactly one wire tool,
run_code, plus a lazy tools:sdk prompt section declaring every other tool
as a generated TypeScript API (jsonSchemaToTs: total over the defineTool
subset, unknown degradation, lexicographic byte-identical rendering);
'both' ships both representations; 'native' is byte-for-byte the old
behavior. Non-native modes fail every assembly loudly without a
typescript-language ctx.codeRuntime.
run_code's dispatch bridge: JSON-normalizes each binding argument before
dispatch (what dispatches is what the tool/code-dispatch event logs — the
append can never fail on payload shape; BigInt/circulars reject that one
call), serializes all program tool calls through a per-run queue (even
Promise.all — no concurrency-safety metadata yet), routes every sub-call
through tools/pre-execute → tools/post-execute (a deny rejects the
program-side promise), drops sub-call additionalContext (no safe outlet
mid-run; pinned), owns a run-scoped abort that follows the outer signal in
and fires on settlement (in-flight sub-dispatch aborted, queued abandoned,
queue drained before returning), and converts a failed run into
CodeRunFailedError → a structured isError carrying kind + captured logs.
tool/code-dispatch joins SessionEventMap by declaration merging (log-only;
deriveMessages ignores it).
The composed surface: the tools config forwards through agent-core and
both app packages; examples/code-agent + demo:code run the worker runtime
under mode code (keyless boot smoke + a with-key e2e proving the collapsed
[run_code] header, the dispatch events, and the file the program wrote);
two new snapshot scenarios (code-mode-turn, both-mode-turn) record the SDK
section, collapsed header, dispatch events, and result card — each its own
header-pinning class (the harness gains per-scenario config overlays and
per-class pins). Catalogs, graphs, cookbook, hooks-bridge notes, and the
RFC (moved to implemented/, restructured to decision-era headings) updated
in the same change.
2026-07-08 12:58:23 +08:00
|
|
|
|
},
|
2026-07-21 03:08:35 +08:00
|
|
|
|
output: {
|
|
|
|
|
|
schema: {
|
|
|
|
|
|
type: 'object',
|
|
|
|
|
|
additionalProperties: false,
|
|
|
|
|
|
properties: {
|
|
|
|
|
|
logs: { type: 'array', required: true, items: { type: 'string' } },
|
|
|
|
|
|
result: { type: 'json' },
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
render: (_args, value) => {
|
|
|
|
|
|
const rendered = value.result === undefined ? '' : renderValue(value.result)
|
|
|
|
|
|
const parts = [value.logs.join('\n'), rendered].filter(part => part.length > 0)
|
|
|
|
|
|
return [{ type: 'text', text: parts.length > 0 ? parts.join('\n') : '(run_code completed with no output)' }]
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
async execute(args, exec): Promise<RunCodeOutput> {
|
2026-07-26 02:43:34 +08:00
|
|
|
|
if (args.description.trim().length === 0) {
|
|
|
|
|
|
throw new Error('invalid description: expected a non-empty string')
|
|
|
|
|
|
}
|
feat: Code Mode — the registry's mode config, the SDK codegen, and the run_code bridge
The dsh-tools half of the Code Mode RFC (its fourth, final change): the
registry gains its first config — mode: native | code | both — and OWNS how
its tools reach the model. 'code' contributes exactly one wire tool,
run_code, plus a lazy tools:sdk prompt section declaring every other tool
as a generated TypeScript API (jsonSchemaToTs: total over the defineTool
subset, unknown degradation, lexicographic byte-identical rendering);
'both' ships both representations; 'native' is byte-for-byte the old
behavior. Non-native modes fail every assembly loudly without a
typescript-language ctx.codeRuntime.
run_code's dispatch bridge: JSON-normalizes each binding argument before
dispatch (what dispatches is what the tool/code-dispatch event logs — the
append can never fail on payload shape; BigInt/circulars reject that one
call), serializes all program tool calls through a per-run queue (even
Promise.all — no concurrency-safety metadata yet), routes every sub-call
through tools/pre-execute → tools/post-execute (a deny rejects the
program-side promise), drops sub-call additionalContext (no safe outlet
mid-run; pinned), owns a run-scoped abort that follows the outer signal in
and fires on settlement (in-flight sub-dispatch aborted, queued abandoned,
queue drained before returning), and converts a failed run into
CodeRunFailedError → a structured isError carrying kind + captured logs.
tool/code-dispatch joins SessionEventMap by declaration merging (log-only;
deriveMessages ignores it).
The composed surface: the tools config forwards through agent-core and
both app packages; examples/code-agent + demo:code run the worker runtime
under mode code (keyless boot smoke + a with-key e2e proving the collapsed
[run_code] header, the dispatch events, and the file the program wrote);
two new snapshot scenarios (code-mode-turn, both-mode-turn) record the SDK
section, collapsed header, dispatch events, and result card — each its own
header-pinning class (the harness gains per-scenario config overlays and
per-class pins). Catalogs, graphs, cookbook, hooks-bridge notes, and the
RFC (moved to implemented/, restructured to decision-era headings) updated
in the same change.
2026-07-08 12:58:23 +08:00
|
|
|
|
const runtime = requireRuntime()
|
|
|
|
|
|
|
|
|
|
|
|
// The run-scoped abort: follows the outer signal in, and fires when the
|
|
|
|
|
|
// run settles for ANY reason, so an in-flight sub-dispatch is aborted
|
|
|
|
|
|
// (its executor kills on this signal) instead of orphaned, and
|
|
|
|
|
|
// queued-unstarted dispatches are abandoned.
|
|
|
|
|
|
const runController = new AbortController()
|
2026-07-19 23:38:54 +08:00
|
|
|
|
const onOuterAbort = (): void => { runController.abort(exec.signal.reason) }
|
|
|
|
|
|
exec.signal.addEventListener('abort', onOuterAbort, { once: true })
|
feat: Code Mode — the registry's mode config, the SDK codegen, and the run_code bridge
The dsh-tools half of the Code Mode RFC (its fourth, final change): the
registry gains its first config — mode: native | code | both — and OWNS how
its tools reach the model. 'code' contributes exactly one wire tool,
run_code, plus a lazy tools:sdk prompt section declaring every other tool
as a generated TypeScript API (jsonSchemaToTs: total over the defineTool
subset, unknown degradation, lexicographic byte-identical rendering);
'both' ships both representations; 'native' is byte-for-byte the old
behavior. Non-native modes fail every assembly loudly without a
typescript-language ctx.codeRuntime.
run_code's dispatch bridge: JSON-normalizes each binding argument before
dispatch (what dispatches is what the tool/code-dispatch event logs — the
append can never fail on payload shape; BigInt/circulars reject that one
call), serializes all program tool calls through a per-run queue (even
Promise.all — no concurrency-safety metadata yet), routes every sub-call
through tools/pre-execute → tools/post-execute (a deny rejects the
program-side promise), drops sub-call additionalContext (no safe outlet
mid-run; pinned), owns a run-scoped abort that follows the outer signal in
and fires on settlement (in-flight sub-dispatch aborted, queued abandoned,
queue drained before returning), and converts a failed run into
CodeRunFailedError → a structured isError carrying kind + captured logs.
tool/code-dispatch joins SessionEventMap by declaration merging (log-only;
deriveMessages ignores it).
The composed surface: the tools config forwards through agent-core and
both app packages; examples/code-agent + demo:code run the worker runtime
under mode code (keyless boot smoke + a with-key e2e proving the collapsed
[run_code] header, the dispatch events, and the file the program wrote);
two new snapshot scenarios (code-mode-turn, both-mode-turn) record the SDK
section, collapsed header, dispatch events, and result card — each its own
header-pinning class (the harness gains per-scenario config overlays and
per-class pins). Catalogs, graphs, cookbook, hooks-bridge notes, and the
RFC (moved to implemented/, restructured to decision-era headings) updated
in the same change.
2026-07-08 12:58:23 +08:00
|
|
|
|
|
|
|
|
|
|
let dispatches = 0
|
2026-07-26 10:33:48 +08:00
|
|
|
|
// The per-run scheduler, reusing the NATIVE concurrency contract through
|
|
|
|
|
|
// the registry's staged view (the loop scheduler's own seam): submitted
|
|
|
|
|
|
// calls START strictly in submission order; only the around-dispatch/body
|
|
|
|
|
|
// stage overlaps — ordered pre-execute runs at start time and ordered
|
|
|
|
|
|
// post-execute/context commitment runs in submission order through the
|
|
|
|
|
|
// commit cursor below, so stateful policy listeners observe submission
|
|
|
|
|
|
// order exactly as they do under the native loop. Consecutive
|
|
|
|
|
|
// parallel-classified calls overlap up to maxParallel; an exclusive call
|
|
|
|
|
|
// waits for the pool to drain, runs alone, and bars later calls.
|
|
|
|
|
|
// Classification is re-read via executionMode() immediately before each
|
|
|
|
|
|
// start (a registry mutation while queued can flip a call exclusive),
|
|
|
|
|
|
// matching the native scheduler's lazy reclassification.
|
2026-07-26 06:02:36 +08:00
|
|
|
|
interface PendingDispatch {
|
2026-07-26 10:33:48 +08:00
|
|
|
|
/** Ordered stage: append the start event, prepare, dispatch (body overlaps), park for commit. */
|
|
|
|
|
|
start(): Promise<void>
|
|
|
|
|
|
classify(): 'parallel' | 'exclusive'
|
2026-07-26 06:02:36 +08:00
|
|
|
|
abandon(): void
|
2026-07-26 10:33:48 +08:00
|
|
|
|
/** Ordered stage: post-execute + context deferral + settle event, in submission order. */
|
|
|
|
|
|
commit(): Promise<void>
|
|
|
|
|
|
/** Set once the dispatch stage settles; commit() runs after this resolves. */
|
|
|
|
|
|
dispatched?: Promise<void>
|
2026-07-26 06:02:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
const pendingQueue: PendingDispatch[] = []
|
|
|
|
|
|
const inFlight = new Set<Promise<void>>()
|
2026-07-26 10:33:48 +08:00
|
|
|
|
const commitQueue: PendingDispatch[] = []
|
|
|
|
|
|
let committing = false
|
2026-07-26 06:02:36 +08:00
|
|
|
|
let exclusiveActive = false
|
2026-07-26 10:33:48 +08:00
|
|
|
|
let pumping = false
|
|
|
|
|
|
/** Ordered commit cursor: drain the head-of-line settled dispatches one at a time. */
|
|
|
|
|
|
const commitReady = async (): Promise<void> => {
|
|
|
|
|
|
if (committing) return
|
|
|
|
|
|
committing = true
|
|
|
|
|
|
try {
|
|
|
|
|
|
while (commitQueue.length > 0) {
|
|
|
|
|
|
const head = commitQueue[0]
|
|
|
|
|
|
/* v8 ignore next -- the loop condition bounds the index. */
|
|
|
|
|
|
if (head === undefined) break
|
|
|
|
|
|
if (head.dispatched === undefined) break
|
|
|
|
|
|
await head.dispatched
|
|
|
|
|
|
commitQueue.shift()
|
|
|
|
|
|
await head.commit()
|
|
|
|
|
|
}
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
committing = false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-07-26 06:02:36 +08:00
|
|
|
|
const pump = (): void => {
|
2026-07-26 10:33:48 +08:00
|
|
|
|
// The finally-driven re-entry below would otherwise recurse.
|
|
|
|
|
|
if (pumping) return
|
|
|
|
|
|
pumping = true
|
|
|
|
|
|
try {
|
|
|
|
|
|
for (;;) {
|
|
|
|
|
|
const head = pendingQueue[0]
|
|
|
|
|
|
if (head === undefined) return
|
|
|
|
|
|
if (runController.signal.aborted) {
|
|
|
|
|
|
pendingQueue.shift()
|
|
|
|
|
|
head.abandon()
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
// Reclassify at start time (fail-closed on registry changes).
|
|
|
|
|
|
const mode = head.classify()
|
|
|
|
|
|
if (exclusiveActive || inFlight.size >= (mode === 'exclusive' ? 1 : maxParallel)) return
|
|
|
|
|
|
if (mode === 'exclusive') {
|
|
|
|
|
|
if (inFlight.size > 0) return
|
|
|
|
|
|
exclusiveActive = true
|
|
|
|
|
|
}
|
2026-07-26 06:02:36 +08:00
|
|
|
|
pendingQueue.shift()
|
2026-07-26 10:33:48 +08:00
|
|
|
|
commitQueue.push(head)
|
|
|
|
|
|
const flight = head.start().finally(() => {
|
|
|
|
|
|
inFlight.delete(flight)
|
|
|
|
|
|
if (mode === 'exclusive') exclusiveActive = false
|
|
|
|
|
|
// Commit ordering and slot refill are independent: the cursor
|
|
|
|
|
|
// may wait head-of-line on an earlier dispatch while later
|
|
|
|
|
|
// slots keep starting.
|
|
|
|
|
|
void commitReady()
|
|
|
|
|
|
pump()
|
|
|
|
|
|
})
|
|
|
|
|
|
inFlight.add(flight)
|
feat: Code Mode — the registry's mode config, the SDK codegen, and the run_code bridge
The dsh-tools half of the Code Mode RFC (its fourth, final change): the
registry gains its first config — mode: native | code | both — and OWNS how
its tools reach the model. 'code' contributes exactly one wire tool,
run_code, plus a lazy tools:sdk prompt section declaring every other tool
as a generated TypeScript API (jsonSchemaToTs: total over the defineTool
subset, unknown degradation, lexicographic byte-identical rendering);
'both' ships both representations; 'native' is byte-for-byte the old
behavior. Non-native modes fail every assembly loudly without a
typescript-language ctx.codeRuntime.
run_code's dispatch bridge: JSON-normalizes each binding argument before
dispatch (what dispatches is what the tool/code-dispatch event logs — the
append can never fail on payload shape; BigInt/circulars reject that one
call), serializes all program tool calls through a per-run queue (even
Promise.all — no concurrency-safety metadata yet), routes every sub-call
through tools/pre-execute → tools/post-execute (a deny rejects the
program-side promise), drops sub-call additionalContext (no safe outlet
mid-run; pinned), owns a run-scoped abort that follows the outer signal in
and fires on settlement (in-flight sub-dispatch aborted, queued abandoned,
queue drained before returning), and converts a failed run into
CodeRunFailedError → a structured isError carrying kind + captured logs.
tool/code-dispatch joins SessionEventMap by declaration merging (log-only;
deriveMessages ignores it).
The composed surface: the tools config forwards through agent-core and
both app packages; examples/code-agent + demo:code run the worker runtime
under mode code (keyless boot smoke + a with-key e2e proving the collapsed
[run_code] header, the dispatch events, and the file the program wrote);
two new snapshot scenarios (code-mode-turn, both-mode-turn) record the SDK
section, collapsed header, dispatch events, and result card — each its own
header-pinning class (the harness gains per-scenario config overlays and
per-class pins). Catalogs, graphs, cookbook, hooks-bridge notes, and the
RFC (moved to implemented/, restructured to decision-era headings) updated
in the same change.
2026-07-08 12:58:23 +08:00
|
|
|
|
}
|
2026-07-26 10:33:48 +08:00
|
|
|
|
} finally {
|
|
|
|
|
|
pumping = false
|
2026-07-26 06:02:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-07-26 10:33:48 +08:00
|
|
|
|
/** Every in-flight dispatch settled AND committed; nothing can start (the run is aborted at call time). */
|
2026-07-26 06:02:36 +08:00
|
|
|
|
const drainDispatches = async (): Promise<void> => {
|
|
|
|
|
|
// Abandon queued-unstarted tasks first, then await the live set until quiescent.
|
|
|
|
|
|
pump()
|
|
|
|
|
|
while (inFlight.size > 0) await Promise.allSettled([...inFlight])
|
2026-07-26 10:33:48 +08:00
|
|
|
|
await commitReady()
|
feat: Code Mode — the registry's mode config, the SDK codegen, and the run_code bridge
The dsh-tools half of the Code Mode RFC (its fourth, final change): the
registry gains its first config — mode: native | code | both — and OWNS how
its tools reach the model. 'code' contributes exactly one wire tool,
run_code, plus a lazy tools:sdk prompt section declaring every other tool
as a generated TypeScript API (jsonSchemaToTs: total over the defineTool
subset, unknown degradation, lexicographic byte-identical rendering);
'both' ships both representations; 'native' is byte-for-byte the old
behavior. Non-native modes fail every assembly loudly without a
typescript-language ctx.codeRuntime.
run_code's dispatch bridge: JSON-normalizes each binding argument before
dispatch (what dispatches is what the tool/code-dispatch event logs — the
append can never fail on payload shape; BigInt/circulars reject that one
call), serializes all program tool calls through a per-run queue (even
Promise.all — no concurrency-safety metadata yet), routes every sub-call
through tools/pre-execute → tools/post-execute (a deny rejects the
program-side promise), drops sub-call additionalContext (no safe outlet
mid-run; pinned), owns a run-scoped abort that follows the outer signal in
and fires on settlement (in-flight sub-dispatch aborted, queued abandoned,
queue drained before returning), and converts a failed run into
CodeRunFailedError → a structured isError carrying kind + captured logs.
tool/code-dispatch joins SessionEventMap by declaration merging (log-only;
deriveMessages ignores it).
The composed surface: the tools config forwards through agent-core and
both app packages; examples/code-agent + demo:code run the worker runtime
under mode code (keyless boot smoke + a with-key e2e proving the collapsed
[run_code] header, the dispatch events, and the file the program wrote);
two new snapshot scenarios (code-mode-turn, both-mode-turn) record the SDK
section, collapsed header, dispatch events, and result card — each its own
header-pinning class (the harness gains per-scenario config overlays and
per-class pins). Catalogs, graphs, cookbook, hooks-bridge notes, and the
RFC (moved to implemented/, restructured to decision-era headings) updated
in the same change.
2026-07-08 12:58:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Read through a call, not a bare property: the abort state genuinely
|
|
|
|
|
|
// changes across awaits, and a direct `.aborted` re-check after one
|
|
|
|
|
|
// would be narrowed away by control flow analysis.
|
|
|
|
|
|
const runOver = (): boolean => runController.signal.aborted
|
|
|
|
|
|
|
2026-07-21 04:34:14 +08:00
|
|
|
|
const binding = (name: string): CodeBindingFunction => async (rawArgs: unknown): Promise<JsonValue> => {
|
feat: Code Mode — the registry's mode config, the SDK codegen, and the run_code bridge
The dsh-tools half of the Code Mode RFC (its fourth, final change): the
registry gains its first config — mode: native | code | both — and OWNS how
its tools reach the model. 'code' contributes exactly one wire tool,
run_code, plus a lazy tools:sdk prompt section declaring every other tool
as a generated TypeScript API (jsonSchemaToTs: total over the defineTool
subset, unknown degradation, lexicographic byte-identical rendering);
'both' ships both representations; 'native' is byte-for-byte the old
behavior. Non-native modes fail every assembly loudly without a
typescript-language ctx.codeRuntime.
run_code's dispatch bridge: JSON-normalizes each binding argument before
dispatch (what dispatches is what the tool/code-dispatch event logs — the
append can never fail on payload shape; BigInt/circulars reject that one
call), serializes all program tool calls through a per-run queue (even
Promise.all — no concurrency-safety metadata yet), routes every sub-call
through tools/pre-execute → tools/post-execute (a deny rejects the
program-side promise), drops sub-call additionalContext (no safe outlet
mid-run; pinned), owns a run-scoped abort that follows the outer signal in
and fires on settlement (in-flight sub-dispatch aborted, queued abandoned,
queue drained before returning), and converts a failed run into
CodeRunFailedError → a structured isError carrying kind + captured logs.
tool/code-dispatch joins SessionEventMap by declaration merging (log-only;
deriveMessages ignores it).
The composed surface: the tools config forwards through agent-core and
both app packages; examples/code-agent + demo:code run the worker runtime
under mode code (keyless boot smoke + a with-key e2e proving the collapsed
[run_code] header, the dispatch events, and the file the program wrote);
two new snapshot scenarios (code-mode-turn, both-mode-turn) record the SDK
section, collapsed header, dispatch events, and result card — each its own
header-pinning class (the harness gains per-scenario config overlays and
per-class pins). Catalogs, graphs, cookbook, hooks-bridge notes, and the
RFC (moved to implemented/, restructured to decision-era headings) updated
in the same change.
2026-07-08 12:58:23 +08:00
|
|
|
|
if (runOver()) {
|
|
|
|
|
|
throw new Error(`run_code run is over (${String(runController.signal.reason)}); ${name} not dispatched`)
|
|
|
|
|
|
}
|
|
|
|
|
|
const normalized = jsonNormalizeArgs(rawArgs)
|
2026-07-26 06:02:36 +08:00
|
|
|
|
const n = ++dispatches
|
|
|
|
|
|
const subCallId = CallId(`${String(exec.callId)}:code:${n}`)
|
|
|
|
|
|
const input = {
|
|
|
|
|
|
callId: subCallId,
|
|
|
|
|
|
name,
|
|
|
|
|
|
arguments: normalized.dispatched,
|
|
|
|
|
|
...exec.agent ? { agent: exec.agent } : {},
|
|
|
|
|
|
parent: exec.token,
|
|
|
|
|
|
signal: runController.signal,
|
|
|
|
|
|
}
|
|
|
|
|
|
type DispatchOutcome = { isError: true; message: string } | { isError: false; value: JsonValue }
|
2026-07-26 10:33:48 +08:00
|
|
|
|
const scheduler = registry[TOOL_REGISTRY_SCHEDULER]
|
2026-07-26 06:02:36 +08:00
|
|
|
|
const outcome = await new Promise<DispatchOutcome>((resolve, reject) => {
|
2026-07-26 10:33:48 +08:00
|
|
|
|
// Set by start(): what commit() finalizes in submission order.
|
|
|
|
|
|
let parked:
|
|
|
|
|
|
| { kind: 'post-result' | 'final-result'; exec: ToolRunContext; result: ToolExecutionResult }
|
|
|
|
|
|
| undefined
|
|
|
|
|
|
const settle = (result: ToolExecutionResult): void => {
|
|
|
|
|
|
exec.agent?.session.append('tool/code-dispatch', {
|
|
|
|
|
|
parentCallId: exec.callId,
|
|
|
|
|
|
subCallId,
|
|
|
|
|
|
name,
|
|
|
|
|
|
// The SIBLING parse of the dispatched value: byte-identical JSON,
|
|
|
|
|
|
// but a separate object — a tool mutating its args cannot desync
|
|
|
|
|
|
// this record from what it actually received.
|
|
|
|
|
|
arguments: normalized.logged,
|
|
|
|
|
|
isError: result.isError,
|
|
|
|
|
|
// The registry deep-froze this projection at result finalization;
|
|
|
|
|
|
// append snapshots it again, so the log copy stays detached.
|
|
|
|
|
|
content: result.content,
|
|
|
|
|
|
})
|
|
|
|
|
|
resolve(result.isError
|
|
|
|
|
|
? { isError: true, message: result.error.message }
|
|
|
|
|
|
: { isError: false, value: result.value })
|
|
|
|
|
|
}
|
2026-07-26 06:02:36 +08:00
|
|
|
|
pendingQueue.push({
|
2026-07-26 10:33:48 +08:00
|
|
|
|
// Re-read per pump pass against the same agent view the SDK
|
2026-07-26 06:02:36 +08:00
|
|
|
|
// declared; fail-closed exclusive when undeclared/invalid.
|
2026-07-26 10:33:48 +08:00
|
|
|
|
classify: () => registry.executionMode(input).kind,
|
2026-07-26 06:02:36 +08:00
|
|
|
|
abandon: () => {
|
|
|
|
|
|
reject(new Error(`run_code run is over (${String(runController.signal.reason)}); ${name} tool call abandoned`))
|
|
|
|
|
|
},
|
2026-07-26 10:33:48 +08:00
|
|
|
|
start(): Promise<void> {
|
2026-07-26 06:02:36 +08:00
|
|
|
|
exec.agent?.session.append('tool/code-dispatch-start', {
|
|
|
|
|
|
parentCallId: exec.callId,
|
|
|
|
|
|
subCallId,
|
|
|
|
|
|
name,
|
|
|
|
|
|
arguments: normalized.logged,
|
|
|
|
|
|
})
|
2026-07-26 10:33:48 +08:00
|
|
|
|
// Ordered prepare (pre-execute/guards) runs here — starts are
|
|
|
|
|
|
// strictly submission-ordered; only dispatch overlaps.
|
|
|
|
|
|
this.dispatched = (async () => {
|
|
|
|
|
|
const prepared = await scheduler.prepare(input)
|
|
|
|
|
|
if (prepared.kind === 'dispatch') {
|
|
|
|
|
|
const dispatchOutcome = await scheduler.dispatch(prepared.exec)
|
|
|
|
|
|
parked = { kind: dispatchOutcome.kind, exec: prepared.exec, result: dispatchOutcome.result }
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
parked = { kind: prepared.kind, exec: prepared.exec, result: prepared.result }
|
|
|
|
|
|
})()
|
|
|
|
|
|
return this.dispatched
|
|
|
|
|
|
},
|
|
|
|
|
|
async commit(): Promise<void> {
|
|
|
|
|
|
/* v8 ignore next -- commit() runs only after this.dispatched resolved, which set parked. */
|
|
|
|
|
|
if (parked === undefined) return
|
|
|
|
|
|
const result = parked.kind === 'post-result'
|
|
|
|
|
|
? await scheduler.finalize(parked.exec, parked.result)
|
|
|
|
|
|
: scheduler.finish(parked.exec, parked.result)
|
2026-07-26 06:02:36 +08:00
|
|
|
|
for (const context of result.additionalContexts ?? []) {
|
|
|
|
|
|
exec.deferContext(context)
|
|
|
|
|
|
}
|
2026-07-26 10:33:48 +08:00
|
|
|
|
settle(result)
|
2026-07-26 06:02:36 +08:00
|
|
|
|
},
|
feat: Code Mode — the registry's mode config, the SDK codegen, and the run_code bridge
The dsh-tools half of the Code Mode RFC (its fourth, final change): the
registry gains its first config — mode: native | code | both — and OWNS how
its tools reach the model. 'code' contributes exactly one wire tool,
run_code, plus a lazy tools:sdk prompt section declaring every other tool
as a generated TypeScript API (jsonSchemaToTs: total over the defineTool
subset, unknown degradation, lexicographic byte-identical rendering);
'both' ships both representations; 'native' is byte-for-byte the old
behavior. Non-native modes fail every assembly loudly without a
typescript-language ctx.codeRuntime.
run_code's dispatch bridge: JSON-normalizes each binding argument before
dispatch (what dispatches is what the tool/code-dispatch event logs — the
append can never fail on payload shape; BigInt/circulars reject that one
call), serializes all program tool calls through a per-run queue (even
Promise.all — no concurrency-safety metadata yet), routes every sub-call
through tools/pre-execute → tools/post-execute (a deny rejects the
program-side promise), drops sub-call additionalContext (no safe outlet
mid-run; pinned), owns a run-scoped abort that follows the outer signal in
and fires on settlement (in-flight sub-dispatch aborted, queued abandoned,
queue drained before returning), and converts a failed run into
CodeRunFailedError → a structured isError carrying kind + captured logs.
tool/code-dispatch joins SessionEventMap by declaration merging (log-only;
deriveMessages ignores it).
The composed surface: the tools config forwards through agent-core and
both app packages; examples/code-agent + demo:code run the worker runtime
under mode code (keyless boot smoke + a with-key e2e proving the collapsed
[run_code] header, the dispatch events, and the file the program wrote);
two new snapshot scenarios (code-mode-turn, both-mode-turn) record the SDK
section, collapsed header, dispatch events, and result card — each its own
header-pinning class (the harness gains per-scenario config overlays and
per-class pins). Catalogs, graphs, cookbook, hooks-bridge notes, and the
RFC (moved to implemented/, restructured to decision-era headings) updated
in the same change.
2026-07-08 12:58:23 +08:00
|
|
|
|
})
|
2026-07-26 06:02:36 +08:00
|
|
|
|
pump()
|
feat: Code Mode — the registry's mode config, the SDK codegen, and the run_code bridge
The dsh-tools half of the Code Mode RFC (its fourth, final change): the
registry gains its first config — mode: native | code | both — and OWNS how
its tools reach the model. 'code' contributes exactly one wire tool,
run_code, plus a lazy tools:sdk prompt section declaring every other tool
as a generated TypeScript API (jsonSchemaToTs: total over the defineTool
subset, unknown degradation, lexicographic byte-identical rendering);
'both' ships both representations; 'native' is byte-for-byte the old
behavior. Non-native modes fail every assembly loudly without a
typescript-language ctx.codeRuntime.
run_code's dispatch bridge: JSON-normalizes each binding argument before
dispatch (what dispatches is what the tool/code-dispatch event logs — the
append can never fail on payload shape; BigInt/circulars reject that one
call), serializes all program tool calls through a per-run queue (even
Promise.all — no concurrency-safety metadata yet), routes every sub-call
through tools/pre-execute → tools/post-execute (a deny rejects the
program-side promise), drops sub-call additionalContext (no safe outlet
mid-run; pinned), owns a run-scoped abort that follows the outer signal in
and fires on settlement (in-flight sub-dispatch aborted, queued abandoned,
queue drained before returning), and converts a failed run into
CodeRunFailedError → a structured isError carrying kind + captured logs.
tool/code-dispatch joins SessionEventMap by declaration merging (log-only;
deriveMessages ignores it).
The composed surface: the tools config forwards through agent-core and
both app packages; examples/code-agent + demo:code run the worker runtime
under mode code (keyless boot smoke + a with-key e2e proving the collapsed
[run_code] header, the dispatch events, and the file the program wrote);
two new snapshot scenarios (code-mode-turn, both-mode-turn) record the SDK
section, collapsed header, dispatch events, and result card — each its own
header-pinning class (the harness gains per-scenario config overlays and
per-class pins). Catalogs, graphs, cookbook, hooks-bridge notes, and the
RFC (moved to implemented/, restructured to decision-era headings) updated
in the same change.
2026-07-08 12:58:23 +08:00
|
|
|
|
})
|
|
|
|
|
|
// A budget expiry or outer cancel that lands while this call was in
|
|
|
|
|
|
// flight already aborted the dispatch; stop the program now rather
|
|
|
|
|
|
// than hand it a result from a run that is over.
|
|
|
|
|
|
if (runOver()) {
|
|
|
|
|
|
throw new Error(`run_code run is over (${String(runController.signal.reason)}); ${name} result discarded`)
|
|
|
|
|
|
}
|
2026-07-21 04:34:14 +08:00
|
|
|
|
// The worker turns a binding rejection into ToolCallError and adds
|
|
|
|
|
|
// only the binding name. Native content and internal error metadata
|
|
|
|
|
|
// stay outside the program-facing failure contract.
|
|
|
|
|
|
if (outcome.isError) throw new Error(outcome.message)
|
|
|
|
|
|
return outcome.value
|
feat: Code Mode — the registry's mode config, the SDK codegen, and the run_code bridge
The dsh-tools half of the Code Mode RFC (its fourth, final change): the
registry gains its first config — mode: native | code | both — and OWNS how
its tools reach the model. 'code' contributes exactly one wire tool,
run_code, plus a lazy tools:sdk prompt section declaring every other tool
as a generated TypeScript API (jsonSchemaToTs: total over the defineTool
subset, unknown degradation, lexicographic byte-identical rendering);
'both' ships both representations; 'native' is byte-for-byte the old
behavior. Non-native modes fail every assembly loudly without a
typescript-language ctx.codeRuntime.
run_code's dispatch bridge: JSON-normalizes each binding argument before
dispatch (what dispatches is what the tool/code-dispatch event logs — the
append can never fail on payload shape; BigInt/circulars reject that one
call), serializes all program tool calls through a per-run queue (even
Promise.all — no concurrency-safety metadata yet), routes every sub-call
through tools/pre-execute → tools/post-execute (a deny rejects the
program-side promise), drops sub-call additionalContext (no safe outlet
mid-run; pinned), owns a run-scoped abort that follows the outer signal in
and fires on settlement (in-flight sub-dispatch aborted, queued abandoned,
queue drained before returning), and converts a failed run into
CodeRunFailedError → a structured isError carrying kind + captured logs.
tool/code-dispatch joins SessionEventMap by declaration merging (log-only;
deriveMessages ignores it).
The composed surface: the tools config forwards through agent-core and
both app packages; examples/code-agent + demo:code run the worker runtime
under mode code (keyless boot smoke + a with-key e2e proving the collapsed
[run_code] header, the dispatch events, and the file the program wrote);
two new snapshot scenarios (code-mode-turn, both-mode-turn) record the SDK
section, collapsed header, dispatch events, and result card — each its own
header-pinning class (the harness gains per-scenario config overlays and
per-class pins). Catalogs, graphs, cookbook, hooks-bridge notes, and the
RFC (moved to implemented/, restructured to decision-era headings) updated
in the same change.
2026-07-08 12:58:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
fix: pre-dispatch rejection of unloggable args, mutation-proof event copies, proto-safe bindings (Codex round 1)
Three findings from the PR-4 convergence round:
(A) A root-undefined binding argument passed normalization untouched, so
the sub-call DISPATCHED and only then failed the tool/code-dispatch append
(Session.append rejects undefined event data) — a sub-call executed with
no log record, violating the nothing-executes-unlogged contract. And the
tool received the SAME object later handed to the append, so a tool
mutating its args desynced the logged record from what was dispatched (or
re-poisoned the append). jsonNormalizeArgs now rejects undefined up front
with a model-correctable message and returns TWO independent parses of the
canonical JSON text: the tool gets one, the event logs the sibling —
identical by construction, mutation-proof.
(B) The bridge built its bindings record with plain-object assignment, so
a registered tool named __proto__ hit the prototype setter and silently
vanished (the runtime host resolves binding names as own properties). The
record is now null-prototype with defineProperty, mirroring the
worker-side namespace build.
(B) The header-pin sanity assertions ran only inside NON-pinning
scenarios, so a class consisting solely of its pinning scenario (the two
Code Mode classes) would accept a re-recorded pin carrying several headers
or a header-delta. A fixtures meta-test now asserts every pinning fixture
directly.
2026-07-08 13:39:51 +08:00
|
|
|
|
// Null-prototype + defineProperty, mirroring the worker-side namespace
|
|
|
|
|
|
// build: a registered tool named `__proto__` must become an ordinary
|
|
|
|
|
|
// own key (a plain-object assignment would hit the prototype setter,
|
|
|
|
|
|
// silently dropping the binding), and the runtime host resolves
|
|
|
|
|
|
// binding names as own properties only.
|
|
|
|
|
|
const functions: Record<string, CodeBindingFunction> = Object.create(null) as Record<string, CodeBindingFunction>
|
Merge origin/master: Code Mode composed with agent scopes
Master advanced 11 commits mid-merge (the Code Mode registry integration:
mode config, run_code, the tools:sdk section, the ACP run_code cards and
unified demos). The fusion makes Code Mode scope-aware end to end:
- wireSchemas(scope): the mode-aware wire contribution is computed from the
CALLING SCOPE's visible set (scoped tools join, shadowing and restrictions
apply) and feeds the {schemas, knownNames} provider protocol.
- knownNames under the mode collapse: a per-scope RESTRICTION is runtime
state, so the universe stays pre-restriction (a restricted-away tool in
toolOrder is a normal absence) — but the MODE collapse is deployment
config, so under mode 'code' the universe is [run_code] and a toolOrder
naming a native tool fails every assembly loud (master's tested decision,
kept).
- The tools:sdk section renders per assembly CONTEXT: the SDK declares
exactly the calling agent's callable set, using the section-text provider
signature this branch already had.
- run_code bindings enumerate schemas(exec.agent) — a program can bind
exactly what its prompt promised; sub-dispatches already threaded
exec.agent through registry.execute, so scoped resolution and carriers
flow unchanged.
- dsh-tools declares both sides' new deps (dsh-scope + dsh-session);
lockfile and all generated catalogs/graphs/api-catalog regenerated.
Gates green on the merged tree: typecheck, lint, per-file 100% coverage
(2710 tests), snapshots (41), doc-sync, module graph, build, hygiene, demo
smoke.
2026-07-09 23:57:50 +08:00
|
|
|
|
// Enumerate the CALLING AGENT's visible set (scoped tools join,
|
|
|
|
|
|
// restricted globals vanish) — the same view the SDK section declared,
|
|
|
|
|
|
// so a program can bind exactly what its prompt promised; sub-dispatch
|
|
|
|
|
|
// re-resolves per call through the same view (exec.agent threads down).
|
|
|
|
|
|
for (const schema of registry.schemas(exec.agent)) {
|
feat: Code Mode — the registry's mode config, the SDK codegen, and the run_code bridge
The dsh-tools half of the Code Mode RFC (its fourth, final change): the
registry gains its first config — mode: native | code | both — and OWNS how
its tools reach the model. 'code' contributes exactly one wire tool,
run_code, plus a lazy tools:sdk prompt section declaring every other tool
as a generated TypeScript API (jsonSchemaToTs: total over the defineTool
subset, unknown degradation, lexicographic byte-identical rendering);
'both' ships both representations; 'native' is byte-for-byte the old
behavior. Non-native modes fail every assembly loudly without a
typescript-language ctx.codeRuntime.
run_code's dispatch bridge: JSON-normalizes each binding argument before
dispatch (what dispatches is what the tool/code-dispatch event logs — the
append can never fail on payload shape; BigInt/circulars reject that one
call), serializes all program tool calls through a per-run queue (even
Promise.all — no concurrency-safety metadata yet), routes every sub-call
through tools/pre-execute → tools/post-execute (a deny rejects the
program-side promise), drops sub-call additionalContext (no safe outlet
mid-run; pinned), owns a run-scoped abort that follows the outer signal in
and fires on settlement (in-flight sub-dispatch aborted, queued abandoned,
queue drained before returning), and converts a failed run into
CodeRunFailedError → a structured isError carrying kind + captured logs.
tool/code-dispatch joins SessionEventMap by declaration merging (log-only;
deriveMessages ignores it).
The composed surface: the tools config forwards through agent-core and
both app packages; examples/code-agent + demo:code run the worker runtime
under mode code (keyless boot smoke + a with-key e2e proving the collapsed
[run_code] header, the dispatch events, and the file the program wrote);
two new snapshot scenarios (code-mode-turn, both-mode-turn) record the SDK
section, collapsed header, dispatch events, and result card — each its own
header-pinning class (the harness gains per-scenario config overlays and
per-class pins). Catalogs, graphs, cookbook, hooks-bridge notes, and the
RFC (moved to implemented/, restructured to decision-era headings) updated
in the same change.
2026-07-08 12:58:23 +08:00
|
|
|
|
if (schema.name === RUN_CODE_NAME) continue
|
fix: pre-dispatch rejection of unloggable args, mutation-proof event copies, proto-safe bindings (Codex round 1)
Three findings from the PR-4 convergence round:
(A) A root-undefined binding argument passed normalization untouched, so
the sub-call DISPATCHED and only then failed the tool/code-dispatch append
(Session.append rejects undefined event data) — a sub-call executed with
no log record, violating the nothing-executes-unlogged contract. And the
tool received the SAME object later handed to the append, so a tool
mutating its args desynced the logged record from what was dispatched (or
re-poisoned the append). jsonNormalizeArgs now rejects undefined up front
with a model-correctable message and returns TWO independent parses of the
canonical JSON text: the tool gets one, the event logs the sibling —
identical by construction, mutation-proof.
(B) The bridge built its bindings record with plain-object assignment, so
a registered tool named __proto__ hit the prototype setter and silently
vanished (the runtime host resolves binding names as own properties). The
record is now null-prototype with defineProperty, mirroring the
worker-side namespace build.
(B) The header-pin sanity assertions ran only inside NON-pinning
scenarios, so a class consisting solely of its pinning scenario (the two
Code Mode classes) would accept a re-recorded pin carrying several headers
or a header-delta. A fixtures meta-test now asserts every pinning fixture
directly.
2026-07-08 13:39:51 +08:00
|
|
|
|
Object.defineProperty(functions, schema.name, { enumerable: true, value: binding(schema.name) })
|
feat: Code Mode — the registry's mode config, the SDK codegen, and the run_code bridge
The dsh-tools half of the Code Mode RFC (its fourth, final change): the
registry gains its first config — mode: native | code | both — and OWNS how
its tools reach the model. 'code' contributes exactly one wire tool,
run_code, plus a lazy tools:sdk prompt section declaring every other tool
as a generated TypeScript API (jsonSchemaToTs: total over the defineTool
subset, unknown degradation, lexicographic byte-identical rendering);
'both' ships both representations; 'native' is byte-for-byte the old
behavior. Non-native modes fail every assembly loudly without a
typescript-language ctx.codeRuntime.
run_code's dispatch bridge: JSON-normalizes each binding argument before
dispatch (what dispatches is what the tool/code-dispatch event logs — the
append can never fail on payload shape; BigInt/circulars reject that one
call), serializes all program tool calls through a per-run queue (even
Promise.all — no concurrency-safety metadata yet), routes every sub-call
through tools/pre-execute → tools/post-execute (a deny rejects the
program-side promise), drops sub-call additionalContext (no safe outlet
mid-run; pinned), owns a run-scoped abort that follows the outer signal in
and fires on settlement (in-flight sub-dispatch aborted, queued abandoned,
queue drained before returning), and converts a failed run into
CodeRunFailedError → a structured isError carrying kind + captured logs.
tool/code-dispatch joins SessionEventMap by declaration merging (log-only;
deriveMessages ignores it).
The composed surface: the tools config forwards through agent-core and
both app packages; examples/code-agent + demo:code run the worker runtime
under mode code (keyless boot smoke + a with-key e2e proving the collapsed
[run_code] header, the dispatch events, and the file the program wrote);
two new snapshot scenarios (code-mode-turn, both-mode-turn) record the SDK
section, collapsed header, dispatch events, and result card — each its own
header-pinning class (the harness gains per-scenario config overlays and
per-class pins). Catalogs, graphs, cookbook, hooks-bridge notes, and the
RFC (moved to implemented/, restructured to decision-era headings) updated
in the same change.
2026-07-08 12:58:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
2026-07-08 22:03:27 +08:00
|
|
|
|
let result: CodeRunResult
|
|
|
|
|
|
try {
|
|
|
|
|
|
result = await runtime.run({
|
|
|
|
|
|
program: args.code,
|
2026-07-23 01:32:17 +08:00
|
|
|
|
bindings: [{
|
|
|
|
|
|
global: 'tools',
|
|
|
|
|
|
functions,
|
|
|
|
|
|
errorClass: { name: 'ToolCallError', memberNameProperty: 'toolName' },
|
|
|
|
|
|
}],
|
2026-07-08 22:03:27 +08:00
|
|
|
|
signal: runController.signal,
|
|
|
|
|
|
})
|
|
|
|
|
|
} finally {
|
2026-07-26 06:02:36 +08:00
|
|
|
|
// Abort sub-dispatches and drain every in-flight dispatch before
|
|
|
|
|
|
// closing the turn (queued-unstarted ones are abandoned unlogged).
|
2026-07-12 03:36:43 +08:00
|
|
|
|
// Binding failures remain observable through their individual promises.
|
2026-07-08 22:03:27 +08:00
|
|
|
|
runController.abort('run_code settled')
|
2026-07-26 06:02:36 +08:00
|
|
|
|
await drainDispatches()
|
2026-07-08 22:03:27 +08:00
|
|
|
|
}
|
feat: Code Mode — the registry's mode config, the SDK codegen, and the run_code bridge
The dsh-tools half of the Code Mode RFC (its fourth, final change): the
registry gains its first config — mode: native | code | both — and OWNS how
its tools reach the model. 'code' contributes exactly one wire tool,
run_code, plus a lazy tools:sdk prompt section declaring every other tool
as a generated TypeScript API (jsonSchemaToTs: total over the defineTool
subset, unknown degradation, lexicographic byte-identical rendering);
'both' ships both representations; 'native' is byte-for-byte the old
behavior. Non-native modes fail every assembly loudly without a
typescript-language ctx.codeRuntime.
run_code's dispatch bridge: JSON-normalizes each binding argument before
dispatch (what dispatches is what the tool/code-dispatch event logs — the
append can never fail on payload shape; BigInt/circulars reject that one
call), serializes all program tool calls through a per-run queue (even
Promise.all — no concurrency-safety metadata yet), routes every sub-call
through tools/pre-execute → tools/post-execute (a deny rejects the
program-side promise), drops sub-call additionalContext (no safe outlet
mid-run; pinned), owns a run-scoped abort that follows the outer signal in
and fires on settlement (in-flight sub-dispatch aborted, queued abandoned,
queue drained before returning), and converts a failed run into
CodeRunFailedError → a structured isError carrying kind + captured logs.
tool/code-dispatch joins SessionEventMap by declaration merging (log-only;
deriveMessages ignores it).
The composed surface: the tools config forwards through agent-core and
both app packages; examples/code-agent + demo:code run the worker runtime
under mode code (keyless boot smoke + a with-key e2e proving the collapsed
[run_code] header, the dispatch events, and the file the program wrote);
two new snapshot scenarios (code-mode-turn, both-mode-turn) record the SDK
section, collapsed header, dispatch events, and result card — each its own
header-pinning class (the harness gains per-scenario config overlays and
per-class pins). Catalogs, graphs, cookbook, hooks-bridge notes, and the
RFC (moved to implemented/, restructured to decision-era headings) updated
in the same change.
2026-07-08 12:58:23 +08:00
|
|
|
|
|
|
|
|
|
|
if (result.error) {
|
2026-07-14 03:07:41 +08:00
|
|
|
|
const logsText = result.logs.length > 0 ? `\nCaptured output:\n${result.logs.join('\n')}` : ''
|
feat: Code Mode — the registry's mode config, the SDK codegen, and the run_code bridge
The dsh-tools half of the Code Mode RFC (its fourth, final change): the
registry gains its first config — mode: native | code | both — and OWNS how
its tools reach the model. 'code' contributes exactly one wire tool,
run_code, plus a lazy tools:sdk prompt section declaring every other tool
as a generated TypeScript API (jsonSchemaToTs: total over the defineTool
subset, unknown degradation, lexicographic byte-identical rendering);
'both' ships both representations; 'native' is byte-for-byte the old
behavior. Non-native modes fail every assembly loudly without a
typescript-language ctx.codeRuntime.
run_code's dispatch bridge: JSON-normalizes each binding argument before
dispatch (what dispatches is what the tool/code-dispatch event logs — the
append can never fail on payload shape; BigInt/circulars reject that one
call), serializes all program tool calls through a per-run queue (even
Promise.all — no concurrency-safety metadata yet), routes every sub-call
through tools/pre-execute → tools/post-execute (a deny rejects the
program-side promise), drops sub-call additionalContext (no safe outlet
mid-run; pinned), owns a run-scoped abort that follows the outer signal in
and fires on settlement (in-flight sub-dispatch aborted, queued abandoned,
queue drained before returning), and converts a failed run into
CodeRunFailedError → a structured isError carrying kind + captured logs.
tool/code-dispatch joins SessionEventMap by declaration merging (log-only;
deriveMessages ignores it).
The composed surface: the tools config forwards through agent-core and
both app packages; examples/code-agent + demo:code run the worker runtime
under mode code (keyless boot smoke + a with-key e2e proving the collapsed
[run_code] header, the dispatch events, and the file the program wrote);
two new snapshot scenarios (code-mode-turn, both-mode-turn) record the SDK
section, collapsed header, dispatch events, and result card — each its own
header-pinning class (the harness gains per-scenario config overlays and
per-class pins). Catalogs, graphs, cookbook, hooks-bridge notes, and the
RFC (moved to implemented/, restructured to decision-era headings) updated
in the same change.
2026-07-08 12:58:23 +08:00
|
|
|
|
throw new CodeRunFailedError(`code run failed (${result.error.kind}): ${result.error.message}${logsText}`)
|
|
|
|
|
|
}
|
|
|
|
|
|
return {
|
2026-07-21 03:08:35 +08:00
|
|
|
|
logs: result.logs,
|
2026-07-21 04:34:14 +08:00
|
|
|
|
...result.value !== undefined ? { result: result.value } : {},
|
feat: Code Mode — the registry's mode config, the SDK codegen, and the run_code bridge
The dsh-tools half of the Code Mode RFC (its fourth, final change): the
registry gains its first config — mode: native | code | both — and OWNS how
its tools reach the model. 'code' contributes exactly one wire tool,
run_code, plus a lazy tools:sdk prompt section declaring every other tool
as a generated TypeScript API (jsonSchemaToTs: total over the defineTool
subset, unknown degradation, lexicographic byte-identical rendering);
'both' ships both representations; 'native' is byte-for-byte the old
behavior. Non-native modes fail every assembly loudly without a
typescript-language ctx.codeRuntime.
run_code's dispatch bridge: JSON-normalizes each binding argument before
dispatch (what dispatches is what the tool/code-dispatch event logs — the
append can never fail on payload shape; BigInt/circulars reject that one
call), serializes all program tool calls through a per-run queue (even
Promise.all — no concurrency-safety metadata yet), routes every sub-call
through tools/pre-execute → tools/post-execute (a deny rejects the
program-side promise), drops sub-call additionalContext (no safe outlet
mid-run; pinned), owns a run-scoped abort that follows the outer signal in
and fires on settlement (in-flight sub-dispatch aborted, queued abandoned,
queue drained before returning), and converts a failed run into
CodeRunFailedError → a structured isError carrying kind + captured logs.
tool/code-dispatch joins SessionEventMap by declaration merging (log-only;
deriveMessages ignores it).
The composed surface: the tools config forwards through agent-core and
both app packages; examples/code-agent + demo:code run the worker runtime
under mode code (keyless boot smoke + a with-key e2e proving the collapsed
[run_code] header, the dispatch events, and the file the program wrote);
two new snapshot scenarios (code-mode-turn, both-mode-turn) record the SDK
section, collapsed header, dispatch events, and result card — each its own
header-pinning class (the harness gains per-scenario config overlays and
per-class pins). Catalogs, graphs, cookbook, hooks-bridge notes, and the
RFC (moved to implemented/, restructured to decision-era headings) updated
in the same change.
2026-07-08 12:58:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
} finally {
|
2026-07-19 23:38:54 +08:00
|
|
|
|
exec.signal.removeEventListener('abort', onOuterAbort)
|
feat: Code Mode — the registry's mode config, the SDK codegen, and the run_code bridge
The dsh-tools half of the Code Mode RFC (its fourth, final change): the
registry gains its first config — mode: native | code | both — and OWNS how
its tools reach the model. 'code' contributes exactly one wire tool,
run_code, plus a lazy tools:sdk prompt section declaring every other tool
as a generated TypeScript API (jsonSchemaToTs: total over the defineTool
subset, unknown degradation, lexicographic byte-identical rendering);
'both' ships both representations; 'native' is byte-for-byte the old
behavior. Non-native modes fail every assembly loudly without a
typescript-language ctx.codeRuntime.
run_code's dispatch bridge: JSON-normalizes each binding argument before
dispatch (what dispatches is what the tool/code-dispatch event logs — the
append can never fail on payload shape; BigInt/circulars reject that one
call), serializes all program tool calls through a per-run queue (even
Promise.all — no concurrency-safety metadata yet), routes every sub-call
through tools/pre-execute → tools/post-execute (a deny rejects the
program-side promise), drops sub-call additionalContext (no safe outlet
mid-run; pinned), owns a run-scoped abort that follows the outer signal in
and fires on settlement (in-flight sub-dispatch aborted, queued abandoned,
queue drained before returning), and converts a failed run into
CodeRunFailedError → a structured isError carrying kind + captured logs.
tool/code-dispatch joins SessionEventMap by declaration merging (log-only;
deriveMessages ignores it).
The composed surface: the tools config forwards through agent-core and
both app packages; examples/code-agent + demo:code run the worker runtime
under mode code (keyless boot smoke + a with-key e2e proving the collapsed
[run_code] header, the dispatch events, and the file the program wrote);
two new snapshot scenarios (code-mode-turn, both-mode-turn) record the SDK
section, collapsed header, dispatch events, and result card — each its own
header-pinning class (the harness gains per-scenario config overlays and
per-class pins). Catalogs, graphs, cookbook, hooks-bridge notes, and the
RFC (moved to implemented/, restructured to decision-era headings) updated
in the same change.
2026-07-08 12:58:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
},
|
2026-07-26 02:43:34 +08:00
|
|
|
|
// The model-authored description is the call's always-visible UI label
|
|
|
|
|
|
// (the bash `description` precedent); the program itself rides rawInput.
|
2026-07-09 20:56:44 +08:00
|
|
|
|
presentCall: args => ({
|
|
|
|
|
|
card: 'generic',
|
2026-07-26 02:43:34 +08:00
|
|
|
|
title: args.description,
|
2026-07-09 20:56:44 +08:00
|
|
|
|
kind: 'execute',
|
|
|
|
|
|
rawInput: args.code,
|
|
|
|
|
|
}),
|
2026-07-23 04:08:27 +08:00
|
|
|
|
// Deliberately no presentResult: the generic surface fallback keeps this
|
|
|
|
|
|
// title and reads durable result content without duplicating a large raw
|
|
|
|
|
|
// result into the host view payload.
|
feat: Code Mode — the registry's mode config, the SDK codegen, and the run_code bridge
The dsh-tools half of the Code Mode RFC (its fourth, final change): the
registry gains its first config — mode: native | code | both — and OWNS how
its tools reach the model. 'code' contributes exactly one wire tool,
run_code, plus a lazy tools:sdk prompt section declaring every other tool
as a generated TypeScript API (jsonSchemaToTs: total over the defineTool
subset, unknown degradation, lexicographic byte-identical rendering);
'both' ships both representations; 'native' is byte-for-byte the old
behavior. Non-native modes fail every assembly loudly without a
typescript-language ctx.codeRuntime.
run_code's dispatch bridge: JSON-normalizes each binding argument before
dispatch (what dispatches is what the tool/code-dispatch event logs — the
append can never fail on payload shape; BigInt/circulars reject that one
call), serializes all program tool calls through a per-run queue (even
Promise.all — no concurrency-safety metadata yet), routes every sub-call
through tools/pre-execute → tools/post-execute (a deny rejects the
program-side promise), drops sub-call additionalContext (no safe outlet
mid-run; pinned), owns a run-scoped abort that follows the outer signal in
and fires on settlement (in-flight sub-dispatch aborted, queued abandoned,
queue drained before returning), and converts a failed run into
CodeRunFailedError → a structured isError carrying kind + captured logs.
tool/code-dispatch joins SessionEventMap by declaration merging (log-only;
deriveMessages ignores it).
The composed surface: the tools config forwards through agent-core and
both app packages; examples/code-agent + demo:code run the worker runtime
under mode code (keyless boot smoke + a with-key e2e proving the collapsed
[run_code] header, the dispatch events, and the file the program wrote);
two new snapshot scenarios (code-mode-turn, both-mode-turn) record the SDK
section, collapsed header, dispatch events, and result card — each its own
header-pinning class (the harness gains per-scenario config overlays and
per-class pins). Catalogs, graphs, cookbook, hooks-bridge notes, and the
RFC (moved to implemented/, restructured to decision-era headings) updated
in the same change.
2026-07-08 12:58:23 +08:00
|
|
|
|
})
|
|
|
|
|
|
}
|