Commit Graph

1907 Commits

Author SHA1 Message Date
Dudu-0223
571c6025d5 fix: address master merge gate failures 2026-07-09 13:01:14 +08:00
Dudu-0223
78318f5d12 Merge remote-tracking branch 'origin/master' into codex/truncated-design
# Conflicts:
#	docs/event-producer-consumer.md
#	docs/module-graph.md
#	docs/rfc/INDEX.md
#	knip.json
#	packages/README.md
#	scripts/gen-module-graph.ts
#	tsconfig.base.json
#	tsconfig.build.json
#	tsconfig.json
2026-07-09 12:54:30 +08:00
Tianyi Cui
a3244a5774 fix(tool-subagent): an omitted agentOptions must not materialize an empty object
The partial-toolFilter materialization fix (da6c6d58) stopped one field
short: the adjacent agentOptions key in the SAME Config has the same
schemastery trap. An omitted agentOptions materializes {}, which is truthy —
so every yml-configured load put a dishonest agentOptions: {} on every start
request and the presence check in execute() could never be false through
config (only unit tests bypassing schemastery ever exercised that branch).
Harmless downstream today (the driver only spreads it), but the request
shape lied and the check was production-dead.

Same discipline as its toolFilter sibling: the omitted key now defaults to
undefined, the presence check is spelled !== undefined like its neighbors,
and a regression test (fails against the unfixed schema) pins that an
omitted agentOptions stays absent from the request. Swept every other
Config in the repo for the class: no further instances — omitted primitives
inside a materialized object stay ABSENT (verified empirically), so
subagent-mock's capabilities spread is safe, and the remaining object/array
fields all carry explicit defaults or the forced-undefined discipline
already.
2026-07-09 12:33:18 +08:00
Tianyi Cui
6f4ea8a260 refactor(subagent): stage structured captures in a WeakMap keyed by execution
Supersedes the single-slot staging the execution-identity fix (06c5f17e)
kept: the one pending slot needed a mismatch-drop branch plus a defensive
coverage-ignored finally to manage orphans, and it carried a latent trap —
under the loop's documented parallel-execution TODO, two in-flight capture
trips would overwrite the slot and BOTH be dropped.

Staging in a WeakMap<ToolExecution, {value}> makes the stale-stage class
structurally impossible instead of managed: an entry orphaned by an outer
short-circuiting listener can never match a different execution's lookup
(whatever call id that execution carries), needs no drop bookkeeping (the
map reclaims it with the execution object), and staging cannot cross-clobber
under parallel execution. Staging is the only layer this future-proofs — a
parallel cut would still owe its own single-accept rule for the captured
value, which is documented rather than claimed. Behavior is pinned by the
existing orphan/call-id-reuse regression tests, which pass unchanged; the
commit listener loses two branches and the v8-ignore.
2026-07-09 12:30:27 +08:00
Tianyi Cui
96c3c94f85 fix(subagent): make the structured re-assert placement-preserving
Re-auditing the review-fix commits surfaced a regression the REPLACE
re-assert (825cbab3) introduced: unconditionally rebuilding both arrays as
filter(...)+append moved structured_output to the END of the model-visible
tool list on every untampered assembly (overriding the registry's
toolOrder/lexicographic contract) and moved the instruction section to the
absolute array end — renderPrompt reads ARRAY order, so any section above
order 190 would render before the trailing instruction, violating the
sections-sorted-ascending contract. The presence-check version it replaced
touched neither array when the entries were intact.

