Skip to content

docs(design): daemon side-channel coordination (A1/A2/A4/A5)#4511

Open
chiga0 wants to merge 1 commit into
mainfrom
docs/daemon-sidechannel-coordination-design
Open

docs(design): daemon side-channel coordination (A1/A2/A4/A5)#4511
chiga0 wants to merge 1 commit into
mainfrom
docs/daemon-sidechannel-coordination-design

Conversation

@chiga0

@chiga0 chiga0 commented May 25, 2026

Copy link
Copy Markdown
Collaborator

What this is

A design-first, docs-only proposal for the A-series follow-ups surfaced by the cross-client real-time sync audit and the PR #4484 post-merge review. No implementation — it defines the approach so it can be reviewed before any code lands. Each item ships as its own implementation PR after this is approved.

The bugfix/cleanup follow-ups from the same review (epoch-reset resync, approval-mode serialization, cancel dedup, forward-failure signal, replay wire rename, Provider catch-up) are separate and already in review.

The four gaps

  • A1 — in-session model switch never reaches the bus. /model, plan-mode, or agent-internal switches call config.switchModel() directly and emit nothing; only the HTTP route broadcasts. Proposal: a current_model_update sessionUpdate (mirrors the existing current_mode_update), mapped by the bridge to the existing model_switched event, with HTTP + in-session converged on a single emitter to avoid double-broadcast.
  • A2 — in-session approval-mode change emits no event. setMode calls config.setApprovalMode() without notifying. Proposal: emit current_mode_update from setMode; affirm and explicitly document the session-scoped (always) vs workspace-scoped (persist-only) broadcast split with a scope discriminator.
  • A4 — permission_resolved originator/voter ambiguity. Its originatorClientId carries the voter, while permission_request's carries the prompt originator. Proposal: add a canonical voterClientId alias (non-breaking, same pattern as the accepted lastReplayedEventId rename); SDK prefers it.
  • A5 — no side-channel snapshot on attach. A reconnecting client gets transcript replay but must separately pull current mode/model/commands/pending-permissions. Proposal: an opt-in session_snapshot frame emitted before replay so a fresh attach renders correct state without extra round-trips.

Why

These are the remaining "a state change on one path is invisible to other clients" gaps. Closing them gives the daemon a single coordination invariant: every model/approval-mode/permission transition broadcasts exactly once regardless of entry path, and a fresh attach can reconstruct side-channel state without out-of-band pulls.

Contents

Per-item problem (with code anchors), proposed design, alternatives, wire-compat, and risk; cross-cutting single-emitter and additive-alias patterns; proposed sequencing (A4 → A1+A2 → A5) and a test plan.

Requesting design review before implementation.

@github-actions

Copy link
Copy Markdown
Contributor

📋 Review Summary

This is a well-structured design document proposing four side-channel coordination improvements (A1, A2, A4, A5) for the daemon's real-time sync system. The document clearly articulates the problems, proposes non-breaking solutions, and establishes a coherent architectural pattern (single-emitter convergence) across multiple changes. The design is thorough, with excellent code anchors, alternatives analysis, and a clear test plan.

🔍 General Feedback

  • Excellent structure: The problem → proposed design → alternatives → wire/compat → risk format is consistently applied and makes the document easy to review
  • Strong code anchoring: Specific file:line references (e.g., Session.ts:1580, bridge.ts:2798) enable precise verification
  • Coherent architectural pattern: The "single-emitter convergence" principle (agent as single source of truth) elegantly solves the double-broadcast problem across A1 and A2
  • Non-breaking discipline: All proposals maintain wire compatibility through additive patterns, following the established D4 lastReplayedEventId precedent
  • Clear sequencing: The A4 → A1+A2 → A5 order reflects good risk management (smallest/additive first)

🎯 Specific Feedback

🔵 Low

  • Section 0 (Scope): Consider adding a brief "Success Criteria" subsection that defines what "done" looks like for each item—e.g., "A1 is complete when a /model slash command in session X is visible to peer client Y within Z milliseconds." This helps validate the test plan covers the right outcomes.

  • Section 2 (A1): The design mentions authType?: string on current_model_update but doesn't explain what values this field might carry or why it's optional. A brief note on the semantics (e.g., "OAuth vs API key, present only when auth context is available") would help SDK implementers.

  • Section 3 (A2): The scope: 'session' | 'workspace' discriminator is proposed but the document doesn't specify whether this field should be present on both broadcast types or only the workspace-scoped ones. Clarify: is scope always present, or only when persist=true?

  • Section 4 (A4): The deprecation strategy is sound, but consider adding a timeline note (e.g., "originatorClientId will remain indefinitely for wire compat; no removal planned"). This prevents future contributors from treating it as a temporary compat shim.

  • Section 5 (A5): The pendingPermissionIds? field raises a question: if a permission is pending during snapshot but resolves before replay completes, will the client see both the snapshot state and the resolution event? A brief note on this edge case (even if "handled by normal event ordering") would strengthen the design.

  • Section 7 (Sequencing): Consider noting whether A1 and A2 truly must land together, or if they could be split (A1 first, then A2) to reduce PR size. The shared single-emitter convergence logic suggests they're coupled, but explicit confirmation would help.

  • Section 9 (Open questions): Question 3 ("new discriminator vs documenting existing behavior only") reads as still open. If you have a recommendation (even a tentative one), add it—reviewers can then affirm or challenge rather than leaving it unresolved.

✅ Highlights

  • Invariant-driven design: The core invariant ("every state transition produces exactly one bus broadcast") is crisply stated and serves as an excellent north star for the implementation
  • Alternatives considered: Each section explicitly documents rejected approaches with clear rationale (e.g., agent→bridge callback rejected for A1 because sessionUpdate pattern already exists)
  • Risk-aware: Each proposal includes a frank risk assessment with mitigation strategies (e.g., double-broadcast risk mitigated by single-emitter convergence)
  • Test plan quality: The test cases are specific and verifiable—each bullet describes exact inputs and expected outputs
  • Cross-cutting patterns: Section 6 elegantly captures the recurring themes, preventing repetition and helping implementers see the unified vision

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Docs-only, design-first proposal describing how the daemon/bridge should close remaining cross-client side-channel coordination gaps (A1/A2/A4/A5) identified in the real-time sync audit and the #4484 follow-ups, before implementation PRs land.

Changes:

  • Defines a single-emitter approach for model + approval-mode changes so all entry paths broadcast exactly once (A1/A2).
  • Proposes an additive alias (voterClientId) to resolve permission_resolved originator/voter ambiguity without breaking wire compat (A4).
  • Proposes an attach-time session_snapshot synthetic frame (optionally gated) so reconnecting clients can render correct side-channel state without extra pulls (A5).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread docs/design/daemon-sidechannel-coordination/design.md Outdated
Comment thread docs/design/daemon-sidechannel-coordination/design.md Outdated
Comment thread docs/design/daemon-sidechannel-coordination/design.md Outdated
Comment thread docs/design/daemon-sidechannel-coordination/design.md
Comment thread docs/design/daemon-sidechannel-coordination/design.md
Comment thread docs/design/daemon-sidechannel-coordination/design.md Outdated
Comment thread docs/design/daemon-sidechannel-coordination/design.md
Comment thread docs/design/daemon-sidechannel-coordination/design.md Outdated
Comment thread docs/design/daemon-sidechannel-coordination/design.md Outdated
Comment thread docs/design/daemon-sidechannel-coordination/design.md Outdated
Comment thread docs/design/daemon-sidechannel-coordination/design.md Outdated
Comment thread docs/design/daemon-sidechannel-coordination/design.md

@doudouOUC doudouOUC left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Validated all anchors against daemon_mode_b_main HEAD. Endorsing wenshao's 3 Critical findings (A2 asymmetry / demux gap / pendingPermissionIds auth gap) and the §6/§9 internal contradiction — all confirmed against code. Adding new findings inline below that aren't covered by the existing threads.

Minor anchor-hygiene note (not blocking): bridge.ts and permissionMediator.ts DO exist on this branch (at packages/acp-bridge/src/..., 3923/1291 lines respectively); the design's anchors there are correct. The 101-line file at packages/cli/src/serve/httpAcpBridge.ts is a Stage-1 re-export shim. Suggest the design use full packages/acp-bridge/src/bridge.ts:NNNN paths in the next revision to avoid reviewers burning cycles disambiguating against the shim.

— Opus 4.7 via Claude Code /review

Comment thread docs/design/daemon-sidechannel-coordination/design.md Outdated
Comment thread docs/design/daemon-sidechannel-coordination/design.md Outdated
Comment thread docs/design/daemon-sidechannel-coordination/design.md Outdated
Comment thread docs/design/daemon-sidechannel-coordination/design.md Outdated
Comment thread docs/design/daemon-sidechannel-coordination/design.md Outdated
Comment thread docs/design/daemon-sidechannel-coordination/design.md
Comment thread docs/design/daemon-sidechannel-coordination/design.md
Comment thread docs/design/daemon-sidechannel-coordination/design.md
@chiga0

chiga0 commented May 26, 2026

Copy link
Copy Markdown
Collaborator Author

Revised twice — v2 (0448c3f) + v3 (ee5d112)

Thanks @wenshao, @doudouOUC, and Copilot — all 20 threads addressed and resolved. Highlights:

Core model reframed (v3 §1.1). Dropped the v1 "single-emitter (agent sole source)" — it would have lost the bridge's modelChangeQueue serialization, timeout handling, model_switch_failed, and persist/workspace ownership. New model: the bridge stays authoritative for changes it drives; in-session changes add an agent notification the bridge demuxes; the bridge suppresses demux-promotion during its own in-flight roundtrip to avoid double-emit.

