Commit Graph
1072 Commits
Author SHA1 Message Date
Tianyi Cui 2cb10cbc63 Merge branch 'code-runtime-worker' into code-mode-tools 2026-07-08 12:59:18 +08:00
Tianyi Cui b59d245c7c 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
Tianyi Cui e20ce35ffb fix: self-contained built bundles + wire-size value cap (bot review)
Two findings from the GitHub review bot on the ready PR:

The tsdown two-entry build emitted the shared bootstrap module as a
lib/bootstrap-*.js chunk imported by both bundles, which the package.json
files whitelist (deliberately exact) omitted — a packed install had
dangling imports. The package now runs two single-entry builds, so each
bundle inlines its own bootstrap copy and every shipped file is
self-contained.

prepareValue admitted any cloneable value whose BOUNDED inspect rendering
fit maxValueBytes, so a huge container with a compact rendering (a
50k-element array renders as '... N more items') crossed the port raw,
bypassing the cap on both sides. The cap now measures the value's real
cross-boundary size — exact bytes for strings, the structured-clone wire
size (v8.serialize) for everything else — and oversized containers cross
as their bounded rendering instead.
2026-07-08 12:55:14 +08:00
Tianyi Cui aa2a7f9a8a fix: validate and re-cap all inbound worker-port traffic (Codex round 1)
The host's message listener trusted the compile-time WorkerToHost shape on
traffic from a peer that runs model code: postMessage(null) threw in the
listener and crashed the host process; forged log/done messages bypassed
maxLogBytes/maxValueBytes (the worker-side LogBuffer and prepareValue cap
only honest flows); and the error-reply renegotiation re-echoed a forged
non-cloneable call id, throwing outside any catch.

Every inbound message now passes a runtime shape gate that validates and
REBUILDS it field by field (junk drops without a throw; call ids must be
numbers, so replies are always clone-plain; forged extra fields never ride
along). One host-side ledger bounds everything landing in logs — honest
port entries, forged ones, and stray pipe bytes — at the single documented
maxLogBytes, with the shared in-band truncation marker emitted host-side
when the ledger trips first; the completion value is re-capped host-side
through the same prepareValue (with exactly the truncation suffix as slack
so honest worker-capped values pass unchanged), and done error text is
bounded. Also folds the stray-capture budget into that shared ledger
(round-1 finding B: it was a second maxLogBytes on top of the documented
shared cap).
2026-07-08 11:42:59 +08:00
Tianyi Cui 466a052159 Merge branch 'code-runtime-pkg' into code-runtime-worker 2026-07-08 11:07:14 +08:00
Tianyi Cui 583704ac1d feat: add the worker-thread code runtime (dsh-code-runtime-worker)
The shipped backend of the code-execution seam, per the Code Mode RFC's
worker-thread section: one fresh Node worker per run, executing the
model's TypeScript after a host-side type-strip (wrapped in an
async-function shell so top-level return/await parse, sliced back out
position-preserved), bindings bridged over the message port under
hostile-peer rules (own-property name lookup, at-most-once replies,
post-settlement drops, null-prototype namespaces), logs streamed eagerly
with an in-band truncation marker, and two independent budgets — measured
event-loop busy time (computeMs) plus a never-pausing wall ceiling
(maxWallMs) — funneling into worker.terminate(). env: {} and execArgv: []
keep the isolate hermetic; disposal aborts in-flight runs and awaits
worker exits.

