Commit Graph
69 Commits
Author SHA1 Message Date
xjt 7bd111b2af Merge origin/master into xjt/proofreading-active-docs-2-apply 2026-08-05 10:57:53 +08:00
imccyu ca27512529 fix(packages): omit source publication payloads 2026-08-05 01:15:19 +08:00
xjt 2db712eec7 docs(i18n): proofread active Chinese documentation 2026-08-04 17:36:14 +08:00
Tianyi Cui ac88b6e4c7 fix(acp): retain nested teardown diagnostics
ACP waits for every owned Agent disposal and throws one AggregateError when any Session teardown fails. The aggregate message embedded each rejected value with String(failure) because the connection-close logger itself renders only the outer error message. String preserves only an Error name and message, so causes and AggregateError members disappeared from the operational warning.

Render each per-session rejection with the existing errorChain diagnostic helper before joining it into the outer message. The original rejected values remain in AggregateError.errors for programmatic inspection, while the message now carries cause chains and nested aggregate members through the String-based logger.

Exercise a disposal failure containing both AggregateError members and a nested cause, while retaining the existing barrier that proves the second Session finishes disposal before any warning is emitted. All 10 ACP disposal tests pass and the ACP TypeScript project builds cleanly.
2026-08-02 20:14:46 +08:00
Tianyi Cui cb835c7ea9 fix(acp): keep per-session teardown failure reasons in the aggregate log
The connection-close teardown path threw a bare `AggregateError` whose
message counts the failed sessions, and its only production consumer logs
through `String(error)` — which renders the message alone. Compared with
the previous `Promise.all` behavior, every actual disposal failure reason
disappeared from operational logs.

Join the per-session reasons into the aggregate message, matching the
subagent seam's own aggregate disposal messages, and pin the reason in
the dispose spec's warning assertion.
2026-08-02 14:05:36 +08:00
imccyu a1b3bebb61 fix(subagent): scope drains and soften final flush 2026-08-02 12:51:10 +08:00
Dudu-0223 191c8cd640 fix(acp): scope connection-owned continuation drain 2026-08-02 12:51:08 +08:00
Dudu-0223 cbaceb73a9 fix(subagent): address codex review round 2
Round 1 traded one teardown ordering problem for another. The observer now
splits capture from emission, which satisfies both consumers at once:

- Terminal facts are captured while the child is still registered, so consumers
  that resolve it for the child's log and scope still work.
- The edge is emitted only after handle disposal settles, so a rejecting scoped
  cleanup is reported as a failed epoch instead of a successful one.

Also:

- Keep the Activation in the map until disposal settles. Removing it first let a
  racing followup() see no Activation and cold-resume into the still-registered
  agent, and let a concurrent forest drain skip a still-disposing child and
  release its parent first.
- Derive terminal telemetry from this epoch's event suffix rather than the whole
  session, so a cold resume whose prompt is blocked no longer reports the
  previous epoch's answer and turn reason.
- Cancel the ACP bridge's own prompts before awaiting the descendant drain: a
  drain can block on persistence, and the top-level agents must not keep running
  model and tool work for its whole duration.
2026-08-02 12:51:08 +08:00
Dudu-0223 19d034169e docs: regenerate the config catalog source line
The ACP bridge's new structural teardown type shifts its Config declaration.
2026-08-02 12:51:08 +08:00
Dudu-0223 542a01c407 test(acp): cover the continuable drain ordering and its failure path
Pins that the bridge releases the Activation forest before its own sessions, and
that a failed drain is reported without stranding that teardown. Reads the one
teardown method structurally so the bridge keeps no dependency on the subagent
seam.
2026-08-02 12:51:08 +08:00
Dudu-0223 c8fbc111db fix(acp): drain continuable subagents before disposing top-level agents
A continuable Activation outlives the turn that started it and owns descendant
teardown, so the bridge must drain that forest child-first before releasing the
top-level agents whose runtime the descendants depend on.