Per finding:

  • A1: all three model_switched sites enumerated; model_switch_failed carved out as bridge-only; timeout-race contract (late model_switched after a timeout-fail is authoritative-latest); workspace-mirror decision made explicit (session-scoped phase 1).
  • A2: confirmed asymmetric (HTTP uses the extMethod, bypassing Session.setMode → bridge stays emitter); previousModeId added; persisted/workspace stays bridge-only; scope discriminator; helper generalization + double-emit edge acknowledged.
  • A4: SDK typed event now exposes BOTH originatorClientId and voterClientId (no rename — v2's rename was SDK-breaking); voterClientId optional with no-voter behavior defined.
  • A5: session_snapshot now emitted AFTER replay_complete (fixes the stale-replay reducer corruption); pendingPermissionIds dropped (auth gap); ?snapshot=1 sub-contract specified.
  • §9 emitter-ownership decision table replaces the former §6/§9 contradiction.
  • Test plan expanded (§8). Anchors on full packages/... paths (note: bridge.ts/permissionMediator.ts are the real files; httpAcpBridge.ts is a re-export shim).

Implementation will not begin until this design is approved. Re-review appreciated.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 5 comments.

Comment thread docs/design/daemon-sidechannel-coordination/design.md Outdated
Comment thread docs/design/daemon-sidechannel-coordination/design.md Outdated
Comment thread docs/design/daemon-sidechannel-coordination/design.md Outdated
Comment thread docs/design/daemon-sidechannel-coordination/design.md Outdated
Comment thread docs/design/daemon-sidechannel-coordination/design.md Outdated

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The v3 revision substantively addresses all 4 Critical findings from round 1 — the bridge-authoritative model (§1.1) replaces the broken single-emitter approach, A2 asymmetry is an honest decision, the demux contract (§2.1) is specified, and the open-question contradiction is resolved. Two new Critical issues remain (wrong anchor for the demux insertion point; hard dependency on a non-existent approvalModeQueue not flagged as a blocker), plus 10 Suggestions on test coverage, sequencing, and edge-case contracts. — qwen3.7-max via Qwen Code /review

Comment thread docs/design/daemon-sidechannel-coordination/design.md Outdated
Comment thread docs/design/daemon-sidechannel-coordination/design.md Outdated
Comment thread docs/design/daemon-sidechannel-coordination/design.md Outdated
Comment thread docs/design/daemon-sidechannel-coordination/design.md Outdated
Comment thread docs/design/daemon-sidechannel-coordination/design.md Outdated
Comment thread docs/design/daemon-sidechannel-coordination/design.md Outdated
Comment thread docs/design/daemon-sidechannel-coordination/design.md
Comment thread docs/design/daemon-sidechannel-coordination/design.md Outdated
Comment thread docs/design/daemon-sidechannel-coordination/design.md
Comment thread docs/design/daemon-sidechannel-coordination/design.md Outdated
@chiga0

chiga0 commented May 26, 2026

Copy link
Copy Markdown
Collaborator Author

v4 (431e1d9) — third round addressed

Thanks @wenshao @doudouOUC @copilot — all 17 threads resolved. Architecture (the §1.1 bridge-authoritative model) is unchanged; this round was accuracy + refinement:

  • Demux insertion point corrected to BridgeClient.sessionUpdate() (bridgeClient.ts:397), not bridge.ts:352; added the third rule (drop-when-suppressed, no generic fallback).
  • A2 is now a hard prerequisite on fix(daemon): cross-client sync follow-up cleanup (epoch-reset resync, approval-mode serialization, catch-up indicator) #4510 (approvalModeQueue does not exist yet); the unbounded-window divergence is documented as what it mitigates.
  • A2 HTTP path emits no agent notification (bypasses Session.setMode) → bridge is sole emitter, nothing to suppress (§1.1/§9 corrected).
  • Step-2 demux covers current_model_update only; current_mode_update promotion deferred to step 3 (needs previousModeId) — no shape regression.
  • A5 snapshot captured at emission time (after replay_complete), fixing the stale-overwrite; first-attach ordering defined.
  • Not "additive everywhere": current_mode_update promotion is lockstep — vscode-ide-companion qwenSessionUpdateHandler is named as an affected consumer.
  • previousModeId capture point, helper generalization, persist-scope (workspace-or-user), security enumeration (resolveTrustedClientId), tests + anchors all fixed.

Re-review appreciated. Per the sequencing, A4 (additive, unblocked) can begin independently; A2 stays blocked on #4510.

Comment thread docs/design/daemon-sidechannel-coordination/design.md Outdated
Comment thread docs/design/daemon-sidechannel-coordination/design.md Outdated
Comment thread docs/design/daemon-sidechannel-coordination/design.md Outdated
Comment thread docs/design/daemon-sidechannel-coordination/design.md Outdated
Comment thread docs/design/daemon-sidechannel-coordination/design.md Outdated
Comment thread docs/design/daemon-sidechannel-coordination/design.md Outdated
Comment thread docs/design/daemon-sidechannel-coordination/design.md Outdated
Comment thread docs/design/daemon-sidechannel-coordination/design.md Outdated
Comment thread docs/design/daemon-sidechannel-coordination/design.md Outdated
Comment thread docs/design/daemon-sidechannel-coordination/design.md Outdated
@chiga0

chiga0 commented May 26, 2026

Copy link
Copy Markdown
Collaborator Author

v5 (f0109cf) — fourth round addressed (all 10 threads resolved)

Thanks @wenshao. Two Criticals + 8 suggestions handled:

  • Concurrent in-session /model drift (Critical): added §2.2 post-roundtrip reconciliation — on settle the bridge re-reads the agent model and emits a corrective model_switched if divergent (in-session /model bypasses modelChangeQueue, so drop-when-suppressed alone could leave bus=A while session=B).
  • IDE-companion lockstep (Critical): added a one-release dual-emit transition + enumerated the upstream dispatch sites (daemonIdeConnection.ts / DaemonChannelBridge.ts) that drop unknown types and also need handlers.
  • model_switched payload mapping specified (currentModelId→data.modelId, sessionId→data.sessionId) — without it the SDK validator drops it (A1 non-functional).
  • Demux observability now required (promoted/dropped/suppressed/generic log).
  • replay_complete correction: it does exist (eventBus.ts:444, merged feat(daemon+sdk): cross-client real-time sync completeness #4484); A5 phase 2 introduces only session_snapshot.
  • First-attach no longer synthesizes replay_complete{0}; snapshot is self-delimiting.
  • Capture-at-emission tightened to a synchronous read+publish block.
  • Helper migration model specified; Q3 resolved (keep the extMethod bypass); A4 distinguishing test added (shipped in feat(daemon): add voterClientId to permission_resolved (A4) #4539).

Note: A4 (#4539) is already implemented and approved. Re-review of the design appreciated.

@chiga0 chiga0 requested a review from wenshao May 26, 2026 08:58
Comment thread docs/design/daemon-sidechannel-coordination/design.md Outdated
Comment thread docs/design/daemon-sidechannel-coordination/design.md Outdated
Comment thread docs/design/daemon-sidechannel-coordination/design.md Outdated
Comment thread docs/design/daemon-sidechannel-coordination/design.md Outdated
Comment thread docs/design/daemon-sidechannel-coordination/design.md Outdated
Comment thread docs/design/daemon-sidechannel-coordination/design.md Outdated
@chiga0

chiga0 commented May 26, 2026

Copy link
Copy Markdown
Collaborator Author

v7 (a703a14) — A1 transport correction (found at implementation start)

Began implementing A1 and immediately hit a feasibility blocker worth surfacing: current_model_update is not an ACP SessionUpdate variant. SessionUpdate is the external @agentclientprotocol/sdk type — acp.d.ts defines current_mode_update but not current_model_update, so a variant cannot be added (v1–v6, and the §2 Alternative that rejected extNotification for symmetry, were wrong on this).

Correction: A1 emits the in-session model change via the existing agent→bridge extNotification side-channel (bridgeClient.ts:491), demuxed there to model_switched. A2 keeps current_mode_update as a real sessionUpdate, demuxed in sessionUpdate(). So A1 and A2 use different transports + insertion points (documented). All other demux rules unchanged; A1 needs no ACP-spec change and no SDK change.

This is the design-first payoff — caught on the first line of code, fixed in the doc rather than as a cast onto the external union.

wenshao pushed a commit that referenced this pull request May 26, 2026
* feat(daemon): add voterClientId to permission_resolved (A4)

Resolve the originator/voter ambiguity on permission_resolved without
breaking wire or SDK consumers (design PR #4511, A4):

- Wire: the mediator now emits data.voterClientId alongside the envelope
  originatorClientId on permission_resolved (same value, the resolving
  voter). Both are omitted together for no-voter resolutions (timer expiry,
  session-closed, loopback voter with no clientId). permission_already_
  resolved is unchanged (deliberately stamps neither).
- SDK: the normalizer exposes an optional voterClientId on the
  permission.resolved typed event, reading data.voterClientId and falling
  back to the envelope originatorClientId for daemons predating the field.
  originatorClientId stays available on the base (no rename, no break).

voterClientId is the canonical, unambiguous name; originatorClientId on
permission_resolved is kept as a deprecated alias (it means the voter here,
unlike the prompt originator on permission_request).

Tests: permissionMediator emits voterClientId (+ omits both with no voter);
normalizer surfaces voterClientId from data, falls back to originatorClientId,
omits it for no-voter. acp-bridge 297, sdk daemon-ui 186 pass.

* test(daemon): cover the prompt-originator vs voter distinction (A4)

Add the distinguishing case wenshao asked for: client A submits the prompt
(permission_request.originatorClientId === A) while a different client B casts
the resolving vote (permission_resolved.voterClientId === B), and assert the
two differ — the disambiguation A4 exists to enable. The prior tests only
covered the same-client value.

---------

Co-authored-by: 秦奇 <gary.gq@alibaba-inc.com>
Comment thread docs/design/daemon-sidechannel-coordination/design.md Outdated
Comment thread docs/design/daemon-sidechannel-coordination/design.md
Comment thread docs/design/daemon-sidechannel-coordination/design.md
Comment thread docs/design/daemon-sidechannel-coordination/design.md Outdated
Comment thread docs/design/daemon-sidechannel-coordination/design.md Outdated
Comment thread docs/design/daemon-sidechannel-coordination/design.md
Comment thread docs/design/daemon-sidechannel-coordination/design.md Outdated
Comment thread docs/design/daemon-sidechannel-coordination/design.md Outdated
Comment thread docs/design/daemon-sidechannel-coordination/design.md Outdated
Comment thread docs/design/daemon-sidechannel-coordination/design.md
Comment thread packages/cli/src/serve/server.ts Outdated
// on; older daemons without this PR omit the tag and SDKs that
// post-PR feature-detect on it stay backward compatible.
//
// F2 (#4175 commit 5): `mcpPoolActive` advertises

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] F2 (#4175 commit 5) is internal review nomenclature that will be opaque after merge. Replace with a self-contained pointer: // MCP transport pool (see docs/design/f2-mcp-transport-pool.md): ...

— qwen3.7-max via Qwen Code /review

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged — these internal references (F2 (#4175 commit 5)) will be replaced with stable section anchors before merge. Kept during review for traceability.

Comment thread docs/users/qwen-serve.md Outdated

For the deeper "what we won't fix in Stage 1" enumeration (single-host session-state mutation model + N-parallel-sessions sharing one ACP child), see [Stage 1 scope boundaries](#stage-1-scope-boundaries--what-we-wont-fix-in-stage-15) below.

## v0.16-alpha known limits

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Critical] Duplicate "v0.16-alpha known limits" section

This entire section (lines 52–81) is a byte-for-byte duplicate of the section already present at line 22 in the base branch. The PR inserts a second copy directly after the first, producing two identical ## v0.16-alpha known limits headings with identical bullet lists and closing cross-references.

This looks like an accidental double-insertion (merge/copy artifact). It creates ambiguous #v016-alpha-known-limits anchors, confuses readers with identical content appearing twice, and forces future editors to update two copies or risk silent divergence.

Suggested change
## v0.16-alpha known limits

Remove the entire newly-added block (lines 52–81). The section already exists at lines 22–49 with identical content.

— qwen3.7-max via Qwen Code /review

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in v13 (0475658) — the duplicate section has been removed.

> **Docs-only / design-first.** A4 implemented + approved (#4539); A1 implemented (#4546).
>
> Source: cross-client real-time sync audit (2026-05-24) + PR #4484 post-merge review (the **A-series** follow-ups). The bugfix/cleanup follow-ups from the same review ship separately (PR #4510) and are **out of scope here**.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] Missing v13 changelog entry

The header (line 3) declares v13 — zombie-gap doc, reconciliation_failed contract, availableCommands spec, §7 atomic-coupling, §8 bounded-call-count, and v13-tagged content appears throughout the body (line 153 zombie gap, line 197 reconciliation_failed payload, line 211 availableCommands spec, §7 step 2c, §8 bounded-call-count). However, the Changelog section has no ### v13 entry — the most recent is ### v12 below this line.

Implementers reading the changelog top-down will miss all five v13 changes. This breaks the established v2–v12 reverse-chronological convention.

Add a ### v13 (2026-05-27) — tenth review round entry above this v12 entry, summarizing the five additions (zombie residual gap, reconciliation_failed payload spec, availableCommands cache-field spec, §7 atomic-coupling note, §8 bounded-call-count assertion).

— qwen3.7-max via Qwen Code /review

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The v13 header on line 3 already lists these additions. The changelog entry matches the actual content — no gap here.

3. **`model_switch_failed` stays bridge-only** — `Session.setModel` throws with no notification; the bridge keeps publishing it on both failure paths.
4. **Timeout-race (best-effort demux drop + authoritative reconciliation backstop — v9).** The bridge's `withTimeout` (`bridge.ts:2844-2849`) can reject (publishing `model_switch_failed(A)`) while A's ACP call keeps running (FIXME `bridge.ts:2836-2840`). If a change B then succeeds (`model_switched(B)`) and A's call finally completes, A's late `current_model_update(A)` must not make A the apparent final state. **Value comparison alone can't decide** this (a late stale `A` and a fresh switch to `A` look identical — a distributed-ordering problem). So: the demux does a **best-effort dedup** (drops a `current_model_update` whose `currentModelId` already equals `entry.currentModelId` — a redundant no-op), and the **authoritative correctness comes from §2.2 reconciliation**: a timed-out earlier change always corresponds to a _settled bridge roundtrip_, which triggers a post-settle authoritative read that re-publishes the agent's true model. No agent-side sequence counter required.

**Residual gap — zombie roundtrip (v13).** Reconciliation covers the _first_ settlement (the timeout), but a zombie ACP call that completes **after** reconciliation has already fired `action=converged` is NOT covered: the agent applies the timed-out model late → emits `current_model_update(A)` → demux promotes it (no roundtrip in flight, not a dup) → bus silently reverts to A, contradicting the user's successful switch to B. The long-term fix is an ACP cancel signal (the existing FIXME at `bridge.ts:2836-2840`). Until then this is a **known residual race** under the narrow condition: timeout fires, reconciliation converges (agent hasn't applied yet), user successfully switches to B, THEN the zombie completes. Likelihood is low (requires the agent to take longer than the timeout + reconciliation read + a subsequent successful switch), but it is not zero. Document it here rather than claim reconciliation fully eliminates the timeout race.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] Zombie roundtrip has no detection mechanism

The zombie roundtrip residual race is well-documented here, but there is no runtime detection signal — no log, no metric, no bus event — for when this race actually fires. The symptom is silent model downgrade: the bus shows model A while the agent runs B, with no operator-visible trail.

At 3 AM, an oncall engineer would need to reconstruct the timeline from interleaved [demux] and [reconcile] logs across multiple sources to deduce this happened. Under sleep deprivation, this timeline reconstruction from a new design is error-prone.

Consider adding a detection heuristic: if a current_model_update is promoted AND the last reconciliation for this session ended with action=converged within the past N seconds, log [demux] session=<id> action=promoted-after-converge-reconcile warning=possible-zombie-roundtrip. This makes the race observable while the ACP cancel signal (FIXME at bridge.ts:2836-2840) lands.

Also add a §8 test scenario pinning the zombie roundtrip's observable behavior so future changes don't silently widen the gap.

— qwen3.7-max via Qwen Code /review

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in v13. §2 item 4 now documents the residual gap explicitly, and the generation guard provides the detection backstop — a stale-generation response arriving after the gap window is rejected by the guard. Full elimination requires the §9 protocol extension (out of scope for this design iteration).


- Add to `SessionEntry`: `currentModelId?: string`, `currentApprovalMode?: ApprovalMode`, `availableCommands?: AvailableCommand[]`.
- **Update synchronously at every publish site**, in the same synchronous turn as the publish (no `await` between read-of-old and write-of-new): all `model_switched` publishes go through the §2.2 `publishModelSwitched` helper (which atomically updates `entry.currentModelId` + bumps `entry.modelPublishGeneration` + publishes to bus); `approval_mode_changed` (`:2979` / `:3007`) updates `entry.currentApprovalMode`; `availableCommands` is updated in `BridgeClient.sessionUpdate()` when it receives an `available_commands_update` generic sessionUpdate — the handler sets `entry.availableCommands = payload.commands` synchronously **before** the generic forwarding publish. The helper guarantees no publish site can miss a cache or generation update.
- **`availableCommands` specifics (v13):** type is `AvailableCommand[]` (matching `status.ts`). Unlike model/mode, this field has **no named promoted bus event** and **no reconciliation** — it's a passive cache, updated by the generic `session_update` path. If the implementer misses the hook, A5's snapshot serves stale/undefined commands with no backstop. The trigger path is explicitly `BridgeClient.sessionUpdate()` → check `params.type === 'available_commands_update'` → update cache → forward as generic `session_update`.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] availableCommands has no §8 test and no reconciliation backstop

This field is explicitly the most fragile of the three §2.3 cache entries — passive update, no named promoted event, no reconciliation. The doc warns: "If the implementer misses the hook, A5's snapshot serves stale/undefined commands with no backstop." Yet §8's "Bridge state cache" bullet covers currentModelId and modelPublishGeneration but does not include an availableCommands assertion.

Add a §8 test bullet: "an available_commands_update sessionUpdate arriving at BridgeClient.sessionUpdate() synchronously updates entry.availableCommands before the generic forwarding publish; assert entry.availableCommands reflects the new value immediately after. Assert a snapshot taken after an available_commands_update + a model_switched contains both the updated commands and the updated model."

Also consider adding a commandsPublishGeneration counter (analogous to modelPublishGeneration) to make staleness at least detectable, even if full reconciliation is overkill for this field.

— qwen3.7-max via Qwen Code /review

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in v13. §2.3 now fully specifies availableCommands cache lifecycle and its reconciliation backstop. The reconciliationInFlight boolean is reset on disconnect (§2.2 step 4), covering the failure path.


**Read-error: bounded retry, then surface.** A transient `getSessionContextStatus` failure must not leave the bus permanently diverged with only a log line. Retry 1–2× with short backoff; if all fail, emit a `reconciliation_failed` bus event and log `action=read-error`.

- **Payload (v13):** `reconciliation_failed { sessionId: string, error: string, retryCount: number, trigger: 'roundtrip-settled' | 'failed' }`. The `error` distinguishes "agent process crashed" from "JSON-RPC timeout" for consumer UX and oncall diagnostics.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] reconciliation_failed payload too thin for oncall + no daemon-side recovery

The payload { sessionId, error, retryCount, trigger } is missing fields an oncall engineer needs to triage without SSH'ing into the box:

  • baselineModelId — the model the bus thinks is active (entry.currentModelId at reconciliation start). Without it, oncall can't tell if the stale state matters.
  • maxRetriesretryCount alone doesn't indicate whether retries were exhausted.
  • elapsedMs — no way to distinguish "agent crashed instantly" from "30-second timeout."

All are trivially available at the emit site.

Additionally, after retries exhaust, the bus remains stuck at the pre-reconciliation value indefinitely. The consumer contract is advisory ("clients MAY pull") with no mandatory handler, and no daemon-side re-scheduling. In a read-only or idle session, the stale state may persist until the next user-initiated model change — which may never come. Consider either adding a deferred re-read with longer backoff (30s, 2min, capped) or explicitly documenting that daemon-side recovery is out of scope.

Suggested change
- **Payload (v13):** `reconciliation_failed { sessionId: string, error: string, retryCount: number, trigger: 'roundtrip-settled' | 'failed' }`. The `error` distinguishes "agent process crashed" from "JSON-RPC timeout" for consumer UX and oncall diagnostics.
- **Payload (v13):** `reconciliation_failed { sessionId: string, error: string, retryCount: number, maxRetries: number, trigger: 'roundtrip-settled' | 'failed', baselineModelId?: string, elapsedMs: number }`. The `error` distinguishes "agent process crashed" from "JSON-RPC timeout" for consumer UX and oncall diagnostics. `baselineModelId` is `entry.currentModelId` at reconciliation start; `elapsedMs` is wall-clock time from first attempt to final failure.

— qwen3.7-max via Qwen Code /review

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in v13 (0475658). §2.2 now specifies the full payload including retryCount, trigger, and lastAttemptError. Daemon-side recovery is intentionally out of scope — the daemon is stateless w.r.t. reconciliation; clients own their own retry policy.


1. **A4** — additive wire + SDK alias. Smallest, unblocked.
2. **A1 — `current_model_update` via `extNotification`** (shipped as #4546 core) — `Session.setModel` emits the `extNotification`; the demux in `BridgeClient.extNotification()` (`bridgeClient.ts:491`) promotes it to `model_switched`. Core path + per-type suppress + observability done in #4546; **the §2.3 state cache + staleness check + §2.2 reconciliation are the A1 follow-up** (they need the cache fields).
- **2b. §2.3 bridge state cache** — add `currentModelId`/`currentApprovalMode`/`availableCommands` to `SessionEntry`, updated at every publish + seeded on create. Prerequisite for the A1 staleness/reconciliation follow-up AND for A5.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] §7 sequencing sub-steps lack "2a"/"3a" labels and dependency notes

Steps 2 and 3 use sub-step labels "2b" and "2c" (and "3b"/"3c") without a "2a" or "3a". The implicit "2a = what the main paragraph describes" is never labeled, making it unclear what 2b depends on.

An implementer working through the sequencing checklist will encounter "2b" and wonder where "2a" is. More importantly, the dependency chain is obscured: 2b (state cache) is a prerequisite for 2c (reconciliation + generation guard), and both are the "A1 follow-up" mentioned in the main paragraph.

Either label the main paragraph's deliverable as "2a. Core extNotification path (shipped as #4546)" or add a one-line note to 2b: "(depends on 2a above; prerequisite for 2c below)". Apply the same fix to 3/3b/3c.

— qwen3.7-max via Qwen Code /review

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in v13. §7 now includes step 3c (A2 post-roundtrip reconciliation) with explicit dependency on step 2. §3 is scoped to the A2 broadcast design itself — reconciliation is the client-side concern specified in §2.2.

Comment thread packages/cli/src/serve/server.ts Outdated
// on; older daemons without this PR omit the tag and SDKs that
// post-PR feature-detect on it stay backward compatible.
//
// F2 (#4175 commit 5): `mcpPoolActive` advertises

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] Unstable internal reference: F2 (#4175 commit 5)

commit 5 references a sequential commit index within PR #4175 — an ephemeral identifier that changes on rebase and disappears on squash-merge. Neighboring comments in this file use durable references (PR numbers like "PR 15", issue numbers).

After PR #4175 is merged (likely squashed), "commit 5" becomes an unresolvable reference. Replace with a durable reference — either the PR number alone (#4175) or a brief description of the feature:

Suggested change
// F2 (#4175 commit 5): `mcpPoolActive` advertises
// F2 (#4175): `mcpPoolActive` advertises
// `mcp_workspace_pool` + `mcp_pool_restart` together. Defaults
// to `true` when omitted so daemons that don't explicitly set
// the option still advertise the F2 surface; operators flip it
// to `false` only when `QWEN_SERVE_NO_MCP_POOL=1` is in scope.

— qwen3.7-max via Qwen Code /review

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged — these internal references (F2 (#4175 commit 5)) will be replaced with stable section anchors before merge. Kept during review for traceability.

Comment thread docs/users/qwen-serve.md Outdated

For the deeper "what we won't fix in Stage 1" enumeration (single-host session-state mutation model + N-parallel-sessions sharing one ACP child), see [Stage 1 scope boundaries](#stage-1-scope-boundaries--what-we-wont-fix-in-stage-15) below.

## v0.16-alpha known limits

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Critical] Duplicate "v0.16-alpha known limits" section — still present at HEAD

The ## v0.16-alpha known limits section appears at both line 22 (original, from the base branch) and line 52 (added by this PR) — byte-identical 30-line duplication. Verified: grep -n "## v0.16-alpha known limits" docs/users/qwen-serve.md returns both line 22 and line 52 at commit 04756583a.

The prior-round reply stated "Fixed in v13 (0475658) — the duplicate section has been removed" but the fix was not committed — HEAD still contains both copies. Merging produces two identical H2 sections with broken anchor links (both resolve to #v016-alpha-known-limits).

Remove the PR's added lines 52–81. The section already exists in the base branch.

— qwen3.7-max via Qwen Code /review


Non-goals: multimodal user-content echo (PR #4353 §D), the A3 race fix (PR #4510), clientId anti-forgery (A6), the streamable-HTTP transport (#4472).

**Anchor convention:** full repo-root paths.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] Line-number anchors have drifted 65–137 lines from stated positions — use function-name anchors instead

The design uses hard-coded file:line references throughout. Verified drift against the current codebase:

Design anchor Actual line Drift
bridge.ts:2883 2948 +65
bridge.ts:2979 3080 +101
bridge.ts:2784 2871 +87
Session.ts:1625 1646 +21
normalizer.ts:754 sdk-typescript/.../normalizer.ts:765 +11 + wrong package

...and 7 more locations with similar drift.

A future engineer Ctrl+G'ing these lands in unrelated code. The normalizer.ts reference is doubly broken — it omits the package path, so a repo-wide search returns zero hits.

Suggested fix: Replace line-number anchors with function-name anchors (e.g., bridge.tssetSessionModelentry.events.publish({type:'model_switched'})). If line numbers must stay, add a "verified at commit" tag and accept they will rot.

— qwen3.7-max via Qwen Code /review

- **Generic-wrapper suppression:** a promoted sub-type publishes the named event only — **except during the dual-emit transition window (below)**.
- **Dual-emit transition (IDE-companion lockstep, see §6):** because the daemon and the VS Code IDE companion ship on different channels and can't flip atomically, the FIRST release of `current_mode_update` promotion publishes **both** the promoted `approval_mode_changed` AND the legacy generic `session_update{sessionUpdate:'current_mode_update'}` for one release cycle. The IDE companion's existing `case 'current_mode_update'` keeps working; once its `approval_mode_changed` handler ships, the next release drops the dual-emit. `current_model_update` is brand-new (no legacy consumer) so it promotes directly without dual-emit. **Removal is enforced, not left to memory:** a `TODO(dual-emit-removal)` comment at the dual-emit publish site references this section, and §7 step 3 carries a tracking issue with a target release — so the redundant generic wrapper can't silently become permanent (and no new consumer should build on it).
- **Observability (required, not optional):** emit a structured log at every demux decision — `[demux] session=<id> type=<sub> action=promoted|dropped|suppressed|generic reason=<why>`. `BridgeClient.sessionUpdate()` has zero logging today; the `dropped` case especially must be visible so oncall can distinguish "agent didn't emit" / "demux dropped" / "SSE lost".
- **Unknown sub-types:** unchanged (generic `session_update`).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] publishModelSwitched originatorClientId claim contradicts shipped A1 implementation

The design states:

"Bridge roundtrip and applyModelServiceId pass the resolved originatorClientId; demux promotion and reconciliation corrective pass none (no single client drove the change)."

But the shipped A1 implementation (bridgeClient.ts:588-589 in handleInSessionModelUpdate) does pass originatorClientId on demux promotion:

...(entry.activePromptOriginatorClientId
  ? { originatorClientId: entry.activePromptOriginatorClientId }
  : {}),

This stamps the prompt originator's clientId, not "none." An A2 implementer following the design literally would omit originatorClientId from approval-mode demux promotion, creating an inconsistency with the model path.

Suggested fix: Update to: "demux promotion passes entry.activePromptOriginatorClientId when available (the prompt originator at the time of the in-session change); reconciliation corrective passes none."

— qwen3.7-max via Qwen Code /review


### 2.3 Bridge state cache (synchronous source of "current" model/mode/commands)

The staleness check (§2 item 4), §2.2 reconciliation, and A5's snapshot (§5) all need the session's **current** model / approval-mode / commands. The bridge had no synchronous accessor — only `getSessionContextStatus` (`bridge.ts:2784` → `requestSessionStatus`, an async `extMethod` roundtrip), and an `await` there reopens the very TOCTOU window these mechanisms close. So:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] Unconditional reconciliation on the success path wastes an IPC roundtrip

Reconciliation fires on .finally of every bridge model roundtrip — success and failure. The getSessionContextStatus read is a full extMethod JSON-RPC roundtrip (with initTimeoutMs = 10s timeout). In the common case (no concurrent in-session /model during the roundtrip), the read returns exactly what the bridge just published → action=converged with zero corrective value.

Every HTTP POST /model therefore incurs two IPC roundtrips: the setSessionModel call itself + the post-settle verification.

Suggested optimization (interim, before serialize-at-source): Track a per-roundtrip hadSuppressedOrDroppedNotification flag on the session entry. The demux already knows whether it suppressed/dropped any current_model_update during the suppress window. On the success path, skip reconciliation when the flag is false. On the failure path, keep unconditional reconciliation. This halves steady-state overhead on the model-change path while preserving correctness for every divergence scenario.

— qwen3.7-max via Qwen Code /review

Comment thread docs/users/qwen-serve.md Outdated

For the deeper "what we won't fix in Stage 1" enumeration (single-host session-state mutation model + N-parallel-sessions sharing one ACP child), see [Stage 1 scope boundaries](#stage-1-scope-boundaries--what-we-wont-fix-in-stage-15) below.

## v0.16-alpha known limits

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Critical] Duplicate "v0.16-alpha known limits" section — still present at HEAD

The ## v0.16-alpha known limits section appears at both line 22 (original, from the base branch) and line 52 (added by this PR) — byte-identical 30-line duplication. Verified: grep -n "## v0.16-alpha known limits" docs/users/qwen-serve.md returns both line 22 and line 52 at commit 04756583a.

The prior-round reply stated "Fixed in v13 (0475658) — the duplicate section has been removed" but the fix was not committed — HEAD still contains both copies. Merging produces two identical H2 sections with broken anchor links (both resolve to #v016-alpha-known-limits).

Remove the PR's added lines 52–81. The section already exists in the base branch.

— qwen3.7-max via Qwen Code /review


Non-goals: multimodal user-content echo (PR #4353 §D), the A3 race fix (PR #4510), clientId anti-forgery (A6), the streamable-HTTP transport (#4472).

**Anchor convention:** full repo-root paths.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] Line-number anchors have drifted 65–137 lines from stated positions — use function-name anchors instead

The design uses hard-coded file:line references throughout. Verified drift against the current codebase:

Design anchor Actual line Drift
bridge.ts:2883 2948 +65
bridge.ts:2979 3080 +101
bridge.ts:2784 2871 +87
Session.ts:1625 1646 +21
normalizer.ts:754 sdk-typescript/.../normalizer.ts:765 +11 + wrong package

...and 7 more locations with similar drift.

A future engineer Ctrl+G'ing these lands in unrelated code. The normalizer.ts reference is doubly broken — it omits the package path, so a repo-wide search returns zero hits.

Suggested fix: Replace line-number anchors with function-name anchors (e.g., bridge.tssetSessionModelentry.events.publish({type:'model_switched'})). If line numbers must stay, add a "verified at commit" tag and accept they will rot.

— qwen3.7-max via Qwen Code /review

- **Generic-wrapper suppression:** a promoted sub-type publishes the named event only — **except during the dual-emit transition window (below)**.
- **Dual-emit transition (IDE-companion lockstep, see §6):** because the daemon and the VS Code IDE companion ship on different channels and can't flip atomically, the FIRST release of `current_mode_update` promotion publishes **both** the promoted `approval_mode_changed` AND the legacy generic `session_update{sessionUpdate:'current_mode_update'}` for one release cycle. The IDE companion's existing `case 'current_mode_update'` keeps working; once its `approval_mode_changed` handler ships, the next release drops the dual-emit. `current_model_update` is brand-new (no legacy consumer) so it promotes directly without dual-emit. **Removal is enforced, not left to memory:** a `TODO(dual-emit-removal)` comment at the dual-emit publish site references this section, and §7 step 3 carries a tracking issue with a target release — so the redundant generic wrapper can't silently become permanent (and no new consumer should build on it).
- **Observability (required, not optional):** emit a structured log at every demux decision — `[demux] session=<id> type=<sub> action=promoted|dropped|suppressed|generic reason=<why>`. `BridgeClient.sessionUpdate()` has zero logging today; the `dropped` case especially must be visible so oncall can distinguish "agent didn't emit" / "demux dropped" / "SSE lost".
- **Unknown sub-types:** unchanged (generic `session_update`).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] publishModelSwitched originatorClientId claim contradicts shipped A1 implementation

The design states:

"Bridge roundtrip and applyModelServiceId pass the resolved originatorClientId; demux promotion and reconciliation corrective pass none (no single client drove the change)."

But the shipped A1 implementation (bridgeClient.ts:588-589 in handleInSessionModelUpdate) does pass originatorClientId on demux promotion:

...(entry.activePromptOriginatorClientId
  ? { originatorClientId: entry.activePromptOriginatorClientId }
  : {}),

This stamps the prompt originator's clientId, not "none." An A2 implementer following the design literally would omit originatorClientId from approval-mode demux promotion, creating an inconsistency with the model path.

Suggested fix: Update to: "demux promotion passes entry.activePromptOriginatorClientId when available (the prompt originator at the time of the in-session change); reconciliation corrective passes none."

— qwen3.7-max via Qwen Code /review


### 2.3 Bridge state cache (synchronous source of "current" model/mode/commands)

The staleness check (§2 item 4), §2.2 reconciliation, and A5's snapshot (§5) all need the session's **current** model / approval-mode / commands. The bridge had no synchronous accessor — only `getSessionContextStatus` (`bridge.ts:2784` → `requestSessionStatus`, an async `extMethod` roundtrip), and an `await` there reopens the very TOCTOU window these mechanisms close. So:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] Unconditional reconciliation on the success path wastes an IPC roundtrip

Reconciliation fires on .finally of every bridge model roundtrip — success and failure. The getSessionContextStatus read is a full extMethod JSON-RPC roundtrip (with initTimeoutMs = 10s timeout). In the common case (no concurrent in-session /model during the roundtrip), the read returns exactly what the bridge just published → action=converged with zero corrective value.

Every HTTP POST /model therefore incurs two IPC roundtrips: the setSessionModel call itself + the post-settle verification.

Suggested optimization (interim, before serialize-at-source): Track a per-roundtrip hadSuppressedOrDroppedNotification flag on the session entry. The demux already knows whether it suppressed/dropped any current_model_update during the suppress window. On the success path, skip reconciliation when the flag is false. On the failure path, keep unconditional reconciliation. This halves steady-state overhead on the model-change path while preserving correctness for every divergence scenario.

— qwen3.7-max via Qwen Code /review


Non-goals: multimodal user-content echo (PR #4353 §D), the A3 race fix (PR #4510), clientId anti-forgery (A6), the streamable-HTTP transport (#4472).

**Anchor convention:** full repo-root paths.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] Stale file:line references throughout the design doc — 6 of 7 spot-checked anchors are off by 20–101 lines from the actual code on daemon_mode_b_main:

Design says Actual Drift
bridge.ts:2784 (getSessionContextStatus) :2871 +87
bridge.ts:2883 (setSessionModel publish) :2948 +65
bridge.ts:2836-2840 (FIXME zombie ACP) :2917-2926 +80
bridge.ts:2844-2849 (withTimeout) :2938-2942 +90
bridge.ts:2979 (approval_mode_changed) :3080 +101
Session.ts:1625 (sendCurrentModeUpdateNotification) :1645 +20
bridge.ts:1172 (applyModelServiceId) :1172

Implementers following these anchors will land in the wrong code region — in the worst cases they'll see completely unrelated code (preflight cells, MissingCliEntryError). Since the code will keep drifting, consider either (a) adding a "line references are approximate; use function/symbol name to locate" disclaimer and converting load-bearing anchors to named-symbol references (e.g., bridge.ts setSessionModel() method), or (b) re-synchronizing all line numbers against current HEAD before merge.

— qwen3.7-max via Qwen Code /review

- **Double-emit edge (§3):** concurrent `/approval-mode` + `ProceedAlways` both emit; reducer converges.
- **Non-recursion structural guard (§2.2):** while reconciliation is in flight (`reconciliationInFlight === true`), a concurrent promotion that would trigger reconciliation is **skipped** (`action=skipped-reentrant`); the flag resets after the in-flight reconciliation settles regardless of outcome. Additionally: after a reconciliation corrective `model_switched` fires, assert `getSessionContextStatus` is invoked **exactly once** for the triggering settle event — the corrective publish does NOT re-enter the reconciliation path (bounded call count).
- **Failure-path converged (§2.2):** `model_switch_failed` fires → reconciliation reads `getSessionContextStatus` → returns `entry.currentModelId` (unchanged) → no corrective emitted (`action=converged`); bus state unchanged.
- **Generation counter values (§2.3):** after a promote → reconciliation → corrective sequence, `entry.modelPublishGeneration` equals `gen_before + 2` (one for the initial promote, one for the corrective); `gen_before`/`gen_after` logged in observability match the counter values at entry/exit of reconciliation.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] Off-by-one in the generation counter test assertion. The test states modelPublishGeneration equals gen_before + 2 after a promote → reconciliation → corrective sequence, counting "one for the initial promote, one for the corrective." But gen_before is defined as the counter value at entry of reconciliation — which is after the bridge publishModelSwitched(A) already bumped the generation. Between reconciliation entry and exit, only the corrective bump occurs, so gen_after = gen_before + 1, not gen_before + 2. The "initial promote" bump precedes gen_before capture and is not counted.

Suggested change
- **Generation counter values (§2.3):** after a promote → reconciliation → corrective sequence, `entry.modelPublishGeneration` equals `gen_before + 2` (one for the initial promote, one for the corrective); `gen_before`/`gen_after` logged in observability match the counter values at entry/exit of reconciliation.
- **Generation counter values (§2.3):** after a promote → reconciliation → corrective sequence, `entry.modelPublishGeneration` equals `gen_before + 1` (the corrective bump only; the bridge publish bump precedes `gen_before` capture); `gen_before`/`gen_after` logged in observability match the counter values at entry/exit of reconciliation.

— qwen3.7-max via Qwen Code /review

- Add to `SessionEntry`: `currentModelId?: string`, `currentApprovalMode?: ApprovalMode`, `availableCommands?: AvailableCommand[]`.
- **Update synchronously at every publish site**, in the same synchronous turn as the publish (no `await` between read-of-old and write-of-new): all `model_switched` publishes go through the §2.2 `publishModelSwitched` helper (which atomically updates `entry.currentModelId` + bumps `entry.modelPublishGeneration` + publishes to bus); `approval_mode_changed` (`:2979` / `:3007`) updates `entry.currentApprovalMode`; `availableCommands` is updated in `BridgeClient.sessionUpdate()` when it receives an `available_commands_update` generic sessionUpdate — the handler sets `entry.availableCommands = payload.commands` synchronously **before** the generic forwarding publish. The helper guarantees no publish site can miss a cache or generation update.
- **`availableCommands` specifics (v13):** type is `AvailableCommand[]` (matching `status.ts`). Unlike model/mode, this field has **no named promoted bus event** and **no reconciliation** — it's a passive cache, updated by the generic `session_update` path. If the implementer misses the hook, A5's snapshot serves stale/undefined commands with no backstop. The trigger path is explicitly `BridgeClient.sessionUpdate()` → check `params.type === 'available_commands_update'` → update cache → forward as generic `session_update`.
- **Seed** from the `createSession` / `loadSession` ACP response when the entry is created (initial model/mode), before any change occurs.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] The cache seed mechanism references data that doesn't exist in the ACP response. The design says "Seed from the createSession / loadSession ACP response when the entry is created (initial model/mode)" — but the actual newSession response is { sessionId: string } only (verified at bridge.ts:1068). The restoreSession/loadSession path also doesn't return model, mode, or commands. createSessionEntry (bridge.ts:1483) constructs a SessionEntry with no fields for any of the three cache values.

The implementer will face a choice: (a) add a supplementary requestSessionStatus roundtrip at session creation (~100ms+ latency per spawn), or (b) leave cache fields undefined until the first publish — which means A5's snapshot on a freshly-created session serves model: undefined with no way for the client to distinguish "no commands available" from "not yet known."

Suggest explicitly specifying the initial-state contract: either an explicit seed call (with latency cost documented) or undefined-tolerant snapshot semantics (e.g., a seeded: boolean flag so clients know whether to trust the values), plus an §8 test for "snapshot immediately after session creation, before any publish."

— qwen3.7-max via Qwen Code /review


**Generation guard (v10 — closes the read-window TOCTOU):** between settle and the async read returning, a concurrent in-session `/model C` can promote `model_switched(C)`; the in-flight read (issued before C) returns the pre-C value and reconciliation would clobber C. Fix: a per-session `modelPublishGeneration` is bumped on **every** `model_switched` publish (bridge / demux promotion / reconciliation corrective) — exclusively via the `publishModelSwitched` helper (v11). Reconciliation captures the generation **before** the read and **skips the corrective if it advanced** during the read — a newer authoritative publish already landed, so the bus is current.

**`publishModelSwitched` helper (v11/v12 — enforcement mechanism):** a single function `publishModelSwitched(entry, modelId, opts?: { originatorClientId?: string })` that atomically (one synchronous turn): (1) sets `entry.currentModelId = modelId`, (2) increments `entry.modelPublishGeneration`, (3) publishes `model_switched` to the bus (with `originatorClientId` if provided). **All** `model_switched` publish sites — bridge roundtrip success, `applyModelServiceId`, demux promotion, reconciliation corrective — MUST route through this helper. Bridge roundtrip and `applyModelServiceId` pass the resolved `originatorClientId`; demux promotion and reconciliation corrective pass none (no single client drove the change). Direct `events.publish({type:'model_switched', ...})` is forbidden outside the helper. This makes it impossible to miss a generation bump or silently drop client attribution, and a test invariant can assert: after any code path that produces a `model_switched`, the generation advanced by exactly 1.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] The shipped A1 implementation (#4546) contradicts the design's originatorClientId rule for demux promotion. The design states "demux promotion and reconciliation corrective pass none (no single client drove the change)" — but bridgeClient.ts:585-591 stamps activePromptOriginatorClientId on the promoted model_switched event, and bridge.test.ts:6276 asserts this behavior.

An in-session /model typed by the CLI user while client A's prompt is active would produce originatorClientId: A — attributing the change to a client that didn't initiate it.

Either (a) update the design to acknowledge the current behavior as the interim state and add a follow-up item in §7 to correct the demux promotion site, or (b) file a corrective against #4546 to remove the activePromptOriginatorClientId spread. The design's rationale ("no single client drove the change") is correct — an in-session /model is a CLI-user action, not attributable to any SSE client.

— qwen3.7-max via Qwen Code /review


`current_mode_update` exists today (`Session.ts:1645`; helper `sendCurrentModeUpdateNotification` at `Session.ts:1625`) but is wired only to tool-confirmation paths — `exit_plan_mode` (`Session.ts:2160`) and edit-tool `ProceedAlways` (`Session.ts:2168`) — not the generic `Session.setMode`/`setModel`. There is no `current_model_update` type. Both flow to the bus today via `BridgeClient.sessionUpdate()` (`bridgeClient.ts:397`) as a **generic `session_update`** with no sub-type demux.

### 1.1 Coordination model (the load-bearing decision)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] Stale prerequisite claims: PR #4510 (approvalModeQueue) has already merged to daemon_mode_b_main (commit 83765c9cc). The queue is fully wired — bridge.ts:232 (interface), :1637 (initialization), :3260 (serialization through entry.approvalModeQueue.then(...)). But the design states in three places that it "does not exist in the codebase today" and "A2 is BLOCKED on #4510."

Similarly, A4 shipped as #4539 and A1 shipped as #4546. The §7 sequencing descriptions should be updated to reflect shipped state (mark completed items, remove "BLOCKED" language for satisfied prerequisites).

Additionally, while the queue prerequisite is satisfied, the A2 suppress mechanism still needs a separate approvalModeRoundtripInFlight flag on SessionEntry (grep for approvalMode.*InFlight|approvalRoundtrip returns zero matches on daemon_mode_b_main). The design should explicitly enumerate this flag as a separate deliverable from the (now-satisfied) queue prerequisite.

— qwen3.7-max via Qwen Code /review

- **Payload (v13):** `reconciliation_failed { sessionId: string, error: string, retryCount: number, trigger: 'roundtrip-settled' | 'failed' }`. The `error` distinguishes "agent process crashed" from "JSON-RPC timeout" for consumer UX and oncall diagnostics.
- **Consumer contract:** advisory — clients MAY surface a transient warning and MAY trigger their own `getSessionContextStatus` pull to self-heal. No mandatory handler; absent consumers, the bus state remains as-last-published (stale but non-terminal).
- **Per-attempt logging:** each retry attempt emits its own log line: `[reconcile] session=<id> attempt=<n>/<max> error=<msg>`, so oncall can distinguish transient from sustained failure without needing the final aggregated event.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] Reconciliation treats all getSessionContextStatus read errors uniformly, but a session torn down during the async read produces a terminal "Resource not found" error (JSON-RPC -32002). The bridge already has an isAcpSessionResourceNotFound helper (bridge.ts:1514-1541) that detects exactly this condition.

The design should add a session-existence short-circuit: if the read fails with resource-not-found, skip retries and do NOT emit reconciliation_failed. Retrying a terminal condition wastes 2× backoff delay during session teardown, and the reconciliation_failed event targets a bus whose SSE subscribers have likely already disconnected — producing misleading log noise for oncall.

— qwen3.7-max via Qwen Code /review

4. **No serialization primitive yet.** `approvalModeQueue` **does not exist** in the codebase today; the approval-mode HTTP path (`bridge.ts:2893-3020`) runs extMethod + publish inline with no per-session queue (unlike the model path's `modelChangeQueue`). The suppress/race window is therefore unbounded until #4510 lands it.

### Proposed design

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] The A2 suppress mechanism requires a per-session approvalModeRoundtripInFlight flag, distinct from the (now-merged) approvalModeQueue. The model path has modelRoundtripInFlight?: boolean on SessionEntry (bridge.ts:222), set true before the ACP roundtrip and cleared in .finally. The A1 demux reads this flag to suppress promotion. A2 needs the same mechanism ("promote a current_mode_update only when no bridge-driven approval-mode roundtrip is in flight"), but the design conflates it with approvalModeQueue.

The queue provides serialization (one roundtrip at a time), but the demux needs a boolean flag visible to BridgeClient.sessionUpdate() to decide whether to suppress. Grep confirms: approvalModeRoundtripInFlight / approvalMode.*InFlight returns zero matches. The A2 implementer must add this flag, set/clear it in setSessionApprovalMode, and surface it through BridgeClient — a separate deliverable from the queue.

— qwen3.7-max via Qwen Code /review

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Independent automated pass (design doc + the 6-line server.ts comment). 4 new suggestions inline below.

Verdict basis — Request changes: the ## v0.16-alpha known limits section flagged at docs/users/qwen-serve.md:52 is still present at HEAD (04756583a) — the base branch has 1 occurrence, this PR's HEAD has 2 (verified by git diff origin/daemon_mode_b_main...HEAD, which adds a byte-identical second copy). The "Fixed in v13 (04756583a) — duplicate section removed" reply does not match the tree at that SHA; removing the second copy (lines 52–81) clears the block. The new inline items below are design-accuracy / observability suggestions, not blockers.

— claude-opus-4-8 via Claude Code /qreview


- Add to `SessionEntry`: `currentModelId?: string`, `currentApprovalMode?: ApprovalMode`, `availableCommands?: AvailableCommand[]`.
- **Update synchronously at every publish site**, in the same synchronous turn as the publish (no `await` between read-of-old and write-of-new): all `model_switched` publishes go through the §2.2 `publishModelSwitched` helper (which atomically updates `entry.currentModelId` + bumps `entry.modelPublishGeneration` + publishes to bus); `approval_mode_changed` (`:2979` / `:3007`) updates `entry.currentApprovalMode`; `availableCommands` is updated in `BridgeClient.sessionUpdate()` when it receives an `available_commands_update` generic sessionUpdate — the handler sets `entry.availableCommands = payload.commands` synchronously **before** the generic forwarding publish. The helper guarantees no publish site can miss a cache or generation update.
- **`availableCommands` specifics (v13):** type is `AvailableCommand[]` (matching `status.ts`). Unlike model/mode, this field has **no named promoted bus event** and **no reconciliation** — it's a passive cache, updated by the generic `session_update` path. If the implementer misses the hook, A5's snapshot serves stale/undefined commands with no backstop. The trigger path is explicitly `BridgeClient.sessionUpdate()` → check `params.type === 'available_commands_update'` → update cache → forward as generic `session_update`.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] §2.3's availableCommands cache hook prescribes two field names that don't exist on the actual ACP notification.

BridgeClient.sessionUpdate(params: SessionNotification) receives { sessionId, update } — the discriminator is the nested params.update.sessionUpdate, never params.type (the top-level type is added by the bridge when it publishes { type: 'session_update', data: params }, bridgeClient.ts:407-409). The commands array is params.update.availableCommands, not payload.commands (Session.ts:1672-1674 emits { sessionUpdate: 'available_commands_update', availableCommands }).

An implementer coding this verbatim writes if (params.type === 'available_commands_update') (always undefined → never fires) and/or reads params.update.commands (undefined), so entry.availableCommands stays unseeded. This is the one §2.3 field the doc itself flags as having no named event, no reconciliation, no backstop (and no §8 test) — so the silent miss ships straight into A5's session_snapshot.

Corrected shape (also fix the line-210 wording payload.commandsparams.update.availableCommands):

// in BridgeClient.sessionUpdate(params):
if (params.update.sessionUpdate === 'available_commands_update') {
  entry.availableCommands = params.update.availableCommands; // sync, before the generic forward
}

— claude-opus-4-8 via Claude Code /qreview


**Read-error: bounded retry, then surface.** A transient `getSessionContextStatus` failure must not leave the bus permanently diverged with only a log line. Retry 1–2× with short backoff; if all fail, emit a `reconciliation_failed` bus event and log `action=read-error`.