The re-assert keeps its REPLACE content semantics but is now
placement-preserving: the tool is replaced IN PLACE (duplicates collapse,
append only when stripped); the section is re-inserted at its
ascending-order position (the first entry above 190 — exactly where the
registry's stable sort put it, so the untampered path reaches the model
byte-identical). Pinned by two regression tests that fail against the
filter+append form: untampered placement (tool before a lexicographically
later tool, instruction before an order-200 section) and tamper recovery
(stripped section re-enters its band; an added duplicate collapses to one
right-schema entry).
2026-07-09 12:27:08 +08:00
Tianyi Cui
bc7da642d4 feat: fold the Code Mode demos into demo:code-mode with a UI argument
Code Mode is the point; the UI is just the surface it happens to wear.
demo:code and demo:acp-code collapse into one dispatcher
(scripts/demo-code-mode.mjs): `pnpm run demo:code-mode [repl|acp]` —
repl (default) boots the stdio REPL over examples/code-agent, acp
serves examples/acp-agent's code-mode overlay; each UI runs the exact
node invocation its standalone script ran, and an unknown argument
fails loud with usage. All nine references across READMEs, the RFC,
the overlay header, and the keyless-smoke comment renamed. Smoked all
three paths: usage exit 2, ACP initialize handshake, REPL boot + EOF.
2026-07-09 12:16:37 +08:00
Dudu-0223
0a3ffe1b04 Merge remote-tracking branch 'origin/master' into timeout-design
# Conflicts:
#	docs/event-producer-consumer.md
#	packages/README.md
2026-07-09 11:52:18 +08:00
Yichen Jiang
c4aff62ef7 Merge remote-tracking branch 'origin/codex/ask-user-question' into codex/ask-user-question 2026-07-09 11:22:12 +08:00
Yichen Jiang
7db9a6c780 fix review findings: document ask-user recommendation convention 2026-07-09 11:20:17 +08:00
Yichen Jiang
a5ba05121a Merge branch 'master' into codex/ask-user-question 2026-07-09 11:11:07 +08:00
Tianyi Cui
673f20c990 Merge remote-tracking branch 'origin/master' into code-mode-tools
# Conflicts:
#	docs/event-producer-consumer.md
#	packages/support/acp-snapshot/tests/suite.spec.ts
2026-07-09 11:06:50 +08:00
Dudu-0223
fef4313685 test(spill-policy): guard loader export shape 2026-07-09 11:04:34 +08:00
pku-xht
6660d2a83e Merge branch 'master' into feat/subagent-process 2026-07-09 11:03:48 +08:00
pku-xht
a11000030a docs(subagent-acp): point the env-scrub section at its one home
The scrub pattern and layering semantics live in the dsh-subagent-process
README (the fact's home since the extraction); the ACP section restated them
in full — two prose copies drift word by word until they disagree (the
one-home-per-fact rule in docs/AGENTS.md). The section now links the library
and keeps only the backend's own story: which credential enters via
config.env and why.
2026-07-09 10:42:41 +08:00
pku-xht
2471e2b2bb fix review finding: exitsWithin cleans up its listener and timer on both arms
Each timed-out wait used to leave the once('exit') listener from its inner
waitForExit attached to the child; the dispose ladder accumulates at most a
couple, but in a shared library a caller polling exitsWithin in a loop would
pile listeners onto one child (MaxListenersExceededWarning at 11) and retain
their closures. The race now owns its wiring: the timeout arm removes the
exit listener, the exit arm clears the (still unref'ed) grace timer, and an
already-exited child short-circuits true without attaching anything. Tests
pin listenerCount('exit') === 0 after every outcome.
2026-07-09 10:42:34 +08:00
pku-xht
7ccf31a59b fix review finding: root-portable rm-failure injection in the config-dir test
The best-effort-remove test provoked a real EACCES via a chmod-000 subtree,
which only fails for unprivileged users — under root, recursive rm ignores
permission bits, deleting the subtree: the existsSync assertion goes red and
the swallow branch loses coverage, failing the per-file gate. The rejection
is now injected deterministically at the node:fs/promises boundary (rm
wrapped with a real-passthrough vi.fn; one test queues a single rejection),
the fs-failure boundary being exactly the non-deterministic seam the testing
policy sanctions mocking. Everything else in the suite stays on the real
filesystem, and the swallow contract stays error-kind agnostic.
2026-07-09 10:20:42 +08:00
Dudu-0223
c9310d2a19 fix: address codex review round 3
- spill-policy enforces the true cap invariant: it never emits a replacement
  larger than maxInlineBytes. When the notice alone exceeds the cap (tiny cap or
  long spill root) there is no within-cap replacement, so the inline result is
  kept — the previous guard only compared against the original size and could
  still return content over the cap for a large original. A within-cap
  replacement is always smaller than the original, so this subsumes the earlier
  check.
- Add the HMR-disposal test the conventions require for a new registration:
  dispose the plugin fiber and assert oversized results stop being transformed
  and nothing more is spilled (no leaked tools/post-execute listener on reload).
2026-07-09 10:07:07 +08:00
Yichen Jiang
1f6b67269e Merge branch 'master' into agent-request-messages 2026-07-09 09:52:33 +08:00
Dudu-0223
326b199f25 fix: address codex review round 2
- spill-policy reserves the spill notice's byte cost inside maxInlineBytes, so
  the replacement (preview + notice) never exceeds the documented model-facing
  cap. When the notice alone fills the budget the preview is empty; when even a
  notice-only replacement is not smaller than the original, the inline result is
  kept (spilling would only add bytes).
- retention TextRetainer trims an oversized single suffix chunk to the last
  suffixCap bytes on push, so tail/headTail retention stays bounded by suffixCap
  instead of retaining and re-copying the whole chunk in finish() — this is the
  spill preview path, which pushes the whole result as one chunk.
2026-07-09 09:51:35 +08:00
Tianyi Cui
e5093244fb fix(subagent): declare the dsh-scope dependency; make the re-assert REPLACE conflicting entries
ds-review-bot round-2 findings: (1) dsh-subagent's runtime import of
@deepseek-ai/dsh-scope was undeclared in its manifest and tsconfig
references (the root paths map masked it; the emitted package would import
an undeclared dependency) — wired as peer+dev with the project reference,
module graph regenerated. (2) The structured re-assert only ensured
PRESENCE, so a downstream listener injecting a same-named entry with the
wrong schema kept it model-visible while validateStructuredValue enforced
the real one; it now REPLACES any same-named tool/section with the run's
own. Pinned by a wrong-schema-injection test asserting exactly one entry
carrying the run's schema.
2026-07-09 05:21:06 +08:00
Tianyi Cui
db6aed0459 fix(subagent): key the structured stage by execution identity, not call id
Codex confirmation-round finding: an OUTERMOST prepend pre-execute deny
skips the runtime's own pre-execute clear, and the denied call still
reaches post-execute — so a reused adapter-minted call id could promote an
orphaned stage on the default accept path. The stage is now keyed by the
ToolExecution OBJECT identity, the one token that provably ties a stage to
one pipeline trip: only the execution whose own body staged can commit,
whatever any call id says. The pre-execute clear is gone (one mechanism);
the commit's mismatch drop is now the reachable primary guard. Repro test:
orphaned stage + outer pre-execute deny with the same call id never
promotes; a fresh valid call still captures.
2026-07-09 05:03:44 +08:00
Tianyi Cui
547aacee2f fix: honor the teardown order on owner unload; make the structured commit unconditional
Adversarial-review findings (own reviewer agent), each verified and pinned:

B1: agents.register() returned a wrapper lambda, so the factory composite's
yield could not identity-nest it — on OWNER unload the unregistration (and
agent/disposed) disposed as a concurrent sibling, firing mid-drain while
the final turn was still closing (pre-existing on master; this branch's
docs re-assert the order, so it must be true). register() now returns the
EXACT cordis effect disposer (the Scope.rawDispose move); the composite
nests it and owner unload runs stop/drain -> unregister -> detach -> scope
like every other path. Regression test pins turn-end before disposed
before detach on owner unload.

B2: the structured two-phase commit could promote a stale stage when a
later capture call REUSED the orphaned stage's call id with a body that
never staged (denied downstream, or invalid args throwing pre-stage). The
runtime's pre-execute listener now clears any stale stage unconditionally
when a new capture call enters the pipeline — only a call's own body can
stage for its commit; the call-id mismatch guard becomes a defensive
second layer. Repro test: blocked capture then same-id invalid call.

C1: an explicit empty toolFilter config now fails at plugin LOAD (the
check is self-contained) instead of killing every delegation at child
setup. C2: Scope.dispose/ScopeHost.dispose @returns state the single-shot
repeat-call semantics honestly.
2026-07-09 04:48:52 +08:00
Tianyi Cui
9ff8720da5 fix(tool-subagent): a partial toolFilter must not materialize an empty allow-list
ds-review-bot finding: forcing only the OUTER toolFilter key absent left
the nested arrays materializing — a deny-only config gained allow: [],
which means deny-EVERYTHING. The nested arrays now default to undefined
too; an explicit allow: [] (grant-only children) still survives. Pinned by
a capture-provider regression test.
2026-07-09 04:03:22 +08:00
Tianyi Cui
513ba2716d fix(agent-loop): one quiescence boundary across owner unload and handle.dispose
Cordis effect disposers are single-shot but not await-idempotent: when the
owning fiber's unload invokes the raw wrapper first, a concurrent
handle.dispose() got an immediate undefined and resolved before teardown
finished — violating the driver's stated one-boundary contract (Codex
implementation-review finding). The teardown chain's FIRST-yielded (so
disposed-last) disposer now resolves a shared completion promise; the
handle path awaits it after the wrapper, so tool-finally, parent-teardown,
and owner-unload all observe the same fully-torn-down state. Regression
test: owner unload begins first, concurrent handle.dispose still awaits
unregistration + session detach.
2026-07-09 03:58:15 +08:00
Tianyi Cui
e7b712453a test: close the per-file coverage gaps for the scoping surface
Every subject-extractor row of the invariants carrier table is exercised
with a matching and a foreign-keyed carrier; the HMR re-apply seed path
(sessions of agents that predate the plugin are marked started) is pinned;
the scoped tool-provider disposal, plural restrict() validation, singular
scopeHost absentee, tool-subagent passthrough, stale-stage drop, and
disposing-parent spawn (INACTIVE_EFFECT, no orphan) each gain their test.
Two genuinely defensive branches carry justified v8-ignore markers.
2026-07-09 03:41:37 +08:00
Tianyi Cui
cc24e79cd2 docs: agent-scope RFC, CONTEXT.md glossary, architecture scope section, README sync
The agent-scope-contexts RFC (implemented) records the decision tree:
the dsh-scope primitive over cordis extend/Context.filter/no-op fibers,
two-level flat scope with shadowing, restriction/grant semantics, the
scoped-dispatch rule with fused helpers, the setup window, and the
alternatives (explicit scope params, isolate, event-filtering-only,
vendored support) with why each lost. CONTEXT.md pins the glossary.
architecture.md gains the Agent Scope section, the dsh-scope spine row,
the scoped turn-flow line, and an extension-table row (ceiling 1640→1790:
the two-layer registration model is a new architectural axis; additions
are condensed to pointers). READMEs of every touched package re-state
their scoped facts; the stale structured-runtime README section is
replaced by the scoped-registration description.
2026-07-09 03:01:11 +08:00
Tianyi Cui
e7bcbb8bc6 feat(dx): scopeHost, agent-aware ACP presentation, and the scoped-dispatch drift gate
scopeHost(ctx, services) is the sanctioned way to mint scopes in tests: it
names absent services loudly instead of the cryptic cordis without-inject
dead end, and catches the silent-no-op host (cordis resolves a
dependency-pending fiber's await without running the inject callback).

The ACP ToolPresenter resolves presentations through the session agent's
view (tools.get(name, agent)) so a scoped/shadowed tool renders with the
same definition that executed.

verify-scoped-dispatch (doc-sync + pre-push) pins the dev-invariants
carrier table against the declaration JSDoc set: an event enforced but
undocumented, documented but unenforced, or a registry-subject notification
leaking into the table fails the build. subagent/start|end docs gain their
scoped-dispatch sentence (a real gap the gate caught on first run).
2026-07-09 02:38:54 +08:00
Tianyi Cui
f91eb39538 docs: regenerate catalogs and sync subagent type-equiv blocks for persona/toolFilter 2026-07-09 02:18:39 +08:00
Tianyi Cui
1ac7857349 feat(invariants): scoped-dispatch carrier/subject checks and the setup-drives tripwire
Three dev-mode invariants close the leak-by-default regression class at
runtime: (1) every scope-filtered event family must dispatch with a scope
carrier — a bare dispatch throws at the call site naming the carrier rule;
(2) where the subject is recoverable from the arguments (agent/*, the tool
pipeline, prompt assembly) the carrier's key must BE that subject, and an
assembly context must never carry agent without scope (use
assembleContextFor); (3) a turn/start logged before the owning agent's
agent/session-start is the setup-drives teaching error (setup registers the
scoped world, it never drives the agent).
2026-07-09 02:11:21 +08:00
Tianyi Cui
15f4d1cd03 feat(subagent): persona + toolFilter become real; structured runtime collapses to scoped registrations
SubagentStartRequest gains persona (capability-gated like toolFilter); the
in-process driver composes the child's scoped world in the factory's setup
window — persona as a scoped shadowing deployment:persona section,
toolFilter as a scoped tools.restrict() (loud unknown-name validation),
outputSchema as the scoped structured runtime. spawn/fork now advertise
every start-time capability; ACP stays all-false. A parent-scope teardown
effect links each child to its parent through the memoized handle, so a
disposed parent reaches its whole subtree even if the delegating tool's
finally never runs; subagent/start|end dispatch in the delegating parent's
scope.

structured.ts loses the placeholder schema, the final-assembly swap/strip,
the refcounted root runtime, and the WeakMap state: each child registers
its OWN capture tool (real schema), instruction section, and enforcement
listeners on child.ctx, riding the child's fiber. The commit listener is
call-keyed (a stale stage from a short-circuited post-execute chain is
dropped, never promoted on a later call), and one scoped prepend re-assert
listener preserves the final-assembly guarantee against a stripping global
listener.

tool-subagent gains persona/toolFilter/maxDepth passthrough config —
deny-listing the delegation tool (or maxDepth) is how a deployment bounds
recursion; the omitted-toolFilter schema key is forced absent (a
materialized {} would mean an empty allow-list, i.e. deny-everything).
2026-07-09 02:10:06 +08:00
Tianyi Cui
f387b774a9 feat(agent): the agent is a registration scope — Agent.ctx, setup slot, fused scoped dispatch
Every live agent owns a dsh-scope context (Agent.ctx, key = the agent),
minted inside the loop's composite lifecycle effect: registrations through
it are agent-visible and agent-lifetime, and agent.ctx listeners hear only
that agent's dispatches. The composite yields the scope's raw disposer
first (identity-nested, no un-nested window), then session entry (scoped
enter captures the session carrier), then registration; teardown runs
stop/drain -> unregister -> detach session -> unwind scope, keeping
store/registry rollback synchronous on every failure path.

CreateAgentOptions.setup(agentCtx) runs after the scope is minted and the
agent registered, before agent/session-start and the loop start — the slot
where a creator composes the agent's scoped world (persona sections,
restrict(), scoped tools); a throwing setup unwinds inside the rollback
boundary. Setup registers, it never drives.

agentEvents(ctx, agent) fuses the scope carrier with the injected subject
argument for every agent/* dispatch (the correct dispatch is the shortest
spelling); assembleContextFor(agent) pairs the agent DX field with the
scope layer selector. All loop/agent/registry dispatch sites converted;
agent/* event declarations carry this: Scoped<Agent>; ctx.agent is a safe
root accessor defaulting undefined, shadowed by each agent context.
2026-07-09 01:17:47 +08:00
Tianyi Cui
3d16026eb0 feat(core): scope-aware registries and session dispatch carriers
dsh-tools and dsh-system-prompt gain a per-scope registration layer over
dsh-scope: a registration through a scoped context files into that scope,
shadows a same-named global contribution for that scope (per-agent persona
and tool variants), and unwinds with the scope. tools.restrict() masks the
global surface per scope (snapshot-at-registration, loud unknown-name
validation, intersection composition; scoped grants bypass). One visibility
function feeds schemas/get/execute, so prompt, presentation, and dispatch
can never disagree; out-of-view executes as UNKNOWN_TOOL.

Prompt tool providers now receive the AssembleContext and return
{schemas, knownNames}: toolOrder validates against the pre-restriction name
universe (a typo fails every assembly loudly) while ordering operates on
the post-restriction schemas (a restricted-away tool is a normal absence).

dsh-session captures each session's dispatch carrier at enter() from the
entering context's scope tag, and the new sessions.flush(session) owns the
awaited session/flush dispatch. tools/pre|post-execute and
system-prompt/assemble dispatch with scope carriers keyed by their subject;
session/created|event|flush by the owning session's scope.
2026-07-09 01:09:21 +08:00
Tianyi Cui
43de115173 Merge branch 'master' into code-runtime-worker 2026-07-09 01:04:01 +08:00
Yichen Jiang
db9437a50e Merge remote-tracking branch 'origin/master' into codex/ask-user-question
# Conflicts:
#	packages/bash/bash-local/tests/executor.spec.ts
#	packages/bash/bash-local/tests/run.spec.ts
2026-07-09 00:48:45 +08:00
Yichen Jiang
56091d5b5d fix review findings: keep ask-user opt-in for acp app 2026-07-09 00:42:36 +08:00
Tianyi Cui
dc855dbd4e Merge branch 'code-runtime-worker' into code-mode-tools 2026-07-09 00:40:52 +08:00
Tianyi Cui
064d1c4ce1 docs: state advisory typing in the worker row (review)
A reviewer read "TypeScript via host-side type-strip" and reasonably
asked what typing buys if nothing checks it — the group README never
said the annotations are advisory by design. The row now states it; the
rationale stays in the RFC and the enforcement story (per-dispatch
validateArgs) in the dsh-tools README.
2026-07-09 00:38:21 +08:00
Tianyi Cui
46187ec3b6 Merge branch 'master' into agent-request-messages 2026-07-09 00:21:05 +08:00
Tianyi Cui
32db205c10 feat(scope): dsh-scope scoped-context registration primitive
createScope(ctx, key) mints a tagged context over a synchronously-usable
no-op-plugin fiber (one fact drives visibility AND lifetime); scopeOf reads
the tag through the prototype chain; scopeTarget(base, key) builds the
scope-filtered dispatch carrier over cordis Context.filter, composing the
base's own filter, branded Scoped<T> and runtime-marked for the dev
invariants. Scope.rawDispose exposes the exact cordis disposer so a
composite effect can nest the scope's teardown at its yield position.
2026-07-09 00:03:22 +08:00
Tianyi Cui
75ae8e38af Merge branch 'master' into codex/fix-bash-local-process-tests 2026-07-09 00:01:28 +08:00
Dudu-0223
d0c2f0916d fix: address codex review round 1
- spill-policy validates maxInlineBytes as a non-negative integer at LOAD, so a
  bad config fails the deployment instead of letting a negative value reach
  TextRetainer and turn every oversized-result call into an isError.
- Document the spill seam vocabulary in docs/core-data-structures/spill.md
  (SaveTextSpill/SpillOwner/SpillSource/SpillRef/SpillPath, verbatim + type-equiv
  gated) and index it from core.md, matching the other capability seams.
2026-07-08 22:54:26 +08:00
Yichen Jiang
fd1071392c docs(compact): sync the compactIfNeeded prose signature with the sessionPrefix parameter
The seam gained sessionPrefix between fullSystemPrompt and signal in
18d478bc, but the compact README member table and the compaction
core-data-structures page still showed the 3-arg form and listed only
agent/system/signal as what pre-step supplies. The generated service
catalog was already correct; only these two prose homes drifted.
2026-07-08 22:40:38 +08:00
Yichen Jiang
0d023b9aaa Merge remote-tracking branch 'origin/master' into agent-request-messages 2026-07-08 22:26:25 +08:00
Yichen Jiang
b31399bdc4 Merge remote-tracking branch 'origin/master' into codex/ask-user-question
# Conflicts:
#	docs/module-graph.md
2026-07-08 22:22:25 +08:00
Tianyi Cui
805f5cfd01 Merge branch 'code-runtime-worker' into code-mode-tools 2026-07-08 22:06:28 +08:00
Tianyi Cui
1b29273f12 fix: reach quiescence even when the runtime rejects (agent review)
[P1] review finding: the run-scoped abort + queue drain ran only after
runtime.run() FULFILLED, so a backend that starts a binding call and
then throws left the sub-dispatch running past run_code's settlement —
its tool/code-dispatch event could append after the parent call
returned, breaking the drain-before-return contract. The quiescence
pair now lives in a finally around runtime.run(); the folded queue tail
keeps the drain from masking the thrown error. Pinned by a test whose
fake runtime fails mid-flight: pre-fix it returns in milliseconds with
the slow tool still running.
2026-07-08 22:03:27 +08:00
Tianyi Cui
90547f283b fix: byte-exact value/error caps + write-callback contract (agent review)
Two [P1] review findings on the worker runtime:

- maxValueBytes gated and sliced the rendered fallback by UTF-16 code
  units, so a multibyte string ("€€€€" under a 4-byte cap) crossed whole
  and a truncated multibyte rendering could still run ~3x over budget.
  New truncateUtf8Bytes cuts at code-point boundaries under a real byte
  budget; prepareValue's fallback and the host's forged-error-text bound
  both use it, and the VALUE_RENDER_SLACK comment drops its now-obsolete
  "sliced by characters" wrinkle.

- The patched stream write dropped Node's optional encoding/callback
  arguments, so a program awaiting flush completion
  (write(chunk, resolve)) hung to the wall ceiling and misreported as a
  timeout. The shim now fires the callback asynchronously once the chunk
  is admitted — including for writes the exhausted budget drops.
2026-07-08 21:56:28 +08:00
Yichen Jiang
959a3c3a8b fix(agent-loop): discard an interrupted prefix composition instead of caching it
A cancel/dispose landing inside the first agent/session-prefix waterfall
used to commit the listener chain's return value to the instance cache
before the interruption check dropped the turn; an abort-aware listener's
degraded fallback would then ship on every later request of the instance.
The commit now happens only after the composition survives the
interruption check — the cache only ever holds a fully composed prefix,
and the next turn recomposes under a live signal.
2026-07-08 21:46:03 +08:00
Yichen Jiang
9c133c644d test(bash-local): wait for process readiness 2026-07-08 21:45:10 +08:00
Dudu-0223
463b72ce96 feat(spill): add tool-output spill seam, local backend, and policy
Oversized plain-text tool results now spill to a session-scoped file and
return a bounded preview plus the spill path, so a verbose result stays
readable via `read` without consuming the next model request in full.

- dsh-spill: minimal SpillFiles seam (saveText → session-scoped SpillPath)
- dsh-spill-local: private 0700 session dirs, traversal-safe names, exclusive
  owner-only writes
- dsh-spill-policy: tools/post-execute transformer; no-op unless maxInlineBytes
  is set; skips read; best-effort on save failure (never turns a success into
  an isError)

web_fetch is the showcase — no tool-specific spill code. The coding-agent
example loads the stack so its keyless Loader smoke guards the namespace-plugin
export shape. Snapshot gap for a transcript-visible web_fetch spill is recorded
in the RFC's Consequences (ACP replay is keyless and cannot hit the web).
2026-07-08 20:41:55 +08:00