Also rewrites the authored continuable snapshot transcript for the Task-free
tool surface; the scenario's keyless replay is still under diagnosis.
2026-08-02 12:51:08 +08:00
kingwl c97b1d025e Merge remote-tracking branch 'origin/master' into codex/web-queue-actions
# Conflicts:
#	apps/web/tsconfig.json
#	docs/architecture.i18n.yaml
#	docs/event-producer-consumer.md
#	packages/client/runtime/README.i18n.yaml
#	packages/client/tsdown.client.ts
#	packages/client/ui-conversation/README.i18n.yaml
#	packages/core/agent-loop/README.i18n.yaml
#	packages/core/agent/README.i18n.yaml
#	packages/core/agent/README.zh.md
#	packages/host/apiproxy/README.i18n.yaml
#	tsconfig.host.json
2026-07-30 00:30:38 +08:00
kingwl f893e2281d feat(agent): add addressable queue operations 2026-07-30 00:05:00 +08:00
xjt de0348bd9e docs(i18n): standardize reviewed README headings 2026-07-29 20:16:45 +08:00
xjt e1481e2d98 docs(i18n): resolve follow-up review findings 2026-07-29 18:38:56 +08:00
j-xiang 599e6edc87 docs(i18n): record proofread README pairs 2026-07-29 15:30:44 +08:00
j-xiang 333b4bcd30 docs(i18n): proofread README translations 1-20 2026-07-29 15:29:24 +08:00
_Kerman fbf87e660c refactor: identify and freeze messages at creation 2026-07-28 13:55:59 +08:00
_Kerman ed67ad53d2 refactor(agent): return request retry action 2026-07-27 21:17:49 +08:00
_Kerman 8117f61396 Merge remote-tracking branch 'origin/master' into xtr/agent-loop-message-machine
# Conflicts:
#	packages/context/workspace-context/README.md
#	packages/llm/llm-retry/README.md
#	packages/session-persistence/session-checkpoint-policy/README.md
#	scripts/type-equiv.manifest.json
2026-07-26 16:45:27 +08:00
_Kerman 6a068ec28d test: restore 100% per-file coverage for agent-loop and the acp bridge
agent-loop: behavior tests for retry-while-busy, cancelled recovery
windows, no-facts stream failures, idle-listener preemption, rejected
driver promises under whenIdle, finish-chunk failures after step close,
presentationMeta persistence, pre-aborted and torn-down create/resume
signals, and configured-start failures over existing artifacts or after
teardown. The remaining guards that no public path can reach carry
justified v8 ignore annotations naming the invariant that starves them.

acp bridge: cover the retry-adoption path (a retry turn resolves the
prompt the failed turn deferred), the no-retry quiescence rejection, and
the admission-blocked cancelled settlement; the synchronous send-throw
catch is annotated as a future-proofing guard since the machine's send()
contains listener failures.
2026-07-26 14:57:24 +08:00
_Kerman fc8d339bf0 fix(acp): settle admission-blocked and retried prompts after the merge
The automation bridge inherited two master-era assumptions the message
machine no longer honors. A prompt blocked at pre-turn admission opens no
turn, so no turn/end could ever settle it — the bridge now watches
whenIdle() and reports a turnless slot as cancelled (the disposed-agent
guard moved to a registry identity check before send). A failed turn no
longer rejects at its turn/end either: agent.retry() closes the failed
turn and opens a successor on the same history, so the bridge holds the
terminal error and lets a retry-triggered turn/start adopt the prompt,
rejecting only at quiescence with no successor.