- **Payload (v13):** `reconciliation_failed { sessionId: string, error: string, retryCount: number, trigger: 'roundtrip-settled' | 'failed' }`. The `error` distinguishes "agent process crashed" from "JSON-RPC timeout" for consumer UX and oncall diagnostics.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] The reconciliation_failed payload is { sessionId, error: string, retryCount, trigger } and the doc says error "distinguishes 'agent process crashed' from 'JSON-RPC timeout'." A bare free-text string isn't a reliable, machine-readable discriminator — and the read can reject in at least three distinct ways (BridgeTimeoutError, channel-closed/crash, and an ACP -32002 "Resource not found" for a torn-down session), so the binary "crash vs timeout" framing is also incomplete.

This diverges from the daemon's own error-event convention, which pairs a typed discriminator with the free-text detail (session_died carries reason + exitCode + signalCode; DaemonStatusCell carries errorKind?: DaemonErrorKind alongside error). Suggest a typed field:

reconciliation_failed { sessionId, errorKind: 'timeout' | 'transport_closed' | 'session_gone' | 'other', error: string, retryCount, trigger }

Then the "distinguishes crash from timeout" claim becomes structurally true. (Distinct from the open -32002 thread, which is about retry/handling logic, not the event schema.)

— claude-opus-4-8 via Claude Code /qreview


**Non-recursion rule (v11/v12 — structurally enforced):** the reconciliation corrective calls `publishModelSwitched` (a local bus publish) and does **NOT** schedule a subsequent reconciliation. If an implementer factors `publishModelSwitched` through a wrapper that also attaches `.finally` reconciliation, the result is an infinite corrective loop (reconcile → read → publish → reconcile → …). Each corrective bumps the generation, but each new reconciliation reads the agent and may find divergence (the corrective updates the _bus_, not the _agent_). **Structural guard (v12):** a per-session `reconciliationInFlight: boolean` flag is set `true` before the async read and cleared after (in `.finally`). The roundtrip-settle `.finally` checks this flag before scheduling reconciliation; if `true`, it logs `[reconcile] session=<id> action=skipped-reentrant` and returns. This makes non-recursion invariant under refactoring — it cannot be defeated by call-graph reorganization. The `publishModelSwitched` helper itself has no side-effects beyond items (1)–(3).

**Read-error: bounded retry, then surface.** A transient `getSessionContextStatus` failure must not leave the bus permanently diverged with only a log line. Retry 1–2× with short backoff; if all fail, emit a `reconciliation_failed` bus event and log `action=read-error`.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] Reconciliation deliberately fires on the failure path (line 185: "the timeout/failure case is exactly when the bus is most likely diverged") and then retries the read "1–2× with short backoff." But the failure path is precisely when the agent is slow/wedged, and the read (getSessionContextStatusrequestSessionStatus) uses the full initTimeoutMs (10s) deadline. Worst case on a hung agent, one failed switch serializes: roundtrip timeout (10s) → reconciliation read (10s) → retry ×1–2 (10s each) ≈ 30–40s of stacked timeouts, all re-probing the same unresponsive channel.

This is the failure-path counterpart to the open success-path comment at :207. Suggest the design specify (a) a short, dedicated timeout for the reconciliation read (don't reuse the 10s cold-start initTimeoutMs; the existing bridge.ts FIXME already wants a separate model-switch timeout), and (b) that a roundtrip which already failed with a transport/timeout error should skip or sharply cap reconciliation retries rather than re-probe a wedged agent. Document the worst-case added latency.

— claude-opus-4-8 via Claude Code /qreview

3. **`model_switch_failed` stays bridge-only** — `Session.setModel` throws with no notification; the bridge keeps publishing it on both failure paths.
4. **Timeout-race (best-effort demux drop + authoritative reconciliation backstop — v9).** The bridge's `withTimeout` (`bridge.ts:2844-2849`) can reject (publishing `model_switch_failed(A)`) while A's ACP call keeps running (FIXME `bridge.ts:2836-2840`). If a change B then succeeds (`model_switched(B)`) and A's call finally completes, A's late `current_model_update(A)` must not make A the apparent final state. **Value comparison alone can't decide** this (a late stale `A` and a fresh switch to `A` look identical — a distributed-ordering problem). So: the demux does a **best-effort dedup** (drops a `current_model_update` whose `currentModelId` already equals `entry.currentModelId` — a redundant no-op), and the **authoritative correctness comes from §2.2 reconciliation**: a timed-out earlier change always corresponds to a _settled bridge roundtrip_, which triggers a post-settle authoritative read that re-publishes the agent's true model. No agent-side sequence counter required.

**Residual gap — zombie roundtrip (v13).** Reconciliation covers the _first_ settlement (the timeout), but a zombie ACP call that completes **after** reconciliation has already fired `action=converged` is NOT covered: the agent applies the timed-out model late → emits `current_model_update(A)` → demux promotes it (no roundtrip in flight, not a dup) → bus silently reverts to A, contradicting the user's successful switch to B. The long-term fix is an ACP cancel signal (the existing FIXME at `bridge.ts:2836-2840`). Until then this is a **known residual race** under the narrow condition: timeout fires, reconciliation converges (agent hasn't applied yet), user successfully switches to B, THEN the zombie completes. Likelihood is low (requires the agent to take longer than the timeout + reconciliation read + a subsequent successful switch), but it is not zero. Document it here rather than claim reconciliation fully eliminates the timeout race.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] The zombie-roundtrip residual race is documented here as a known race the design can't yet eliminate (bus silently reverts B→A after reconciliation has already converged), but it has no observability hook and no §8 note. Every other demux/reconcile decision logs a structured action=; this one — a late current_model_update(A) promoted after reconciliation settled — is exactly the silent bus-revert that, when it fires, leaves oncall nothing to correlate.

Suggest (a) tagging the demux promote that occurs with no roundtrip-in-flight shortly after a recent settle so the revert is greppable (e.g. [demux] action=promoted reason=post-settle-late), or at least a counter; and (b) a one-line §8 note that the zombie race is a documented residual with no automated test (pending the ACP cancel signal, FIXME bridge.ts:2836-2840). Note also: any claim that the generation guard catches this is inaccurate — the guard only acts inside the reconciliation read window, not on a promote that lands after reconciliation has already settled (per this paragraph's own "no roundtrip in flight, not a dup").

— claude-opus-4-8 via Claude Code /qreview

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two new design-accuracy items are inline below (Suggestions). The reason for Request changes is not new:

[Critical] The duplicate ## v0.16-alpha known limits section is still present at HEAD. @chiga0 replied "Fixed in v13 (0475658) — the duplicate section has been removed", but commit 04756583a still contains two byte-identical copies of that section (file lines 22–51 and 52–81; the base branch e45d9b4 has exactly one). The +30 lines this PR adds to docs/users/qwen-serve.md are a verbatim re-insertion of the section already present at line 22. Already flagged by @wenshao and still open — re-confirmed here against HEAD (base = 1 occurrence, HEAD = 2), not re-posted inline to avoid a duplicate thread. Fix: delete the second copy.

The two inline items are design-doc accuracy gaps, not merge blockers on their own.

Minor (not filed, to keep the threads clean): §5's "old SDK surfaces the unknown frame as a debug UI event" — the SDK reducer silently no-ops unknown event types (events.ts), it does not project a debug event; and §2.3's demux pseudo-code params.type === 'available_commands_update' / payload.commands should be params.update.sessionUpdate / params.update.availableCommands per the ACP SessionNotification shape ({ sessionId, update: SessionUpdate }).

— claude-opus-4-8 via Claude Code /qreview

- **Seed** from the `createSession` / `loadSession` ACP response when the entry is created (initial model/mode), before any change occurs.
- **Consumers (synchronous field reads):**
- **A5 snapshot (§5):** read all three fields in one synchronous block — the cache's primary purpose.
- **Best-effort demux dedup (§2.1):** drop a `current_model_update` whose `currentModelId` already equals `entry.currentModelId`.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] The §2.1 dedup and §2.2 reconciliation comparisons assume one model-id representation, but the paths they compare use two different forms — so the comparisons are representation-sensitive and will misfire:

  • HTTP setSessionModel / applyModelServiceId publish model_switched with the client-sent value, i.e. the formatted ${id}(${authType}) form (clients pick from the advertised list, built via formatAcpModelIdacpModelUtils.ts).
  • The in-session demux (shipped in feat(daemon): in-session model switch reaches the bus (A1) #4546) publishes currentModelId: parsed.modelId — the bare id (Session.setModel runs it through parseAcpModelOption, stripping (authType)).
  • The reconciliation read getSessionContextStatus returns formatAcpModelId(...)formatted (acpAgent.ts).

So entry.currentModelId holds a bare value after any in-session change and a formatted value after an HTTP change / seed. Result: this best-effort dedup never fires across a bare↔formatted boundary; §2.2 reconciliation compares a formatted read against a possibly-bare entry.currentModelId, sees perpetual divergence, and fires a spurious corrective that re-seeds the formatted form → representation flip-flop on the bus. The v6 changelog's decision to drop authType from the current_model_update payload as "dead data" removes the field needed to normalize.

Define a single canonical representation for entry.currentModelId / the bus / the reconciliation read and normalize at every boundary (e.g. compare on parseAcpBaseModelId, or re-add authType so the bridge can formatAcpModelId consistently), and state it explicitly in §2.1/§2.3.

— claude-opus-4-8 via Claude Code /qreview

- **Mitigation (§2.1 dual-emit):** the first release emits BOTH the legacy generic `session_update{current_mode_update}` AND the promoted `approval_mode_changed`; the IDE companion keeps working on the legacy frame; once its `approval_mode_changed` path ships, the next release drops the dual-emit. A4 (`voterClientId`) and A5 (opt-in frame) ARE additive (no transition needed).
- **Failure events stay bridge-only** (`model_switch_failed`).
- **Concurrent-in-session drift** is bounded by §2.2 post-roundtrip reconciliation.
- **SDK reducer updates** (naming, to avoid the A1/A2 mix-up): A1 introduces **`current_model_update`** → `model.changed`; A2 promotes **`current_mode_update`** → `approval_mode_changed`; A4 adds optional `voterClientId`; A5 seeds side-channel state from `session.snapshot`.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] This SDK-reducer enumeration omits reconciliation_failed. §2.2 introduces it as a new bus event with a consumer contract ("clients MAY surface a warning / trigger their own pull"), but it is not in the SDK's DAEMON_KNOWN_EVENT_TYPE_VALUES allowlist (packages/sdk-typescript/src/daemon/events.ts). asKnownDaemonEvent returns undefined for unlisted types and reduceDaemonSessionEvent no-ops them, so the frame is silently dropped before any SDK consumer can observe it — the self-heal contract is unreachable as specified. (Contrast session_snapshot, which §5/§6 do enumerate for the SDK.)

Add reconciliation_failed here alongside the A1/A2/A4/A5 entries, plus a DaemonReconciliationFailedData validator + allowlist entry + reducer case (mirroring model_switch_failed), and note it in §7 sequencing.

— claude-opus-4-8 via Claude Code /qreview

- **Mitigation (§2.1 dual-emit):** the first release emits BOTH the legacy generic `session_update{current_mode_update}` AND the promoted `approval_mode_changed`; the IDE companion keeps working on the legacy frame; once its `approval_mode_changed` path ships, the next release drops the dual-emit. A4 (`voterClientId`) and A5 (opt-in frame) ARE additive (no transition needed).
- **Failure events stay bridge-only** (`model_switch_failed`).
- **Concurrent-in-session drift** is bounded by §2.2 post-roundtrip reconciliation.
- **SDK reducer updates** (naming, to avoid the A1/A2 mix-up): A1 introduces **`current_model_update`** → `model.changed`; A2 promotes **`current_mode_update`** → `approval_mode_changed`; A4 adds optional `voterClientId`; A5 seeds side-channel state from `session.snapshot`.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] model.changed is a typo — the rest of the document (38 occurrences) and the codebase consistently use model_switched as the bus event name. An implementer reading §6 for SDK reducer naming would use model.changed, which matches nothing in the actual SDK types or bus events.

Suggested change
- **SDK reducer updates** (naming, to avoid the A1/A2 mix-up): A1 introduces **`current_model_update`**`model.changed`; A2 promotes **`current_mode_update`**`approval_mode_changed`; A4 adds optional `voterClientId`; A5 seeds side-channel state from `session.snapshot`.
- **SDK reducer updates** (naming, to avoid the A1/A2 mix-up): A1 introduces **`current_model_update`**`model_switched`; A2 promotes **`current_mode_update`**`approval_mode_changed`; A4 adds optional `voterClientId`; A5 seeds side-channel state from `session.snapshot`.

— qwen3.7-max via Qwen Code /review

>
> Source: cross-client real-time sync audit (2026-05-24) + PR #4484 post-merge review (the **A-series** follow-ups). The bugfix/cleanup follow-ups from the same review ship separately (PR #4510) and are **out of scope here**.

## Changelog

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] The header (line 3) declares "v13 — zombie-gap doc, reconciliation_failed contract, availableCommands spec, §7 atomic-coupling, §8 bounded-call-count" but the Changelog has no ### v13 entry — it starts with v12. The five v13 changes are referenced in body text (lines 155, 197, 212 with (v13) annotations) but never summarized here, breaking the document's own convention where every prior version (v2–v12) has a dedicated Changelog entry.

Add a ### v13 (2026-05-27) — tenth review round (residual gap, error contract, cache spec, atomic coupling, bounded calls) entry above the v12 entry summarizing the five changes.

— qwen3.7-max via Qwen Code /review


**Residual gap — zombie roundtrip (v13).** Reconciliation covers the _first_ settlement (the timeout), but a zombie ACP call that completes **after** reconciliation has already fired `action=converged` is NOT covered: the agent applies the timed-out model late → emits `current_model_update(A)` → demux promotes it (no roundtrip in flight, not a dup) → bus silently reverts to A, contradicting the user's successful switch to B. The long-term fix is an ACP cancel signal (the existing FIXME at `bridge.ts:2836-2840`). Until then this is a **known residual race** under the narrow condition: timeout fires, reconciliation converges (agent hasn't applied yet), user successfully switches to B, THEN the zombie completes. Likelihood is low (requires the agent to take longer than the timeout + reconciliation read + a subsequent successful switch), but it is not zero. Document it here rather than claim reconciliation fully eliminates the timeout race.

### 2.1 Demux contract (two insertion points)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] The zombie roundtrip residual gap is well-documented but leaves zero diagnostic trail when it fires in production. A demux promotion of a zombie current_model_update produces [demux] session=<id> action=promoted — byte-identical to a legitimate in-session model switch. An oncall engineer investigating "model silently reverted" has no way to distinguish zombie from legitimate promotion.

Consider specifying a detection heuristic in the observability format — e.g., add a postReconciliation=<seconds> field to the demux log when a promotion occurs after a reconciliation settled, or stamp reconciliationSettledAt on the entry and compare against promotion time. This makes the residual race operationally visible, not just documented.

— qwen3.7-max via Qwen Code /review

## 9. Resolved decisions (emitter ownership)

| Entry | agent path | through `Session.*`? | session-scoped emitter | workspace publish |
| -------------------------------------------------- | ---------------------------------------------------------------------------- | ----------------------------- | --------------------------------------------------------------------------------- | ------------------------------------------ |

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] §2.3 specifies availableCommands cache update via BridgeClient.sessionUpdate() intercepting available_commands_update, and explicitly warns: "If the implementer misses the hook, A5's snapshot serves stale/undefined commands with no backstop." Yet the "Bridge state cache (§2.3)" test bullet only asserts currentModelId/modelPublishGeneration via publishModelSwitched — the third cache field has zero test coverage.

Add a test scenario: an available_commands_update generic sessionUpdate arriving at BridgeClient.sessionUpdate() updates entry.availableCommands synchronously (assert the cache value before the generic forwarding publish); an A5 session_snapshot emitted immediately after reflects the updated commands.

— qwen3.7-max via Qwen Code /review

- _failure-path trigger:_ a timed-out roundtrip (`model_switch_failed`) still triggers reconciliation; the comparison baseline is `entry.currentModelId` (the pre-roundtrip value, since `model_switch_failed` does NOT update the cache); if the agent actually applied the timed-out model A (read returns A) and `entry.currentModelId` is still the old value B, reconciliation emits corrective `model_switched(A)` via `publishModelSwitched` → bus converges on A.
- _read-error:_ status read fails all retries → emits `reconciliation_failed { sessionId, error, retryCount, trigger }` with correct payload; per-attempt logs emitted (`attempt=1/<max>`, `attempt=2/<max>`); no corrective.
- **Cross-axis non-suppression (§2.1):** an in-flight bridge **model** roundtrip does NOT suppress an in-session `current_mode_update` (it IS promoted), and vice-versa.
- **Bridge state cache (§2.3):** every `model_switched` publish site routes through `publishModelSwitched` which updates `entry.currentModelId` AND bumps `entry.modelPublishGeneration`; assert generation advanced by exactly 1 after each (including the reconciliation corrective). The snapshot/dedup/generation-guard reads see the latest value synchronously; cache seeded on session create.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] §2.1 requires demux observability as "required, not optional": [demux] session=<id> type=<sub> action=promoted|dropped|suppressed|generic reason=<why>. The design warns that the dropped case "especially must be visible so oncall can distinguish 'agent didn't emit' / 'demux dropped' / 'SSE lost'." Yet §8's "Demux/§1.1" bullet tests promotion and suppression outcomes but has zero assertions on [demux] structured log output.

Add a test bullet: assert [demux] structured log lines are emitted for at least promoted, suppressed, and dropped actions, with correct session, type, action, and reason fields.

— qwen3.7-max via Qwen Code /review

@DragonnZhang DragonnZhang left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review summary

Docs-only design PR. The design document (design.md) is thorough — 13 revision rounds with extensive self-correction. The technical design is sound: the bridge-authoritative model, per-type suppress, generation-guarded reconciliation, and the dual-emit transition are all well-specified. Code references were spot-checked against the branch and are accurate (minor line-number drift is expected and acceptable for a living design doc).

One concrete issue found in the user-facing docs change.

Findings

# Severity File Issue
1 Minor docs/users/qwen-serve.md:80 Duplicate paragraph — "For the deeper..." appears at both line 50 and line 80

— qwen-code via Qwen Code /review

Comment thread docs/users/qwen-serve.md Outdated
- ⏸️ **Rate limiting + observability + load test harness** — defers to v0.17 F4 Phase-1 scale instrumentation when 30-50 active sessions becomes a real target.
- ⏸️ **`--max-body-size` CLI flag** — daemon enforces `express.json({ limit: '10mb' })` by default which comfortably covers text-only prompts (model context windows are well under 10 MiB of chars). Tunable via flag in v0.16.x.

For the deeper "what we won't fix in Stage 1" enumeration (single-host session-state mutation model + N-parallel-sessions sharing one ACP child), see [Stage 1 scope boundaries](#stage-1-scope-boundaries--what-we-wont-fix-in-stage-15) below.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate paragraph: this "For the deeper..." sentence already appears at line 50 (pre-existing, before the new section). The new "v0.16-alpha known limits" section re-introduces it verbatim as its trailing paragraph, resulting in two identical paragraphs separated by ~30 lines.

Suggest removing this trailing copy — the pre-existing one at line 50 already provides the forward reference to the Stage 1 scope boundaries section.

— qwen-code via Qwen Code /review

- **Mitigation (§2.1 dual-emit):** the first release emits BOTH the legacy generic `session_update{current_mode_update}` AND the promoted `approval_mode_changed`; the IDE companion keeps working on the legacy frame; once its `approval_mode_changed` path ships, the next release drops the dual-emit. A4 (`voterClientId`) and A5 (opt-in frame) ARE additive (no transition needed).
- **Failure events stay bridge-only** (`model_switch_failed`).
- **Concurrent-in-session drift** is bounded by §2.2 post-roundtrip reconciliation.
- **SDK reducer updates** (naming, to avoid the A1/A2 mix-up): A1 introduces **`current_model_update`** → `model.changed`; A2 promotes **`current_mode_update`** → `approval_mode_changed`; A4 adds optional `voterClientId`; A5 seeds side-channel state from `session.snapshot`.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] model.changed is a typo — the rest of the document (38 occurrences) and the codebase consistently use model_switched as the bus event name. The SDK reducer enumeration should match:

Suggested change
- **SDK reducer updates** (naming, to avoid the A1/A2 mix-up): A1 introduces **`current_model_update`**`model.changed`; A2 promotes **`current_mode_update`**`approval_mode_changed`; A4 adds optional `voterClientId`; A5 seeds side-channel state from `session.snapshot`.
- **SDK reducer updates** (naming, to avoid the A1/A2 mix-up): A1 introduces **`current_model_update`**`model_switched`; A2 promotes **`current_mode_update`**`approval_mode_changed`; A4 adds optional `voterClientId`; A5 seeds side-channel state from `session.snapshot`.

Re-posting from R13 — still present at HEAD (0475658).

— qwen3.7-max via Qwen Code /review


## Changelog

### v12 (2026-05-27) — ninth review round (helper signature + structural guard)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] The header (line 3) declares v13 — zombie-gap doc, reconciliation_failed contract, availableCommands spec, §7 atomic-coupling, §8 bounded-call-count, but the Changelog section's highest entry is ### v12. Add a ### v13 (2026-05-27) block enumerating the five v13 additions so future readers can locate them without cross-referencing the header.

Re-posting from R13 — still present at HEAD (0475658). The reply "the v13 header on line 3 already lists these additions" describes the header, not the Changelog; the Changelog itself has no v13 entry.

— qwen3.7-max via Qwen Code /review

Comment thread docs/users/qwen-serve.md Outdated

For the deeper "what we won't fix in Stage 1" enumeration (single-host session-state mutation model + N-parallel-sessions sharing one ACP child), see [Stage 1 scope boundaries](#stage-1-scope-boundaries--what-we-wont-fix-in-stage-15) below.

## v0.16-alpha known limits

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] Duplicate ## v0.16-alpha known limits section — still present at HEAD (0475658). Verified:

grep -n "^## v0.16-alpha known limits" docs/users/qwen-serve.md
# 22:## v0.16-alpha known limits    ← pre-existing (base branch)
# 52:## v0.16-alpha known limits    ← added by this PR (byte-identical 30-line block)

Lines 22–51 and 52–81 are identical (including the trailing "For the deeper..." paragraph at lines 50 and 80). The earlier reply "Fixed in v13 (0475658) — the duplicate section has been removed" does not match the file at that commit; please remove the second block (lines 52–81) entirely.

— qwen3.7-max via Qwen Code /review

