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.
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.
ui-sidebar shell landed on master (#643: geometry-only shell, the
browsing region moved to the sidebar.workspaces slot); this branch's
sidebar.settings foot seat re-applies on top — the hardcoded foot row
becomes the seat, the seat rides the railIn crossfade, and the shell
spec's renderSlot stub key-splits region vs settings.
The shell is now a pure composition face: no dictionaries, no locale
dependency, and three new chrome content seats (settings.trigger /
settings.header / settings.close) whose slot content also carries the
accessible names (trigger text, dialog aria-labelledby, visually hidden
close label). ui-settings-general returns as the owner of copy that
belongs to no single feature: chrome content, the General section with
its item slot, and the settings dictionaries. Slot types split homes —
trigger/header/close/section live in the shell contract; the
settings.general.item entry moves to the locale package (the common
dependency of every item registrant), with ui-theme consuming it
through a re-export seam; the verbatim duplicate merges are gone and
the dependency graph is a clean DAG.
deferRegistration() stays the shipped form; the note pins the follow-up
direction (bridge declarations into slot:<name> services, migrate
registrants to nested ctx.inject, delete the helper) and the boundaries
a separate PR must settle.
Downstream packages lost the tests that exercised their agent-loop-facing
edges when the loop was rewritten. Restore 100% per-file coverage with
behavior tests through public seams: llm-retry config validation and
cancellation races, goal replay drift/staleness/teardown edges, plan-mode
disposed-flush, workspace-context empty-change commits, api-proxy
synchronous send failures, acp-snapshot spill-path extraction and refresh
write-back, ACP injection-triggered turns, cli-demo and tui inbox
lifecycle edges, and agent-loop retry/settlement/lifecycle branches.
The only source changes are narrowly-justified v8 ignore annotations on
invariant guards and one redundant-guard removal (workspace-context).
The branch edited the English side of 17 doc pairs without their Chinese
counterparts. Bring every .zh.md up to date (loop.ts references now point
at agent.ts, send/steer/inject unification, context/message folded into
user/message, HookContext replaced by UserMessageData in tool decision
blocks, plan/skill delivery moved to agent/step) and re-record the pairing
hashes. Deduplicate type-equiv.manifest.json entries duplicated by the
master merge and drop entries for symbols the refactor removed. Complete
the missing JSDoc on extractSnapshotSpillPaths.
This branch intentionally appends plugin-sourced context (plan-mode
notices, the skill catalog) after the prompt, so the request's last message
is no longer the user's text. The scripted adapter keyed every scenario off
messages.at(-1) only, missed its triggers, and fell through to the
ask_user_question default — the frozen 'Waiting for the first token' PTY
timeouts. It now scans all text since the last assistant message.
Prompt admission runs before any turn opens, so the agent/prompt-submit
flush listener could only ever append plan/mode outside an open turn —
invariant-vetoed and retried at agent/step when the session invariant is
mounted, silently out-of-turn in the durable log when it is not. agent/step
fires inside the open turn before every request derivation (including
turn 1 step 1), so it is the sole flush point and no behavior is lost: a
pending idle switch lands as the first step's in-turn plan/mode. Tests pin
the new contract: admission never appends; the first step boundary flushes.
concludesTurn is terminal: a completed step now carries the concluded fact
to the driver, which ends the turn immediately instead of letting steering
that arrived during the tool batch (or from an agent/stopping listener)
reopen it. The steering is not lost — it drained into the log as
steering/message before the decision point, so it feeds the next turn's
request. Also annotates the driver's unreachable invariant guards
(exhaustiveness default, abort-slot ownership) for the coverage gate.
The five settings-surface registrants carried near-identical
spec-check/ledger-judge/subscribe scaffolding (three jscpd clones);
ui-slots now owns deferRegistration() — ledger-judged presence, refresh
for registrant-localized labels, one-call disposal — and every
registrant shrinks to its registration body.
The ui-layout apply bench provides a real LocaleService before the
theme plugin boots (ui-theme now injects slots/locale to register its
Appearance row); drop locale's unused clsx dependency.
Locale and ui-theme apply coverage on a real Context + SlotCore:
dictionary assembly, declaration-aware registration both ways,
inject-time getter re-sync, service write-back through the event flow,
HMR collapse recovery, and teardown reclamation. Four settings-surface
packages sit at full per-file coverage.
Generated docs follow the ui-models rename and the removed
ui-settings-general package; the English note side picks up the
feature-owner self-registration doctrine.
Settings collaboration direction (recorded in the note): the shell only
provides composition faces — feature plugins register themselves. The
General section moves into the ui-settings shell (order 0, skeleton
rows) and declares the settings.general.item list slot; locale registers
the Language row and ui-theme the Appearance row (each with its own
store mirror, dictionaries, and ledger-judged deferral); the
ui-settings-general package is gone. ui-settings-models becomes
ui-models — a feature package that contributes its Settings section
rather than a settings-owned satellite. The item-slot SlotMap entry is
authored in the ui-settings contract and repeated verbatim in
locale/ui-theme (reference-cycle avoidance; declaration merging keeps
the copies identical).