Commit Graph
9203 Commits
Author SHA1 Message Date
Dudu-0223 ef2a150293 Merge remote-tracking branch 'origin/master' into timeout-design 2026-07-08 15:40:56 +08:00
Dudu-0223 a7c055270d chore(timeout-policy): drop now-unused schemastery dependency
The zero-config enforcer no longer imports schemastery (its Config was
removed); knip flags the stale dependency. Remove it from the manifest
and sync the lockfile.
2026-07-08 15:10:15 +08:00
Dudu-0223 395a0b8336 docs(timeout): update RFC + generated catalogs for the declaration split
The RFC's deployment-policy decision is unchanged; state the current
mechanism in place — the per-tool budget is declared on ToolDefinition
(timeoutMs, set by the owning tool plugin from its config) and the
enforcer is zero-config, so a mistyped tool name is impossible.
Regenerate config-catalog (timeout-policy -> no-config; tool-web gains
fetch/searchTimeoutMs), the event graph (tools/change loses its
timeout-policy consumer), the ToolDefinition type-equiv block, and a
source-line drift in the cordis services catalog.
2026-07-08 15:06:02 +08:00
kingwl 232c1957e2 Merge remote-tracking branch 'origin/master' into feat/repeat-tool-guard 2026-07-08 14:52:12 +08:00
Tianyi Cui 030eebb634 Merge remote-tracking branch 'origin/master' into code-runtime-worker 2026-07-08 14:44:23 +08:00
Tianyi Cui dabc5e6225 Merge pull request #205 from deepseek-harness/code-runtime-pkg
feat: add the code-execution capability seam (ctx.codeRuntime)
2026-07-08 14:44:04 +08:00
Dudu-0223 7a822ee402 feat(tool-web): declare web tool timeout budgets via config
fetchTimeoutMs/searchTimeoutMs (default 30000) resolve to each tool's
ToolDefinition.timeoutMs, moving the budget's declaration home onto the
owning tool plugin and preserving per-tool deployment override without a
mistypable central tool-name map.
2026-07-08 14:40:14 +08:00
kingwl e491759f30 fix review finding: cap the detailed reminder's argument payload 2026-07-08 14:40:08 +08:00
Tianyi Cui d0314736de Merge remote-tracking branch 'origin/master' into code-runtime-pkg 2026-07-08 14:38:54 +08:00
Tianyi Cui 4398daa62b Merge pull request #203 from deepseek-harness/code-mode-rfc
docs: rewrite the Code Mode RFC — registry-native mode over a worker-thread code-runtime seam
2026-07-08 14:38:34 +08:00
Dudu-0223 534b1dc6d0 refactor(timeout-policy): read budget from ToolDefinition, drop config
The enforcer now reads ctx.tools.get(exec.name).timeoutMs instead of a
free-text tool-name config map, so a mistyped name is impossible and the
tools/change warn-once apparatus is gone. exec.name always resolves in the
registry during dispatch, so there is no unknown-name path to warn about.
2026-07-08 14:37:42 +08:00
Tianyi Cui 89941c0b42 Merge remote-tracking branch 'origin/master' into code-mode-rfc 2026-07-08 14:29:50 +08:00
kingwl a581963070 docs(rfc): promote the repeat-tool-guard RFC to implemented 2026-07-08 14:24:20 +08:00
kingwl a0e39db3b6 test(acp-snapshot): add the repeat-tool-guard reminder scenario 2026-07-08 14:24:20 +08:00
kingwl 93d5e4c560 test(acp-snapshot): replace the authored-implies-override guard with an explicit overridden flag 2026-07-08 14:24:20 +08:00
kingwl db26ef479d feat(guard): add the repeat-tool-guard plugin 2026-07-08 14:24:20 +08:00
Tianyi Cui d7a27b20df test: pin the no-recursive-run_code invariant; document the fold at the drain site (bot review)
Both bot criticals verified against the code and rejected as exploit
paths — pinned instead of patched:

The bindings loop already excludes run_code (the skip predates the
finding), and the runtime host resolves forged port calls as own
properties of the bindings record, so an absent binding is unreachable
from a program under any mode. A new both-mode test pins the invariant:
the record has no run_code key on any lookup path.