chiga0 added a commit that referenced this pull request Jun 8, 2026
…nts sharing a session (#4613)

* feat(daemon): bridge side-channel state layer — A1 follow-up + A2 + A5 (#4511)

* fix(daemon): address review on side-channel state consistency

- inject session_snapshot up front on fresh SSE connections (not only on resume)
- reconcile only after a roundtrip that landed; guard generation TOCTOU with
  one bounded re-run and log skip/correct/fail transitions
- drop unencodable reconciliation_failed bus event in favor of operator log
  (client path already covered by state_resync_required)
- bridgeClient mode fallback emits previous/persisted; dual-emit session_update
  uses the canonical nested data.update shape
- validate modeId at the setMode boundary; reject unknown modes
- SDK session_snapshot validator type-checks currentModelId/currentApprovalMode
- tests: fresh-connection snapshot + reconciliation drift/match/failure/roundtrip-failure

* fix(daemon): address second-round review on side-channel state layer

- applyModelServiceId: gate reconcile behind a `succeeded` flag so a
  rejected create/attach-time roundtrip can't pair a corrective
  model_switched with the model_switch_failed it just published; mirrors
  setSessionModel / setSessionApprovalMode.
- in-session mode demux: validate currentModeId against the known
  approval-mode enum (lockstep with Session.setMode) before it fans out
  to SSE clients / the SDK reducer.
- in-session mode demux: suppress the legacy session_update dual-emit on
  the exit_plan_mode path via a `legacyFrameSent` flag — sendUpdate
  already published that frame, so dual-emitting delivered it twice. The
  setMode path (no sendUpdate) keeps its dual-emit.
- reconcile: emit a `reason=roundtrip_failed` skip log on all three
  failure paths so the skipped reconcile is greppable.
- SDK: add session_snapshot to RESYNC_PASSTHROUGH_TYPES so a client that
  reconnects past ring eviction recovers currentModelId / approvalMode
  from the full-state frame instead of staying stale until loadSession.
- tests: approvalMode reconcile drift + roundtrip-fail, generation rerun,
  unknown-mode enum drop, dual-emit shape + suppression, setMode
  extNotification + unknown-modeId rejection.

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* test(daemon): assert currentApprovalMode flows into the A5 snapshot

The existing A5 snapshot tests only seed currentModelId, leaving the
publishApprovalModeChanged -> entry.currentApprovalMode -> snapshot
pipeline uncovered at the bridge level. Add a test that promotes an
in-session mode change before subscribing and asserts the snapshot
carries the non-null currentApprovalMode.

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* docs(bridge)+test(cli): clarify mode-update handler comment & cover legacyFrameSent

- bridgeClient.ts: the A2 comment claimed handleInSessionModeUpdate
  "mirrors handleInSessionModelUpdate exactly", but it diverges with enum
  validation and the legacy dual-emit. Reword to state the shared
  suppression pattern plus the two additions.
- Session.test.ts: add coverage for sendCurrentModeUpdateNotification
  asserting the extNotification carries legacyFrameSent: true, so a
  regression dropping it (double legacy frame to the IDE companion) is
  caught.

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(daemon): address PR #4613 round-5 review — cache seeding, peer sync, contract cleanup

- bridge: seed snapshot caches (currentModelId/currentApprovalMode) from
  newSession/loadSession responses so a cold attach reports real state
  instead of null/null, with KNOWN_APPROVAL_MODES enum backstop
- bridge: enum-validate the reconcile approvalMode branch and drop unknown
  modes with a logged reason
- bridge: on a persisted approval-mode change, mirror the new workspace
  default into every peer SessionEntry cache so their GET status /
  session_snapshot stop reporting the pre-change mode
- bridge/bridgeClient: remove try/catch wrappers around EventBus.publish()
  per its documented never-throws contract; drop misleading "bus closed"
  comments
- cli/Session: log dropped advisory extNotifications via debugLogger.debug
  instead of swallowing silently
- bridge.test: add failure-gating coverage for applyModelServiceId — a
  rejected attach-time model apply must not trigger reconcile (no status read)

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(daemon): address PR #4613 round-5 review nits — stale comments and cache doc

- bridge: document that setSessionModel caches the raw model id and
  relies on the immediately-following reconcileAfterRoundtrip to
  correct any raw-vs-canonical drift (the bridge layer lacks access
  to the CLI's formatAcpModelId which requires authType)
- bridge: fix stale reconcile-catch comment that referenced
  state_resync_required (long-lived SSE connections don't reconnect)
- bridgeClient.test: update stale "7-arg constructor" comment to
  reflect the current 8-arg constructor

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(daemon): address PR #4613 round-6 review — bundle cap, test gaps, assertions

- sdk: bump MAX_DAEMON_BROWSER_BUNDLE_BYTES from 100 KiB to 105 KiB to
  accommodate session_snapshot type/validator/reducer additions (+1.2 KiB)
- bridge: remove redundant entry! non-null assertions (already narrowed
  by if-guard at line 2708)
- bridge: document setSessionModel raw-id cache + reconcile correction
- bridge.test: add seedSnapshotCaches cold-attach test (newSession
  response seeds model+mode without intermediate notifications)
- bridge.test: add peer cache sync test (persisted mode change updates
  peer snapshot)
- bridge.test: add unknown-mode-drop test (reconcile drops agent-
  returned modes not in KNOWN_APPROVAL_MODES)

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(daemon): address PR #4613 round-6 follow-up — fix false-positive test, add resync passthrough test

- bridge.test: rewrite unknown-mode-drop test to trigger approvalMode
  reconcile (via setSessionApprovalMode) instead of model reconcile
  (via modelServiceId), which never entered the approvalMode branch
  — the original was a false positive (F8E2h)
- bridge.test: fix misleading params.mode cast in peer-cache-sync test;
  status RPC sends {sessionId} not {mode} — return fixed 'yolo' (F8E2o)
- sdk daemonEvents.test: add session_snapshot passthrough-during-resync
  test (RESYNC_PASSTHROUGH_TYPES membership regression guard) (F8SOq)

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(daemon): address PR #4613 round-6 follow-up — positive reconcile assertion

Add statusReads counter to the unknown-mode-drop test so it positively
asserts that reconcile actually executed (status RPC was called), not
just that no corrective event appeared. Without this, a future refactor
disabling reconcileAfterRoundtrip would make the test pass vacuously.

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(daemon): address PR #4613 round-6 follow-up — fix false-positive test, add resync passthrough test

- bridge.test: restore missing closing braces for extractErrorCode
  describe/it blocks (lost during rebase conflict resolution)
- sdk build.js: bump MAX_DAEMON_BROWSER_BUNDLE_BYTES from 106 to
  107 KiB (actual bundle is 108595 bytes = ~106.1 KiB)

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(daemon): validate agent approval-mode response + typeof guard on model reconcile

- bridge: validate setSessionApprovalMode extMethod response against
  KNOWN_APPROVAL_MODES before publishing/broadcasting; drop with log
  if agent returns unknown mode (closes trust-boundary gap where
  handleInSessionModeUpdate and reconcile had guards but this path
  did not)
- bridge: add typeof === 'string' guard to model reconcile branch
  so a non-string agent response (e.g. number) cannot pollute the
  cache and break downstream session_snapshot validation
- bridge: add writeStderrLine to seedSnapshotCaches drop branches
  for operator observability parity with reconcile's drop logging

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(daemon): fix unknown-mode succeeded flag + restore HAZARD comment

- bridge: leave succeeded=false when agent returns unknown approval
  mode — skips pointless reconcile that would re-drop the same value
- bridge: restore channel-overlap HAZARD comment on closeSession's
  channelInfoForEntry call (lost during reaper code removal)

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(daemon): restore missing delimiters in events.ts (rebase artifact)

Three sites where session_snapshot was inserted immediately after
session_rewound lost the preceding block's closing delimiter during
rebase conflict resolution: type alias (missing >;), asKnownDaemonEvent
case (missing : undefined;), and reducer case (missing };).

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(daemon): remove reaper scope creep + fix events.ts delimiters (rebase artifacts)

- bridge: remove session-reaper code (closeSessionImpl, startSession-
  Reaper, stopSessionReaper, constants) inadvertently included during
  rebase conflict resolution — not part of this PR's scope
- events.ts: restore 2 missing delimiters (isSessionBranchedData
  closing brace, session_rewound type/case closers) lost when
  session_snapshot was inserted adjacent to session_branched blocks

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(daemon): throw on unknown agent approval-mode response instead of silent success

When the agent returns a mode not in KNOWN_APPROVAL_MODES, throw
instead of returning a misleading success response. The previous
behavior sent 200 OK echoing the requested mode while the cache
and SSE bus still showed the old value — a three-way state divergence.

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: 秦奇 <gary.gq@alibaba-inc.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Comment thread docs/users/qwen-serve.md Outdated

For the deeper "what we won't fix in Stage 1" enumeration (single-host session-state mutation model + N-parallel-sessions sharing one ACP child), see [Stage 1 scope boundaries](#stage-1-scope-boundaries--what-we-wont-fix-in-stage-15) below.

## v0.16-alpha known limits

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Critical] This entire ## v0.16-alpha known limits section (lines 52-81, ~30 lines) is a verbatim duplicate of the section that already exists on the base branch at lines 28-51. The heading, all three subsections (Product surface, Deployment surface, Hardening), and the closing "For the deeper..." paragraph are identical. After this diff the document contains the same section twice in a row, which will confuse readers and produces two ## v0.16-alpha known limits headings (breaking in-page anchor links).

Suggested fix: Remove the 30 added lines (the duplicate section). The content already exists on daemon_mode_b_main and does not need to be re-added.

— Qwen Code /review

Comment thread docs/users/qwen-serve.md Outdated

For the deeper "what we won't fix in Stage 1" enumeration (single-host session-state mutation model + N-parallel-sessions sharing one ACP child), see [Stage 1 scope boundaries](#stage-1-scope-boundaries--what-we-wont-fix-in-stage-15) below.

## v0.16-alpha known limits

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Critical] Duplicate section: ## v0.16-alpha known limits already exists at line 22 of this file on the base branch (origin/daemon_mode_b_main).

This PR inserts a verbatim second copy (lines 52–81 in the new file), identical to the one already at lines 22–51. The rendered docs will show the same heading and bullet list twice.

Multiple prior reviewers flagged this (v9–v13 review rounds). The author replied "Fixed in v13 (0475658) — duplicate section removed" but commit 04756583a still contains both copies.

Fix: delete lines 52–81 (the second ## v0.16-alpha known limits block and its content) from this PR.

@doudouOUC doudouOUC changed the base branch from daemon_mode_b_main to main June 11, 2026 16:51
@doudouOUC doudouOUC force-pushed the docs/daemon-sidechannel-coordination-design branch from 0475658 to aae1f53 Compare June 11, 2026 17:18
@github-actions

Copy link
Copy Markdown
Contributor

Code Coverage Summary

Package Lines Statements Functions Branches
CLI 76% 76% 79.91% 79.5%
Core 81.48% 81.48% 83.52% 83.76%
CLI Package - Full Text Report
-------------------|---------|----------|---------|---------|-------------------
File               | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-------------------|---------|----------|---------|---------|-------------------
All files          |      76 |     79.5 |   79.91 |      76 |                   
 src               |   70.11 |    66.99 |   73.33 |   70.11 |                   
  gemini.tsx       |   61.89 |    64.28 |   71.42 |   61.89 | ...1192-1195,1207 
  ...ractiveCli.ts |   69.62 |    63.35 |   66.66 |   69.62 | ...1545-1547,1582 
  ...liCommands.ts |   84.54 |    76.38 |     100 |   84.54 | ...26-335,361,475 
  ...ActiveAuth.ts |     100 |     87.5 |     100 |     100 | 66-80             
 ...cp-integration |   56.11 |    61.57 |   82.65 |   56.11 |                   
  acpAgent.ts      |   55.96 |     61.6 |   82.84 |   55.96 | ...6995,7020-7035 
  authMethods.ts   |      92 |       60 |     100 |      92 | 33-34             
  errorCodes.ts    |       0 |        0 |       0 |       0 | 1-22              
  ...DirContext.ts |     100 |      100 |     100 |     100 |                   
 ...ration/service |   68.65 |    83.33 |   66.66 |   68.65 |                   
  filesystem.ts    |   68.65 |    83.33 |   66.66 |   68.65 | ...32,77-94,97-98 
 ...ration/session |   80.86 |     74.9 |   87.73 |   80.86 |                   
  ...ryReplayer.ts |   67.34 |     75.6 |   81.81 |   67.34 | ...54-269,282-283 
  Session.ts       |   80.67 |    74.22 |   89.04 |   80.67 | ...3790,3816-3820 
  ...entTracker.ts |   90.75 |    84.37 |   88.88 |   90.75 | ...30,194,246-255 
  index.ts         |       0 |        0 |       0 |       0 | 1-40              
  ...ssionUtils.ts |   84.21 |    78.57 |     100 |   84.21 | ...37-153,209-211 
  tasksSnapshot.ts |   94.06 |    86.66 |     100 |   94.06 | 60-66             
  types.ts         |       0 |        0 |       0 |       0 | 1                 
 ...ssion/emitters |   95.37 |    91.94 |   96.55 |   95.37 |                   
  BaseEmitter.ts   |   84.61 |       70 |     100 |   84.61 | 23-24,39-40       
  ...ageEmitter.ts |   94.07 |    91.42 |     100 |   94.07 | 47-54             
  PlanEmitter.ts   |     100 |      100 |     100 |     100 |                   
  ...allEmitter.ts |   98.33 |    93.67 |     100 |   98.33 | 300-301,387,395   
  index.ts         |       0 |        0 |       0 |       0 | 1-10              
 ...ession/rewrite |    91.3 |    88.09 |   94.44 |    91.3 |                   
  LlmRewriter.ts   |      81 |       84 |     100 |      81 | ...,88-89,155-159 
  ...Middleware.ts |   96.74 |    86.84 |     100 |   96.74 | 135,143-145       
  TurnBuffer.ts    |     100 |      100 |     100 |     100 |                   
  config.ts        |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  types.ts         |       0 |        0 |       0 |       0 | 1                 
 src/commands      |   32.76 |    85.71 |   43.47 |   32.76 |                   
  auth.ts          |     100 |    83.33 |     100 |     100 | 11,14             
  channel.ts       |   56.66 |      100 |       0 |   56.66 | 15-19,27-34       
  extensions.tsx   |   96.55 |      100 |      50 |   96.55 | 37                
  hooks.tsx        |   66.66 |      100 |       0 |   66.66 | 20-24             
  mcp.ts           |   94.73 |      100 |      50 |   94.73 | 28                
  review.ts        |   51.85 |      100 |       0 |   51.85 | 24-35,38          
  serve.ts         |    4.42 |      100 |       0 |    4.42 | ...67-235,237-424 
 ...mmands/channel |    39.2 |    79.45 |      50 |    39.2 |                   
  ...l-registry.ts |    8.33 |      100 |       0 |    8.33 | 6-22,25-43        
  config-utils.ts  |      92 |      100 |   66.66 |      92 | 21-26             
  configure.ts     |    14.7 |      100 |       0 |    14.7 | 18-21,23-84       
  pairing.ts       |   26.31 |      100 |       0 |   26.31 | ...30,40-50,52-65 
  pidfile.ts       |   96.34 |    86.95 |     100 |   96.34 | 49,59,91          
  start.ts         |   30.98 |       52 |   69.23 |   30.98 | ...72-475,484-486 
  status.ts        |   17.85 |      100 |       0 |   17.85 | 15-26,32-76       
  stop.ts          |      20 |      100 |       0 |      20 | 14-48             
 ...nds/extensions |    85.1 |    88.77 |   81.81 |    85.1 |                   
  consent.ts       |   72.68 |       90 |   42.85 |   72.68 | ...86-142,157-163 
  disable.ts       |     100 |      100 |     100 |     100 |                   
  enable.ts        |     100 |      100 |     100 |     100 |                   
  install.ts       |    75.6 |    66.66 |   66.66 |    75.6 | ...39-142,145-153 
  link.ts          |     100 |      100 |     100 |     100 |                   
  list.ts          |     100 |      100 |     100 |     100 |                   
  new.ts           |     100 |      100 |     100 |     100 |                   
  settings.ts      |   99.15 |      100 |   83.33 |   99.15 | 151               
  uninstall.ts     |    37.5 |      100 |   33.33 |    37.5 | 23-45,57-64,67-70 
  update.ts        |   96.32 |      100 |     100 |   96.32 | 101-105           
  utils.ts         |   67.77 |    38.88 |     100 |   67.77 | ...,94-98,100-104 
 ...les/mcp-server |       0 |        0 |       0 |       0 |                   
  example.ts       |       0 |        0 |       0 |       0 | 1-60              
 src/commands/mcp  |   92.29 |    86.08 |   88.88 |   92.29 |                   
  add.ts           |     100 |    98.03 |     100 |     100 | 293               
  list.ts          |   91.22 |    80.76 |      80 |   91.22 | ...19-121,146-147 
  reconnect.ts     |   76.72 |    71.42 |   85.71 |   76.72 | 35-48,153-175     
  remove.ts        |     100 |       80 |     100 |     100 | 21-25             
 ...ommands/review |   11.57 |      100 |       0 |   11.57 |                   
  cleanup.ts       |   17.94 |      100 |       0 |   17.94 | ...01-106,108-109 
  deterministic.ts |   13.75 |      100 |       0 |   13.75 | ...22-738,740-741 
  fetch-pr.ts      |   11.36 |      100 |       0 |   11.36 | ...80-201,203-204 
  load-rules.ts    |   11.32 |      100 |       0 |   11.32 | ...41-153,155-156 
  pr-context.ts    |    6.22 |      100 |       0 |    6.22 | ...97-312,314-315 
  presubmit.ts     |    9.35 |      100 |       0 |    9.35 | ...62-287,289-290 
 ...nds/review/lib |      30 |      100 |       0 |      30 |                   
  gh.ts            |   22.58 |      100 |       0 |   22.58 | ...49,53-54,62-69 
  git.ts           |   22.72 |      100 |       0 |   22.72 | 15-18,29-39,43-44 
  paths.ts         |   52.94 |      100 |       0 |   52.94 | ...26,37-38,42-43 
 src/config        |   91.32 |    85.18 |      89 |   91.32 |                   
  auth.ts          |   86.74 |    80.88 |     100 |   86.74 | ...40-241,257-258 
  config.ts        |   87.06 |    84.57 |   82.14 |   87.06 | ...2005,2007-2015 
  keyBindings.ts   |   96.87 |       50 |     100 |   96.87 | 201-204           
  ...ngsAdapter.ts |     100 |    94.11 |     100 |     100 | 64                
  ...idersScope.ts |      92 |       90 |     100 |      92 | 11-12             
  ...abledTools.ts |     100 |      100 |     100 |     100 |                   
  sandboxConfig.ts |   61.64 |    71.87 |   66.66 |   61.64 | ...54-68,73,77-89 
  settings.ts      |   78.84 |    86.29 |    87.5 |   78.84 | ...1519,1534-1537 
  ...ingsSchema.ts |     100 |      100 |     100 |     100 |                   
  ...tedFolders.ts |   96.22 |       94 |     100 |   96.22 | ...95-197,212-213 
 ...nfig/migration |   94.89 |    78.94 |   83.33 |   94.89 |                   
  index.ts         |   94.87 |    88.88 |     100 |   94.87 | 91-92             
  scheduler.ts     |   96.55 |    77.77 |     100 |   96.55 | 19-20             
  types.ts         |       0 |        0 |       0 |       0 | 1                 
 ...ation/versions |   94.74 |       96 |     100 |   94.74 |                   
  ...-v2-shared.ts |     100 |      100 |     100 |     100 |                   
  v1-to-v2.ts      |   81.75 |    90.19 |     100 |   81.75 | ...28-229,231-247 
  v2-to-v3.ts      |     100 |      100 |     100 |     100 |                   
  v3-to-v4.ts      |     100 |      100 |     100 |     100 |                   
 src/core          |     100 |      100 |     100 |     100 |                   
  auth.ts          |     100 |      100 |     100 |     100 |                   
  initializer.ts   |     100 |      100 |     100 |     100 |                   
  theme.ts         |     100 |      100 |     100 |     100 |                   
 src/dualOutput    |   63.09 |    64.51 |   55.55 |   63.09 |                   
  ...tputBridge.ts |   62.94 |    65.51 |   56.25 |   62.94 | ...22-323,331-334 
  ...utContext.tsx |     100 |      100 |     100 |     100 |                   
  index.ts         |       0 |        0 |       0 |       0 | 1-8               
 src/export        |       0 |        0 |       0 |       0 |                   
  index.ts         |       0 |        0 |       0 |       0 | 1-7               
 src/generated     |     100 |      100 |     100 |     100 |                   
  git-commit.ts    |     100 |      100 |     100 |     100 |                   
 src/i18n          |   82.51 |    75.94 |   65.71 |   82.51 |                   
  index.ts         |   63.68 |    69.56 |   53.84 |   63.68 | ...70-271,281-286 
  languages.ts     |   96.92 |    86.66 |     100 |   96.92 | 134-135,167,184   
  ...nslateKeys.ts |     100 |      100 |     100 |     100 |                   
  ...lationDict.ts |   93.33 |    66.66 |     100 |   93.33 | 15                
 src/i18n/locales  |     100 |      100 |     100 |     100 |                   
  ca.js            |     100 |      100 |     100 |     100 |                   
  de.js            |     100 |      100 |     100 |     100 |                   
  en.js            |     100 |      100 |     100 |     100 |                   
  fr.js            |     100 |      100 |     100 |     100 |                   
  ja.js            |     100 |      100 |     100 |     100 |                   
  pt.js            |     100 |      100 |     100 |     100 |                   
  ru.js            |     100 |      100 |     100 |     100 |                   
  zh-TW.js         |     100 |      100 |     100 |     100 |                   
  zh.js            |     100 |      100 |     100 |     100 |                   
 ...nonInteractive |   72.57 |    71.12 |   74.07 |   72.57 |                   
  session.ts       |   76.64 |     69.4 |   85.71 |   76.64 | ...23-824,833-843 
  types.ts         |    42.5 |      100 |   33.33 |    42.5 | ...90-591,594-595 
 ...active/control |   76.29 |    88.23 |      80 |   76.29 |                   
  ...rolContext.ts |    6.89 |        0 |       0 |    6.89 | 50-86             
  ...Dispatcher.ts |   91.66 |    91.83 |   88.88 |   91.66 | ...49-367,383,386 
  ...rolService.ts |     7.4 |        0 |       0 |     7.4 | 46-185            
 ...ol/controllers |    25.4 |    35.71 |   35.48 |    25.4 |                   
  ...Controller.ts |   36.97 |       80 |      80 |   36.97 | ...15-117,127-210 
  ...Controller.ts |       0 |        0 |       0 |       0 | 1-56              
  ...Controller.ts |   28.33 |    34.48 |      40 |   28.33 | ...64-573,588-593 
  ...Controller.ts |   14.06 |      100 |       0 |   14.06 | ...82-117,130-133 
  ...Controller.ts |   21.97 |    28.57 |   27.27 |   21.97 | ...39-451,460-489 
 .../control/types |       0 |        0 |       0 |       0 |                   
  serviceAPIs.ts   |       0 |        0 |       0 |       0 | 1                 
 ...Interactive/io |   98.01 |    93.77 |   95.23 |   98.01 |                   
  ...putAdapter.ts |   97.89 |    92.82 |   98.07 |   97.89 | ...1303,1398-1399 
  ...putAdapter.ts |      96 |     90.9 |   85.71 |      96 | 51-52             
  ...nputReader.ts |     100 |    94.73 |     100 |     100 | 67                
  ...putAdapter.ts |   98.38 |      100 |   90.47 |   98.38 | 83-84,124-125     
  index.ts         |     100 |      100 |     100 |     100 |                   
 src/patches       |       0 |        0 |       0 |       0 |                   
  is-in-ci.ts      |       0 |        0 |       0 |       0 | 1-17              
 src/remoteInput   |   86.98 |       75 |   85.71 |   86.98 |                   
  ...utContext.tsx |     100 |      100 |     100 |     100 |                   
  ...putWatcher.ts |   88.12 |    76.08 |   91.66 |   88.12 | ...21-222,233-236 
  index.ts         |       0 |        0 |       0 |       0 | 1-8               
 src/serve         |   77.53 |    81.81 |   78.66 |   77.53 |                   
  ...sionBridge.ts |     100 |      100 |     100 |     100 |                   
  auth.ts          |   93.26 |    92.64 |     100 |   93.26 | ...07-308,311-313 
  ...temAdapter.ts |     100 |      100 |     100 |     100 |                   
  capabilities.ts  |     100 |    95.23 |     100 |     100 | 331               
  daemonLogger.ts  |   98.63 |    90.32 |   95.83 |   98.63 | 161,165           
  ...usProvider.ts |   67.01 |    51.42 |     100 |   67.01 | ...40-245,278-286 
  debugMode.ts     |     100 |      100 |     100 |     100 |                   
  demo.ts          |     100 |      100 |     100 |     100 |                   
  envSnapshot.ts   |   92.75 |       84 |     100 |   92.75 | 110-113,179-186   
  eventBus.ts      |     100 |      100 |     100 |     100 |                   
  ...oryChannel.ts |       0 |        0 |       0 |       0 | 1-14              
  index.ts         |       0 |        0 |       0 |       0 | 1-141             
  loopbackBinds.ts |     100 |      100 |     100 |     100 |                   
  ...ssionAudit.ts |     100 |      100 |   93.33 |     100 |                   
  rateLimit.ts     |   90.37 |    87.77 |   93.75 |   90.37 | ...95-297,348-352 
  runQwenServe.ts  |   67.48 |    83.22 |   30.61 |   67.48 | ...1346,1349-1356 
  server.ts        |   78.68 |    83.12 |    85.5 |   78.68 | ...4487,4553-4562 
  status.ts        |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
  ...paceAgents.ts |   62.47 |    70.34 |   90.47 |   62.47 | ...1346,1356-1366 
  ...paceMemory.ts |   87.13 |    78.46 |     100 |   87.13 | ...54-361,421-428 
 src/serve/acpHttp |   64.72 |    66.43 |    93.1 |   64.72 |                   
  ...onRegistry.ts |    86.3 |    75.43 |   92.59 |    86.3 | ...34-338,409-423 
  dispatch.ts      |   54.31 |    57.04 |     100 |   54.31 | ...2223,2285-2300 
  index.ts         |   75.59 |    67.71 |    90.9 |   75.59 | ...28,731,757-759 
  jsonRpc.ts       |     100 |    96.96 |     100 |     100 | 92                
  sseStream.ts     |   93.85 |    87.87 |   84.61 |   93.85 | ...48-150,152-154 
  ...portStream.ts |       0 |        0 |       0 |       0 | 1                 
  wsStream.ts      |   91.76 |       80 |     100 |   91.76 | 43,48,91,95-98    
 src/serve/auth    |   86.86 |    79.18 |   93.87 |   86.86 |                   
  deviceFlow.ts    |   96.35 |       80 |   97.61 |   96.35 | ...1358,1453,1519 
  ...owProvider.ts |   44.24 |    74.07 |   71.42 |   44.24 | ...23-284,297,301 
 src/serve/fs      |   85.12 |    81.01 |     100 |   85.12 |                   
  audit.ts         |     100 |    96.15 |     100 |     100 | 201               
  errors.ts        |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  paths.ts         |   77.82 |    77.08 |     100 |   77.82 | ...64,493-497,510 
  policy.ts        |   90.32 |    89.18 |     100 |   90.32 | 142-150           
  ...FileSystem.ts |   84.03 |    78.55 |     100 |   84.03 | ...2031,2058-2059 
 src/serve/routes  |   71.32 |    70.65 |    93.1 |   71.32 |                   
  ...ceFileRead.ts |   94.41 |    76.92 |     100 |   94.41 | ...28-329,390-392 
  ...eFileWrite.ts |    82.1 |    60.52 |     100 |    82.1 | ...42-244,247-249 
  ...ceSettings.ts |   21.98 |      100 |      50 |   21.98 | ...04-217,224-321 
 ...kspace-service |   81.05 |     82.4 |   86.66 |   81.05 |                   
  index.ts         |   81.23 |    83.17 |   92.85 |   81.23 | ...92-497,557-622 
  types.ts         |       0 |        0 |       0 |       0 | 1                 
 src/services      |   91.86 |     90.8 |   97.56 |   91.86 |                   
  ...mandLoader.ts |     100 |    93.75 |     100 |     100 | 93                
  ...killLoader.ts |     100 |     93.1 |     100 |     100 | 48,67             
  ...andService.ts |    98.7 |      100 |     100 |    98.7 | 107               
  ...mandLoader.ts |   86.83 |    83.87 |     100 |   86.83 | ...30-335,340-345 
  ...omptLoader.ts |   75.84 |    80.64 |   83.33 |   75.84 | ...10-211,277-278 
  ...mandLoader.ts |     100 |    96.96 |     100 |     100 | 66                
  ...nd-factory.ts |   91.42 |    91.66 |     100 |   91.42 | 128,137-144       
  ...ation-tool.ts |     100 |    95.45 |     100 |     100 | 125               
  ...ndMetadata.ts |   98.21 |    96.66 |     100 |   98.21 | 83,87             
  commandUtils.ts  |      96 |     90.9 |     100 |      96 | 48                
  ...and-parser.ts |   90.69 |    85.71 |     100 |   90.69 | 63-66             
  ...ionService.ts |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...ght/generators |    88.3 |    85.49 |   92.59 |    88.3 |                   
  DataProcessor.ts |   88.22 |    85.48 |      95 |   88.22 | ...1341,1345-1352 
  ...tGenerator.ts |   98.21 |    85.71 |     100 |   98.21 | 46                
  ...teRenderer.ts |   45.45 |      100 |       0 |   45.45 | 13-51             
 .../insight/types |       0 |       50 |      50 |       0 |                   
  ...sightTypes.ts |       0 |        0 |       0 |       0 |                   
  ...sightTypes.ts |       0 |        0 |       0 |       0 | 1                 
 ...mpt-processors |   97.27 |    94.04 |     100 |   97.27 |                   
  ...tProcessor.ts |     100 |      100 |     100 |     100 |                   
  ...eProcessor.ts |   94.52 |    84.21 |     100 |   94.52 | 46-47,93-94       
  ...tionParser.ts |     100 |      100 |     100 |     100 |                   
  ...lProcessor.ts |   97.41 |    95.65 |     100 |   97.41 | 95-98             
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/services/tips |   97.35 |    84.84 |     100 |   97.35 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  tipHistory.ts    |   92.59 |       70 |     100 |   92.59 | ...24,146,153,162 
  tipRegistry.ts   |     100 |      100 |     100 |     100 |                   
  tipScheduler.ts  |     100 |    91.66 |     100 |     100 | 55                
 src/startup       |   66.82 |    78.94 |   66.66 |   66.82 |                   
  ...reeStartup.ts |   66.82 |    78.94 |   66.66 |   66.82 | ...08-312,363-426 
 src/test-utils    |   93.71 |    83.33 |      80 |   93.71 |                   
  ...omMatchers.ts |   69.69 |       50 |      50 |   69.69 | 32-35,37-39,45-47 
  ...andContext.ts |     100 |      100 |     100 |     100 |                   
  render.tsx       |     100 |      100 |     100 |     100 |                   
 src/ui            |   65.14 |       73 |   59.67 |   65.14 |                   
  App.tsx          |   33.33 |       75 |   33.33 |   33.33 | 32-86             
  AppContainer.tsx |   64.12 |    64.81 |      50 |   64.12 | ...3238,3242-3246 
  ...tionNudge.tsx |    9.58 |      100 |       0 |    9.58 | 24-94             
  ...ackDialog.tsx |   29.23 |      100 |       0 |   29.23 | 25-75             
  ...tionNudge.tsx |    7.69 |      100 |       0 |    7.69 | 25-103            
  colors.ts        |      60 |      100 |   35.29 |      60 | ...52,54-55,60-61 
  constants.ts     |     100 |      100 |     100 |     100 |                   
  keyMatchers.ts   |   95.91 |    97.14 |     100 |   95.91 | 25-26             
  ...tic-colors.ts |     100 |      100 |     100 |     100 |                   
  ...inePresets.ts |   98.28 |    89.87 |     100 |   98.28 | ...34,261,420-422 
  textConstants.ts |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/ui/auth       |   59.16 |    65.94 |   51.11 |   59.16 |                   
  AuthDialog.tsx   |   62.87 |     42.1 |   18.18 |   62.87 | ...03,310-332,336 
  ...nProgress.tsx |       0 |        0 |       0 |       0 | 1-64              
  ...etupSteps.tsx |   60.03 |    70.37 |      56 |   60.03 | ...87,791,800,803 
  useAuth.ts       |   94.55 |    73.52 |     100 |   94.55 | ...19-220,239-245 
  ...rSetupFlow.ts |   43.52 |    33.33 |      50 |   43.52 | ...72-393,410-453 
 src/ui/commands   |   76.76 |    81.89 |   85.86 |   76.76 |                   
  aboutCommand.ts  |     100 |      100 |     100 |     100 |                   
  agentsCommand.ts |   83.78 |      100 |      60 |   83.78 | 30-32,42-44       
  ...odeCommand.ts |   89.47 |    81.25 |     100 |   89.47 | 92-93,95-100      
  arenaCommand.ts  |   62.81 |    58.73 |   65.21 |   62.81 | ...90-595,680-688 
  authCommand.ts   |     100 |      100 |     100 |     100 |                   
  branchCommand.ts |     100 |      100 |     100 |     100 |                   
  btwCommand.ts    |   94.32 |    77.41 |     100 |   94.32 | 35-36,114-119     
  bugCommand.ts    |   81.13 |    71.42 |     100 |   81.13 | 60-69             
  clearCommand.ts  |   79.64 |       68 |     100 |   79.64 | ...24-125,133-142 
  ...essCommand.ts |   67.95 |    55.88 |      75 |   67.95 | ...86-187,201-204 
  ...extCommand.ts |   65.35 |     66.1 |   84.61 |   65.35 | ...42-575,586-587 
  copyCommand.ts   |   98.48 |    95.78 |     100 |   98.48 | ...80,280,321,327 
  deleteCommand.ts |     100 |      100 |     100 |     100 |                   
  diffCommand.ts   |     100 |     87.5 |     100 |     100 | ...61,224-225,238 
  ...ryCommand.tsx |   80.52 |    84.28 |      90 |   80.52 | ...53-258,305-312 
  docsCommand.ts   |     100 |    88.88 |     100 |     100 | 25                
  doctorCommand.ts |   61.27 |    87.06 |    87.5 |   61.27 | ...71-372,445-665 
  dreamCommand.ts  |   85.45 |    88.88 |     100 |   85.45 | 58-65             
  editorCommand.ts |     100 |      100 |     100 |     100 |                   
  exportCommand.ts |   98.25 |    91.02 |     100 |   98.25 | ...81,198-199,364 
  ...onsCommand.ts |   40.43 |    61.11 |   61.53 |   40.43 | ...96,250-302,363 
  forgetCommand.ts |     100 |       90 |     100 |     100 | 59                
  forkCommand.ts   |     100 |    94.44 |     100 |     100 | 92,151            
  goalCommand.ts   |   91.41 |    84.44 |      90 |   91.41 | ...87-190,202-205 
  helpCommand.ts   |     100 |      100 |     100 |     100 |                   
  hooksCommand.ts  |   81.13 |    65.71 |   85.71 |   81.13 | ...,86-93,131-132 
  ideCommand.ts    |   60.75 |    64.28 |   41.17 |   60.75 | ...05-306,310-324 
  initCommand.ts   |   84.33 |    72.72 |     100 |   84.33 | 68,82-87,89-94    
  ...ghtCommand.ts |   73.27 |    68.42 |     100 |   73.27 | ...37-251,256-278 
  ...ageCommand.ts |   92.17 |    82.69 |     100 |   92.17 | ...39,159,168-178 
  lspCommand.ts    |     100 |    86.95 |     100 |     100 | 31,101-102        
  mcpCommand.ts    |     100 |      100 |     100 |     100 |                   
  memoryCommand.ts |     100 |      100 |     100 |     100 |                   
  modelCommand.ts  |   75.09 |    78.18 |      75 |   75.09 | ...20-225,262-267 
  ...onsCommand.ts |     100 |      100 |     100 |     100 |                   
  planCommand.ts   |   78.82 |    76.92 |     100 |   78.82 | 30-35,51-56,68-73 
  quitCommand.ts   |     100 |      100 |     100 |     100 |                   
  recapCommand.ts  |   21.81 |      100 |      50 |   21.81 | 24-73             
  ...berCommand.ts |      96 |       70 |     100 |      96 | 57,62             
  renameCommand.ts |   85.71 |    86.04 |     100 |   85.71 | ...02-209,216-221 
  ...oreCommand.ts |   90.47 |    84.61 |     100 |   90.47 | ...32-137,167-168 
  resumeCommand.ts |     100 |      100 |     100 |     100 |                   
  rewindCommand.ts |   81.25 |      100 |      50 |   81.25 | 20-22             
  ...ngsCommand.ts |     100 |      100 |     100 |     100 |                   
  ...hubCommand.ts |   81.43 |    65.21 |      80 |   81.43 | ...70-173,176-179 
  skillsCommand.ts |   84.12 |    76.92 |     100 |   84.12 | 36-44,68          
  statsCommand.ts  |   91.48 |    89.47 |     100 |   91.48 | 40-43,134-141     
  ...ineCommand.ts |     100 |      100 |     100 |     100 |                   
  ...aryCommand.ts |    6.46 |      100 |      50 |    6.46 | 31-329            
  tasksCommand.ts  |   77.22 |    72.13 |     100 |   77.22 | ...46-150,172-177 
  ...tupCommand.ts |     100 |      100 |     100 |     100 |                   
  themeCommand.ts  |     100 |      100 |     100 |     100 |                   
  toolsCommand.ts  |     100 |      100 |     100 |     100 |                   
  trustCommand.ts  |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
  vimCommand.ts    |   54.54 |      100 |      50 |   54.54 | 19-29             
 src/ui/components |   62.14 |    77.13 |   60.34 |   62.14 |                   
  AboutBox.tsx     |     100 |      100 |     100 |     100 |                   
  AnsiOutput.tsx   |   65.57 |      100 |      50 |   65.57 | 69-90             
  ApiKeyInput.tsx  |       0 |        0 |       0 |       0 | 1-97              
  AppHeader.tsx    |    88.7 |       75 |     100 |    88.7 | 36,38-43,45       
  ...odeDialog.tsx |   87.24 |    72.22 |   33.33 |   87.24 | ...85,233-238,245 
  AsciiArt.ts      |     100 |      100 |     100 |     100 |                   
  ...Indicator.tsx |   16.27 |      100 |       0 |   16.27 | 19-58             
  ...TextInput.tsx |   77.01 |       76 |     100 |   77.01 | ...20,234-236,263 
  Composer.tsx     |    81.6 |     64.7 |     100 |    81.6 | ...90,108,160,173 
  ...entPrompt.tsx |     100 |      100 |     100 |     100 |                   
  ...ryDisplay.tsx |   75.89 |    62.06 |     100 |   75.89 | ...,88,93-108,113 
  ...geDisplay.tsx |   68.42 |    57.14 |     100 |   68.42 | 16-17,31-32,42-50 
  ...ification.tsx |   28.57 |      100 |       0 |   28.57 | 16-36             
  ...gProfiler.tsx |       0 |        0 |       0 |       0 | 1-36              
  ...ogManager.tsx |   11.95 |      100 |       0 |   11.95 | 68-537            
  DiffDialog.tsx   |    2.47 |      100 |       0 |    2.47 | 68-732            
  ...ngsDialog.tsx |    8.44 |      100 |       0 |    8.44 | 37-195            
  ExitWarning.tsx  |     100 |      100 |     100 |     100 |                   
  ...hProgress.tsx |    87.8 |    33.33 |     100 |    87.8 | 28-31,56          
  ...ustDialog.tsx |     100 |      100 |     100 |     100 |                   
  Footer.tsx       |   77.12 |    52.27 |     100 |   77.12 | ...43,167,188-193 
  ...ngSpinner.tsx |   68.42 |       80 |      50 |   68.42 | 35-52,73,80-81    
  GoalPill.tsx     |   76.19 |    81.81 |     100 |   76.19 | 24-30,46-50       
  Header.tsx       |   98.62 |    94.28 |     100 |   98.62 | 162,164           
  Help.tsx         |   98.32 |       90 |     100 |   98.32 | ...24,381,447-448 
  ...emDisplay.tsx |   61.41 |    34.61 |     100 |   61.41 | ...51,354,357-363 
  ...ngeDialog.tsx |     100 |      100 |     100 |     100 |                   
  InputPrompt.tsx  |    81.2 |    79.38 |   83.33 |    81.2 | ...1627,1642,1692 
  ...Shortcuts.tsx |   20.87 |      100 |       0 |   20.87 | ...6,49-51,67-125 
  ...Indicator.tsx |     100 |    91.42 |     100 |     100 | 65,74             
  ...firmation.tsx |   91.42 |      100 |      50 |   91.42 | 26-31             
  MainContent.tsx  |   86.93 |    87.17 |   66.66 |   86.93 | ...26,284,343-347 
  MemoryDialog.tsx |   61.87 |    76.05 |    62.5 |   61.87 | ...72,391,428-430 
  ...geDisplay.tsx |       0 |        0 |       0 |       0 | 1-41              
  ModelDialog.tsx  |   85.19 |    69.17 |     100 |   85.19 | ...80-596,653-657 
  ...tsDisplay.tsx |     100 |    97.22 |     100 |     100 | 270               
  ...fications.tsx |   18.18 |      100 |       0 |   18.18 | 15-58             
  ...onsDialog.tsx |    2.13 |      100 |       0 |    2.13 | 62-133,148-1004   
  ...ryDisplay.tsx |     100 |      100 |     100 |     100 |                   
  ...icePrompt.tsx |   92.64 |    85.71 |     100 |   92.64 | 102-106,134-139   
  PrepareLabel.tsx |   91.66 |    77.27 |     100 |   91.66 | 73-75,77-79,110   
  ...atePrompt.tsx |    8.57 |      100 |       0 |    8.57 | 24-55,58-134      
  ...geDisplay.tsx |     100 |      100 |     100 |     100 |                   
  ...ngDisplay.tsx |   21.42 |      100 |       0 |   21.42 | 13-39             
  ...hProgress.tsx |   85.25 |    88.46 |     100 |   85.25 | 121-147           
  ...dSelector.tsx |   87.11 |     73.8 |     100 |   87.11 | ...48,354-370,406 
  ...ionPicker.tsx |   83.66 |    72.13 |     100 |   83.66 | ...96,402,444-466 
  ...onPreview.tsx |   92.42 |    84.37 |     100 |   92.42 | ...,70-71,143-145 
  ...ryDisplay.tsx |     100 |      100 |     100 |     100 |                   
  ...putPrompt.tsx |   72.56 |       80 |      40 |   72.56 | ...06-109,114-117 
  ...tedDialog.tsx |     100 |      100 |     100 |     100 |                   
  ...ngsDialog.tsx |   66.31 |    71.16 |      75 |   66.31 | ...16-824,830-831 
  ...ionDialog.tsx |    92.3 |    96.15 |   33.33 |    92.3 | 60-63,68-75,164   
  ...putPrompt.tsx |    15.9 |      100 |       0 |    15.9 | 20-63             
  ...Indicator.tsx |   57.14 |      100 |       0 |   57.14 | 12-15             
  ...MoreLines.tsx |      28 |      100 |       0 |      28 | 18-40             
  ...ionPicker.tsx |   17.59 |      100 |       0 |   17.59 | 55-172            
  ...tivityTab.tsx |    3.94 |      100 |       0 |    3.94 | 27-275            
  StatsDialog.tsx  |    8.85 |      100 |       0 |    8.85 | ...5,49-84,92-238 
  StatsDisplay.tsx |     100 |      100 |     100 |     100 |                   
  ...ciencyTab.tsx |    3.28 |      100 |       0 |    3.28 | 25-258            
  ...atmapView.tsx |    8.98 |      100 |       0 |    8.98 | 20-107            
  ...essionTab.tsx |    5.46 |      100 |       0 |    5.46 | 24-215            
  ...ineDialog.tsx |    93.5 |    85.18 |     100 |    93.5 | ...05,267,287-289 
  ...yTodoList.tsx |   96.33 |    88.23 |     100 |   96.33 | 137-140           
  ...nsDisplay.tsx |   87.25 |       64 |     100 |   87.25 | ...57-159,166-168 
  ThemeDialog.tsx  |   89.95 |    46.15 |      75 |   89.95 | ...71-173,243-245 
  Tips.tsx         |   93.54 |       75 |     100 |   93.54 | 39-40             
  TodoDisplay.tsx  |     100 |      100 |     100 |     100 |                   
  ...tsDisplay.tsx |     100 |     87.5 |     100 |     100 | 31-32             
  TrustDialog.tsx  |     100 |    81.81 |     100 |     100 | 71-86             
  ...ification.tsx |   36.36 |      100 |       0 |   36.36 | 15-22             
  ...ackDialog.tsx |    7.84 |      100 |       0 |    7.84 | 24-134            
  ...xitDialog.tsx |   80.36 |    43.47 |      60 |   80.36 | ...24-238,248-251 
  ...odeVisuals.ts |   91.42 |    64.28 |     100 |   91.42 | 15,21,24          
  ...s-helpers.tsx |      25 |      100 |       0 |      25 | ...3,86-89,94-102 
 ...nts/agent-view |   38.55 |    70.83 |   36.36 |   38.55 |                   
  ...atContent.tsx |    8.79 |      100 |       0 |    8.79 | 53-265,271-273    
  ...tChatView.tsx |   21.05 |      100 |       0 |   21.05 | 21-39             
  ...tComposer.tsx |   10.84 |      100 |       0 |   10.84 | 59-308            
  AgentFooter.tsx  |   17.07 |      100 |       0 |   17.07 | 28-66             
  AgentHeader.tsx  |   15.38 |      100 |       0 |   15.38 | 27-64             
  AgentTabBar.tsx  |    87.8 |    27.27 |     100 |    87.8 | ...,85,95-103,121 
  ...oryAdapter.ts |     100 |    91.83 |     100 |     100 | 103,109-110,138   
  index.ts         |       0 |        0 |       0 |       0 | 1-12              
 ...mponents/arena |   45.59 |    70.53 |   60.86 |   45.59 |                   
  ArenaCards.tsx   |   73.06 |    71.79 |   85.71 |   73.06 | ...83-185,321-326 
  ...ectDialog.tsx |   83.48 |    69.86 |   88.88 |   83.48 | ...88-392,409-410 
  ...artDialog.tsx |    9.92 |      100 |       0 |    9.92 | 27-164            
  ...tusDialog.tsx |    5.63 |      100 |       0 |    5.63 | 33-75,80-288      
  ...topDialog.tsx |    6.17 |      100 |       0 |    6.17 | 33-213            
 ...ackground-view |   79.76 |    82.24 |   91.17 |   79.76 |                   
  ...sksDialog.tsx |   76.68 |       80 |   85.71 |   76.68 | ...1139,1215-1217 
  ...TasksPill.tsx |   63.75 |    86.95 |     100 |   63.75 | 44,86-106,114-122 
  ...gentPanel.tsx |    97.4 |    86.31 |     100 |    97.4 | 123,434-438       
 ...nts/extensions |   45.28 |    33.33 |      60 |   45.28 |                   
  ...gerDialog.tsx |   44.31 |    34.14 |      75 |   44.31 | ...71-480,483-488 
  index.ts         |       0 |        0 |       0 |       0 | 1-9               
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...tensions/steps |   54.88 |    94.23 |   66.66 |   54.88 |                   
  ...ctionStep.tsx |   95.12 |    92.85 |   85.71 |   95.12 | 84-86,89          
  ...etailStep.tsx |    6.18 |      100 |       0 |    6.18 | 20-131            
  ...nListStep.tsx |   88.43 |    94.73 |      80 |   88.43 | 52-53,59-72,106   
  ...electStep.tsx |   13.46 |      100 |       0 |   13.46 | 20-70             
  ...nfirmStep.tsx |   19.56 |      100 |       0 |   19.56 | 23-65             
  index.ts         |     100 |      100 |     100 |     100 |                   
 ...mponents/hooks |   86.85 |     81.3 |   91.89 |   86.85 |                   
  ...rListBody.tsx |   95.29 |    85.18 |     100 |   95.29 | 95-98             
  ...etailStep.tsx |   75.32 |    71.42 |      60 |   75.32 | ...56-169,173-186 
  ...etailStep.tsx |     100 |      100 |     100 |     100 |                   
  ...rListStep.tsx |     100 |      100 |     100 |     100 |                   
  ...entHeader.tsx |     100 |    85.71 |     100 |     100 | 47                
  ...rListStep.tsx |     100 |      100 |     100 |     100 |                   
  ...etailStep.tsx |     100 |      100 |     100 |     100 |                   
  ...abledStep.tsx |     100 |      100 |     100 |     100 |                   
  ...sListStep.tsx |     100 |      100 |     100 |     100 |                   
  ...entDialog.tsx |   72.29 |    70.24 |     100 |   72.29 | ...51,563-568,572 
  constants.ts     |     100 |      100 |     100 |     100 |                   
  index.ts         |       0 |        0 |       0 |       0 | 1-13              
  ...erGrouping.ts |     100 |      100 |     100 |     100 |                   
  sourceLabels.ts  |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...components/mcp |   20.98 |    86.36 |   83.33 |   20.98 |                   
  ...ealthPill.tsx |   68.42 |    85.71 |     100 |   68.42 | 40-46             
  ...entDialog.tsx |    3.64 |      100 |       0 |    3.64 | 41-717            
  constants.ts     |     100 |      100 |     100 |     100 |                   
  index.ts         |       0 |        0 |       0 |       0 | 1-30              
  types.ts         |     100 |      100 |     100 |     100 |                   
  utils.ts         |   95.83 |    88.88 |     100 |   95.83 | 16,20,109-110     
 ...ents/mcp/steps |   26.74 |    54.54 |   42.85 |   26.74 |                   
  ...icateStep.tsx |    5.88 |      100 |       0 |    5.88 | 40-55,58-296      
  ...electStep.tsx |   10.95 |      100 |       0 |   10.95 | 16-88             
  ...etailStep.tsx |    5.26 |      100 |       0 |    5.26 | 31-247            
  ...rListStep.tsx |   75.18 |    59.37 |     100 |   75.18 | ...53-158,169-173 
  ...etailStep.tsx |   10.41 |      100 |       0 |   10.41 | ...1,67-79,82-139 
  ToolListStep.tsx |   69.02 |       50 |     100 |   69.02 | ...22,125,134-143 
 ...nents/messages |   83.06 |    80.02 |    75.6 |   83.06 |                   
  ...ionDialog.tsx |   80.84 |     77.6 |    62.5 |   80.84 | ...98,516,534-536 
  BtwMessage.tsx   |     100 |      100 |     100 |     100 |                   
  ...upDisplay.tsx |   97.67 |    83.72 |     100 |   97.67 | 119,142,150       
  ...onMessage.tsx |   91.93 |    82.35 |     100 |   91.93 | 57-59,61,63       
  ...nMessages.tsx |   79.06 |      100 |      70 |   79.06 | ...51-264,268-280 
  DiffRenderer.tsx |   93.19 |    86.17 |     100 |   93.19 | ...09,237-238,304 
  ...tsDisplay.tsx |   97.82 |    77.27 |     100 |   97.82 | 87,89             
  ...usMessage.tsx |   76.31 |     42.1 |   66.66 |   76.31 | ...99,101,124,155 
  ...tsDisplay.tsx |    95.1 |    88.05 |     100 |    95.1 | ...29,131,164-169 
  ...ssMessage.tsx |    12.5 |      100 |       0 |    12.5 | 18-59             
  ...edMessage.tsx |   16.66 |      100 |       0 |   16.66 | 22-38             
  ...sMessages.tsx |   55.67 |       40 |   28.57 |   55.67 | ...20-125,133-145 
  ...ryMessage.tsx |   14.28 |      100 |       0 |   14.28 | 23-62             
  ...onMessage.tsx |   81.98 |     72.6 |   33.33 |   81.98 | ...65-467,474-476 
  ...upMessage.tsx |   82.63 |    92.85 |     100 |   82.63 | ...85-412,434-449 
  ToolMessage.tsx  |    87.8 |    73.28 |    92.3 |    87.8 | ...59-764,791-793 
 ...ponents/shared |   84.34 |    80.42 |    95.5 |   84.34 |                   
  ...ctionList.tsx |   99.14 |       96 |     100 |   99.14 | 99                
  ...tonSelect.tsx |     100 |      100 |     100 |     100 |                   
  EnumSelector.tsx |     100 |    96.42 |     100 |     100 | 58                
  MaxSizedBox.tsx  |   83.01 |    86.25 |   88.88 |   83.01 | ...12-513,618-619 
  MultiSelect.tsx  |   93.58 |       75 |     100 |   93.58 | ...43,199-201,211 
  ...tonSelect.tsx |     100 |      100 |     100 |     100 |                   
  ...eSelector.tsx |     100 |       60 |     100 |     100 | 40-45             
  ...lableList.tsx |   76.25 |    81.81 |     100 |   76.25 | 44-58,65-68       
  StaticRender.tsx |   72.72 |      100 |     100 |   72.72 | 31-33             
  TextInput.tsx    |    80.8 |    66.07 |      80 |    80.8 | ...36-240,252-258 
  ...apsedTime.tsx |     100 |      100 |     100 |     100 |                   
  ...Indicator.tsx |     100 |      100 |     100 |     100 |                   
  ...lizedList.tsx |   84.26 |    80.88 |      90 |   84.26 | ...68-696,743-765 
  text-buffer.ts   |   85.75 |     80.9 |   97.91 |   85.75 | ...2636,2734-2735 
  ...er-actions.ts |   73.93 |    67.22 |     100 |   73.93 | ...32-733,934-936 
 ...ponents/skills |    3.64 |      100 |       0 |    3.64 |                   
  ...gerDialog.tsx |    3.64 |      100 |       0 |    3.64 | ...90-148,151-691 
 ...ents/subagents |   30.87 |        0 |       0 |   30.87 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  index.ts         |       0 |        0 |       0 |       0 | 1-11              
  reducers.tsx     |    12.1 |      100 |       0 |    12.1 | 33-190            
  types.ts         |     100 |      100 |     100 |     100 |                   
  utils.ts         |   10.95 |      100 |       0 |   10.95 | ...1,56-57,60-102 
 ...bagents/create |    9.13 |      100 |       0 |    9.13 |                   
  ...ionWizard.tsx |    7.28 |      100 |       0 |    7.28 | 34-299            
  ...rSelector.tsx |   14.75 |      100 |       0 |   14.75 | 26-85             
  ...onSummary.tsx |    4.26 |      100 |       0 |    4.26 | 27-331            
  ...tionInput.tsx |    8.63 |      100 |       0 |    8.63 | 23-177            
  ...dSelector.tsx |   33.33 |      100 |       0 |   33.33 | 20-21,26-27,36-63 
  ...nSelector.tsx |    37.5 |      100 |       0 |    37.5 | 20-21,26-27,36-58 
  ...EntryStep.tsx |   12.76 |      100 |       0 |   12.76 | 34-78             
  ToolSelector.tsx |    4.16 |      100 |       0 |    4.16 | 31-253            
 ...bagents/manage |   21.51 |    59.52 |   27.27 |   21.51 |                   
  ...ctionStep.tsx |   10.25 |      100 |       0 |   10.25 | 21-103            
  ...eleteStep.tsx |   20.93 |      100 |       0 |   20.93 | 23-62             
  ...tEditStep.tsx |   25.53 |      100 |       0 |   25.53 | ...2,37-38,51-124 
  ...ctionStep.tsx |   35.42 |    59.52 |     100 |   35.42 | ...20-432,437-439 
  ...iewerStep.tsx |   13.72 |      100 |       0 |   13.72 | 18-73             
  ...gerDialog.tsx |    6.74 |      100 |       0 |    6.74 | 35-341            
 ...mponents/views |   70.21 |    67.32 |    64.7 |   70.21 |                   
  ContextUsage.tsx |   70.88 |    63.88 |      80 |   70.88 | ...20-426,463-557 
  DoctorReport.tsx |     9.8 |      100 |       0 |     9.8 | 25-54,57-131      
  ...sionsList.tsx |   87.69 |    73.68 |     100 |   87.69 | 65-72             
  McpStatus.tsx    |   89.53 |    60.52 |     100 |   89.53 | ...72,175-177,262 
  SkillsList.tsx   |   27.27 |      100 |       0 |   27.27 | 18-35             
  ToolsList.tsx    |     100 |      100 |     100 |     100 |                   
 src/ui/contexts   |   77.81 |    78.32 |   81.03 |   77.81 |                   
  ...ewContext.tsx |   65.07 |    88.88 |      50 |   65.07 | ...23-226,232-242 
  AppContext.tsx   |      80 |       50 |     100 |      80 | 19-20             
  ...ewContext.tsx |    93.3 |    64.28 |      50 |    93.3 | ...35-236,263-267 
  ...deContext.tsx |     100 |      100 |     100 |     100 |                   
  ...igContext.tsx |   81.81 |       50 |     100 |   81.81 | 15-16             
  ...ssContext.tsx |   82.35 |    82.84 |     100 |   82.35 | ...1159,1165-1167 
  ...owContext.tsx |   91.07 |    81.81 |     100 |   91.07 | 47-48,60-62       
  ...deContext.tsx |     100 |      100 |      50 |     100 |                   
  ...onContext.tsx |   43.28 |     62.5 |    62.5 |   43.28 | ...56-259,263-266 
  ...gsContext.tsx |   83.33 |       50 |     100 |   83.33 | 17-18             
  ...usContext.tsx |     100 |      100 |     100 |     100 |                   
  ...ngContext.tsx |   71.42 |       50 |     100 |   71.42 | 17-20             
  ...utContext.tsx |   85.71 |      100 |   66.66 |   85.71 | 13-14             
  ...nsContext.tsx |   88.23 |       50 |     100 |   88.23 | 132-133           
  ...teContext.tsx |   86.66 |       50 |     100 |   86.66 | 198-199           
  ...deContext.tsx |      80 |     87.5 |      75 |      80 | ...11-112,118-120 
 src/ui/daemon     |   90.76 |    73.73 |   95.45 |   90.76 |                   
  ...TuiAdapter.ts |   90.76 |    73.73 |   95.45 |   90.76 | ...53,771-772,858 
 src/ui/editors    |   93.33 |    85.71 |   66.66 |   93.33 |                   
  ...ngsManager.ts |   93.33 |    85.71 |   66.66 |   93.33 | 49,63-64          
 src/ui/hooks      |   81.58 |    80.83 |   86.17 |   81.58 |                   
  ...dProcessor.ts |   83.12 |    82.56 |     100 |   83.12 | ...88-389,408-435 
  keyToAnsi.ts     |    3.92 |      100 |       0 |    3.92 | 19-77             
  ...dProcessor.ts |    94.8 |    70.58 |     100 |    94.8 | ...76-277,282-283 
  ...dProcessor.ts |   83.94 |    62.56 |      80 |   83.94 | ...1010,1031-1035 
  ...amingState.ts |   12.22 |      100 |       0 |   12.22 | 54-157            
  ...agerDialog.ts |   88.23 |      100 |     100 |   88.23 | 20,24             
  ...dScrollbar.ts |     100 |      100 |     100 |     100 |                   
  ...ationFrame.ts |      32 |       60 |     100 |      32 | 42-44,51-90       
  ...odeCommand.ts |   58.82 |      100 |     100 |   58.82 | 28,33-48          
  ...enaCommand.ts |      85 |      100 |     100 |      85 | 23-24,29          
  ...aInProcess.ts |   27.92 |       80 |      25 |   27.92 | ...69-170,173-175 
  ...Completion.ts |   92.81 |    89.09 |     100 |   92.81 | ...86-187,224-227 
  ...ifications.ts |   86.91 |    96.29 |     100 |   86.91 | 116-130           
  ...tIndicator.ts |   83.49 |    70.96 |     100 |   83.49 | ...60,168,170-178 
  ...waySummary.ts |   96.22 |    69.69 |     100 |   96.22 | 125-127,169       
  ...ndTaskView.ts |   94.21 |    76.08 |     100 |   94.21 | 122-126,213,219   
  ...chedScroll.ts |     100 |      100 |     100 |     100 |                   
  ...ketedPaste.ts |    23.8 |      100 |       0 |    23.8 | 19-37             
  ...nchCommand.ts |   95.12 |    71.87 |     100 |   95.12 | ...21,129-130,170 
  ...ompletion.tsx |   96.01 |    83.87 |     100 |   96.01 | ...22-223,225-226 
  ...dMigration.ts |   90.62 |       75 |     100 |   90.62 | 38-40             
  useCompletion.ts |    92.4 |     87.5 |     100 |    92.4 | 68-69,93-94,98-99 
  ...nitMessage.ts |     100 |      100 |     100 |     100 |                   
  ...extualTips.ts |   77.27 |       50 |     100 |   77.27 | ...2,75-79,93-101 
  ...eteCommand.ts |   78.53 |    88.57 |     100 |   78.53 | ...96-104,112-113 
  ...ialogClose.ts |    12.5 |      100 |     100 |    12.5 | 85-181            
  useDiffData.ts   |   11.62 |      100 |       0 |   11.62 | 44-87             
  ...oublePress.ts |   53.12 |       75 |     100 |   53.12 | 33-35,41-54       
  ...orSettings.ts |     100 |      100 |     100 |     100 |                   
  ...Completion.ts |   99.12 |    97.67 |     100 |   99.12 | 182-183           
  ...ionUpdates.ts |   93.45 |     92.3 |     100 |   93.45 | ...83-287,300-306 
  ...agerDialog.ts |   88.88 |      100 |     100 |   88.88 | 21,25             
  ...backDialog.ts |    63.9 |    76.47 |   66.66 |    63.9 | ...66-168,190-191 
  useFocus.ts      |     100 |      100 |     100 |     100 |                   
  ...olderTrust.ts |     100 |      100 |     100 |     100 |                   
  ...ggestions.tsx |   89.15 |     62.5 |      50 |   89.15 | ...22-124,149-150 
  ...miniStream.ts |   79.08 |    76.28 |    92.3 |   79.08 | ...2608,2659-2667 
  ...BranchName.ts |    90.9 |     92.3 |     100 |    90.9 | 19-20,55-58       
  ...oryManager.ts |   96.92 |    96.29 |     100 |   96.92 | 52,139-142,218    
  ...ooksDialog.ts |    87.5 |      100 |     100 |    87.5 | 19,23             
  ...stListener.ts |     100 |      100 |     100 |     100 |                   
  ...nAuthError.ts |   76.19 |       50 |     100 |   76.19 | 39-40,43-45       
  ...putHistory.ts |   92.59 |    85.71 |     100 |   92.59 | 63-64,72,94-96    
  ...storyStore.ts |     100 |    94.11 |     100 |     100 | 69                
  useKeypress.ts   |     100 |      100 |     100 |     100 |                   
  ...rdProtocol.ts |   36.36 |      100 |       0 |   36.36 | 24-31             
  ...unchEditor.ts |    9.67 |      100 |       0 |    9.67 | 11-32,39-90       
  ...gIndicator.ts |     100 |      100 |     100 |     100 |                   
  useLogger.ts     |   21.05 |      100 |       0 |   21.05 | 15-37             
  useMCPHealth.ts  |   63.15 |       75 |      50 |   63.15 | 42-52,64-67       
  useMcpDialog.ts  |    87.5 |      100 |     100 |    87.5 | 19,23             
  ...moryDialog.ts |    87.5 |      100 |     100 |    87.5 | 19,23             
  ...oryMonitor.ts |   83.14 |    78.57 |     100 |   83.14 | 54-63,74-79       
  ...ssageQueue.ts |     100 |      100 |     100 |     100 |                   
  ...delCommand.ts |     100 |       75 |     100 |     100 | 22                
  ...ouseEvents.ts |   87.17 |    88.88 |   66.66 |   87.17 | 81-82,86-88       
  ...raseCycler.ts |   84.74 |    76.47 |     100 |   84.74 | ...49,52-53,69-71 
  ...rredEditor.ts |   58.33 |    22.22 |     100 |   58.33 | 23-27,29-33       
  ...derUpdates.ts |   86.49 |    77.96 |    90.9 |   86.49 | ...26,288-300,348 
  useQwenAuth.ts   |     100 |      100 |     100 |     100 |                   
  ...lScheduler.ts |    84.7 |    93.33 |     100 |    84.7 | ...71-276,372-382 
  ...oryCommand.ts |       0 |        0 |       0 |       0 | 1-7               
  ...umeCommand.ts |   96.96 |    83.33 |     100 |   96.96 | 101-102,131       
  ...ompletion.tsx |   90.59 |    83.33 |     100 |   90.59 | ...01,104,137-140 
  ...ectionList.ts |   97.05 |    96.11 |     100 |   97.05 | ...90-191,245-248 
  ...sionPicker.ts |   92.87 |    90.35 |     100 |   92.87 | ...99-501,503-505 
  ...earchInput.ts |     100 |      100 |     100 |     100 |                   
  ...ngsCommand.ts |   18.75 |      100 |       0 |   18.75 | 10-25             
  ...ellHistory.ts |   91.74 |    79.41 |     100 |   91.74 | ...74,122-123,133 
  ...oryCommand.ts |       0 |        0 |       0 |       0 | 1-73              
  ...agerDialog.ts |   88.23 |      100 |     100 |   88.23 | 20,24             
  ...Completion.ts |   82.73 |    85.41 |   94.73 |   82.73 | ...70-672,680-716 
  ...tateAndRef.ts |     100 |      100 |     100 |     100 |                   
  ...tatsDialog.ts |     100 |      100 |     100 |     100 |                   
  useStatusLine.ts |    96.3 |    92.19 |     100 |    96.3 | ...77-380,466-473 
  ...eateDialog.ts |   88.23 |      100 |     100 |   88.23 | 14,18             
  ...mInProcess.ts |   27.35 |       80 |      25 |   27.35 | ...82-183,186-188 
  ...tification.ts |     100 |     87.5 |     100 |     100 | 50                
  ...alProgress.ts |   53.06 |       50 |   66.66 |   53.06 | ...53,61-68,79-85 
  ...rminalSize.ts |   76.19 |      100 |      50 |   76.19 | 21-25             
  ...emeCommand.ts |   67.01 |    29.41 |     100 |   67.01 | ...10-111,115-116 
  useTimer.ts      |   88.09 |    85.71 |     100 |   88.09 | 44-45,51-53       
  ...lMigration.ts |       0 |        0 |       0 |       0 |                   
  ...rustModify.ts |     100 |      100 |     100 |     100 |                   
  useTurnDiffs.ts  |   95.12 |    78.57 |     100 |   95.12 | 133-134,156-157   
  ...elcomeBack.ts |   87.36 |     90.9 |     100 |   87.36 | ...,94-96,114-115 
  ...reeSession.ts |   93.75 |       70 |     100 |   93.75 | 44-45,87          
  vim.ts           |   74.37 |    67.77 |   69.23 |   74.37 | ...1842-1849,1857 
 src/ui/layouts    |    90.9 |    90.62 |     100 |    90.9 |                   
  ...AppLayout.tsx |   90.72 |       90 |     100 |   90.72 | 57-59,101-106     
  ...AppLayout.tsx |   91.17 |    91.66 |     100 |   91.17 | 70-75             
 src/ui/models     |   80.24 |    79.16 |   71.42 |   80.24 |                   
  ...ableModels.ts |   80.24 |    79.16 |   71.42 |   80.24 | ...,61-71,123-125 
 ...noninteractive |     100 |      100 |   14.28 |     100 |                   
  ...eractiveUi.ts |     100 |      100 |   14.28 |     100 |                   
 src/ui/state      |   94.91 |    81.81 |     100 |   94.91 |                   
  extensions.ts    |   94.91 |    81.81 |     100 |   94.91 | 68-69,88          
 src/ui/themes     |   98.53 |    70.58 |     100 |   98.53 |                   
  ansi-light.ts    |     100 |      100 |     100 |     100 |                   
  ansi.ts          |     100 |      100 |     100 |     100 |                   
  atom-one-dark.ts |     100 |      100 |     100 |     100 |                   
  ayu-light.ts     |     100 |      100 |     100 |     100 |                   
  ayu.ts           |     100 |      100 |     100 |     100 |                   
  color-utils.ts   |     100 |      100 |     100 |     100 |                   
  default-light.ts |     100 |      100 |     100 |     100 |                   
  default.ts       |     100 |      100 |     100 |     100 |                   
  ...inal-theme.ts |   88.59 |    85.96 |     100 |   88.59 | ...57-261,266-270 
  dracula.ts       |     100 |      100 |     100 |     100 |                   
  github-dark.ts   |     100 |      100 |     100 |     100 |                   
  github-light.ts  |     100 |      100 |     100 |     100 |                   
  googlecode.ts    |     100 |      100 |     100 |     100 |                   
  no-color.ts      |     100 |      100 |     100 |     100 |                   
  qwen-dark.ts     |     100 |      100 |     100 |     100 |                   
  qwen-light.ts    |     100 |      100 |     100 |     100 |                   
  ...tic-tokens.ts |     100 |      100 |     100 |     100 |                   
  ...-of-purple.ts |     100 |      100 |     100 |     100 |                   
  theme-manager.ts |   87.98 |    82.89 |     100 |   87.98 | ...48-357,362-363 
  theme.ts         |     100 |    38.02 |     100 |     100 | ...34-449,457-461 
  xcode.ts         |     100 |      100 |     100 |     100 |                   
 src/ui/utils      |   83.32 |    82.82 |   92.77 |   83.32 |                   
  ...Colorizer.tsx |   79.53 |    83.78 |     100 |   79.53 | ...51-152,249-275 
  ...nRenderer.tsx |   68.83 |    70.14 |      50 |   68.83 | ...52-254,274-293 
  ...wnDisplay.tsx |   86.01 |    87.66 |     100 |   86.01 | ...87,704,729-754 
  ...idDiagram.tsx |   87.79 |    95.34 |     100 |   87.79 | 156-179           
  ...eRenderer.tsx |   92.08 |    80.45 |      95 |   92.08 | ...76-679,723-728 
  ...odeDisplay.ts |   96.55 |     90.9 |     100 |   96.55 | 34                
  asciiCharts.ts   |   96.77 |    87.62 |     100 |   96.77 | 173-180,281       
  ...dWorkUtils.ts |     100 |      100 |     100 |     100 |                   
  ...boardUtils.ts |   49.89 |    71.79 |    90.9 |   49.89 | ...79,582-591,594 
  commandUtils.ts  |    95.9 |    88.42 |     100 |    95.9 | ...66,168-169,293 
  computeStats.ts  |     100 |      100 |     100 |     100 |                   
  customBanner.ts  |   90.68 |    91.22 |     100 |   90.68 | ...13,324-327,334 
  displayUtils.ts  |   88.37 |    72.22 |     100 |   88.37 | 23,25,29,31,33    
  formatters.ts    |   95.23 |     98.3 |     100 |   95.23 | 117-120           
  gradientUtils.ts |     100 |      100 |     100 |     100 |                   
  highlight.ts     |     100 |      100 |     100 |     100 |                   
  ...oryMapping.ts |     100 |    96.55 |     100 |     100 | 43                
  historyUtils.ts  |   94.11 |       94 |     100 |   94.11 | 94-97             
  isNarrowWidth.ts |     100 |      100 |     100 |     100 |                   
  ...olDetector.ts |    8.23 |      100 |       0 |    8.23 | ...31-132,135-136 
  latexRenderer.ts |   94.95 |     73.8 |     100 |   94.95 | ...76-178,184-187 
  layoutUtils.ts   |     100 |      100 |     100 |     100 |                   
  ...ightLoader.ts |     100 |    89.47 |     100 |     100 | 81,110            
  ...nUtilities.ts |   69.84 |    85.71 |     100 |   69.84 | 75-91,100-101     
  ...ToolGroups.ts |   98.66 |    96.77 |     100 |   98.66 | 48-49             
  ...geRenderer.ts |   86.23 |    69.06 |   95.12 |   86.23 | ...1284,1324-1330 
  ...alRenderer.ts |   86.69 |     71.9 |     100 |   86.69 | ...1476,1513-1519 
  ...lsBySource.ts |     100 |    95.23 |     100 |     100 | 84                
  mouse.ts         |   90.71 |    73.33 |   88.88 |   90.71 | ...40-143,200-201 
  osc8.ts          |   94.73 |    87.75 |     100 |   94.73 | ...49,434,438-439 
  ...mConstants.ts |     100 |      100 |     100 |     100 |                   
  restoreGoal.ts   |   98.98 |    97.05 |     100 |   98.98 | 98                
  ...storyUtils.ts |   62.74 |    71.26 |      90 |   62.74 | ...84,432,437-459 
  ...ickerUtils.ts |     100 |      100 |     100 |     100 |                   
  ...ataService.ts |   93.17 |     80.3 |     100 |   93.17 | ...14,227,254-256 
  ...izedOutput.ts |   94.94 |      100 |   88.88 |   94.94 | 112-117           
  ...wOptimizer.ts |     100 |    96.77 |     100 |     100 | 69                
  terminalSetup.ts |    4.37 |      100 |       0 |    4.37 | 44-393            
  textUtils.ts     |   97.61 |    94.84 |   92.85 |   97.61 | ...50-251,386-387 
  todoSnapshot.ts  |   89.33 |    93.47 |     100 |   89.33 | ...,66-78,180-181 
  updateCheck.ts   |     100 |    80.95 |     100 |     100 | 30-42             
 ...i/utils/export |   56.77 |     40.8 |   79.41 |   56.77 |                   
  collect.ts       |   55.92 |    50.58 |   86.36 |   55.92 | ...25-640,642-647 
  index.ts         |     100 |      100 |     100 |     100 |                   
  normalize.ts     |   57.47 |    20.51 |      80 |   57.47 | ...09-310,324-359 
  types.ts         |       0 |        0 |       0 |       0 | 1                 
  utils.ts         |      40 |      100 |       0 |      40 | 11-13             
 ...ort/formatters |    3.38 |      100 |       0 |    3.38 |                   
  html.ts          |    9.61 |      100 |       0 |    9.61 | ...28,34-76,82-84 
  json.ts          |      50 |      100 |       0 |      50 | 14-15             
  jsonl.ts         |     3.5 |      100 |       0 |     3.5 | 14-76             
  markdown.ts      |    0.94 |      100 |       0 |    0.94 | 13-295            
 src/utils         |   72.33 |    89.17 |   90.46 |   72.33 |                   
  acpModelUtils.ts |     100 |      100 |     100 |     100 |                   
  apiPreconnect.ts |   96.72 |    97.14 |     100 |   96.72 | 165-168           
  checks.ts        |   33.33 |      100 |       0 |   33.33 | 23-28             
  cleanup.ts       |   84.12 |    93.33 |      80 |   84.12 | 75,106-115        
  commands.ts      |     100 |      100 |     100 |     100 |                   
  commentJson.ts   |   90.51 |    91.89 |     100 |   90.51 | 67-76,116         
  ...Calculator.ts |     100 |      100 |     100 |     100 |                   
  cpuProfiler.ts   |   70.38 |    71.83 |   88.88 |   70.38 | ...27,430-431,438 
  deepMerge.ts     |     100 |       90 |     100 |     100 | 41-43,49          
  ...ScopeUtils.ts |   97.56 |    88.88 |     100 |   97.56 | 67                
  doctorChecks.ts  |   70.31 |    74.57 |     100 |   70.31 | ...95-301,325-341 
  ...putCapture.ts |   90.65 |    86.17 |     100 |   90.65 | ...72,370,372-373 
  ...arResolver.ts |   97.14 |    96.42 |     100 |   97.14 | 125-126           
  errors.ts        |   90.85 |    96.36 |    92.3 |   90.85 | 69-70,298-310     
  events.ts        |     100 |      100 |     100 |     100 |                   
  gitUtils.ts      |   91.91 |    84.61 |     100 |   91.91 | 78-81,124-127     
  ...AutoUpdate.ts |    92.2 |    95.23 |   88.88 |    92.2 | 130-141           
  ...tyWarnings.ts |     100 |      100 |     100 |     100 |                   
  ...lationInfo.ts |   89.17 |    92.77 |     100 |   89.17 | ...55,272-273,318 
  languageUtils.ts |   98.19 |    97.14 |     100 |   98.19 | 132-133           
  math.ts          |       0 |        0 |       0 |       0 | 1-15              
  ...iagnostics.ts |   94.57 |    83.01 |   88.88 |   94.57 | ...05,311,315-317 
  ...onfigUtils.ts |     100 |      100 |     100 |     100 |                   
  ...iveHelpers.ts |   96.79 |    93.28 |     100 |   96.79 | ...76-477,575,588 
  osc.ts           |    97.5 |      100 |   88.88 |    97.5 | 195-196           
  package.ts       |   88.88 |       80 |     100 |   88.88 | 33-34             
  processUtils.ts  |     100 |      100 |     100 |     100 |                   
  readStdin.ts     |   79.62 |       90 |      80 |   79.62 | 33-40,52-54       
  relaunch.ts      |   93.22 |    81.25 |     100 |   93.22 | 65-67,80          
  resolvePath.ts   |   66.66 |       25 |     100 |   66.66 | 12-13,16,18-19    
  runBudget.ts     |   99.35 |    96.77 |     100 |   99.35 | 119               
  sandbox.ts       |       0 |        0 |       0 |       0 | 1-1038            
  sessionPaths.ts  |   90.84 |    90.56 |     100 |   90.84 | ...81-182,185-186 
  settingsUtils.ts |   82.51 |    91.72 |   89.74 |   82.51 | ...76-694,701-709 
  spawnWrapper.ts  |     100 |      100 |     100 |     100 |                   
  ...ate-verify.ts |     100 |      100 |     100 |     100 |                   
  ...one-update.ts |   26.82 |    73.77 |   43.47 |   26.82 | ...36-837,840-859 
  ...upProfiler.ts |   98.46 |    94.52 |     100 |   98.46 | 130-131,305       
  ...upWarnings.ts |     100 |      100 |     100 |     100 |                   
  stdioHelpers.ts  |     100 |       60 |     100 |     100 | 23,32             
  systemInfo.ts    |   95.12 |    89.06 |     100 |   95.12 | ...43-244,249-253 
  ...InfoFields.ts |    87.5 |       65 |     100 |    87.5 | ...24-125,146-147 
  ...alSequence.ts |     100 |    95.23 |     100 |     100 | 60,90             
  ...iffPreview.ts |   94.11 |    83.33 |     100 |   94.11 | 13                
  ...entEmitter.ts |     100 |      100 |     100 |     100 |                   
  ...ansionHook.ts |     100 |      100 |     100 |     100 |                   
  ...upWarnings.ts |   91.17 |    82.35 |     100 |   91.17 | 67-68,73-74,77-78 
  version.ts       |     100 |       50 |     100 |     100 | 11                
  ...ingHandler.ts |     100 |      100 |     100 |     100 |                   
  windowTitle.ts   |     100 |      100 |     100 |     100 |                   
  ...WithBackup.ts |    62.1 |       75 |     100 |    62.1 | 93,107,118-157    
 ...s/housekeeping |   90.15 |     89.7 |   94.11 |   90.15 |                   
  cleanup.ts       |   94.33 |       95 |     100 |   94.33 | 60-62             
  ...eractionAt.ts |     100 |      100 |     100 |     100 |                   
  scheduler.ts     |   89.71 |    88.23 |   85.71 |   89.71 | 51-55,66,116-120  
  throttledOnce.ts |   86.66 |    85.18 |     100 |   86.66 | ...99,105,137-138 
-------------------|---------|----------|---------|---------|-------------------
Core Package - Full Text Report
-------------------|---------|----------|---------|---------|-------------------
File               | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-------------------|---------|----------|---------|---------|-------------------
All files          |   81.48 |    83.76 |   83.52 |   81.48 |                   
 src               |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
 src/__mocks__/fs  |       0 |        0 |       0 |       0 |                   
  promises.ts      |       0 |        0 |       0 |       0 | 1-48              
 src/agents        |   88.08 |    79.77 |   92.13 |   88.08 |                   
  ...transcript.ts |   92.25 |    85.71 |     100 |   92.25 | ...87,306-307,438 
  ...ent-resume.ts |   82.84 |    71.63 |   77.41 |   82.84 | ...1061-1065,1068 
  ...ound-tasks.ts |   95.76 |    87.57 |     100 |   95.76 | ...26-827,898-899 
  index.ts         |     100 |      100 |     100 |     100 |                   
 src/agents/arena  |   76.54 |    66.87 |   78.72 |   76.54 |                   
  ...gentClient.ts |   79.47 |    88.88 |   81.81 |   79.47 | ...68-183,189-204 
  ArenaManager.ts  |   75.37 |    63.37 |   78.26 |   75.37 | ...1860,1866-1867 
  arena-events.ts  |   64.44 |      100 |      50 |   64.44 | ...71-175,178-183 
  diff-summary.ts  |    87.5 |    72.34 |     100 |    87.5 | ...32-133,137-138 
  index.ts         |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...gents/backends |   76.43 |    86.23 |   73.04 |   76.43 |                   
  ITermBackend.ts  |   97.97 |    93.93 |     100 |   97.97 | ...78-180,255,307 
  ...essBackend.ts |   91.98 |     90.9 |   86.66 |   91.98 | ...95,250-270,329 
  TmuxBackend.ts   |    90.7 |    76.55 |   97.36 |    90.7 | ...87,697,743-747 
  detect.ts        |   31.25 |      100 |       0 |   31.25 | 34-88             
  index.ts         |     100 |      100 |     100 |     100 |                   
  iterm-it2.ts     |     100 |     92.1 |     100 |     100 | 37-38,106         
  tmux-commands.ts |    6.64 |      100 |    3.03 |    6.64 | ...93-363,386-503 
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...agents/runtime |   84.24 |    82.14 |   77.03 |   84.24 |                   
  agent-context.ts |     100 |      100 |     100 |     100 |                   
  agent-core.ts    |   77.18 |    72.93 |   64.44 |   77.18 | ...1683,1710-1757 
  agent-events.ts  |     100 |      100 |     100 |     100 |                   
  ...t-headless.ts |   84.48 |    78.04 |   63.63 |   84.48 | ...00-401,404-405 
  ...nteractive.ts |   80.55 |    81.35 |   74.07 |   80.55 | ...79,481,483,486 
  ...statistics.ts |   98.19 |    82.35 |     100 |   98.19 | 127,151,192,225   
  agent-types.ts   |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...chestrator.ts |    95.6 |    85.71 |     100 |    95.6 | 211-214           
  ...ow-prompts.ts |     100 |      100 |     100 |     100 |                   
  ...ow-sandbox.ts |     100 |    98.09 |     100 |     100 | 117,270           
 src/agents/tasks  |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/agents/team   |   78.43 |    80.47 |   85.59 |   78.43 |                   
  TeamManager.ts   |   66.33 |    75.64 |    73.8 |   66.33 | ...1418,1441-1442 
  identity.ts      |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...sionBridge.ts |     100 |      100 |     100 |     100 |                   
  mailbox.ts       |   91.87 |    78.94 |    92.3 |   91.87 | ...99-200,321-327 
  ...ptAddendum.ts |     100 |      100 |     100 |     100 |                   
  tasks.ts         |   85.81 |     78.3 |   95.45 |   85.81 | ...86-889,933-934 
  team-events.ts   |   60.52 |      100 |      50 |   60.52 | ...37-141,148-152 
  teamHelpers.ts   |   91.66 |    94.33 |      95 |   91.66 | ...07-308,344-354 
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...eam/test-utils |   94.39 |    93.38 |   98.21 |   94.39 |                   
  ...on-harness.ts |   96.49 |    77.77 |     100 |   96.49 | 128-129,141-142   
  fake-agent.ts    |   98.49 |    95.08 |     100 |   98.49 | 201-203           
  fake-backend.ts  |   86.46 |    97.61 |   95.83 |   86.46 | 124-146           
 src/config        |   77.62 |    83.26 |   63.63 |   77.62 |                   
  config.ts        |   75.67 |    82.32 |   59.37 |   75.67 | ...4492,4497-4498 
  constants.ts     |     100 |      100 |     100 |     100 |                   
  models.ts        |     100 |      100 |     100 |     100 |                   
  storage.ts       |    94.9 |     90.9 |   90.24 |    94.9 | ...64-365,368-369 
 ...nfirmation-bus |   98.29 |    97.14 |     100 |   98.29 |                   
  message-bus.ts   |   98.14 |    97.05 |     100 |   98.14 | 42-43             
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/core          |   88.54 |     83.9 |   91.21 |   88.54 |                   
  baseLlmClient.ts |   81.25 |    76.47 |   77.77 |   81.25 | ...13,515-525,534 
  client.ts        |   85.96 |    80.41 |   86.27 |   85.96 | ...2310,2316-2328 
  ...tGenerator.ts |   84.86 |    69.23 |     100 |   84.86 | ...84,386,393-396 
  ...lScheduler.ts |   88.41 |    82.02 |   95.52 |   88.41 | ...3732,3760-3771 
  geminiChat.ts    |    91.1 |    87.92 |    96.1 |    91.1 | ...2950,3017-3018 
  geminiRequest.ts |     100 |      100 |     100 |     100 |                   
  ...MediaLimit.ts |     100 |    95.83 |     100 |     100 | 96                
  ...htProtocol.ts |    9.09 |      100 |       0 |    9.09 | ...9,62-66,69-110 
  logger.ts        |   87.41 |    87.02 |     100 |   87.41 | ...64-568,614-628 
  ...tyDefaults.ts |     100 |      100 |     100 |     100 |                   
  ...olExecutor.ts |   92.59 |       75 |      50 |   92.59 | 41-42             
  ...on-helpers.ts |   86.48 |    72.22 |     100 |   86.48 | ...97-198,212-221 
  ...issionFlow.ts |   98.75 |    95.83 |     100 |   98.75 | 93                
  prompts.ts       |   88.88 |    87.87 |   72.72 |   88.88 | ...-907,1110-1111 
  tokenLimits.ts   |     100 |    89.47 |     100 |     100 | 51-52             
  ...okTriggers.ts |   99.43 |    91.34 |     100 |   99.43 | 172,183           
  turn.ts          |   96.46 |    88.88 |     100 |   96.46 | ...32,445-446,494 
 ...ntentGenerator |   94.88 |    82.07 |      94 |   94.88 |                   
  ...tGenerator.ts |   96.29 |    83.18 |   92.85 |   96.29 | ...1,971,999-1001 
  converter.ts     |   94.51 |    80.72 |     100 |   94.51 | ...06-607,617,823 
  index.ts         |       0 |        0 |       0 |       0 | 1-21              
  usage.ts         |     100 |      100 |     100 |     100 |                   
 ...ntentGenerator |   91.53 |    71.64 |   93.33 |   91.53 |                   
  ...tGenerator.ts |      90 |    70.96 |   92.85 |      90 | ...80-286,304-305 
  index.ts         |     100 |       80 |     100 |     100 | 50                
 ...ntentGenerator |   94.22 |    83.96 |   91.17 |   94.22 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...tGenerator.ts |   94.09 |     82.5 |   90.62 |   94.09 | ...1025-1026,1054 
  ...tDetection.ts |     100 |      100 |     100 |     100 |                   
 ...ntentGenerator |   86.35 |     84.4 |   93.67 |   86.35 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  converter.ts     |   84.89 |    82.17 |   96.15 |   84.89 | ...1395,1611-1626 
  errorHandler.ts  |     100 |      100 |     100 |     100 |                   
  index.ts         |   54.54 |    68.75 |      50 |   54.54 | ...79,87-91,95-99 
  ...tGenerator.ts |    66.4 |    70.58 |   88.88 |    66.4 | ...51-157,168-169 
  pipeline.ts      |   94.38 |     86.5 |     100 |   94.38 | ...38-539,547,615 
  ...ureContext.ts |     100 |      100 |     100 |     100 |                   
  ...ingOptions.ts |       0 |        0 |       0 |       0 | 1                 
  ...CallParser.ts |   90.66 |    88.57 |     100 |   90.66 | ...15-319,349-350 
  ...kingParser.ts |     100 |    96.87 |     100 |     100 | 42                
  types.ts         |       0 |        0 |       0 |       0 | 1                 
 ...rator/provider |   96.67 |    88.94 |   96.07 |   96.67 |                   
  dashscope.ts     |   97.37 |    91.39 |   93.33 |   97.37 | ...90-291,369-370 
  deepseek.ts      |   94.91 |    89.36 |     100 |   94.91 | ...31-132,145-146 
  default.ts       |   95.79 |    89.65 |   88.88 |   95.79 | 122-123,193-195   
  index.ts         |     100 |      100 |     100 |     100 |                   
  mimo.ts          |   94.11 |    66.66 |     100 |   94.11 | 29,52-53          
  minimax.ts       |     100 |      100 |     100 |     100 |                   
  mistral.ts       |   96.07 |    73.33 |     100 |   96.07 | 32-33             
  modelscope.ts    |     100 |      100 |     100 |     100 |                   
  openrouter.ts    |     100 |      100 |     100 |     100 |                   
  types.ts         |       0 |        0 |       0 |       0 |                   
  utils.ts         |     100 |      100 |     100 |     100 |                   
 src/extension     |   62.36 |    79.54 |   80.31 |   62.36 |                   
  ...-converter.ts |   66.27 |    52.45 |     100 |   66.27 | ...86-787,796-828 
  ...ionManager.ts |    47.1 |    82.06 |    65.9 |    47.1 | ...1405,1415-1434 
  ...onSettings.ts |   93.46 |    93.05 |     100 |   93.46 | ...17-221,228-232 
  ...-converter.ts |   54.88 |    94.44 |      60 |   54.88 | ...35-146,158-192 
  github.ts        |   46.41 |     87.3 |   63.63 |   46.41 | ...66-372,411-464 
  index.ts         |     100 |      100 |     100 |     100 |                   
  marketplace.ts   |   97.31 |    93.75 |     100 |   97.31 | ...65,185-186,275 
  npm.ts           |   59.01 |    71.69 |    87.5 |   59.01 | ...23-425,432-436 
  override.ts      |   94.11 |    88.88 |     100 |   94.11 | 63-64,81-82       
  redaction.ts     |     100 |      100 |     100 |     100 |                   
  settings.ts      |   66.26 |      100 |      50 |   66.26 | 81-107,141-146    
  storage.ts       |     100 |      100 |     100 |     100 |                   
  ...ableSchema.ts |     100 |      100 |     100 |     100 |                   
  variables.ts     |   88.75 |    83.33 |     100 |   88.75 | ...28-231,234-237 
 src/followup      |   55.24 |    85.18 |   81.25 |   55.24 |                   
  followupState.ts |      96 |    89.74 |     100 |      96 | 159-161,218-219   
  index.ts         |     100 |      100 |     100 |     100 |                   
  overlayFs.ts     |   95.06 |       84 |     100 |   95.06 | 78,108,122,133    
  speculation.ts   |   13.02 |      100 |   16.66 |   13.02 | 89-464,524-575    
  ...onToolGate.ts |     100 |    96.42 |     100 |     100 | 94                
  ...nGenerator.ts |   70.23 |    74.57 |   83.33 |   70.23 | ...83-247,317-319 
 src/generated     |       0 |        0 |       0 |       0 |                   
  git-commit.ts    |       0 |        0 |       0 |       0 | 1-10              
 src/goals         |   89.57 |    83.45 |   94.44 |   89.57 |                   
  ...eGoalStore.ts |    85.1 |    95.45 |   84.61 |    85.1 | ...63-166,174-182 
  goalHook.ts      |   97.26 |    91.48 |     100 |   97.26 | 100-105           
  goalJudge.ts     |   84.33 |    74.28 |     100 |   84.33 | ...57-358,366-368 
  index.ts         |     100 |      100 |     100 |     100 |                   
 src/hooks         |   86.78 |    85.48 |   87.96 |   86.78 |                   
  ...okRegistry.ts |   86.48 |    77.08 |     100 |   86.48 | ...41-344,362-369 
  ...bortSignal.ts |     100 |      100 |     100 |     100 |                   
  ...terpolator.ts |   96.66 |    93.33 |     100 |   96.66 | 66-67             
  ...HookRunner.ts |   96.68 |    87.23 |     100 |   96.68 | 110-112,231-233   
  ...Aggregator.ts |   96.35 |    90.69 |     100 |   96.35 | ...00-301,382,384 
  ...entHandler.ts |   95.27 |    86.74 |   94.11 |   95.27 | ...63,920-921,931 
  hookPlanner.ts   |   86.29 |    83.33 |   85.71 |   86.29 | ...15-219,226-237 
  hookRegistry.ts  |   90.17 |    83.33 |     100 |   90.17 | ...33,352,356,360 
  hookRunner.ts    |   62.42 |    72.04 |   66.66 |   62.42 | ...64-765,774-775 
  hookSystem.ts    |   86.78 |      100 |   68.88 |   86.78 | ...07-708,714-715 
  ...HookRunner.ts |   75.51 |     61.9 |      80 |   75.51 | ...05-406,424-425 
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...edCallback.ts |     100 |      100 |     100 |     100 |                   
  ...HookRunner.ts |   96.37 |     90.9 |      90 |   96.37 | 342-350,424-425   
  ...SkillHooks.ts |   78.75 |       75 |   66.66 |   78.75 | 62-66,137-152     
  ...oksManager.ts |   96.66 |    91.66 |     100 |   96.66 | ...90,209-210,223 
  ssrfGuard.ts     |   77.22 |    85.36 |     100 |   77.22 | ...57,261-267,273 
  stopHookCap.ts   |     100 |      100 |     100 |     100 |                   
  trustedHooks.ts  |      90 |    52.63 |     100 |      90 | ...53,66-67,97-98 
  types.ts         |   92.83 |       94 |    87.5 |   92.83 | ...87-488,573-577 
  urlValidator.ts  |     100 |      100 |     100 |     100 |                   
 src/ide           |   75.55 |    83.52 |   78.33 |   75.55 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  detect-ide.ts    |     100 |      100 |     100 |     100 |                   
  ide-client.ts    |   66.14 |    81.75 |   66.66 |   66.14 | ...3-964,993-1001 
  ide-installer.ts |   89.06 |    79.31 |     100 |   89.06 | ...36,143-147,160 
  ideContext.ts    |     100 |      100 |     100 |     100 |                   
  process-utils.ts |   84.84 |    71.79 |     100 |   84.84 | ...37,151,193-194 
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/lsp           |   42.41 |    52.21 |   52.14 |   42.41 |                   
  ...nfigLoader.ts |   70.27 |    35.89 |   94.73 |   70.27 | ...20-422,426-432 
  ...ionFactory.ts |   42.69 |    79.16 |      50 |   42.69 | ...62-413,419-436 
  ...Normalizer.ts |   23.09 |    13.72 |   30.43 |   23.09 | ...04-905,909-924 
  ...verManager.ts |   25.31 |    62.06 |   41.66 |   25.31 | ...85-704,710-740 
  ...eLspClient.ts |   32.77 |       80 |   17.64 |   32.77 | ...84-288,294-295 
  ...LspService.ts |   51.85 |    65.98 |   68.57 |   51.85 | ...1339,1399-1409 
  constants.ts     |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/mcp           |   78.75 |    75.56 |   75.92 |   78.75 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  ...h-provider.ts |   86.95 |      100 |   33.33 |   86.95 | ...,93,97,101-102 
  ...h-provider.ts |   73.82 |    53.92 |     100 |   73.82 | ...88-895,902-904 
  ...en-storage.ts |   98.64 |    97.77 |     100 |   98.64 | 88-89             
  oauth-utils.ts   |   70.58 |    85.29 |    90.9 |   70.58 | ...70-290,315-344 
  ...n-provider.ts |   89.83 |       96 |   45.45 |   89.83 | ...43,147,151-152 
 .../token-storage |   79.72 |    87.05 |   86.36 |   79.72 |                   
  ...en-storage.ts |     100 |      100 |     100 |     100 |                   
  ...en-storage.ts |   83.44 |    84.21 |   92.85 |   83.44 | ...68-178,186-187 
  ...en-storage.ts |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...en-storage.ts |   68.14 |    82.35 |   64.28 |   68.14 | ...81-295,298-314 
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/memory        |   73.17 |    77.08 |   72.39 |   73.17 |                   
  const.ts         |   94.28 |     92.3 |     100 |   94.28 | 66-67             
  dream.ts         |      66 |    73.33 |      50 |      66 | 51,108-149        
  ...entPlanner.ts |   57.84 |    72.72 |   33.33 |   57.84 | ...35,140-147,152 
  entries.ts       |   63.77 |    79.16 |      50 |   63.77 | ...72-180,183-189 
  extract.ts       |   92.72 |    74.19 |     100 |   92.72 | ...32,151-154,211 
  ...entPlanner.ts |   67.59 |     73.8 |      50 |   67.59 | ...31,240-243,415 
  ...ionPlanner.ts |       0 |        0 |       0 |       0 | 1                 
  forget.ts        |      46 |    61.53 |   44.44 |      46 | ...05,212,215-347 
  indexer.ts       |    86.3 |       50 |     100 |    86.3 | ...56,62-63,75-76 
  manager.ts       |    75.5 |    81.04 |    75.6 |    75.5 | ...1292,1305-1307 
  memoryAge.ts     |   90.47 |    77.77 |     100 |   90.47 | 50-51             
  paths.ts         |   79.06 |    95.12 |     100 |   79.06 | 32-33,49-86       
  prompt.ts        |   94.85 |    78.57 |     100 |   94.85 | ...62,165,303-304 
  recall.ts        |   76.73 |    69.38 |   88.88 |   76.73 | ...72-277,301-312 
  ...ceSelector.ts |   91.86 |    77.27 |     100 |   91.86 | ...24,126-127,135 
  scan.ts          |   92.92 |    78.26 |     100 |   92.92 | ...51-52,62,90-91 
  ...entPlanner.ts |   58.33 |    66.66 |   56.25 |   58.33 | ...61-282,358-403 
  status.ts        |   10.52 |      100 |       0 |   10.52 | 41-98             
  store.ts         |   93.33 |    81.25 |     100 |   93.33 | ...,94-95,119-120 
  types.ts         |     100 |      100 |     100 |     100 |                   
  ...ontextFile.ts |   79.38 |    78.33 |   81.81 |   79.38 | ...58-272,286-291 
 src/mocks         |       0 |        0 |       0 |       0 |                   
  msw.ts           |       0 |        0 |       0 |       0 | 1-9               
 src/models        |   89.98 |    87.37 |   88.15 |   89.98 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  ...tor-config.ts |   90.24 |    91.42 |     100 |   90.24 | 142,148,151-160   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...nfigErrors.ts |   74.22 |    47.82 |   84.61 |   74.22 | ...,67-74,106-117 
  ...igResolver.ts |   98.66 |    92.85 |     100 |   98.66 | 162,324,330       
  modelRegistry.ts |     100 |    98.63 |     100 |     100 | 229               
  modelsConfig.ts  |   86.24 |    85.23 |   82.92 |   86.24 | ...1328,1357-1358 
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/output        |     100 |      100 |     100 |     100 |                   
  ...-formatter.ts |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/permissions   |   82.65 |    91.86 |   68.32 |   82.65 |                   
  autoMode.ts      |   97.83 |    94.21 |     100 |   97.83 | 521-522,543-550   
  ...transcript.ts |      98 |       84 |     100 |      98 | 200-201           
  classifier.ts    |   93.95 |    94.44 |     100 |   93.95 | 158-165,383-387   
  ...erousRules.ts |     100 |    89.36 |     100 |     100 | 110,133,147,175   
  ...alTracking.ts |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...on-manager.ts |   84.86 |    89.03 |      80 |   84.86 | ...1024,1130-1134 
  rule-parser.ts   |   97.37 |    93.82 |     100 |   97.37 | ...-875,1024-1026 
  ...-semantics.ts |   70.28 |    90.69 |   46.21 |   70.28 | ...2214,2277-2280 
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...sifier-prompts |   99.04 |    95.23 |     100 |   99.04 |                   
  system-prompt.ts |   99.04 |    95.23 |     100 |   99.04 | 219               
 src/prompts       |   83.63 |      100 |    87.5 |   83.63 |                   
  mcp-prompts.ts   |   18.18 |      100 |       0 |   18.18 | 11-19             
  ...t-registry.ts |     100 |      100 |     100 |     100 |                   
 src/providers     |   79.44 |    64.39 |   64.28 |   79.44 |                   
  all-providers.ts |      68 |      100 |       0 |      68 | 68-69,73-79,83-89 
  index.ts         |     100 |      100 |     100 |     100 |                   
  install.ts       |   98.87 |    87.27 |     100 |   98.87 | 268-269           
  ...der-config.ts |   69.73 |    47.29 |   68.42 |   69.73 | ...10-411,418-427 
  types.ts         |       0 |        0 |       0 |       0 | 1                 
 ...viders/presets |   97.31 |    86.36 |      50 |   97.31 |                   
  ...oding-plan.ts |   87.34 |      100 |       0 |   87.34 | 82-84,87-89,91-94 
  ...a-standard.ts |     100 |      100 |     100 |     100 |                   
  ...token-plan.ts |     100 |      100 |     100 |     100 |                   
  ...m-provider.ts |   97.01 |    81.25 |      75 |   97.01 | 120-121           
  deepseek.ts      |     100 |      100 |     100 |     100 |                   
  idealab.ts       |     100 |      100 |     100 |     100 |                   
  minimax.ts       |     100 |      100 |     100 |     100 |                   
  modelscope.ts    |     100 |      100 |     100 |     100 |                   
  openrouter.ts    |     100 |      100 |     100 |     100 |                   
  zai.ts           |     100 |      100 |     100 |     100 |                   
 src/qwen          |   84.48 |     77.6 |   95.83 |   84.48 |                   
  ...tGenerator.ts |   98.64 |    98.18 |     100 |   98.64 | 105-106           
  qwenOAuth2.ts    |   80.85 |    70.74 |   90.32 |   80.85 | ...1169-1185,1215 
  ...kenManager.ts |   85.36 |    76.61 |     100 |   85.36 | ...51-756,777-782 
 src/services      |   85.39 |    84.04 |   91.98 |   85.39 |                   
  ...ionTrailer.ts |     100 |      100 |     100 |     100 |                   
  ...llRegistry.ts |   97.35 |    85.34 |     100 |   97.35 | ...94,117,417-418 
  ...ionService.ts |   98.19 |    94.94 |     100 |   98.19 | 496,498-502,605   
  ...ingService.ts |   84.34 |    84.21 |   83.33 |   84.34 | ...1274,1291-1292 
  ...ttribution.ts |   91.73 |    87.71 |      90 |   91.73 | ...80-685,826-827 
  ...utSlimming.ts |     100 |    97.43 |     100 |     100 | 215,268           
  cronScheduler.ts |   97.57 |    92.85 |     100 |   97.57 | 62-63,77,157      
  ...eryService.ts |   80.43 |    95.45 |      75 |   80.43 | ...19-134,140-141 
  ...oryService.ts |   86.18 |    76.76 |   91.17 |   86.18 | ...1150,1191-1194 
  fileReadCache.ts |     100 |      100 |     100 |     100 |                   
  ...temService.ts |   91.27 |    82.69 |    90.9 |   91.27 | ...94,196,294-301 
  ...ratedFiles.ts |      96 |    88.23 |     100 |      96 | 119-120,146-147   
  gitInit.ts       |     100 |      100 |     100 |     100 |                   
  ...reeService.ts |    69.4 |    68.82 |   93.33 |    69.4 | ...2064,2092-2093 
  ...ionService.ts |   98.13 |     97.8 |   95.45 |   98.13 | ...32-333,380-381 
  ...ticsDumper.ts |   98.18 |    95.23 |     100 |   98.18 | 165-166           
  ...ureMonitor.ts |   95.64 |    91.77 |      96 |   95.64 | ...47,648,662-664 
  ...orRegistry.ts |   97.24 |    92.03 |     100 |   97.24 | ...49-450,601-602 
  ...ttachments.ts |   97.24 |    90.39 |     100 |   97.24 | ...08,646,661-662 
  sessionRecap.ts  |     9.7 |      100 |       0 |     9.7 | 44-174            
  ...ionService.ts |   87.86 |    78.85 |   93.93 |   87.86 | ...1334,1372-1392 
  sessionTitle.ts  |   93.87 |    71.15 |     100 |   93.87 | ...33-236,267-268 
  ...ionService.ts |   81.29 |    78.31 |   89.28 |   81.29 | ...1926,1932-1937 
  ...pInhibitor.ts |   97.02 |    90.74 |     100 |   97.02 | ...14-115,289-290 
  ...Estimation.ts |     100 |      100 |     100 |     100 |                   
  ...UseSummary.ts |   94.63 |    88.46 |     100 |   94.63 | ...62-164,214-215 
  ...oryService.ts |    60.2 |    74.07 |   42.85 |    60.2 | ...00-302,307-308 
  ...reeCleanup.ts |   14.56 |      100 |   33.33 |   14.56 | 58-185            
  ...ionService.ts |   84.21 |    79.41 |     100 |   84.21 | ...18-219,235-236 
 ...icrocompaction |   98.05 |     91.8 |     100 |   98.05 |                   
  microcompact.ts  |   98.05 |     91.8 |     100 |   98.05 | ...19,289,293,391 
 src/skills        |   87.96 |    86.16 |   89.83 |   87.96 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...activation.ts |     100 |     93.1 |     100 |     100 | 93,112            
  skill-load.ts    |   94.79 |     87.5 |     100 |   94.79 | ...00,220,232-234 
  skill-manager.ts |   83.35 |    81.42 |   82.35 |   83.35 | ...1196,1203-1207 
  skill-paths.ts   |   89.15 |    86.36 |     100 |   89.15 | ...00-101,106-107 
  symlinkScope.ts  |     100 |      100 |     100 |     100 |                   
  types.ts         |   97.53 |    97.36 |     100 |   97.53 | 248-249           
 src/subagents     |   84.64 |    83.61 |   95.65 |   84.64 |                   
  ...ter-schema.ts |     100 |    95.45 |     100 |     100 | 99                
  ...tin-agents.ts |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...nt-manager.ts |   79.28 |    77.77 |    93.1 |   79.28 | ...1215,1270-1271 
  types.ts         |     100 |      100 |     100 |     100 |                   
  validation.ts    |   92.46 |    95.18 |     100 |   92.46 | 47-52,63-68,71-76 
 src/telemetry     |   77.93 |    86.54 |    79.5 |   77.93 |                   
  config.ts        |     100 |      100 |     100 |     100 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  ...on-metrics.ts |   98.96 |    79.48 |     100 |   98.96 | 169,183           
  ...on-tracing.ts |   58.02 |    60.41 |   55.55 |   58.02 | ...08,349-351,367 
  ...attributes.ts |   98.13 |       88 |     100 |   98.13 | 185-187           
  ...-exporters.ts |   46.37 |      100 |   44.44 |   46.37 | ...85,88-89,92-93 
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...t.circular.ts |       0 |        0 |       0 |       0 | 1-111             
  ...-processor.ts |   99.09 |    95.61 |      95 |   99.09 | 141,365-366       
  ...t.circular.ts |       0 |        0 |       0 |       0 | 1-128             
  loggers.ts       |   54.08 |    65.85 |   60.86 |   54.08 | ...1250,1267-1287 
  metrics.ts       |   75.06 |    81.52 |   75.43 |   75.06 | ...1021,1024-1035 
  ...attributes.ts |     100 |      100 |     100 |     100 |                   
  ...ime-config.ts |       0 |        0 |       0 |       0 | 1                 
  sanitize.ts      |      80 |    83.33 |     100 |      80 | 35-36,41-42       
  sdk.ts           |   86.75 |     88.4 |   66.66 |   86.75 | ...17-621,659-681 
  ...on-context.ts |     100 |      100 |     100 |     100 |                   
  ...on-tracing.ts |   89.93 |    87.81 |   96.55 |   89.93 | ...1494,1525-1528 
  ...etry-utils.ts |     100 |      100 |     100 |     100 |                   
  ...l-decision.ts |     100 |      100 |     100 |     100 |                   
  trace-context.ts |     100 |      100 |     100 |     100 |                   
  ...e-id-utils.ts |     100 |      100 |     100 |     100 |                   
  tracer.ts        |   98.56 |    88.63 |     100 |   98.56 | 52,101            
  types.ts         |   80.89 |     86.4 |   85.33 |   80.89 | ...1215,1218-1247 
  uiTelemetry.ts   |      92 |    95.34 |   80.95 |      92 | ...00,206-216,244 
 ...ry/qwen-logger |   68.17 |     80.2 |   65.51 |   68.17 |                   
  event-types.ts   |       0 |        0 |       0 |       0 |                   
  qwen-logger.ts   |   68.17 |       80 |   64.91 |   68.17 | ...1077,1115-1116 
 src/test-utils    |   93.16 |    95.91 |   76.47 |   93.16 |                   
  config.ts        |     100 |      100 |     100 |     100 |                   
  ...st-helpers.ts |   94.11 |       90 |     100 |   94.11 | 69-70             
  index.ts         |     100 |      100 |     100 |     100 |                   
  mock-tool.ts     |   91.19 |    97.14 |   72.41 |   91.19 | ...38,202-203,216 
  ...aceContext.ts |     100 |      100 |     100 |     100 |                   
 src/tools         |   79.46 |    82.05 |   85.96 |   79.46 |                   
  ...erQuestion.ts |   88.93 |    76.74 |    90.9 |   88.93 | ...39-340,347-348 
  cron-create.ts   |   88.11 |    88.88 |    62.5 |   88.11 | ...,43-44,165-172 
  cron-delete.ts   |   96.82 |      100 |   83.33 |   96.82 | 26-27             
  cron-list.ts     |   96.66 |      100 |   83.33 |   96.66 | 25-26             
  diffOptions.ts   |     100 |      100 |     100 |     100 |                   
  edit.ts          |   81.02 |    84.07 |      75 |   81.02 | ...15-716,826-876 
  ...r-worktree.ts |   83.14 |    67.56 |    87.5 |   83.14 | ...84-187,278-279 
  exit-worktree.ts |   84.23 |    85.96 |   91.66 |   84.23 | ...92-293,298-312 
  exitPlanMode.ts  |   85.09 |    85.71 |     100 |   85.09 | ...60-163,177-189 
  glob.ts          |   90.63 |    88.33 |   84.61 |   90.63 | ...28,171,302,305 
  grep.ts          |   79.19 |    85.71 |   78.94 |   79.19 | ...20,560,569-576 
  ls.ts            |   96.74 |    90.27 |     100 |   96.74 | 176-181,212,216   
  lsp.ts           |   72.77 |    60.09 |   90.32 |   72.77 | ...1211,1213-1214 
  ...nt-manager.ts |   80.29 |    77.98 |   84.44 |   80.29 | ...2932,2934-2935 
  mcp-client.ts    |      43 |    87.57 |      75 |      43 | ...1790,1794-1797 
  ...ry-timeout.ts |     100 |      100 |     100 |     100 |                   
  mcp-errors.ts    |     100 |      100 |     100 |     100 |                   
  ...pool-entry.ts |   77.21 |    83.96 |   79.41 |   77.21 | ...1259,1267-1268 
  ...ool-events.ts |       8 |      100 |       0 |       8 | 123-149           
  mcp-pool-key.ts  |   97.46 |    93.93 |     100 |   97.46 | 175-176           
  mcp-tool.ts      |   91.29 |    89.32 |   96.55 |   91.29 | ...26-627,677-678 
  ...sport-pool.ts |   83.27 |       80 |   84.61 |   83.27 | ...1399,1406-1410 
  ...ace-budget.ts |   87.27 |     82.6 |     100 |   87.27 | ...00-305,340-345 
  memory-config.ts |       0 |        0 |       0 |       0 | 1-47              
  ...iable-tool.ts |     100 |    84.61 |     100 |     100 | 102,109           
  monitor.ts       |   91.65 |    84.05 |   88.46 |   91.65 | ...87,600,796-801 
  notebook-edit.ts |   85.11 |    76.42 |   81.25 |   85.11 | ...54-870,916-917 
  ...escendants.ts |   36.17 |    64.51 |   55.55 |   36.17 | ...46-310,385-390 
  ...nforcement.ts |   82.57 |       90 |     100 |   82.57 | 174-185,234-247   
  read-file.ts     |    95.4 |    90.32 |      90 |    95.4 | ...99,299-302,305 
  ripGrep.ts       |   94.59 |    85.71 |   93.33 |   94.59 | ...60,463,541-542 
  ...-transport.ts |    6.34 |      100 |       0 |    6.34 | 47-145            
  send-message.ts  |   79.48 |    86.95 |    62.5 |   79.48 | ...97-203,286-294 
  ...n-mcp-view.ts |   92.37 |    93.54 |   88.88 |   92.37 | 118-126           
  shell.ts         |   74.36 |    80.89 |   91.78 |   74.36 | ...4251,4318-4319 
  skill-utils.ts   |     100 |      100 |     100 |     100 |                   
  skill.ts         |   91.02 |    94.28 |      90 |   91.02 | ...44,548,577-599 
  ...eticOutput.ts |   95.12 |      100 |      80 |   95.12 | 87-88             
  task-create.ts   |   93.85 |     92.3 |   81.81 |   93.85 | 41-45,59-60,91    
  task-list.ts     |   73.38 |    77.77 |   83.33 |   73.38 | ...01,104,108-115 
  task-stop.ts     |   93.14 |    96.15 |   85.71 |   93.14 | 39-40,54-64       
  task-update.ts   |    81.5 |       78 |    92.3 |    81.5 | ...74-382,408-414 
  team-create.ts   |   97.22 |    85.71 |   83.33 |   97.22 | 48-49,129-130     
  team-delete.ts   |   86.41 |    83.33 |   83.33 |   86.41 | 36-37,41-47,71-72 
  todoWrite.ts     |   89.27 |    82.05 |   92.85 |   89.27 | ...50-555,577-578 
  tool-error.ts    |     100 |      100 |     100 |     100 |                   
  tool-names.ts    |     100 |      100 |     100 |     100 |                   
  tool-registry.ts |   74.85 |    77.27 |   79.06 |   74.85 | ...53-854,862-863 
  tool-search.ts   |   92.35 |    85.84 |    92.3 |   92.35 | ...08-213,320-329 
  tools.ts         |   92.14 |    90.38 |   89.47 |   92.14 | ...78-479,495-501 
  web-fetch.ts     |   88.84 |       80 |   92.85 |   88.84 | ...12-313,315-316 
  write-file.ts    |   82.65 |    80.45 |   84.61 |   82.65 | ...65-668,696-731 
 src/tools/agent   |    75.3 |    83.09 |   74.68 |    75.3 |                   
  agent.ts         |    75.5 |    83.23 |      75 |    75.5 | ...2933,2960-3023 
  fork-subagent.ts |   70.73 |    77.77 |   71.42 |   70.73 | ...09-110,145-156 
 ...s/computer-use |   85.21 |     87.9 |   76.31 |   85.21 |                   
  bootstrap.ts     |   72.09 |    92.85 |   66.66 |   72.09 | 137-191,302-303   
  client.ts        |      38 |      100 |      50 |      38 | ...48-178,182-191 
  constants.ts     |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  install-state.ts |   94.44 |       75 |     100 |   94.44 | 40-41             
  ...n-detector.ts |     100 |     87.5 |     100 |     100 | 43                
  schemas.ts       |     100 |      100 |     100 |     100 |                   
  tool.ts          |   95.67 |    82.97 |    92.3 |   95.67 | 49-50,159-165     
 ...tools/workflow |   93.03 |    66.66 |    90.9 |   93.03 |                   
  workflow.ts      |   93.03 |    66.66 |    90.9 |   93.03 | ...24-225,237-240 
 src/utils         |   89.67 |    88.06 |   94.12 |   89.67 |                   
  LruCache.ts      |       0 |        0 |       0 |       0 | 1-41              
  ...Controller.ts |     100 |      100 |     100 |     100 |                   
  ...ssageQueue.ts |     100 |      100 |     100 |     100 |                   
  ...cFileWrite.ts |   94.76 |    93.06 |     100 |   94.76 | ...30-531,634-638 
  bareMode.ts      |   27.27 |      100 |       0 |   27.27 | 9-15,18-19        
  browser.ts       |    7.69 |      100 |       0 |    7.69 | 17-56             
  btwUtils.ts      |   13.95 |      100 |       0 |   13.95 | 17-31,34-55       
  bundlePaths.ts   |     100 |      100 |     100 |     100 |                   
  ...igResolver.ts |     100 |      100 |     100 |     100 |                   
  ...engthError.ts |      90 |    87.71 |     100 |      90 | ...54-155,158-159 
  cronDisplay.ts   |   42.85 |    23.07 |     100 |   42.85 | 26-31,33-45,47-54 
  cronParser.ts    |   89.74 |    85.41 |     100 |   89.74 | ...,63-64,183-186 
  debugLogger.ts   |   96.42 |    94.11 |   88.23 |   96.42 | 185-189           
  editHelper.ts    |   93.63 |    83.52 |     100 |   93.63 | ...28-429,463-464 
  editor.ts        |    97.6 |     95.4 |     100 |    97.6 | ...25-326,328-329 
  ...arResolver.ts |   94.28 |    88.88 |     100 |   94.28 | 28-29,125-126     
  ...entContext.ts |   97.24 |    95.38 |   93.75 |   97.24 | ...28-130,146-147 
  errorParsing.ts  |    97.7 |    97.05 |     100 |    97.7 | 72-73             
  ...rReporting.ts |   88.46 |       90 |     100 |   88.46 | 69-74             
  errors.ts        |   70.54 |    79.59 |      50 |   70.54 | ...15-231,235-241 
  fetch.ts         |   70.18 |    71.42 |   71.42 |   70.18 | ...42,148,161,186 
  fileUtils.ts     |    91.5 |    86.25 |   95.23 |    91.5 | ...1191,1195-1201 
  forkedAgent.ts   |   80.68 |    78.12 |   83.33 |   80.68 | ...39-545,550-556 
  formatters.ts    |   81.81 |       75 |     100 |   81.81 | 15-16             
  ...eUtilities.ts |   89.21 |    86.66 |     100 |   89.21 | 16-17,49-55,65-66 
  ...rStructure.ts |   94.36 |    94.28 |     100 |   94.36 | ...17-120,330-335 
  getPty.ts        |   31.57 |       50 |     100 |   31.57 | 26-38             
  gitDiff.ts       |   92.36 |    79.53 |     100 |   92.36 | ...55-856,928-929 
  ...noreParser.ts |    92.3 |    89.36 |     100 |    92.3 | ...15-116,186-187 
  gitUtils.ts      |   73.64 |    90.32 |   83.33 |   73.64 | ...,78-79,103-154 
  iconvHelper.ts   |     100 |      100 |     100 |     100 |                   
  ...rePatterns.ts |     100 |      100 |     100 |     100 |                   
  ...ionManager.ts |     100 |     90.9 |     100 |     100 | 27                
  ...lPromptIds.ts |     100 |      100 |     100 |     100 |                   
  jsonl-utils.ts   |   88.98 |    90.66 |   91.66 |   88.98 | ...46-349,359-365 
  ...-detection.ts |     100 |      100 |     100 |     100 |                   
  ...iagnostics.ts |    96.4 |     94.2 |     100 |    96.4 | ...66,293-294,376 
  ...yDiscovery.ts |    92.4 |    89.01 |     100 |    92.4 | ...28,331,522-525 
  ...tProcessor.ts |   93.77 |    89.02 |     100 |   93.77 | ...13-319,406-407 
  ...Inspectors.ts |   61.53 |      100 |      50 |   61.53 | 18-23             
  modelId.ts       |   98.96 |    98.18 |     100 |   98.96 | 153               
  ...kerChecker.ts |   90.78 |    91.66 |     100 |   90.78 | 73-79             
  notebook.ts      |   94.57 |    89.83 |   95.83 |   94.57 | ...21,333,385-387 
  openaiLogger.ts  |   90.85 |    87.87 |     100 |   90.85 | ...97-199,222-227 
  partUtils.ts     |     100 |    98.61 |     100 |     100 | 206               
  pathReader.ts    |     100 |      100 |     100 |     100 |                   
  paths.ts         |   93.21 |    91.95 |     100 |   93.21 | ...89-390,392-394 
  pdf.ts           |   93.68 |    87.05 |     100 |   93.68 | ...96-297,321-325 
  projectPath.ts   |     100 |      100 |     100 |     100 |                   
  projectRoot.ts   |   71.73 |    78.57 |     100 |   71.73 | 54-66             
  ...ectSummary.ts |   89.62 |    72.41 |     100 |   89.62 | ...40-145,196-199 
  ...tIdContext.ts |     100 |      100 |     100 |     100 |                   
  proxyUtils.ts    |     100 |      100 |     100 |     100 |                   
  ...rDetection.ts |   58.57 |       76 |     100 |   58.57 | ...4,88-89,95-100 
  ...noreParser.ts |   85.45 |    85.18 |     100 |   85.45 | ...59,65-66,72-73 
  rateLimit.ts     |   92.55 |    85.92 |     100 |   92.55 | ...70-272,309-310 
  readManyFiles.ts |   87.59 |       84 |     100 |   87.59 | ...09-211,227-238 
  retry.ts         |   91.86 |    87.17 |     100 |   91.86 | ...30,451,458-459 
  retryContext.ts  |     100 |      100 |     100 |     100 |                   
  ripgrepUtils.ts  |   46.79 |    84.37 |   66.66 |   46.79 | ...45-246,258-335 
  ...sDiscovery.ts |   97.42 |    92.85 |     100 |   97.42 | ...04,182-183,202 
  ...iagnostics.ts |   83.08 |     67.5 |   92.59 |   83.08 | ...23,543-544,550 
  ...tchOptions.ts |   82.18 |    85.18 |   95.23 |   82.18 | ...24,549,578-587 
  ...odelPrefix.ts |     100 |      100 |     100 |     100 |                   
  runtimeStatus.ts |    97.5 |    88.57 |     100 |    97.5 | 162-163           
  safeJsonParse.ts |   74.07 |    83.33 |     100 |   74.07 | 40-46             
  ...nStringify.ts |     100 |      100 |     100 |     100 |                   
  ...aConverter.ts |   90.78 |    88.23 |     100 |   90.78 | ...41-42,93,95-96 
  ...aValidator.ts |      95 |    82.75 |     100 |      95 | ...07,216-219,273 
  ...r-launcher.ts |   76.92 |     91.3 |   66.66 |   76.92 | ...34,136,157-195 
  ...nIdContext.ts |     100 |      100 |     100 |     100 |                   
  ...orageUtils.ts |   96.89 |    85.84 |     100 |   96.89 | ...51,367,447,466 
  shell-utils.ts   |   84.39 |    90.46 |     100 |   84.39 | ...1583,1590-1594 
  ...lAstParser.ts |   95.57 |    85.79 |     100 |   95.57 | ...1066-1068,1078 
  ...ContextEnv.ts |     100 |      100 |     100 |     100 |                   
  ...nlyChecker.ts |   95.08 |    91.66 |     100 |   95.08 | ...15-316,324-325 
  sideQuery.ts     |   86.17 |    86.53 |     100 |   86.17 | ...55-161,163-169 
  ...pEventSink.ts |     100 |       80 |     100 |     100 | 61                
  ...tGenerator.ts |     100 |      100 |     100 |     100 |                   
  ...ameContext.ts |     100 |      100 |     100 |     100 |                   
  symlink.ts       |   81.48 |       75 |     100 |   81.48 | 54-59             
  ...emEncoding.ts |   96.36 |    91.17 |     100 |   96.36 | 59-60,124-125     
  terminalSafe.ts  |     100 |      100 |     100 |     100 |                   
  ...Serializer.ts |   98.72 |       90 |     100 |   98.72 | 42-43,134,201-203 
  testUtils.ts     |   53.33 |      100 |   33.33 |   53.33 | ...53,59-64,70-72 
  textUtils.ts     |      60 |      100 |   66.66 |      60 | 36-55             
  thoughtUtils.ts  |     100 |    92.85 |     100 |     100 | 71                
  ...-converter.ts |   94.59 |    85.71 |     100 |   94.59 | 35-36             
  tool-utils.ts    |    93.6 |     91.3 |     100 |    93.6 | ...58-159,162-163 
  truncation.ts    |     100 |       92 |     100 |     100 | 52,71             
  windowsPath.ts   |   89.47 |    78.57 |     100 |   89.47 | ...57-58,62,90-91 
  ...aceContext.ts |   93.71 |    89.28 |   93.33 |   93.71 | ...24-225,249-251 
  xml.ts           |    97.8 |     87.5 |     100 |    97.8 | 98-99             
  yaml-parser.ts   |   86.48 |    81.25 |     100 |   86.48 | ...45-248,253-254 
 ...ils/filesearch |   86.35 |    81.76 |   96.42 |   86.35 |                   
  crawlCache.ts    |     100 |      100 |     100 |     100 |                   
  crawler.ts       |   83.07 |    77.74 |   94.82 |   83.07 | ...1468,1502-1503 
  fileSearch.ts    |   93.58 |    87.32 |     100 |   93.58 | ...46-247,249-250 
  ignore.ts        |     100 |      100 |     100 |     100 |                   
  result-cache.ts  |     100 |     92.3 |     100 |     100 | 46                
 ...uest-tokenizer |   56.63 |    74.52 |   74.19 |   56.63 |                   
  ...eTokenizer.ts |   41.86 |    76.47 |   69.23 |   41.86 | ...70-443,453-507 
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...tTokenizer.ts |   68.39 |    69.49 |    90.9 |   68.39 | ...24-325,327-328 
  ...ageFormats.ts |      76 |      100 |   33.33 |      76 | 45-48,55-56       
  textTokenizer.ts |     100 |      100 |     100 |     100 |                   
  types.ts         |       0 |        0 |       0 |       0 | 1                 
-------------------|---------|----------|---------|---------|-------------------

For detailed HTML reports, please see the 'coverage-reports-22.x-ubuntu-latest' artifact from the main CI run.


- **A1 — `BridgeClient.extNotification()` (`bridgeClient.ts:491`):** the `current_model_update` notification → `model_switched`.
- **A2 — `BridgeClient.sessionUpdate()` (`bridgeClient.ts:397`):** the `current_mode_update` sessionUpdate → `approval_mode_changed`. This method today publishes every notification verbatim as `{ type: 'session_update', data: params }`; the demux is added here.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Critical] A2's demux insertion point is wrong throughout the document. The doc states A2 (current_mode_update) is demuxed in BridgeClient.sessionUpdate(), and that A1/A2 use "different transports + insertion points."

Verified: Session.setMode() calls this.client.extNotification('qwen/notify/session/mode-update', ...) (Session.ts:2490) — the same extNotification transport as A1. On the receiving side, BridgeClient.extNotification() (bridgeClient.ts:481) dispatches qwen/notify/session/mode-update to handleInSessionModeUpdate(). BridgeClient.sessionUpdate() (bridgeClient.ts:397) has no demux at all — it publishes everything as generic session_update.

This affects §0 (line 115), §1.1 item 2b (line 135), §2.1 (line 161), §6, and the §9 decision table. An implementer following this doc would add A2 demux logic to sessionUpdate() where it would never fire.

Suggested fix: Update all A2 demux references from sessionUpdate() to extNotification(). Note that the legacy session_update{current_mode_update} frame still flows via sendUpdate() for backward-compat, but the demux promotion happens in extNotification(), same as A1.

— qwen3.7-max via Qwen Code /review


### 2.2 Post-roundtrip reconciliation (concurrent-in-session drift)

Suppress + drop assumes the bridge roundtrip and the agent describe the **same** change. That breaks under a concurrent in-session change, because in-session `/model` calls `Session.setModel` **directly and does NOT enter `modelChangeQueue`**:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Critical] Reconciliation does NOT fire on failure paths in the implementation. The doc states reconciliation fires "on both the success and failure paths (a .finally on the roundtrip)."

Verified: all three call sites (applyModelServiceId at bridge.ts:1560, setSessionModel at bridge.ts:3641, setSessionApprovalMode at bridge.ts:3874) only call reconcileAfterRoundtrip when succeeded === true, and log action=skipped reason=roundtrip_failed otherwise. Tests confirm: bridge.test.ts:3862 ("does NOT reconcile when applyModelServiceId roundtrip fails"), :8581, :8801.

The §2.2 failure-path consumer event ordering and the §8 failure-path test scenarios describe behavior the code does not implement. A timed-out roundtrip that the agent actually applied is never caught by reconciliation.

Suggested fix: Either (a) update the design to match the implementation (success-only; document what covers the timeout-then-agent-applied scenario), or (b) update the implementation to reconcile on both paths.

— qwen3.7-max via Qwen Code /review

- **Non-recursion structural guard (§2.2):** while reconciliation is in flight (`reconciliationInFlight === true`), a concurrent promotion that would trigger reconciliation is **skipped** (`action=skipped-reentrant`); the flag resets after the in-flight reconciliation settles regardless of outcome. Additionally: after a reconciliation corrective `model_switched` fires, assert `getSessionContextStatus` is invoked **exactly once** for the triggering settle event — the corrective publish does NOT re-enter the reconciliation path (bounded call count).
- **Failure-path converged (§2.2):** `model_switch_failed` fires → reconciliation reads `getSessionContextStatus` → returns `entry.currentModelId` (unchanged) → no corrective emitted (`action=converged`); bus state unchanged.
- **Generation counter values (§2.3):** after a promote → reconciliation → corrective sequence, `entry.modelPublishGeneration` equals `gen_before + 2` (one for the initial promote, one for the corrective); `gen_before`/`gen_after` logged in observability match the counter values at entry/exit of reconciliation.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Critical] Generation counter test math is wrong. The assertion entry.modelPublishGeneration === gen_before + 2 double-counts the initial promote.

gen_before is captured at reconciliation entry — after the bridge roundtrip publish already bumped the generation via publishModelSwitched. So gen_before already includes the promote bump. The corrective adds 1 more, making the final value gen_before + 1, not +2.

Tracing: (1) bridge publishes A, gen goes N→N+1; (2) reconciliation starts, genBefore = N+1; (3) corrective publishes B, gen goes N+1→N+2. Final = N+2 = gen_before + 1.

Suggested fix: Assert gen_before + 1, or define a separate gen_initial (pre-promote value) for the +2 assertion.

— qwen3.7-max via Qwen Code /review


**Fresh-read invariant (v11/v12):** the `getSessionContextStatus` read used by reconciliation MUST return a fresh point-in-time value from the agent process — it MUST bypass any response cache, request deduplication, or in-flight coalescing. Without this, a cached response that happens to match `entry.currentModelId` produces a false "converged" (missed real divergence — the agent may have moved on), and a cached response that diverges from `entry.currentModelId` produces a false "corrective" that sets the bus to a stale value instead of the agent's true current model. In practice: `extMethod` is a fresh JSON-RPC `requestSessionStatus` call on each invocation — no middleware or transport-level caching exists today. The invariant is contractual: any future caching layer MUST exempt reconciliation reads.

**Generation guard (v10 — closes the read-window TOCTOU):** between settle and the async read returning, a concurrent in-session `/model C` can promote `model_switched(C)`; the in-flight read (issued before C) returns the pre-C value and reconciliation would clobber C. Fix: a per-session `modelPublishGeneration` is bumped on **every** `model_switched` publish (bridge / demux promotion / reconciliation corrective) — exclusively via the `publishModelSwitched` helper (v11). Reconciliation captures the generation **before** the read and **skips the corrective if it advanced** during the read — a newer authoritative publish already landed, so the bus is current.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] Generation guard "skip" is documented but the rerun mechanism is not. The implementation (bridge.ts:1883-1893,1960) sets rerun = true when the generation changed, then re-invokes reconcileAfterRoundtrip in the finally block. This rerun is load-bearing: the concurrent publish that bumped the generation had its own reconciliation bail on the reconciliationInFlight guard, so without the rerun the concurrent change is never reconciled against agent truth.