Also: refresh the empty-response-retry fixture for retry-as-turn logging,
adapt master-side tests to the unified send()/UserMessageData API and
registry-fact disposal, resync the doc pairs both sides touched, trim
architecture.md back under its word ceiling, and regenerate the event and
persistence catalogs.
2026-07-26 14:29:30 +08:00
_Kerman 57d90c343d Merge remote-tracking branch 'origin/master' into xtr/agent-loop-message-machine
# Conflicts:
#	.agents/notes/implemented/feature/2026-06-14-acp-agent-client-protocol.md
#	.agents/notes/implemented/feature/2026-06-14-acp-agent-client-protocol.zh.md
#	.agents/notes/implemented/feature/2026-06-30-hook-bridges.i18n.yaml
#	.agents/notes/implemented/feature/2026-06-30-interception-seams.i18n.yaml
#	.agents/notes/implemented/feature/2026-07-06-sandbox.i18n.yaml
#	.agents/notes/implemented/feature/2026-07-06-sandbox.md
#	.agents/notes/implemented/feature/2026-07-06-sandbox.zh.md
#	.agents/notes/implemented/feature/2026-07-19-model-facing-goal-tools.i18n.yaml
#	.agents/notes/implemented/feature/2026-07-19-same-session-goal-round-driver.i18n.yaml
#	.agents/notes/implemented/feature/2026-07-25-workspace-ui-product-flow.i18n.yaml
#	.agents/notes/implemented/simplification/2026-07-02-remove-stream-chunk-mirror.i18n.yaml
#	docs/architecture.i18n.yaml
#	docs/cookbook/adding-a-tool.i18n.yaml
#	docs/cookbook/extension-cookbook.i18n.yaml
#	docs/core-data-structures/llm-streaming.i18n.yaml
#	docs/core-data-structures/session.i18n.yaml
#	docs/core-data-structures/tools.i18n.yaml
#	docs/event-producer-consumer.md
#	docs/persistence-catalog.md
#	examples/acp-agent/tests/snapshots/cordis-inspect-jsdoc/stdout.expected.jsonl
#	examples/acp-agent/tests/snapshots/escalation-approved/session.jsonl
#	examples/acp-agent/tests/snapshots/escalation-rejected/session.jsonl
#	examples/acp-agent/tests/snapshots/fs-escalation-approved/session.jsonl
#	examples/acp-agent/tests/snapshots/hook-cc-pretool-ask/session.jsonl
#	examples/acp-agent/tests/snapshots/permission-switching/session.jsonl
#	examples/acp-agent/tests/snapshots/plan-mode-reject/session.jsonl
#	examples/acp-agent/tests/snapshots/plan-mode/session.jsonl
#	examples/acp-agent/tests/snapshots/session-sandbox-root/session.jsonl
#	packages/context/session-reference/README.md
#	packages/core/agent-loop/tests/agent.spec.ts
#	packages/hooks/hooks-claude/tests/coverage-cases.ts
#	packages/host/runtime/tests/host-runtime.spec.ts
#	packages/llm/llm-retry/tests/retry.spec.ts
#	packages/session-persistence/session-persistence/src/coordinator.ts
#	packages/support/acp-snapshot/README.md
#	packages/support/acp-snapshot/src/normalize.ts
#	packages/ui/acp/acp-feature-support.md
#	packages/ui/acp/src/codec.ts
#	packages/ui/acp/src/index.ts
#	packages/ui/acp/tests/bridge.spec.ts
#	packages/ui/acp/tests/codec.spec.ts
#	packages/ui/acp/tests/config-options.spec.ts
#	packages/ui/acp/tests/dispose.spec.ts
#	packages/ui/acp/tests/edges.spec.ts
#	packages/ui/acp/tests/stream-update.spec.ts
#	packages/ui/acp/tests/turns.spec.ts
2026-07-26 14:05:33 +08:00
Tianyi Cui 226dc7a249 docs: translate remaining READMEs 2026-07-26 05:06:39 +08:00
Tianyi Cui 440384fdb4 Merge remote-tracking branch 'origin/master' into worktree/acp-automation-protocol
# Conflicts:
#	.agents/notes/implemented/architecture/2026-06-14-session-persistence.md
#	.agents/notes/implemented/architecture/2026-06-20-package-hierarchy.md
#	.agents/notes/implemented/architecture/2026-07-02-tool-render-intent-union.md
#	.agents/notes/implemented/feature/2026-06-14-acp-agent-client-protocol.md
#	.agents/notes/implemented/feature/2026-06-14-acp-multi-session.md
#	.agents/notes/implemented/feature/2026-06-18-acp-terminal-and-tool-rendering.md
#	.agents/notes/implemented/feature/2026-07-19-model-facing-goal-tools.i18n.yaml
#	.agents/notes/implemented/feature/2026-07-19-model-facing-goal-tools.md
#	.agents/notes/implemented/feature/2026-07-19-model-facing-goal-tools.zh.md
#	.agents/notes/implemented/simplification/2026-07-04-trim-acp-bridge-unreachable-surface.md
#	docs/architecture.i18n.yaml
#	docs/cookbook/extension-cookbook.i18n.yaml
#	docs/cookbook/extension-cookbook.md
#	docs/cookbook/extension-cookbook.zh.md
#	docs/core-data-structures/approval.md
#	docs/core-data-structures/user-interaction.md
#	docs/event-producer-consumer.md
#	docs/persistence-catalog.md
#	docs/testing.md
#	docs/tool-catalog.md
#	examples/acp-agent/tests/fixtures/live-mode-switching-2026-07-07.session.jsonl
#	examples/acp-agent/tests/snapshots/cordis-inspect-jsdoc/stdout.expected.jsonl
#	examples/acp-agent/tests/snapshots/permission-switching/session.jsonl
#	packages/goal/tool-goal/README.md
#	packages/ui/acp/README.md
#	packages/ui/acp/acp-feature-support.md
#	packages/ui/acp/src/index.ts
#	packages/ui/acp/tests/bridge.spec.ts
#	packages/ui/acp/tests/dispose.spec.ts
#	packages/ui/acp/tests/edges.spec.ts
#	packages/ui/acp/tests/turns.spec.ts
2026-07-24 23:43:10 +08:00
Tianyi Cui 90057b25f2 docs(acp): state both arms of the spec's version-negotiation rule 2026-07-24 22:19:10 +08:00
Tianyi Cui 74d14f68bb fix(acp): accept baseline resource links and release a failed prompt slot
ACP v1 requires every agent to accept text AND resource_link prompt
content; the automation rewrite dropped the resource_link half of that
baseline. Restore the old bracketed-reference flattening in the codec,
reject only beyond-baseline blocks, and update the package contract and
Agent Note.