The worker entry loads unbuilt via Node's native type stripping
(src/worker.ts, erasable-only) and ships built as a sibling tsdown bundle
(lib/worker.js); tests/built-lib.e2e.ts pins the built load path under
plain node and joins the built-artifact smoke gate. Unit suites cover the
bootstrap in-process (fake port) and the runtime over real workers,
per-file 100%.
2026-07-08 11:07:14 +08:00
Tianyi Cui d10761a577 Merge branch 'code-mode-rfc' into code-runtime-pkg 2026-07-08 10:44:35 +08:00
Tianyi Cui e94c3b9015 docs: pin JSON normalization at the dispatch bridge (review finding)
The seam's structured-clone boundary admits values JSON does not (BigInt,
Map, circulars), while tool/code-dispatch events must be JSON-appendable —
left unhandled, a sub-call could execute and then fail at logging time.
The bridge now JSON-normalizes binding arguments BEFORE dispatch (a value
that does not survive rejects that one call), so the dispatched form and
the logged form are the same JSON value by construction.
2026-07-08 10:42:14 +08:00
Tianyi Cui 15a3431913 docs: catalog the code-runtime seam vocabulary (Codex review finding)
Adds the missing core-data-structures coverage the catalog policy
requires for non-spine seam vocabulary: the code-runtime.md sub-page
with drift-checked type-equiv blocks for all six seam types, the core.md
sub-page row, the type-equiv manifest entries, and LINK_MAP entries so
the generated service signature links CodeRunRequest/CodeRunResult;
cordis/config catalogs regenerated.
2026-07-08 02:38:47 +08:00
Tianyi Cui 6da6f04016 feat: add the code-execution capability seam (ctx.codeRuntime)
New group packages/code-runtime/ with the interface package
@deepseek-ai/dsh-code-runtime, per the Code Mode RFC: abstract CodeRuntime
service (run() resolves program failures as an error field, rejects only
for seam misuse), the CodeRunRequest/CodeBindingNamespace/CodeRunResult/
CodeLogEntry/CodeRunFailure vocabulary, and readonly language/isolation
backend descriptors. Registered in the tsconfig maps, packages/README,
architecture service map, and the doc-graph service-role classification;
catalogs regenerated.

The RFC's one forward path token to the worker package becomes an npm-name
mention until PR3 creates that directory (verify-package-paths is
drift-scoped: the now-existing group made the token checkable).