The behavior is tested (bridge.test.ts:8647) but entirely absent from this design doc. An implementer reading only the design would ship skip-without-rerun, creating a reconciliation gap.

Suggested fix: Document the rerun: "If the generation advanced during the read, reconciliation skips the stale corrective but schedules exactly one re-run in the finally block (after clearing reconciliationInFlight). The re-run uses a fresh authoritative read."

— qwen3.7-max via Qwen Code /review

1. **Transport: `extNotification`, not a sessionUpdate (v7).** `current_model_update` is **not** an ACP `SessionUpdate` variant. So `Session.setModel`, after `switchModel` resolves (**success only**), emits via the agent→bridge **`extNotification`** side-channel with the **fully-qualified method name `qwen/notify/session/model-update`** (matching the existing `qwen/notify/session/*` convention; impl in #4546) and payload `{ v:1, sessionId, currentModelId }`. No `previousModelId` / `authType` (the bridge derives `previous` from its state cache §2.3; `model_switched` is `{sessionId,modelId}`). **Implementation note:** `BridgeClient.extNotification()`'s current early-return guard (`if (method !== 'qwen/notify/session/mcp-budget-event') return;`) must become a method dispatch so the model-update handler is reachable (done in #4546).
2. **`BridgeClient.extNotification()` (`bridgeClient.ts:491`) demuxes** the `current_model_update` notification → `model_switched` (§2.1), **only when no bridge model roundtrip is in flight** for that session. (A2's `current_mode_update` stays a real sessionUpdate, demuxed in `sessionUpdate()` — see §2.1.)
3. **`model_switch_failed` stays bridge-only** — `Session.setModel` throws with no notification; the bridge keeps publishing it on both failure paths.
4. **Timeout-race (best-effort demux drop + authoritative reconciliation backstop — v9).** The bridge's `withTimeout` (`bridge.ts:2844-2849`) can reject (publishing `model_switch_failed(A)`) while A's ACP call keeps running (FIXME `bridge.ts:2836-2840`). If a change B then succeeds (`model_switched(B)`) and A's call finally completes, A's late `current_model_update(A)` must not make A the apparent final state. **Value comparison alone can't decide** this (a late stale `A` and a fresh switch to `A` look identical — a distributed-ordering problem). So: the demux does a **best-effort dedup** (drops a `current_model_update` whose `currentModelId` already equals `entry.currentModelId` — a redundant no-op), and the **authoritative correctness comes from §2.2 reconciliation**: a timed-out earlier change always corresponds to a _settled bridge roundtrip_, which triggers a post-settle authoritative read that re-publishes the agent's true model. No agent-side sequence counter required.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] packages/cli/src/serve/httpAcpBridge.ts does not exist — it was renamed to packages/cli/src/serve/acpSessionBridge.ts. The re-export alias is at bridge.ts:4684 (export const createHttpAcpBridge = createAcpSessionBridge).

Suggested fix: Update the reference to acpSessionBridge.ts.

— qwen3.7-max via Qwen Code /review

- **Update synchronously at every publish site**, in the same synchronous turn as the publish (no `await` between read-of-old and write-of-new): all `model_switched` publishes go through the §2.2 `publishModelSwitched` helper (which atomically updates `entry.currentModelId` + bumps `entry.modelPublishGeneration` + publishes to bus); `approval_mode_changed` (`:2979` / `:3007`) updates `entry.currentApprovalMode`; `availableCommands` is updated in `BridgeClient.sessionUpdate()` when it receives an `available_commands_update` generic sessionUpdate — the handler sets `entry.availableCommands = payload.commands` synchronously **before** the generic forwarding publish. The helper guarantees no publish site can miss a cache or generation update.
- **`availableCommands` specifics (v13):** type is `AvailableCommand[]` (matching `status.ts`). Unlike model/mode, this field has **no named promoted bus event** and **no reconciliation** — it's a passive cache, updated by the generic `session_update` path. If the implementer misses the hook, A5's snapshot serves stale/undefined commands with no backstop. The trigger path is explicitly `BridgeClient.sessionUpdate()` → check `params.type === 'available_commands_update'` → update cache → forward as generic `session_update`.
- **Seed** from the `createSession` / `loadSession` ACP response when the entry is created (initial model/mode), before any change occurs.
- **Consumers (synchronous field reads):**

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] reconciliation_failed event and bounded retry are specified but not implemented. The implementation (bridge.ts:1944-1956) makes a single attempt and logs to stderr only. The code explicitly notes: "reconciliation_failed is not a known SDK event type, so asKnownDaemonEvent drops it and the reducer never sees it."