Also release the per-session prompt slot when agent.send() throws
synchronously (an agent disposed outside the bridge would otherwise
wedge the session into permanent 'already in flight' rejections),
drop the tautological version-negotiation branch, prove the scenario
env layer reaches the snapshot subprocess, pin bridge-side fail-closed
permission errors, and correct two overpromising test names.
2026-07-24 22:11:49 +08:00
Tianyi Cui 331e959c97 fix(acp): tighten automation-only contracts 2026-07-24 01:51:20 +08:00
Tianyi Cui e819a586b0 refactor(acp): reduce bridge to automation protocol 2026-07-24 01:40:25 +08:00
Tianyi Cui d02e9f1bd6 Reorganize packages into a modular hierarchy
Move the 18 flat packages/<name> packages into role-grouped dirs:
core/, llm/, bash/, session-persistence/, ui/, support/. Group dirs are
pure containers; each package keeps its @deepseek-ai/dsh-* name.

Collapse the per-package tsconfig paths maps (base + typecheck) into one
@deepseek-ai/dsh-* wildcard with a candidate per group, and derive the
publint list from the hierarchy. Update all depth-coupled globs/configs
(workspace, tsdown, vitest, eslint, knip, tsconfig includes/refs,
per-package tsconfigs, generators, doc-script scopes, type-equiv manifest)
and the cross-package/script relative imports in tests.

Fix doc-typecheck's workspacePaths() to parse tsconfig JSONC via the
TypeScript API instead of a regex comment-strip, which corrupted the
new wildcard `/*/` path candidates.