docs/architecture.md ceiling 1630 -> 1640: the doc gained a genuinely new
capability-service row; the row itself is already minimal.
2026-07-08 02:17:24 +08:00
Tianyi Cui dd5b7cd07d docs: add the run-local abort/settlement contract (Codex round 2)
Budget expiry terminated the worker but nothing cancelled an in-flight
host-side sub-dispatch, and a late dispatch could append events after
run_code returned. The bridge now owns a run-scoped AbortController
(follows exec.signal; fired on any run settlement), sub-dispatches get
the run signal, and run_code returns only after the dispatch queue
drains — no post-settlement appends, per dispose-to-quiescence.
2026-07-08 01:39:10 +08:00
Tianyi Cui 3e67818025 docs: fix Codex round-1 findings on the Code Mode RFC
(A1) Scope the wire-collapse guarantee honestly: systemPrompt.tools() is
a public multi-provider API, so the mode governs the registry's
contribution (the only shipped source); deliberate extra providers own
what they add, and the shipped-configuration invariant is test-pinned.
(A2) Replace pause-on-pending-RPC timeout with two independent budgets:
computeMs metered by worker.performance.eventLoopUtilization() busy time
(unfoolable by an un-awaited decoy dispatch; probe-verified) plus a
never-pausing maxWallMs ceiling.
(A3) Specify sub-call additionalContext as deliberately suppressed in
the MVP (immediate inject would break call/result adjacency; the plural
channel is named follow-up work).
(B) Orphan-process caveat vs bash-local's group kill; null-prototype
binding namespaces (__proto__/constructor names); per-PR doc artifacts
(packages/README row, architecture service map in PR2, config/tool/
persistence catalogs per owning PR); engines range corrected to
^22.19.0 || >=24.0.0.
2026-07-08 01:26:33 +08:00
Tianyi Cui b3bdbd2762 docs: name the persistence-catalog gate for the tool/code-dispatch event
Research finding: a SessionEventMap member is a log event — JSDoc prose
required, @mode is a hard error there, and docs/persistence-catalog.md
must be regenerated (todo/write is the log-only precedent). PR4's plan
now names both.
2026-07-08 01:02:37 +08:00
Tianyi Cui 1b22db5987 docs: record the codeRuntime consumption idiom — cordis has no optional inject
Own adversarial pass finding: a static inject on the registry would gate
ctx.tools (and every tool plugin) on a code runtime existing even under
mode 'native'. The RFC now names the sanctioned pattern: soft
ctx.get('codeRuntime') at use time (the agent-loop sessionPersistence
precedent) with absence failing loud in the provider thunk.
2026-07-08 00:58:26 +08:00
Tianyi Cui 80585a7cd9 docs: rewrite the Code Mode RFC — registry-native mode over a worker-thread code-runtime seam
Supersedes the add-on-plugin + node:vm-stub draft in place (still proposed/):
code mode becomes a first-class ToolRegistry presentation mode
('native' | 'code' | 'both'), execution goes behind a new ctx.codeRuntime
capability seam whose shipped backend is one fresh Node worker thread per
run (type-strip, empty env, resource limits, hard terminate), at
bash-equivalent trust with no unsafe-flag ceremony. Renames the file to
2026-06-15-code-mode.md and regenerates the RFC index.
2026-07-08 00:45:21 +08:00
Tianyi Cui 2696fd3e86 Merge pull request #192 from deepseek-harness/structured-output-subagent-seam
Structured output on the subagent seam: schema subset, capture runtime, spawn/fork support
2026-07-07 23:50:24 +08:00
Tianyi Cui cd55c1d0f5 Merge branch 'master' into structured-output-subagent-seam 2026-07-07 23:50:12 +08:00
Tianyi Cui 329e46bfef Merge pull request #181 from deepseek-harness/worktree-node-22-18-compat
build: lower the Node engines floor to 22.19
2026-07-07 23:41:07 +08:00
imccyu 101816d59b Merge branch 'master' into worktree-node-22-18-compat 2026-07-07 23:23:26 +08:00
imccyu 0205955294 docs: update budget 2026-07-07 23:21:10 +08:00
Tianyi Cui 0172e3b67d Merge remote-tracking branch 'origin/master' into structured-output-subagent-seam 2026-07-07 22:59:35 +08:00
Tianyi Cui f8a6525b5c Merge pull request #187 from deepseek-harness/codex/add-dsh-pre-push-checks
docs: add the dsh-pre-push-checks skill
2026-07-07 22:53:17 +08:00
Tianyi Cui de3a03ff93 Merge branch 'master' into codex/add-dsh-pre-push-checks 2026-07-07 22:51:41 +08:00
imccyu 85308526c8 Merge branch 'master' into worktree-node-22-18-compat 2026-07-07 22:46:02 +08:00
Tianyi Cui 7396b931e7 Merge pull request #196 from deepseek-harness/worktree-llm-tool-order
fix(system-prompt): deterministic model-facing tool order — explicit toolOrder config, lexicographic default
2026-07-07 22:37:58 +08:00
imccyu 958742cac6 fix: cordis-catalog 2026-07-07 22:33:09 +08:00
Tianyi Cui aeaccf6d36 docs: satisfy the new export-JSDoc gate on the assertion signature
Master's verify-export-jsdoc (landed mid-stack) wants @returns on every
exported function including asserts-returning ones; document the narrowing.
2026-07-07 22:13:01 +08:00
imccyu 161275e287 feat: add assembly-time validation rejects placeholder 2026-07-07 22:03:12 +08:00
Tianyi Cui a1c01cda52 Merge remote-tracking branch 'origin/master' into structured-output-subagent-seam
# Conflicts:
#	docs/config-catalog.md
2026-07-07 21:40:07 +08:00
imccyu b149a040d0 docs: fix catalog and budgets 2026-07-07 21:39:48 +08:00
imccyu eeeadd7bef Merge remote-tracking branch 'origin/master' into worktree-llm-tool-order 2026-07-07 21:35:28 +08:00
imccyu 4b95514829 Merge remote-tracking branch 'origin/master' into worktree-node-22-18-compat 2026-07-07 21:33:40 +08:00
Tianyi Cui 23fb1febcd chore: keep the type-equiv manifest in its one-line-per-entry format
The previous commit rewrote the whole file through a JSON pretty-printer,
reformatting every existing entry; restore the established compact style with
the four new entries appended to the tools.md group.
2026-07-07 21:17:52 +08:00
Tianyi Cui 0e0f3b2f19 review: acquire the structured runtime per run, not per backend
The Codex simplification concern plus the duplication comment on the spawn
apply, resolved by deletion: the backend-lifetime holds are gone, so the
runtime registers at the first structured run and disposes when the last
settles — a deployment that never passes outputSchema carries no always-on
global state, and there is no per-backend acquisition block left to extract.