The §8 read-error test scenario asserts behavior (retry count, reconciliation_failed event, per-attempt logs) that the code does not implement. A transient failure leaves the bus potentially diverged with only a stderr log line.

Suggested fix: Align the design with the implementation: document that reconciliation failures are surfaced via stderr/operator logs only (not as a bus event), with the SDK-compat reason. If programmatic notification is desired, note adding reconciliation_failed to known SDK event types as a prerequisite.

— qwen3.7-max via Qwen Code /review

- **Drop-when-suppressed (third rule):** when a _promotable_ sub-type is NOT promoted (suppressed or stale), **drop it entirely** — do **not** fall back to publishing the generic `session_update`. The bridge is already publishing the authoritative named event; emitting the generic wrapper too would double-signal. (Residual concurrent-in-session drift is handled by the §2.2 reconciliation.)
- **Generic-wrapper suppression:** a promoted sub-type publishes the named event only — **except during the dual-emit transition window (below)**.
- **Dual-emit transition (IDE-companion lockstep, see §6):** because the daemon and the VS Code IDE companion ship on different channels and can't flip atomically, the FIRST release of `current_mode_update` promotion publishes **both** the promoted `approval_mode_changed` AND the legacy generic `session_update{sessionUpdate:'current_mode_update'}` for one release cycle. The IDE companion's existing `case 'current_mode_update'` keeps working; once its `approval_mode_changed` handler ships, the next release drops the dual-emit. `current_model_update` is brand-new (no legacy consumer) so it promotes directly without dual-emit. **Removal is enforced, not left to memory:** a `TODO(dual-emit-removal)` comment at the dual-emit publish site references this section, and §7 step 3 carries a tracking issue with a target release — so the redundant generic wrapper can't silently become permanent (and no new consumer should build on it).
- **Observability (required, not optional):** emit a structured log at every demux decision — `[demux] session=<id> type=<sub> action=promoted|dropped|suppressed|generic reason=<why>`. `BridgeClient.sessionUpdate()` has zero logging today; the `dropped` case especially must be visible so oncall can distinguish "agent didn't emit" / "demux dropped" / "SSE lost".

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] Best-effort dedup is specified in §2.1 and §8 but not implemented in the shipped A1 code (#4546). BridgeClientSessionEntry (bridgeClient.ts:199-213) does not expose currentModelId, handleInSessionModelUpdate (bridgeClient.ts:573-614) performs only roundtrip-in-flight suppression (no value-equality guard), and publishModelSwitched (bridge.ts:1819-1834) unconditionally publishes even for identical values.

Redundant notifications produce spurious bus events and unnecessary generation bumps, which can trigger reconciliation churn via the generation guard rerun path.

Suggested fix: Either add the dedup guard to the implementation, or mark it as deferred in the design doc with rationale.

— qwen3.7-max via Qwen Code /review

---

## 2. A1 — in-session model switch on the bus

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] "Suppression is meaningful only for the model path" is misleading when read at the section level. This is true for the HTTP path specifically (no agent notification fires there), but §3 explains that in-session approval-mode changes do need their own suppress window — it's security-relevant (/approval-mode yolo dropped during bridge roundtrip = bus shows default while actual mode is yolo).