WIP: doc cross-links and package/RFC docs still to update.
2026-06-20 22:55:20 +08:00
Tianyi Cui f593f33dea Merge remote-tracking branch 'origin/master' into codex/rfc-simplify-candidates 2026-06-20 21:42:01 +08:00
Tianyi Cui 80179a5ed0 docs: address simplification review feedback 2026-06-20 20:34:56 +08:00
Tianyi Cui 552612622c docs: fold useful simplification RFCs from pr 74 2026-06-20 19:23:05 +08:00
Tianyi Cui f5e61417ee docs: move ACP checklist into packages/acp
Co-locate the ACP feature support checklist with the bridge package
(packages/acp/acp-feature-support.md) and rewrite its relative links for
the new depth. Broaden the doc-sync globs (doc-typecheck, verify-md-wrap,
verify-md-links) from packages/*/README.md to packages/*/*.md so a
package-level doc beyond the README stays under the drift gates, and
update the AGENTS.md prose describing that scope.
2026-06-20 18:08:17 +08:00
Tianyi Cui fd9ecdce40 Merge remote-tracking branch 'origin/worktree-agent-handle' into worktree-bash-owner-token 2026-06-20 13:57:14 +08:00
Tianyi Cui 44762efbd7 docs(acp): drop PR-letter ref from dispose test comment (review)
The disconnect-mid-prompt test comment said "PR D's per-agent AgentHandle
teardown", narrating the change's origin. Per the repo doc-current-state
convention, state the mechanism (the session's AgentHandle teardown) without
naming the PR that introduced it.
2026-06-20 13:51:42 +08:00
Tianyi Cui 90a19f072d docs(acp,rfc): fix stale ownership wording + propose unifying agent/session id (review)
Review follow-ups on the bash owner-token PR:

- packages/acp/README.md still described task isolation in object-identity terms
  ("records each background task's owning agent", "a different agent"). Rewrite
  to the session-token model: ownership is by `session.header.id`, stored on the
  executor's task, so a different Agent object on the same session may access it
  and ownership survives a tool-bash HMR reload.

- The reviewer flagged that the notice routes by `session.header.id` while the
  registry only enforces unique `agent.id`, so a programmatic caller could
  register two agents sharing a session token and mis-route a notice (not
  reachable via ACP). Rather than bolt a session-id invariant onto the generic
  registry, add a proposed RFC (2026-06-20-unify-agent-and-session-id) to remove
  the precondition by construction — an agent IS its session, one id — with a
  full risks discussion (forecloses multi-session-actor / fork futures, makes the
  config resume-or-create policy load-bearing, migration churn). The actual
  unification ships as its own Codex-converged PR. Cross-linked from the
  agent-lifecycle RFC's seam-precondition note.

- Reframe the tool-bash module-doc ownership paragraph to current-state (per the
  new AGENTS.md doc convention): contrast storing the token on the executor vs
  in the plugin as a standing rationale, not as "closing the old gap".
2026-06-20 13:38:48 +08:00
Tianyi Cui 083a6fc990 fix(agent): re-check id in enter() + memoize AgentHandle.dispose() (review)
Two blocking lifecycle findings from the deep review:

- `SessionStore.enter()` is a public cross-package primitive that a caller can
  separate from `prepare()` by arbitrary work, so it must re-check the id: a
  stale prepared session could otherwise overwrite a live store entry of the
  same id, and the stale session's detach disposer would later delete the REAL
  session. Re-add the duplicate-id throw (removed earlier on a coverage
  rationale that only held for the back-to-back internal caller). Tests cover
  the stale-overwrite rejection and the prepare/enter/announce lifecycle (which
  also covers the throw branch).

- `AgentHandle.dispose()` exposed the raw single-shot cordis effect disposer, so
  a concurrent/second dispose() returned immediately (effect epoch already
  cleared) instead of awaiting the in-flight teardown — violating the
  dispose(): Promise<void> contract that every caller observes the same
  quiescence boundary. Memoize the disposal promise in startOwned. Regression
  test gates the loop's final flush, fires two dispose() calls, and asserts the
  second stays pending until the first's teardown completes (fails without the
  memo).