The driver spec now drives an INLINE spawn-shaped provider over
startInProcessRun, which removes the spawn/fork devDependencies (the
test-only workspace cycle); plugin-level structured coverage moves to the
backends' own specs (capture through the shipped plugin, mid-run backend
unload, seeded fork capture). tools.md, the driver README, and both backend
READMEs describe the run-scoped lifetime; the module-graph regenerates
without the cycle edges.
2026-07-07 21:09:02 +08:00
Tianyi Cui 280233ba78 fix review finding: the capture commits only on the final post-execute accept
The cross-seam blocker: structured_output recorded its value in the tool
BODY, before tools/post-execute could block the call — a PostToolUse hook's
block turned the logged result into isError while readResult still returned
structured success and the continuation veto ended the turn.

Two-phase commit: the body validates and STAGES (RunState.pending); a fourth
runtime listener on tools/post-execute — prepend, so await next() returns the
composed final decision — promotes the stage to captured only on an accepted
call, and clears it on every path. A block now yields a consistent pair: the
model and log see the isError feedback, the run settles error with no
structured value, and the turn continues so the model can react. Regressions:
block denies the capture end-to-end; accept-with-replacement still commits.
2026-07-07 21:08:12 +08:00
Tianyi Cui d1b52a063b fix review findings: own-property and plain-JSON discipline in the schema subset
Three Codex findings on json-schema.ts, one discipline:

- required-declared and every value check now use Object.hasOwn — 'in' let
  inherited names (toString) satisfy required, dodge additionalProperties:
  false, and validate a declared property against the value's prototype
  member instead of a carried one
- isObjectLike now means PLAIN JSON object (proto chain of at most one link,
  realm-agnostic): a Date annotation or a Map-as-properties no longer passes
  structurally and serializes lossily — they fail loud as subset violations
- startInProcessRun asserts BEFORE the defensive structuredClone, so a
  hostile schema fails as OutputSchemaError, never a raw DataCloneError