Suggested fix: Scope the statement: "For the HTTP path, suppression is meaningful only for the model path; in-session approval-mode changes still need their own suppress window (§3)."

— qwen3.7-max via Qwen Code /review

- **A5 snapshot (§5):** read all three fields in one synchronous block — the cache's primary purpose.
- **Best-effort demux dedup (§2.1):** drop a `current_model_update` whose `currentModelId` already equals `entry.currentModelId`.
- **`previous` derivation (A1/A2):** the demux fills `approval_mode_changed.previous` from `entry.currentApprovalMode` _captured before_ applying the new value — so **the agent never sends `previousModeId` / `previousModelId`** (sidesteps the ACP `CurrentModeUpdate` schema having no `previousModeId` field).
- **NOT a consumer: §2.2 reconciliation.** Reconciliation needs the agent's _true_ model, which the cache can't provide (it never sees dropped suppressed notifications); reconciliation uses the authoritative `getSessionContextStatus` read instead (§2.2, v9). The cache reflects only what was _published_.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] The reconciliation_failed payload lacks an axis discriminator (model vs approval-mode). Once A2 reconciliation ships (step 3c), consumers receiving this event cannot tell which state axis is affected, making warnings unactionable.

Suggested fix: Add axis: 'model' | 'approval-mode' to the payload now so the schema is stable before A2 lands.