2026-06-20 13:06:28 +08:00
Tianyi Cui de0c4605bd docs(acp): correct teardown wording — dispose uses the disposed path, not cancel() (review)
A reviewer noted the quiesce() comment + ACP README said `AgentHandle.dispose()`
stops the loop "with the queue-aware cancel", but the handle delegates to the
start-disposer's `stop(); await agent.done`, where `stop()` sets `disposed` and
aborts the current controller — it does NOT call `agent.cancel()`. The pre-step
teardown window is still closed (the disposed promise wakes the parked loop and
`isDisposed()` breaks before a turn starts), but the mechanism is the DISPOSED
path and a mid-flight turn ends with reason `disposed`, not `aborted`. Corrected
the comment and the README to describe the actual path.

(This commit follows the merge of PR C's `cancel(reason)` fix up into this branch.)
2026-06-20 11:55:56 +08:00
Tianyi Cui 15335f74ba Merge remote-tracking branch 'origin/worktree-cancel-primitive' into worktree-agent-handle 2026-06-20 11:52:45 +08:00
Tianyi Cui 6a1e381e38 fix(agent): carry cancel(reason) through the marker-only windows (review)
A reviewer found that `cancel(reason)` only preserved the caller's reason when
an active AbortController observed it (the mid-step path, via
`abort.signal.reason`). The marker-only windows (step-start at loop.ts and the
continuation gate) hardcoded `reason: 'cancelled'`, so the logged `turn/end`
reason was race-dependent on WHERE the cancel landed and the public
`cancel(reason?)` parameter was half-effective.

Capture the resolved reason (`reason ?? 'cancelled'`) on the agent when the
marker is armed, expose it on the LoopHandle as `cancelReason()`, and use it in
both marker branches so a turn dropped without a live controller records the
SAME `{kind:'aborted', reason}` the mid-step path produces.

The two existing window tests asserted `reason: 'cancelled'` while passing
`'from turn-start'` / `'from continuation'` — they documented the bug. Updated
both to assert the caller's reason (behavior + test changed together, per
AGENTS.md "tests document behavior, not golden truth").

Also fixes two stale docs the PR's contract change left behind: the
module-level ACP mapping comment and `codec.ts` both still said `session/cancel
-> agent.abort()`.
2026-06-20 11:51:32 +08:00
Tianyi Cui 5a5b7d19c3 fix(agent): contain a throwing agent/disposed listener in the register disposer (Codex review)
Codex found a real teardown-leak (A): the AgentHandle's composite effect runs
its disposers as a `.then()` chain, and the register disposer emitted
`agent/disposed` UNCONTAINED. A throwing listener rejected the chain, skipping
the LATER session-detach disposer — stranding the session in the store with
`onAppend` attached (a leak AND a durability hole, since the new composite
design relies on detach running). Verified by tracing fiber.ts:299-301
(`task = task.then(dispose)`) against the yield order in AgentLoop.start.

Wrap the disposer's `agent/disposed` emit in try/catch + logger.warn (the
store entry is already removed before the emit — the useful state is captured
— so logging and continuing is correct, mirroring the guarded `agent/status`
emit in ReactLoopAgent). The sibling `agent/created` emit stays uncontained on
purpose: its throw is MEANT to propagate and roll the registration back.

Regression test (acp dispose.spec): register a throwing `agent/disposed`
listener, drive a clean turn, dispose, assert the session was STILL removed.
Confirmed it FAILS without the guard (the throw escapes dispose and detach is
skipped) and passes with it.

Also (B): document the new `prepare`/`enter`/`announce` ordered-teardown
lifecycle primitives in the dsh-session README (they are public cross-package
methods now consumed by dsh-agent-loop).
2026-06-20 07:47:24 +08:00
Tianyi Cui a53a56ff48 fix(agent-loop): fold session lifecycle into the agent effect for ordered teardown
A stronger durability test (dispose MID-turn, then re-load from disk) caught
that the original two-sibling-effect design dropped the loop's closing
`turn/end` on the bare fiber-dispose path: a fiber unload disposes sibling
effects CONCURRENTLY (`Promise.all`, vendor/cordis/fiber.ts), so the
session-create effect detached `onAppend` racing the loop's final
`session/flush` — the re-loaded log showed crash-recovery's synthetic
`interrupted` closer instead of the real `disposed` reason. The disconnect
path happened to work (only `quiesce()` ran), but the contract must hold
uniformly.