The drain await cannot mask a run failure: `queue` is the folded tail
(every link swallows its rejection), so `await queue` never rejects and
the runtime's own result.error always reaches the CodeRunFailedError
conversion — the existing abort test exercises exactly the
queued-abandonment-plus-run-failure scenario. Stated at the drain site so
the fold's purpose is explicit.
2026-07-08 14:11:48 +08:00
Dudu-0223 5d451bb2a0 feat(tools): add ToolDefinition.timeoutMs declared+validated via defineTool
A tool declares its cooperative timeout budget on its own definition
rather than a deployment naming it in a central config map. The field
never reaches the model (schemas() whitelists name/description/parameters)
and defineTool rejects a non-positive-finite value at authorship.
2026-07-08 14:06:24 +08:00
lintianle 283cd1b021 Merge remote-tracking branch 'origin/master' into feat/mcp-client 2026-07-08 13:50:39 +08:00
lintianle a1a78ae30a fix: prevent partial tool leaks and non-blocking dispose
- syncTools: on paginated listTools failure, unregister any tools already
  registered in the current sync before rethrowing (prevents orphans)
- Effect disposer: call client.close() directly without awaiting startup
  completion — aborts a hanging connect promptly on HMR/dispose
2026-07-08 13:49:05 +08:00
Wenlu Wang 2f162308fe Merge pull request #209 from deepseek-harness/repeat-tool-guard-rfc
docs(rfc): propose the repeat-tool-guard plugin
2026-07-08 13:48:12 +08:00
kingwl 36d93b4ad3 docs(rfc): propose the repeat-tool-guard plugin 2026-07-08 13:44:50 +08:00
Tianyi Cui d6363d3d27 Merge pull request #207 from deepseek-harness/all-checks-passed-gate
ci: add all-checks-passed aggregate job for branch protection
2026-07-08 13:42:44 +08:00
Tianyi Cui 84088300bc 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
lintianle 418b259a11 fix: prevent partial tool leaks and non-blocking dispose
- syncTools: on paginated listTools failure, unregister any tools already
  registered in the current sync before rethrowing (prevents orphans)
- Effect disposer: call client.close() directly without awaiting startup
  completion — aborts a hanging connect promptly on HMR/dispose
2026-07-08 13:29:07 +08:00
lintianle 0c8f2f7daf fix: prevent partial tool leaks and non-blocking dispose
- syncTools: on paginated listTools failure, unregister any tools already
  registered in the current sync before rethrowing (prevents orphans)
- Effect disposer: call client.close() directly without awaiting startup
  completion — aborts a hanging connect promptly on HMR/dispose
2026-07-08 13:24:06 +08:00
lintianle fdd7d1a91c chore: register mcp-client e2e entries in knip config
Add the mcp-client workspace override so knip recognises the e2e test
file, fixture-server entry, and the bin-only devDeps (server-everything,
server-filesystem) that are invoked at runtime rather than imported.
2026-07-08 13:01:59 +08:00
lintianle c65e05cff1 test: add MCP client e2e tests with real MCP servers
Prove the full MCP protocol flow works end-to-end against real servers:
- Self-written fixture server: tool discovery, execution, error handling,
  image placeholder, toolPrefix, and clean disposal
- @modelcontextprotocol/server-everything: echo, get-sum, get-tiny-image
- @modelcontextprotocol/server-filesystem: write_file + read_file round-trip,
  list_directory with world-verification

All 15 tests keyless and deterministic (no API key needed).
2026-07-08 13:01:59 +08:00
lintianle 2efe8ad418 test: cover resync coalescing, onclose, and error path in mcp-client
Addresses CI coverage gap: exercises the latest-wins resync coalescing
(pendingResync branch), the client.onclose callback, and ensures index.ts
is loaded without module mocks for stable v8 coverage across environments.
2026-07-08 13:01:59 +08:00
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
Ziya e2572b7fa6 Merge branch 'rfc-recallable-compaction' of https://github.com/deepseek-harness/deepseek-harness into rfc-recallable-compaction 2026-07-07 21:09:57 -07:00
lintianle 77fce21253 Merge remote-tracking branch 'origin/master' into feat/mcp-client 2026-07-08 12:08:56 +08:00
Ziya 7089c88e4a docs(rfc): self-contained work references; add richer search query forms follow-up 2026-07-07 21:07:06 -07:00
lintianle f38111e5ca fix: handle MCP transport disconnect and concurrent tool re-sync
- Add client.onclose handler to unregister tools when the MCP server
  disconnects (crash or intentional close)