Also the type-equiv catalog gap: tools.md gains the structured-output subset
vocabulary (4 blocks) with matching manifest entries. The driver index also
drops the runtime internals from its public re-export (runs acquire it
internally; no external consumer remains — see the following commit).
2026-07-07 21:07:54 +08:00
Tianyi Cui 5baacf5fd3 Merge pull request #190 from deepseek-harness/worktree-export-jsdoc-gate
Gate JSDoc completeness on every package export
2026-07-07 20:57:01 +08:00
imccyu 709bb5912d fix: cordis-catalog 2026-07-07 20:49:50 +08:00
imccyu adbba0deb2 fix(system-prompt): reject a toolOrder that names an unregistered tool
Review follow-up (#196): a listed name with no registered tool was silently
ignored; misconfiguration must block work instead. The check lives in the
assembly — the earliest moment the registered tool set exists (tool plugins
register after the service constructs) and the only universal one (cordis has
no "all plugins loaded" event; registrations change at any time). assemble()
is now async so the throw surfaces as a rejection rather than a synchronous
escape from a Promise-returning method.

Blast radius, pinned by a loop-level test: the rejection reaches the turn's
outer catch — the turn closes balanced with an `error` reason, agent/error
mirrors it, no step opens, no request/header is logged, no request reaches
the adapter, and the agent returns to idle; every turn fails identically
until the config is fixed. A boot-time validation pass was considered and
rejected (recorded in the RFC). The general principle — misconfiguration
fails loud, never a silent skip — is added to AGENTS.md.
2026-07-07 20:49:42 +08:00
imccyu 72933ec558 refactor(system-prompt): rename TOOL_ORDER_REST from '...' to '<unlisted-tools>'
A three-dot rest entry reads as elision in a cordis.yml; the spelled-out
sentinel says what lands there. The literal now appears once in code (the
constant) and once in the value-pinning test; every other reference — the
forwarding tests included — imports TOOL_ORDER_REST, which adds the
dsh-system-prompt devDependency to the two app packages.

Review follow-up on #196.
2026-07-07 20:49:35 +08:00
Tianyi Cui 5e4ac5e472 fix review findings: CI leaf-gate wiring, heritage return surface, AGENTS.md self-containedness
- run-gates.ts docSyncLeafGates() gains verify-export-jsdoc — CI lanes
  and the pre-push hook execute this leaf list, not the doc-sync npm
  script, so the gate was previously unenforced there (proven by
  SessionForkErrorCode landing undocumented via a master merge while
  checks stayed green; now documented). Same wiring gap fixed for
  master's verify-config-catalog, which was also missing from the list.
- The heritage exemption now recovers the base's return surface: a void
  base return carried no @returns duty, so an override returning a
  concrete result documents it itself (annotated overrides run the
  standard check; unannotated ones are classified by the checker so
  faithful void overrides need no boilerplate annotation). Three new
  negative-path tests pin it; RFC and module doc updated.
- AGENTS.md states each principle inline instead of citing RFCs (eight
  citations removed; high-level doc links kept) and the editing section
  now carries the self-containedness rule.
- Generated catalogs/graphs regenerated for the shifted line pointers.
2026-07-07 20:30:34 +08:00
Yichen Jiang 589259e70c simplify pre-push skill guidance 2026-07-07 19:17:16 +08:00
imccyu 995ba1f105 docs: tighten development onboarding wording 2026-07-07 19:03:14 +08:00
imccyu 92b5eccc96 build: upgrade to 22.19 for deps 2026-07-07 17:39:04 +08:00
imccyu 6edce91735 ci: e2e stay on Node 24 2026-07-07 17:24:57 +08:00
Tianyi Cui 9296411ea1 Merge remote-tracking branch 'origin/master' into worktree-export-jsdoc-gate
# Conflicts:
#	docs/config-catalog.md
#	docs/cordis-catalog/services.md
2026-07-07 17:17:03 +08:00
imccyu 393da2b983 fix: engines ^22.18.0 || >=24.0.0 — exclude EOL Node 23 2026-07-07 17:15:41 +08:00
imccyu 1c2823c73d fix(scripts): replace async fs glob with globSync (failed on Node 22.18) 2026-07-07 17:15:41 +08:00
imccyu f33e14ff19 build: lower the Node engines floor to 22.18 2026-07-07 17:15:41 +08:00
Tianyi Cui 51b715433d fix review finding: an export list surfaces only the declarators it names
A name resolved through an export list (or a default-export identifier)
mapped back to its whole VariableStatement, and checkDecl walked every
declarator — so a private sibling sharing the statement with an exported
const was wrongly required to carry JSDoc.

The scope dispatch is now two-phase: requests accumulate per statement
(null = whole statement for a direct export modifier or ambient scope;
name sets union across lists, so two lists naming different declarators
of one statement both count), then each surfaced statement is checked
once with the declarator filter. Regressions pin the private-sibling
skip, the cross-list union, and the default-export sibling.
2026-07-07 17:05:32 +08:00