Fix: fold the session lifecycle INTO the agent's single composite effect.
`SessionStore` now exposes `prepare` (validate + construct, no store entry),
`enter` (attach onAppend + store, returns detach), and `announce` (emit
session/created), replacing the sibling-effect `createOwned`. `AgentLoop.start`
builds ONE effect that yields, in order: session-detach, register, then
stop-and-`await agent.done`. LIFO disposal runs them as an ORDERED chain (the
runtime awaits each disposer's promise before the next), so the loop is
stopped and awaited to exit — its closing flush captured through the still-
attached onAppend — BEFORE the session detaches, whether the trigger is the
handle's dispose() OR a fiber unload. The config path uses prepare()+start
too, so it gets the same ordered teardown. All three factory entrypoints now
funnel through the one composite builder.

The mid-turn durability test asserts the REAL `disposed` reason lands on disk
(not a recovered `interrupted` substitute), proving the closing event was
captured rather than reconstructed.
2026-06-20 07:12:29 +08:00
Tianyi Cui ee4cad3ada feat(acp): dispose each session's agent on disconnect/teardown
The bridge now holds each session's `AgentHandle` disposer in its
`SessionRecord` and runs it on teardown (client disconnect or fiber dispose)
instead of the old `abort()` + `whenIdle()` drain that left agents
registered. A bare client disconnect now leaves NO registered agent and NO
session-store entry — not an idled-but-still-registered one. The queue-aware
`cancel()` inside the disposer also closes the former pre-step best-effort
window (a turn about to start is dropped), so teardown reaches true
quiescence.

The `session/load`-races-teardown leak is fixed: if the bridge closed while
`resume()` was pending, the just-resumed handle is disposed before throwing,
so it leaves no orphan (it has no SessionRecord, so quiesce() never sees it).

Tests: the disconnect test now asserts (through the SAME memoized teardown)
that the agent is unregistered AND its session removed; a durability test
re-loads the persisted log after dispose and asserts the closing turn/end is
on disk (guards the teardown-order contract); a sibling-isolation test proves
one handle's dispose() leaves other agents untouched. Docs: agent /
agent-loop / acp READMEs, architecture.md, and the stale in-code quiesce()
ownership comment updated to the per-agent disposal model; the now-resolved
TODO(rfc010-agent-disposal) / TODO(rfc010-cancel-prestep) teardown notes
removed.
2026-06-20 06:44:58 +08:00
Tianyi Cui c4bc6e0e38 feat(agent): add queue-aware Agent.cancel() primitive
abort() only kills the in-flight step, so a queued-but-not-yet-started prompt
ran to completion after a cancel and a prompt accepted right after could be
batched into the cancelled turn (the loop merges queued messages into one turn).
This closes TODO(rfc010-cancel-prestep) with a distinct cancel() verb.

cancel() clears the queued + steering FIFOs, aborts the in-flight step, and
drives a turn-scoped marker on the LoopHandle that the driver checks at EVERY
point a turn could start or continue:
- right after the idle wait (window 1): drop the about-to-run turn and settle
  whenIdle() waiters directly (no running→idle transition fires, and no
  agent/status is emitted, so an ACP listener can't see a spurious idle that
  resolves a freshly-queued prompt as cancelled);
- after the synchronous setStatus('running') emit (window 2): a running listener
  can cancel in the gap before runTurn;
- in the step-start window (before runStep, after setAbort): a synchronous
  turn-start/step-start listener can cancel before any AbortController exists;
- at the continuation gate: a cancel during the continuation waterfall (the
  finished step's controller already cleared) ends the turn aborted.

The marker is ARMED only when there is something to cancel (running, an
in-flight step, or queued/steering work) — an idle no-op cancel cannot leave it
set to drop a later prompt — and RESET unconditionally once per loop iteration,
so it governs exactly one turn and never leaks onto the next prompt (even when a
send() lands in the cancelled turn's flush window).

ACP session/cancel now maps to agent.cancel() (keeping the synchronous
settlePrompt). Teardown/disconnect still use abort('disposed') until PR D, so
the ACP README narrows the remaining best-effort window to teardown only.

Tests (agent-loop/cancel.spec.ts) cover every window unit-level (the F1 hang
guard: a whenIdle() waiter registered before a pre-step cancel resolves; the F2
leak guard: idle cancel then a prompt runs; mid-step, continuation, both
pre-step windows, turn-start-listener, steering-cleared, marker-reset). ACP
turns.spec.ts adds the through-bridge tests with NO intervening whenIdle (idle
cancel→prompt runs; mid-stream cancel→immediate next prompt runs) and updates
the stale pre-step test to the queue-aware guarantee. The existing cancel
snapshot golden is byte-identical (it drives the new cancel() path end-to-end
through the real subprocess), so no new golden is needed. 100% coverage.
2026-06-20 04:51:32 +08:00
Tianyi Cui 815bac7de9 refactor(session): drop the dead mutable SessionSummary
SessionSummary (updatedAt/title/firstPrompt) and SessionPersistence.update()
were dead state: zero production callers of update(), no production reader of
updatedAt/firstPrompt, and ACP's title comes from a tool-call presenter, not
storage. The live Session.header was already typed SessionHeader, so the
summary only ever existed in the persistence layer, written and read by nothing
but its own contract test.

Delete it entirely (no SessionMeta alias — SessionMeta collapses to
SessionHeader everywhere). This removes the JSONL .summary.json sidecar
machinery, the SQLite title/first_prompt/updated_at columns and per-append
updated_at bump, and the update() method from the abstract service and both
backends. SQLite SCHEMA_VERSION goes 1->2 and openDatabase now rejects any
non-current user_version (older or newer) — no migration, unreleased software.

Net -400 lines, and it erases the JSONL-sidecar-vs-SQLite-column durability
divergence that the upcoming write coordinator would otherwise have to model.

Records the decision in docs/rfc/implemented/2026-06-19-drop-mutable-session-summary.md
and migrates the 2026-06-14 session-persistence RFC's facts to current truth.
Adds a standalone AGENTS.md section "Tests document behavior, not golden truth"
(a passing test pins current behavior, not necessarily correct behavior) with
the summary-drop as its worked example, and reinforces the no-migration
pre-release stance.
2026-06-20 01:03:57 +08:00
Tianyi Cui 224c6f029a refactor(agent-loop): rename LoopAgent to ReactLoopAgent
Rename the concrete Agent class to make its ReAct-style reasoning loop
explicit in the name. Package name, default-export plugin (`AgentLoop`),
and the `ctx.agentLoop` service key are unchanged.
2026-06-19 10:13:33 +08:00
Tianyi Cui deeb3c9e6d refactor(acp): share turn-end prompt settlement 2026-06-19 01:45:21 +08:00
Tianyi Cui 4a3f3af296 Address Claude review follow-ups 2026-06-19 00:37:12 +08:00
Tianyi Cui 7fa113be0e Merge remote-tracking branch 'origin/master' into codex/pr48-repo-hardening-rfcs
# Conflicts:
#	docs/adr/README.md
#	docs/rfc/009-session-persistence-and-resumability.md
#	docs/rfc/README.md
#	docs/rfc/implemented/2026-06-11-doc-sync-enforcement.md
#	docs/rfc/proposed/2026-06-14-acp-agent-client-protocol.md
#	examples/acp-agent/tests/acp.e2e.ts
#	packages/acp/README.md
#	packages/acp/src/index.ts
#	packages/acp/tests/stream-update.spec.ts
#	packages/agent-loop/src/loop.ts
#	packages/tools/src/index.ts
2026-06-18 23:41:14 +08:00