- Replace bare `let disposers` with a shared mutable state object so the
  effect disposer and notification handler always reference the current
  generation
- Serialize tools/list_changed resyncs with latest-wins coalescing
  (syncing + pendingResync flags) to prevent concurrent races
2026-07-08 12:04:33 +08:00
Dudu-0223 3265bdbf70 fix(timeout-policy): warn on configured-but-unregistered tool names
ds-review-bot flagged that a typo'd or stale config key (e.g. web_fech for
web_fetch) silently applies the timeout to nothing — the tools/execute lookup
just never matches. Mirror dsh-tool-subagent's lifecycle-driven handling of a
configured-but-unregistered provider: on every tools/change (and once at load),
logger.warn each configured name still absent from ctx.tools, warning each name
at most once so a late registration silences it. Not a load-time throw — the
tool set is dynamic (cordis.yml load order, HMR), so a real tool may register
later.

Declare inject = ['tools'] since the plugin now reads ctx.tools synchronously
in apply (previously only inside event callbacks). Regenerate config-catalog
(Requires: tools) and event-producer-consumer graph.
2026-07-08 11:43:29 +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
Dudu-0223 a76285c4e6 Merge origin/master into timeout-design
Resolve conflicts from master's catalog/doc refactors landing alongside the
tool-call timeout work:
- knip.json: keep both new workspace entries (util/timeout + support/acp-snapshot).
- tool-web/src/fetch.ts: keep the timeout_ms removal, adopt master's richer
  JSDoc @param/@returns style on parseFetchArgs/presentFetchCall.
- tools/README.md: keep the tools/execute pipeline wording, adopt master's
  flattened docs/tool-catalog.md path.
- Regenerate every generated doc (cordis-catalog, tool-catalog, config-catalog,
  doc-graphs, module-graph) so they carry both master's changes and the
  tools/execute event + timeout-policy package.
- Add @param/@returns to toolTimeoutResult for master's new verify-export-jsdoc gate.
2026-07-08 11:18:27 +08:00
Tianyi Cui ae1845fea0 Merge branch 'master' into all-checks-passed-gate 2026-07-08 11:09:32 +08:00
Yichen Jiang d3f86996f7 Merge remote-tracking branch 'origin/master' into codex/ask-user-question
# Conflicts:
#	docs/config-catalog.md
#	examples/acp-agent/tests/snapshots/text-turn/session.jsonl
#	pnpm-lock.yaml
2026-07-08 11:09:20 +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
Wenlu Wang a7e8ac04d8 Merge pull request #204 from deepseek-harness/feat/shared-acp-snapshot
Extract the ACP snapshot suite into a support package (dsh-acp-snapshot)
2026-07-08 11:02:21 +08:00
Tianyi Cui 2be0b9266c ci: add all-checks-passed aggregate job for branch protection
A single stable required check that needs every other job in ci.yml, so
branch protection no longer enumerates matrix leg names that change as
lanes and node versions evolve. if: always() keeps the job running when
a dependency fails (a skipped required check would count as passing);
any non-success result — failure, cancelled, or skipped — fails it.
2026-07-08 10:57:25 +08:00
Wenlu Wang 987f9c00eb Merge branch 'master' into feat/shared-acp-snapshot 2026-07-08 10:51:08 +08:00
Tianyi Cui b16fb37f89 Merge pull request #200 from deepseek-harness/rfc-subagent-claude-codex
docs(rfc): propose Claude Code and Codex subagent backends
2026-07-08 10:50:34 +08:00
Tianyi Cui d7cc90a04c Merge branch 'master' into rfc-subagent-claude-codex 2026-07-08 10:49:17 +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