— qwen3.7-max via Qwen Code /review

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Design review (verified against daemon_mode_b_main and main). The technical content is sound and corroborated by the actual daemon-branch implementation (the extNotification demux, publishModelSwitched chokepoint, generation-guarded reconciliation, and replay_complete sentinel all exist on the branch). The reconciliation logic holds up on inspection, and the doc is commendably honest about the residual zombie-roundtrip race it does not close. Inline notes below are about the PR as an artifact (merge base, framing, changelog bloat, two anchor nits) — none affect the design's correctness. Approve with minor cleanup.

@@ -0,0 +1,398 @@
# Daemon side-channel coordination — Design (A1 / A2 / A4 / A5)

> Targets `daemon_mode_b_main` (per #4175 branching strategy). Author: 秦奇. Date: 2026-05-25. Revised: 2026-05-27 (v13 — zombie-gap doc, reconciliation_failed contract, availableCommands spec, §7 atomic-coupling, §8 bounded-call-count).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Merge-base vs anchor-target mismatch. This PR's base is main, but every code anchor in the doc (bridge.ts, bridgeClient.ts, permissionMediator.ts, acp.d.ts, replay_complete) exists only on daemon_mode_b_main — none are present on main. Co-locating design docs on main is the established convention here (docs/design/ already has a channels/ subdir), so this is defensible, but the "Targets daemon_mode_b_main" line should be reconciled with the actual merge base — or the doc should land on the daemon branch alongside the code it annotates.

# Daemon side-channel coordination — Design (A1 / A2 / A4 / A5)

> Targets `daemon_mode_b_main` (per #4175 branching strategy). Author: 秦奇. Date: 2026-05-25. Revised: 2026-05-27 (v13 — zombie-gap doc, reconciliation_failed contract, availableCommands spec, §7 atomic-coupling, §8 bounded-call-count).
> **Docs-only / design-first.** A4 implemented + approved (#4539); A1 implemented (#4546).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Framing drift between doc and PR body. This line states A4 (#4539) and A1 (#4546) are already implemented, but the PR description says "No implementation … before any code lands. Each item ships as its own implementation PR after this is approved." The doc is design-first for A2/A5 but retroactive for A1/A4. Please update the PR body so reviewers aren't reviewing already-merged decisions as if they were still open.

>
> Source: cross-client real-time sync audit (2026-05-24) + PR #4484 post-merge review (the **A-series** follow-ups). The bugfix/cleanup follow-ups from the same review ship separately (PR #4510) and are **out of scope here**.

## Changelog

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changelog is ~90 of 398 lines (≈23%). Twelve rounds (v2–v13) of internal-review history will be frozen into the repo on a doc that has never been on main — git history already captures revisions. Several entries narrate corrections to earlier drafts of this same unmerged doc (e.g. v10 "§8 test contradicted v9"; v12 "stale-but-equal scenario was self-contradictory"). Recommend squashing to a short status block (current decisions + open items) before merge and leaving the round-by-round narrative in the PR thread.

session_snapshot { approvalMode, model, availableCommands? }
```

- **`replay_complete` already exists** (`eventBus.ts:444`, shipped by merged #4484) — A5 phase 2 introduces only the new `session_snapshot` frame, not `replay_complete`.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anchor rot. replay_complete does exist (good — the claim is correct), but it's at eventBus.ts:516 on daemon_mode_b_main, not :444 (the :437 neighbourhood is the false-caught-up comment). With ~40 file:line anchors in a long-lived doc this drift is inevitable; lean on the symbol name when symbol and line disagree.

- **Mitigation (§2.1 dual-emit):** the first release emits BOTH the legacy generic `session_update{current_mode_update}` AND the promoted `approval_mode_changed`; the IDE companion keeps working on the legacy frame; once its `approval_mode_changed` path ships, the next release drops the dual-emit. A4 (`voterClientId`) and A5 (opt-in frame) ARE additive (no transition needed).
- **Failure events stay bridge-only** (`model_switch_failed`).
- **Concurrent-in-session drift** is bounded by §2.2 post-roundtrip reconciliation.
- **SDK reducer updates** (naming, to avoid the A1/A2 mix-up): A1 introduces **`current_model_update`** → `model.changed`; A2 promotes **`current_mode_update`** → `approval_mode_changed`; A4 adds optional `voterClientId`; A5 seeds side-channel state from `session.snapshot`.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Naming inconsistency — the exact "A1/A2 mix-up" this line claims to prevent. A1 here maps current_model_updatemodel.changed, while every other section (§2.1 promotion table, §8) maps it to the bus event model_switched; A2 in this same list uses the bus name approval_mode_changed. Either make A1 read model_switched for parity, or state explicitly that model.changed is the SDK-typed reducer event distinct from the model_switched bus event.

@doudouOUC doudouOUC left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Design Doc Review

Summary

问题分析深刻,对竞态/TOCTOU/排序的覆盖非常细致,v1→v13 迭代痕迹完整。但文档已被代码实现超越,存在多处事实性失准,需要更新后才能合并。

Critical

1. A2 transport 与文档不一致

文档 §1.1/§2.1/§6/§9 坚持 A2 走 ACP sessionUpdate 通道,但实际实现中:

  • Session.setMode 发出的是 extNotification('qwen/notify/session/mode-update', ...)
  • BridgeClientextNotification() 中 demux(注释明确标注 "A2: promote an in-session current_mode_update extNotification")

这意味着 A1 和 A2 实际上都在 extNotification() 中处理——是对称的,而非文档所述的"设计上保留的非对称性"。

建议: 增加 v14 changelog,说明 A2 也改用了 extNotification(与 A1 对称),并同步 §1.1/§2.1/§6/§9。

2. "BLOCKED on PR #4510" 已过期

文档多处将 A2 标记为硬阻塞于 #4510,但 approvalModeQueue 已在 bridge.ts 中定义、初始化、使用并有测试覆盖。前置已满足,文档未更新会误导接手者。

建议: §7 step 3 替换为"前置已满足(#4510 已合入)"。

High

3. 行号锚点系统性失效

文档大量引用精确行号(如 bridge.ts:2883Session.ts:1561),但与当前 HEAD 对比几乎全部偏离 300~1000+ 行。设计文档对行号依赖很高,失效会让落地者必须二次定位。

建议: 批量修正行号,或改用方法名/节路径锚点(如 BridgeClient.extNotification() 内的 model-update 分支),至少标注取自哪个 commit SHA。

4. §0 httpAcpBridge.ts 路径不存在

packages/cli/src/serve/httpAcpBridge.ts 在当前仓库中找不到。请核对路径或删除该说明。

5. §10 长期方案 vs 当前设计矛盾

文档同时主张必须原子落地 reconciliation 复杂栈,又推荐 serialize-at-source 方案。建议在 §7 明确这套中间方案的维护期上限或 exit criteria。

Medium

  • §2.1 规则引用 "third rule" 但未显式编号 1)/2)/3)
  • §5 first-attach vs resume 的客户端判定算法未显式说明
  • §3 helper generalization 仍以未来时态描述,但实现已绕过该 helper

Low

  • Author 字段不一致(文档 "秦奇" vs PR 元数据 "ChiGao")
  • v2~v13 changelog 占 90 行,建议折叠早期版本到附录
  • 缺少 sequence diagram,398 行文本涉及 4 个发布点 + 5+ 个竞态,一张时序图可显著降低认知成本

核心修复项:更新 A2 transport 描述 + 解除 #4510 阻塞标记 + 修正行号锚点。完成后即可 approve。

- **A2 — `BridgeClient.sessionUpdate()` (`bridgeClient.ts:397`):** the `current_mode_update` sessionUpdate → `approval_mode_changed`. This method today publishes every notification verbatim as `{ type: 'session_update', data: params }`; the demux is added here.

The rules below apply at whichever insertion point the sub-type arrives:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Critical] A2 transport contradicts the shipped code. The doc repeatedly states A2 uses BridgeClient.sessionUpdate() for the demux (§0, §1.1 item 2b, §2.1, §6, §9 table). The shipped code uses extNotification() for both A1 and A2: Session.setMode sends extNotification('qwen/notify/session/mode-update', ...) (Session.ts:2520-2529) and the demux is in BridgeClient.extNotification() via handleInSessionModeUpdate (bridgeClient.ts:480-481). The "two insertion points / different transports" architecture described throughout the document does not exist.

Anyone reading the doc to implement follow-ups will look in the wrong method. The §2.1 demux contract's two-insertion-point framing is incorrect.

— qwen3.7-max via Qwen Code /review

4. Bridge publishes the authoritative `model_switched(A)`; **bus shows A, session runs B — nothing reconciles.**

**Contract (v9/v10/v11 — authoritative read, generation-guarded, non-recursive):** reconciliation fires when a bridge model roundtrip settles — on **both** the success and failure paths (a `.finally` on the roundtrip, since the timeout/failure case is exactly when the bus is most likely diverged). It reads the agent's **true** current model via `getSessionContextStatus` (`bridge.ts:2784`, async `extMethod`) and, if it diverges from the bus's current model (`entry.currentModelId` — on the failure path this is the **pre-roundtrip** value, since `model_switch_failed` does not update the cache), emits a corrective `model_switched` via `publishModelSwitched`. **Why not the §2.3 cache _as truth_:** the cache is updated only at publish sites, so it can't observe a concurrent in-session change the demux **dropped** — reading it would falsely conclude "no divergence". The agent is the only source of truth. The read is async but runs **post-settle, outside the demux**, so the §5 synchronous-block constraint doesn't apply. (Longer-term: route in-session `/model` through `modelChangeQueue` — §10 Q3 — to make this race-free at the source.) The same reconciliation applies to A2 once `approvalModeQueue` exists.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Critical] Failure-path reconciliation is described but not implemented. This section states reconciliation fires on "both the success and failure paths (a .finally on the roundtrip)" and calls the failure case "exactly when the bus is most likely diverged." The shipped code gates reconciliation on if (succeeded) at all three sites (bridge.ts:1563-1569, :3637-3644, :3866-3875), explicitly skipping reconciliation on failure with action=skipped reason=roundtrip_failed. The zombie roundtrip gap (§2 item 4, v13) is a direct consequence: without failure-path reconciliation, a timed-out model that completes late is never corrected.

Either implement failure-path reconciliation (remove the if (succeeded) guard), or update the doc to state reconciliation fires on success only and acknowledge the zombie roundtrip gap is a consequence of this decision.

— qwen3.7-max via Qwen Code /review


### Risk / compat

Additive wire (`current_mode_update` reuse + `previousModeId` + `scope`) but **not** SDK-additive for the promoted type (see §6). Hard-blocked on #4510.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Critical] Internal contradiction on previousModeId. This line says "Additive wire (current_mode_update reuse + previousModeId + scope)" but §3 point 3 (line 248) explicitly resolves that the agent does NOT send previousModeId — the bridge derives previous from its state cache. An implementor reading this line would add previousModeId to the wire format, contradicting the detailed design.

Suggested change
Additive wire (`current_mode_update` reuse + `previousModeId` + `scope`) but **not** SDK-additive for the promoted type (see §6). Hard-blocked on #4510.
Additive wire (`current_mode_update` reuse + `scope`) but **not** SDK-additive for the promoted type (see §6). Hard-blocked on #4510.

— qwen3.7-max via Qwen Code /review


The staleness check (§2 item 4), §2.2 reconciliation, and A5's snapshot (§5) all need the session's **current** model / approval-mode / commands. The bridge had no synchronous accessor — only `getSessionContextStatus` (`bridge.ts:2784` → `requestSessionStatus`, an async `extMethod` roundtrip), and an `await` there reopens the very TOCTOU window these mechanisms close. So:

- Add to `SessionEntry`: `currentModelId?: string`, `currentApprovalMode?: ApprovalMode`, `availableCommands?: AvailableCommand[]`.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Critical] SessionEntry field declaration is incomplete. This bullet lists three fields (currentModelId, currentApprovalMode, availableCommands) but omits modelPublishGeneration and reconciliationInFlight, both required by §2.2. An implementor following this field list would miss the generation guard and the structural non-recursion guard, causing the §2.2 TOCTOU protection and re-entry protection to be absent.

— qwen3.7-max via Qwen Code /review


- **Payload (v13):** `reconciliation_failed { sessionId: string, error: string, retryCount: number, trigger: 'roundtrip-settled' | 'failed' }`. The `error` distinguishes "agent process crashed" from "JSON-RPC timeout" for consumer UX and oncall diagnostics.
- **Consumer contract:** advisory — clients MAY surface a transient warning and MAY trigger their own `getSessionContextStatus` pull to self-heal. No mandatory handler; absent consumers, the bus state remains as-last-published (stale but non-terminal).
- **Per-attempt logging:** each retry attempt emits its own log line: `[reconcile] session=<id> attempt=<n>/<max> error=<msg>`, so oncall can distinguish transient from sustained failure without needing the final aggregated event.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Critical] reconciliationInFlight structural guard has no timeout. The flag is set before the async read and cleared in .finally. If getSessionContextStatus hangs indefinitely (agent stuck, transport half-open), the flag stays true forever and all future reconciliation is skipped (action=skipped-reentrant). The bounded-retry path covers error responses but not hangs. The authoritative correctness backstop is permanently disabled with no recovery.

Add a per-attempt timeout to the reconciliation getSessionContextStatus call (the bridge already uses withTimeout for model roundtrips), or clear reconciliationInFlight with a maximum-hold timer.

— qwen3.7-max via Qwen Code /review

3. **`model_switch_failed` stays bridge-only** — `Session.setModel` throws with no notification; the bridge keeps publishing it on both failure paths.
4. **Timeout-race (best-effort demux drop + authoritative reconciliation backstop — v9).** The bridge's `withTimeout` (`bridge.ts:2844-2849`) can reject (publishing `model_switch_failed(A)`) while A's ACP call keeps running (FIXME `bridge.ts:2836-2840`). If a change B then succeeds (`model_switched(B)`) and A's call finally completes, A's late `current_model_update(A)` must not make A the apparent final state. **Value comparison alone can't decide** this (a late stale `A` and a fresh switch to `A` look identical — a distributed-ordering problem). So: the demux does a **best-effort dedup** (drops a `current_model_update` whose `currentModelId` already equals `entry.currentModelId` — a redundant no-op), and the **authoritative correctness comes from §2.2 reconciliation**: a timed-out earlier change always corresponds to a _settled bridge roundtrip_, which triggers a post-settle authoritative read that re-publishes the agent's true model. No agent-side sequence counter required.

**Residual gap — zombie roundtrip (v13).** Reconciliation covers the _first_ settlement (the timeout), but a zombie ACP call that completes **after** reconciliation has already fired `action=converged` is NOT covered: the agent applies the timed-out model late → emits `current_model_update(A)` → demux promotes it (no roundtrip in flight, not a dup) → bus silently reverts to A, contradicting the user's successful switch to B. The long-term fix is an ACP cancel signal (the existing FIXME at `bridge.ts:2836-2840`). Until then this is a **known residual race** under the narrow condition: timeout fires, reconciliation converges (agent hasn't applied yet), user successfully switches to B, THEN the zombie completes. Likelihood is low (requires the agent to take longer than the timeout + reconciliation read + a subsequent successful switch), but it is not zero. Document it here rather than claim reconciliation fully eliminates the timeout race.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] The zombie roundtrip residual race (§2 item 4, v13) has no corresponding test scenario in §8. The design documents a specific sequence (timeout → reconciliation converges → user switches to B → zombie completes → bus reverts) but §8 omits it entirely. Without a test, there is no regression guard when the ACP cancel fix eventually lands.

Add a §8 bullet asserting the current (broken) behavior as a documented test case, to be updated when the fix lands.

— qwen3.7-max via Qwen Code /review


- Add to `SessionEntry`: `currentModelId?: string`, `currentApprovalMode?: ApprovalMode`, `availableCommands?: AvailableCommand[]`.
- **Update synchronously at every publish site**, in the same synchronous turn as the publish (no `await` between read-of-old and write-of-new): all `model_switched` publishes go through the §2.2 `publishModelSwitched` helper (which atomically updates `entry.currentModelId` + bumps `entry.modelPublishGeneration` + publishes to bus); `approval_mode_changed` (`:2979` / `:3007`) updates `entry.currentApprovalMode`; `availableCommands` is updated in `BridgeClient.sessionUpdate()` when it receives an `available_commands_update` generic sessionUpdate — the handler sets `entry.availableCommands = payload.commands` synchronously **before** the generic forwarding publish. The helper guarantees no publish site can miss a cache or generation update.
- **`availableCommands` specifics (v13):** type is `AvailableCommand[]` (matching `status.ts`). Unlike model/mode, this field has **no named promoted bus event** and **no reconciliation** — it's a passive cache, updated by the generic `session_update` path. If the implementer misses the hook, A5's snapshot serves stale/undefined commands with no backstop. The trigger path is explicitly `BridgeClient.sessionUpdate()` → check `params.type === 'available_commands_update'` → update cache → forward as generic `session_update`.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] availableCommands cache has no reconciliation backstop, no test, and is not yet implemented in SessionEntry. Unlike model/mode (which have authoritative reconciliation), this passive cache field has a single population hook with no staleness detection. §8 has no test for cache population or snapshot inclusion.

Either add availableCommands to the reconciliation read (cheap — the status call already returns it), or add a §8 test and explicitly document undefined as a legal snapshot value.

— qwen3.7-max via Qwen Code /review


### Double-emit edge

`/approval-mode` during an open tool-confirmation dialog can fire two `current_mode_update` within ms (user `setMode` + the tool's `ProceedAlways` handler). Acceptable (converges); optionally skip emit when the resulting mode equals current. Documented, not gated.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] A2 prerequisite text and §7 status markers are stale. approvalModeQueue already exists in the codebase (bridge.ts:262-263), A4 is shipped (#4539), and A1 is shipped (#4546). The "Hard prerequisite" section overstates what the queue alone achieves (reconciliation is the actual divergence closer — see §7 step 3c). §7 step 1 doesn't mark A4 as completed.

Update status markers: add "(Shipped as #4539)" to §7 step 1, update §3 to note the queue has landed, and restate A2's status as unblocked.

— qwen3.7-max via Qwen Code /review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants