Skip to content

fix(web-shell): surface loop detection turn errors - #8853

Merged
ytahdn merged 18 commits into
mainfrom
fix/web-shell-loop-detection-error
Aug 14, 2026
Merged

fix(web-shell): surface loop detection turn errors#8853
ytahdn merged 18 commits into
mainfrom
fix/web-shell-loop-detection-error

Conversation

@ytahdn

@ytahdn ytahdn commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

This PR turns foreground tool-loop protection stops into structured turn errors, preserves the existing failed tool cards, and gives Web Shell users localized guidance that the current turn stopped while the session remains usable. Loop errors do not offer a retry action, and the current error is retained when a live session is reloaded from paginated history.

Why it's needed

Loop protection previously completed the turn normally after marking unstarted tools as failed. Web Shell therefore exposed an internal English tool error without clearly explaining that only the turn stopped. Reloading the page could also discard the turn error because paginated persisted history replaced the live in-memory terminal event.

Reviewer Test Plan

How to verify

  1. Start Web Shell and trigger the per-turn tool-call safety cap.
  2. Confirm the existing tool cards remain present and collapsed by default.
  3. Confirm a localized error explains that the model was stopped by loop protection, the session remains open, the retry action is absent, and the composer remains enabled.
  4. Refresh the page without restarting the daemon and confirm the error remains visible.
  5. Send a more specific follow-up and confirm the session continues normally.
  6. Verify cancellation still wins if it races loop termination and that cron/background notification behavior is unchanged.

Evidence (Before & After)

Before: the turn appeared completed and the visible explanation was the internal skipped-tool error; refreshing removed the turn error.

After: Web Shell shows localized turn-level guidance while retaining normal tool cards; refreshing a live session retains the current error, and the next prompt can continue.

Tested on

OS Status
macOS ✅ Tested
Windows Not tested
Linux Not tested

Environment (optional)

Verified with the daemon development server and Web Shell on localhost. Automated verification includes the full build, typecheck, lint, all Bridge tests, targeted CLI loop/cancellation tests, affected Web Shell tests, and the WebUI replay regression test.

Risk & Scope

  • Main risk or tradeoff: foreground loop-protection stops now use the existing error terminal instead of a successful end-turn terminal.
  • Not validated / out of scope: deterministic browser reproduction depends on model behavior; cron/background notifications and persisted historical turn errors are unchanged.
  • Breaking changes / migration notes: None. New error metadata is optional and older clients fall back to the daemon message.

Linked Issues

N/A

Pictures

Before

image

After

image
中文说明

本 PR 做了什么

本 PR 将前台工具循环保护导致的停止转换为结构化的轮次错误,保留现有的失败工具卡片,并在 Web Shell 中用本地化文案说明当前轮次已停止、会话仍可继续。循环错误不提供重试操作;当 live session 从分页历史重新加载时,当前错误也会继续保留。

为什么需要这个改动

此前循环保护会先将尚未执行的工具标记为失败,然后把本轮正常结束。Web Shell 因此只展示内部英文工具错误,无法明确告诉用户只有当前轮次被停止。刷新页面时,分页持久化历史还会替换内存中的终止事件,导致轮次错误消失。

Reviewer 测试计划

验证方式

  1. 启动 Web Shell,并触发单轮工具调用安全上限。
  2. 确认原有工具卡片仍正常展示且默认收起。
  3. 确认本地化错误说明模型被循环保护停止、会话仍然可用、没有重试入口,并且输入框仍可使用。
  4. 不重启 daemon,直接刷新页面,确认错误仍然可见。
  5. 发送一个更明确的后续指令,确认会话可以正常继续。
  6. 验证取消操作与循环终止同时发生时仍以取消为准,并确认 cron/后台通知行为没有变化。

前后效果

改动前:本轮看起来是正常完成的,用户只能看到内部的“工具被跳过”错误;刷新后轮次错误会消失。

改动后:Web Shell 在保留正常工具卡片的同时展示本地化轮次提示;刷新 live session 后当前错误仍然保留,下一条指令可以继续执行。

测试平台

系统 状态
macOS ✅ 已测试
Windows 未测试
Linux 未测试

环境(可选)

已通过 daemon 开发服务和 localhost Web Shell 完成人工验证。自动化验证包括完整构建、类型检查、lint、全部 Bridge 测试、CLI 循环/取消相关测试、受影响的 Web Shell 测试以及 WebUI replay 回归测试。

风险与范围

  • 主要风险或取舍:前台循环保护停止现在使用现有的错误终止事件,而不是成功的轮次结束事件。
  • 未验证/范围外:浏览器中的确定性复现依赖模型行为;cron/后台通知以及历史轮次错误持久化不在本次改动范围内。
  • 破坏性变更/迁移说明:无。新增错误元数据均为可选字段,旧客户端会回退展示 daemon 提供的消息。

关联 Issue

@ytahdn

ytahdn commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator Author

E2E test report

  • Platform: macOS
  • Environment: npm run dev:daemon, Web Shell on localhost
  • Triggered the foreground per-turn tool-call safety cap and observed a structured loop-detection turn error.
  • Confirmed existing tool cards remained visible with their normal collapsed-by-default behavior.
  • Confirmed Web Shell displayed the localized explanation, omitted the retry action, and kept the composer enabled.
  • Refreshed the page without restarting the daemon and confirmed the current turn error remained visible.
  • Sent a follow-up prompt after the refresh and confirmed the same session completed the next turn normally.
  • Stopped the development service after verification.

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

⚠️ Deferred approval withheld — 1 PR CI workflow run(s) on 59c5ccb did not finish green; see the updated table in the Stage 2 comment. Re-run @qwen-code /triage after fixes. finalize run

⚠️ 延迟审批已搁置 —— 59c5ccb 有 1 个 PR CI workflow 未以绿色完成,详见 Stage 2 评论中已更新的表格。修复后可重新运行 @qwen-code /triage查看 finalize 运行

@ytahdn
ytahdn requested a review from wenshao August 10, 2026 08:43
@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Thanks for the PR!

Template looks good ✓

Problem: observed, not theoretical. The per-turn loop-protection stop is a real runtime behavior, and the PR shows before/after screenshots of what Web Shell renders today (the internal skipped-tool error as the only explanation, and the turn error lost on page refresh), plus a macOS E2E walk-through in this thread. That is enough evidence that the problem exists.

Direction: aligned. The tool-call safety caps are an existing mechanism; turning their stops into a localized, non-retryable turn error instead of a silent end_turn is squarely a UX-correctness fix for the Web Shell surface. No direct CHANGELOG reference, but runaway-loop caps are an active area in adjacent agents (session-wide WebSearch caps, subagent spawn caps), and explaining those stops to the user is exactly this problem.

Size: cross-package change (cli, acp-bridge, sdk-typescript, web-shell) → core-infrastructure paths, so the two-tier gate applies. ~126 production logic lines vs ~336 test lines vs 21 docs lines — well under the 500-line escalation threshold. No size concerns.

Approach: the scope feels right. Reusing the existing turn_error terminal with a new loop_detected kind, instead of inventing a new event type, is the minimal backward-compatible path, and each layer of the event flow gets a matching change plus tests. One thing I'll check in code review: the stop-message emission removed from the shared #buildNextMessageAfterToolRun helper also served the cron/background callers — more on that below.

Risk: packages/cli/src/acp-integration/session/Session.ts matches the revert-correlated high-risk pattern (acp-integration), so this one gets full-depth review: all enrichments, CI evidence required before approval, and a named sandboxed lane for the behavioral claim.

Moving on to code review. 🔍

中文说明

感谢贡献!

模板完整 ✓

问题:已观测到,不是理论性问题。单轮循环保护停止是真实存在的运行时行为,PR 提供了 before/after 截图(Web Shell 目前只展示内部的"工具被跳过"错误、刷新后轮次错误丢失),并在本帖中附上了 macOS 手工 E2E 验证记录。问题存在的证据充分。

方向:对齐。工具调用安全上限是既有机制;把它的停止从静默 end_turn 改为本地化、不可重试的轮次错误,属于 Web Shell 界面的 UX 正确性修复。CHANGELOG 无直接条目,但失控循环上限在相邻 agent 中是活跃领域(会话级 WebSearch 上限、子 agent 派生上限),如何向用户解释这类停止正是本 PR 解决的问题。

规模:跨包改动(cliacp-bridgesdk-typescriptweb-shell)→ 属于核心基础设施路径,双层门禁适用。约 126 行生产逻辑 vs 约 336 行测试 vs 21 行文档——远低于 500 行升级阈值,无规模顾虑。

方案:范围合理。复用既有 turn_error 终止事件并新增 loop_detected 类型(而不是发明新事件类型)是最小的向后兼容路径,事件流的每一层都有对应改动和测试。代码审查中需要确认一点:从共享的 #buildNextMessageAfterToolRun 中移除的停止消息发送,原本也服务于 cron/后台调用方——详见下文。

风险:packages/cli/src/acp-integration/session/Session.ts 命中与 revert 相关的高风险模式(acp-integration),因此按最大深度审查:完整 enrichments、批准前必须有 CI 证据、并为行为性声明指定沙箱验证通道。

进入代码审查 🔍

Qwen Code · qwen3.8-max

Reviewed at 59c5ccb779084162eb812311f8fdd9456154889a · re-run with @qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

🖼️ web-shell visual preview

Rendered against a mock daemon (no real backend): the PR base vs this PR head f291d23. Only screenshots that changed are shown (flows below, if any, are head-only) — refreshes on every push.

Screenshots · before / after

ℹ️ No screenshot changed against the PR base — but this PR edits 2 render-shaping files:

  • packages/web-shell/client/App.tsx
  • packages/web-shell/client/i18n.tsx

Either the change has no visual effect (logic, plumbing, a state the scenarios never reach), or no scenario renders this UI — in which case the preview cannot see it, and an empty result is a coverage gap rather than a clean bill of health. To make it visible, add a scenario to packages/web-shell/client/e2e/visuals/screenshots.spec.ts that seeds whatever state the UI is gated on; it then appears here as a head-only (NEW) capture.

Full-resolution recordings (.webm) are attached to the workflow run.

Qwen Code · web-shell visuals

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

Code review

The layering matches what this fix needs, and it follows the flow I would have proposed independently: Session throws a structured RequestError when loop protection stops a foreground turn → the bridge lifts errorKind / code / loopType out of the JSON-RPC error data into the existing turn_error terminal → the SDK types gain the new kind additively → Web Shell localizes the message and suppresses retry. Reusing the existing terminal event instead of adding a parallel mechanism is the right call, and older daemons/clients fall back cleanly (message-based classification is kept as the fallback).

Correctness checks I ran against the diff and base code:

  • Structured errorKind takes precedence over message classification; loopType is recorded at first detection; every throw site checks pendingSend.signal.aborted first, so cancellation still wins the race (the deleted stop-message race test is replaced by a stronger drain-gate version).
  • getAbortAwareEndTurnStopReason is still used by the remaining callers — no dead code introduced.
  • Skipped tools keep their failed tool_call_update cards (asserted for all 101), the model history keeps the System: stop note, and the session is proven reusable by the follow-up prompt test.
  • errorKind is in scope in the web-shell adapter; retry is suppressed in both places that matter (the App.tsx retryable-block scan and the adapter's retryable flag); EN+ZH strings both present.

Two non-blocking questions:

  1. Shared helper serves cron/background too. Removing the emitAgentMessage(REPEATED_TOOL_FAILURE_STOP_MESSAGE) emission from #buildNextMessageAfterToolRun is correct for the foreground paths (they now throw the structured error instead), but that helper is also called by the cron prompt path and the background-notification path, which keep the old "preserve the stopped run, end the turn" behavior. Those turns previously emitted the "Automatic continuation stopped" explanation into the transcript; now they end without it (model context and tool cards are still preserved, so nothing functional is lost). The PR description says cron/background behavior is unchanged — strictly, their transcript content differs in enforce mode. Intentional? If so, a one-line design-doc note would settle it; if not, keep the emission on the non-interactive callers.
  2. Refresh-append edge. The bounded-refresh path appends the in-memory current turn_error to the persisted page. entry.turnError only ever holds the active turn's failure (cleared when the next prompt starts) and terminal events are not part of persisted history — consistent with the bug being fixed — so the common refresh path looks right and the new bridge test covers it. Worth a quick confirmation that a persisted page can never already contain that same turn_error (e.g. after a flush); otherwise a later refresh could render it twice.

Nothing blocking found.

sequenceDiagram
    participant P1 as Session (cli)
    participant P2 as Bridge (acp-bridge)
    participant P3 as Web Shell
    P1->>P1: loop cap trips, unstarted tools marked failed
    P1->>P2: prompt rejects with RequestError (LOOP_DETECTED, loopType)
    P2->>P3: turn_error event with errorKind loop_detected
    P3->>P3: localized guidance, no retry, composer stays enabled
    P3->>P2: page refresh reloads the live session
    P2->>P3: persisted page plus appended in-memory turn_error
Loading
Files changed (14 of 14 shown)
File What changed
docs/design/web-shell-loop-detection-turn-error.md New design doc: problem, approach, compatibility notes
packages/acp-bridge/src/bridge.test.ts Two new tests: structured loop details ride the turn_error event; the current turn error survives a refresh from persisted history
packages/acp-bridge/src/bridge.ts Reads structured errorKind/code/loopType from the JSON-RPC error data; appends the in-memory current turn error to the persisted replay page on bounded refresh
packages/cli/src/acp-integration/session/Session.test.ts Cap and repeated-failure stops now expect a structured rejection; skipped-tool updates preserved; session reusable afterwards; cancellation-wins race reworked
packages/cli/src/acp-integration/session/Session.ts Records loopType; foreground loop stops throw a structured RequestError instead of returning end_turn; cancellation precedence kept; drops the repeated-failure stop-message emission
packages/sdk-typescript/src/daemon/events.ts Adds optional loopType to the turn-error data type
packages/sdk-typescript/src/daemon/types.ts Adds loop_detected to the daemon error kinds
packages/web-shell/client/App.test.tsx Asserts no retry action and an enabled composer for a loop-detected turn error
packages/web-shell/client/App.tsx Skips the retryable-turn-error selection when errorKind is loop_detected
packages/web-shell/client/adapters/transcriptToMessages.test.ts Structured loop error renders the localized label and is not retryable
packages/web-shell/client/adapters/transcriptToMessages.ts loop_detected uses the localized label; turn_error retryability excludes loop_detected
packages/web-shell/client/hooks/useMessages.test.ts Covers the localized loop label wiring
packages/web-shell/client/hooks/useMessages.ts Passes the loopDetected label into the adapter
packages/web-shell/client/i18n.tsx EN and ZH strings for error.loopDetected

Testing evidence

This is an unattended CI run — no PR code was built or executed by the reviewer; the evidence below is the PR's own CI on the reviewed commit, fetched via the API. As of this review there are no red checks: the main unit suite and the web-shell visual capture are still running, and the completed checks are green (desktop shell builds on ubuntu/windows, live host on macOS, SDK Java across Java 11/17/21). The author's macOS E2E walk-through in this thread is their claim, not reviewer evidence.

Final CI results for 59c5ccb (auto-updated by the triage finalize job after CI completed):

Check Conclusion
Test (ubuntu-latest, Node 22.x) ❌ failure
Capture web-shell visuals (ubuntu-latest, Node 22.x) ✅ success
Classify PR ✅ success
Desktop Shell (ubuntu-22.04) ✅ success
Desktop Shell (windows-2022) ✅ success
Live Host (macos-latest) ✅ success
macos-latest / Java 21 ✅ success
Post Coverage Comment (ubuntu-latest, 22.x) ✅ success
Real daemon E2E / Java 11 ✅ success
review-scan ✅ success
route ✅ success
takeover-ack ✅ success
ubuntu-latest / Java 11 ✅ success
ubuntu-latest / Java 17 ✅ success
ubuntu-latest / Java 21 ✅ success
web-shell E2E Smoke (ubuntu-latest, Node 22.x) ✅ success
windows-latest / Java 21 ✅ success

One row per check name (latest run); skipped checks omitted; failures sort first. / 每个检查名一行(取最新一次运行),省略 skipped,失败项排在最前。

Not verified: browser-side rendering of the localized error and the refresh-retention behavior — the unit suite pins the wiring (the new tests fail without the change), but no automated check drives a real Web Shell session.

Sandboxed verification would settle this: @qwen-code /verify — that Web Shell actually renders the localized loop error without a retry action and retains it across a page refresh is the PR's central behavioural claim and rests on the author's macOS-only manual run; @qwen-code /tmux is also available if a maintainer wants a live-session walk-through of the cap trigger and refresh flow.

中文说明

代码审查

改动分层与本修复所需完全一致,也与我独立设想的方案一致:Session 在循环保护停止前台轮次时抛出结构化 RequestError → bridge 从 JSON-RPC 错误的 data 中提取 errorKind / code / loopType,挂到既有的 turn_error 终止事件上 → SDK 类型以向后兼容方式新增该类型 → Web Shell 展示本地化文案并隐藏重试。复用既有终止事件而不是新增并行机制是正确的选择,旧版 daemon/客户端也有干净的兜底(基于消息的分类保留为 fallback)。

对照 diff 与基线代码完成的正确性检查:结构化 errorKind 优先于消息分类;loopType 在首次检测时记录;每个 throw 点都先检查 pendingSend.signal.aborted,取消仍然优先(被删除的"停止消息发送时取消"测试由更强的 drain-gate 版本替代);getAbortAwareEndTurnStopReason 仍有其他调用方,未产生死代码;被跳过的工具保留失败卡片(断言了全部 101 个)、模型历史保留 System: 停止说明、后续 prompt 测试证明会话可继续使用;web-shell adapter 中 errorKind 在作用域内,重试在两处关键位置(App.tsx 的可重试块扫描与 adapter 的 retryable 标志)都被抑制;中英文文案齐全。

两个非阻塞问题:其一,从共享的 #buildNextMessageAfterToolRun 中移除停止消息发送,对前台路径是正确的(改为抛出结构化错误),但该 helper 同样被 cron 与后台通知路径调用——这两条路径保留旧的"保留工具运行、正常结束轮次"行为,其转录中将不再出现"Automatic continuation stopped"解释(模型上下文与工具卡片不受影响)。PR 描述称 cron/后台行为不变,严格说其转录内容有差异,请确认是否有意为之。其二,bounded-refresh 追加逻辑把内存中的当前 turn_error 附加到持久化分页之后;entry.turnError 只保存当前轮次的错误(下一个 prompt 启动时清除),且终止事件不进入持久化历史,常见刷新路径正确且有新测试覆盖,但值得确认持久化分页不可能已包含同一条 turn_error(例如 flush 之后),避免更晚的刷新出现重复展示。未发现阻塞问题。

测试证据

本次为无人值守 CI 运行——审查方未构建或执行任何 PR 代码,以下证据为 PR 自身 CI 在被审 commit 上的结果(经 API 获取)。截至目前无红色检查:主单元测试与 web-shell 视觉截图仍在运行,已完成检查均为绿色。作者在帖中的 macOS 手工验证是其声明,不构成审查证据。未验证项:浏览器侧的本地化错误渲染与刷新保留行为——单元测试固定了接线(新测试在缺少改动时会失败),但没有自动化检查驱动真实 Web Shell 会话。维护者可用 @qwen-code /verify 做沙箱行为验证,或用 @qwen-code /tmux 做真实会话走查。

Qwen Code · qwen3.8-max

Reviewed at 59c5ccb779084162eb812311f8fdd9456154889a · re-run with @qwen-code /triage

@github-actions

github-actions Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Code Coverage Summary

Package Lines Statements Functions Branches
CLI 84.17% 84.17% 90.02% 83.4%
Core 87.97% 87.97% 89.53% 86.51%
CLI Package - Full Text Report
-------------------|---------|----------|---------|---------|-------------------
File               | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-------------------|---------|----------|---------|---------|-------------------
All files          |   84.17 |     83.4 |   90.02 |   84.17 |                   
 src               |   85.71 |    81.87 |   87.93 |   85.71 |                   
  cli.ts           |   95.68 |    84.11 |     100 |   95.68 | ...60-561,565-566 
  gemini.tsx       |   73.75 |    79.33 |   80.76 |   73.75 | ...1319-1323,1450 
  ...ractiveCli.ts |   87.92 |    82.11 |    88.7 |   87.92 | ...3097,3103,3168 
  ...liCommands.ts |   88.64 |    82.96 |      80 |   88.64 | ...77-579,593,692 
  ...ActiveAuth.ts |     100 |     87.5 |     100 |     100 | 66-80             
 ...cp-integration |   71.79 |    74.19 |   91.49 |   71.79 |                   
  acpAgent.ts      |   71.18 |       74 |   91.03 |   71.18 | ...59,12664-12666 
  ...k-reporter.ts |     100 |       80 |     100 |     100 | 81,84,119,141     
  authMethods.ts   |      92 |       60 |     100 |      92 | 33-34             
  errorCodes.ts    |     100 |      100 |     100 |     100 |                   
  ...ion-skills.ts |     100 |    88.23 |     100 |     100 | 17,32             
  generation.ts    |    97.1 |    81.25 |     100 |    97.1 | 109,112           
  ...figuration.ts |     100 |      100 |     100 |     100 |                   
  ...DirContext.ts |     100 |      100 |     100 |     100 |                   
 ...ration/service |    97.1 |    95.83 |   93.33 |    97.1 |                   
  filesystem.ts    |    97.1 |    95.83 |   93.33 |    97.1 | ...22-123,246-247 
 ...ration/session |   91.07 |    86.62 |   96.28 |   91.07 |                   
  Session.ts       |   90.37 |    85.03 |   95.86 |   90.37 | ...93,11520-11524 
  ...entTracker.ts |    96.8 |    89.36 |      90 |    96.8 | 137-143,221       
  ...projection.ts |   98.85 |    91.59 |     100 |   98.85 | 234,250,262       
  ...stop-guard.ts |     100 |    98.07 |     100 |     100 | 37,127            
  ...eplay-page.ts |   94.46 |    90.62 |     100 |   94.46 | ...05-108,135-145 
  ...y-replayer.ts |   83.17 |    92.98 |   94.11 |   83.17 | ...24-142,260-262 
  index.ts         |       0 |        0 |       0 |       0 | 1-40              
  ...ssionUtils.ts |   89.76 |    87.32 |     100 |   89.76 | ...54-270,326-328 
  ...oal-update.ts |   98.61 |    97.29 |     100 |   98.61 | 64                
  ...lure-guard.ts |   98.32 |    97.72 |     100 |   98.32 | 294-295,340-341   
  tasksSnapshot.ts |    94.3 |     87.5 |     100 |    94.3 | 65-71             
  ...on-tracker.ts |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...ssion/emitters |    95.7 |    93.37 |   96.87 |    95.7 |                   
  ...ageEmitter.ts |   95.25 |    93.54 |     100 |   95.25 | ...08-115,128-129 
  PlanEmitter.ts   |     100 |       90 |     100 |     100 | 66                
  base-emitter.ts  |   78.26 |       75 |     100 |   78.26 | 23-24,26-28       
  index.ts         |       0 |        0 |       0 |       0 | 1-10              
  ...ll-emitter.ts |   99.18 |    96.47 |     100 |   99.18 | 355-356           
 ...ession/rewrite |    91.8 |    89.13 |   94.44 |    91.8 |                   
  LlmRewriter.ts   |    82.4 |     86.2 |     100 |    82.4 | ...,88-89,166-170 
  ...Middleware.ts |   96.96 |    88.09 |     100 |   96.96 | 144,152-154       
  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/agent-view    |   89.03 |    81.37 |   89.09 |   89.03 |                   
  ...t-cli-argv.ts |     100 |      100 |     100 |     100 |                   
  protocol.ts      |     100 |      100 |     100 |     100 |                   
  ...sor-client.ts |   80.38 |    72.54 |   76.66 |   80.38 | ...22-626,652-656 
  ...or-process.ts |   96.61 |    89.47 |   84.61 |   96.61 | 129-130,150-151   
  ...sor-runner.ts |    84.9 |     75.6 |      85 |    84.9 | ...44,468,471-481 
  ...sor-server.ts |   85.71 |    83.06 |   95.45 |   85.71 | ...67-468,471-488 
  ...isor-store.ts |   97.73 |    81.16 |     100 |   97.73 | ...92,594,607,643 
  ...nal-bridge.ts |   93.98 |     91.3 |   83.33 |   93.98 | 228-238           
 src/commands      |   90.96 |       80 |   66.66 |   90.96 |                   
  auth.ts          |     100 |    83.33 |     100 |     100 | 11,14             
  channel.ts       |   55.55 |      100 |       0 |   55.55 | 18-22,30-40       
  extensions.tsx   |   96.77 |      100 |      50 |   96.77 | 39                
  hooks.tsx        |   66.66 |      100 |       0 |   66.66 | 20-24             
  mcp.ts           |   95.45 |      100 |      50 |   95.45 | 31                
  review.ts        |   98.66 |      100 |      50 |   98.66 | 86                
  serve.ts         |   90.08 |    77.84 |     100 |   90.08 | ...81,884-887,899 
  sessions.ts      |     100 |      100 |      50 |     100 |                   
  update.ts        |   98.13 |    94.44 |   66.66 |   98.13 | 82-83             
 ...mmands/channel |   88.95 |    88.55 |   90.59 |   88.95 |                   
  channel-cwd.ts   |     100 |      100 |     100 |     100 |                   
  ...l-registry.ts |   94.11 |    95.45 |   88.88 |   94.11 | ...54-257,302-305 
  ...entry-path.ts |      75 |       50 |     100 |      75 | 8-9               
  config-utils.ts  |   95.87 |    96.35 |     100 |   95.87 | ...08-213,271-274 
  configure.ts     |    14.7 |      100 |       0 |    14.7 | 18-21,23-84       
  daemon-worker.ts |   93.91 |    85.55 |   94.33 |   93.91 | ...1264,1271-1272 
  loop-runtime.ts  |   91.66 |      100 |      50 |   91.66 | 15,22             
  ...classifier.ts |   98.53 |    96.66 |     100 |   98.53 | 115-116,161       
  ...tact-store.ts |   93.51 |    87.65 |     100 |   93.51 | ...71,288-289,337 
  pairing.ts       |      75 |      100 |      50 |      75 | 22-28,59-70       
  pidfile.ts       |   95.55 |       90 |     100 |   95.55 | ...50-251,315-316 
  proxy.ts         |     100 |      100 |     100 |     100 |                   
  reload.ts        |    77.5 |    86.95 |      75 |    77.5 | 72-84,93-97       
  runtime.ts       |   82.43 |    86.44 |     100 |   82.43 | ...87-191,251-253 
  set.ts           |   75.72 |    85.71 |      50 |   75.72 | 65-83,111-116     
  start.ts         |    85.8 |    82.17 |      88 |    85.8 | ...85,591-594,606 
  ...ure-format.ts |   93.65 |    82.45 |     100 |   93.65 | ...42,48-49,74-75 
  status.ts        |   78.57 |    59.25 |   66.66 |   78.57 | ...36-137,150-161 
  stop.ts          |   57.83 |    82.35 |      50 |   57.83 | ...3,74-76,85-111 
 ...nds/extensions |   88.85 |    87.91 |   87.09 |   88.85 |                   
  consent.ts       |   72.53 |    90.32 |   42.85 |   72.53 | ...86-142,157-163 
  disable.ts       |     100 |       90 |     100 |     100 | 30                
  enable.ts        |     100 |    91.66 |     100 |     100 | 38                
  install.ts       |   82.95 |    81.57 |      75 |   82.95 | ...96-199,202-211 
  link.ts          |     100 |      100 |     100 |     100 |                   
  list.ts          |     100 |     90.9 |     100 |     100 | 18                
  new.ts           |     100 |      100 |     100 |     100 |                   
  settings.ts      |   99.15 |      100 |   83.33 |   99.15 | 151               
  sources.ts       |   93.42 |    87.09 |   92.85 |   93.42 | ...4-66,96-98,167 
  uninstall.ts     |   74.57 |       40 |   66.66 |   74.57 | 45-47,60-67,70-73 
  update.ts        |   96.71 |    97.05 |     100 |   96.71 | 114-118           
  utils.ts         |   75.63 |    57.14 |     100 |   75.63 | ...30-134,136-140 
 ...les/mcp-server |       0 |        0 |       0 |       0 |                   
  example.ts       |       0 |        0 |       0 |       0 | 1-60              
 ...amples/starter |       0 |        0 |       0 |       0 |                   
  example.ts       |       0 |        0 |       0 |       0 | 1-64              
 src/commands/mcp  |   90.31 |    84.61 |   83.33 |   90.31 |                   
  add.ts           |    99.3 |    96.07 |     100 |    99.3 | 154-155           
  approve.ts       |   76.19 |     87.5 |   66.66 |   76.19 | ...,89-99,114-124 
  list.ts          |   93.15 |    84.84 |      80 |   93.15 | ...78-180,198-199 
  reconnect.ts     |   78.85 |    66.66 |   85.71 |   78.85 | 42-55,169-191     
  remove.ts        |     100 |       80 |     100 |     100 | 21-25             
 ...ommands/review |   88.08 |    88.25 |   88.72 |   88.08 |                   
  agent-prompt.ts  |   93.75 |     92.3 |   97.22 |   93.75 | ...2538,2663-2743 
  base-tree.ts     |   76.16 |    80.76 |   77.77 |   76.16 | ...50-371,373-386 
  capture-local.ts |   68.57 |     90.9 |      75 |   68.57 | 107-111,158-189   
  ...k-coverage.ts |   50.71 |       35 |   66.66 |   50.71 | ...40-245,279-289 
  cleanup.ts       |   89.12 |    82.22 |   83.33 |   89.12 | ...99-504,506-507 
  ...ent-status.ts |   93.03 |    83.87 |   83.33 |   93.03 | 291,531-551       
  ...ose-review.ts |   97.02 |    93.27 |   96.55 |   97.02 | ...2224,2252-2274 
  cost-ledger.ts   |   94.67 |    95.86 |   78.57 |   94.67 | ...04-505,545-555 
  drive.ts         |   76.07 |    85.71 |   81.81 |   76.07 | ...90-492,497-499 
  extract-step.ts  |   91.36 |    90.62 |   88.88 |   91.36 | ...90-707,714-729 
  fetch-pr.ts      |    76.7 |    68.75 |   63.63 |    76.7 | ...95,417,450-455 
  findings.ts      |   89.89 |    89.56 |   95.65 |   89.89 | ...77-980,989-990 
  load-rules.ts    |   26.41 |      100 |   16.66 |   26.41 | ...41-153,155-156 
  match-remote.ts  |   85.54 |     92.3 |   66.66 |   85.54 | 67-72,131-136     
  mock-provider.ts |   95.44 |    90.25 |   89.47 |   95.44 | 145,690-709       
  parse-args.ts    |    99.7 |    96.94 |     100 |    99.7 | 458               
  plan-diff.ts     |   64.04 |      100 |   66.66 |   64.04 | 127-163           
  pr-context.ts    |   81.77 |    81.32 |   92.85 |   81.77 | ...1043,1072-1074 
  presubmit.ts     |   87.93 |       88 |   88.88 |   87.93 | ...98-599,676-706 
  ...ish-assets.ts |   80.72 |    82.35 |   85.71 |   80.72 | ...58-462,489-535 
  repo-context.ts  |   94.92 |    90.82 |     100 |   94.92 | ...67-368,376-377 
  ...ve-anchors.ts |   77.77 |    88.88 |      75 |   77.77 | ...77-182,194-211 
  run.ts           |   82.16 |    87.12 |   91.66 |   82.16 | ...52,468-516,529 
  save-artifact.ts |    89.9 |    81.81 |   94.11 |    89.9 | ...08-311,404-407 
  script-lint.ts   |   83.67 |    78.41 |   88.88 |   83.67 | ...59-773,775-797 
  submit.ts        |   85.01 |    86.36 |      90 |   85.01 | ...99,588,615-651 
  test-delta.ts    |   87.13 |    91.46 |      75 |   87.13 | 206-237,477-485   
  test-efficacy.ts |   88.04 |    84.12 |   95.45 |   88.04 | ...2602,2610-2630 
  test-plan.ts     |   91.44 |    91.39 |   89.47 |   91.44 | ...38-839,903-920 
 ...w/__fixtures__ |     100 |      100 |     100 |     100 |                   
  ...r-default.mjs |     100 |      100 |     100 |     100 |                   
  ...der-empty.mjs |     100 |      100 |     100 |     100 |                   
  ...der-named.mjs |     100 |      100 |     100 |     100 |                   
 ...nds/review/lib |   97.52 |    95.18 |   98.33 |   97.52 |                   
  agent-briefs.ts  |      99 |      100 |      50 |      99 | 747-748           
  anchors.ts       |     100 |    94.79 |     100 |     100 | ...33,169,178,225 
  assets.ts        |     100 |      100 |     100 |     100 |                   
  audit-layers.ts  |   98.67 |    96.15 |     100 |   98.67 | 277-279           
  authorization.ts |   93.02 |    94.11 |     100 |   93.02 | 152-158           
  budget.ts        |     100 |    97.14 |     100 |     100 | 513,553           
  coverage.ts      |   96.81 |    94.42 |     100 |   96.81 | ...85-486,526-537 
  deadline.ts      |   98.33 |    93.61 |     100 |   98.33 | ...88,237,629,661 
  diff-flags.ts    |     100 |        0 |     100 |     100 | 63                
  diff-plan.ts     |   98.73 |    93.01 |     100 |   98.73 | ...41,264,290-291 
  disk.ts          |     100 |      100 |     100 |     100 |                   
  effort.ts        |     100 |      100 |     100 |     100 |                   
  gh.ts            |   87.07 |    92.45 |   76.47 |   87.07 | ...72,309-310,337 
  git.ts           |   97.64 |    95.65 |     100 |   97.64 | 180-181           
  heavy.ts         |     100 |      100 |     100 |     100 |                   
  inline-counts.ts |     100 |      100 |     100 |     100 |                   
  ...audit-gate.ts |     100 |    96.15 |     100 |     100 | 110               
  ledger.ts        |     100 |      100 |     100 |     100 |                   
  local-diff.ts    |   84.86 |    90.38 |     100 |   84.86 | ...63-473,475-483 
  ...ry-context.ts |   96.61 |    95.51 |     100 |   96.61 | ...47-450,496-499 
  merge-base.ts    |     100 |      100 |     100 |     100 |                   
  npm-toolchain.ts |   97.36 |    95.37 |     100 |   97.36 | ...86,409,770,787 
  path-rules.ts    |     100 |      100 |     100 |     100 |                   
  paths.ts         |     100 |     87.5 |     100 |     100 | 92                
  prompt-record.ts |   97.88 |    93.87 |     100 |   97.88 | 260-261,267       
  receipt.ts       |     100 |      100 |     100 |     100 |                   
  remote-match.ts  |   97.26 |    91.42 |     100 |   97.26 | 49-50             
  report.ts        |   94.68 |    93.75 |     100 |   94.68 | 189-193           
  ...ry-context.ts |     100 |    98.66 |     100 |     100 | 187               
  retirement.ts    |     100 |    92.39 |     100 |     100 | ...28,308-309,449 
  review-footer.ts |     100 |      100 |     100 |     100 |                   
  ...w-settings.ts |     100 |      100 |     100 |     100 |                   
  roster.ts        |     100 |    95.71 |     100 |     100 | 145,163,208       
  shell-quote.ts   |     100 |      100 |     100 |     100 |                   
  stale-bundle.ts  |   98.11 |    94.11 |     100 |   98.11 | 416,457,497-498   
  test-utils.ts    |     100 |      100 |     100 |     100 |                   
  toolchain.ts     |     100 |      100 |     100 |     100 |                   
  transcripts.ts   |   96.59 |    94.62 |     100 |   96.59 | ...08,297-298,323 
  ...pace-scope.ts |     100 |    96.96 |     100 |     100 | 172               
  workspaces.ts    |     100 |    96.77 |     100 |     100 | 222,452,499,512   
  worktree.ts      |     100 |      100 |     100 |     100 |                   
 ...mands/sessions |   91.56 |    86.95 |   83.33 |   91.56 |                   
  common.ts        |     100 |      100 |     100 |     100 |                   
  list.ts          |   90.96 |    86.66 |   81.81 |   90.96 | 208-219,221-222   
 src/config        |   94.93 |    89.87 |   96.28 |   94.93 |                   
  ...l-fallback.ts |     100 |      100 |     100 |     100 |                   
  auth.ts          |   89.35 |    83.56 |     100 |   89.35 | ...97-298,314-315 
  ...eMcpImport.ts |   87.91 |    81.52 |     100 |   87.91 | ...63-371,453-454 
  compile-cache.ts |     100 |      100 |     100 |     100 |                   
  config.ts        |   89.12 |    88.69 |   83.78 |   89.12 | ...2497,2499-2507 
  ...cy-monitor.ts |   88.75 |    76.19 |     100 |   88.75 | ...3,90-92,98,101 
  ...ust-policy.ts |   83.02 |    88.88 |     100 |   83.02 | ...02-209,232-240 
  ...heme-names.ts |     100 |      100 |     100 |     100 |                   
  environment.ts   |    96.5 |    93.58 |      95 |    96.5 | ...85-586,640-641 
  ...le-watcher.ts |   90.86 |    83.65 |   95.83 |   90.86 | ...23-325,370,418 
  ...resh-state.ts |   90.57 |    97.29 |   93.75 |   90.57 | 137-142,146-152   
  ...ime-reload.ts |     100 |    69.69 |     100 |     100 | ...12-113,122-123 
  hot-reload.ts    |     100 |    89.13 |     100 |     100 | 47,172-178,238    
  keyBindings.ts   |    97.4 |       50 |     100 |    97.4 | 240-243           
  ...ngsAdapter.ts |     100 |    94.11 |     100 |     100 | 64                
  ...ig-watcher.ts |   95.17 |    83.05 |     100 |   95.17 | ...78,200,292-293 
  ...er-secrets.ts |   98.97 |    96.96 |     100 |   98.97 | 85                
  mcpApprovals.ts  |   96.55 |    95.55 |     100 |   96.55 | 223-224,229-231   
  mcpJson.ts       |     100 |      100 |     100 |     100 |                   
  mcpServers.ts    |   92.85 |     87.5 |     100 |   92.85 | 46-47             
  ...idersScope.ts |      95 |    94.73 |     100 |      95 | 11-12             
  ...abledTools.ts |     100 |      100 |     100 |     100 |                   
  ...comparison.ts |     100 |      100 |     100 |     100 |                   
  ...n-settings.ts |   99.15 |    93.75 |     100 |   99.15 | 63                
  sandboxConfig.ts |   93.33 |    93.33 |     100 |   93.33 | ...42-147,216-217 
  session-id.ts    |     100 |      100 |     100 |     100 |                   
  ...ings-cache.ts |   96.52 |    93.93 |     100 |   96.52 | 90-91,201-202     
  settings.ts      |   91.27 |    92.64 |      90 |   91.27 | ...1030,1032-1033 
  ...ingsSchema.ts |     100 |      100 |     100 |     100 |                   
  ...ngsWatcher.ts |   95.54 |    88.34 |     100 |   95.54 | ...28,277-278,293 
  ...d-env-keys.ts |     100 |      100 |     100 |     100 |                   
  ...l-settings.ts |     100 |      100 |     100 |     100 |                   
  ...paths-lite.ts |   89.47 |       88 |     100 |   89.47 | 43-44,53-54,56-57 
  ...precedence.ts |   98.79 |     92.3 |     100 |   98.79 | 62                
  ...tedFolders.ts |   92.53 |    93.47 |     100 |   92.53 | ...36-337,373-384 
 ...nfig/migration |   95.23 |    77.77 |   83.33 |   95.23 |                   
  index.ts         |   95.65 |     87.5 |     100 |   95.65 | 117-118           
  scheduler.ts     |   96.55 |    77.77 |     100 |   96.55 | 19-20             
  types.ts         |       0 |        0 |       0 |       0 | 1                 
 ...ation/versions |   94.91 |      100 |     100 |   94.91 |                   
  ...-v2-shared.ts |     100 |      100 |     100 |     100 |                   
  v1-to-v2.ts      |   81.75 |      100 |     100 |   81.75 | ...28-229,231-247 
  v2-to-v3.ts      |     100 |      100 |     100 |     100 |                   
  v3-to-v4.ts      |     100 |      100 |     100 |     100 |                   
  v5-to-v4.ts      |      96 |      100 |     100 |      96 | 94-95,99          
 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    |   75.08 |    67.64 |   71.42 |   75.08 |                   
  ...tputBridge.ts |   75.33 |    68.18 |   73.68 |   75.33 | ...09-410,418-421 
  ...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/hooks         |     100 |      100 |     100 |     100 |                   
  ...elete-hook.ts |     100 |      100 |     100 |     100 |                   
 src/i18n          |   85.98 |    81.92 |   89.65 |   85.98 |                   
  index.ts         |   73.45 |    77.77 |      90 |   73.45 | ...70-271,294-299 
  languages.ts     |   93.07 |     92.3 |   85.71 |   93.07 | ...35,164-169,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 |   80.98 |    77.27 |   84.12 |   80.98 |                   
  session.ts       |   84.97 |    76.31 |   96.07 |   84.97 | ...1048,1057-1067 
  types.ts         |    42.5 |      100 |   33.33 |    42.5 | ...31-632,635-636 
 ...active/control |   75.54 |    89.83 |      80 |   75.54 |                   
  ...rolContext.ts |    6.06 |        0 |       0 |    6.06 | 57-99             
  ...Dispatcher.ts |   91.95 |    92.98 |   88.88 |   91.95 | ...54-372,392,395 
  ...rolService.ts |    6.89 |        0 |       0 |    6.89 | 46-188            
 ...ol/controllers |   45.95 |    69.03 |   55.26 |   45.95 |                   
  ...Controller.ts |    42.4 |      100 |   83.33 |    42.4 | 101-105,140-223   
  ...Controller.ts |       0 |        0 |       0 |       0 | 1-56              
  ...Controller.ts |   55.01 |    67.14 |   58.33 |   55.01 | ...15-624,639-644 
  ...Controller.ts |   49.23 |       60 |      50 |   49.23 | ...07-108,111-121 
  ...Controller.ts |   40.64 |    68.11 |   46.66 |   40.64 | ...72-684,693-722 
 .../control/types |       0 |        0 |       0 |       0 |                   
  serviceAPIs.ts   |       0 |        0 |       0 |       0 | 1                 
 ...Interactive/io |   98.12 |    94.18 |   95.29 |   98.12 |                   
  ...putAdapter.ts |   97.98 |    93.23 |   98.07 |   97.98 | ...1416,1432-1433 
  ...putAdapter.ts |      96 |    91.66 |   85.71 |      96 | 51-52             
  ...nputReader.ts |     100 |    94.73 |     100 |     100 | 67                
  ...putAdapter.ts |   98.49 |      100 |   90.47 |   98.49 | 85-86,126-127     
  ...projection.ts |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
 src/patches       |       0 |        0 |       0 |       0 |                   
  is-in-ci.ts      |       0 |        0 |       0 |       0 | 1-17              
 src/remoteInput   |   87.31 |    75.32 |   88.23 |   87.31 |                   
  ...utContext.tsx |     100 |      100 |     100 |     100 |                   
  ...putWatcher.ts |   88.01 |       76 |   93.33 |   88.01 | ...49-350,361-364 
  index.ts         |       0 |        0 |       0 |       0 | 1-8               
 src/runtime       |   99.61 |    95.04 |     100 |   99.61 |                   
  ...livery-ipc.ts |     100 |     90.9 |     100 |     100 | 94,106,134        
  ...l-delivery.ts |     100 |      100 |     100 |     100 |                   
  cpu-percent.ts   |     100 |      100 |     100 |     100 |                   
  ...erver-name.ts |     100 |      100 |     100 |     100 |                   
  ...-constants.ts |     100 |      100 |     100 |     100 |                   
  ...-summaries.ts |   86.66 |       50 |     100 |   86.66 | 11,19             
  ...ber-errors.ts |     100 |    95.32 |     100 |     100 | 53,93-94,172,192  
  ...ls-mapping.ts |     100 |      100 |     100 |     100 |                   
 src/serve         |   87.67 |    83.95 |   90.55 |   87.67 |                   
  ...tp-enabled.ts |     100 |      100 |     100 |     100 |                   
  ...ion-bridge.ts |     100 |      100 |     100 |     100 |                   
  auth.ts          |   93.43 |    92.95 |     100 |   93.43 | ...20-321,324-326 
  ...em-adapter.ts |     100 |      100 |     100 |     100 |                   
  capabilities.ts  |     100 |    98.07 |     100 |     100 | 685               
  ...cp-command.ts |     100 |      100 |     100 |     100 |                   
  ...horization.ts |   92.79 |    93.33 |    87.5 |   92.79 | 75-80,135-136     
  ...op-mcp-ipc.ts |   81.06 |    73.68 |   94.11 |   81.06 | ...37-242,267,289 
  ...nt-service.ts |    94.1 |    86.89 |     100 |    94.1 | ...75-477,484,486 
  ...-selection.ts |     100 |      100 |     100 |     100 |                   
  ...ings-store.ts |   88.59 |    93.65 |   96.29 |   88.59 | ...95-207,451-454 
  ...ebhook-ipc.ts |    98.5 |    86.66 |     100 |    98.5 | 47                
  ...iagnostics.ts |     100 |      100 |     100 |     100 |                   
  ...worker-env.ts |     100 |      100 |     100 |     100 |                   
  ...rker-group.ts |   87.27 |     85.2 |     100 |   87.27 | ...10,816-820,838 
  ...er-manager.ts |   89.39 |    83.88 |   93.33 |   89.39 | ...98,711,722-724 
  ...horization.ts |     100 |      100 |     100 |     100 |                   
  ...tartup-ipc.ts |   97.72 |    96.66 |     100 |   97.72 | 88-89             
  ...supervisor.ts |   92.54 |    84.53 |   97.14 |   92.54 | ...1489,1543-1547 
  ...e-grouping.ts |     100 |    94.28 |     100 |     100 | 71,137            
  core-runtime.ts  |     100 |      100 |     100 |     100 |                   
  ...ub-session.ts |    90.1 |    77.83 |   94.73 |    90.1 | ...1014,1021-1026 
  daemon-logger.ts |   82.82 |    78.68 |   92.04 |   82.82 | ...1775,1802-1808 
  ...y-pressure.ts |     100 |    96.96 |     100 |     100 | 135               
  ...trics-ring.ts |     100 |      100 |     100 |     100 |                   
  ...s-provider.ts |   68.04 |    52.77 |     100 |   68.04 | ...44-249,282-290 
  daemon-status.ts |   98.58 |    90.88 |     100 |   98.58 | ...1438,1440-1441 
  debug-mode.ts    |     100 |      100 |     100 |     100 |                   
  env-snapshot.ts  |   93.37 |    85.18 |     100 |   93.37 | 114-117,195-202   
  ...-scheduler.ts |   87.34 |    83.87 |     100 |   87.34 | 33-36,48-50,79-81 
  ...d-provider.ts |   92.06 |    86.95 |     100 |   92.06 | ...72,287-293,316 
  ...-path-argv.ts |     100 |      100 |     100 |     100 |                   
  ...h-settings.ts |   94.94 |    90.41 |     100 |   94.94 | ...30,708,724,734 
  fast-path.ts     |   90.99 |    81.38 |   95.45 |   90.99 | ...33-542,608-609 
  ...ration-sse.ts |   42.55 |    33.33 |     100 |   42.55 | 23-24,30,33-56    
  health-query.ts  |     100 |      100 |     100 |     100 |                   
  index.ts         |       0 |        0 |       0 |       0 | 1-144             
  ...e-observer.ts |   89.89 |    83.24 |      96 |   89.89 | ...11-512,541-543 
  ...back-binds.ts |     100 |    88.88 |     100 |     100 | 32                
  ...-workspace.ts |    90.9 |    85.71 |     100 |    90.9 | ...30-131,142-143 
  ...iders-edit.ts |     100 |    82.14 |     100 |     100 | 58-60,65,81       
  ...ory-picker.ts |     100 |    86.95 |     100 |     100 | 36,66,92          
  ...sion-audit.ts |     100 |      100 |   93.33 |     100 |                   
  rate-limit.ts    |   92.68 |    88.29 |     100 |   92.68 | ...89-291,303-305 
  ...qwen-serve.ts |   84.27 |    80.34 |    75.6 |   84.27 | ...7571,7577-7578 
  ...tup-errors.ts |     100 |      100 |     100 |     100 |                   
  ...-keepalive.ts |   94.25 |    87.96 |     100 |   94.25 | ...28,532-533,572 
  ...-lifecycle.ts |     100 |      100 |     100 |     100 |                   
  server.ts        |   90.96 |    91.34 |   72.32 |   90.96 | ...2731,2745-2749 
  ...-admission.ts |   98.24 |     94.8 |     100 |   98.24 | 79-80,303-304     
  ...on-helpers.ts |     100 |      100 |     100 |     100 |                   
  ...t-event-id.ts |     100 |    95.23 |     100 |     100 | 12                
  ...-admission.ts |   98.71 |    89.65 |     100 |   98.71 | 68                
  types.ts         |     100 |      100 |     100 |     100 |                   
  ...ion-limits.ts |     100 |      100 |     100 |     100 |                   
  ...t-sessions.ts |   93.72 |    77.93 |     100 |   93.72 | ...51,854,867-869 
  ...l-resolver.ts |   90.32 |    66.66 |     100 |   90.32 | 16,45-46          
  ...ell-static.ts |   92.18 |    88.37 |     100 |   92.18 | ...21-224,267-270 
  ...ace-agents.ts |   66.13 |    70.57 |   92.68 |   66.13 | ...2246,2256-2266 
  ...generation.ts |    95.4 |    82.35 |   66.66 |    95.4 | 55-56,78,92       
  ...-git-state.ts |     100 |    91.93 |    90.9 |     100 | 161,172,202,265   
  ...ace-inputs.ts |     100 |      100 |     100 |     100 |                   
  ...ace-memory.ts |      83 |    74.54 |     100 |      83 | ...30-537,597-604 
  ...ers-status.ts |   98.58 |       79 |     100 |   98.58 | 106,134,174,177   
  ...tion-store.ts |   89.67 |    88.27 |   92.59 |   89.67 | ...91-400,411-414 
  ...e-registry.ts |   93.89 |     87.5 |     100 |   93.89 | ...18-519,525-526 
  ...e-remember.ts |   98.23 |    92.56 |     100 |   98.23 | ...36,340-345,386 
  ...te-runtime.ts |   83.98 |    90.29 |     100 |   83.98 | ...48-156,216-237 
  ...me-storage.ts |     100 |      100 |     100 |     100 |                   
  ...management.ts |   72.63 |    72.72 |      96 |   72.63 | ...88-889,896-900 
  ...lls-status.ts |     100 |    95.45 |     100 |     100 | 152               
  ...reconciler.ts |   91.63 |    84.09 |     100 |   91.63 | ...71-273,306-307 
 ...serve/acp-http |   78.26 |    80.02 |    93.1 |   78.26 |                   
  ...r-registry.ts |   96.92 |    94.87 |     100 |   96.92 | 184-187           
  client-mcp-ws.ts |   54.85 |    58.62 |   72.72 |   54.85 | ...99-300,304-305 
  ...n-registry.ts |    98.2 |    88.55 |     100 |    98.2 | 1015,1041-1052    
  dispatch.ts      |   73.87 |    77.71 |   94.23 |   73.87 | ...5240,5288-5294 
  index.ts         |   82.23 |    80.11 |   91.07 |   82.23 | ...2341,2425-2426 
  json-rpc.ts      |     100 |    96.96 |     100 |     100 | 92                
  safe-ws-send.ts  |   52.94 |    71.42 |     100 |   52.94 | 33-42,47-55       
  sse-stream.ts    |   93.96 |    88.57 |   84.61 |   93.96 | ...57-159,161-163 
  ...ort-stream.ts |       0 |        0 |       0 |       0 | 1                 
  ws-stream.ts     |   91.86 |       80 |     100 |   91.86 | 45,50,96,100-103  
 src/serve/auth    |   86.86 |     79.7 |   93.87 |   86.86 |                   
  device-flow.ts   |   96.35 |    80.57 |   97.61 |   96.35 | ...1358,1453,1519 
  ...w-provider.ts |   44.24 |    74.07 |   71.42 |   44.24 | ...23-284,297,301 
 ...rve/cdp-tunnel |   87.73 |    76.21 |    97.5 |   87.73 |                   
  ...r-emulator.ts |   93.27 |    77.77 |     100 |   93.27 | ...53-256,282-283 
  ...verse-link.ts |      88 |    76.19 |     100 |      88 | ...28-329,420-423 
  ...l-registry.ts |     100 |      100 |     100 |     100 |                   
  cdp-ws.ts        |   76.28 |    61.29 |    87.5 |   76.28 | ...13-217,223-228 
 ...nel/acceptance |    6.12 |    57.89 |   46.15 |    6.12 |                   
  ...helpers.d.mts |       0 |        0 |       0 |       0 | 1                 
  ...e-helpers.mjs |   97.64 |    70.96 |     100 |   97.64 | 22-23             
  ...mcp-smoke.mjs |       0 |        0 |       0 |       0 | 1-124             
  ...cceptance.mjs |       0 |        0 |       0 |       0 | 1-473             
  ...re-server.mjs |       0 |        0 |       0 |       0 | 1-59              
  ...ols-smoke.mjs |       0 |        0 |       0 |       0 | 1-268             
  real-tab.mjs     |       0 |        0 |       0 |       0 | 1-218             
  ...al-chrome.mjs |       0 |        0 |       0 |       0 | 1-223             
 .../conversations |   92.81 |    90.51 |     100 |   92.81 |                   
  ...me-manager.ts |     100 |      100 |     100 |     100 |                   
  ...-workspace.ts |   88.26 |    82.53 |     100 |   88.26 | ...33-234,246-247 
  ...ion-source.ts |     100 |      100 |     100 |     100 |                   
 src/serve/fs      |   87.27 |    82.01 |     100 |   87.27 |                   
  audit.ts         |     100 |    96.15 |     100 |     100 | 204               
  errors.ts        |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...x-registry.ts |     100 |      100 |     100 |     100 |                   
  paths.ts         |   77.64 |    74.01 |     100 |   77.64 | ...65,594-598,611 
  policy.ts        |   90.52 |    89.18 |     100 |   90.52 | 172-180           
  text-cursor.ts   |   88.23 |       90 |     100 |   88.23 | 74-77,92-95       
  ...ile-system.ts |   87.37 |    81.39 |     100 |   87.37 | ...2811,2821-2822 
 src/serve/live    |   77.56 |    68.68 |   90.08 |   77.56 |                   
  ...en-context.ts |   95.74 |    81.25 |     100 |   95.74 | ...0,66-67,99-100 
  discovery.ts     |   85.77 |    76.92 |      90 |   85.77 | ...49-250,255-256 
  ...structions.ts |     100 |      100 |     100 |     100 |                   
  ...oordinator.ts |   82.67 |    76.75 |   97.01 |   82.67 | ...1319,1351-1353 
  ...-installer.ts |    64.3 |    82.35 |   80.76 |    64.3 | ...45-446,460-472 
  ...oordinator.ts |   75.99 |    65.18 |   85.71 |   75.99 | ...1883,1974-1975 
  ...controller.ts |   67.82 |    79.31 |   72.72 |   67.82 | ...66-278,287-295 
  ...ak-to-user.ts |   96.66 |      100 |   83.33 |   96.66 | 37-38             
  ...sk-service.ts |    86.3 |    59.78 |   93.33 |    86.3 | ...1160,1184-1191 
  ...task-tools.ts |      99 |      100 |   85.71 |      99 | 205-206           
  ...redentials.ts |   96.26 |    93.47 |     100 |   96.26 | 91-94             
  ...me-session.ts |   65.63 |    57.24 |   88.88 |   65.63 | ...2270,2275-2282 
  ...up-context.ts |   94.83 |    77.58 |     100 |   94.83 | ...18,327-330,350 
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/serve/routes  |   85.79 |    80.39 |   94.91 |   85.79 |                   
  a2ui-action.ts   |   96.84 |     88.5 |    87.5 |   96.84 | ...70-272,309-311 
  capabilities.ts  |   98.63 |    95.65 |     100 |   98.63 | 76                
  ...nel-notify.ts |   86.45 |       88 |     100 |   86.45 | ...,83-87,103-104 
  ...l-webhooks.ts |   93.56 |    84.09 |     100 |   93.56 | ...42,292,332,334 
  daemon-status.ts |   85.71 |    83.33 |     100 |   85.71 | 101-108           
  goals.ts         |   98.92 |     90.9 |     100 |   98.92 | 143               
  health.ts        |   99.09 |    91.17 |     100 |   99.09 | 147               
  live-setup.ts    |   33.33 |     37.5 |      50 |   33.33 | ...18-123,130-135 
  live.ts          |    82.4 |    71.42 |     100 |    82.4 | ...-94,96-101,121 
  permission.ts    |     100 |     92.3 |     100 |     100 | 50,98             
  ...uled-tasks.ts |   87.29 |    82.94 |   92.59 |   87.29 | ...1275,1318-1319 
  ...on-runtime.ts |     100 |    90.47 |     100 |     100 | 58,94             
  session.ts       |   85.86 |     82.4 |   91.66 |   85.86 | ...4969,4971-4972 
  sse-events.ts    |   86.82 |    85.71 |   94.11 |   86.82 | ...16-927,930,937 
  usage-stats.ts   |     100 |    95.45 |     100 |     100 | 118               
  ...space-auth.ts |   85.55 |    75.64 |     100 |   85.55 | ...21-326,331,345 
  ...el-control.ts |   86.26 |    78.94 |     100 |   86.26 | ...17-318,339-347 
  ...management.ts |   90.92 |    79.69 |     100 |   90.92 | ...81-482,501-502 
  ...d-contacts.ts |     100 |      100 |     100 |     100 |                   
  ...controller.ts |   83.11 |    79.31 |      90 |   83.11 | ...1033,1039,1042 
  ...extensions.ts |   88.15 |    74.95 |   92.98 |   88.15 | ...2027,2072-2073 
  ...-file-read.ts |      91 |    80.91 |     100 |      91 | ...20-621,624-625 
  ...file-write.ts |   89.58 |    79.16 |     100 |   89.58 | ...84,698-705,786 
  ...t-branches.ts |   75.43 |    66.66 |     100 |   75.43 | ...13-618,627-634 
  ...e-git-diff.ts |   97.32 |    90.56 |     100 |   97.32 | 161-162,189-191   
  ...ce-git-log.ts |     100 |    93.18 |     100 |     100 | 52,77,188         
  workspace-git.ts |   77.08 |    89.65 |     100 |   77.08 | 97-118            
  ...github-prs.ts |   88.26 |    63.46 |     100 |   88.26 | ...38-239,264-265 
  ...-lifecycle.ts |   95.23 |    75.75 |     100 |   95.23 | ...50-151,186-187 
  ...management.ts |   87.44 |    84.23 |     100 |   87.44 | ...1667,1687-1692 
  ...cp-control.ts |    73.2 |    67.54 |   85.71 |    73.2 | ...27-633,644-645 
  ...ace-models.ts |   95.53 |    89.74 |     100 |   95.53 | ...52-157,296-297 
  ...ermissions.ts |    77.9 |    72.41 |     100 |    77.9 | ...69-277,298-316 
  ...e-settings.ts |   75.04 |    72.99 |     100 |   75.04 | ...79-690,696-697 
  ...tup-github.ts |   77.97 |    70.58 |   84.21 |   77.97 | ...46-352,397-398 
  ...ace-skills.ts |    76.9 |    87.15 |     100 |    76.9 | ...29-354,360-394 
  ...ace-status.ts |   82.94 |     74.5 |     100 |   82.94 | ...84-486,490-491 
  ...pace-tools.ts |   75.94 |    69.69 |   66.66 |   75.94 | ...59-164,193-194 
  ...pace-trust.ts |   78.92 |    66.21 |      80 |   78.92 | ...38-343,351-352 
  ...pace-voice.ts |   91.33 |    80.92 |     100 |   91.33 | ...70-673,676-678 
 src/serve/server  |   91.76 |    89.39 |   96.95 |   91.76 |                   
  access-log.ts    |    98.7 |    97.14 |     100 |    98.7 | 118,189           
  ...er-helpers.ts |   63.82 |    77.96 |   81.81 |   63.82 | ...16,330,332-347 
  ...w-registry.ts |    98.8 |    81.81 |     100 |    98.8 | 107               
  ...r-handlers.ts |   97.29 |       75 |     100 |   97.29 | 17                
  ...r-response.ts |   86.54 |    72.48 |     100 |   86.54 | ...49,766,829-838 
  fs-factory.ts    |     100 |    94.54 |     100 |     100 | 42,103,159        
  ...branch-ops.ts |     100 |      100 |     100 |     100 |                   
  ...list-cache.ts |   99.01 |    95.52 |     100 |   99.01 | 184-185           
  ...t-deadline.ts |     100 |      100 |     100 |     100 |                   
  ...iter-setup.ts |      65 |    73.33 |   33.33 |      65 | 30-35,38-43,47-48 
  ...st-helpers.ts |   95.13 |    95.09 |     100 |   95.13 | ...66-168,423-428 
  self-origin.ts   |   76.19 |       80 |     100 |   76.19 | 45-54             
  ...e-features.ts |      95 |     87.5 |     100 |      95 | 182-188           
  ...on-archive.ts |   89.55 |    87.83 |   97.14 |   89.55 | ...32-836,888-889 
  ...ion-export.ts |     100 |    94.44 |     100 |     100 | 64                
  session-list.ts  |   95.86 |    93.37 |     100 |   95.86 | ...-848,1026-1030 
  telemetry.ts     |   99.04 |    97.44 |     100 |   99.04 | ...37,652,794-796 
 src/serve/voice   |    92.7 |    91.48 |   97.67 |    92.7 |                   
  ...ice-config.ts |   84.81 |       30 |     100 |   84.81 | 91-100,104-105    
  voice-ws.ts      |   91.58 |    93.44 |      96 |   91.58 | ...68,483,521-523 
  ...oordinator.ts |     100 |    98.21 |     100 |     100 | 176               
 ...kspace-service |   90.65 |    87.73 |   91.11 |   90.65 |                   
  index.ts         |   90.13 |    87.04 |   89.74 |   90.13 | ...1464-1468,1471 
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/services      |   92.49 |    89.25 |      98 |   92.49 |                   
  ...mandLoader.ts |     100 |    88.88 |     100 |     100 | 105-118           
  ...killLoader.ts |   97.19 |    85.29 |     100 |   97.19 | 142,153-154       
  ...andService.ts |   98.73 |      100 |     100 |   98.73 | 107               
  ...mandLoader.ts |   87.09 |    83.07 |     100 |   87.09 | ...35-340,345-350 
  ...omptLoader.ts |   79.55 |    88.29 |   83.33 |   79.55 | ...48,178,245-246 
  ...mandLoader.ts |   97.77 |    92.15 |     100 |   97.77 | 176,183-184       
  ...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.23 |    96.72 |     100 |   98.23 | 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 |                   
  prompt-stash.ts  |   96.66 |    92.85 |     100 |   96.66 | 34-35             
  ...tree-lease.ts |   88.23 |    86.48 |     100 |   88.23 | ...94-199,232-233 
  ...low-loader.ts |     100 |    96.15 |     100 |     100 | 88                
  setup-github.ts  |    90.8 |    80.95 |     100 |    90.8 | ...49-450,457-458 
  ...-args-file.ts |   93.93 |    91.66 |    87.5 |   93.93 | 208-210,224-230   
  types.ts         |     100 |      100 |     100 |     100 |                   
  ...e-keyterms.ts |   98.64 |    95.77 |     100 |   98.64 | 116,142-143       
  voice-model.ts   |     100 |      100 |     100 |     100 |                   
  voice-service.ts |    90.4 |    87.87 |     100 |    90.4 | ...81,288,353-358 
  ...e-settings.ts |     100 |    95.23 |     100 |     100 | 19                
  ...ranscriber.ts |   91.77 |    87.11 |   97.22 |   91.77 | ...99-901,904-906 
 ...rvices/insight |     100 |      100 |     100 |     100 |                   
  dates.ts         |     100 |      100 |     100 |     100 |                   
 ...ght/generators |   88.91 |     86.8 |   96.15 |   88.91 |                   
  DataProcessor.ts |   88.28 |    86.77 |   94.73 |   88.28 | ...1362,1366-1373 
  ...tGenerator.ts |   98.24 |    85.71 |     100 |   98.24 | 47                
  ...teRenderer.ts |     100 |      100 |     100 |     100 |                   
 .../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 | 96-99             
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/services/tips |   97.27 |    84.61 |     100 |   97.27 |                   
  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       |   88.99 |    83.47 |    90.9 |   88.99 |                   
  ...p-prefetch.ts |   98.09 |    94.23 |    87.5 |   98.09 | 50,209,225-226    
  ...reeStartup.ts |   80.53 |     74.6 |     100 |   80.53 | ...94,403,409-412 
 src/test-utils    |   94.09 |    79.16 |   77.77 |   94.09 |                   
  ci-env.ts        |      88 |     62.5 |     100 |      88 | 22-23,28          
  ...omMatchers.ts |   69.69 |       50 |      50 |   69.69 | 32-35,37-39,45-47 
  ...mised-lock.ts |     100 |      100 |   66.66 |     100 |                   
  ...andContext.ts |     100 |      100 |     100 |     100 |                   
  render.tsx       |     100 |      100 |     100 |     100 |                   
 src/ui            |   73.24 |    75.55 |   67.03 |   73.24 |                   
  App.tsx          |   33.33 |       75 |   33.33 |   33.33 | 32-86             
  AppContainer.tsx |   74.45 |    72.09 |   68.57 |   74.45 | ...4188,4304-4310 
  ...tionNudge.tsx |    9.58 |      100 |       0 |    9.58 | 24-94             
  ...ackDialog.tsx |    30.3 |      100 |       0 |    30.3 | 26-76             
  ...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 |                   
  ...ractiveUI.tsx |   70.51 |       74 |    62.5 |   70.51 | ...12,339,392-397 
  ...inePresets.ts |   96.27 |    83.87 |     100 |   96.27 | ...97,402,410-412 
  textConstants.ts |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/ui/auth       |   58.76 |    66.66 |   51.06 |   58.76 |                   
  AuthDialog.tsx   |   59.01 |     42.1 |   16.66 |   59.01 | ...25,332-354,358 
  ...nProgress.tsx |       0 |        0 |       0 |       0 | 1-64              
  ...etupSteps.tsx |   60.21 |    70.73 |   57.69 |   60.21 | ...90,794,803,806 
  useAuth.ts       |   94.83 |       75 |     100 |   94.83 | ...33-234,253-259 
  ...rSetupFlow.ts |   43.18 |    33.33 |      50 |   43.18 | ...78-399,416-459 
 src/ui/commands   |   83.35 |    83.55 |   89.88 |   83.35 |                   
  aboutCommand.ts  |     100 |      100 |     100 |     100 |                   
  agentsCommand.ts |   83.78 |      100 |      60 |   83.78 | 30-32,42-44       
  ...odeCommand.ts |    93.1 |    95.23 |     100 |    93.1 | 77-82             
  arenaCommand.ts  |   63.89 |    65.71 |   65.21 |   63.89 | ...01-606,691-699 
  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    |     100 |    77.77 |     100 |     100 | 27,61             
  cdCommand.ts     |    92.3 |    82.75 |     100 |    92.3 | ...,94-99,178,187 
  clearCommand.ts  |    80.9 |    70.83 |     100 |    80.9 | ...28-129,137-146 
  ...essCommand.ts |   68.06 |    54.05 |      75 |   68.06 | ...96-197,211-214 
  ...astCommand.ts |   84.17 |       75 |     100 |   84.17 | ...,91-97,125-130 
  ...ig-command.ts |   93.12 |    88.42 |     100 |   93.12 | ...07-315,321-323 
  ...extCommand.ts |   69.07 |     72.6 |   84.61 |   69.07 | ...78-611,622-623 
  copyCommand.ts   |    98.7 |    96.29 |     100 |    98.7 | 66-67,172,272,323 
  ...or-command.ts |   85.95 |    80.55 |   88.88 |   85.95 | ...68-274,298-309 
  deleteCommand.ts |     100 |      100 |     100 |     100 |                   
  diffCommand.ts   |     100 |    87.87 |     100 |     100 | ...63,231-232,245 
  ...ryCommand.tsx |   81.64 |    87.67 |    90.9 |   81.64 | ...73-278,325-332 
  docsCommand.ts   |     100 |     90.9 |     100 |     100 | 25                
  doctorCommand.ts |   70.16 |    84.61 |      95 |   70.16 | ...29-679,682-816 
  dreamCommand.ts  |   85.45 |    88.88 |     100 |   85.45 | 58-65             
  editorCommand.ts |     100 |      100 |     100 |     100 |                   
  ...rt-command.ts |   80.48 |       75 |     100 |   80.48 | 49-54,69-72,93-98 
  effort-utils.ts  |     100 |      100 |     100 |     100 |                   
  exportCommand.ts |   98.25 |    91.02 |     100 |   98.25 | ...81,198-199,364 
  ...onsCommand.ts |   52.31 |    56.25 |   69.23 |   52.31 | ...09,277-329,390 
  forgetCommand.ts |     100 |       90 |     100 |     100 | 59                
  forkCommand.ts   |     100 |    94.11 |     100 |     100 | 96,147            
  goalCommand.ts   |     100 |    96.49 |     100 |     100 | 139,192           
  helpCommand.ts   |     100 |      100 |     100 |     100 |                   
  ...oryCommand.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 
  ...figCommand.ts |   52.83 |    81.25 |      70 |   52.83 | ...74-319,321-330 
  initCommand.ts   |   91.86 |       80 |     100 |   91.86 | 48,83-88          
  ...ghtCommand.ts |   77.87 |    71.42 |     100 |   77.87 | ...44-245,250-272 
  ...ageCommand.ts |   94.44 |    90.14 |     100 |   94.44 | ...13-214,241-251 
  learn-command.ts |     100 |      100 |     100 |     100 |                   
  lspCommand.ts    |     100 |    86.95 |     100 |     100 | 31,101-102        
  mcpCommand.ts    |     100 |      100 |     100 |     100 |                   
  memoryCommand.ts |     100 |      100 |     100 |     100 |                   
  modelCommand.ts  |   85.02 |    82.53 |     100 |   85.02 | ...1089,1123-1128 
  ...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             
  ...ns-command.ts |   98.83 |    81.81 |     100 |   98.83 | 100               
  ...berCommand.ts |     100 |     87.5 |     100 |     100 | 46                
  renameCommand.ts |    89.6 |       90 |     100 |    89.6 | ...72-176,212-219 
  ...oreCommand.ts |   90.96 |    86.04 |     100 |   90.96 | ...41-146,177-178 
  resumeCommand.ts |     100 |      100 |     100 |     100 |                   
  rewindCommand.ts |   81.25 |      100 |      50 |   81.25 | 20-22             
  ...ngsCommand.ts |     100 |      100 |     100 |     100 |                   
  ...hubCommand.ts |   89.47 |       75 |      80 |   89.47 | 54-59             
  skillsCommand.ts |   78.82 |    81.81 |     100 |   78.82 | 37-52,78,97       
  statsCommand.ts  |   90.65 |    76.73 |     100 |   90.65 | ...30-733,825-832 
  ...ineCommand.ts |     100 |      100 |     100 |     100 |                   
  ...aryCommand.ts |   73.04 |     82.3 |      90 |   73.04 | ...20-547,561-565 
  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 |                   
  ...te-command.ts |     100 |    94.11 |     100 |     100 | 74,148            
  vimCommand.ts    |   54.54 |      100 |      50 |   54.54 | 19-29             
  voice-command.ts |   93.57 |       88 |     100 |   93.57 | 35,97-102         
  ...owsCommand.ts |   92.92 |       85 |   66.66 |   92.92 | ...72-177,276-281 
 src/ui/components |   71.96 |    79.54 |   79.56 |   71.96 |                   
  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 |   95.65 |    66.66 |     100 |   95.65 | 27,52             
  ...TextInput.tsx |   88.65 |    90.41 |     100 |   88.65 | ...84-286,300-302 
  Composer.tsx     |   94.49 |    66.66 |     100 |   94.49 | ...-76,88,143,157 
  ...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 
  CronPill.tsx     |     100 |    93.75 |     100 |     100 | 19                
  ...ification.tsx |      84 |       60 |     100 |      84 | 23-24,40-42       
  ...gProfiler.tsx |       0 |        0 |       0 |       0 | 1-36              
  ...ogManager.tsx |       0 |        0 |       0 |       0 | 1-598             
  DiffDialog.tsx   |    53.5 |     37.5 |   69.23 |    53.5 | ...32-737,747-760 
  ...ngsDialog.tsx |       0 |        0 |       0 |       0 | 1-195             
  EffortDialog.tsx |   97.36 |      100 |     100 |   97.36 | 55-56             
  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       |   81.27 |    69.23 |      50 |   81.27 | ...06,245,267-272 
  ...ngSpinner.tsx |   68.42 |    85.71 |      50 |   68.42 | 35-52,73,80-81    
  GoalPill.tsx     |   93.51 |    81.81 |     100 |   93.51 | 37-38,106-109,123 
  Header.tsx       |   98.65 |    94.73 |     100 |   98.65 | 173,175           
  Help.tsx         |   98.33 |       90 |     100 |   98.33 | ...25,382,448-449 
  ...emDisplay.tsx |   79.28 |    66.99 |     100 |   79.28 | ...08,511,514-520 
  ...ngeDialog.tsx |     100 |      100 |     100 |     100 |                   
  InputPrompt.tsx  |   84.26 |    82.94 |      80 |   84.26 | ...2215,2236,2332 
  ...Shortcuts.tsx |     100 |       88 |     100 |     100 | 98,119            
  ...Indicator.tsx |   98.18 |    97.82 |     100 |   98.18 | 161-162           
  ...firmation.tsx |   91.42 |      100 |      50 |   91.42 | 26-31             
  MainContent.tsx  |   95.58 |    95.06 |   46.15 |   95.58 | ...79,482-486,489 
  MemoryDialog.tsx |   86.59 |    80.15 |     100 |   86.59 | ...34-435,485,553 
  ...geDisplay.tsx |       0 |        0 |       0 |       0 | 1-41              
  ModelDialog.tsx  |   85.22 |    74.08 |     100 |   85.22 | ...1041,1097,1099 
  ...tsDisplay.tsx |     100 |    97.22 |     100 |     100 | 270               
  ...fications.tsx |       0 |        0 |       0 |       0 | 1-56              
  ...onsDialog.tsx |       0 |        0 |       0 |       0 | 1-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 |       0 |        0 |       0 |       0 | 1-134             
  ...geDisplay.tsx |     100 |      100 |     100 |     100 |                   
  ...ngDisplay.tsx |       0 |        0 |       0 |       0 | 1-39              
  ...hProgress.tsx |   85.25 |    88.46 |     100 |   85.25 | 121-147           
  ...dSelector.tsx |   92.79 |    82.65 |     100 |   92.79 | ...19-323,354-370 
  ...ionPicker.tsx |   83.66 |    72.13 |     100 |   83.66 | ...96,402,444-466 
  ...onPreview.tsx |   93.58 |    83.78 |     100 |   93.58 | ...,70-71,195-197 
  ...ryDisplay.tsx |     100 |      100 |     100 |     100 |                   
  ...putPrompt.tsx |   92.06 |    86.36 |   83.33 |   92.06 | ...,70-72,120-123 
  ...tedDialog.tsx |     100 |      100 |     100 |     100 |                   
  ...ngsDialog.tsx |   71.49 |    73.89 |   69.23 |   71.49 | ...1244,1250-1251 
  ...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 |       0 |        0 |       0 |       0 | 1-40              
  ...iewDialog.tsx |   97.77 |    87.67 |     100 |   97.77 | ...97,305-307,324 
  ...tsDisplay.tsx |   95.86 |       75 |     100 |   95.86 | 67-71             
  ...ionPicker.tsx |       0 |        0 |       0 |       0 | 1-172             
  ...tivityTab.tsx |    3.94 |      100 |       0 |    3.94 | 27-275            
  StatsDialog.tsx  |    8.64 |      100 |       0 |    8.64 | ...76-111,130-322 
  StatsDisplay.tsx |     100 |      100 |     100 |     100 |                   
  ...ciencyTab.tsx |    78.9 |    56.52 |     100 |    78.9 | ...26,213,262-288 
  ...atmapView.tsx |    8.98 |      100 |       0 |    8.98 | 20-107            
  ...essionTab.tsx |      80 |    66.66 |     100 |      80 | ...70-277,283-300 
  ...ineDialog.tsx |    93.9 |    86.88 |     100 |    93.9 | ...20,282,302-304 
  ...yTodoList.tsx |   96.36 |    88.23 |     100 |   96.36 | 138-141           
  ...nsDisplay.tsx |   95.62 |    87.09 |     100 |   95.62 | ...24-125,273-275 
  ...inalImage.tsx |     100 |    93.93 |     100 |     100 | 75,129            
  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 |    83.33 |     100 |     100 | 72-87             
  ...ification.tsx |   36.36 |      100 |       0 |   36.36 | 15-22             
  ...Indicator.tsx |    92.5 |     87.5 |     100 |    92.5 | 50-53             
  ...ackDialog.tsx |       0 |        0 |       0 |       0 | 1-134             
  ...xitDialog.tsx |   80.36 |    43.47 |      60 |   80.36 | ...24-238,248-251 
  ...odeVisuals.ts |   97.22 |    85.71 |     100 |   97.22 | 25                
  ...s-helpers.tsx |   66.25 |    81.25 |      50 |   66.25 | 25-32,46-53,62-72 
 ...nts/agent-view |   55.05 |    69.09 |      50 |   55.05 |                   
  ...atContent.tsx |    9.09 |      100 |       0 |    9.09 | 54-275,281-283    
  ...tChatView.tsx |   21.05 |      100 |       0 |   21.05 | 21-39             
  ...tComposer.tsx |   69.48 |    33.33 |   66.66 |   69.48 | ...51,269,277-279 
  AgentFooter.tsx  |   15.38 |      100 |       0 |   15.38 | 28-65             
  AgentHeader.tsx  |   15.38 |      100 |       0 |   15.38 | 27-64             
  AgentTabBar.tsx  |    87.9 |    63.88 |     100 |    87.9 | ...88,110-118,136 
  ...oryAdapter.ts |     100 |    91.83 |     100 |     100 | 103,109-110,138   
  index.ts         |       0 |        0 |       0 |       0 | 1-12              
 ...mponents/arena |    42.3 |    68.69 |   73.68 |    42.3 |                   
  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 |       0 |        0 |       0 |       0 | 1-166             
  ...tusDialog.tsx |       0 |        0 |       0 |       0 | 1-288             
  ...topDialog.tsx |       0 |        0 |       0 |       0 | 1-213             
 ...ackground-view |   85.34 |    84.91 |   92.98 |   85.34 |                   
  ...sksDialog.tsx |   81.87 |    82.77 |   85.71 |   81.87 | ...1853,1965-1971 
  ...TasksPill.tsx |   78.84 |    94.28 |     100 |   78.84 | 64,109-129        
  ...gentPanel.tsx |   97.08 |    86.31 |     100 |   97.08 | 132,442-446,520   
  agent-forest.ts  |    99.2 |    93.93 |     100 |    99.2 | 258               
  ...Visibility.ts |     100 |      100 |     100 |     100 |                   
  ...e-overlay.tsx |    88.2 |    76.47 |     100 |    88.2 | ...36-138,140-142 
 ...nts/extensions |   84.32 |    76.78 |   83.33 |   84.32 |                   
  ...gerDialog.tsx |   82.15 |    76.08 |     100 |   82.15 | ...91-198,258,260 
  TabBar.tsx       |   97.29 |    88.88 |     100 |   97.29 | 33                
  index.ts         |       0 |        0 |       0 |       0 | 1-12              
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...tensions/steps |   46.26 |       85 |   58.82 |   46.26 |                   
  ...ctionStep.tsx |   95.12 |    92.85 |   85.71 |   95.12 | 84-86,89          
  ...etailStep.tsx |       0 |        0 |       0 |       0 | 1-145             
  ...nListStep.tsx |   75.26 |    88.37 |   66.66 |   75.26 | ...53,174,203-209 
  ...electStep.tsx |       0 |        0 |       0 |       0 | 1-83              
  ...nfirmStep.tsx |   16.32 |      100 |       0 |   16.32 | 28-74             
  index.ts         |       0 |        0 |       0 |       0 | 1-11              
 ...xtensions/tabs |   71.92 |    68.21 |   70.83 |   71.92 |                   
  DiscoverTab.tsx  |   68.22 |    67.66 |   55.55 |   68.22 | ...93,656-660,664 
  InstalledTab.tsx |   75.49 |    67.44 |   83.33 |   75.49 | ...77,782-783,820 
  SourcesTab.tsx   |   71.67 |    70.47 |   77.77 |   71.67 | ...28,547,621-633 
 ...tensions/views |    50.7 |    52.38 |   20.83 |    50.7 |                   
  ...tionsView.tsx |   73.75 |    56.36 |   66.66 |   73.75 | ...30,353,369-374 
  ...tionsView.tsx |   43.45 |    44.82 |    6.66 |   43.45 | ...98-405,408-420 
  ...etailView.tsx |    9.24 |      100 |       0 |    9.24 | 40-67,70-163      
 ...mponents/hooks |   87.11 |    81.37 |   91.89 |   87.11 |                   
  ...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.49 |     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 |   40.91 |    63.44 |   70.58 |   40.91 |                   
  ...ealthPill.tsx |     100 |      100 |     100 |     100 |                   
  ...entDialog.tsx |   32.09 |    26.19 |      40 |   32.09 | ...12,914,927-933 
  ...valDialog.tsx |   15.06 |      100 |       0 |   15.06 | 40-109            
  constants.ts     |     100 |      100 |     100 |     100 |                   
  index.ts         |       0 |        0 |       0 |       0 | 1-35              
  types.ts         |     100 |      100 |     100 |     100 |                   
  utils.ts         |      97 |       95 |     100 |      97 | 24,113-114        
 ...ents/mcp/steps |   53.94 |    73.51 |   57.14 |   53.94 |                   
  ...icateStep.tsx |    5.65 |      100 |       0 |    5.65 | 40-66,69-308      
  ...electStep.tsx |   10.95 |      100 |       0 |   10.95 | 16-88             
  ...etailStep.tsx |     100 |      100 |     100 |     100 |                   
  ...eListStep.tsx |   99.09 |    97.36 |     100 |   99.09 | 71                
  ...etailStep.tsx |   62.83 |       60 |   33.33 |   62.83 | ...87-296,307-332 
  ...rListStep.tsx |   88.53 |    81.25 |     100 |   88.53 | ...64,170,175-180 
  ...etailStep.tsx |    10.3 |      100 |       0 |    10.3 | ...1,67-79,82-140 
  ToolListStep.tsx |   69.29 |       50 |     100 |   69.29 | ...23,126,135-144 
 ...nents/messages |   90.26 |    86.89 |   85.57 |   90.26 |                   
  ...ionDialog.tsx |   89.23 |     84.9 |   81.81 |   89.23 | ...75,593,611-613 
  BtwMessage.tsx   |     100 |      100 |     100 |     100 |                   
  ...upDisplay.tsx |     100 |    94.73 |     100 |     100 | ...43,289,402,432 
  ...onMessage.tsx |   92.06 |    82.35 |     100 |   92.06 | 58-60,62,64       
  ...nMessages.tsx |   94.11 |    95.91 |   76.92 |   94.11 | ...47-349,352-355 
  DiffRenderer.tsx |   93.17 |    86.02 |     100 |   93.17 | ...07,235-236,302 
  ...tsDisplay.tsx |   97.08 |    77.77 |     100 |   97.08 | 95,97,106         
  ...usMessage.tsx |   81.73 |     65.9 |      75 |   81.73 | ...10-214,222,245 
  ...tsDisplay.tsx |   95.52 |    88.31 |     100 |   95.52 | ...40,142,175-180 
  ...ssMessage.tsx |    12.5 |      100 |       0 |    12.5 | 18-59             
  ...edMessage.tsx |   21.05 |      100 |       0 |   21.05 | 23-39             
  ...sMessages.tsx |   59.04 |       50 |    37.5 |   59.04 | ...21-126,147-159 
  ...ryMessage.tsx |   13.63 |      100 |       0 |   13.63 | 23-64             
  ...onMessage.tsx |   91.87 |    82.63 |     100 |   91.87 | ...49-651,658-660 
  ...upMessage.tsx |   98.38 |    95.38 |     100 |   98.38 | 188-191,422       
  ToolMessage.tsx  |   93.06 |    86.32 |   93.75 |   93.06 | ...1037,1082-1084 
 ...ponents/shared |   86.29 |     82.4 |   94.17 |   86.29 |                   
  ...ctionList.tsx |     100 |      100 |      75 |     100 |                   
  ...tonSelect.tsx |     100 |      100 |     100 |     100 |                   
  EnumSelector.tsx |     100 |    96.42 |     100 |     100 | 58                
  ...rBoundary.tsx |     100 |      100 |     100 |     100 |                   
  MaxSizedBox.tsx  |   84.71 |    86.86 |      90 |   84.71 | ...67-568,685-686 
  MultiSelect.tsx  |   93.58 |       75 |     100 |   93.58 | ...43,199-201,211 
  ...tonSelect.tsx |     100 |      100 |     100 |     100 |                   
  ...ontroller.tsx |     100 |    83.33 |     100 |     100 | 73,93-95          
  ...eSelector.tsx |     100 |       60 |     100 |     100 | 40-45             
  ...lableList.tsx |   81.48 |    84.84 |     100 |   81.48 | 46-66,73-76       
  StaticRender.tsx |     100 |      100 |     100 |     100 |                   
  TextInput.tsx    |    80.8 |    67.24 |      80 |    80.8 | ...36-240,252-258 
  ...ontroller.tsx |     100 |    81.81 |     100 |     100 | 59-62             
  ...apsedTime.tsx |     100 |      100 |     100 |     100 |                   
  ...Indicator.tsx |     100 |      100 |     100 |     100 |                   
  ...lizedList.tsx |   91.49 |    86.66 |   83.33 |   91.49 | ...18-846,859,959 
  text-buffer.ts   |   85.98 |    81.81 |   97.91 |   85.98 | ...2664,2762-2763 
  ...er-actions.ts |   73.93 |    67.22 |     100 |   73.93 | ...32-733,934-936 
 ...ponents/skills |       0 |        0 |       0 |       0 |                   
  ...gerDialog.tsx |       0 |        0 |       0 |       0 | 1-681             
 ...ents/subagents |       0 |        0 |       0 |       0 |                   
  constants.ts     |       0 |        0 |       0 |       0 | 1-71              
  index.ts         |       0 |        0 |       0 |       0 | 1-11              
  reducers.tsx     |       0 |        0 |       0 |       0 | 1-190             
  types.ts         |       0 |        0 |       0 |       0 | 1-125             
  utils.ts         |       0 |        0 |       0 |       0 | 1-102             
 ...bagents/create |       0 |        0 |       0 |       0 |                   
  ...ionWizard.tsx |       0 |        0 |       0 |       0 | 1-299             
  ...rSelector.tsx |       0 |        0 |       0 |       0 | 1-85              
  ...onSummary.tsx |       0 |        0 |       0 |       0 | 1-331             
  ...tionInput.tsx |       0 |        0 |       0 |       0 | 1-177             
  ...dSelector.tsx |       0 |        0 |       0 |       0 | 1-63              
  ...nSelector.tsx |       0 |        0 |       0 |       0 | 1-58              
  ...EntryStep.tsx |       0 |        0 |       0 |       0 | 1-78              
  ToolSelector.tsx |       0 |        0 |       0 |       0 | 1-253             
 ...bagents/manage |   14.14 |    53.19 |    37.5 |   14.14 |                   
  ...ctionStep.tsx |       0 |        0 |       0 |       0 | 1-103             
  ...eleteStep.tsx |       0 |        0 |       0 |       0 | 1-62              
  ...tEditStep.tsx |       0 |        0 |       0 |       0 | 1-124             
  ...ctionStep.tsx |   35.61 |    59.52 |     100 |   35.61 | ...21-433,438-440 
  ...iewerStep.tsx |       0 |        0 |       0 |       0 | 1-73              
  ...gerDialog.tsx |       0 |        0 |       0 |       0 | 1-341             
 ...mponents/views |    70.1 |    72.89 |   61.11 |    70.1 |                   
  ContextUsage.tsx |   71.49 |    64.86 |      80 |   71.49 | ...30-436,473-567 
  DoctorReport.tsx |     9.8 |      100 |       0 |     9.8 | 25-54,57-131      
  ...sionsList.tsx |   88.05 |       75 |     100 |   88.05 | 70-77             
  McpStatus.tsx    |   92.01 |     73.8 |     100 |   92.01 | ...36,175-177,262 
  SkillsList.tsx   |   20.51 |      100 |       0 |   20.51 | 17-20,27-57       
  ToolsList.tsx    |     100 |      100 |     100 |     100 |                   
 src/ui/contexts   |   84.16 |    81.76 |   85.13 |   84.16 |                   
  ...ewContext.tsx |   64.83 |    88.88 |      50 |   64.83 | ...16-219,225-235 
  AppContext.tsx   |      80 |       50 |     100 |      80 | 19-20             
  ...ewContext.tsx |   93.83 |    68.51 |   42.85 |   93.83 | ...44,281-285,317 
  ...igContext.tsx |   81.81 |       50 |     100 |   81.81 | 15-16             
  ...ssContext.tsx |   85.65 |    84.85 |     100 |   85.65 | ...1612-1614,1620 
  ...owContext.tsx |   91.07 |    81.81 |     100 |   91.07 | 47-48,60-62       
  ...deContext.tsx |     100 |      100 |      50 |     100 |                   
  ...onContext.tsx |   80.77 |    79.56 |    92.3 |   80.77 | ...31-434,443-446 
  ...gsContext.tsx |     100 |      100 |     100 |     100 |                   
  ...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             
  ...edContext.tsx |     100 |      100 |      50 |     100 |                   
  ...nsContext.tsx |   88.88 |       50 |     100 |   88.88 | 156-157           
  ...teContext.tsx |   86.66 |       50 |     100 |   86.66 | 235-236           
  ...deContext.tsx |      80 |     87.5 |      75 |      80 | ...11-112,118-120 
  ...rtContext.tsx |     100 |      100 |     100 |     100 |                   
 src/ui/daemon     |   88.35 |    73.51 |   95.45 |   88.35 |                   
  ...ui-adapter.ts |   88.35 |    73.51 |   95.45 |   88.35 | ...74,792-793,879 
 src/ui/editors    |       0 |        0 |       0 |       0 |                   
  ...ngsManager.ts |       0 |        0 |       0 |       0 | 1-67              
 src/ui/hooks      |   85.64 |    83.42 |   87.39 |   85.64 |                   
  ...dProcessor.ts |   85.53 |    85.13 |     100 |   85.53 | ...-970,1017-1018 
  ...ention-ref.ts |   97.72 |       84 |     100 |   97.72 | 65                
  keyToAnsi.ts     |    3.92 |      100 |       0 |    3.92 | 19-77             
  ...esourceRef.ts |     100 |      100 |     100 |     100 |                   
  ...completion.ts |     100 |    95.45 |     100 |     100 | 95                
  ...ention-ref.ts |     100 |      100 |     100 |     100 |                   
  ...dProcessor.ts |   94.62 |    73.58 |     100 |   94.62 | ...87-288,293-294 
  ...dProcessor.ts |   86.86 |    71.67 |   83.33 |   86.86 | ...1540,1562-1566 
  ...rt-command.ts |     100 |      100 |     100 |     100 |                   
  ...sced-flush.ts |     100 |      100 |     100 |     100 |                   
  ...ng-enabled.ts |     100 |      100 |     100 |     100 |                   
  ...oice-input.ts |   92.36 |    81.95 |   66.66 |   92.36 | ...00,502-503,658 
  ...ke-repaint.ts |     100 |      100 |     100 |     100 |                   
  ...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 |      52 |    63.63 |     100 |      52 | ...59,67-70,76-87 
  ...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 |   86.44 |    88.48 |     100 |   86.44 | ...14-515,525-541 
  ...ifications.ts |   87.82 |    96.77 |     100 |   87.82 | 138-152           
  ...tIndicator.ts |   88.28 |    81.57 |     100 |   88.28 | ...66,175,179-187 
  ...waySummary.ts |   96.26 |       75 |     100 |   96.26 | 126-128,170       
  ...ndTaskView.ts |   94.89 |    77.55 |     100 |   94.89 | 164-168,257,263   
  ...chedScroll.ts |     100 |      100 |     100 |     100 |                   
  ...ketedPaste.ts |    23.8 |      100 |       0 |    23.8 | 19-37             
  ...nchCommand.ts |   95.53 |    83.01 |     100 |   95.53 | ...64-165,289-292 
  ...ompletion.tsx |   97.09 |    87.09 |     100 |   97.09 | ...23-324,334-335 
  ...dMigration.ts |    92.1 |    88.88 |     100 |    92.1 | 42-44             
  useCompletion.ts |   96.29 |    90.56 |     100 |   96.29 | ...17-218,222-223 
  ...nitMessage.ts |     100 |      100 |     100 |     100 |                   
  ...extualTips.ts |   78.26 |       50 |     100 |   78.26 | ...2,75-79,96-104 
  ...eteCommand.ts |   89.52 |    90.69 |     100 |   89.52 | ...98-106,114-115 
  ...ialogClose.ts |   36.11 |       10 |     100 |   36.11 | ...89-195,202-207 
  useDiffData.ts   |       0 |        0 |       0 |       0 | 1-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.72 |    92.98 |     100 |   93.72 | ...87-291,314-320 
  ...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 |    93.33 |     100 |     100 | 62                
  ...ggestions.tsx |   96.47 |    78.94 |     100 |   96.47 | 121,155-156       
  ...miniStream.ts |   86.07 |    82.29 |    73.8 |   86.07 | ...5362-5364,5366 
  ...BranchName.ts |     100 |    94.44 |     100 |     100 | 54                
  ...oryManager.ts |   98.38 |    98.85 |     100 |   98.38 | 141-144           
  ...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 |   22.58 |      100 |      50 |   22.58 | 11-32,44-85       
  ...gIndicator.ts |     100 |    96.66 |     100 |     100 | 109               
  useLogger.ts     |      16 |      100 |       0 |      16 | 15-45             
  useMCPHealth.ts  |   10.52 |      100 |       0 |   10.52 | 36-75             
  ...cpApproval.ts |   93.12 |    86.11 |     100 |   93.12 | ...24-127,139-140 
  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 |     97.4 |     100 |     100 | 175,262           
  ...delCommand.ts |     100 |       96 |     100 |     100 | 61                
  ...ouseEvents.ts |   94.89 |       95 |   83.33 |   94.89 | 78-82             
  ...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 |   85.29 |    80.28 |    92.3 |   85.29 | ...36,351-361,441 
  useQwenAuth.ts   |     100 |      100 |     100 |     100 |                   
  ...lScheduler.ts |   89.48 |    88.88 |     100 |   89.48 | ...54-456,489-499 
  ...oryCommand.ts |       0 |        0 |       0 |       0 | 1-7               
  ...umeCommand.ts |   95.34 |    77.14 |     100 |   95.34 | 124-125,227-232   
  ...ompletion.tsx |   90.67 |    83.33 |     100 |   90.67 | ...02,105,138-141 
  ...ectionList.ts |   97.12 |    96.19 |     100 |   97.12 | ...92-193,247-250 
  ...sionPicker.ts |   92.87 |    90.35 |     100 |   92.87 | ...99-501,503-505 
  ...earchInput.ts |     100 |    97.29 |     100 |     100 | 82                
  ...ngsCommand.ts |   18.75 |      100 |       0 |   18.75 | 10-25             
  ...ellHistory.ts |   93.28 |    80.95 |     100 |   93.28 | ...96,153-154,164 
  ...oryCommand.ts |   85.48 |    58.33 |     100 |   85.48 | 22-28,40,71       
  ...agerDialog.ts |   88.23 |      100 |     100 |   88.23 | 20,24             
  ...Completion.ts |   82.85 |    85.13 |   94.73 |   82.85 | ...78-680,688-724 
  ...tateAndRef.ts |     100 |      100 |     100 |     100 |                   
  ...tatsDialog.ts |     100 |      100 |     100 |     100 |                   
  useStatusLine.ts |   97.32 |    93.93 |     100 |   97.32 | ...18-422,518-525 
  ...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 |   67.34 |    58.82 |   66.66 |   67.34 | 52-53,61-68,79-85 
  ...rminalSize.ts |     100 |      100 |     100 |     100 |                   
  ...emeCommand.ts |    79.2 |    35.29 |     100 |    79.2 | ...15-116,120-121 
  useTimer.ts      |   97.59 |    94.73 |     100 |   97.59 | 17-18             
  ...lMigration.ts |       0 |        0 |       0 |       0 |                   
  ...rustModify.ts |     100 |    90.47 |     100 |     100 | 112,134           
  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 | 47-48,72          
  vim.ts           |      74 |    67.56 |   69.23 |      74 | ...1854-1861,1869 
 src/ui/layouts    |   91.25 |    89.47 |     100 |   91.25 |                   
  ...AppLayout.tsx |   90.99 |     87.5 |     100 |   90.99 | 61-63,111-116,152 
  ...AppLayout.tsx |   91.66 |    92.85 |     100 |   91.66 | 75-80             
 src/ui/models     |   80.72 |       80 |   71.42 |   80.72 |                   
  ...ableModels.ts |   80.72 |       80 |   71.42 |   80.72 | ...,61-71,125-127 
 ...noninteractive |     100 |      100 |    6.66 |     100 |                   
  ...eractiveUi.ts |     100 |      100 |    6.66 |     100 |                   
 src/ui/selection  |   93.56 |    86.13 |     100 |   93.56 |                   
  screen-buffer.ts |   94.73 |    64.28 |     100 |   94.73 | 51-52             
  ...ion-coords.ts |     100 |      100 |     100 |     100 |                   
  ...ction-span.ts |   93.81 |     92.1 |     100 |   93.81 | ...1,45-46,99-100 
  ...tion-state.ts |     100 |      100 |     100 |     100 |                   
  ...ction-text.ts |   93.85 |    93.44 |     100 |   93.85 | 30-34,130-131     
  ...selection.tsx |   91.88 |    78.57 |     100 |   91.88 | ...16-417,446-447 
 src/ui/state      |      95 |    81.81 |     100 |      95 |                   
  extensions.ts    |      95 |    81.81 |     100 |      95 | 69-70,89          
 src/ui/themes     |    98.5 |    73.17 |     100 |    98.5 |                   
  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   |   99.23 |    97.05 |     100 |   99.23 | 277-278           
  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 |   88.68 |    84.52 |     100 |   88.68 | ...83-392,397-398 
  theme.ts         |     100 |    38.02 |     100 |     100 | ...34-449,457-461 
  xcode.ts         |     100 |      100 |     100 |     100 |                   
 src/ui/utils      |   87.57 |    85.62 |   95.76 |   87.57 |                   
  ...Colorizer.tsx |   80.31 |    85.41 |     100 |   80.31 | ...00-201,313-339 
  ...nRenderer.tsx |   80.07 |     75.6 |     100 |   80.07 | ...70,274,332-333 
  ...wnDisplay.tsx |   92.87 |    93.46 |     100 |   92.87 | ...,955,1002-1020 
  ...idDiagram.tsx |   87.79 |    95.34 |     100 |   87.79 | 156-179           
  ...eRenderer.tsx |   93.63 |    81.77 |   95.23 |   93.63 | ...47-750,803-808 
  ...odeDisplay.ts |   94.28 |    85.71 |     100 |   94.28 | 23,40             
  asciiCharts.ts   |    96.7 |     87.5 |     100 |    96.7 | 170-177,278       
  ...dWorkUtils.ts |     100 |      100 |     100 |     100 |                   
  ...boardUtils.ts |    52.9 |    74.15 |    92.3 |    52.9 | ...29,632-641,644 
  commandUtils.ts  |   98.38 |    92.38 |     100 |   98.38 | 108,136-137,343   
  computeStats.ts  |     100 |      100 |     100 |     100 |                   
  customBanner.ts  |   90.68 |    91.22 |     100 |   90.68 | ...13,324-327,334 
  displayUtils.ts  |   73.84 |    73.91 |     100 |   73.84 | ...34,36-40,42-46 
  formatters.ts    |   94.87 |    98.21 |     100 |   94.87 | 116-119           
  goal-runtime.ts  |   91.42 |       95 |     100 |   91.42 | 32-34             
  gradientUtils.ts |     100 |      100 |     100 |     100 |                   
  highlight.ts     |     100 |      100 |     100 |     100 |                   
  ...gap-notice.ts |     100 |      100 |     100 |     100 |                   
  ...oryMapping.ts |     100 |       95 |     100 |     100 | 44,103            
  historyUtils.ts  |   96.03 |     97.1 |     100 |   96.03 | 103-106           
  ...mage-parts.ts |   97.75 |    94.59 |     100 |   97.75 | 82-83             
  inline-math.ts   |   98.48 |    95.23 |     100 |   98.48 | 129-130           
  input-mouse.ts   |     100 |    85.71 |     100 |     100 | 48,93             
  isNarrowWidth.ts |     100 |      100 |     100 |     100 |                   
  ...olDetector.ts |   68.81 |       75 |   66.66 |   68.81 | ...27-132,160-161 
  latexRenderer.ts |   94.95 |     73.8 |     100 |   94.95 | ...76-178,184-187 
  layoutUtils.ts   |     100 |      100 |     100 |     100 |                   
  list-mouse.ts    |     100 |      100 |     100 |     100 |                   
  ...ightLoader.ts |     100 |       95 |     100 |     100 | 81                
  ...nUtilities.ts |   98.72 |    94.36 |     100 |   98.72 | 145-146           
  ...t-position.ts |     100 |     87.5 |     100 |     100 | 85                
  ...geRenderer.ts |   86.51 |    70.16 |   95.12 |   86.51 | ...1286,1326-1332 
  ...alRenderer.ts |   86.69 |     71.9 |     100 |   86.69 | ...1476,1513-1519 
  ...lsBySource.ts |     100 |    95.23 |     100 |     100 | 84                
  mouse.ts         |   92.85 |    74.19 |     100 |   92.85 | ...38,145,149-152 
  osc8.ts          |   91.33 |    79.03 |     100 |   91.33 | ...73,273,277-278 
  ...red-height.ts |   98.38 |    97.14 |     100 |   98.38 | 195-197           
  ...mConstants.ts |     100 |      100 |     100 |     100 |                   
  restoreGoal.ts   |     100 |      100 |     100 |     100 |                   
  ...storyUtils.ts |   82.86 |    79.48 |     100 |   82.86 | ...88-610,741-742 
  ...ickerUtils.ts |     100 |      100 |     100 |     100 |                   
  ...evel-label.ts |   77.77 |    66.66 |     100 |   77.77 | 18,22-24          
  ...are-cursor.ts |   89.47 |    85.71 |     100 |   89.47 | 39-44             
  ...ataService.ts |   93.17 |     79.1 |     100 |   93.17 | ...14,227,254-256 
  suggestions.ts   |     100 |      100 |     100 |     100 |                   
  ...izedOutput.ts |   95.19 |      100 |   88.88 |   95.19 | 121-126           
  ...nal-buffer.ts |     100 |      100 |     100 |     100 |                   
  ...e-renderer.ts |   90.61 |    83.44 |     100 |   90.61 | ...80,482-484,607 
  ...ize-reflow.ts |     100 |     92.3 |     100 |     100 | 57,62,209,217,347 
  ...wOptimizer.ts |     100 |    94.11 |     100 |     100 | 33,76             
  terminalSetup.ts |    4.37 |      100 |       0 |    4.37 | 44-393            
  textUtils.ts     |   97.94 |    95.49 |   94.11 |   97.94 | ...82-283,443-444 
  ...background.ts |     100 |      100 |     100 |     100 |                   
  todoSnapshot.ts  |   90.42 |    92.85 |     100 |   90.42 | ...06-207,240-241 
  ...isplay-map.ts |     100 |      100 |     100 |     100 |                   
  updateCheck.ts   |     100 |    92.75 |     100 |     100 | 227-239,331       
  ...ow-keyword.ts |     100 |      100 |     100 |     100 |                   
 ...i/utils/export |   75.03 |     60.3 |   94.59 |   75.03 |                   
  collect.ts       |   71.27 |    66.38 |      96 |   71.27 | ...90-633,655-656 
  index.ts         |     100 |      100 |     100 |     100 |                   
  normalize.ts     |   80.42 |    50.68 |     100 |   80.42 | ...59-364,376-378 
  types.ts         |       0 |        0 |       0 |       0 | 1                 
  utils.ts         |     100 |      100 |     100 |     100 |                   
 ...ort/formatters |   52.92 |    47.22 |   71.42 |   52.92 |                   
  html.ts          |   84.61 |       50 |     100 |   84.61 | ...53,57-58,62-63 
  json.ts          |     100 |      100 |     100 |     100 |                   
  jsonl.ts         |   82.45 |     37.5 |     100 |   82.45 | ...48,50-51,65-66 
  markdown.ts      |   36.32 |    47.05 |      50 |   36.32 | ...16-219,233-295 
 src/ui/voice      |   81.27 |    79.92 |   81.94 |   81.27 |                   
  ...d-recorder.ts |     6.2 |      100 |       0 |     6.2 | ...33-159,162-163 
  ...o-recorder.ts |   84.61 |    93.33 |   57.14 |   84.61 | ...16-117,131-136 
  ...me-session.ts |   91.09 |     92.1 |     100 |   91.09 | ...99,305,316-319 
  sox-recorder.ts  |    92.7 |    71.87 |     100 |    92.7 | ...34-135,153-154 
  ...ailability.ts |     100 |      100 |     100 |     100 |                   
  ...e-keyterms.ts |     100 |      100 |     100 |     100 |                   
  voice-model.ts   |     100 |      100 |     100 |     100 |                   
  ...e-recorder.ts |   88.29 |    67.74 |   81.81 |   88.29 | ...,98-99,112,115 
  voice-refine.ts  |     100 |    93.33 |     100 |     100 | 92                
  ...ream-retry.ts |   86.79 |       70 |     100 |   86.79 | 16-18,48-49,59-60 
  ...am-session.ts |   88.02 |    66.66 |   84.61 |   88.02 | ...26,343-345,363 
  ...ranscriber.ts |     100 |      100 |     100 |     100 |                   
 src/utils         |   81.63 |    87.33 |   92.75 |   81.63 |                   
  ...p-profiler.ts |   98.39 |    92.59 |     100 |   98.39 | 141,185,235       
  acpModelUtils.ts |   97.36 |    95.19 |     100 |   97.36 | ...09-210,214-215 
  apiPreconnect.ts |   96.74 |    94.59 |     100 |   96.74 | 167-170           
  ...ol-call-id.ts |   84.61 |       60 |     100 |   84.61 | 26-27,37-38       
  ...ng-failure.ts |     100 |      100 |     100 |     100 |                   
  checks.ts        |   33.33 |      100 |       0 |   33.33 | 23-28             
  ...-api-error.ts |     100 |    96.42 |     100 |     100 | 14                
  cleanup.ts       |   84.05 |    94.11 |      80 |   84.05 | 80,111-121        
  commands.ts      |   97.45 |    96.66 |     100 |   97.45 | 153-155           
  ...Calculator.ts |     100 |      100 |     100 |     100 |                   
  cpuProfiler.ts   |   70.73 |    73.23 |   88.88 |   70.73 | ...27,430-431,438 
  deepMerge.ts     |     100 |    89.65 |     100 |     100 | 41-43,49          
  ...re-runtime.ts |     100 |      100 |     100 |     100 |                   
  ...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.31 |     100 |   90.65 | ...73,371,373-374 
  ...arResolver.ts |   97.14 |    96.55 |     100 |   97.14 | 125-126           
  errors.ts        |   97.56 |    94.64 |     100 |   97.56 | 69-70,304-305     
  events.ts        |     100 |      100 |     100 |     100 |                   
  ...on-mention.ts |   88.48 |     82.6 |     100 |   88.48 | ...56-160,164-168 
  gitUtils.ts      |   92.85 |    86.66 |     100 |   92.85 | ...13-116,164-167 
  ...AutoUpdate.ts |    93.1 |       94 |      90 |    93.1 | 103,108,179-190   
  ...tyWarnings.ts |     100 |      100 |     100 |     100 |                   
  ...lationInfo.ts |   97.68 |    94.28 |     100 |   97.68 | ...64,381-382,427 
  ...projection.ts |   95.27 |    95.58 |     100 |   95.27 | 140-145           
  jsonc-editor.ts  |   93.18 |    92.66 |     100 |   93.18 | ...80-381,384-385 
  languageUtils.ts |   98.88 |    97.01 |     100 |   98.88 | 184-185           
  load-undici.ts   |     100 |      100 |     100 |     100 |                   
  ...npm-update.ts |   86.64 |    77.02 |     100 |   86.64 | ...03-304,335-345 
  math.ts          |       0 |        0 |       0 |       0 | 1-15              
  ...er-mention.ts |     100 |    66.66 |     100 |     100 | 14,30,44-46       
  ...iagnostics.ts |   94.57 |    83.01 |   88.88 |   94.57 | ...05,311,315-317 
  ...serMessage.ts |     100 |      100 |     100 |     100 |                   
  ...onfigUtils.ts |   94.25 |    91.17 |     100 |   94.25 | ...30,436,439-443 
  ...iveHelpers.ts |   95.13 |    91.79 |     100 |   95.13 | ...53-454,552,565 
  osc.ts           |   97.18 |      100 |    87.5 |   97.18 | 182-183           
  package.ts       |   88.88 |    85.71 |     100 |   88.88 | 31-32             
  ...uggestions.ts |   84.29 |    70.83 |     100 |   84.29 | 70-76,92-103      
  processUtils.ts  |    92.3 |       80 |     100 |    92.3 | 45-46             
  readStdin.ts     |   93.67 |    94.11 |   85.71 |   93.67 | 79-83             
  relaunch.ts      |   95.87 |    89.28 |     100 |   95.87 | 103-105,131       
  resolvePath.ts   |     100 |      100 |     100 |     100 |                   
  runBudget.ts     |   99.35 |    96.77 |     100 |   99.35 | 119               
  sandbox-path.ts  |     100 |      100 |     100 |     100 |                   
  sandbox.ts       |   45.52 |    57.35 |   76.92 |   45.52 | ...1040,1052-1075 
  ...xImageName.ts |     100 |    77.77 |     100 |     100 | 10,18             
  sandboxMounts.ts |     100 |      100 |     100 |     100 |                   
  sessionPaths.ts  |   90.84 |    90.56 |     100 |   90.84 | ...81-182,185-186 
  settingsUtils.ts |   82.35 |    89.57 |      90 |   82.35 | ...25-743,750-758 
  spawnWrapper.ts  |     100 |      100 |     100 |     100 |                   
  ...ate-verify.ts |     100 |      100 |     100 |     100 |                   
  ...one-update.ts |   39.81 |    77.44 |   62.16 |   39.81 | ...1193,1196-1215 
  ...upProfiler.ts |   98.47 |    94.66 |     100 |   98.47 | 132-133,308       
  ...upWarnings.ts |     100 |      100 |     100 |     100 |                   
  stdioHelpers.ts  |     100 |       90 |     100 |     100 | 23                
  systemInfo.ts    |   95.12 |    90.27 |     100 |   95.12 | ...54-255,260-264 
  ...InfoFields.ts |    87.5 |    65.85 |     100 |    87.5 | ...24-125,146-147 
  ...alSequence.ts |     100 |    97.61 |     100 |     100 | 60                
  ...iffPreview.ts |   76.47 |       25 |     100 |   76.47 | 13,17,23-24       
  ...on-handler.ts |    73.8 |       75 |     100 |    73.8 | 17-18,25-26,67-73 
  ...e-relaunch.ts |   89.61 |    86.66 |      50 |   89.61 | 56-61,83-84       
  ...entEmitter.ts |     100 |      100 |     100 |     100 |                   
  ...ansionHook.ts |     100 |      100 |     100 |     100 |                   
  ...upWarnings.ts |   87.75 |       75 |     100 |   87.75 | 47-48,53-54,57-58 
  version.ts       |     100 |    66.66 |     100 |     100 | 11                
  ...ingHandler.ts |     100 |      100 |     100 |     100 |                   
  windowTitle.ts   |   96.55 |    94.73 |     100 |   96.55 | 56-57             
  ...WithBackup.ts |   65.04 |    77.77 |     100 |   65.04 | 97,112,133-172    
 ...s/housekeeping |   93.51 |    90.95 |   96.96 |   93.51 |                   
  cleanup.ts       |   92.59 |    93.75 |     100 |   92.59 | ...02-205,209-211 
  ...eractionAt.ts |     100 |      100 |     100 |     100 |                   
  scheduler.ts     |      93 |    88.34 |      95 |      93 | ...57-359,411-415 
  throttledOnce.ts |   95.95 |    93.93 |     100 |   95.95 | 77-78,153-154     
-------------------|---------|----------|---------|---------|-------------------
Core Package - Full Text Report
-------------------|---------|----------|---------|---------|-------------------
File               | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-------------------|---------|----------|---------|---------|-------------------
All files          |   87.97 |    86.51 |   89.53 |   87.97 |                   
 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        |   90.45 |    84.65 |   94.88 |   90.45 |                   
  ...transcript.ts |   88.49 |    84.09 |     100 |   88.49 | ...32,640,646-650 
  ...ent-resume.ts |   85.59 |    77.75 |   83.33 |   85.59 | ...1794-1798,1801 
  ...ound-tasks.ts |   94.63 |    90.13 |   96.38 |   94.63 | ...1773,1793-1796 
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...ent-result.ts |    96.8 |    92.68 |     100 |    96.8 | 106,129-131       
  ...n-registry.ts |   94.79 |     87.7 |     100 |   94.79 | ...1067,1081-1083 
  ...w-snapshot.ts |   92.12 |    77.14 |     100 |   92.12 | ...65,189,196-198 
 src/agents/arena  |   76.94 |    68.22 |   78.94 |   76.94 |                   
  ...gentClient.ts |   79.47 |    88.88 |   81.81 |   79.47 | ...68-183,189-204 
  ArenaManager.ts  |   75.89 |     65.2 |   78.57 |   75.89 | ...1887,1893-1894 
  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 |   78.09 |    85.23 |   76.28 |   78.09 |                   
  ITermBackend.ts  |   97.97 |    93.93 |     100 |   97.97 | ...78-180,255,307 
  ...essBackend.ts |    90.9 |    85.36 |   93.33 |    90.9 | ...70,672,674-675 
  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 |   91.22 |    86.83 |   89.31 |   91.22 |                   
  agent-context.ts |     100 |      100 |     100 |     100 |                   
  agent-core.ts    |   85.07 |     76.8 |   77.77 |   85.07 | ...2291,2337-2339 
  agent-events.ts  |     100 |      100 |     100 |     100 |                   
  ...t-headless.ts |   93.49 |    89.41 |   83.33 |   93.49 | ...96-497,500-501 
  ...nteractive.ts |   81.01 |    82.35 |   76.66 |   81.01 | ...33,535-538,541 
  ...statistics.ts |   98.29 |    82.55 |     100 |   98.29 | 141,165,206,239   
  agent-types.ts   |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...ool-policy.ts |   98.38 |      100 |    92.3 |   98.38 | 85-86             
  ...low-budget.ts |     100 |      100 |     100 |     100 |                   
  ...-scheduler.ts |   97.43 |    96.36 |     100 |   97.43 | 128-130           
  ...ow-journal.ts |   91.76 |    75.86 |     100 |   91.76 | ...38-139,179-181 
  ...chestrator.ts |   92.92 |    90.57 |   84.61 |   92.92 | ...2012,2061-2064 
  ...ow-prompts.ts |     100 |      100 |     100 |     100 |                   
  ...low-runner.ts |   94.85 |     87.5 |   92.85 |   94.85 | ...93,260,280-283 
  ...ow-sandbox.ts |   96.85 |    91.28 |     100 |   96.85 | ...1705,1711-1712 
  ...flow-saved.ts |   96.51 |    94.36 |     100 |   96.51 | 134-135,234-237   
  ...flow-stall.ts |    97.9 |    83.33 |     100 |    97.9 | 138-139,236       
 src/agents/tasks  |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/agents/team   |   82.72 |    84.65 |   89.05 |   82.72 |                   
  TeamManager.ts   |    73.6 |    80.82 |   79.62 |    73.6 | ...1706,1729-1730 
  identity.ts      |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...sionBridge.ts |     100 |      100 |     100 |     100 |                   
  mailbox.ts       |   96.02 |    87.23 |     100 |   96.02 | 352-358           
  ...ptAddendum.ts |     100 |      100 |     100 |     100 |                   
  tasks.ts         |   89.24 |    82.82 |     100 |   89.24 | ...-994,1038-1039 
  team-events.ts   |   60.52 |      100 |      50 |   60.52 | ...40-144,151-155 
  teamHelpers.ts   |   92.02 |    94.91 |   95.23 |   92.02 | ...31-332,368-378 
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...eam/test-utils |   94.39 |    94.35 |   98.21 |   94.39 |                   
  ...on-harness.ts |   96.49 |       85 |     100 |   96.49 | 128-129,141-142   
  fake-agent.ts    |   98.49 |    95.16 |     100 |   98.49 | 201-203           
  fake-backend.ts  |   86.46 |    97.61 |   95.83 |   86.46 | 124-146           
 src/config        |   84.07 |    86.63 |    75.1 |   84.07 |                   
  approval-mode.ts |     100 |      100 |     100 |     100 |                   
  ...xtDefaults.ts |     100 |      100 |     100 |     100 |                   
  config.ts        |   83.36 |    86.33 |   73.53 |   83.36 | ...8719,8723-8724 
  constants.ts     |     100 |      100 |     100 |     100 |                   
  models.ts        |     100 |      100 |     100 |     100 |                   
  storage.ts       |   94.39 |    91.57 |   88.23 |   94.39 | ...45-446,449-450 
 ...nfirmation-bus |   98.27 |    97.14 |     100 |   98.27 |                   
  message-bus.ts   |   98.14 |    97.05 |     100 |   98.14 | 42-43             
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/core          |   92.41 |    88.11 |    93.3 |   92.41 |                   
  baseLlmClient.ts |    88.4 |     83.8 |   81.81 |    88.4 | ...59,672,678-680 
  client.ts        |   92.27 |    87.55 |   91.76 |   92.27 | ...4101,4199-4200 
  ...tGenerator.ts |   86.34 |    87.34 |   84.61 |   86.34 | ...96-497,542-548 
  ...lScheduler.ts |   90.04 |    84.67 |   96.15 |   90.04 | ...6217,6245-6261 
  geminiChat.ts    |    94.7 |    90.12 |   95.53 |    94.7 | ...5052,5100-5101 
  geminiRequest.ts |     100 |      100 |     100 |     100 |                   
  genai-compat.ts  |     100 |      100 |     100 |     100 |                   
  ...MediaLimit.ts |     100 |       96 |     100 |     100 | 96                
  ...htProtocol.ts |    9.09 |      100 |       0 |    9.09 | ...9,62-66,69-110 
  ...ream-error.ts |     100 |      100 |     100 |     100 |                   
  logger.ts        |   87.41 |    87.02 |     100 |   87.41 | ...64-568,614-628 
  ...lay-buffer.ts |     100 |      100 |     100 |     100 |                   
  ...dispatcher.ts |     100 |      100 |     100 |     100 |                   
  ...tyDefaults.ts |     100 |      100 |     100 |     100 |                   
  ...olExecutor.ts |   93.54 |    83.33 |      50 |   93.54 | 49-50             
  ...on-helpers.ts |   93.49 |    78.57 |     100 |   93.49 | ...10-211,228-229 
  ...issionFlow.ts |   98.97 |    96.96 |     100 |   98.97 | 107               
  ...try-policy.ts |     100 |      100 |     100 |     100 |                   
  ...ell-policy.ts |   94.89 |    88.54 |     100 |   94.89 | ...51-252,297-298 
  prompts.ts       |   93.64 |    91.42 |   83.33 |   93.64 | ...1209,1412-1413 
  ...ing-effort.ts |     100 |      100 |     100 |     100 |                   
  ...n-recovery.ts |   95.13 |       80 |     100 |   95.13 | ...06-107,142-144 
  ...t-profiler.ts |    97.9 |    81.15 |   88.23 |    97.9 | 117,124-125,130   
  ...port-retry.ts |     100 |      100 |     100 |     100 |                   
  tokenLimits.ts   |     100 |    91.89 |     100 |     100 | 87,122-139        
  ...reparation.ts |     100 |      100 |     100 |     100 |                   
  ...tion-guard.ts |   90.38 |    94.73 |     100 |   90.38 | 68-72             
  ...allIdUtils.ts |   98.41 |    93.47 |     100 |   98.41 | 36,45             
  ...okTriggers.ts |   99.45 |    92.43 |     100 |   99.45 | 182,193           
  ...terruption.ts |     100 |     92.3 |     100 |     100 | 86,104            
  turn.ts          |   98.67 |    93.07 |     100 |   98.67 | ...79,707-708,755 
  ...l-fallback.ts |     100 |      100 |     100 |     100 |                   
 ...ntentGenerator |   96.33 |    88.12 |   96.15 |   96.33 |                   
  ...tGenerator.ts |   97.24 |    86.72 |   94.87 |   97.24 | ...1436,1465,1476 
  converter.ts     |   96.19 |    89.25 |     100 |   96.19 | ...1329,1550-1552 
  index.ts         |       0 |        0 |       0 |       0 | 1-21              
  usage.ts         |     100 |      100 |     100 |     100 |                   
 ...ntentGenerator |   88.78 |    72.36 |   89.47 |   88.78 |                   
  ...tGenerator.ts |   87.18 |    71.83 |   88.88 |   87.18 | ...58-364,382-383 
  index.ts         |     100 |       80 |     100 |     100 | 50                
 ...ntentGenerator |   95.99 |    90.61 |   90.69 |   95.99 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...tGenerator.ts |   95.92 |    89.96 |   90.24 |   95.92 | ...1272-1273,1301 
  ...tDetection.ts |     100 |      100 |     100 |     100 |                   
 ...ntentGenerator |   91.86 |    90.62 |   95.61 |   91.86 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  converter.ts     |    91.3 |    89.49 |   96.87 |    91.3 | ...1942,2111-2126 
  errorHandler.ts  |     100 |      100 |     100 |     100 |                   
  index.ts         |   68.25 |    82.35 |      50 |   68.25 | 44-53,74-78,90-94 
  ...tGenerator.ts |    66.4 |    70.58 |   88.88 |    66.4 | ...51-157,168-169 
  pipeline.ts      |   95.48 |    91.27 |     100 |   95.48 | ...1309,1317,1416 
  ...ix-caching.ts |   95.23 |    92.85 |     100 |   95.23 | 45-46,69-70       
  ...ureContext.ts |     100 |      100 |     100 |     100 |                   
  ...ingOptions.ts |       0 |        0 |       0 |       0 | 1                 
  ...CallParser.ts |   92.24 |     92.4 |     100 |   92.24 | ...28-529,549-552 
  ...kingParser.ts |     100 |    96.87 |     100 |     100 | 42                
  types.ts         |       0 |        0 |       0 |       0 | 1                 
 ...rator/provider |   97.39 |    92.28 |    98.5 |   97.39 |                   
  dashscope.ts     |   98.36 |    95.08 |   96.42 |   98.36 | ...08-709,851-852 
  deepseek.ts      |   94.91 |    89.36 |     100 |   94.91 | ...31-132,145-146 
  default.ts       |   99.18 |    97.05 |     100 |   99.18 | 208               
  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 |                   
  types.ts         |       0 |        0 |       0 |       0 |                   
  utils.ts         |     100 |      100 |     100 |     100 |                   
  zai.ts           |   92.13 |    82.14 |     100 |   92.13 | ...,39-40,135-137 
 src/extension     |   87.71 |    84.62 |   92.57 |   87.71 |                   
  ...ive-safety.ts |     100 |      100 |     100 |     100 |                   
  ...-converter.ts |   80.55 |    73.66 |     100 |   80.55 | ...1133,1179-1180 
  corruptFile.ts   |     100 |       50 |     100 |     100 | 40-45             
  ...-converter.ts |     100 |      100 |     100 |     100 |                   
  ...me-refresh.ts |     100 |      100 |     100 |     100 |                   
  ...sion-store.ts |   90.94 |    86.26 |   97.91 |   90.94 | ...1230-1236,1280 
  ...ionManager.ts |   83.89 |    82.86 |   81.72 |   83.89 | ...2832,2861-2862 
  ...references.ts |     100 |     90.9 |     100 |     100 | ...05,129,197,200 
  ...onSettings.ts |    92.3 |     94.4 |     100 |    92.3 | ...98-501,570-571 
  ...-converter.ts |    75.9 |    85.71 |   85.71 |    75.9 | ...98,202,214-248 
  github.ts        |   90.48 |    82.71 |     100 |   90.48 | ...4,994-995,1005 
  http-client.ts   |   84.61 |       80 |     100 |   84.61 | 20-21             
  i18n.ts          |   78.26 |       96 |      50 |   78.26 | 104-110,116-123   
  index.ts         |     100 |      100 |     100 |     100 |                   
  marketplace.ts   |   88.39 |    83.11 |     100 |   88.39 | ...08,494,507-508 
  ...ork-policy.ts |   89.72 |       90 |     100 |   89.72 | ...36,148-154,156 
  npm.ts           |   89.02 |    81.81 |     100 |   89.02 | ...86-688,695-700 
  override.ts      |   94.11 |    93.33 |     100 |   94.11 | 63-64,81-82       
  ...-converter.ts |   94.89 |    90.41 |     100 |   94.89 | ...50-151,222-224 
  redaction.ts     |     100 |      100 |     100 |     100 |                   
  settings.ts      |   66.26 |      100 |      50 |   66.26 | 81-107,141-146    
  ...ceRegistry.ts |   94.01 |    83.14 |     100 |   94.01 | ...38-344,365-366 
  storage.ts       |     100 |      100 |     100 |     100 |                   
  ...ableSchema.ts |     100 |      100 |     100 |     100 |                   
  variables.ts     |   88.95 |    84.21 |     100 |   88.95 | ...32-235,238-241 
  ...extraction.ts |   85.77 |       81 |   89.47 |   85.77 | ...02-205,260-261 
 ...ent-plugins-v1 |   84.94 |    79.51 |     100 |   84.94 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  manifest.ts      |   81.87 |    84.48 |     100 |   81.87 | ...55-156,161-174 
  mcp.ts           |   84.98 |    79.56 |     100 |   84.98 | ...88-389,419-420 
  paths.ts         |     100 |    94.44 |     100 |     100 | 59                
  skills.ts        |   82.31 |    63.88 |     100 |   82.31 | ...38-141,150-151 
 src/followup      |    79.9 |    78.92 |    90.9 |    79.9 |                   
  followupState.ts |   98.44 |    95.74 |     100 |   98.44 | 236-237           
  index.ts         |     100 |      100 |     100 |     100 |                   
  overlayFs.ts     |   96.29 |    88.88 |     100 |   96.29 | 78,108,122        
  speculation.ts   |   71.76 |    64.76 |   71.42 |   71.76 | ...53-654,661-662 
  ...onToolGate.ts |   97.97 |     87.5 |     100 |   97.97 | 105,110           
  ...nGenerator.ts |   72.03 |    81.15 |   83.33 |   72.03 | ...68-219,331-333 
 src/generated     |       0 |        0 |       0 |       0 |                   
  git-commit.ts    |       0 |        0 |       0 |       0 | 1-10              
 src/goals         |   93.08 |    88.52 |   94.66 |   93.08 |                   
  ...eGoalStore.ts |   87.61 |    88.88 |   86.66 |   87.61 | ...85-188,196-204 
  ...t-verifier.ts |   96.27 |     90.9 |     100 |   96.27 | ...20,143-146,163 
  ...checkpoint.ts |   81.48 |    76.19 |     100 |   81.48 | ...02-105,115-118 
  goal-evidence.ts |   89.32 |     85.9 |     100 |   89.32 | ...1162,1185-1188 
  ...projection.ts |   66.66 |    72.97 |   33.33 |   66.66 | ...87,190,194-196 
  ...ersistence.ts |   87.29 |    85.71 |    87.5 |   87.29 | ...53-154,185-190 
  goal-protocol.ts |      96 |    93.75 |     100 |      96 | 167-168           
  goal-reducer.ts  |   94.91 |    92.06 |   96.96 |   94.91 | ...27,501,519-520 
  goal-runtime.ts  |   96.86 |    89.86 |   95.74 |   96.86 | ...1295-1296,1417 
  goal-tools.ts    |   98.22 |    93.02 |      95 |   98.22 | ...46-147,248-249 
  ...rn-context.ts |     100 |      100 |     100 |     100 |                   
  goal-verifier.ts |   92.46 |    92.85 |     100 |   92.46 | ...69-172,185-187 
  goal-wire.ts     |       0 |        0 |       0 |       0 | 1-28              
  goalHook.ts      |   96.91 |    92.42 |     100 |   96.91 | 115-120,221-222   
  goalJudge.ts     |   95.84 |    87.09 |     100 |   95.84 | ...55-356,448-449 
  index.ts         |     100 |      100 |     100 |     100 |                   
 src/hooks         |   88.07 |    86.35 |   88.54 |   88.07 |                   
  ...okRegistry.ts |   86.48 |    77.08 |     100 |   86.48 | ...41-344,362-369 
  ...bortSignal.ts |     100 |      100 |     100 |     100 |                   
  context-usage.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.57 |    91.48 |     100 |   96.57 | ...20-321,402,404 
  ...entHandler.ts |   95.57 |    84.76 |   94.73 |   95.57 | ...1040-1041,1051 
  hookPlanner.ts   |   87.55 |    85.54 |   86.66 |   87.55 | ...22-226,233-244 
  hookRegistry.ts  |   92.53 |    85.43 |     100 |   92.53 | ...39,458,462,466 
  hookRunner.ts    |   62.65 |    72.34 |   66.66 |   62.65 | ...70-771,780-781 
  hookSystem.ts    |   87.64 |     98.5 |   70.83 |   87.64 | ...58-759,765-766 
  ...HookRunner.ts |   79.06 |    66.66 |      80 |   79.06 | ...33-434,452-456 
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...edCallback.ts |     100 |      100 |     100 |     100 |                   
  ...HookRunner.ts |   94.19 |    84.37 |   81.81 |   94.19 | ...76-384,458-459 
  ...SkillHooks.ts |   78.75 |       75 |   66.66 |   78.75 | 62-66,137-152     
  ...oksManager.ts |   94.87 |    88.88 |     100 |   94.87 | ...84,325,327-329 
  ssrfGuard.ts     |   86.45 |    89.13 |     100 |   86.45 | ...85,289-295,301 
  stopHookCap.ts   |     100 |      100 |     100 |     100 |                   
  trustedHooks.ts  |      90 |    52.63 |     100 |      90 | ...53,66-67,97-98 
  types.ts         |   94.25 |    96.09 |   88.88 |   94.25 | ...46-547,632-636 
  urlValidator.ts  |     100 |      100 |     100 |     100 |                   
  ...it-context.ts |     100 |      100 |     100 |     100 |                   
 src/ide           |   76.98 |    85.03 |   79.03 |   76.98 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  detect-ide.ts    |     100 |      100 |     100 |     100 |                   
  ide-client.ts    |   69.16 |    84.65 |   68.29 |   69.16 | ...1068,1097-1105 
  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           |   58.96 |    70.57 |   66.14 |   58.96 |                   
  ...nfigLoader.ts |   80.55 |       72 |   95.45 |   80.55 | ...02-504,508-514 
  ...ionFactory.ts |   42.81 |    73.07 |      50 |   42.81 | ...76-427,433-450 
  ...Normalizer.ts |   23.09 |    13.72 |   30.43 |   23.09 | ...04-905,909-924 
  ...verManager.ts |   75.73 |     80.1 |   79.66 |   75.73 | ...1346,1352-1382 
  ...eLspClient.ts |   32.78 |       80 |   16.66 |   32.78 | ...89-293,299-300 
  ...LspService.ts |      60 |    73.36 |   78.26 |      60 | ...1575,1635-1645 
  configHash.ts    |     100 |      100 |     100 |     100 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/mcp           |    82.3 |    77.81 |   78.33 |    82.3 |                   
  configHash.ts    |     100 |      100 |     100 |     100 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  ...h-provider.ts |   86.95 |      100 |   33.33 |   86.95 | ...,93,97,101-102 
  ...h-provider.ts |   79.31 |    58.06 |     100 |   79.31 | ...26-933,940-942 
  ...en-storage.ts |   98.78 |    97.95 |     100 |   98.78 | 106-107           
  oauth-utils.ts   |   73.61 |    85.48 |    92.3 |   73.61 | ...46-366,392-421 
  ...n-provider.ts |   89.83 |       96 |   45.45 |   89.83 | ...43,147,151-152 
 .../token-storage |   82.12 |    88.19 |   89.28 |   82.12 |                   
  ...en-storage.ts |     100 |      100 |     100 |     100 |                   
  ...en-storage.ts |   87.08 |    87.03 |   95.23 |   87.08 | ...00-201,214-215 
  ...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        |   87.83 |    83.81 |   90.47 |   87.83 |                   
  ...y-document.ts |   89.52 |    84.61 |     100 |   89.52 | ...24-325,329-330 
  ...nel-memory.ts |   97.36 |    96.63 |   96.42 |   97.36 | ...91-293,367-368 
  const.ts         |   94.28 |     92.3 |     100 |   94.28 | 66-67             
  dream.ts         |    64.6 |    72.22 |      50 |    64.6 | ...04-109,124-165 
  ...entPlanner.ts |     100 |    83.33 |     100 |     100 | 136,146           
  entries.ts       |   75.59 |    84.84 |   83.33 |   75.59 | ...56-157,172-180 
  extract.ts       |   92.41 |    79.41 |     100 |   92.41 | 56-61,100,119-122 
  ...entPlanner.ts |   91.59 |    76.74 |     100 |   91.59 | ...05,114-117,293 
  ...ionPlanner.ts |       0 |        0 |       0 |       0 | 1                 
  forget.ts        |   81.83 |       75 |   83.33 |   81.83 | ...51,474,478-507 
  indexer.ts       |   94.14 |       84 |     100 |   94.14 | ...32-233,334,337 
  ...kill-agent.ts |   97.94 |    89.36 |     100 |   97.94 | 82-83,179-180     
  manager.ts       |    78.4 |    82.29 |   77.77 |    78.4 | ...1482,1495-1497 
  ...ent-config.ts |   86.99 |    82.69 |   86.36 |   86.99 | ...69,389,396-402 
  memoryAge.ts     |   90.47 |       80 |     100 |   90.47 | 50-51             
  paths.ts         |     100 |      100 |     100 |     100 |                   
  ...ing-skills.ts |     100 |       72 |     100 |     100 | 31-35,73-78,97    
  prompt.ts        |   97.26 |    86.79 |     100 |   97.26 | ...10-218,222,225 
  recall.ts        |   82.06 |       75 |    90.9 |   82.06 | ...59-364,395-406 
  refresh.ts       |   93.58 |    89.58 |     100 |   93.58 | ...75-176,183-184 
  ...ceSelector.ts |    93.1 |    81.81 |     100 |    93.1 | ...25,127-128,136 
  remember.ts      |   98.89 |    90.19 |     100 |   98.89 | 50,70             
  scan.ts          |   93.12 |    77.41 |     100 |   93.12 | ...08-109,154,157 
  scopes.ts        |     100 |      100 |     100 |     100 |                   
  ...et-scanner.ts |     100 |      100 |     100 |     100 |                   
  ...entPlanner.ts |   77.24 |    74.07 |   72.22 |   77.24 | ...52-456,459,465 
  status.ts        |   10.52 |      100 |       0 |   10.52 | 41-98             
  store.ts         |   92.92 |    81.81 |     100 |   92.92 | ...16-117,147-148 
  ...git-status.ts |     100 |     87.5 |     100 |     100 | 30                
  ...cret-guard.ts |     100 |      100 |     100 |     100 |                   
  ...emory-sync.ts |   94.24 |    82.85 |     100 |   94.24 | ...34-236,246-247 
  types.ts         |     100 |      100 |     100 |     100 |                   
  ...ontextFile.ts |   81.21 |     79.1 |   81.81 |   81.21 | ...63-277,291-296 
 src/mocks         |       0 |        0 |       0 |       0 |                   
  msw.ts           |       0 |        0 |       0 |       0 | 1-9               
 src/models        |   92.55 |    88.97 |   91.13 |   92.55 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  ...tor-config.ts |   97.77 |    91.83 |     100 |   97.77 | 155,161,171       
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...nfigErrors.ts |   74.22 |    47.82 |   84.61 |   74.22 | ...,67-74,106-117 
  ...igResolver.ts |   98.71 |    93.33 |     100 |   98.71 | 166,328,334       
  modelRegistry.ts |     100 |    98.11 |     100 |     100 | 177,261           
  modelsConfig.ts  |   89.36 |    86.93 |   88.09 |   89.36 | ...1404,1433-1434 
  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   |   83.79 |    91.16 |   71.07 |   83.79 |                   
  autoMode.ts      |   97.66 |    93.13 |     100 |   97.66 | ...82-589,635,712 
  ...transcript.ts |      98 |       84 |     100 |      98 | 200-201           
  classifier.ts    |      94 |    94.54 |     100 |      94 | 158-165,389-393   
  ...erousRules.ts |     100 |    89.36 |     100 |     100 | 110,133,147,175   
  ...alTracking.ts |     100 |      100 |     100 |     100 |                   
  ...e-commands.ts |   86.77 |     73.8 |     100 |   86.77 | 131-141,210-214   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...on-manager.ts |   86.63 |    88.88 |      80 |   86.63 | ...1111,1217-1221 
  rule-parser.ts   |   94.49 |    92.72 |     100 |   94.49 | ...1447,1481-1483 
  ...-semantics.ts |   70.44 |    91.09 |   46.66 |   70.44 | ...2237,2311-2314 
  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 | 220               
 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     |   83.71 |     78.6 |   81.25 |   83.71 |                   
  all-providers.ts |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  install.ts       |   93.11 |     84.5 |     100 |   93.11 | ...56-257,330-331 
  ...der-config.ts |   75.85 |    74.04 |   78.26 |   75.85 | ...73-474,502-503 
  types.ts         |       0 |        0 |       0 |       0 | 1                 
 ...viders/presets |   97.82 |    91.66 |   63.63 |   97.82 |                   
  ...oding-plan.ts |   87.34 |      100 |       0 |   87.34 | 81-83,86-88,90-93 
  ...a-standard.ts |     100 |      100 |     100 |     100 |                   
  ...token-plan.ts |     100 |      100 |     100 |     100 |                   
  ...m-provider.ts |   97.05 |    81.25 |      75 |   97.05 | 118-119           
  deepseek.ts      |     100 |      100 |     100 |     100 |                   
  grok.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 |                   
  requesty.ts      |     100 |      100 |     100 |     100 |                   
  zai.ts           |     100 |      100 |     100 |     100 |                   
 src/qwen          |   85.41 |    78.76 |   95.89 |   85.41 |                   
  ...tGenerator.ts |   98.64 |    98.18 |     100 |   98.64 | 105-106           
  qwenOAuth2.ts    |   82.79 |    73.75 |   90.62 |   82.79 | ...1205-1221,1251 
  ...kenManager.ts |   85.36 |    76.61 |     100 |   85.36 | ...52-757,778-783 
 src/resources     |     100 |      100 |     100 |     100 |                   
  ...e-registry.ts |     100 |      100 |     100 |     100 |                   
 src/services      |   89.99 |    85.44 |   96.91 |   89.99 |                   
  ...ionTrailer.ts |     100 |      100 |     100 |     100 |                   
  ...llRegistry.ts |   98.48 |    87.28 |     100 |   98.48 | 81-82,105,474-475 
  ...ionService.ts |   97.51 |    96.15 |     100 |   97.51 | ...,929,1072-1080 
  ...ingService.ts |   91.75 |    85.48 |   95.83 |   91.75 | ...2197,2224-2225 
  ...ttribution.ts |   91.73 |    87.71 |      90 |   91.73 | ...80-685,826-827 
  ...utSlimming.ts |    97.2 |    94.17 |     100 |    97.2 | ...39-340,378-381 
  cronScheduler.ts |   94.17 |    90.45 |      98 |   94.17 | ...1333,1736-1737 
  cronTasksFile.ts |   95.49 |    90.82 |     100 |   95.49 | ...37,346-347,483 
  cronTasksLock.ts |   94.44 |    89.47 |     100 |   94.44 | ...02-103,132-133 
  ...eryService.ts |   96.22 |    93.54 |      90 |   96.22 | 121,155-156,161   
  ...oryService.ts |   88.17 |    79.02 |    92.3 |   88.17 | ...1303,1344-1347 
  fileReadCache.ts |    97.5 |    96.07 |     100 |    97.5 | 349-350,363-364   
  ...temService.ts |    92.8 |    84.68 |   94.11 |    92.8 | ...53,479-486,531 
  ...ratedFiles.ts |      96 |    88.23 |     100 |      96 | 119-120,146-147   
  gitInit.ts       |     100 |      100 |     100 |     100 |                   
  ...reeService.ts |    73.7 |    68.49 |   95.83 |    73.7 | ...2196,2225-2226 
  ...on-service.ts |   87.38 |       72 |     100 |   87.38 | ...01-305,343-344 
  ...references.ts |   98.39 |    88.88 |     100 |   98.39 | 154-155,215-216   
  ...ionService.ts |   98.26 |    97.35 |     100 |   98.26 | ...13-714,761-762 
  ...ticsDumper.ts |   98.37 |    95.23 |     100 |   98.37 | 185-186           
  ...ureMonitor.ts |   95.82 |    90.52 |   97.05 |   95.82 | ...60,861,875-877 
  ...orRegistry.ts |   97.22 |    90.99 |     100 |   97.22 | ...55-456,609-610 
  ...ttachments.ts |   97.74 |     90.9 |     100 |   97.74 | 298-308,646       
  ...pi-history.ts |   98.94 |    88.88 |     100 |   98.94 | 43                
  ...ersistence.ts |   91.66 |    80.75 |     100 |   91.66 | ...1060-1061,1089 
  ...tory-state.ts |     100 |    95.23 |     100 |     100 | 31                
  ...on-service.ts |   94.49 |    92.26 |   97.14 |   94.49 | ...98-600,656-664 
  ...ce-service.ts |    98.5 |    94.11 |    90.9 |    98.5 | 64-65             
  ...ken-counts.ts |     100 |       96 |     100 |     100 | 58                
  ...ipt-reader.ts |   93.59 |    91.41 |    97.7 |   93.59 | ...2643-2644,2712 
  ...turn-state.ts |   94.11 |     90.9 |   91.66 |   94.11 | 108-112,129-130   
  ...est-helper.ts |       0 |        0 |       0 |       0 | 1-65              
  ...iter-lease.ts |   82.45 |    74.11 |   97.61 |   82.45 | ...2433,2445-2448 
  sessionRecap.ts  |   67.56 |    43.47 |     100 |   67.56 | ...60,178,180-183 
  ...ionService.ts |   88.31 |    85.37 |   95.58 |   88.31 | ...2342,2356-2376 
  sessionTitle.ts  |   95.75 |    77.41 |     100 |   95.75 | ...53-256,287-288 
  ...ionService.ts |   84.43 |    78.45 |   97.18 |   84.43 | ...2496,2502-2507 
  ...pInhibitor.ts |   97.42 |    92.77 |     100 |   97.42 | ...30,169,369-370 
  ...Estimation.ts |     100 |    94.11 |     100 |     100 | 118               
  ...ageService.ts |   97.76 |    91.59 |   93.75 |   97.76 | ...61-262,366,567 
  ...ite-origin.ts |     100 |    93.33 |     100 |     100 | 32                
  ...UseSummary.ts |   94.63 |    88.46 |     100 |   94.63 | ...62-164,214-215 
  ...rd-service.ts |     100 |    88.37 |     100 |     100 | ...29,145-146,241 
  ...oryService.ts |   90.72 |    84.07 |     100 |   90.72 | ...06-509,561-562 
  ...reeCleanup.ts |   14.42 |      100 |   33.33 |   14.42 | 58-186            
  ...ionService.ts |   88.36 |     87.7 |     100 |   88.36 | ...48-449,465-466 
 ...icrocompaction |    98.9 |    95.08 |     100 |    98.9 |                   
  microcompact.ts  |    98.9 |    95.08 |     100 |    98.9 | ...40,749,758-759 
 ...s/visionBridge |   98.81 |    92.12 |     100 |   98.81 |                   
  ...capability.ts |     100 |      100 |     100 |     100 |                   
  ...part-utils.ts |     100 |      100 |     100 |     100 |                   
  ...ion-bridge.ts |   98.72 |    82.35 |     100 |   98.72 | 65,71             
  ...-constants.ts |     100 |      100 |     100 |     100 |                   
  ...ge-service.ts |   98.61 |     94.7 |     100 |   98.61 | ...06,666,679-680 
 src/skills        |   89.29 |    85.92 |   93.61 |   89.29 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...activation.ts |     100 |    93.33 |     100 |     100 | 93,112            
  skill-curator.ts |   89.71 |    81.54 |     100 |   89.71 | ...01-902,904-907 
  skill-load.ts    |   94.84 |    87.69 |     100 |   94.84 | ...03,223,235-237 
  skill-manager.ts |   84.82 |    85.29 |   83.33 |   84.82 | ...1243,1250-1254 
  skill-paths.ts   |   90.42 |     87.5 |     100 |   90.42 | ...19-120,125-126 
  symlinkScope.ts  |     100 |      100 |     100 |     100 |                   
  types.ts         |   97.91 |    98.03 |     100 |   97.91 | 277-278           
 ...ataviz/scripts |   80.06 |    95.23 |   88.23 |   80.06 |                   
  ...te_palette.js |   80.06 |    95.23 |   88.23 |   80.06 | 261-296,306-328   
 ...s/bundled/loop |   97.48 |    95.77 |     100 |   97.48 |                   
  ...omous-loop.ts |     100 |      100 |     100 |     100 |                   
  ...-task-file.ts |   94.85 |     92.4 |     100 |   94.85 | ...56,367,375-376 
  ...k-resolver.ts |     100 |      100 |     100 |     100 |                   
 src/subagents     |   87.72 |    89.01 |   96.55 |   87.72 |                   
  ...ter-schema.ts |     100 |    98.07 |     100 |     100 | 99                
  ...tin-agents.ts |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...nt-manager.ts |   84.48 |    85.91 |   94.87 |   84.48 | ...1582,1659-1660 
  types.ts         |     100 |      100 |     100 |     100 |                   
  validation.ts    |   92.46 |    95.18 |     100 |   92.46 | 47-52,63-68,71-76 
 src/telemetry     |    82.4 |    84.26 |   85.74 |    82.4 |                   
  ...ty-tracker.ts |     100 |      100 |     100 |     100 |                   
  config.ts        |     100 |      100 |     100 |     100 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  ...on-metrics.ts |   99.08 |    80.95 |     100 |   99.08 | 185,199           
  ...on-tracing.ts |   76.92 |    75.71 |   73.68 |   76.92 | ...88,395-397,413 
  ...attributes.ts |   96.98 |    91.37 |     100 |   96.98 | ...47-348,366-367 
  ...ag-metrics.ts |     100 |    77.77 |     100 |     100 | 21,40             
  ...t-loop-lag.ts |   96.85 |    85.71 |     100 |   96.85 | 170-173           
  ...-exporters.ts |   65.78 |    83.33 |   55.55 |   65.78 | ...04-105,108-109 
  ...ai-content.ts |    74.5 |    66.41 |   91.66 |    74.5 | ...1480,1493-1502 
  ...i-provider.ts |     100 |       99 |     100 |     100 | 99                
  ...ai-request.ts |   87.52 |    92.79 |   83.78 |   87.52 | ...55-561,564-570 
  gen-ai-usage.ts  |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...t.circular.ts |       0 |        0 |       0 |       0 | 1-111             
  ...-processor.ts |   99.12 |    96.03 |      95 |   99.12 | 150,379-380       
  ...t.circular.ts |       0 |        0 |       0 |       0 | 1-128             
  loggers.ts       |   60.73 |    78.01 |   66.66 |   60.73 | ...1507,1524-1544 
  metrics.ts       |   80.37 |    82.35 |   80.95 |   80.37 | ...1150,1153-1164 
  otlp-urls.ts     |     100 |      100 |     100 |     100 |                   
  ...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       
  ...rters-grpc.ts |     100 |      100 |     100 |     100 |                   
  ...rters-http.ts |     100 |      100 |     100 |     100 |                   
  sdk-impl.ts      |   93.89 |    86.32 |      75 |   93.89 | ...39,489-490,506 
  sdk.ts           |    82.7 |     90.9 |   66.66 |    82.7 | ...00-204,242-264 
  ...on-context.ts |     100 |      100 |     100 |     100 |                   
  ...ion-events.ts |     100 |      100 |     100 |     100 |                   
  ...on-tracing.ts |   91.13 |    88.64 |    97.5 |   91.13 | ...1914,1943-1946 
  ...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         |   83.09 |    88.31 |   86.36 |   83.09 | ...1467,1471-1478 
  uiTelemetry.ts   |   97.18 |    93.93 |      88 |   97.18 | ...70,314,461-462 
 ...ry/qwen-logger |   74.23 |     80.7 |      70 |   74.23 |                   
  event-types.ts   |       0 |        0 |       0 |       0 |                   
  qwen-logger.ts   |   74.23 |    80.53 |   69.49 |   74.23 | ...1122,1160-1161 
 src/test-utils    |   96.38 |    98.61 |   83.33 |   96.38 |                   
  config.ts        |     100 |      100 |     100 |     100 |                   
  ...st-helpers.ts |   94.11 |       90 |     100 |   94.11 | 69-70             
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...mised-lock.ts |     100 |      100 |     100 |     100 |                   
  mock-tool.ts     |   94.85 |      100 |   78.78 |   94.85 | ...53,227-228,241 
  ...aceContext.ts |     100 |      100 |     100 |     100 |                   
 src/tools         |   86.28 |    85.09 |   88.72 |   86.28 |                   
  ...erQuestion.ts |   89.71 |    80.76 |   91.66 |   89.71 | ...66-367,374-375 
  ...-registrar.ts |    77.7 |    66.66 |   66.66 |    77.7 | ...72-277,292-294 
  ...ub-session.ts |   89.67 |     91.3 |   81.81 |   89.67 | ...03-304,315-322 
  cron-create.ts   |   90.64 |    92.85 |   72.72 |   90.64 | ...,73-74,223-231 
  cron-delete.ts   |   97.56 |      100 |   83.33 |   97.56 | 31-32             
  cron-list.ts     |   98.23 |    95.34 |    87.5 |   98.23 | 57-58             
  diffOptions.ts   |     100 |      100 |     100 |     100 |                   
  display-image.ts |   87.42 |    84.84 |   88.88 |   87.42 | ...29-134,194-195 
  edit.ts          |   82.76 |    86.77 |   81.25 |   82.76 | ...45-746,865-915 
  ...r-worktree.ts |   83.14 |    67.56 |    87.5 |   83.14 | ...84-187,278-279 
  enterPlanMode.ts |      85 |     82.6 |    87.5 |      85 | ...28-133,161-175 
  exit-worktree.ts |   83.29 |    83.65 |   94.44 |   83.29 | ...14-515,537-538 
  exitPlanMode.ts  |      95 |    85.29 |     100 |      95 | ...21-325,344,378 
  ...permission.ts |     100 |      100 |     100 |     100 |                   
  glob.ts          |   96.33 |     88.5 |     100 |   96.33 | ...24-225,373,376 
  grep.ts          |   90.73 |    86.61 |   85.71 |   90.73 | ...76-677,727-728 
  ...adTracking.ts |     100 |      100 |     100 |     100 |                   
  image-gen.ts     |   91.66 |    77.41 |    90.9 |   91.66 | ...13-214,221-222 
  list-agents.ts   |   94.02 |    82.35 |   83.33 |   94.02 | 31-32,47-48       
  loop-wakeup.ts   |   99.27 |    92.85 |     100 |   99.27 | 45                
  ls.ts            |   96.74 |    90.27 |     100 |   96.74 | 176-181,212,216   
  lsp.ts           |   72.71 |     59.5 |   90.32 |   72.71 | ...1212,1214-1215 
  ...nt-manager.ts |   82.13 |    80.47 |   85.71 |   82.13 | ...3234,3236-3237 
  mcp-client.ts    |   80.03 |    86.58 |   89.47 |   80.03 | ...2272,2276-2279 
  ...ry-timeout.ts |     100 |      100 |     100 |     100 |                   
  mcp-errors.ts    |     100 |      100 |     100 |     100 |                   
  ...pool-entry.ts |   79.21 |    85.71 |   81.57 |   79.21 | ...1341,1349-1350 
  ...ool-events.ts |       8 |      100 |       0 |       8 | 132-158           
  mcp-pool-key.ts  |   97.46 |    93.93 |     100 |   97.46 | 176-177           
  ...ce-content.ts |   96.55 |    91.17 |     100 |   96.55 | 80-82             
  mcp-retry.ts     |   97.67 |    95.65 |     100 |   97.67 | 131-132           
  ...ion-config.ts |     100 |      100 |     100 |     100 |                   
  mcp-status.ts    |     100 |      100 |     100 |     100 |                   
  mcp-tool.ts      |   98.35 |    93.71 |     100 |   98.35 | ...-990,1045-1046 
  ...sport-pool.ts |   83.98 |     80.3 |   88.46 |   83.98 | ...1409,1416-1420 
  ...ace-budget.ts |   87.27 |     82.6 |     100 |   87.27 | ...00-305,340-345 
  memory-config.ts |     100 |      100 |     100 |     100 |                   
  ...iable-tool.ts |     100 |    84.61 |     100 |     100 | 101,108           
  monitor.ts       |   91.82 |    83.09 |   88.46 |   91.82 | ...99,612,810-815 
  notebook-edit.ts |   85.71 |    77.08 |   81.25 |   85.71 | ...96-912,958-959 
  ...escendants.ts |   36.17 |    64.51 |   55.55 |   36.17 | ...46-310,385-390 
  ...nforcement.ts |   83.21 |    90.69 |     100 |   83.21 | 147-158,207-220   
  read-file.ts     |   95.49 |    88.52 |   86.66 |   95.49 | ...49,464,536-537 
  ...p-resource.ts |   96.85 |      100 |   91.66 |   96.85 | 92-96             
  ...d-artifact.ts |   91.18 |    86.71 |    87.5 |   91.18 | ...26-427,441-453 
  ripGrep.ts       |    94.6 |    87.26 |   95.23 |    94.6 | ...33-734,740-741 
  ...-transport.ts |   71.42 |    55.55 |   71.42 |   71.42 | ...36-137,143-144 
  send-message.ts  |   81.13 |    89.74 |    62.5 |   81.13 | ...80-286,363-371 
  ...n-mcp-view.ts |   94.07 |    91.89 |    90.9 |   94.07 | 131-139           
  shell.ts         |   78.85 |    84.22 |   91.91 |   78.85 | ...5040,5103-5104 
  skill-utils.ts   |     100 |      100 |     100 |     100 |                   
  skill.ts         |   91.39 |    92.55 |      90 |   91.39 | ...84,488,534-556 
  ...eticOutput.ts |   95.12 |      100 |      80 |   95.12 | 87-88             
  task-create.ts   |    94.4 |    93.33 |   81.81 |    94.4 | 45-49,63-64,95    
  task-list.ts     |   78.22 |    84.21 |   83.33 |   78.22 | ...66,105,109-116 
  task-stop.ts     |   93.14 |    96.15 |   85.71 |   93.14 | 39-40,54-64       
  task-update.ts   |   82.89 |    83.92 |    92.3 |   82.89 | ...14-422,454-465 
  team-create.ts   |   97.22 |    85.71 |   83.33 |   97.22 | 48-49,129-130     
  team-delete.ts   |   86.74 |    83.33 |   83.33 |   86.74 | 37-38,42-48,72-73 
  ...n-approval.ts |   92.14 |    96.77 |   77.77 |   92.14 | 38-39,42-43,93-99 
  todoWrite.ts     |   95.13 |    87.85 |   93.33 |   95.13 | ...23-527,540-545 
  tool-error.ts    |     100 |      100 |     100 |     100 |                   
  tool-names.ts    |     100 |      100 |     100 |     100 |                   
  tool-registry.ts |   78.57 |    79.59 |    82.6 |   78.57 | ...89-990,998-999 
  tool-search.ts   |   96.19 |    89.72 |   93.33 |   96.19 | ...09,259-264,426 
  tools.ts         |   93.11 |    92.53 |   91.66 |   93.11 | ...69-570,586-592 
  ...reapproved.ts |   99.27 |    94.11 |     100 |   99.27 | 170               
  web-fetch.ts     |   96.05 |    90.54 |   96.77 |   96.05 | ...85-786,800-801 
  web-search.ts    |   90.58 |    83.57 |      80 |   90.58 | ...1025,1083-1086 
  write-file.ts    |   86.72 |    84.92 |   88.88 |   86.72 | ...25-828,865-900 
  zoom-image.ts    |   95.76 |    93.75 |      90 |   95.76 | 54-59,203-204     
 src/tools/agent   |   86.92 |    87.48 |   88.59 |   86.92 |                   
  agent.ts         |   85.51 |    86.38 |   86.17 |   85.51 | ...4333,4367-4377 
  fork-profile.ts  |   93.65 |       90 |     100 |   93.65 | ...33-134,171-174 
  fork-subagent.ts |   98.73 |       95 |     100 |   98.73 | 101-102,173       
 ...tools/artifact |   95.78 |    92.51 |   88.63 |   95.78 |                   
  artifact-tool.ts |   91.46 |    88.46 |   71.42 |   91.46 | ...13-314,322-325 
  ...-publisher.ts |     100 |    85.71 |     100 |     100 | 32                
  ...-publisher.ts |   96.74 |    97.72 |    87.5 |   96.74 | 29-30,156-157     
  html.ts          |     100 |    96.77 |     100 |     100 | 122               
  ...-publisher.ts |     100 |       80 |     100 |     100 | 30                
  oss-publisher.ts |    98.1 |    91.48 |     100 |    98.1 | 43-45             
  publisher.ts     |     100 |      100 |     100 |     100 |                   
 ...s/computer-use |   90.21 |    82.17 |   78.08 |   90.21 |                   
  bootstrap.ts     |   59.42 |    80.95 |   41.66 |   59.42 | ...35-339,341-345 
  client.ts        |   80.11 |       90 |   77.77 |   80.11 | ...97,242-243,274 
  constants.ts     |     100 |    94.73 |     100 |     100 | 129,256           
  downloader.ts    |   65.29 |    52.77 |   58.33 |   65.29 | ...99-300,316-355 
  index.ts         |     100 |      100 |     100 |     100 |                   
  install-state.ts |   94.44 |    72.72 |     100 |   94.44 | 44-45             
  ...n-detector.ts |     100 |     87.5 |     100 |     100 | 50                
  schemas.ts       |     100 |      100 |     100 |     100 |                   
  tool.ts          |    96.3 |    85.71 |     100 |    96.3 | 75-76,184,252-258 
 ...tools/workflow |   86.51 |    84.81 |      75 |   86.51 |                   
  workflow.ts      |   86.51 |    84.81 |      75 |   86.51 | ...67,512,514-515 
 src/utils         |   92.92 |    89.66 |    96.9 |   92.92 |                   
  LruCache.ts      |     100 |      100 |     100 |     100 |                   
  ...Controller.ts |     100 |      100 |     100 |     100 |                   
  ...ssageQueue.ts |     100 |      100 |     100 |     100 |                   
  ...cFileWrite.ts |   94.94 |    92.47 |     100 |   94.94 | ...43-544,651-655 
  bareMode.ts      |   81.81 |      100 |      50 |   81.81 | 18-19             
  ...ry-content.ts |   98.45 |    95.45 |     100 |   98.45 | 132-133,159-160   
  browser.ts       |   86.84 |    78.94 |     100 |   86.84 | 34,36-37,65-66    
  btwUtils.ts      |   13.95 |      100 |       0 |   13.95 | 17-31,34-55       
  bundlePaths.ts   |     100 |      100 |     100 |     100 |                   
  ...on-context.ts |     100 |      100 |     100 |     100 |                   
  ...igResolver.ts |     100 |      100 |     100 |     100 |                   
  ...engthError.ts |   91.06 |    89.47 |     100 |   91.06 | ...46-147,154-155 
  ...n-branches.ts |   95.88 |    94.11 |      95 |   95.88 | ...98-499,511-524 
  ...tion-chain.ts |     100 |      100 |     100 |     100 |                   
  cronDisplay.ts   |     100 |    97.61 |     100 |     100 | 46                
  cronParser.ts    |   95.34 |    93.33 |     100 |   95.34 | 41-42,47-48,70-71 
  debugLogger.ts   |   96.66 |    96.61 |   88.88 |   96.66 | 192-196           
  editHelper.ts    |   93.63 |     83.9 |     100 |   93.63 | ...27-428,462-463 
  editor.ts        |   97.65 |    95.45 |     100 |   97.65 | ...35-336,338-339 
  encoding.ts      |     100 |      100 |     100 |     100 |                   
  env.ts           |     100 |      100 |     100 |     100 |                   
  ...arResolver.ts |   94.28 |    88.88 |     100 |   94.28 | 28-29,125-126     
  ...entContext.ts |   96.63 |    90.13 |   96.66 |   96.63 | ...42,444-445,512 
  errorParsing.ts  |     100 |      100 |     100 |     100 |                   
  ...rReporting.ts |   95.65 |    93.33 |     100 |   95.65 | 37-38             
  errors.ts        |   83.39 |    95.17 |    61.9 |   83.39 | ...81-397,401-407 
  fetch.ts         |   90.68 |    82.51 |     100 |   90.68 | ...72,483-484,503 
  file-identity.ts |     100 |      100 |     100 |     100 |                   
  fileUtils.ts     |   94.87 |    92.97 |   96.15 |   94.87 | ...1907,1915-1916 
  forkedAgent.ts   |   92.45 |    82.35 |   93.75 |   92.45 | ...34,642,647-654 
  formatters.ts    |     100 |      100 |     100 |     100 |                   
  ...eUtilities.ts |    92.4 |    86.95 |     100 |    92.4 | ...52-158,168-169 
  ...rStructure.ts |   94.39 |    94.28 |     100 |   94.39 | ...29-132,343-348 
  getPty.ts        |   31.57 |       50 |     100 |   31.57 | 26-38             
  git-branches.ts  |    91.6 |    84.21 |    92.3 |    91.6 | ...90,405-410,570 
  ...fig-safety.ts |   97.01 |       80 |     100 |   97.01 | 53-54             
  gitDiff.ts       |   95.19 |    81.36 |     100 |   95.19 | ...1073,1419-1420 
  gitDirect.ts     |   98.84 |    94.28 |     100 |   98.84 | 234,318           
  ...noreParser.ts |   94.48 |    93.22 |     100 |   94.48 | ...23-124,158-159 
  gitUtils.ts      |   78.83 |    82.35 |    87.5 |   78.83 | ...22-123,164-215 
  github-prs.ts    |   95.74 |    82.27 |     100 |   95.74 | 216,314-322       
  iconvHelper.ts   |     100 |      100 |     100 |     100 |                   
  ...rePatterns.ts |     100 |      100 |     100 |     100 |                   
  image-view.ts    |   95.08 |    93.33 |     100 |   95.08 | ...62-166,234-238 
  ...ionManager.ts |     100 |     90.9 |     100 |     100 | 27                
  ...lPromptIds.ts |     100 |      100 |     100 |     100 |                   
  ...on-context.ts |     100 |      100 |     100 |     100 |                   
  jsonl-utils.ts   |   95.41 |    93.61 |     100 |   95.41 | ...27-328,370-373 
  ...-detection.ts |     100 |      100 |     100 |     100 |                   
  ...iconv-lite.ts |     100 |      100 |     100 |     100 |                   
  ...simple-git.ts |   96.77 |    91.66 |     100 |   96.77 | 38                
  ...m-headless.ts |      96 |    88.88 |     100 |      96 | 34                
  ...iagnostics.ts |    96.4 |     94.2 |     100 |    96.4 | ...66,293-294,376 
  ...yDiscovery.ts |    92.4 |    89.13 |     100 |    92.4 | ...28,331,522-525 
  ...tProcessor.ts |   94.01 |       90 |     100 |   94.01 | ...47-353,445-446 
  ...Inspectors.ts |     100 |      100 |     100 |     100 |                   
  modelId.ts       |   98.96 |    98.21 |     100 |   98.96 | 153               
  ...kerChecker.ts |    90.9 |    91.66 |     100 |    90.9 | 73-79             
  notebook.ts      |   94.57 |    89.91 |   95.83 |   94.57 | ...21,333,385-387 
  openaiLogger.ts  |   91.66 |    89.74 |     100 |   91.66 | ...26-228,251-256 
  osc8.ts          |   54.26 |    64.86 |   83.33 |   54.26 | ...72-195,197-257 
  partUtils.ts     |     100 |    98.64 |     100 |     100 | 211               
  pathReader.ts    |     100 |      100 |     100 |     100 |                   
  paths.ts         |   93.61 |    92.42 |     100 |   93.61 | ...62-563,565-567 
  pdf.ts           |   92.17 |    85.81 |     100 |   92.17 | ...64-565,606-611 
  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 |   71.15 |       86 |     100 |   71.15 | ...-90,96-101,147 
  ...noreParser.ts |   92.63 |    91.66 |     100 |   92.63 | ...77-178,197-198 
  rateLimit.ts     |   93.75 |    89.62 |     100 |   93.75 | ...13,218-219,262 
  ...text-range.ts |   96.98 |    87.15 |     100 |   96.98 | ...87-688,763-764 
  readManyFiles.ts |   95.75 |    80.86 |     100 |   95.75 | ...05,558,568-572 
  retry.ts         |   96.09 |    92.52 |     100 |   96.09 | ...67,558-559,577 
  retryContext.ts  |     100 |      100 |     100 |     100 |                   
  ...sification.ts |   97.63 |    97.08 |     100 |   97.63 | ...17,251-252,278 
  retryPolicy.ts   |   97.72 |    90.56 |     100 |   97.72 | 130-131           
  ripgrepUtils.ts  |   90.04 |    93.43 |   95.45 |   90.04 | ...55-565,598-599 
  ...sDiscovery.ts |   97.46 |    93.05 |     100 |   97.46 | ...04,182-183,202 
  ...iagnostics.ts |   83.08 |     67.5 |   92.59 |   83.08 | ...23,543-544,550 
  ...tchOptions.ts |   84.87 |    86.71 |   96.29 |   84.87 | ...71,696,725-734 
  ...odelPrefix.ts |     100 |      100 |     100 |     100 |                   
  runtimeStatus.ts |   97.77 |    91.48 |     100 |   97.77 | 172-173           
  safe-mode.ts     |     100 |      100 |     100 |     100 |                   
  safeJsonParse.ts |     100 |      100 |     100 |     100 |                   
  ...nStringify.ts |     100 |      100 |     100 |     100 |                   
  ...-child-env.ts |     100 |      100 |     100 |     100 |                   
  ...aConverter.ts |   98.03 |    97.75 |     100 |   98.03 | 100,102-103       
  ...aValidator.ts |   92.09 |    83.65 |   90.47 |   92.09 | ...60,882-883,896 
  ...r-launcher.ts |   96.35 |    93.97 |   85.71 |   96.35 | ...35-336,347-348 
  sedEditParser.ts |   91.78 |    92.18 |     100 |   91.78 | ...66-569,645-646 
  ...nIdContext.ts |     100 |      100 |     100 |     100 |                   
  ...orageUtils.ts |   96.21 |    84.95 |     100 |   96.21 | ...70,386,466,485 
  ...-pager-env.ts |     100 |      100 |     100 |     100 |                   
  ...fety-rules.ts |     100 |     89.7 |     100 |     100 | ...01,304,309-311 
  shell-utils.ts   |   86.07 |    88.36 |     100 |   86.07 | ...2269,2276-2280 
  ...lAstParser.ts |   98.27 |    91.38 |     100 |   98.27 | ...1321-1323,1333 
  ...ContextEnv.ts |     100 |       92 |     100 |     100 | 50-52             
  ...nlyChecker.ts |   96.33 |    96.57 |     100 |   96.33 | ...83-284,292-293 
  sideQuery.ts     |   86.82 |    86.66 |     100 |   86.82 | ...79-185,187-193 
  ...pEventSink.ts |     100 |       80 |     100 |     100 | 61                
  ...tGenerator.ts |     100 |      100 |     100 |     100 |                   
  ...ameContext.ts |     100 |      100 |     100 |     100 |                   
  symlink.ts       |   77.77 |       50 |     100 |   77.77 | 44,54-59          
  ...e-encoding.ts |   85.96 |    76.47 |     100 |   85.96 | 58-61,64-65,78-79 
  ...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 
  ...-constants.ts |     100 |      100 |     100 |     100 |                   
  textUtils.ts     |      65 |      100 |      75 |      65 | 56-75             
  thoughtUtils.ts  |     100 |    95.65 |     100 |     100 | 99                
  ...-converter.ts |   95.23 |    85.71 |     100 |   95.23 | 36-37             
  ...name-utils.ts |     100 |      100 |     100 |     100 |                   
  ...-finalizer.ts |   97.66 |     90.9 |     100 |   97.66 | 165-166,168-172   
  ...-retention.ts |     100 |    95.83 |     100 |     100 | 116               
  tool-utils.ts    |    95.2 |    93.61 |     100 |    95.2 | ...58-159,162-163 
  ...ultCleanup.ts |   54.62 |       64 |      75 |   54.62 | ...03-105,108-134 
  ...Compaction.ts |   96.13 |    96.42 |     100 |   96.13 | ...34-339,341-346 
  ...pt-records.ts |   87.52 |    86.13 |     100 |   87.52 | ...77-481,511-526 
  truncation.ts    |   90.61 |    90.59 |     100 |   90.61 | ...53-461,498-504 
  windowsPath.ts   |   89.47 |    79.31 |     100 |   89.47 | ...57-58,62,90-91 
  ...aceContext.ts |   95.39 |    89.47 |     100 |   95.39 | ...16-317,321-322 
  xml.ts           |    97.8 |    87.69 |     100 |    97.8 | 98-99             
  yaml-parser.ts   |   83.87 |    77.27 |     100 |   83.87 | ...31-234,239-240 
 ...ils/filesearch |   83.94 |    80.72 |   94.73 |   83.94 |                   
  crawlCache.ts    |     100 |      100 |     100 |     100 |                   
  crawler.ts       |    82.9 |    76.81 |   95.08 |    82.9 | ...1563,1597-1598 
  fileSearch.ts    |   93.78 |    87.67 |     100 |   93.78 | ...71-272,274-275 
  fzfWorker.ts     |       0 |        0 |       0 |       0 | 1-109             
  ...rkerHandle.ts |   84.05 |    75.43 |   89.47 |   84.05 | ...30-334,340-341 
  ignore.ts        |     100 |    97.36 |     100 |     100 | 187               
  result-cache.ts  |     100 |    93.75 |     100 |     100 | 49                
 ...uest-tokenizer |   69.76 |    75.47 |   85.29 |   69.76 |                   
  ...eTokenizer.ts |   65.72 |    74.02 |    92.3 |   65.72 | ...65-466,479-533 
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...tTokenizer.ts |   68.39 |    69.49 |    90.9 |   68.39 | ...24-325,327-328 
  ...ageFormats.ts |   76.92 |      100 |   33.33 |   76.92 | 46-49,56-57       
  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.

@ytahdn ytahdn added the autofix/takeover Summon the autofix loop to manage this PR (remove to release; needs triage+) label Aug 10, 2026
@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Confidence: 4/5 — solid, well-layered fix with tests that pin the change; two non-blocking questions (the cron/background stop-message removal, and the refresh-append dedup edge) are worth an author reply but don't block.

Stepping back: this is exactly the change I would have proposed. The loop-protection stop already preserves the failed tool cards; the missing piece was a terminal that tells the user the turn (not the session) stopped, in their language, without offering a pointless retry. Reusing the existing turn_error terminal with a new structured kind is the minimum-surface way to get there, and the compatibility story holds: old clients show the daemon message, old daemons fall back to message-based classification. The before/after screenshots show a real UX defect, the design doc matches the implementation, and every layer of the event flow gets a matching test — including session reuse after the error and cancellation precedence.

Reservations, named plainly: the shared-helper change quietly drops the transcript explanation on cron/background stops, so the PR description's "cron/background behavior is unchanged" is not strictly accurate. I suspect the honest answer is "intentional, cosmetic for non-interactive paths" — but the author should say so, and the design doc could carry one line about it. The refresh-append dedup question is a confirmation, not a demonstrated defect.

CI is still running on the reviewed commit (main unit suite pending, no red checks so far), so approval is deferred until CI lands green on 59c5ccb779084162eb812311f8fdd9456154889a — the finalize job posts the commit-pinned approval if everything completes green, and flags the status comment if anything lands red or the head moves.

中文说明

置信度:4/5 —— 扎实、分层合理的修复,新测试将改动固定下来;两个非阻塞问题(cron/后台停止消息的移除、刷新追加的去重边界)值得作者回应,但不阻塞合入。

回顾整体:这正是我会提出的方案。循环保护停止本就保留失败工具卡片,缺的是一个用用户语言说明"停止的是本轮而非会话"、且不提供无意义重试的终止事件。复用既有 turn_error 终止事件并新增结构化类型是表面积最小的做法,兼容性成立:旧客户端展示 daemon 消息,旧 daemon 回退到基于消息的分类。before/after 截图展示了真实的 UX 缺陷,设计文档与实现一致,事件流每一层都有对应测试(包括错误后的会话复用与取消优先级)。

保留意见直说:共享 helper 的改动顺带移除了 cron/后台停止时的转录解释文案,因此 PR 描述中"cron/后台行为不变"并不严格准确。我倾向于答案是"有意为之,对非交互路径仅是展示层面"——但应由作者确认,设计文档也可以补一句。刷新追加的去重问题属于待确认项,不是已证实的缺陷。

被审 commit 的 CI 仍在运行(主单元测试未完成,目前无红色检查),因此批准推迟至 CI 在该 commit 上全绿后执行——finalize 任务会在全部检查通过后提交绑定 commit 的批准;若有检查变红或 head 移动,则标记状态评论。

Qwen Code · qwen3.8-max

Reviewed at 59c5ccb779084162eb812311f8fdd9456154889a · re-run with @qwen-code /triage

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤝 Takeover engaged: the autofix loop now manages this PR — it will address new review feedback and resolve base conflicts until the label is removed or the round cap is reached. Remove the autofix/takeover label (or comment @qwen-code /takeover stop) to release.

中文说明

🤝 已接管:autofix 循环现在管理此 PR —— 将持续处理新的评审反馈与 base 冲突,直到移除标签或达到轮次上限。移除 autofix/takeover 标签(或评论 @qwen-code /takeover stop)即可释放。

@qwen-code-dev-bot

qwen-code-dev-bot commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

AutoFix round 7 finishedview run. See this round's report below.

中文说明

AutoFix 第 7 轮已完成 —— 查看运行。本轮报告见下方。

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 Addressed the latest review feedback (round 1/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 1/100 轮)。改动内容与我反驳保留之处如下:

Autofix review summary — PR #8853

Feedback points and dispositions

1. Failed check: Test (ubuntu-latest Node 22.x) — FIXED

Root cause (evidence-based, reproduced locally). This PR changes every loop-protection stop to reject the ACP prompt with a structured LOOP_DETECTED request error (code: LOOP_DETECTED, errorKind: loop_detected, loopType: <detector>) instead of resolving with stopReason: end_turn. The PR updated the tests exercising the per-turn tool-call cap and the repeated-tool-execution-failure paths, but missed one pre-existing test in packages/cli/src/acp-integration/session/Session.test.ts: Session > prompt > conversation_finished telemetry (#4602 review) > stops an ACP prompt after repeated invalid tool parameters with fresh ids. That test awaited a resolving prompt, so the new throw createLoopDetectedTurnError(...) failed it.

Reproduction at the previous head 59c5ccb (environment-independent, pure behavior assertion):

FAIL src/acp-integration/session/Session.test.ts > ... > stops an ACP prompt after repeated invalid tool parameters with fresh ids
RequestError: Tool-call loop protection stopped this turn. ...
 ❯ createLoopDetectedTurnError src/acp-integration/session/Session.ts:631
 ❯ src/acp-integration/session/Session.ts:3951
Tests  1 failed | 560 passed (561)

Fix (minimal, matching the PR's own pattern). Updated the stale test to expect the structured rejection — rejects.toMatchObject({ data: { code: 'LOOP_DETECTED', errorKind: 'loop_detected', loopType: core.LoopType.INVALID_TOOL_PARAMS_STAGNATION } }) — identical in shape to the PR's other updated loop-stop tests. Result: Session.test.ts 561/561 pass.

Audit of remaining local failures. A full local npm run test:ci surfaced additional failures, all in files this PR does not touch (cli config/settings/UI-command tests, core logger/storage/token-storage/ide-client tests, vscode-ide-companion ide-server). Each was traced to this self-hosted runner's exported QWEN_HOME, OPENAI_API_KEY, OPENAI_BASE_URL, OPENAI_MODEL environment (the CI Test job blanks these and uses a fresh HOME; one failing test is even named "…when QWEN_HOME is not set"). Every failing file was re-run under a CI-equivalent environment (fresh HOME + blanked keys) and passed. One AuthDialog list-navigation test still fails only on this runner's terminal input; that file's own guard skips it whenever CI === 'true' (itWhenTuiInputReliable), which GitHub Actions always sets, so it cannot fail the CI job. None of these are related to or caused by this PR.

2. E2E test report (@ytahdn) — No action needed

Informational confirmation that the feature works end-to-end on macOS: the loop-detection turn error renders localized text, the retry action is omitted, the composer stays enabled, the error survives a page refresh, and the session completes the next turn normally.

3. web-shell visual preview (@qwen-code-ci-bot) — Declined (optional coverage extension)

The preview bot notes no visual scenario renders the new loop_detected UI and suggests adding one to screenshots.spec.ts. Declined for this round: it is an optional visual-coverage extension, not a defect — the behavior is covered by unit tests (App.test.tsx, transcriptToMessages.test.ts, useMessages.test.ts) and by the manual real-daemon E2E report above; seeding a new mock-daemon state in the visuals harness would grow the diff without fixing anything.

4. Triage reviewer's two non-blocking questions (qwen-code-ci-bot stage-2/3 comments)

These lifecycle comments were filtered out of the actionable feedback list by the workflow, but both were checked on the evidence:

Q2 — refresh-append dedup edge: ANSWERED, no defect. The bounded-refresh append can never duplicate the event. turn_error is a bridge-level daemon event: it is published only on the bridge's in-memory event bus (entry.events, packages/acp-bridge/src/bridge.ts) and consumed by SSE subscribers, the in-memory compaction engine, and the live-session coordinator. The persisted page it is appended to comes from the agent channel's sessionTranscript ext method — the CLI agent process's persisted ACP session-update journal — and the CLI side contains no code that writes a bridge turn_error into that journal (verified by grep: CLI references to turn_error only read the bridge event stream or mention it in comments). Terminal events never enter persisted history, which is exactly the gap the append fills, so a later refresh cannot render it twice from persistence.

Q1 — cron/background stop-message emission: OPEN QUESTION for the author/maintainer (not decided here). Removing the emitAgentMessage(REPEATED_TOOL_FAILURE_STOP_MESSAGE) call from the shared #buildNextMessageAfterToolRun does change the cron path's transcript content in enforce mode: the cron loop (Session.ts ~line 6365) keeps its old "preserve the stopped run, end the turn" behavior but no longer emits the "Automatic continuation stopped" explanation. Functionally nothing is lost (model context and tool cards are preserved), but the PR description's "cron and background-notification work keep their existing non-interactive handling" is only strictly true for handling, not transcript wording. Options: (a) confirm intentional and add a one-line note to the design doc (recommended if the structured turn error is meant to be the sole user-facing stop explanation), or (b) restore the emission only for non-interactive callers. Left open because it is an intent call on another round's design decision, and the triage reviewer explicitly marked it non-blocking and deferred to the author.

Conflict notes

--conflict false: no merge of origin/main performed; branch stays on its own history.

Commit

c004d559a2 fix(cli): expect loop-detected turn error in invalid-params stop test (#8853) — one file changed (packages/cli/src/acp-integration/session/Session.test.ts, +11/−3).

Verification

Commands actually run and their results:

  • npm run build — passed (exit 0)
  • npm run typecheck — passed
  • npm run lint — passed
  • npx prettier --check packages/cli/src/acp-integration/session/Session.test.ts — passed
  • npm run check-i18n — passed
  • vitest packages/cli src/acp-integration/session/Session.test.ts (pre-fix) — reproduced the failure: 1 failed | 560 passed
  • vitest packages/cli src/acp-integration/session/Session.test.ts (post-fix) — 561 passed
  • vitest packages/acp-bridge (full) — 1174 passed (26 files)
  • vitest packages/sdk-typescript (full) — 1501 passed (32 files)
  • vitest packages/web-shell (full) — 3071 passed (177 files)
  • npm run test:ci (all workspaces, at the fixed head) — every PR-touched package green; the only remaining failures were this runner's env-var artifacts described above, re-verified under a CI-equivalent env: cli 9 previously failing files — 624 passed (remaining 1 AuthDialog test is self-skipped when CI=true); core 12 previously failing files — 143 + 643 passed; vscode-ide-companion ide-server — 12 passed, 1 skipped
  • No-AK integration gate not run: its test files do not exercise loop detection at all (verified via grep), and the changed behavior is fully covered by the unit and bridge tests above
  • npm run generate:settings-schema not required: no settings source (settingsSchema.ts/settings.ts) was changed
中文说明

Autofix 审查总结 — PR #8853

反馈点及处理结论

1. 失败检查项:Test (ubuntu-latest Node 22.x) — 已修复

根因(基于证据,已在本地复现)。 本 PR 将所有循环保护触发的停止从以 stopReason: end_turn 正常返回,改为用结构化的 LOOP_DETECTED 请求错误(code: LOOP_DETECTEDerrorKind: loop_detectedloopType: <检测器>)拒绝 ACP prompt。PR 已更新了覆盖"单轮工具调用上限"和"重复工具执行失败"两条路径的测试,但遗漏了 packages/cli/src/acp-integration/session/Session.test.ts 中一个既有测试:Session > prompt > conversation_finished telemetry (#4602 review) > stops an ACP prompt after repeated invalid tool parameters with fresh ids。该测试仍然等待 prompt 正常返回,因此新增的 throw createLoopDetectedTurnError(...) 使其失败。

在修复前的 head 59c5ccb 上复现(与环境无关,纯行为断言):

FAIL src/acp-integration/session/Session.test.ts > ... > stops an ACP prompt after repeated invalid tool parameters with fresh ids
RequestError: Tool-call loop protection stopped this turn. ...
 ❯ createLoopDetectedTurnError src/acp-integration/session/Session.ts:631
 ❯ src/acp-integration/session/Session.ts:3951
Tests  1 failed | 560 passed (561)

修复(最小改动,与本 PR 自身模式一致)。 将该过期测试更新为期望结构化拒绝 —— rejects.toMatchObject({ data: { code: 'LOOP_DETECTED', errorKind: 'loop_detected', loopType: core.LoopType.INVALID_TOOL_PARAMS_STAGNATION } }) —— 与 PR 中其他已更新的循环停止测试形式完全一致。结果:Session.test.ts 561/561 全部通过。

对其余本地失败的排查。 本地完整运行 npm run test:ci 还出现了其他失败,全部位于本 PR 未触碰的文件(cli 的 config/settings/UI 命令测试、core 的 logger/storage/token-storage/ide-client 测试、vscode-ide-companion 的 ide-server)。逐一追溯后确认均由本自托管 runner 导出的 QWEN_HOMEOPENAI_API_KEYOPENAI_BASE_URLOPENAI_MODEL 环境变量导致(CI 的 Test 任务会清空这些变量并使用全新 HOME;其中一个失败测试的名字甚至就是"…when QWEN_HOME is not set")。所有失败文件都在 CI 等效环境(全新 HOME + 清空密钥)下重跑并通过。另有一个 AuthDialog 列表导航测试仅在本 runner 的终端输入下失败,而该文件自身的守卫会在 CI === 'true'(GitHub Actions 恒为 true)时跳过它,因此它不可能在 CI 中失败。这些失败均与本 PR 无关,也不是本 PR 引起的。

2. E2E 测试报告(@ytahdn)— 无需处理

信息性确认:功能在 macOS 上端到端工作正常 —— 循环检测的 turn error 显示本地化文案、重试按钮被省略、输入框保持可用、刷新页面后错误仍然可见、会话可以正常完成下一轮。

3. web-shell 视觉预览(@qwen-code-ci-bot)— 拒绝(可选的覆盖扩展)

预览机器人指出没有视觉场景渲染新的 loop_detected UI,建议向 screenshots.spec.ts 添加一个场景。本轮予以拒绝:这是可选的视觉覆盖扩展,并非缺陷 —— 该行为已有单元测试覆盖(App.test.tsxtranscriptToMessages.test.tsuseMessages.test.ts),且上述人工 E2E 报告已在真实 daemon 上验证;为视觉测试桩注入新的 mock daemon 状态只会扩大 diff,并不修复任何问题。

4. 审查(triage)机器人的两个非阻塞问题(qwen-code-ci-bot stage-2/3 评论)

这两条生命周期评论被工作流从可处理反馈列表中过滤掉了,但两者都做了证据核查:

问题 2 —— 刷新追加的去重边界:已确认,无缺陷。 bounded-refresh 的追加不可能造成重复。turn_error 是 bridge 层的 daemon 事件:它只发布在 bridge 的内存事件总线上(entry.eventspackages/acp-bridge/src/bridge.ts),由 SSE 订阅者、内存压缩引擎和 live-session 协调器消费。被追加的持久化分页来自 agent 通道的 sessionTranscript ext 方法 —— 即 CLI agent 进程持久化的 ACP session-update 日志 —— 而 CLI 侧没有任何代码会把 bridge 的 turn_error 写入该日志(已用 grep 核实:CLI 中对 turn_error 的引用只读取 bridge 事件流或出现在注释里)。终止事件从不进入持久化历史,这正是本次追加要填补的空缺,因此后续刷新不会从持久化中渲染出两条。

问题 1 —— cron/后台停止消息的发送:留给作者/维护者的开放问题(此处不做决定)。 从共享的 #buildNextMessageAfterToolRun 中移除 emitAgentMessage(REPEATED_TOOL_FAILURE_STOP_MESSAGE) 确实改变了 cron 路径在 enforce 模式下的转录内容:cron 循环(Session.ts 约 6365 行)保留旧的"保留停止的工具运行、正常结束轮次"行为,但不再输出"Automatic continuation stopped"解释。功能上没有任何损失(模型上下文与工具卡片均保留),但 PR 描述中"cron 与后台通知保持既有非交互处理"严格说只对处理流程成立,对转录措辞不成立。选项:(a) 确认为有意为之,并在设计文档中补一句说明(若结构化 turn error 被定位为唯一的用户可见停止解释,推荐此项);或 (b) 仅对非交互调用方恢复该消息发送。保持开放,因为这是另一轮设计决策的意图判断,且审查机器人已明确标注非阻塞并交由作者回答。

冲突说明

--conflict false:未合并 origin/main,分支保持在自身历史上。

提交

c004d559a2 fix(cli): expect loop-detected turn error in invalid-params stop test (#8853) —— 仅改动一个文件(packages/cli/src/acp-integration/session/Session.test.ts,+11/−3)。

验证

实际执行的命令及结果:

  • npm run build — 通过(exit 0)
  • npm run typecheck — 通过
  • npm run lint — 通过
  • npx prettier --check packages/cli/src/acp-integration/session/Session.test.ts — 通过
  • npm run check-i18n — 通过
  • vitest packages/cli src/acp-integration/session/Session.test.ts(修复前)— 复现失败:1 failed | 560 passed
  • vitest packages/cli src/acp-integration/session/Session.test.ts(修复后)— 561 passed
  • vitest packages/acp-bridge(全量)— 1174 passed(26 个文件)
  • vitest packages/sdk-typescript(全量)— 1501 passed(32 个文件)
  • vitest packages/web-shell(全量)— 3071 passed(177 个文件)
  • npm run test:ci(全部 workspace,修复后的 head)— 本 PR 触碰的所有包均为绿色;其余失败均为上述本 runner 环境变量造成的假象,并已在 CI 等效环境下逐一复核:cli 原先失败的 9 个文件 — 624 passed(剩余 1 个 AuthDialog 测试在 CI=true 时自行跳过);core 原先失败的 12 个文件 — 143 + 643 passed;vscode-ide-companion ide-server — 12 passed, 1 skipped
  • 未运行 no-AK 集成门禁:其测试文件完全不涉及循环检测(已用 grep 核实),且变更行为已被上述单元与 bridge 测试完整覆盖
  • 无需运行 npm run generate:settings-schema:未改动任何 settings 源(settingsSchema.ts/settings.ts

Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。

Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. · 有空请复审;第 100 轮后本 bot 停止并将 PR 交给人工。


🧠 Handled by Qwen Code · model/模型 qwen3.8-max

@qwen-code-ci-bot qwen-code-ci-bot 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.

Not explored to full depth (tool budget reached): You are review agent reverse-audit — Reverse audit agen...: (none); This PR converts foreground tool-loop-protection stops in...: did not trace selectBackwardPageUuids → replayTranscriptRecordPage end-to-end to prove backward pages replay in chronological order (assumed when validating…; This PR converts foreground tool-loop-protection stops in...: did not run the web-shell/acp-bridge/Session test suites to confirm the new tests pass (code audit only).; You are review agent 8a — a diff-specialized finder for...: did not trace the daemon-side transcript writer to independently prove terminal events are never persisted (relied on the structural argument that turn_error is…; This PR converts foreground tool-loop-protection stops in...: end-to-end runtime verification of the cron channel-delivery skip in finding 1 (no cron E2E executed; mechanism verified by code path only)., and 1 more.

中文说明

未探索到全部深度(达到工具调用预算):You are review agent reverse-audit — Reverse audit agen...:(none);This PR converts foreground tool-loop-protection stops in...:did not trace selectBackwardPageUuids → replayTranscriptRecordPage end-to-end to prove backward pages replay in chronological order (assumed when validating…;This PR converts foreground tool-loop-protection stops in...:did not run the web-shell/acp-bridge/Session test suites to confirm the new tests pass (code audit only).;You are review agent 8a — a diff-specialized finder for...:did not trace the daemon-side transcript writer to independently prove terminal events are never persisted (relied on the structural argument that turn_error is…;This PR converts foreground tool-loop-protection stops in...:end-to-end runtime verification of the cron channel-delivery skip in finding 1 (no cron E2E executed; mechanism verified by code path only).,另有 1 条。

— qwen3.8-max via Qwen Code /review (v0.21.8)

Comment on lines +4879 to +4881
if (toolRun.loopDetected) {
this.todoStopGuard.suspend();
await this.#preserveStoppedToolRun(toolRun, pendingSend.signal);

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] The new unconditional throw createLoopDetectedTurnError(toolLoopState) inside #runStopContinuation is shared by cron and background-notification callers via #handleStopHookLoop (Session.ts:6380-6389 and 6990-6997; none of the three #runStopContinuation call sites is wrapped in try/catch). The continuation's loop state is created with createDaemonToolLoopState('off') (Session.ts:4328), which disables only the repeated-failure guard — the per-turn tool-call cap and invalid-params stagnation detectors stay active, so a Stop continuation can still trip loop detection. Pre-diff this path returned {kind:'terminal', stopReason:'end_turn'}; now the throw propagates into the cron catch-all (Session.ts:6397-6407): cronHadError = true, completion status flips to 'error', and cronCompleted stays false so #scheduleChannelDelivery (Session.ts:6432-6446) is silently skipped. Background-notification turns degrade into the [notification error] path (~7004). This contradicts the design doc added by this PR ("Cron and background-notification work keep their existing non-interactive handling") — true for the main loops (Session.ts:6372-6378, 6974-6981), not for this shared Stop-continuation path. — Failure scenario: a cron/background-notification turn with pending todos enters a Stop continuation whose tool calls trip loop detection (e.g. three invalid-params attempts for one tool, or the per-turn cap) → the turn is classified 'error', emits [cron error] Tool-call loop protection stopped this turn…, and the configured channel delivery is silently skipped — the scheduled task's output never reaches its target. Suggested fix: gate the rejection on the caller (e.g. a rejectOnLoopDetected option only the foreground chain sets), or catch the LOOP_DETECTED RequestError in the cron/notification handlers and restore the graceful end-turn handling:

// #runStopContinuation — foreground chain passes rejectOnLoopDetected: true
if (toolRun.loopDetected) {
  this.todoStopGuard.suspend();
  await this.#preserveStoppedToolRun(toolRun, pendingSend.signal);
  if (pendingSend.signal.aborted) { /* cancelled return, as today */ }
  if (!options.rejectOnLoopDetected) {
    return { kind: 'terminal', stopReason: 'end_turn' }; // pre-PR behavior for automatic turns
  }
  throw createLoopDetectedTurnError(toolLoopState);
}
中文说明

[Critical] #runStopContinuation 中新增的无条件 throw createLoopDetectedTurnError(toolLoopState) 会被 cron 和后台通知调用方通过 #handleStopHookLoop 共享(调用点见 Session.ts:6380-6389 与 6990-6997;#runStopContinuation 的三个调用点均未包裹 try/catch)。续跑(continuation)的循环状态以 createDaemonToolLoopState('off') 创建(Session.ts:4328),'off' 仅关闭重复失败守卫——单轮工具调用上限与无效参数停滞检测仍然有效,因此 Stop 续跑仍可能触发循环检测。改动前该路径返回 {kind:'terminal', stopReason:'end_turn'};现在 throw 会传入 cron 的兜底 catch(Session.ts:6397-6407):cronHadError = true、完成状态变为 'error',且 cronCompleted 保持 false 导致 #scheduleChannelDelivery(Session.ts:6432-6446)被静默跳过。后台通知轮次同样退化为 [notification error] 路径(约 7004 行)。这与本 PR 新增设计文档中“Cron 与后台通知保持既有非交互处理”的承诺矛盾——该承诺仅对主循环(Session.ts:6372-6378、6974-6981)成立,对这条共享的 Stop 续跑路径不成立。 — 失败场景:带有待办 todos 的 cron/后台通知轮次进入 Stop 续跑,续跑中的工具调用触发循环检测(例如同一工具连续 3 次无效参数,或达到单轮上限)→ 该轮被标记为 'error',输出 [cron error] Tool-call loop protection stopped this turn…,且配置的频道投递被静默跳过——定时任务的输出永远无法送达目标。建议修复:按调用方门控该拒绝(例如仅前台链路设置 rejectOnLoopDetected 选项),或在 cron/通知处理器中捕获 LOOP_DETECTED RequestError 并恢复原有的正常结束轮次处理(见上方代码示例)。

— qwen3.8-max via Qwen Code /review (v0.21.8)

Comment thread packages/acp-bridge/src/bridge.ts Outdated
Comment on lines +4940 to +4942
const replay = entry.events.snapshotReplay();
const turnError = entry.turnError
? [

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] snapshotReplay() is computed unconditionally on every bounded refresh, but is only read inside the entry.turnError branch below it. It runs flattenReplaySegments() (slice + flatMap over all replay segments) plus liveJournal.slice() — O(replay window) — and the result is discarded whenever entry.turnError is unset, which is the overwhelming common case (set only on active-turn failure, deleted at the next dispatch). Pre-PR this branch returned page.events directly with no snapshot; snapshot() has no side effects, so deferring it is safe. — Concrete cost: every Web Shell refresh of a live session without a turn error pays the full replay-window flatten + copy and discards it; long sessions (replay window up to the configured caps) pay proportionally more on this user-facing path. Suggested fix: hoist the snapshot into the branch that uses it:

const turnError = entry.turnError
  ? (() => {
      const replay = entry.events.snapshotReplay();
      return [
        ...(replay?.compactedTurns ?? []),
        ...(replay?.liveJournal ?? []),
      ]
        .reverse()
        .find((event) => event.type === 'turn_error');
    })()
  : undefined;
中文说明

[Suggestion] snapshotReplay() 在每次 bounded refresh 上都会被无条件计算,但只有下方 entry.turnError 分支会读取它。该调用会执行 flattenReplaySegments()(对全部 replay 段做 slice + flatMap)外加 liveJournal.slice()——开销为 O(replay 窗口)——而 entry.turnError 未设置时(绝大多数情况:它只在活跃轮次失败时设置、在下次 dispatch 时删除)结果会被直接丢弃。改动前该分支直接返回 page.events,完全不做快照;snapshot() 没有副作用,因此延迟执行是安全的。 — 具体代价:每次没有 turn error 的 live session 刷新(Web Shell 的常见场景)都要为整个 replay 窗口付出一次 flatten + 拷贝再丢弃;长会话(replay 窗口可达配置上限)在这条用户可见路径上的开销按比例放大。建议修复:将快照移入使用它的分支内(见上方代码示例)。

— qwen3.8-max via Qwen Code /review (v0.21.8)

Comment thread packages/web-shell/client/App.tsx Outdated
Comment on lines +6056 to +6058
if (block.errorKind !== 'loop_detected') {
retryableTurnErrorId = block.id;
}

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 "loop_detected is not retryable" policy is encoded as two independent string checks — here and in transcriptToMessages.ts:782-783 (retryable: errorBlock.source === 'turn_error' && errorKind !== 'loop_detected') — with no shared predicate; grep confirms these are the only two loop_detected policy sites in web-shell, and this diff itself extends the DAEMON_ERROR_KINDS taxonomy they key off. — Failure scenario: when a second non-retryable kind is added, both sites must be edited; updating only the adapter leaves the composer retry affordance offering to resubmit the exact prompt the daemon just terminated (for loop_detected: re-running the prompt that tripped loop protection, likely re-looping and burning another turn), and updating only App.tsx leaves the per-message affordance inconsistent with the hint. Suggested fix: extract one predicate in the transcript adapter and consume it from both sites:

export function isRetryableTurnErrorKind(errorKind: string | undefined): boolean {
  return errorKind !== 'loop_detected';
}
中文说明

[Suggestion] “loop_detected 不可重试”这一策略被写成两处相互独立的字符串比较——此处与 transcriptToMessages.ts:782-783retryable: errorBlock.source === 'turn_error' && errorKind !== 'loop_detected')——没有共享谓词;grep 确认这是 web-shell 中仅有的两处 loop_detected 策略点,而本 PR 本身还在扩展它们所依赖的 DAEMON_ERROR_KINDS 分类。 — 失败场景:将来新增第二种不可重试的 kind 时,两处都必须修改;只改 adapter 会让输入框上方的重试入口仍然提议重新提交刚被 daemon 终止的 prompt(对 loop_detected 而言就是重跑触发循环保护的 prompt,很可能再次循环并再烧一轮);只改 App.tsx 则会让单条消息上的重试入口与提示不一致。建议修复:在 transcript adapter 中提取一个共享谓词(见上方代码),两处都消费它。

— qwen3.8-max via Qwen Code /review (v0.21.8)

Comment on lines +1224 to +1227
const code =
structuredErrorKind === 'loop_detected'
? (extractJsonRpcErrorField(err, 'code') ?? extractErrorCode(err))
: extractErrorCode(err);

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 preference for the structured data.code over the JSON-RPC numeric code is gated on structuredErrorKind === 'loop_detected' instead of on "this error is structured" (structuredErrorKind !== undefined). This PR establishes new RequestError(-32603, msg, { code: 'X', errorKind: 'x_kind' }) as the pattern for structured turn errors, and the DAEMON_ERROR_KINDS taxonomy it extends is explicitly growing. No observable misbehavior today — the other structured producers (session_writer_* kinds) carry no data.code and their per-kind rpcCodes are meaningful — but the gate is written for exactly one kind. — Failure scenario: the next error kind thrown with this PR's RequestError pattern passes through broadcastTurnError unchanged and ships code: '-32603' (a stringified JSON-RPC number) in its turn_error payload until someone edits this function again.

Suggested change
const code =
structuredErrorKind === 'loop_detected'
? (extractJsonRpcErrorField(err, 'code') ?? extractErrorCode(err))
: extractErrorCode(err);
const code =
structuredErrorKind !== undefined
? (extractJsonRpcErrorField(err, 'code') ?? extractErrorCode(err))
: extractErrorCode(err);
中文说明

[Suggestion] 结构化 data.code 优先于 JSON-RPC 数字 code 的取值逻辑被门控在 structuredErrorKind === 'loop_detected' 上,而不是门控在“这是一个结构化错误”(structuredErrorKind !== undefined)上。本 PR 已把 new RequestError(-32603, msg, { code: 'X', errorKind: 'x_kind' }) 确立为结构化轮次错误的模式,而它所扩展的 DAEMON_ERROR_KINDS 分类明确还在增长。当前没有可观察的错误行为——其他结构化来源(session_writer_* 系列)不携带 data.code,且各自的 rpcCode 本身有意义——但这个门控只为一种 kind 而写。 — 失败场景:下一个按本 PR 的 RequestError 模式抛出的错误 kind 会原样通过 broadcastTurnError,在其 turn_error 负载中携带 code: '-32603'(被字符串化的 JSON-RPC 数字),直到有人再次修改这个函数。建议按上方 suggestion 修改(对现有行为逐字节等价,未来的结构化 kind 自动获得正确的取值优先级)。

— qwen3.8-max via Qwen Code /review (v0.21.8)

Comment on lines 3936 to 3941
if (nextAfterTools.stoppedByRepeatedToolFailure) {
return {
stopReason: getAbortAwareEndTurnStopReason(
pendingSend.signal,
),
};
if (pendingSend.signal.aborted) {
return { stopReason: 'cancelled' };
}
throw createLoopDetectedTurnError(toolLoopState);
}

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 abort-precedence-then-throw block is pasted four times in this diff — here and at Session.ts:3947-3951 (plain { stopReason: 'cancelled' } shape), plus 4883-4892 and 4902-4911 ({ kind: 'terminal', stopReason: 'cancelled', supersededAutomaticContinuation? } shape) — replacing two call sites of the file's existing precedence helper getAbortAwareEndTurnStopReason (Session.ts:402) with inlined copies. The PR's stated invariant "cancellation takes precedence when it races the loop stop" is now encoded in four separate places in two already-divergent shapes. — Failure scenario: any future change to loop-stop terminal semantics — a new structured field on the error, a different stop reason, superseded-continuation handling in the guard-loop copies — must be applied to all four sites; missing one makes one loop type (tool-call cap vs repeated-failure vs continuation) resolve to a different stopReason/outcome than the others, silently per detector. Suggested fix: extract one helper next to createLoopDetectedTurnError and call it from all four sites, building each site's return object around the returned 'cancelled':

function cancelledOrThrowLoopDetected(
  signal: AbortSignal,
  loopState: DaemonToolLoopState,
): 'cancelled' {
  if (signal.aborted) return 'cancelled';
  throw createLoopDetectedTurnError(loopState);
}
中文说明

[Suggestion] 这个“先检查 abort 再 throw”的块在本 diff 中被粘贴了四次——此处与 Session.ts:3947-3951(普通 { stopReason: 'cancelled' } 形状),以及 4883-4892 与 4902-4911({ kind: 'terminal', stopReason: 'cancelled', supersededAutomaticContinuation? } 形状)——把该文件已有的优先级辅助函数 getAbortAwareEndTurnStopReason(Session.ts:402)的两处调用替换成了内联副本。PR 声明的不变量“取消与循环停止竞争时以取消为准”现在被编码在四个位置、且已是两种相互分歧的形状。 — 失败场景:未来任何对循环停止终止语义的修改——错误上新增结构化字段、更换 stop reason、在 guard-loop 副本中处理 superseded-continuation——都必须同时应用到四处;漏掉任何一处都会让某种循环类型(工具调用上限 / 重复失败 / 续跑)解析出与其他类型不同的 stopReason/结果,且按检测器静默分歧。建议修复:在 createLoopDetectedTurnError 旁提取一个辅助函数(见上方代码),四处调用它,各调用点围绕返回的 'cancelled' 构造自己的返回对象。

— qwen3.8-max via Qwen Code /review (v0.21.8)

Comment thread packages/acp-bridge/src/bridge.test.ts Outdated
Comment on lines +4131 to +4133
expect(refreshed.compactedReplay).toContainEqual(
expect.objectContaining({
type: 'turn_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] This test mocks the persisted transcript page with events: [] and asserts with toContainEqual, so the position of the appended turn_error is unpinned. Probe-verified on this commit: mutating refreshedReplayFieldsFor from [...page.events, turnError] to [turnError, ...page.events] survives the test; strengthening the mocked page with one event and asserting the turn_error is the last element fails under the same mutation (and passes against the PR as written). — Failure scenario: a future refactor of the bounded-refresh path prepends (or otherwise misorders) the in-memory turn_error; with a non-empty persisted page, Web Shell would render the loop-detected error block above the replayed history of the turn it terminates, and this test stays green because toContainEqual over a single element plus an empty array cannot distinguish append from prepend. Suggested fix: return at least one event from the mocked sessionTranscript response and pin the position:

expect(
  refreshed.compactedReplay[refreshed.compactedReplay.length - 1],
).toMatchObject({ type: 'turn_error', promptId: 'prompt-loop' });
中文说明

[Suggestion] 该测试把持久化转录分页 mock 成 events: [] 并用 toContainEqual 断言,因此追加的 turn_error 的位置没有被钉住。已在本提交上用探针验证:把 refreshedReplayFieldsFor[...page.events, turnError] 变异为 [turnError, ...page.events],测试仍然通过;把 mock 分页改为返回一个事件并断言 turn_error 是最后一个元素后,同一变异会使测试失败(对未变异的 PR 代码则通过)。 — 失败场景:未来对 bounded-refresh 路径的重构把内存中的 turn_error 前置(或以其他方式错序);在持久化分页非空时,Web Shell 会把循环检测错误块渲染在它终止的那轮重放历史上方,而本测试仍为绿色——因为 toContainEqual 作用于单元素加空数组时无法区分追加与前置。建议修复:让 mock 的 sessionTranscript 响应至少返回一个事件,并钉住位置(见上方代码)。

— qwen3.8-max via Qwen Code /review (v0.21.8)

Comment on lines +4901 to +4904
if (nextAfterTools.stoppedByRepeatedToolFailure) {
if (pendingSend.signal.aborted) {
return {
kind: 'terminal',

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 diff deletes the only test covering cancellation racing a repeated-tool-failure loop stop (returns cancelled when cancellation arrives while the stop message is emitted) and adds no replacement for that path. Verified: the replacement test (lets cancellation win while a loop-detected Stop continuation is preserved) reaches only the tool-call-cap toolRun.loopDetected branch of #runStopContinuation#buildNextMessageAfterToolRun is never called on that path. The pre-existing drain test does not cover these branches either: its cancellation fires #buildNextMessageAfterToolRun's abort early-return (Session.ts:5370-5382) BEFORE the stop decision is computed, so stoppedByRepeatedToolFailure is never set (its own not.toHaveBeenCalledWith(... REPEATED_TOOL_EXECUTION_FAILURE) assertion proves it). The new abort sub-branches at Session.ts:3937-3939 and 4902-4910 therefore have zero cancellation coverage. — Failure scenario: if the if (pendingSend.signal.aborted) check were dropped or reordered during a future refactor of the four-way duplicated block, a user cancellation landing during the post-failure drain would surface a LOOP_DETECTED turn error instead of stopReason: 'cancelled' — violating the PR's own "cancellation still wins if it races loop termination" promise — and no test would fail. Suggested fix: add a cancellation test for the repeated-tool-failure path (failing tool + failure streak in enforce mode, cancel during the post-failure drain window — the sibling test's blocking-rewriter pattern reaches that window — expect { stopReason: 'cancelled' }), or parameterize the existing cancellation test over both loop triggers.

中文说明

[Suggestion] 本 diff 删除了唯一覆盖“取消与重复工具失败循环停止竞争”的测试(returns cancelled when cancellation arrives while the stop message is emitted),且没有为该路径补充替代测试。已核实:替代测试(lets cancellation win while a loop-detected Stop continuation is preserved)只到达 #runStopContinuation 中工具调用上限的 toolRun.loopDetected 分支——该路径根本不会调用 #buildNextMessageAfterToolRun。既有的 drain 测试也未覆盖这些分支:它的取消触发的是 #buildNextMessageAfterToolRun 的 abort 提前返回(Session.ts:5370-5382),发生在停止决定计算之前,因此 stoppedByRepeatedToolFailure 从未被置位(该测试自己的 not.toHaveBeenCalledWith(... REPEATED_TOOL_EXECUTION_FAILURE) 断言恰好证明了这一点)。于是 Session.ts:3937-3939 与 4902-4910 新增的 abort 子分支完全没有取消场景覆盖。 — 失败场景:如果未来重构这个四处复制的块时删除或调换了 if (pendingSend.signal.aborted) 检查,用户在失败后 drain 期间发起的取消会表现为 LOOP_DETECTED 轮次错误而不是 stopReason: 'cancelled'——违背 PR 自己承诺的“取消与循环终止竞争时仍以取消为准”——且没有任何测试会失败。建议修复:为重复工具失败路径补一个取消测试(enforce 模式下的失败工具 + 失败连击,在失败后 drain 窗口内取消——姊妹测试的阻塞 rewriter 模式可以到达该窗口——期望 { stopReason: 'cancelled' }),或把现有取消测试参数化到两种循环触发器。

— qwen3.8-max via Qwen Code /review (v0.21.8)

Comment thread packages/acp-bridge/src/bridge.ts Outdated
Comment on lines +4950 to +4952
compactedReplay: turnError
? [...page.events, turnError]
: page.events,

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] Probe-verified on this commit (outOfOrder=true on the unmodified PR; flipped by the guard below): cron/background-notification turns execute inside the CLI session and bypass bridge prompt admission — the only delete entry.turnError site (bridge.ts:6778) — while their session updates still flow through the bridge fan-in (bridgeClient.ts:908-927) into the journal with newer ids and into persisted records. This append then places the stale loop turn_error after page.events that already end with the newer automatic turn's content, so the refreshed transcript renders the loop error after content that actually happened later — and the stale entry.turnError keeps re-appending it on every refresh until the next interactive prompt admission. — Failure scenario: an interactive prompt fails with loop detection; before the user refreshes, a cron tick or background-notification turn runs on the same session → the refreshed Web Shell transcript shows the loop error after the automatic turn's output, reading as if the error belongs to the automatic turn. Suggested fix: only append when the found turn_error is still the last journaled event (or store the published event on the entry at broadcast time and append it only while it is newest):

Suggested change
compactedReplay: turnError
? [...page.events, turnError]
: page.events,
compactedReplay:
turnError &&
[...(replay?.compactedTurns ?? []), ...(replay?.liveJournal ?? [])].at(-1)?.id === turnError.id
? [...page.events, turnError]
: page.events,
中文说明

[Suggestion] 已在本提交上用探针验证(未修改的 PR 代码上观察到 outOfOrder=true;加上下方守卫后翻转):cron/后台通知轮次在 CLI 会话内部执行,绕过 bridge 的 prompt 准入——而那是唯一 delete entry.turnError 的位置(bridge.ts:6778)——但它们的 session update 仍会经由 bridge 扇入(bridgeClient.ts:908-927)以更小的新 id 进入日志并写入持久化记录。于是这里的追加会把过期的循环 turn_error 放在已经以更新的自动轮次内容结尾的 page.events 之后,刷新后的转录会把循环错误渲染在实际发生得更晚的内容之后——且过期的 entry.turnError 会在每次刷新时重复追加,直到下一个交互 prompt 准入。 — 失败场景:交互 prompt 因循环检测失败;用户刷新之前,同一会话上运行了一次 cron 或后台通知轮次 → 刷新后的 Web Shell 转录在自动轮次的输出之后显示循环错误,读起来像是该错误属于那个自动轮次。建议修复:仅当找到的 turn_error 仍是日志中最后一个事件时才追加(见上方 suggestion;或在广播时把发布的事件存到 entry 上,仅当它仍最新时追加)。

— qwen3.8-max via Qwen Code /review (v0.21.8)

Comment on lines 4911 to 4913
throw createLoopDetectedTurnError(toolLoopState);
}
if (nextAfterTools.hadMidTurnUserInput) {

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 stoppedByRepeatedToolFailure branch added to #runStopContinuation is unreachable: the function creates its only loop state via createDaemonToolLoopState('off') (Session.ts:4328, never re-created or mutated), and reduceRepeatedToolFailureGuard short-circuits to {kind:'none'} for mode 'off' (repeated-tool-failure-guard.ts:186-188) — so #buildNextMessageAfterToolRun can never return stoppedByRepeatedToolFailure: true here. The sibling toolRun.loopDetected branch above IS reachable (cap detection is mode-independent); only this branch is dead. — Concrete cost: 12 lines of dead code tell readers a Stop-hook continuation can terminate with a repeated-failure loop error, which it cannot, and add a fourth divergent site to the pasted abort-throw block flagged above; if the mode at Session.ts:4328 is ever changed to make this reachable, the throw would also land in the cron/background-notification callers of #handleStopHookLoop — the exact regression the design doc rules out for them. Suggested fix: drop the branch from #runStopContinuation (keep it only at the foreground site, Session.ts:3936), or add a comment stating it is defensive for a future mode change.

中文说明

[Suggestion] 新增到 #runStopContinuation 中的这个 stoppedByRepeatedToolFailure 分支不可达:该函数唯一的循环状态由 createDaemonToolLoopState('off') 创建(Session.ts:4328,之后从不重建或变更模式),而 reduceRepeatedToolFailureGuard 对 mode 'off' 直接短路返回 {kind:'none'}(repeated-tool-failure-guard.ts:186-188)——因此 #buildNextMessageAfterToolRun 在这里永远不可能返回 stoppedByRepeatedToolFailure: true。上方姊妹分支 toolRun.loopDetected 是可达的(上限检测与 mode 无关);只有这个分支是死代码。 — 具体代价:12 行死代码会让读者以为 Stop-hook 续跑可能以重复失败循环错误终止,而实际不可能,并且为上文标记的粘贴式 abort-throw 块增加了第四个分歧位置;如果 Session.ts:4328 的 mode 未来被改为使该分支可达,这个 throw 还会落入 #handleStopHookLoop 的 cron/后台通知调用方——正是设计文档明确排除的回归。建议修复:从 #runStopContinuation 中删除该分支(仅保留前台位置 Session.ts:3936 的那份),或加注释说明它是为未来 mode 变更预留的防御代码。

— qwen3.8-max via Qwen Code /review (v0.21.8)

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 AutoFix ran out of time before finishing (idle-timeout (no output for 1200000ms — the sandbox likely hung at startup)) (attempt 2/100) — it will retry on the next scan.

What I found before stopping:
Qwen failed during address-review: idle-timeout (no output for 1200000ms — the sandbox likely hung at startup).

See the Qwen Autofix agent step logs for model/tool output.

Run log: https://github.com/QwenLM/qwen-code/actions/runs/31389561905


🧠 Handled by Qwen Code · model/模型 qwen3.8-max

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 AutoFix updated a stale base — the fix did not pass verification, but this PR was behind main, so it merged current main in via update-branch and will retry on the next scan. A stale base (a dependency or symbol main already changed) can fail the build without being the fix's fault; if it still fails once current, it hands off to a human.

What I found before stopping:
Qwen failed during address-review: status 125.

See the Qwen Autofix agent step logs for model/tool output.

Run log: https://github.com/QwenLM/qwen-code/actions/runs/31394772225


🧠 Handled by Qwen Code · model/模型 qwen3.8-max

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 AutoFix ran out of time before finishing (idle-timeout (no output for 1200000ms — the sandbox likely hung at startup)) (attempt 4/100) — it will retry on the next scan.

What I found before stopping:
Qwen failed during address-review: idle-timeout (no output for 1200000ms — the sandbox likely hung at startup).

See the Qwen Autofix agent step logs for model/tool output.

Run log: https://github.com/QwenLM/qwen-code/actions/runs/31399630023


🧠 Handled by Qwen Code · model/模型 qwen3.8-max

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 AutoFix stopped: this counting window now contains 3 time-budget exhaustions (pushed rounds in between included; this round itself may have failed differently). That is 3 full agent runs that pushed nothing. 3 of those were silent-sandbox (idle) timeouts that no budget increase can cure — investigate the sandbox image and runner docker daemon for those. A human should investigate the sandbox image and runner docker daemon, then comment @qwen-code /retry to re-arm. Until then future scans will skip this PR.

What I found before stopping:
Qwen failed during address-review: idle-timeout (no output for 1200000ms — the sandbox likely hung at startup).

See the Qwen Autofix agent step logs for model/tool output.

Run log: https://github.com/QwenLM/qwen-code/actions/runs/31404260822


🧠 Handled by Qwen Code · model/模型 qwen3.8-max

@qwen-code-ci-bot qwen-code-ci-bot 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.

Not explored to full depth (tool budget reached): PR #8853: converts foreground tool-loop-protection stops ...: none — all checks I started were completed within budget.; PR #8853: converts foreground tool-loop-protection stops ...: did not run build/tests; did not exhaustively trace the bridge's queued-prompt deadline flow beyond reading the broadcastTurnError comment; did not verify the d…; PR #8853: converts foreground tool-loop-protection stops ...: did not run build/unit tests to confirm the new tests pass (read-only review).; PR #8853: converts foreground tool-loop-protection stops ...: did not exhaustively trace the bridge queued-prompt deadline/teardown-flush flows beyond broadcastTurnError 's documented mutateTurnState gating.; PR #8853: converts foreground tool-loop-protection stops ...: did not read compactionEngine.ts deeply enough to rule out that compaction always retains terminal events (bears only on Finding 2's mode 1 likelihood)., and 1 more.

中文说明

未探索到全部深度(达到工具调用预算):PR #8853: converts foreground tool-loop-protection stops ...:none — all checks I started were completed within budget.;PR #8853: converts foreground tool-loop-protection stops ...:did not run build/tests; did not exhaustively trace the bridge's queued-prompt deadline flow beyond reading the broadcastTurnError comment; did not verify the d…;PR #8853: converts foreground tool-loop-protection stops ...:did not run build/unit tests to confirm the new tests pass (read-only review).;PR #8853: converts foreground tool-loop-protection stops ...:did not exhaustively trace the bridge queued-prompt deadline/teardown-flush flows beyond broadcastTurnError 's documented mutateTurnState gating.;PR #8853: converts foreground tool-loop-protection stops ...:did not read compactionEngine.ts deeply enough to rule out that compaction always retains terminal events (bears only on Finding 2's mode 1 likelihood).,另有 1 条。

— qwen3.8-max via Qwen Code /review (v0.21.8)

Comment on lines 4891 to 4893
throw createLoopDetectedTurnError(toolLoopState);
}
const nextAfterTools = await this.#buildNextMessageAfterToolRun(

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] R1-1: Round 2 — still stands at the reviewed commit 54cfa0f (this file is unchanged since round 1; HEAD moved only via a merge of main). The new unconditional throw createLoopDetectedTurnError(toolLoopState) inside #runStopContinuation is shared by cron and background-notification callers via #handleStopHookLoop (Session.ts:6380-6389 and 6990-6997; none of the three #runStopContinuation call sites is wrapped in try/catch). The continuation's loop state is created with createDaemonToolLoopState('off') (Session.ts:4328), which disables only the repeated-failure guard — the per-turn tool-call cap and invalid-params stagnation detectors stay active, so a Stop continuation can still trip loop detection. Pre-diff this path returned {kind:'terminal', stopReason:'end_turn'}; now the throw propagates into the cron catch-all (Session.ts:6397-6407): cronHadError = true, completion status flips to 'error', and cronCompleted stays false so #scheduleChannelDelivery (Session.ts:6432-6446) is silently skipped. Background-notification turns degrade into the [notification error] path (~7004). This contradicts the design doc added by this PR ("Cron and background-notification work keep their existing non-interactive handling") — true for the main loops (Session.ts:6372-6378, 6974-6981), not for this shared Stop-continuation path. — Failure scenario: a cron/background-notification turn with pending todos enters a Stop continuation whose tool calls trip loop detection (e.g. three invalid-params attempts for one tool, or the per-turn cap) → the turn is classified 'error', emits [cron error] Tool-call loop protection stopped this turn…, and the configured channel delivery is silently skipped — the scheduled task's output never reaches its target. Suggested fix: gate the rejection on the caller (e.g. a rejectOnLoopDetected option only the foreground chain sets), or catch the LOOP_DETECTED RequestError in the cron/notification handlers and restore the graceful end-turn handling:

// #runStopContinuation — foreground chain passes rejectOnLoopDetected: true
if (toolRun.loopDetected) {
  this.todoStopGuard.suspend();
  await this.#preserveStoppedToolRun(toolRun, pendingSend.signal);
  if (pendingSend.signal.aborted) { /* cancelled return, as today */ }
  if (!options.rejectOnLoopDetected) {
    return { kind: 'terminal', stopReason: 'end_turn' }; // pre-PR behavior for automatic turns
  }
  throw createLoopDetectedTurnError(toolLoopState);
}
中文说明

[Critical] #runStopContinuation 中新增的无条件 throw createLoopDetectedTurnError(toolLoopState) 会被 cron 和后台通知调用方通过 #handleStopHookLoop 共享(调用点见 Session.ts:6380-6389 与 6990-6997;#runStopContinuation 的三个调用点均未包裹 try/catch)。续跑(continuation)的循环状态以 createDaemonToolLoopState('off') 创建(Session.ts:4328),'off' 仅关闭重复失败守卫——单轮工具调用上限与无效参数停滞检测仍然有效,因此 Stop 续跑仍可能触发循环检测。改动前该路径返回 {kind:'terminal', stopReason:'end_turn'};现在 throw 会传入 cron 的兜底 catch(Session.ts:6397-6407):cronHadError = true、完成状态变为 'error',且 cronCompleted 保持 false 导致 #scheduleChannelDelivery(Session.ts:6432-6446)被静默跳过。后台通知轮次同样退化为 [notification error] 路径(约 7004 行)。这与本 PR 新增设计文档中“Cron 与后台通知保持既有非交互处理”的承诺矛盾——该承诺仅对主循环(Session.ts:6372-6378、6974-6981)成立,对这条共享的 Stop 续跑路径不成立。 — 失败场景:带有待办 todos 的 cron/后台通知轮次进入 Stop 续跑,续跑中的工具调用触发循环检测(例如同一工具连续 3 次无效参数,或达到单轮上限)→ 该轮被标记为 'error',输出 [cron error] Tool-call loop protection stopped this turn…,且配置的频道投递被静默跳过——定时任务的输出永远无法送达目标。建议修复:按调用方门控该拒绝(例如仅前台链路设置 rejectOnLoopDetected 选项),或在 cron/通知处理器中捕获 LOOP_DETECTED RequestError 并恢复原有的正常结束轮次处理(见上方代码示例)。

— qwen3.8-max via Qwen Code /review (v0.21.8)

Comment thread packages/acp-bridge/src/bridge.ts Outdated
Comment on lines +4940 to +4941
const replay = entry.events.snapshotReplay();
const turnError = entry.turnError

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] R1-2: Round 2 — still stands at the reviewed commit (unchanged since round 1). snapshotReplay() is computed unconditionally on every bounded refresh, but is only read inside the entry.turnError branch below it. It runs flattenReplaySegments() (slice + flatMap over all replay segments) plus liveJournal.slice() — O(replay window) — and the result is discarded whenever entry.turnError is unset, which is the overwhelming common case (set only on active-turn failure, deleted at the next dispatch). Pre-PR this branch returned page.events directly with no snapshot; snapshot() has no side effects, so deferring it is safe. — Concrete cost: every Web Shell refresh of a live session without a turn error pays the full replay-window flatten + copy and discards it; long sessions (replay window up to the configured caps) pay proportionally more on this user-facing path. Suggested fix: hoist the snapshot into the branch that uses it:

const turnError = entry.turnError
  ? (() => {
      const replay = entry.events.snapshotReplay();
      return [
        ...(replay?.compactedTurns ?? []),
        ...(replay?.liveJournal ?? []),
      ]
        .reverse()
        .find((event) => event.type === 'turn_error');
    })()
  : undefined;
中文说明

[Suggestion] snapshotReplay() 在每次 bounded refresh 上都会被无条件计算,但只有下方 entry.turnError 分支会读取它。该调用会执行 flattenReplaySegments()(对全部 replay 段做 slice + flatMap)外加 liveJournal.slice()——开销为 O(replay 窗口)——而 entry.turnError 未设置时(绝大多数情况:它只在活跃轮次失败时设置、在下次 dispatch 时删除)结果会被直接丢弃。改动前该分支直接返回 page.events,完全不做快照;snapshot() 没有副作用,因此延迟执行是安全的。 — 具体代价:每次没有 turn error 的 live session 刷新(Web Shell 的常见场景)都要为整个 replay 窗口付出一次 flatten + 拷贝再丢弃;长会话(replay 窗口可达配置上限)在这条用户可见路径上的开销按比例放大。建议修复:将快照移入使用它的分支内(见上方代码示例)。

— qwen3.8-max via Qwen Code /review (v0.21.8)

Comment thread packages/web-shell/client/App.tsx Outdated
Comment on lines +6056 to +6057
if (block.errorKind !== 'loop_detected') {
retryableTurnErrorId = block.id;

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] R1-3: Round 2 — still stands at the reviewed commit (unchanged since round 1). The "loop_detected is not retryable" policy is encoded as two independent string checks — here and in transcriptToMessages.ts:782-783 (retryable: errorBlock.source === 'turn_error' && errorKind !== 'loop_detected') — with no shared predicate; grep confirms these are the only two loop_detected policy sites in web-shell, and this diff itself extends the DAEMON_ERROR_KINDS taxonomy they key off. — Failure scenario: when a second non-retryable kind is added, both sites must be edited; updating only the adapter leaves the composer retry affordance offering to resubmit the exact prompt the daemon just terminated (for loop_detected: re-running the prompt that tripped loop protection, likely re-looping and burning another turn), and updating only App.tsx leaves the per-message affordance inconsistent with the hint. Suggested fix: extract one predicate in the transcript adapter and consume it from both sites:

export function isRetryableTurnErrorKind(errorKind: string | undefined): boolean {
  return errorKind !== 'loop_detected';
}
中文说明

[Suggestion] “loop_detected 不可重试”这一策略被写成两处相互独立的字符串比较——此处与 transcriptToMessages.ts:782-783retryable: errorBlock.source === 'turn_error' && errorKind !== 'loop_detected')——没有共享谓词;grep 确认这是 web-shell 中仅有的两处 loop_detected 策略点,而本 PR 本身还在扩展它们所依赖的 DAEMON_ERROR_KINDS 分类。 — 失败场景:将来新增第二种不可重试的 kind 时,两处都必须修改;只改 adapter 会让输入框上方的重试入口仍然提议重新提交刚被 daemon 终止的 prompt(对 loop_detected 而言就是重跑触发循环保护的 prompt,很可能再次循环并再烧一轮);只改 App.tsx 则会让单条消息上的重试入口与提示不一致。建议修复:在 transcript adapter 中提取一个共享谓词(见上方代码),两处都消费它。

— qwen3.8-max via Qwen Code /review (v0.21.8)

Comment on lines +1224 to +1226
const code =
structuredErrorKind === 'loop_detected'
? (extractJsonRpcErrorField(err, 'code') ?? extractErrorCode(err))

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] R1-4: Round 2 — still stands at the reviewed commit (unchanged since round 1). The preference for the structured data.code over the JSON-RPC numeric code is gated on structuredErrorKind === 'loop_detected' instead of on "this error is structured" (structuredErrorKind !== undefined). This PR establishes new RequestError(-32603, msg, { code: 'X', errorKind: 'x_kind' }) as the pattern for structured turn errors, and the DAEMON_ERROR_KINDS taxonomy it extends is explicitly growing. No observable misbehavior today — the other structured producers (session_writer_* kinds) carry no data.code and their per-kind rpcCodes are meaningful — but the gate is written for exactly one kind. — Failure scenario: the next error kind thrown with this PR's RequestError pattern passes through broadcastTurnError unchanged and ships code: '-32603' (a stringified JSON-RPC number) in its turn_error payload until someone edits this function again.

Suggested change
const code =
structuredErrorKind === 'loop_detected'
? (extractJsonRpcErrorField(err, 'code') ?? extractErrorCode(err))
const code =
structuredErrorKind !== undefined
? (extractJsonRpcErrorField(err, 'code') ?? extractErrorCode(err))
: extractErrorCode(err);
中文说明

[Suggestion] 结构化 data.code 优先于 JSON-RPC 数字 code 的取值逻辑被门控在 structuredErrorKind === 'loop_detected' 上,而不是门控在“这是一个结构化错误”(structuredErrorKind !== undefined)上。本 PR 已把 new RequestError(-32603, msg, { code: 'X', errorKind: 'x_kind' }) 确立为结构化轮次错误的模式,而它所扩展的 DAEMON_ERROR_KINDS 分类明确还在增长。当前没有可观察的错误行为——其他结构化来源(session_writer_* 系列)不携带 data.code,且各自的 rpcCode 本身有意义——但这个门控只为一种 kind 而写。 — 失败场景:下一个按本 PR 的 RequestError 模式抛出的错误 kind 会原样通过 broadcastTurnError,在其 turn_error 负载中携带 code: '-32603'(被字符串化的 JSON-RPC 数字),直到有人再次修改这个函数。建议按上方 suggestion 修改(对现有行为逐字节等价,未来的结构化 kind 自动获得正确的取值优先级)。

— qwen3.8-max via Qwen Code /review (v0.21.8)

Comment on lines +3936 to +3940
if (nextAfterTools.stoppedByRepeatedToolFailure) {
return {
stopReason: getAbortAwareEndTurnStopReason(
pendingSend.signal,
),
};
if (pendingSend.signal.aborted) {
return { stopReason: 'cancelled' };
}
throw createLoopDetectedTurnError(toolLoopState);

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] R1-5: Round 2 — still stands at the reviewed commit (unchanged since round 1). This abort-precedence-then-throw block is pasted four times in this diff — here and at Session.ts:3947-3951 (plain { stopReason: 'cancelled' } shape), plus 4883-4892 and 4902-4911 ({ kind: 'terminal', stopReason: 'cancelled', supersededAutomaticContinuation? } shape) — replacing two call sites of the file's existing precedence helper getAbortAwareEndTurnStopReason (Session.ts:402) with inlined copies. The PR's stated invariant "cancellation takes precedence when it races the loop stop" is now encoded in four separate places in two already-divergent shapes. — Failure scenario: any future change to loop-stop terminal semantics — a new structured field on the error, a different stop reason, superseded-continuation handling in the guard-loop copies — must be applied to all four sites; missing one makes one loop type (tool-call cap vs repeated-failure vs continuation) resolve to a different stopReason/outcome than the others, silently per detector. Suggested fix: extract one helper next to createLoopDetectedTurnError and call it from all four sites, building each site's return object around the returned 'cancelled':

function cancelledOrThrowLoopDetected(
  signal: AbortSignal,
  loopState: DaemonToolLoopState,
): 'cancelled' {
  if (signal.aborted) return 'cancelled';
  throw createLoopDetectedTurnError(loopState);
}
中文说明

[Suggestion] 这个“先检查 abort 再 throw”的块在本 diff 中被粘贴了四次——此处与 Session.ts:3947-3951(普通 { stopReason: 'cancelled' } 形状),以及 4883-4892 与 4902-4911({ kind: 'terminal', stopReason: 'cancelled', supersededAutomaticContinuation? } 形状)——把该文件已有的优先级辅助函数 getAbortAwareEndTurnStopReason(Session.ts:402)的两处调用替换成了内联副本。PR 声明的不变量“取消与循环停止竞争时以取消为准”现在被编码在四个位置、且已是两种相互分歧的形状。 — 失败场景:未来任何对循环停止终止语义的修改——错误上新增结构化字段、更换 stop reason、在 guard-loop 副本中处理 superseded-continuation——都必须同时应用到四处;漏掉任何一处都会让某种循环类型(工具调用上限 / 重复失败 / 续跑)解析出与其他类型不同的 stopReason/结果,且按检测器静默分歧。建议修复:在 createLoopDetectedTurnError 旁提取一个辅助函数(见上方代码),四处调用它,各调用点围绕返回的 'cancelled' 构造自己的返回对象。

— qwen3.8-max via Qwen Code /review (v0.21.8)

Comment thread packages/acp-bridge/src/bridge.test.ts Outdated
Comment on lines +4131 to +4132
expect(refreshed.compactedReplay).toContainEqual(
expect.objectContaining({

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] R1-6: Round 2 — still stands at the reviewed commit (unchanged since round 1). This test mocks the persisted transcript page with events: [] and asserts with toContainEqual, so the position of the appended turn_error is unpinned. Probe-verified in round 1 on this code: mutating refreshedReplayFieldsFor from [...page.events, turnError] to [turnError, ...page.events] survives the test; strengthening the mocked page with one event and asserting the turn_error is the last element fails under the same mutation (and passes against the PR as written). — Failure scenario: a future refactor of the bounded-refresh path prepends (or otherwise misorders) the in-memory turn_error; with a non-empty persisted page, Web Shell would render the loop-detected error block above the replayed history of the turn it terminates, and this test stays green because toContainEqual over a single element plus an empty array cannot distinguish append from prepend. Suggested fix: return at least one event from the mocked sessionTranscript response and pin the position:

expect(
  refreshed.compactedReplay[refreshed.compactedReplay.length - 1],
).toMatchObject({ type: 'turn_error', promptId: 'prompt-loop' });
中文说明

[Suggestion] 该测试把持久化转录分页 mock 成 events: [] 并用 toContainEqual 断言,因此追加的 turn_error 的位置没有被钉住。已在第 1 轮用探针验证:把 refreshedReplayFieldsFor[...page.events, turnError] 变异为 [turnError, ...page.events],测试仍然通过;把 mock 分页改为返回一个事件并断言 turn_error 是最后一个元素后,同一变异会使测试失败(对未变异的 PR 代码则通过)。 — 失败场景:未来对 bounded-refresh 路径的重构把内存中的 turn_error 前置(或以其他方式错序);在持久化分页非空时,Web Shell 会把循环检测错误块渲染在它终止的那轮重放历史上方,而本测试仍为绿色——因为 toContainEqual 作用于单元素加空数组时无法区分追加与前置。建议修复:让 mock 的 sessionTranscript 响应至少返回一个事件,并钉住位置(见上方代码)。

— qwen3.8-max via Qwen Code /review (v0.21.8)

Comment on lines +4901 to +4903
if (nextAfterTools.stoppedByRepeatedToolFailure) {
if (pendingSend.signal.aborted) {
return {

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] R1-7: Round 2 — still stands at the reviewed commit (unchanged since round 1). The diff deletes the only test covering cancellation racing a repeated-tool-failure loop stop (returns cancelled when cancellation arrives while the stop message is emitted) and adds no replacement for that path. Verified again this round: the replacement test (lets cancellation win while a loop-detected Stop continuation is preserved) reaches only the tool-call-cap toolRun.loopDetected branch of #runStopContinuation#buildNextMessageAfterToolRun is never called on that path. The pre-existing drain test does not cover these branches either: its cancellation fires #buildNextMessageAfterToolRun's abort early-return before the stop decision is computed. The new abort sub-branches at Session.ts:3937-3939 and here therefore have zero cancellation coverage (round-2 v8 coverage measurement: 0 hits). — Failure scenario: if the if (pendingSend.signal.aborted) check were dropped or reordered during a future refactor of the four-way duplicated block, a user cancellation landing during the post-failure drain would surface a LOOP_DETECTED turn error instead of stopReason: 'cancelled' — violating the PR's own "cancellation still wins if it races loop termination" promise — and no test would fail. Suggested fix: add a cancellation test for the repeated-tool-failure path (failing tool + failure streak in enforce mode, cancel during the post-failure drain window, expect { stopReason: 'cancelled' }), or parameterize the existing cancellation test over both loop triggers.

中文说明

[Suggestion] 本 diff 删除了唯一覆盖“取消与重复工具失败循环停止竞争”的测试(returns cancelled when cancellation arrives while the stop message is emitted),且没有为该路径补充替代测试。本轮再次核实:替代测试(lets cancellation win while a loop-detected Stop continuation is preserved)只到达 #runStopContinuation 中工具调用上限的 toolRun.loopDetected 分支——该路径根本不会调用 #buildNextMessageAfterToolRun。既有的 drain 测试也未覆盖这些分支:它的取消触发的是 #buildNextMessageAfterToolRun 的 abort 提前返回,发生在停止决定计算之前。于是 Session.ts:3937-3939 与此处新增的 abort 子分支完全没有取消场景覆盖(第 2 轮 v8 覆盖率实测:0 次命中)。 — 失败场景:如果未来重构这个四处复制的块时删除或调换了 if (pendingSend.signal.aborted) 检查,用户在失败后 drain 期间发起的取消会表现为 LOOP_DETECTED 轮次错误而不是 stopReason: 'cancelled'——违背 PR 自己承诺的“取消与循环终止竞争时仍以取消为准”——且没有任何测试会失败。建议修复:为重复工具失败路径补一个取消测试(enforce 模式下的失败工具 + 失败连击,在失败后 drain 窗口内取消,期望 { stopReason: 'cancelled' }),或把现有取消测试参数化到两种循环触发器。

— qwen3.8-max via Qwen Code /review (v0.21.8)

Comment thread packages/acp-bridge/src/bridge.ts Outdated
Comment on lines +4950 to +4951
compactedReplay: turnError
? [...page.events, turnError]

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] R1-8: Round 2 — still stands at the reviewed commit (unchanged since round 1). Probe-verified in round 1 on this code (outOfOrder=true on the unmodified PR; flipped by the guard below): cron/background-notification turns execute inside the CLI session and bypass bridge prompt admission — the only delete entry.turnError site (bridge.ts:6778) — while their session updates still flow through the bridge fan-in (bridgeClient.ts:908-927) into the journal with newer ids and into persisted records. This append then places the stale loop turn_error after page.events that already end with the newer automatic turn's content, so the refreshed transcript renders the loop error after content that actually happened later — and the stale entry.turnError keeps re-appending it on every refresh until the next interactive prompt admission. — Failure scenario: an interactive prompt fails with loop detection; before the user refreshes, a cron tick or background-notification turn runs on the same session → the refreshed Web Shell transcript shows the loop error after the automatic turn's output, reading as if the error belongs to the automatic turn. Suggested fix: only append when the found turn_error is still the last journaled event (or store the published event on the entry at broadcast time and append it only while it is newest):

Suggested change
compactedReplay: turnError
? [...page.events, turnError]
compactedReplay:
turnError &&
[...(replay?.compactedTurns ?? []), ...(replay?.liveJournal ?? [])].at(-1)?.id === turnError.id
? [...page.events, turnError]
: page.events,
中文说明

[Suggestion] 已在第 1 轮用探针验证(未修改的 PR 代码上观察到 outOfOrder=true;加上下方守卫后翻转):cron/后台通知轮次在 CLI 会话内部执行,绕过 bridge 的 prompt 准入——而那是唯一 delete entry.turnError 的位置(bridge.ts:6778)——但它们的 session update 仍会经由 bridge 扇入(bridgeClient.ts:908-927)以更小的新 id 进入日志并写入持久化记录。于是这里的追加会把过期的循环 turn_error 放在已经以更新的自动轮次内容结尾的 page.events 之后,刷新后的转录会把循环错误渲染在实际发生得更晚的内容之后——且过期的 entry.turnError 会在每次刷新时重复追加,直到下一个交互 prompt 准入。 — 失败场景:交互 prompt 因循环检测失败;用户刷新之前,同一会话上运行了一次 cron 或后台通知轮次 → 刷新后的 Web Shell 转录在自动轮次的输出之后显示循环错误,读起来像是该错误属于那个自动轮次。建议修复:仅当找到的 turn_error 仍是日志中最后一个事件时才追加(见上方 suggestion;或在广播时把发布的事件存到 entry 上,仅当它仍最新时追加)。

— qwen3.8-max via Qwen Code /review (v0.21.8)

Comment on lines 3940 to 3942
throw createLoopDetectedTurnError(toolLoopState);
}
if (toolRun.loopDetected) {

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] R2-2: Loop-detected rejections route through the bridge's pre-existing "forward failed" compensation path (broadcastPromptCancelled[Once] in bridge.ts ~6864-6900), newly reachable because this PR is what makes loop stops reject. The compensation handler treats any non-deadline, non-abort rejection as a failed forward: it logs sendPrompt: forward failed for session … to daemon stderr and publishes prompt_cancelled{reason:'forward_failed'} before the formal turn_error terminal. Probe-verified on this commit: running this PR's own new bridge test prints sendPrompt: forward failed for session sess:/work/a: Loop protection stopped this turn. The SDK transcript reducer already suppresses the prompt.cancelled UI block for the forward_failed reason (sdk-typescript transcript.ts:372-382), so Web Shell renders nothing extra — the practical harm is a phantom forward-failure line on daemon stderr for every loop-detected turn (oncall noise) plus a misleading wire event for non-SDK consumers. — Failure scenario: oncall investigating a loop-stopped turn sees sendPrompt: forward failed in stderr and chases a forward/transport problem that does not exist; non-SDK event consumers receive prompt_cancelled{forward_failed} for a turn that was neither cancelled nor a forward failure. Suggested fix: in the compensation handler, skip the stderr line and the prompt_cancelled{forward_failed} broadcast for structured turn errors carrying data.errorKind (the formal turn_error terminal already covers them).

中文说明

[Suggestion] loop 检测导致的 prompt 拒绝会走进 bridge 既有的 “forward failed” 补偿路径(bridge.ts 约 6864-6900 的 broadcastPromptCancelled[Once])——这条路径是因为本 PR 让循环停止变为拒绝才变得可达。补偿处理器把任何非超时、非取消的拒绝都当作转发失败:向日志 stderr 打印 sendPrompt: forward failed for session …,并在正式的 turn_error 终止事件之前发布 prompt_cancelled{reason:'forward_failed'}。已在本提交上用探针验证:运行本 PR 自己新增的 bridge 测试会打印 sendPrompt: forward failed for session sess:/work/a: Loop protection stopped this turn。SDK 的转录 reducer 已对 forward_failed 原因抑制 prompt.cancelled UI 块(sdk-typescript transcript.ts:372-382),因此 Web Shell 不会多渲染内容——实际危害是每次循环检测停止都会在 daemon stderr 留下一条虚假的转发失败日志(误导 oncall),以及非 SDK 事件消费方收到一条语义错误的 wire 事件。 — 失败场景:排查循环停止轮次的 oncall 在 stderr 看到 sendPrompt: forward failed,会去追查一个并不存在的转发/传输问题;非 SDK 事件消费方会收到 prompt_cancelled{forward_failed},而该轮既不是用户取消也不是转发失败。建议修复:在补偿处理器中,对携带 data.errorKind 的结构化轮次错误跳过 stderr 打印与 prompt_cancelled{forward_failed} 广播(正式的 turn_error 终止事件已覆盖)。

— qwen3.8-max via Qwen Code /review (v0.21.8)

Comment thread packages/web-shell/client/App.tsx Outdated
Comment on lines +6055 to +6056
if (block?.kind === 'error' && block.source === 'turn_error') {
retryableTurnErrorId = block.id;
if (block.errorKind !== 'loop_detected') {

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] R2-5: Suppressing retryability for loop_detected also silences the public onSessionChange turn_complete error signal, because that signal is derived from the same retryableTurnErrorIdRef. The streaming→idle effect (App.tsx:6092-6104) builds the public dispatch as error: retryableTurnErrorIdRef.current != null ? new Error(...) : undefined — so a loop-stopped turn, which is now a genuine failure everywhere else (prompt rejected with -32603, turn_error terminal event, summary.turnError set, localized error block rendered), dispatches turn_complete with error: undefined. Probe-verified on this commit: a loop-detected turn dispatches turn_complete with error: undefined while a turn_error of any other kind carries the error; reverting this gate restores it. Not a regression (pre-diff loop turns also dispatched error: undefined), but a new undocumented asymmetry among error kinds introduced by reusing the retryability ref. — Failure scenario: an embedder of the web-shell package keying failure notifications/badges on turn_complete.error cannot distinguish a loop-detected failure from a successful turn. Suggested fix: track turn-error detection separately from retryability — keep a lastTurnErrorIdRef set for every turn_error block (including loop_detected) and use it for the turn_complete error flag, while retryableTurnErrorIdRef stays gated on errorKind !== 'loop_detected' for the retry hint.

中文说明

[Suggestion]loop_detected 抑制重试的同时,也让公开的 onSessionChange turn_complete 错误信号失声了——因为该信号派生自同一个 retryableTurnErrorIdRef。streaming→idle 副作用(App.tsx:6092-6104)按 error: retryableTurnErrorIdRef.current != null ? new Error(...) : undefined 构造公开事件——于是被循环保护停止的轮次(在其他所有层面都是真正的失败:prompt 以 -32603 拒绝、turn_error 终止事件、summary.turnError 已设置、本地化错误块已渲染)却会以 error: undefined 派发 turn_complete。已在本提交上用探针验证:loop_detected 轮次派发的 turn_completeerrorundefined,而其他任何 kind 的 turn_error 都携带 error;还原这个门控后信号恢复。这不是回归(改动前循环停止轮次同样派发 error: undefined),而是复用重试引用带来的、未记录在案的新 kind 间不对称。 — 失败场景:基于 turn_complete.error 做失败通知/角标的 web-shell 嵌入方无法区分“循环检测停止”与“轮次成功”。建议修复:把轮次错误检测与可重试性分开追踪——为每个 turn_error 块(含 loop_detected)设置一个 lastTurnErrorIdRef 并用于 turn_complete 的 error 标志,retryableTurnErrorIdRef 继续按 errorKind !== 'loop_detected' 门控重试提示。

— qwen3.8-max via Qwen Code /review (v0.21.8)

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

⏸️ Takeover paused: this PR reached its round cap (100/100). Comment @qwen-code /takeover to re-arm a fresh window and continue management, or @qwen-code /takeover stop to release.

中文说明

⏸️ 托管已暂停:本 PR 达到轮次上限(100/100)。评论 @qwen-code /takeover 可重新武装、开启新窗口继续托管;或评论 @qwen-code /takeover stop 释放。

@ytahdn

ytahdn commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator Author

@qwen-code /takeover

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🔄 Takeover re-armed: the round counter starts a fresh window (previous rounds no longer count toward the cap); management continues.

中文说明

🔄 已重新武装:轮次计数开启新窗口(此前轮次不再计入上限),托管继续。

Stop-hook continuations are shared with cron and background-notification
turns, which must keep their pre-loop-error graceful end-turn handling;
only the foreground prompt chain now rejects a loop-detected stop.

Also folds in review feedback: drop the unreachable repeated-failure
branch from the continuation, extract the cancellation-precedence
helper, defer and freshness-guard the bounded-refresh turn-error replay,
skip the phantom forward-failed compensation for structured turn errors,
key the structured code gate on structuredness, share the retryability
predicate in Web Shell, and keep the turn_complete error signal alive
for loop-detected turns.
@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 Addressed the latest review feedback (round 1/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 1/100 轮)。改动内容与我反驳保留之处如下:

Review feedback addressed — PR #8853 (round 3)

All 11 unique findings (2 Critical rounds of R1-1 + 9 Suggestions) were verified against the reviewed commit and resolved in code in commit 0fe2ef9291. No finding was declined, deferred, or escalated; no conflicts (--conflict false, base main merged at HEAD via the prior merge commit).

Findings and resolutions

  • R1-1 (Critical, rc:3749466923 / rc:3751317684) — Confirmed: the unconditional throw createLoopDetectedTurnError(...) inside #runStopContinuation was shared by cron and background-notification callers through #handleStopHookLoop, turning their turns into error completions and silently skipping cron channel delivery — contradicting the design doc. Fix: added a rejectOnLoopDetected option threaded through #handleStopHookLoop to all three #runStopContinuation call sites; only the foreground prompt chain passes true. Automatic turns now return the pre-PR { kind: 'terminal', stopReason: 'end_turn' } (or cancelled when racing an abort). Regression test added: keeps a cron turn graceful when its Stop continuation trips loop protection — a cron turn whose Stop continuation trips the explicit per-turn tool-call cap ends gracefully (no [cron error], loop event still recorded).
  • R1-9 (rc:3749466978) — Confirmed unreachable: #runStopContinuation's loop state is createDaemonToolLoopState('off') and reduceRepeatedToolFailureGuard short-circuits for mode off, so stoppedByRepeatedToolFailure can never be true there. Fix: removed the dead 12-line branch (also removes the fourth divergent abort-throw copy).
  • R1-5 (rc:3749466951 / rc:3751317715) — Confirmed the four pasted abort-precedence-then-throw blocks. Fix: extracted cancelledOrThrowLoopDetected(signal, loopState) next to createLoopDetectedTurnError and called it from the three remaining sites (two foreground + one continuation); combined with the R1-1 gate the continuation site selects between the helper and getAbortAwareEndTurnStopReason by caller.
  • R1-2 (rc:3749466928 / rc:3751317689) — Confirmed snapshotReplay() ran on every bounded refresh and was discarded without entry.turnError. Fix: the snapshot is now taken only inside the entry.turnError branch of refreshedReplayFieldsFor.
  • R1-8 (rc:3749466972 / rc:3751317733) — Confirmed the stale in-memory turn_error was re-appended after newer automatic-turn events. Fix: the append now happens only while the found turn_error is still the newest journaled event (journal.at(-1)?.id === turnError.id).
  • R2-2 (rc:3751317736) — Confirmed loop-detected rejections hit the forward failed compensation path (phantom stderr line + prompt_cancelled{forward_failed} before the formal turn_error). Fix: in the compensation handler, errors carrying structured data.errorKind skip the stderr line and the prompt_cancelled broadcast (pending permissions are still cleared); the formal turn_error terminal covers the turn.
  • R1-4 (rc:3749466937 / rc:3751317701) — Confirmed the structured-code preference was gated on === 'loop_detected'. Fix: gated on structuredErrorKind !== undefined (byte-equivalent today, correct for future structured kinds).
  • R1-3 (rc:3749466933 / rc:3751317695) — Confirmed two independent loop_detected string checks. Fix: exported isRetryableTurnErrorKind(errorKind) from transcriptToMessages.ts and consumed it from both the adapter and App.tsx.
  • R2-5 (rc:3751317743) — Confirmed turn_complete derived its error flag from retryableTurnErrorIdRef, silencing loop-detected turns. Fix: added lastTurnErrorIdRef, set for every turn_error block regardless of kind, and used it for the turn_complete error flag; retryableTurnErrorIdRef keeps gating only the retry affordance. Regression test added: still reports a loop-detected turn error through turn_complete.
  • R1-6 (rc:3749466957 / rc:3751317721) — Confirmed the bridge test left the appended position unpinned. Fix: the mocked persisted page now returns one event and the test asserts the turn_error is the last replay element. Probe-verified: mutating the append to a prepend now fails this test (it passed before the strengthening).
  • R1-7 (rc:3749466966 / rc:3751317727) — Confirmed zero cancellation coverage for the repeated-tool-failure stop branch. Fix: added returns cancelled when cancellation races the repeated-failure stop — enforce mode + failing-tool streak, cancellation lands inside the post-failure waitForPendingRewrites window (the blocking-rewriter pattern), expects { stopReason: 'cancelled' } and proves the stop decision actually fired (loop_detected telemetry recorded). The abort sub-branch in #runStopContinuation flagged here was removed as dead by the R1-9 fix.

Files changed

  • packages/cli/src/acp-integration/session/Session.ts
  • packages/cli/src/acp-integration/session/Session.test.ts
  • packages/acp-bridge/src/bridge.ts
  • packages/acp-bridge/src/bridge.test.ts
  • packages/web-shell/client/App.tsx
  • packages/web-shell/client/App.test.tsx
  • packages/web-shell/client/adapters/transcriptToMessages.ts

Verification

Commands actually run (this round, after the final state of the commit):

  • npm run buildpassed (exit 0)
  • npm run typecheckpassed (exit 0)
  • npm run lintpassed (exit 0)
  • npx prettier --write / --check on the seven touched files — passed (no reformatting needed)
  • cd packages/cli && npx vitest run src/acp-integration/session20 files, 948 passed
  • cd packages/cli && npx vitest run src/acp-integration/session/Session.test.ts563 passed
  • cd packages/acp-bridge && npx vitest run26 files, 1202 passed
  • cd packages/web-shell && npx vitest run177 files, 3072 passed
  • Mutation probe for the R1-6 test strengthening: temporarily mutating the replay append from [...page.events, turnError] to [turnError, ...page.events] makes keeps the current turn error when refreshing from persisted history fail (restored afterwards; the same test passes against the committed code).
  • Integration tests were not run: the touched behavior (ACP session loop-stop handling, bridge replay/compensation, Web Shell rendering) is exercised directly by the unit suites above, not only through the bundled CLI or integration harness.
中文说明

已处理的评审反馈 — PR #8853(第 3 轮)

全部 11 个独立发现(R1-1 的两轮 Critical + 9 条 Suggestion)均已在被评审提交上逐条核实,并在提交 0fe2ef9291于代码层面解决。没有拒绝、延后或升级任何发现;无冲突(--conflict false,基线 main 已通过先前的合并提交并入 HEAD)。

发现与处理

  • R1-1(Critical,rc:3749466923 / rc:3751317684) — 已确认:#runStopContinuation 中无条件的 throw createLoopDetectedTurnError(...) 会经由 #handleStopHookLoop 被 cron 与后台通知调用方共享,使这些轮次变为 error 完成状态,并静默跳过 cron 频道投递——与设计文档矛盾。**修复:**新增 rejectOnLoopDetected 选项,经 #handleStopHookLoop 传递到全部三处 #runStopContinuation 调用点;仅前台 prompt 链路传入 true。自动轮次恢复 PR 前的 { kind: 'terminal', stopReason: 'end_turn' }(与取消竞争时为 cancelled)。新增回归测试:keeps a cron turn graceful when its Stop continuation trips loop protection —— cron 轮次的 Stop 续跑触发显式单轮工具调用上限后优雅结束(无 [cron error],循环事件仍被记录)。
  • R1-9(rc:3749466978) — 已确认不可达:#runStopContinuation 的循环状态为 createDaemonToolLoopState('off'),而 reduceRepeatedToolFailureGuardoff 模式直接短路,因此那里的 stoppedByRepeatedToolFailure 永远不可能为真。**修复:**删除该 12 行死分支(同时消除了第四份分歧的 abort-throw 拷贝)。
  • R1-5(rc:3749466951 / rc:3751317715) — 已确认四处粘贴的“先 abort 后 throw”块。**修复:**在 createLoopDetectedTurnError 旁提取 cancelledOrThrowLoopDetected(signal, loopState),并在剩余三处(前台两处 + 续跑一处)调用;结合 R1-1 的门控,续跑处按调用方在 helper 与 getAbortAwareEndTurnStopReason 之间选择。
  • R1-2(rc:3749466928 / rc:3751317689) — 已确认 snapshotReplay() 在每次 bounded refresh 上都会执行、且在无 entry.turnError 时被丢弃。**修复:**快照现在只在 refreshedReplayFieldsForentry.turnError 分支内获取。
  • R1-8(rc:3749466972 / rc:3751317733) — 已确认过期的内存 turn_error 会被追加到更新的自动轮次事件之后。**修复:**仅当找到的 turn_error 仍是日志中最新事件(journal.at(-1)?.id === turnError.id)时才追加。
  • R2-2(rc:3751317736) — 已确认 loop 检测拒绝会落入 forward failed 补偿路径(虚假 stderr 日志 + 在正式 turn_error 之前发出 prompt_cancelled{forward_failed})。**修复:**补偿处理器对携带结构化 data.errorKind 的错误跳过 stderr 输出与 prompt_cancelled 广播(仍会清理待处理权限);轮次由正式的 turn_error 终止事件覆盖。
  • R1-4(rc:3749466937 / rc:3751317701) — 已确认结构化 code 的优先取值被门控在 === 'loop_detected' 上。**修复:**改为门控在 structuredErrorKind !== undefined 上(当前行为逐字节等价,对未来新增的结构化 kind 自动正确)。
  • R1-3(rc:3749466933 / rc:3751317695) — 已确认两处相互独立的 loop_detected 字符串判断。**修复:**在 transcriptToMessages.ts 中导出 isRetryableTurnErrorKind(errorKind),adapter 与 App.tsx 两处共同消费。
  • R2-5(rc:3751317743) — 已确认 turn_complete 的错误标志派生自 retryableTurnErrorIdRef,导致 loop 检测停止的轮次失声。**修复:**新增 lastTurnErrorIdRef,对任何 kind 的 turn_error 块都会设置,并用于 turn_complete 的错误标志;retryableTurnErrorIdRef 继续只门控重试提示。新增回归测试:still reports a loop-detected turn error through turn_complete
  • R1-6(rc:3749466957 / rc:3751317721) — 已确认该 bridge 测试未钉住追加位置。修复:mock 的持久化分页现在返回一个事件,并断言 turn_error 是重放的最后一个元素。已用探针验证:把追加变异为前置后该测试会失败(加强前该变异可以存活)。
  • R1-7(rc:3749466966 / rc:3751317727) — 已确认重复工具失败停止分支完全没有取消覆盖。**修复:**新增 returns cancelled when cancellation races the repeated-failure stop —— enforce 模式 + 失败工具连击,取消落在失败后 waitForPendingRewrites 窗口内(阻塞 rewriter 模式),期望 { stopReason: 'cancelled' },并证明停止决定确实已触发(记录了 loop_detected 遥测)。此处被点名的 #runStopContinuation 内 abort 子分支已随 R1-9 作为死代码删除。

变更文件

  • packages/cli/src/acp-integration/session/Session.ts
  • packages/cli/src/acp-integration/session/Session.test.ts
  • packages/acp-bridge/src/bridge.ts
  • packages/acp-bridge/src/bridge.test.ts
  • packages/web-shell/client/App.tsx
  • packages/web-shell/client/App.test.tsx
  • packages/web-shell/client/adapters/transcriptToMessages.ts

验证

本轮在提交最终状态上实际执行的命令:

  • npm run build通过(退出码 0)
  • npm run typecheck通过(退出码 0)
  • npm run lint通过(退出码 0)
  • 对 7 个变更文件执行 npx prettier --write / --check通过(无需重新格式化)
  • cd packages/cli && npx vitest run src/acp-integration/session20 个文件,948 通过
  • cd packages/cli && npx vitest run src/acp-integration/session/Session.test.ts563 通过
  • cd packages/acp-bridge && npx vitest run26 个文件,1202 通过
  • cd packages/web-shell && npx vitest run177 个文件,3072 通过
  • R1-6 测试加强的变异探针:将重放追加从 [...page.events, turnError] 临时变异为 [turnError, ...page.events] 会使 keeps the current turn error when refreshing from persisted history 失败(随后已还原;同一测试对已提交代码通过)。
  • 未运行集成测试:本次触及的行为(ACP 会话循环停止处理、bridge 重放/补偿、Web Shell 渲染)已由上述单元测试直接覆盖,并非只能通过打包后的 CLI 或集成测试框架验证。

Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。

Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. · 有空请复审;第 100 轮后本 bot 停止并将 PR 交给人工。


🧠 Handled by Qwen Code · model/模型 qwen3.8-max

@qwen-code-ci-bot qwen-code-ci-bot 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.

Not explored to full depth (tool budget reached): PR #8853 converts foreground tool-loop-protection stops i...: did not trace the out-of-repo channel gateway's handling of a rejected prompt RPC (blocks full confirmation of finding 1's end-to-end outcome).; PR #8853 converts foreground tool-loop-protection stops i...: did not exhaustively enumerate every future producer of data.errorKind prompt rejections beyond Session.ts/acpAgent.ts writer and trust paths (finding 2's lat…; PR #8853 converts foreground tool-loop-protection stops i...: did not verify whether webui/SDK-projection clients have an independent idle-time tool-spinner cleanup that would mask Finding 1's residual-spinner scenario (th…; PR #8853 converts foreground tool-loop-protection stops i...: none — all checks above were completed within budget.; You are review agent reverse-audit — Reverse audit agen...: none — all planned checks completed within budget., and 1 more.

中文说明

未探索到全部深度(达到工具调用预算):PR #8853 converts foreground tool-loop-protection stops i...:did not trace the out-of-repo channel gateway's handling of a rejected prompt RPC (blocks full confirmation of finding 1's end-to-end outcome).;PR #8853 converts foreground tool-loop-protection stops i...:did not exhaustively enumerate every future producer of data.errorKind prompt rejections beyond Session.ts/acpAgent.ts writer and trust paths (finding 2's lat…;PR #8853 converts foreground tool-loop-protection stops i...:did not verify whether webui/SDK-projection clients have an independent idle-time tool-spinner cleanup that would mask Finding 1's residual-spinner scenario (th…;PR #8853 converts foreground tool-loop-protection stops i...:none — all checks above were completed within budget.;You are review agent reverse-audit — Reverse audit agen...:none — all planned checks completed within budget.,另有 1 条。

— qwen3.8-max via Qwen Code /review (v0.21.9)

Comment thread packages/acp-bridge/src/bridge.ts Outdated
Comment on lines +4955 to +4957
if (turnError && journal.at(-1)?.id === turnError.id) {
compactedReplay = [...page.events, turnError];
}

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] R3-1: The refresh-append guard only appends the in-memory turn_error while it is still the LAST journaled event of any type — but turn_error is a turn-boundary event (folded into compactedTurns, journal reset at publish), so any later non-transient event defeats the guard. Two concrete triggers: (a) pending_prompt_completed — published by result.finally for every prompt that was queued at admission — lands in liveJournal after the loop turn_error of a queued-then-promoted prompt; (b) model_switched / approval_mode_changed publishes. — Failure scenario: prompt B queues while A runs, B is promoted and trips loop protection → turn_error published → result.finally publishes pending_prompt_completed → page refresh: journal.at(-1) is pending_prompt_completed, the guard fails, the error is not appended → the reloaded transcript shows B's user message and failed/skipped tools with no terminal error — the exact "error vanishes on refresh" failure this PR exists to fix. Probe-verified on the unmodified PR: the refreshed replay contained zero turn_error events for the queued shape; a boundary-based guard restored it. Suggested fix: compare against the last turn-boundary event (turn_error/turn_complete) rather than the last event of any kind, or walk backwards skipping queue-bookkeeping events (pending_prompt_*) before comparing ids.

中文说明

[Critical] R3-1:刷新追加守卫仅在内存中的 turn_error 仍是日志中“任意类型的最后一个事件”时才追加——但 turn_error 是轮次边界事件(发布时即被折叠进 compactedTurns、日志被重置),因此其后出现的任何非瞬态事件都会使守卫失效。两个具体触发:(a) pending_prompt_completed——对每个入队时被标记为 queued 的 prompt,result.finally 都会发布它——它会落在“排队后晋升”的 prompt 的 loop turn_error 之后进入 liveJournal;(b) model_switched / approval_mode_changed 的发布同样如此。— 失败场景:prompt A 运行时 B 排队,B 晋升后触发循环保护 → 发布 turn_errorresult.finally 发布 pending_prompt_completed → 刷新页面:journal.at(-1)pending_prompt_completed,守卫失效,错误未被追加 → 重新加载的转录只显示 B 的用户消息与失败/跳过的工具,没有任何终止错误——这正是本 PR 要修复的“刷新后错误消失”问题。已在未修改的 PR 上用探针验证:排队场景下刷新后的 replay 中 turn_error 事件数为 0;改为基于轮次边界的守卫后恢复。建议修复:与最后一个轮次边界事件(turn_error/turn_complete)比较,而不是与任意类型的最后一个事件比较;或在比较 id 前从尾部向前跳过队列簿记事件(pending_prompt_*)。

— qwen3.8-max via Qwen Code /review (v0.21.9)

Comment on lines +3983 to +3984
channelDeliveryCapture,
true, // rejectOnLoopDetected

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] R3-3: Channel turns that trip loop detection lose their channel delivery entirely. Channel prompts run through Session.prompt() — the foreground path that now throws LOOP_DETECTED (this call passes rejectOnLoopDetected: true) — but #scheduleChannelDelivery is gated on channelDelivery && result.stopReason === 'end_turn' (Session.ts:3077), which only runs when #executePrompt resolves; the new throw propagates through prompt()'s catch (only SessionWriterError is converted) and skips the gate. The design doc preserves "existing non-interactive handling" for cron/background turns only — channels are a class of non-interactive turn it does not mention. — Failure scenario: a webhook turn trips the per-turn tool-call cap → prompt rejects with LOOP_DETECTED → delivery is never scheduled → DaemonChannelBridge rejects on the turn_error terminal, runWebhookTask's catch emits a failed lifecycle event and never calls pushProactive → the remote channel user gets no terminal reply where pre-PR the collected text was delivered. Suggested fix: catch the loop RequestError for channel turns and schedule the delivery (with the error text) before rethrowing, or extend the rejectOnLoopDetected gating so channel turns keep the graceful end_turn.

中文说明

[Critical] R3-3:触发循环检测的频道轮次会完全丢失频道投递。频道 prompt 经由 Session.prompt() 运行——即现在会抛出 LOOP_DETECTED 的前台路径(此调用传入 rejectOnLoopDetected: true)——但 #scheduleChannelDeliverychannelDelivery && result.stopReason === 'end_turn'(Session.ts:3077)门控,仅在 #executePrompt 正常返回时执行;新的 throw 穿过 prompt() 的 catch(只转换 SessionWriterError)并跳过该门。设计文档只为 cron/后台通知轮次保留“既有非交互处理”——频道同样是一类非交互轮次,却未被提及。— 失败场景:webhook 轮次触发单轮工具调用上限 → prompt 以 LOOP_DETECTED 拒绝 → 投递永远不会被调度 → DaemonChannelBridgeturn_error 终止事件上拒绝,runWebhookTask 的 catch 发出 failed 生命周期事件且从不调用 pushProactive → 远端频道用户收不到任何终止回复,而 PR 前已收集的文本本会被投递。建议修复:为频道轮次捕获 loop RequestError,在重新抛出前调度投递(附带错误文本);或扩展 rejectOnLoopDetected 门控,使频道轮次保持优雅的 end_turn

— qwen3.8-max via Qwen Code /review (v0.21.9)

@@ -3921,8 +3944,9 @@ export class Session implements SessionContext {
nextMessage = nextAfterTools.message;
if (nextAfterTools.stoppedByRepeatedToolFailure) {

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] R3-12: Loop-detected rejections strand queued cron/notification work. prompt() drains #drainCronQueue/#drainNotificationQueue only on the resolve path (Session.ts:3074-3075); the finally drain is gated on five TodoStopGuard flags that are all false in steady state (and the loop stop calls todoStopGuard.suspend() first, forcing blocksUnrelatedAutomaticTurns false); the mid-turn scheduler drain no-ops on if (this.pendingPrompt) return; BEFORE reaching #deferAutomaticQueueDrainUntilTurnsSettle, so nothing re-arms. Loop-detected turns previously always resolved end_turn and drained; this diff routes them into the rejection class, breaking the invariant documented at Session.ts:5923-5924. — Failure scenario: cron enabled, no active TodoStopGuard work chain; a cron job fires mid-foreground-turn and is queued; the turn trips loop detection and rejects → the cron queue is never drained; a one-shot/@wakeup job waits indefinitely for the next unrelated drain trigger — in an idle session the wakeup chain dies silently. Probe-verified: the reject arm left cronQueue=1 (stranded) while the resolve control arm drained to 0; reverting this diff hunk flips the result. Note: an unconditional finally-drain fix breaks the pinned test Session.test.ts:24292 — the fix must target the loop-detected rejection specifically (e.g. drain in a LOOP_DETECTED catch path).

中文说明

[Critical] R3-12:循环检测拒绝会使已排队的 cron/通知任务滞留。prompt() 仅在正常返回路径上排空 #drainCronQueue/#drainNotificationQueue(Session.ts:3074-3075);finally 中的排空被五个 TodoStopGuard 标志门控,稳态下它们全为 false(且循环停止会先调用 todoStopGuard.suspend(),强制 blocksUnrelatedAutomaticTurns 为 false);轮中的调度器排空在到达 #deferAutomaticQueueDrainUntilTurnsSettle 之前就因 if (this.pendingPrompt) return; 提前返回,因此没有任何重新武装。循环检测停止的轮次此前总是以 end_turn 正常返回并排空队列;本 diff 把它们路由进拒绝类,破坏了 Session.ts:5923-5924 处文档化的不变量。— 失败场景:启用 cron、无活动 TodoStopGuard 工作链;前台轮次运行中 cron 任务触发并入队;该轮触发循环检测并拒绝 → cron 队列永远不会被排空;一次性/@wakeup 任务无限期等待下一个无关的排空触发——空闲会话中唤醒链会静默死亡。探针验证:拒绝分支下 cronQueue=1(滞留),正常返回对照分支排空为 0;还原该 diff hunk 后结果翻转。注意:无条件的 finally 排空修复会破坏既有钉住的测试 Session.test.ts:24292——修复必须只针对循环检测拒绝(例如在 LOOP_DETECTED 的 catch 路径中排空)。

— qwen3.8-max via Qwen Code /review (v0.21.9)

Comment thread packages/acp-bridge/src/bridge.ts Outdated
Comment on lines +4942 to +4946
const replay = entry.events.snapshotReplay();
const journal = [
...(replay?.compactedTurns ?? []),
...(replay?.liveJournal ?? []),
];

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] R3-4: This rebuilds and scans the entire replay journal when the outcome depends only on the last event: snapshotReplay() flattens up to the full compacted replay window (default 4 MB, configurable to 256 MB) and maps the live journal (default 10,000 events), then the code copies it twice ([...spread], [...journal].reverse()) and .find()s it — while the trailing guard only ever uses journal.at(-1). Concrete cost: every loadSession attach/page-refresh of a live session while entry.turnError is pending (set for every running-prompt failure, cleared only at the next prompt dispatch) pays an O(replay-window) allocation + scan on the transcript-render latency path, growing with maxReplayBytes; and when an automatic turn ran after the error the whole computation is discarded. The backward-scan shape also misleads maintainers into thinking history is searched, inviting removal of the trailing-id guard that actually protects against stale re-append. Suggested fix: read the tail directly (liveJournal.at(-1) ?? compactedTurns.at(-1)), or store the published turn_error event (or its id) on entry.turnError at broadcast time and compare against the lastEventId getter with no snapshot at all.

中文说明

[Suggestion] R3-4:当结果只取决于最后一个事件时,这里却重建并扫描了整个回放日志:snapshotReplay() 会展平至多整个压缩回放窗口(默认 4 MB,可配置到 256 MB)并 map 整个 live journal(默认 10,000 个事件),随后代码又复制两次([...spread][...journal].reverse())并 .find()——而尾部守卫实际只使用 journal.at(-1)。具体代价:只要 entry.turnError 悬置(任何运行中 prompt 的失败都会设置,直到下一次 prompt 分发才清除),每次 live session 的 loadSession 附加/页面刷新都会在转录渲染的延迟路径上付出 O(回放窗口) 的分配 + 扫描,且随 maxReplayBytes 增长;若错误之后还运行过自动轮次,整个计算结果还会被丢弃。反向扫描的形态还会误导维护者以为在搜索历史,诱导其删除真正防止陈旧错误被重新追加的尾部 id 守卫。建议修复:直接读取尾部(liveJournal.at(-1) ?? compactedTurns.at(-1));或在广播时把已发布的 turn_error 事件(或其 id)存到 entry.turnError,与 lastEventId getter 比较,完全不做快照。

— qwen3.8-max via Qwen Code /review (v0.21.9)

Comment thread packages/acp-bridge/src/bridge.ts Outdated
Comment on lines +4955 to +4957
if (turnError && journal.at(-1)?.id === turnError.id) {
compactedReplay = [...page.events, turnError];
}

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] R3-6: The no-append (stale-error) branch of this guard has no test. The only refresh test ('keeps the current turn error when refreshing from persisted history') has the turn_error as the newest journaled event, so the reject path — a stale error after newer events, the exact scenario this comment describes — is never exercised. Mutation probe: replacing journal.at(-1)?.id === turnError.id with true (always append) leaves the full bridge suite green (524/524). A future regression of the staleness guard would ship uncaught. Suggested fix: add a bridge test — produce a loop turn_error, publish a subsequent turn's events without an interactive dispatch (simulate the automatic turn), refresh via loadSession, and assert compactedReplay does NOT contain the stale turn_error.

中文说明

[Suggestion] R3-6:该守卫的“不追加”(陈旧错误)分支没有任何测试。唯一的刷新测试('keeps the current turn error when refreshing from persisted history')让 turn_error 恰好是日志中最新的事件,因此拒绝路径——即更新事件出现后的陈旧错误,正是此注释描述的场景——从未被执行。变异探针:把 journal.at(-1)?.id === turnError.id 替换为 true(总是追加)后,完整 bridge 测试套件仍然全绿(524/524)。陈旧守卫未来的回归将不被发现地被合入。建议修复:新增 bridge 测试——制造一个 loop turn_error,在没有交互式分发的情况下发布后续轮次的事件(模拟自动轮次),通过 loadSession 刷新,并断言 compactedReplay 不包含陈旧的 turn_error

— qwen3.8-max via Qwen Code /review (v0.21.9)

Comment on lines +6897 to +6898
if (extractJsonRpcErrorField(err, 'errorKind')) {
// Structured turn error (e.g. loop_detected): the

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] R3-7: The suppression this branch implements has no test asserting its effect. Both new bridge tests assert only the presence of turn_error, never the absence of prompt_cancelled. Mutation probe: deleting this entire branch (restoring the stderr line + prompt_cancelled{forward_failed} broadcast for structured errors) leaves the full bridge suite green (524/524) — under natural event ordering the extra event lands before turn_error, so even the journal-tail assertion still passes. The misreport this branch exists to prevent would silently return. Suggested fix: in 'preserves structured loop detection details on turn_error', collect all emitted events and assert none has type === 'prompt_cancelled' (mirroring the existing C3 forward-failure test's collection pattern).

中文说明

[Suggestion] R3-7:该分支实现的抑制行为没有任何测试断言其效果。两个新增 bridge 测试都只断言 turn_error存在,从不断言 prompt_cancelled缺席。变异探针:删除整个分支(为结构化错误恢复 stderr 行 + prompt_cancelled{forward_failed} 广播)后,完整 bridge 套件仍全绿(524/524)——在自然事件顺序下多余事件落在 turn_error 之前,连日志尾部断言也依然通过。该分支要防止的误报会悄无声息地回归。建议修复:在 'preserves structured loop detection details on turn_error' 中收集所有发出的事件,并断言没有 type === 'prompt_cancelled' 的事件(仿照既有 C3 forward-failure 测试的收集模式)。

— qwen3.8-max via Qwen Code /review (v0.21.9)

Comment on lines +4906 to +4908
stopReason: options.rejectOnLoopDetected
? cancelledOrThrowLoopDetected(pendingSend.signal, toolLoopState)
: getAbortAwareEndTurnStopReason(pendingSend.signal),

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] R3-8: The rejectOnLoopDetected threading is pinned by tests at only one of its three call sites (the cron graceful test). This foreground branch is never exercised as a rejection: no test makes a foreground Stop-continuation loop trip actually REJECT — the cancellation test reaches only the 'cancelled' side of cancelledOrThrowLoopDetected, and the cron test covers only the graceful false side. Mutation probe: flipping truefalse at the foreground #handleStopHookLoop call site leaves Session.test.ts green (563/563). If that happens in a future edit, a foreground turn whose Stop-hook continuation batch trips loop protection resolves as a successful end_turn — precisely the misreport this PR exists to fix, confined to the stop-hook path. (The sibling gap at the notification call site is noted in the comment on the rejectOnLoopDetected = false default.) Suggested fix: add a Session test reusing the cron test's setup (explicit 1-call cap, Stop hook blocking, 2-call continuation batch) but without cancelling, asserting session.prompt(...) rejects with data.code === 'LOOP_DETECTED'.

中文说明

[Suggestion] R3-8:rejectOnLoopDetected 的传递只在三个调用点中的一个(cron 优雅测试)被测试钉住。这个前台分支从未以“拒绝”形态被执行:没有任何测试让前台 Stop 续跑的循环触发真正 REJECT——取消测试只到达 cancelledOrThrowLoopDetected'cancelled' 侧,cron 测试只覆盖优雅的 false 侧。变异探针:把前台 #handleStopHookLoop 调用点的 truefalse 后,Session.test.ts 仍全绿(563/563)。若未来编辑真的这样改,Stop-hook 续跑批次触发循环保护的前台轮次将以成功的 end_turn 返回——正是本 PR 要修复的误报,只是被限制在 stop-hook 路径。(通知调用点的姊妹缺口见 rejectOnLoopDetected = false 默认值处的评论。)建议修复:复用 cron 测试的装置(显式 1 次调用上限、Stop hook 阻塞、2 次调用的续跑批次)但不取消,断言 session.prompt(...)data.code === 'LOOP_DETECTED' 拒绝。

— qwen3.8-max via Qwen Code /review (v0.21.9)

Comment on lines +4023 to +4024
channelDeliveryCapture?: ChannelDeliveryCapture,
rejectOnLoopDetected = false,

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] R3-8 (location 2 of 2): The background-notification graceful handling rests on this default at the notification call site (Session.ts:~7001 passes only 5 args), and no test pins it — the new graceful test covers only the cron call site. Mutation probe: passing true at the notification call site ships green (563/563), while the identical mutation at the pinned cron site fails the suite — the comparator is alive, the notification half is genuinely unpinned. A future true there makes a notification turn that trips loop protection throw past #emitBackgroundNotificationEndTurn into the notification catch, emitting [notification error] … and calling pauseForTrustedRetry() — misreporting a designed-graceful automatic turn as an error, silently violating the design doc's "cron and background-notification work keep their existing non-interactive handling" for half of that pair. Suggested fix: add a sibling of the cron graceful test for a background-notification turn whose Stop continuation trips the explicit cap, asserting no [notification error] chunk and end_turn telemetry.

中文说明

[Suggestion] R3-8(第 2 处,共 2 处):后台通知的优雅处理依赖通知调用点(Session.ts:~7001 只传 5 个参数)处的这个默认值,且没有测试钉住它——新增的优雅测试只覆盖 cron 调用点。变异探针:在通知调用点传入 true 后套件全绿(563/563),而在被钉住的 cron 调用点做同样变异会使测试失败——比较器有效,通知这一半确实未被钉住。未来若在此传入 true,触发循环保护的通知轮次会抛过 #emitBackgroundNotificationEndTurn 进入通知 catch,输出 [notification error] … 并调用 pauseForTrustedRetry()——把设计上应优雅处理的自动轮次误报为错误,静默违背设计文档“cron 与后台通知保持既有非交互处理”承诺的一半。建议修复:为后台通知轮次补一个 cron 优雅测试的姊妹测试(其 Stop 续跑触发显式上限),断言没有 [notification error] 块且遥测为 end_turn

— qwen3.8-max via Qwen Code /review (v0.21.9)

Comment on lines +628 to +631
function createLoopDetectedTurnError(
loopState: DaemonToolLoopState,
): RequestError {
return new RequestError(-32603, LOOP_DETECTED_TURN_ERROR_MESSAGE, {

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] R3-9: LOOP_DETECTED_TURN_ERROR_MESSAGE is pinned by no test. The flipped Session tests match only the data envelope (code/errorKind/loopType), and the bridge tests inject their own synthetic message text that differs from the real constant. The design doc's compatibility section makes this message the surface older clients and the session summary show verbatim ("older clients show the daemon message"). Failure scenario: mutating or emptying the constant leaves every test green while older clients and the session summary silently display a wrong or empty explanation. Suggested fix: extend one flipped assertion with message: LOOP_DETECTED_TURN_ERROR_MESSAGE (import the constant), e.g.

await expect(session.prompt(...)).rejects.toMatchObject({
  message: LOOP_DETECTED_TURN_ERROR_MESSAGE,
  data: expect.objectContaining({ code: 'LOOP_DETECTED' }),
});
中文说明

[Suggestion] R3-9:LOOP_DETECTED_TURN_ERROR_MESSAGE 没有任何测试钉住。翻转后的 Session 测试只匹配 data 信封(code/errorKind/loopType),bridge 测试注入的是与真实常量文本不同的合成消息。设计文档的兼容性章节使该消息成为旧客户端与会话摘要逐字展示的表面(“旧客户端展示 daemon 消息”)。失败场景:修改或清空该常量后所有测试仍为绿色,而旧客户端与会话摘要会静默展示错误甚至为空的说明。建议修复:在一个翻转断言中补上 message: LOOP_DETECTED_TURN_ERROR_MESSAGE(导入该常量),例如上文代码块所示。

— qwen3.8-max via Qwen Code /review (v0.21.9)

Address round-3 review findings:

- The bounded-refresh append guard no longer compares the in-memory
  turn_error against the last journaled event of any kind. The published
  event is stored on the entry at broadcast time; any newer turn terminal
  clears it, and only turn-content events journaled after it block the
  append (queue/config bookkeeping no longer hides the error on refresh).
  The staleness check reads the in-flight journal via a new
  liveJournalSnapshot() accessor instead of flattening the replay window.
- Channel turns keep the graceful end-turn handling like cron and
  background-notification turns, so their collected text is still
  delivered when loop protection stops them.
- Loop-detected rejections drain the cron/notification queues in
  prompt()'s finally, preserving the pre-rejection drain invariant for
  queued automatic work.
- Tests pin each behavior: queued-bookkeeping refresh append, stale-error
  no-append, prompt_cancelled absence, foreground stop-hook rejection,
  background-notification graceful default, and the user-facing message
  constant.
@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 Addressed the latest review feedback (round 2/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 2/100 轮)。改动内容与我反驳保留之处如下:

Review round 3 — PR #8853 address summary

All 9 findings from the automated reviewer (3 Critical, 6 Suggestion) were verified against the code and addressed in one batch. No finding was declined.

Critical findings

R3-1 — refresh-append guard defeated by queue bookkeeping (bridge.ts)

Fixed. The guard compared the in-memory turn_error against the LAST journaled event of any type, so pending_prompt_completed (published by result.finally for a queued-then-promoted prompt) or model_switched/approval_mode_changed after the loop terminal silently dropped the error on refresh — the exact "error vanishes on refresh" failure this PR exists to fix.

The new guard stores the published turn_error event on the session entry (turnErrorEvent) at broadcast time and appends it while no newer turn CONTENT was journaled after it:

  • Any newer turn terminal (turn_complete/turn_error, including queued-prompt flushes and cancellations) clears turnErrorEvent at broadcast, so a stale error is never re-appended.
  • Queue/config bookkeeping events (pending_prompt_added/pending_prompt_completed, prompt_cancelled, model_switched, approval_mode_changed) carry no turn content and no longer defeat the append.
  • Automatic turns (cron/background notification) publish session_update content, which still blocks the append as before.
  • turnErrorEvent is cleared at the next prompt dispatch alongside turnError and is not part of the session summary.

A regression test reproduces the queued shape (prompt B queued behind A, promoted, trips loop protection, pending_prompt_completed lands after the terminal) and asserts the refreshed replay still ends with the turn_error.

R3-3 — channel turns lose delivery entirely (Session.ts)

Fixed. Channel turns now keep the graceful non-interactive handling: prompt() threads rejectOnLoopDetected = channelDelivery === undefined through #executePrompt/#executePromptInner, gating all three foreground loop-stop sites (repeated-tool-failure stop, tool-run loop stop, and the #handleStopHookLoop call). A channel turn that trips loop protection resolves end_turn as pre-PR, so #scheduleChannelDelivery runs and the collected text is still delivered. The design doc now lists channel-delivery turns alongside cron and background-notification turns. A new Session test asserts the channel turn resolves end_turn and the delivery ext-method is invoked.

R3-12 — loop rejections strand queued cron/notification work (Session.ts)

Fixed. prompt() records when the rejection is a loop-detected turn error (data.code === 'LOOP_DETECTED') and adds that to the finally drain condition, after releasePendingSend() so the drain gates pass (the settle-defer retry fires once the turn's completion promise resolves). This preserves the invariant that a loop-stopped turn drains automatic queues while NOT draining on generic errors — the pinned test at Session.test.ts:24292 still passes. A new Session test fires a cron job mid-foreground-turn, trips the explicit one-call cap, and asserts the prompt rejects with LOOP_DETECTED while the queued cron work is drained.

Suggestions

  • R3-4 — fixed together with R3-1: the published event is stored at broadcast time, and the staleness check reads only the in-flight journal via a new liveJournalSnapshot() accessor (added to CompactionEngine/EventBus/TurnBoundaryCompactionEngine), which never flattens the compacted replay window. The double-copy + reverse + find scan is gone.
  • R3-6 — added 'drops the stale turn error on refresh after newer automatic-turn content': loop error, then automatic-turn content journaled via the session/update fan-in (no interactive dispatch), refresh asserts the stale turn_error is absent.
  • R3-7 — 'preserves structured loop detection details on turn_error' now collects every emitted event up to the terminal and asserts none is prompt_cancelled, pinning the forward-failure phantom suppression.
  • R3-8 (location 1) — added 'rejects a foreground turn whose Stop continuation trips loop protection': explicit one-call cap, Stop hook blocking, two-call continuation batch, no cancellation; asserts the prompt rejects with LOOP_DETECTED and the structured message, pinning rejectOnLoopDetected: true at the foreground call site.
  • R3-8 (location 2) — added the sibling 'keeps a background-notification turn graceful when its Stop continuation trips loop protection': asserts the cap-tripping notification turn emits no [notification error] chunk and reports end_turn via _qwencode/end_turn, pinning the graceful default at the notification call site.
  • R3-9 — exported LOOP_DETECTED_TURN_ERROR_MESSAGE and pinned it in the turn-cap rejection assertion (message: LOOP_DETECTED_TURN_ERROR_MESSAGE).

Conflict notes

--conflict false: no base merge performed.

Verification

Commands actually run (all from the repository root unless noted), with results:

  • npm run build — passed (full workspace build; also required so the cli vitest run can resolve @qwen-code/channel-base).
  • npm run typecheck — passed (run before and after the final prettier pass).
  • npm run lint — passed.
  • npx prettier --write <the 7 changed files> — applied; two test files were reflowed, all other files unchanged.
  • Focused Vitest, packages/acp-bridge (npx vitest run src/bridge.test.ts src/eventBus.test.ts src/compactionEngine.test.ts) — 688 tests passed (run twice: after the fix and again after formatting).
  • Focused Vitest, packages/cli (npx vitest run src/acp-integration/session/Session.test.ts) — 567 tests passed (5 new), re-run after formatting; plus npx vitest run src/serve/daemon-status-provider.test.ts (cli-side suite over the same acp-bridge harness) — 4 tests passed.
  • Integration tests after npm run bundle — not run: every changed behavior (bridge refresh-append, prompt drain/channel gating) is exercised directly by the unit suites above, not only through the bundled CLI or integration harness.
中文说明

评审第 3 轮 — PR #8853 处理总结

自动评审者的全部 9 条发现(3 条 Critical、6 条 Suggestion)均已对照代码核实,并在同一批提交中处理完毕。没有任何发现被拒绝。

Critical 发现

R3-1 — 刷新追加守卫被队列簿记事件击穿(bridge.ts)

已修复。 原守卫把内存中的 turn_error 与日志中“任意类型的最后一个事件”比较,因此“排队后晋升”的 prompt 在其 loop 终止事件之后由 result.finally 发布的 pending_prompt_completed,或其后发布的 model_switched/approval_mode_changed,都会在刷新时悄悄丢掉错误——这正是本 PR 要修复的“刷新后错误消失”问题。

新守卫在广播时把已发布的 turn_error 事件保存到会话条目(turnErrorEvent)上,并仅在其后没有任何更新的轮次“内容”被记录时才追加它:

  • 任何更新的轮次终止事件(turn_complete/turn_error,包括排队 prompt 的清场终止与取消)都会在广播时清除 turnErrorEvent,保证陈旧错误不会被重新追加。
  • 队列/配置簿记事件(pending_prompt_added/pending_prompt_completedprompt_cancelledmodel_switchedapproval_mode_changed)不携带轮次内容,不再阻止追加。
  • 自动轮次(cron/后台通知)发布的 session_update 内容仍会像以前一样阻止追加。
  • turnErrorEvent 在下一次 prompt 分发时与 turnError 一起清除,且不属于会话摘要。

新增回归测试复现了排队场景(prompt B 排在 A 之后入队、晋升、触发循环保护,pending_prompt_completed 落在终止事件之后),并断言刷新后的回放仍以该 turn_error 结尾。

R3-3 — 频道轮次完全丢失投递(Session.ts)

已修复。 频道轮次现在保持优雅的非交互处理:prompt()rejectOnLoopDetected = channelDelivery === undefined 传递到 #executePrompt/#executePromptInner,门控全部三个前台循环停止点(重复工具失败停止、工具运行循环停止、#handleStopHookLoop 调用)。触发循环保护的频道轮次像 PR 前一样以 end_turn 正常返回,#scheduleChannelDelivery 照常执行,已收集的文本仍会被投递。设计文档已把频道投递轮次与 cron、后台通知轮次并列。新增 Session 测试断言频道轮次以 end_turn 返回且投递 ext-method 被调用。

R3-12 — 循环拒绝使已排队的 cron/通知任务滞留(Session.ts)

已修复。 prompt() 记录拒绝是否为循环检测轮次错误(data.code === 'LOOP_DETECTED'),并将其加入 finally 的排空条件;排空发生在 releasePendingSend() 之后,因此排空门可以通过(结算延迟重试会在该轮 completion promise 解决后触发)。这保留了“循环停止的轮次必然排空自动队列”的不变量,同时不在普通错误时排空——Session.test.ts:24292 处钉住的测试仍然通过。新增 Session 测试在前台轮次运行中触发 cron 任务入队、触发显式单次调用上限,断言 prompt 以 LOOP_DETECTED 拒绝且排队的 cron 任务被排空。

Suggestion 发现

  • R3-4 — 与 R3-1 一并修复:发布时保存事件,陈旧性检查只通过新增的 liveJournalSnapshot() 访问器(加在 CompactionEngine/EventBus/TurnBoundaryCompactionEngine 上)读取进行中日志,不再展平压缩回放窗口。两次复制 + 反转 + find 的扫描已移除。
  • R3-6 — 新增 'drops the stale turn error on refresh after newer automatic-turn content':先产生 loop 错误,再经由 session/update 汇入(无交互分发)记录自动轮次内容,刷新后断言陈旧 turn_error 不存在。
  • R3-7 — 'preserves structured loop detection details on turn_error' 现在收集直到终止事件为止的全部事件,并断言其中没有 prompt_cancelled,钉住对 forward-failure 幻影事件的抑制。
  • R3-8(第 1 处) — 新增 'rejects a foreground turn whose Stop continuation trips loop protection':显式单次调用上限、Stop hook 阻塞、两次调用的续跑批次、不取消;断言 prompt 以 LOOP_DETECTED 及结构化消息拒绝,钉住前台调用点的 rejectOnLoopDetected: true
  • R3-8(第 2 处) — 新增姊妹测试 'keeps a background-notification turn graceful when its Stop continuation trips loop protection':断言触发上限的通知轮次不发出 [notification error] 块,且通过 _qwencode/end_turn 上报 end_turn,钉住通知调用点的优雅默认值。
  • R3-9 — 导出 LOOP_DETECTED_TURN_ERROR_MESSAGE,并在触发上限的拒绝断言中钉住它(message: LOOP_DETECTED_TURN_ERROR_MESSAGE)。

冲突说明

--conflict false:未执行 base 合并。

验证

实际执行的命令(如无特别说明均在仓库根目录执行)及结果:

  • npm run build — 通过(全工作区构建;cli 的 vitest 运行也需要它来解析 @qwen-code/channel-base)。
  • npm run typecheck — 通过(在最终 prettier 处理前后各运行一次)。
  • npm run lint — 通过。
  • npx prettier --write <7 个改动文件> — 已应用;两个测试文件被重排,其余文件无变化。
  • 聚焦 Vitest,packages/acp-bridgenpx vitest run src/bridge.test.ts src/eventBus.test.ts src/compactionEngine.test.ts)— 688 个测试通过(运行两次:修复后一次、格式化后又一次)。
  • 聚焦 Vitest,packages/clinpx vitest run src/acp-integration/session/Session.test.ts)— 567 个测试通过(新增 5 个),格式化后已重跑;另运行 npx vitest run src/serve/daemon-status-provider.test.ts(cli 侧基于同一 acp-bridge 测试装置的套件)— 4 个测试通过。
  • npm run bundle 后的集成测试 — 未运行:所有改动行为(bridge 刷新追加、prompt 排空/频道门控)都由上述单元测试套件直接覆盖,并非只能通过打包 CLI 或集成测试框架执行。

Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。

Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. · 有空请复审;第 100 轮后本 bot 停止并将 PR 交给人工。


🧠 Handled by Qwen Code · model/模型 qwen3.8-max

…tection-error

# Conflicts:
#	packages/acp-bridge/src/bridge.test.ts
@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 Addressed the latest review feedback (round 3/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 3/100 轮)。改动内容与我反驳保留之处如下:

Autofix address-review summary — PR #8853 (round with base-conflict resolution)

Feedback points

The prepared feedback contained no review findings, no inline comments, no
issue-level comments, no failed checks, and no still-red checks newer than the
last evaluation (2026-08-13T08:32:34Z). There was nothing to triage.

Changes this round

The only work was the flagged base conflict (--conflict true): merged
origin/main (up to 7141976130) into fix/web-shell-loop-detection-error
as additive merge commit 209a28e948. No history rewriting.

Conflict notes

Exactly one content conflict: packages/acp-bridge/src/eventBus.ts. Both
sides independently added pass-through accessors on EventBus at the same
location, backed by optional CompactionEngine methods:

  • This PR added liveJournalSnapshot() (in-flight journal without flattening
    the replay window), consumed by bridge.ts for turn-error handling.
  • main added journalLimits() / journalLimitBytes() (adaptive journal
    growth policy), consumed by the bridge's growth policy and daemon status.

The two additions are orthogonal. Resolution kept both accessor groups on
EventBus; the CompactionEngine interface already carried both optional
methods after auto-merge. Verified against both parents: relative to the
origin/main parent the merge re-introduces exactly the PR's
liveJournalSnapshot surface, and nothing from either side was dropped.

All other overlaps (bridge.ts, compactionEngine.ts, SDK daemon types,
web-shell App.tsx / i18n.tsx / adapters, package-lock.json including the
sharp ^0.35.0 bump) auto-merged; spot-checked that both features coexist
(adaptive growth + live journal snapshot in compactionEngine.ts, both SDK
fields, both languages' error.loopDetected keys) and confirmed
node_modules satisfies the merged lockfile (sharp 0.35.3 installed).

Verification

  • npm run build — passed
  • npm run typecheck — passed
  • npm run lint — passed
  • npx vitest run in packages/acp-bridge (touched by conflict) — 28 files, 1385 passed
  • npx vitest run src/acp-integration in packages/cli (touched by PR) — 396 passed, 0 failed, 0 errors
  • npx vitest run test/unit/DaemonClient.test.ts in packages/sdk-typescript (daemon types touched) — 329 passed
  • npx vitest run client/App.test.tsx client/adapters/transcriptToMessages.test.ts client/hooks/useMessages.test.ts in packages/web-shell (touched by PR) — 3 files, 571 passed
  • Settings schema regeneration — not needed: merge did not touch settingsSchema.ts / settings.ts
中文说明

Autofix address-review 总结 — PR #8853(含基分支冲突解决的一轮)

反馈点

准备好的反馈中没有任何新的评审发现、行内评论、issue 级评论、失败检查项,也没有上次评估(2026-08-13T08:32:34Z)之后仍然标红的检查项。本轮没有需要分诊处理的反馈。

本轮改动

唯一的工作是处理被标记的基分支冲突(--conflict true):将 origin/main(至 7141976130)以追加式合并提交 209a28e948 合并进 fix/web-shell-loop-detection-error。未改写任何历史。

冲突说明

仅有一处内容冲突:packages/acp-bridge/src/eventBus.ts。双方在同一位置各自独立地为 EventBus 添加了直通访问方法,分别对应 CompactionEngine 上的可选方法:

  • 本 PR 添加了 liveJournalSnapshot()(仅取进行中的日志、不展平回放窗口),供 bridge.ts 的回合错误处理使用。
  • main 添加了 journalLimits() / journalLimitBytes()(自适应日志增长策略),供桥接的增长策略与 daemon 状态接口使用。

两处新增互不相关。解决方式是在 EventBus同时保留两组方法;自动合并后 CompactionEngine 接口已同时包含两个可选方法。已对照双亲提交核验:相对 origin/main 父提交,合并结果恰好重新引入了本 PR 的 liveJournalSnapshot 接口面,双方内容均无丢失。

其余所有重叠文件(bridge.tscompactionEngine.ts、SDK daemon 类型、web-shell 的 App.tsx / i18n.tsx / 适配器,以及包含 sharp ^0.35.0 升级的 package-lock.json)均自动合并成功;抽查确认两套功能共存(compactionEngine.ts 中自适应增长与实时日志快照并存、SDK 两个字段均在、error.loopDetected 中英文文案齐全),并确认 node_modules 满足合并后的 lockfile(已安装 sharp 0.35.3)。

验证

  • npm run build — 通过
  • npm run typecheck — 通过
  • npm run lint — 通过
  • packages/acp-bridge 目录下 npx vitest run(冲突涉及包)— 28 个文件,1385 个测试通过
  • packages/cli 目录下 npx vitest run src/acp-integration(PR 涉及)— 396 个测试通过,0 失败,0 错误
  • packages/sdk-typescript 目录下 npx vitest run test/unit/DaemonClient.test.ts(daemon 类型涉及)— 329 个测试通过
  • packages/web-shell 目录下 npx vitest run client/App.test.tsx client/adapters/transcriptToMessages.test.ts client/hooks/useMessages.test.ts(PR 涉及)— 3 个文件,571 个测试通过
  • 设置 schema 重新生成 — 不需要:合并未触及 settingsSchema.ts / settings.ts

Base-conflict check · 基分支冲突检查: conflicted with main — resolved in this push. · 与 main 有冲突——已在本次推送中解决。

Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. · 有空请复审;第 100 轮后本 bot 停止并将 PR 交给人工。


🧠 Handled by Qwen Code · model/模型 qwen3.8-max

@qwen-code-ci-bot qwen-code-ci-bot 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.

Partially reviewed — gaps disclosed.

Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.

Not explored to full depth (tool budget reached): "You are review agent reverse-audit — Reverse audit agent…": did not trace daemon-side sessionTranscript persistence semantics (whether a persisted page can itself already contain a turn-error record, which would make t…; "You are review agent reverse-audit — Reverse audit agent…": none — all checks I started were completed within budget.; "You are review agent reverse-audit — Reverse audit agent…": none — all checks above completed within budget.; "You are review agent reverse-audit — Reverse audit agent…": none — all checks above completed within budget.; "You are review agent reverse-audit — Reverse audit agent…": could not inspect @qwen-code/webui 's useStreamingState (external package not in this worktree's node_modules), so "streaming returns to idle with zero block…, and 12 more.

中文说明

仅完成部分审查,审查缺口已披露。

未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。

未探索到全部深度(达到工具调用预算):"You are review agent reverse-audit — Reverse audit agent…"did not trace daemon-side sessionTranscript persistence semantics (whether a persisted page can itself already contain a turn-error record, which would make t…"You are review agent reverse-audit — Reverse audit agent…"none — all checks I started were completed within budget."You are review agent reverse-audit — Reverse audit agent…"none — all checks above completed within budget."You are review agent reverse-audit — Reverse audit agent…"none — all checks above completed within budget."You are review agent reverse-audit — Reverse audit agent…"could not inspect @qwen-code/webui 's useStreamingState (external package not in this worktree's node_modules), so "streaming returns to idle with zero block…,另有 12 条。

— qwen3.8-max via Qwen Code /review (v0.21.11)

Comment on lines +5508 to +5509
// turn — that opts the turn out of loop-detected rejection and the
// repeated-failure 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.

[Critical] R9-1: The new trusted-parent gating covers only qwen.channel.prompt; the second input to the same channel classification, qwen.daemon.channelDelivery, still passes the ACP boundary unstripped from untrusted callers, so it can mark a turn as a channel turn — exactly what the comment above says an untrusted caller "must not be able to". — Failure scenario: a standalone qwen --acp agent initializes untrusted and its sanitize block deletes only the five reserved keys, letting unknown _meta keys through (probe-verified on this commit: a forged delivery payload reaches session.prompt). Session.prompt computes channelTurn = channelDelivery !== undefined || channelPromptTurn and uses it to set rejectOnLoopDetected = false and force the repeated-failure guard 'off' → the turn never rejects with the structured LOOP_DETECTED error no matter how badly it loops, the guard stays off even under enforce, and a bogus channel delivery is scheduled at end_turn — reintroducing through a sibling key the exact ungated opt-out this PR gates qwen.channel.prompt to close. (The daemon route is NOT exposed for this key — bridge.ts strips it and re-injects only the internally-set trusted context.channelDelivery.)

Suggested fix — mirror the channel-prompt pattern for the delivery key:

const suppliedChannelDelivery = meta[DAEMON_CHANNEL_DELIVERY_META_KEY];
delete meta[DAEMON_CHANNEL_DELIVERY_META_KEY];
// ...after the existing trusted re-injections:
if (this.privateParentState === 'trusted' && suppliedChannelDelivery !== undefined) {
  meta[DAEMON_CHANNEL_DELIVERY_META_KEY] = suppliedChannelDelivery;
}

and extend the new untrusted-caller test to assert a forged qwen.daemon.channelDelivery payload is also stripped.

中文说明

[Critical] R9-1:新的可信父级门控仅覆盖了 qwen.channel.prompt;同一 channel 分类的第二个输入 qwen.daemon.channelDelivery 仍会原样穿过不可信调用方的 ACP 边界,从而把轮次标记为 channel turn——正是上方注释声明不可信调用方"不得做到"的事。— 失败场景:独立 qwen --acp agent 以 untrusted 初始化,其 sanitize 块只删除五个保留键,未知 _meta 键会被放行(已在当前提交上探针验证:伪造的 delivery 载荷到达了 session.prompt)。Session.prompt 计算 channelTurn = channelDelivery !== undefined || channelPromptTurn,并据此把 rejectOnLoopDetected 置为 false、把重复失败守卫强制为 'off' → 轮次无论怎样循环都不会以结构化 LOOP_DETECTED 错误拒绝,enforce 配置下守卫也保持关闭,且 end_turn 时会调度一次伪造的频道投递——经由姊妹键重新引入了本 PR 正要堵住的无门控豁免。(daemon 路由不受此键影响——bridge.ts 会剥离该键,只重新注入内部设置的可信 context.channelDelivery。)建议按 channel-prompt 的模式处理 delivery 键(捕获→删除→仅可信时重注入),并扩展新的 untrusted 测试,断言伪造的 qwen.daemon.channelDelivery 同样被剥离。

— qwen3.8-max via Qwen Code /review (v0.21.11)

Comment on lines +5510 to +5515
if (
this.privateParentState === 'trusted' &&
suppliedChannelPrompt === true
) {
meta[CHANNEL_PROMPT_META_KEY] = true;
}

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] R9-2: The new trust gate is bypassed on the daemon-hosted path: no daemon-side layer strips qwen.channel.prompt from client-supplied _meta, and the daemon is a trusted parent — so a forged key survives end to end and is honored by this re-injection. — Failure scenario: probe-verified through the real bridge hop on this commit: any SDK/HTTP client that can prompt a daemon session sends _meta: {'qwen.channel.prompt': true}; the serve route copies _meta into the forwarded body deleting only two keys, and the bridge admission strip deletes seven reserved keys but not this one; the daemon initializes the child with the private-parent capability, so privateParentState === 'trusted' and this gate re-injects the forged key. Session.prompt then computes channelTurn = true: a loop-stopped turn resolves end_turn (Web Shell never gets the loop_detected turn error this PR ships) and the repeated-failure guard is forced 'off' even under enforce — exactly what the gate's comment says "must not" happen. The new test only proves the direct qwen --acp child path; DaemonChannelBridge legitimately sends the key over this same public route, so the legitimate sender is indistinguishable from a forger here. This diff doubles the key's privileges and asserts the boundary in new code/comments (Session.ts: "the ACP boundary strips the channel-prompt key from untrusted callers"), leaving the assertion false on the primary path.

Suggested fix: strip CHANNEL_PROMPT_META_KEY in the bridge admission strip list and the serve route's forwarded-meta cleanup, and re-inject it only for prompts dispatched through a trusted channel context (the way context.channelDelivery / display text are re-injected, bridge.ts:7669-7683) — or require the existing channel-worker authorization for that key. Add a daemon-path regression test: a forged qwen.channel.prompt must still reject on loop detection and keep the configured guard mode.

中文说明

[Critical] R9-2:新的信任门控在 daemon 托管路径上被绕过:daemon 侧没有任何一层剥离客户端 _meta 中的 qwen.channel.prompt,而 daemon 是可信父级——伪造的键全程存活,并被此处的重新注入逻辑承认。— 失败场景:已在当前提交上通过真实 bridge 链路探针验证:任何能对 daemon 会话发起 prompt 的 SDK/HTTP 客户端发送 _meta: {'qwen.channel.prompt': true};serve 路由转发 _meta 时只删除两个键,bridge 准入剥离删除七个保留键但不包含此键;daemon 用 private-parent capability 初始化子进程,privateParentState === 'trusted',此门控把伪造的键重新注入。Session.prompt 随即计算 channelTurn = true:循环停止的轮次以 end_turn 结束(Web Shell 永远收不到本 PR 提供的 loop_detected 轮次错误),重复失败守卫在 enforce 下也被强制关闭——正是门控注释声明"不得发生"的情形。新增测试只证明了直连 qwen --acp 子进程路径;DaemonChannelBridge 经由同一公共路由合法发送该键,合法发送者与伪造者在该路径上不可区分。本 diff 使该键的特权翻倍,并在新代码/注释中断言该边界(Session.ts:"the ACP boundary strips the channel-prompt key from untrusted callers"),而该断言在主路径上不成立。建议修复:在 bridge 准入剥离列表与 serve 路由的转发元数据清理中剥离 CHANNEL_PROMPT_META_KEY,仅对经受信 channel 上下文分发的 prompt 重新注入(如同 context.channelDelivery / display text 的处理方式),或对该键要求现有的 channel-worker 授权;并补充 daemon 路径回归测试(伪造的 qwen.channel.prompt 必须仍在循环检测时拒绝、并保持配置的守卫模式)。

— qwen3.8-max via Qwen Code /review (v0.21.11)

// journal holds exactly the events published since the last
// turn boundary (the terminal itself folds into the replay
// window), so no history scan is needed.
const journal = entry.events.liveJournalSnapshot() ?? [];

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] R9-3: The refresh-append guard treats the synthetic history_truncated live-journal marker as newer turn content — journal truncation silently drops the pending loop terminal from the refreshed transcript. — Failure scenario: probe-verified on the unmodified PR (small injected maxJournalBytes, idle shell output after a loop reject): the loop turn_error boundary resets the journal and journalTruncatedEvents; verbose idle user-shell output (each output chunk a separate journal entry — the very traffic isUserShellSessionUpdate exists to exempt) exceeds the caps; eviction sets journalTruncatedEvents > 0; on refresh liveJournalSnapshot() (this line) unshifts a synthetic history_truncated event that was never ingested as content; the predicate sees a type outside REFRESH_APPEND_BOOKKEEPING_EVENT_TYPES that is not a user-shell update → hasNewerTurnContent = true → the append is skipped → the loop terminal disappears from the refreshed transcript while the summary still advertises it — the exact regression this PR fixes, smuggled back in by the truncation marker of the very shell traffic the exemption protects. Both journal caps are documented bridge options accepting any integer ≥ 1, and the growth pool is skipped precisely when the operator pins them, so small pinned caps hit this with a handful of events; none of the nine new tests induces truncation.

// exempt the size-accounting marker from the predicate, e.g.:
const hasNewerTurnContent = journal.some(
  (event) =>
    event.type !== 'history_truncated' &&
    !REFRESH_APPEND_BOOKKEEPING_EVENT_TYPES.has(event.type) &&
    !isUserShellSessionUpdate(event),
);

plus a regression test that pushes the post-terminal journal past a small injected cap before refreshing.

中文说明

[Critical] R9-3:刷新追加守卫把合成的 history_truncated 实时日志标记判为"更新的轮次内容"——日志截断会静默丢掉刷新转录中本应保留的循环终止事件。— 失败场景:已在未修改的 PR 上探针验证(注入较小的 maxJournalBytes,循环拒绝后运行空闲 shell):loop turn_error 边界重置日志与 journalTruncatedEvents;冗长的空闲 user-shell 输出(每个输出块都是独立的日志条目——正是 isUserShellSessionUpdate 要豁免的流量)超出上限;逐出逻辑把 journalTruncatedEvents 置为非零;刷新时 liveJournalSnapshot()(本行)在头部插入一个从未被写入日志的合成 history_truncated 事件;守卫谓词看到一个既不在 REFRESH_APPEND_BOOKKEEPING_EVENT_TYPES 中、又不是 user-shell 更新的类型 → hasNewerTurnContent = true → 跳过追加 → 刷新后的转录丢失循环终止事件,而摘要仍在宣告它——正是本 PR 要修复的回归,却由豁免机制所保护的 shell 流量自身的截断标记重新引入。两个日志上限都是有文档的 bridge 选项、接受任意 ≥ 1 的整数,且操作者固定上限时增长池恰好被跳过,因此较小的固定上限只需少量事件即可触发;九个新测试均未构造截断。建议在谓词中豁免该标记(如上方代码),并补充一个在刷新前把终止后日志推过较小注入上限的回归测试。

— qwen3.8-max via Qwen Code /review (v0.21.11)

Comment thread packages/web-shell/client/App.tsx Outdated
) {
retriedTurnErrorIdRef.current = retryableTurnError.id;
}
lastTurnErrorIdRef.current = turnErrorId;

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] R9-4: The new backward turnErrorId scan breaks on ANY user block, while getRetryableTurnError(blocks) — computed in the same effect for the retry decision — deliberately skips user blocks with meta.source === 'background_notification'; the two walks disagree about whether the current turn has a turn error. — Failure scenario: probe-verified on this commit with the transcript shape [turn_error, background_notification user block] that the existing test 'keeps a turn-error retry visible through background notifications' pins: the inline scan stops at the notification user block → lastTurnErrorIdRef.current = nullturn_complete fires with error: undefined while the retry path still sees the error → the host embedding Web Shell is told the turn succeeded while the UI shows the error and offers retry. Pre-PR this interleaving DID report the error (turn_complete read retryableTurnErrorIdRef derived from getRetryableTurnError semantics), and the PR's stated guarantee "Loop-detected turn errors still surface through turn_complete" is defeated in it. The new App.test.tsx case covers only the bare shape with no trailing notification.

Suggested change
lastTurnErrorIdRef.current = turnErrorId;
lastTurnErrorIdRef.current = getRetryableTurnError(blocks)?.id ?? null;

(getRetryableTurnError does not filter by kind, so loop-detected errors still surface through turn_complete as intended — the duplicated inline scan above then has no remaining reader and can be removed.)

中文说明

[Critical] R9-4:新的从后向前 turnErrorId 扫描在遇到任何 user 块时终止,而同一 effect 中用于重试判定的 getRetryableTurnError(blocks) 会刻意跳过 meta.source === 'background_notification' 的 user 块——两次遍历对"当前轮次是否存在 turn error"给出不同答案。— 失败场景:已在当前提交上探针验证,使用既有测试 'keeps a turn-error retry visible through background notifications' 所钉住的转录形态 [turn_error, background_notification user 块]:内联扫描在通知 user 块处停止 → lastTurnErrorIdRef.current = nullturn_completeerror: undefined 触发,而重试路径仍能看到错误 → 嵌入 Web Shell 的宿主被告知轮次成功,UI 却同时显示错误并提供重试。PR 前该交错形态确实会上报错误(turn_complete 读取由 getRetryableTurnError 语义派生的 retryableTurnErrorIdRef),本 PR 声明的"Loop-detected turn errors still surface through turn_complete"保证在该交错下失效。新增的 App.test.tsx 用例只覆盖了没有尾随通知块的裸形态。建议改用 getRetryableTurnError(blocks)?.id ?? null(该函数不按 kind 过滤,loop-detected 错误仍会如预期经由 turn_complete 上报),上方的内联扫描随之失去读者、可一并删除。

— qwen3.8-max via Qwen Code /review (v0.21.11)

Comment thread packages/acp-bridge/src/bridge.ts Outdated
Comment on lines +1660 to +1661
function isUserShellSessionUpdate(event: BridgeEvent): boolean {
if (event.type !== 'session_update') return false;

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] R9-5: The refresh-append guard exempts only user-shell session_updates, but other idle-reachable bookkeeping session_update subtypes — concretely available_commands_update and the legacy dual-emit current_mode_update frame — are journaled while idle and defeat the append, so the loop terminal disappears after a page refresh. — Failure scenario: probe-verified on this commit: after a loop reject, a workspace skills/settings refresh fans refreshSkillsFromSettings over all active sessions and each session emits session_update{available_commands_update} (Session.ts sendAvailableCommandsUpdateOrThrow); BridgeClient.sessionUpdate forwards every agent session_update onto the session bus with no idle gating and ingest() journals it; on refresh hasNewerTurnContent sees the event — it is not in REFRESH_APPEND_BOOKKEEPING_EVENT_TYPES, and isUserShellSessionUpdate returns false (its _meta carries availableSkills, not source: 'user-shell') → the append is skipped → the refreshed transcript loses the turn_error{loop_detected} terminal while the summary still advertises it. The set's own doc comment mandates auditing "any other idle-reachable session-bus publish", and the PR explicitly put sibling idle events (settings_changed, extensions_changed) in the set and tested them — available_commands_update is an unargued sibling state of the same shared gate.

Suggested fix: extend the skip to idle bookkeeping subtypes — e.g. treat the LATEST_WINS_UPDATES subtypes (available_commands_update, current_mode_update) the same as user-shell output in this predicate (renaming it to isIdleBookkeepingSessionUpdate), and add a matrix case publishing such a session_update after the terminal.

中文说明

[Critical] R9-5:刷新追加守卫只豁免 user-shell 的 session_update,但其他空闲可达的簿记类 session_update 子类型——具体是 available_commands_update 与遗留双发的 current_mode_update 帧——会在空闲期被写入日志并击穿追加,导致循环终止事件在刷新后消失。— 失败场景:已在当前提交上探针验证:循环拒绝之后,一次工作区技能/设置刷新会对所有活动会话执行 refreshSkillsFromSettings,每个会话发出 session_update{available_commands_update}(Session.ts sendAvailableCommandsUpdateOrThrow);BridgeClient.sessionUpdate 把每个 agent session_update 无空闲门控地转发到会话总线,ingest() 将其写入日志;刷新时 hasNewerTurnContent 看到该事件——它不在 REFRESH_APPEND_BOOKKEEPING_EVENT_TYPES 中,isUserShellSessionUpdate 也返回 false(其 _meta 携带 availableSkills 而非 source: 'user-shell')→ 跳过追加 → 刷新后的转录丢失 turn_error{loop_detected} 终止事件,而摘要仍在宣告它。集合自身的注释要求审计"任何其他空闲可达的会话总线发布",且本 PR 已把同族空闲事件(settings_changedextensions_changed)加入集合并配了测试——available_commands_update 是同一共享门控下未经论证的兄弟状态。建议把空闲簿记子类型纳入豁免——例如在本谓词中把 LATEST_WINS_UPDATES 子类型(available_commands_updatecurrent_mode_update)与 user-shell 输出同等对待(可更名为 isIdleBookkeepingSessionUpdate),并在矩阵测试中新增一个在终止事件之后发布此类 session_update 的用例。

— qwen3.8-max via Qwen Code /review (v0.21.11)

Comment on lines +4969 to +4971
it('keeps the turn error on refresh when a queued deadline terminal lands after it', async () => {
// A queued prompt's terminal publishes the event alone without mutating
// turn state; it must not erase the refresh-replay record of the active

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] R9-6: A queued prompt's terminal (deadline expiry / queued removal) is a turn_error/turn_complete boundary that resets the compaction journal while leaving entry.turnErrorEvent intact — newer automatic-turn content journaled before it is wiped from the guard's only evidence source, and the refresh-append then re-appends the stale loop error AFTER newer turn content: the exact misplacement the guard exists to prevent. — Failure scenario: probe-verified on this commit in both variants (held-cancel-ack deadline expiry, and removePendingPrompt while a cancel-forward drain is pending): loop reject sets turnErrorEvent; an automatic turn (e.g. a cron turn drained by the rejection itself) journals session_update content — correctly blocking the append, as the sibling drop test pins; the queued terminal then folds+resets the journal again without clearing turnErrorEvent; on refresh liveJournalSnapshot() is empty → hasNewerTurnContent = false → replay = persisted page + automatic-turn content + the stale loop turn_error. The refreshed transcript shows the newer automatic turn's content followed by the previous turn's stale loop_detected terminal, contradicting the guard's own comment ("re-appending the stale error after their newer content would misplace it") and the field doc ("any newer turn terminal clears it so a stale error is never re-appended"). No test covers content-then-queued-terminal; the two new queued-terminal tests pin only the empty-journal case.

Suggested fix: track the content evidence outside the resettable journal — e.g. set a flag on the entry when a non-bookkeeping, non-user-shell event is ingested while turnErrorEvent is set, cleared only on admission or a mutating terminal; have the guard require both an empty-of-content journal AND that flag. Add a regression test: loop terminal → inject an automatic-turn session_update → queued deadline/removal terminal → loadSession → assert the stale error is NOT appended.

中文说明

[Critical] R9-6:排队 prompt 的终止事件(截止期到期 / 排队中被移除)是 turn_error/turn_complete 边界,会重置压缩日志,却不触动 entry.turnErrorEvent——先于它写入日志的更新轮次内容被从守卫唯一的证据来源中抹掉,刷新追加于是把过期的循环错误追加到更新轮次内容之后:正是守卫要防止的错位。— 失败场景:已在当前提交上探针验证两种变体(挂起 cancel ack 时的截止期到期;cancel 转发 drain 挂起时 removePendingPrompt):循环拒绝置位 turnErrorEvent;一个自动轮次(例如被该拒绝自身排空的 cron 轮次)把 session_update 内容写入日志——正确阻断追加(兄弟 drop 测试已钉住此行为);随后排队终止事件再次折叠+重置日志,却不清除 turnErrorEvent;刷新时 liveJournalSnapshot() 为空 → hasNewerTurnContent = false → replay = 持久化页内容 + 自动轮次内容 + 过期的 loop turn_error。刷新后的转录显示较新自动轮次的内容之后跟着上一轮的过期 loop_detected 终止事件,与守卫自身注释("把过期错误重新追加到更新内容之后会造成错位")及字段文档("任何更新的轮次终止都会清除它,过期错误永不被重新追加")矛盾。没有测试覆盖"先有内容、后有排队终止"的序列;两个新的排队终止测试只钉住了日志为空的情形。建议修复:把内容证据记录在可重置的日志之外——例如当 turnErrorEvent 置位期间有非簿记、非 user-shell 事件写入时,在 entry 上置一个标志,仅在准入或变更性终止时清除;守卫要求"日志无内容"与该标志同时成立。并补充回归测试:循环终止 → 注入自动轮次 session_update → 排队截止/移除终止 → loadSession → 断言过期错误未被追加。

— qwen3.8-max via Qwen Code /review (v0.21.11)

Comment on lines +4951 to +4953
const compactedReplay = refreshed.compactedReplay ?? [];
expect(compactedReplay).toHaveLength(2);
expect(compactedReplay[compactedReplay.length - 1]).toMatchObject({

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] R9-7: This test (and the queued-deadline/queued-removed tests at ~5085/~5205) assert presence of the appended turn_error without anchoring compactedReplay[0] to the persisted page — they pass unchanged if the bounded-append branch regresses into the in-memory fallback: the exact regression shape the sibling tests' anchors were written to catch. — Concrete cost: probe-verified on this commit: running the identical shell scenario with the persisted-transcript fetch throwing (forcing the bounded path's catchreplayFieldsFor in-memory fallback) passes this test's exact assertions — the fallback's compactedReplay has the same length-2/last-is-terminal shape — so such a regression ships green through all three tests; only the sibling tests anchoring compactedReplay[0] to 'persisted turn content' would fire.

// add the sibling anchor:
expect(compactedReplay[0]).toMatchObject({
  type: 'session_update',
  data: expect.objectContaining({
    content: { type: 'text', text: 'persisted turn content' },
  }),
});
中文说明

[Suggestion] R9-7:该测试(以及 ~5085/~5205 处的排队截止/排队移除测试)只断言追加的 turn_error 存在,未把 compactedReplay[0] 锚定到持久化页——若有界追加分支回归为内存回退,这些测试会原样通过:这正是兄弟测试的锚点专门要捕获的回归形态。— 具体代价:已在当前提交上探针验证:让持久化转录拉取抛错(强制有界路径走 catchreplayFieldsFor 内存回退)运行相同的 shell 场景,该测试的全部断言依然通过——回退的 compactedReplay 具有相同的"长度为 2、末位为终止事件"形态——因此这类回归会在三个测试全绿的情况下溜走;只有把 compactedReplay[0] 锚定到 'persisted turn content' 的兄弟测试才会变红。建议按上方代码补上同款锚点。

— qwen3.8-max via Qwen Code /review (v0.21.11)

Comment on lines +15161 to +15162
it('keeps a Goal turn graceful when loop protection stops it', async () => {
// Goal continuations are non-interactive and bypass the bridge: a

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] R9-8: The graceful repeated-failure-guard stop branch — the only stop path this PR newly conditioned on rejectOnLoopDetected — has zero test coverage: all three new graceful-stop tests trip TURN_TOOL_CALL_CAP, and goal turns are the one non-interactive path that keeps the configured guard mode (Session.ts: channelTurn ? 'off' : mode) while getting rejectOnLoopDetected = false. — Concrete cost: mutation-verified on this commit — deleting the gated emit block, or flipping the graceful stoppedByRepeatedToolFailure resolution to an unconditional rejection, leaves the full Session.test.ts suite green (606/606 under both mutants); the old positive emission assertion was inverted in this very diff. A future regression here would reject goal turns, settling them failed and pausing the goal with no turn_error published, and the only user-visible explanation of a silently-stopped autonomous goal turn disappears with no red signal.

Suggested fix: add a sibling to the goal test below driving the repeated-failure guard — recreate the session with guard mode enforce, run a goal turn through a failing-batch streak, and assert it resolves gracefully with logLoopDetectedSpy called for REPEATED_TOOL_EXECUTION_FAILURE and the stop message emitted to the client.

中文说明

[Suggestion] R9-8:优雅的重复失败守卫停止分支——本 PR 唯一新近以 rejectOnLoopDetected 为条件的停止路径——完全没有测试覆盖:三个新的优雅停止测试全部触发 TURN_TOOL_CALL_CAP,而 goal turn 是唯一既保留配置守卫模式(Session.ts:channelTurn ? 'off' : mode)又得到 rejectOnLoopDetected = false 的非交互路径。— 具体代价:已在当前提交上变异验证——删除受门控的发送块,或把优雅的 stoppedByRepeatedToolFailure 返回翻转为无条件拒绝,Session.test.ts 全套 606 个测试在两种变异下均保持全绿;旧的正向发送断言恰好在本 diff 中被反转。未来此处的回归会拒绝 goal turn、将其按失败结算并暂停 goal,且不发布任何 turn_error——被静默停止的自主 goal 轮次唯一的用户可见解释就此消失,且没有任何红色信号。建议在该 goal 测试旁补一个驱动重复失败守卫的兄弟测试:以 enforce 模式重建会话,让 goal 轮次经历失败批次连击,断言其优雅结束、logLoopDetectedSpyREPEATED_TOOL_EXECUTION_FAILURE 被调用、且停止消息已发送给客户端。

— qwen3.8-max via Qwen Code /review (v0.21.11)


The session remains alive and the per-turn loop state is recreated for the next prompt. Cron, background-notification, channel-delivery, and goal turns keep their existing non-interactive handling: only interactive foreground prompts reject. Goal turns bypass the bridge entirely, so rejecting one would settle it as failed and pause the goal without publishing any `turn_error`; they resolve `end_turn` like the other automatic turn types. A loop-detected rejection still drains the cron/notification queues, preserving the invariant that a loop-stopped turn never strands queued automatic work.

When Web Shell reloads a live session from paginated persisted history, the bridge appends the current in-memory `turn_error` to that replay. This keeps the terminal error visible across a page refresh without changing historical persistence.

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] R9-9: This sentence claims unconditional refresh-survival, but the doc's own queue-drain invariant (previous paragraph) creates turns whose journaled content permanently disables the refresh-append. — Concrete cost: a cron job queued while a foreground turn loops is drained by prompt()'s finally on rejection (rejectedByLoopProtection); the drained in-daemon cron turn publishes ordinary session_updates that are not in the bookkeeping set, so hasNewerTurnContent is true and the append is skipped until the next interactive terminal (which clears turnErrorEvent anyway). The code behavior is deliberate — the PR's own stale-drop test pins it — but this sentence reads as if refresh-survival were unconditional, and the doc's own drain invariant makes the defeating condition likely immediately after a loop stop whenever work was queued.

Suggested change
When Web Shell reloads a live session from paginated persisted history, the bridge appends the current in-memory `turn_error` to that replay. This keeps the terminal error visible across a page refresh without changing historical persistence.
When Web Shell reloads a live session from paginated persisted history, the bridge appends the current in-memory `turn_error` to that replay. This keeps the terminal error visible across a page refresh while the session remains idle; newer turn content — including automatic turns the rejection itself drains — supersedes it by design.
中文说明

[Suggestion] R9-9:该句声称刷新后无条件保留,但文档上一段自己的队列排空不变量会制造出"日志内容永久禁用刷新追加"的轮次。— 具体代价:前台轮次循环期间排队的 cron 任务会在拒绝时被 prompt()finallyrejectedByLoopProtection)排空;被排空的 daemon 内 cron 轮次发布的是不在簿记集合中的普通 session_update,于是 hasNewerTurnContent 为真,追加被跳过,直到下一个交互轮次终止(而那时 turnErrorEvent 也已被清除)。代码行为是刻意的——本 PR 的 stale-drop 测试已钉住——但该句读起来像刷新保留是无条件的,且文档自身的排空不变量使得"只要有排队工作,循环停止后立刻触发击败条件"成为大概率事件。建议按上方文本限定该句。

— qwen3.8-max via Qwen Code /review (v0.21.11)

Comment on lines +5051 to +5053
undefined,
{ promptId: 'prompt-queued', deadlineMs: 120 },
);

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] R9-10: This test's stated premise — the deadline expires while the prompt is still queued, AFTER the loop terminal has landed — is enforced only by a 120 ms real-time budget, and no assertion verifies that ordering, so the scenario silently inverts under load while the test stays green. — Concrete cost: the deadline timer is armed at sendPrompt time, before queueing; between that point and heldTurn.reject(...) the test performs two vi.waitFor polls (50 ms granularity) plus a held cancel-forward — on a loaded 2–4-core CI worker this can exceed 120 ms. Probe-verified: once the window is exceeded the order inverts (deadline terminal before loop terminal), and under the inversion every assertion in the test still passes (turnErrorEvent is set last by the loop terminal, the journal is empty at refresh, the summary is set last) — silently, unlike the sibling queue-bookkeeping test, which asserts its ordering with an explicit indexOf comparison. Regression coverage for exactly this PR's newest protection (a queued terminal erasing turnErrorEvent/turnError) degrades to luck without any red signal.

// pin the ordering like the sibling test, e.g.:
expect(seenTypes.indexOf('turn_error')).toBeLessThan(
  seenTypes.lastIndexOf('prompt_deadline_exceeded'),
);
中文说明

[Suggestion] R9-10:该测试声明的前提——截止期在 prompt 仍处于排队状态、且 loop 终止事件已经落地之后才到期——只由 120 ms 实时预算保证,没有任何断言验证这个顺序,因此负载下场景会静默反转而测试依旧全绿。— 具体代价:截止期定时器在 sendPrompt 时启动,早于入队;从那里到 heldTurn.reject(...) 之间,测试执行两次 vi.waitFor 轮询(50 ms 粒度)外加一次挂起的 cancel 转发——在负载较高的 2–4 核 CI runner 上可能超过 120 ms。探针验证:窗口一旦被超过,顺序反转(截止终止事件先于 loop 终止事件),而反转后测试的所有断言仍然通过(turnErrorEvent 最后由 loop 终止事件置位、刷新时日志为空、摘要最后置位)——全程无声,不像兄弟队列簿记测试那样用显式 indexOf 比较钉住顺序。针对本 PR 最新保护(排队终止事件不得抹掉 turnErrorEvent/turnError)的回归覆盖就此退化为碰运气,且无任何红色信号。建议按上方代码像兄弟测试一样钉住顺序。

— qwen3.8-max via Qwen Code /review (v0.21.11)

…esh replay (#8853)

Close two trust-gate gaps in the channel-turn classification introduced
for loop-detected turn errors: the `qwen.daemon.channelDelivery` sibling
key was not gated like `qwen.channel.prompt` at the standalone ACP
boundary, and on the daemon-hosted path a client-forged
`qwen.channel.prompt` survived the serve route and bridge admission strip
to be re-injected for trusted parents. Both keys are now stripped from
untrusted callers and honored only from trusted context (private parent
state, or the channel-worker prompt authorization validated by the daemon
prompt route).

Also harden the refresh-append guard for pending turn errors: the
synthetic `history_truncated` journal marker and idle latest-wins
`session_update` snapshots no longer defeat the append, and a queued
terminal that folds newer turn content supersedes the stale error before
the fold erases the evidence. Web Shell now derives turn_complete's error
from the same backward walk as the retry decision.
@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 Addressed the latest review feedback (round 4/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 4/100 轮)。改动内容与我反驳保留之处如下:

Autofix review round 9 — summary

All ten findings (6 Critical, 4 Suggestion) are resolved in the code. No finding was declined, deferred, or escalated; --conflict false, no merge performed.

Critical

  • R9-1 — qwen.daemon.channelDelivery ungated at the standalone ACP boundary. Verified: the sanitize block deleted only the five reserved keys, so an untrusted caller's forged delivery payload reached session.prompt and forced channelTurn = true, opting the turn out of loop-detected rejection. Fixed by mirroring the qwen.channel.prompt pattern: capture + delete + re-inject only when privateParentState === 'trusted' (acpAgent.ts). Tests: the untrusted-caller strip test now forges both keys; the trusted test pins that a trusted parent's delivery survives.
  • R9-2 — daemon-hosted bypass via forged qwen.channel.prompt. Verified: the serve prompt route stripped only two meta keys and the bridge admission strip omitted qwen.channel.prompt, so a forged key survived end-to-end and was re-injected for trusted parents. Fixed at all three hops: the serve route now extracts the key, drops it, and re-arms it through a new trusted channelPrompt context flag only when the channel-worker prompt authorization validates (same token that gates display text); the bridge admission strip deletes the key and re-injects it only from that context flag; DaemonChannelBridge now always presents its worker token (previously only with display text) so legitimate display-text-less channel prompts keep their classification. Also refreshed the now-stale "routing hint, never an authorization boundary" comment on the channel-base key and added a cross-package wire-key test pinning the twin constants (following the display-text-wire-key.test.ts convention). Tests: serve-route forged/forged-token/trusted matrix, bridge strip/re-inject pair, unconditional-token channel-bridge test.
  • R9-3 — history_truncated marker defeats the append. Verified: liveJournalSnapshot() unshifts the synthetic marker when the journal truncated, and the predicate treated it as newer turn content. Fixed by factoring the predicate into isRefreshAppendTurnContent, which skips history_truncated (size accounting, never ingested content). Test: pinned maxJournalEvents: 1 forces truncation through idle user-shell output; regression-verified via mutation.
  • R9-4 — Web Shell backward scans disagree. Verified: the inline scan broke on any user block while getRetryableTurnError skips background_notification user blocks. Fixed by deriving lastTurnErrorIdRef from getRetryableTurnError(blocks)?.id ?? null and deleting the duplicated scan. Test: a [turn_error, background_notification] interleaving now reports the error through turn_complete while retry stays visible; regression-verified via mutation.
  • R9-5 — idle available_commands_update / current_mode_update defeat the append. Verified: these latest-wins state snapshots fan out to idle sessions (skills/settings refresh, approval-mode change) and the predicate treated them as turn content. Fixed by renaming isUserShellSessionUpdate to isIdleBookkeepingSessionUpdate and skipping both subtypes alongside the user-shell stream; the set doc comment now names the exemption. Tests: it.each over both subtypes with persisted-page anchors; regression-verified via mutation.
  • R9-6 — queued terminal resets the journal without clearing turnErrorEvent. Verified: a queued deadline/removed terminal is a turn boundary that folds and resets the live journal, erasing the guard's only evidence of newer automatic-turn content, so the stale loop error could be re-appended AFTER newer content. Fixed in publishPromptTerminal: before dispatching a non-mutating (queued) terminal, if the journal holds newer turn content, the refresh-replay record is dropped — content supersedes the stale error before the fold erases it. The session summary is untouched (pinned by both queued-terminal tests). Test: loop terminal → injected automatic-turn content → queued removed terminal → refresh asserts no stale append and the persisted-page anchor; regression-verified via mutation. (Chosen over the suggested ingest-time flag: equivalent outcome in every interleaving, no new entry field or per-event hot-path work.)

Suggestion

  • R9-7 — missing persisted-page anchor. Added the compactedReplay[0] anchor to the idle user-shell test and both queued-terminal tests (deadline and removed), matching the bookkeeping-matrix pattern.
  • R9-8 — repeated-failure guard graceful branch untested. Added a goal-turn sibling test: session recreated with guard mode enforce, a 4+4+1 failing-tool streak stops the goal turn through the graceful branch — asserts finishTurn (iteration settled, goal not paused), REPEATED_TOOL_EXECUTION_FAILURE telemetry, and the user-visible stop message emitted as agent_message_chunk. Regression-verified via mutation.
  • R9-9 — design doc overclaims. The refresh-survival sentence now states the idle-only scope and that newer turn content (including automatic turns the rejection drains) supersedes the append by design.
  • R9-10 — queued-deadline premise unpinned. The collector now records both terminals and the test asserts the loop terminal precedes the prompt_deadline_exceeded terminal, so an inverted 120 ms race fails loudly instead of silently testing a different scenario.

Files changed

packages/acp-bridge/src/{bridge.ts, bridgeTypes.ts, bridge.test.ts}, packages/cli/src/acp-integration/{acpAgent.ts, acpAgent.test.ts, session/Session.test.ts}, packages/cli/src/serve/{routes/session.ts, server.test.ts}, packages/cli/src/commands/channel/channel-prompt-wire-key.test.ts (new), packages/channels/base/src/{ChannelAgentBridge.ts, DaemonChannelBridge.ts, DaemonChannelBridge.test.ts}, packages/web-shell/client/{App.tsx, App.test.tsx}, docs/design/web-shell-loop-detection-turn-error.md.

Verification

Commands actually run (all passed):

  • npm run build — passed (twice: before and after formatting)
  • npm run typecheck — passed (twice: before and after formatting)
  • npm run lint — passed
  • npx prettier --check on all changed files — passed after prettier --write on the three flagged files
  • vitest run src/bridge.test.ts (packages/acp-bridge) — 611 passed
  • vitest run (packages/acp-bridge, full suite) — 28 files, 1390 passed
  • vitest run src/acp-integration/acpAgent.test.ts src/commands/channel/channel-prompt-wire-key.test.ts src/commands/channel/display-text-wire-key.test.ts (packages/cli) — 3 files, 398 passed
  • vitest run src/acp-integration/session/Session.test.ts (packages/cli) — 607 passed
  • vitest run src/serve/server.test.ts (packages/cli) — 933 passed, 1 pre-existing skip
  • vitest run src/serve/{sse-last-event-id,session-id-admission,total-session-admission}.test.ts (packages/cli) — 43 passed
  • vitest run client/App.test.tsx (packages/web-shell) — 437 passed
  • vitest run (packages/channels/base, full suite) — 19 files, 1029 passed
  • Mutation checks (each fix temporarily reverted, then restored): the truncation test, the queued-terminal supersede test, both idle-subtype tests, the Web Shell interleaving test, and the goal guard-stop test all fail without their fix — the new tests are load-bearing.
  • Not run: bundled-CLI integration tests — the changed behavior is exercised by the package unit suites above (serve route via supertest against the real express app, bridge admission via the in-memory channel, Web Shell via the component harness), not only through the bundled CLI.
中文说明

Autofix 评审第 9 轮 — 总结

全部 10 条发现(6 条 Critical、4 条 Suggestion)均已在代码中解决。没有拒绝、推迟或升级任何发现;--conflict false,未执行合并。

Critical

  • R9-1 — qwen.daemon.channelDelivery 在独立 ACP 边界未设门控。 已核实:sanitize 块只删除了 5 个保留键,因此不可信调用方伪造的 delivery 载荷可到达 session.prompt 并强制 channelTurn = true,使该轮绕过 loop-detected 拒绝。修复方式与 qwen.channel.prompt 保持一致:捕获 + 删除,仅当 privateParentState === 'trusted' 时重新注入(acpAgent.ts)。测试:不可信调用方的剥离测试现在同时伪造两个键;可信测试确认可信父级的 delivery 得以保留。
  • R9-2 — 伪造 qwen.channel.prompt 绕过 daemon 托管路径。 已核实:serve prompt 路由只剥离两个 meta 键,bridge 准入剥离列表又遗漏了 qwen.channel.prompt,因此伪造键可以端到端存活并被重新注入给可信父级。已在三个环节全部修复:serve 路由现在提取该键、将其删除,并且仅在 channel-worker prompt 授权校验通过时(与显示文本使用同一个令牌)通过新的可信 channelPrompt 上下文字段重新启用;bridge 准入剥离删除该键,且仅从该上下文字段重新注入;DaemonChannelBridge 现在始终出示其 worker 令牌(之前仅在携带显示文本时出示),从而保证没有显示文本的合法 channel prompt 仍保留其分类。同时更新了 channel-base 键上已过时的"仅作路由提示、永不作为授权边界"的注释,并按照 display-text-wire-key.test.ts 的惯例新增了一个跨包 wire-key 测试以固定两处常量一致。测试:serve 路由的伪造/伪令牌/可信矩阵、bridge 的剥离/重注入对、channel-bridge 的无条件令牌测试。
  • R9-3 — history_truncated 标记破坏追加。 已核实:当日志发生截断时 liveJournalSnapshot() 会在头部插入合成标记,而谓词把它当作更新的轮次内容。修复方式是将谓词提取为 isRefreshAppendTurnContent,跳过 history_truncated(它只是容量统计,从未作为内容被摄取)。测试:固定 maxJournalEvents: 1,通过空闲 user-shell 输出强制触发截断;已通过变异测试验证。
  • R9-4 — Web Shell 两个反向扫描不一致。 已核实:内联扫描遇到任何 user 块即中断,而 getRetryableTurnError 会跳过 background_notification 的 user 块。修复方式是改用 getRetryableTurnError(blocks)?.id ?? null 来计算 lastTurnErrorIdRef,并删除重复的扫描。测试:[turn_error, background_notification] 交错场景下 turn_complete 仍会上报错误且重试按钮保持可见;已通过变异测试验证。
  • R9-5 — 空闲的 available_commands_update / current_mode_update 破坏追加。 已核实:这些 latest-wins 状态快照会扇出到空闲会话(skills/settings 刷新、审批模式变更),谓词将其当作轮次内容。修复方式是将 isUserShellSessionUpdate 重命名为 isIdleBookkeepingSessionUpdate,在 user-shell 流之外同时跳过这两个子类型;集合的文档注释也已注明该豁免。测试:对两个子类型做 it.each,均带持久化页锚点;已通过变异测试验证。
  • R9-6 — 排队终结事件重置日志却不清除 turnErrorEvent 已核实:排队的 deadline/移除终结事件是一个轮次边界,会折叠并重置 live journal,抹掉守卫关于更新自动轮次内容的唯一证据,导致陈旧的 loop 错误被重新追加到更新内容之后。修复位置在 publishPromptTerminal:在派发非 mutating(排队)终结事件之前,若日志中存在更新的轮次内容,则丢弃刷新重放记录——内容在折叠抹除证据之前取代陈旧错误。会话摘要不受影响(两个排队终结测试均已固定)。测试:loop 终结 → 注入自动轮次内容 → 排队移除终结 → 刷新时断言不再追加陈旧错误且持久化页锚点存在;已通过变异测试验证。(未采用建议中的"摄取时置标志"方案:两者在所有交错顺序下结果等价,且无需新增 entry 字段或每事件热路径开销。)

Suggestion

  • R9-7 — 缺少持久化页锚点。 已为空闲 user-shell 测试和两个排队终结测试(deadline 与 removed)添加 compactedReplay[0] 锚点,与 bookkeeping 矩阵测试的写法保持一致。
  • R9-8 — 重复失败守卫的 graceful 分支无测试。 新增 goal 轮次的姊妹测试:以守卫模式 enforce 重建会话,4+4+1 的失败工具连击使 goal 轮次走 graceful 分支——断言 finishTurn(迭代正常结算、goal 未暂停)、REPEATED_TOOL_EXECUTION_FAILURE 遥测,以及停止消息以 agent_message_chunk 形式对用户可见。已通过变异测试验证。
  • R9-9 — 设计文档表述过度。 刷新存活的句子现在明确仅适用于会话保持空闲的场景,且更新的轮次内容(包括拒绝本身排空的自动轮次)按设计会取代该追加。
  • R9-10 — 排队 deadline 前提未固定。 收集器现在记录两个终结事件,测试断言 loop 终结先于 prompt_deadline_exceeded 终结,使得 120 ms 竞态一旦发生顺序反转即醒目失败,而不是悄悄测试另一种场景。

变更文件

packages/acp-bridge/src/{bridge.ts, bridgeTypes.ts, bridge.test.ts}packages/cli/src/acp-integration/{acpAgent.ts, acpAgent.test.ts, session/Session.test.ts}packages/cli/src/serve/{routes/session.ts, server.test.ts}packages/cli/src/commands/channel/channel-prompt-wire-key.test.ts(新增)、packages/channels/base/src/{ChannelAgentBridge.ts, DaemonChannelBridge.ts, DaemonChannelBridge.test.ts}packages/web-shell/client/{App.tsx, App.test.tsx}docs/design/web-shell-loop-detection-turn-error.md

验证

实际执行的命令(全部通过):

  • npm run build — 通过(两次:格式化前后各一次)
  • npm run typecheck — 通过(两次:格式化前后各一次)
  • npm run lint — 通过
  • 对所有变更文件执行 npx prettier --check — 在对 3 个被标记文件执行 prettier --write 后通过
  • vitest run src/bridge.test.ts(packages/acp-bridge)— 611 通过
  • vitest run(packages/acp-bridge 全量)— 28 个文件,1390 通过
  • vitest run src/acp-integration/acpAgent.test.ts src/commands/channel/channel-prompt-wire-key.test.ts src/commands/channel/display-text-wire-key.test.ts(packages/cli)— 3 个文件,398 通过
  • vitest run src/acp-integration/session/Session.test.ts(packages/cli)— 607 通过
  • vitest run src/serve/server.test.ts(packages/cli)— 933 通过,1 个既有跳过
  • vitest run src/serve/{sse-last-event-id,session-id-admission,total-session-admission}.test.ts(packages/cli)— 43 通过
  • vitest run client/App.test.tsx(packages/web-shell)— 437 通过
  • vitest run(packages/channels/base 全量)— 19 个文件,1029 通过
  • 变异检查(逐个临时回退修复后再恢复):截断测试、排队终结取代测试、两个空闲子类型测试、Web Shell 交错测试、goal 守卫停止测试在缺少对应修复时均会失败——新测试确实承载验证作用。
  • 未运行:捆绑 CLI 集成测试——变更行为由上述包级单测覆盖(serve 路由通过 supertest 针对真实 express 应用、bridge 准入通过内存通道、Web Shell 通过组件测试框架),并非只能经由捆绑 CLI 验证。

Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。

Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. · 有空请复审;第 100 轮后本 bot 停止并将 PR 交给人工。


🧠 Handled by Qwen Code · model/模型 qwen3.8-max

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

🩺 serve daemon A/B

Built the PR base vs this PR head f291d23, drove a fixed endpoint set against each, and diffed the JSON responses. Only fields that changed are shown.

No response changes against the PR base across 4 scenario(s).

Qwen Code · serve A/B

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下:

Autofix address-review round: no action required (PR #8853)

This round found no actionable work:

  • Reviews: none newer than the last evaluation (2026-08-13T16:01:57Z).
  • Inline comments: none.
  • Issue-level comments: none from trusted maintainers or the automated reviewer.
  • Failed checks: none — every CI check passed or was intentionally skipped.
  • Base conflicts: none; origin/main was not merged (--conflict false).

Critical-only mode is active because the PR diff grew beyond this counting window's net-growth budget (src +75 / test +765 net lines vs. the 400/400 budgets). This is a growth-only engagement, so the single deferred non-Critical item — the automated reviewer's summary comment (issuecomment-5284329652) — is an audit record, not work: no code changes, thread resolutions, or comment replies were made for it. It remains open for human follow-up.

No commits were made; the PR head is unchanged.

中文说明

Autofix 处理评审轮次:无需处理(PR #8853

本轮没有发现需要处理的工作:

  • 评审(Reviews): 自上次评估(2026-08-13T16:01:57Z)以来没有新的评审。
  • 行内评论(Inline comments): 无。
  • Issue 级评论(Issue-level comments): 受信维护者或自动评审机器人均未发表新评论。
  • 失败的检查(Failed checks): 无 —— 所有 CI 检查均已通过或按预期跳过。
  • 与 base 分支的冲突: 无;未合并 origin/main--conflict false)。

由于本 PR 的 diff 净增长超出了当前计数窗口的预算(源码 +75 / 测试 +765 行净增长,预算为 400/400),目前已进入仅处理 Critical 的模式。这是一次纯增长(growth-only)触发,因此唯一被延后的非 Critical 条目 —— 自动评审机器人的总结评论(issuecomment-5284329652)—— 属于审计记录而非本轮工作:未针对它做任何代码改动、线程解决或评论回复。该条目保持开放,留待人工跟进。

本轮未产生任何提交;PR 的 head 保持不变。

Deferred non-Critical feedback

Critical-only mode is active: the PR's diff grew src 75 / test 765 net lines beyond this counting window's baseline (budgets: 400/400). The workflow excluded the non-Critical feedback below from this round's actionable sections; the items remain open for human follow-up. Maintainer feedback continues to flow unaffected during a growth-only engagement (the per-author batch budget applies only after 5 change-producing rounds). (@qwen-code /retry starts a fresh counting window.)

中文说明

已进入仅处理 Critical 的模式:本计数窗口内 diff 净增长已达 源码 75 / 测试 765 行(预算 400/400)。本轮可执行区域已排除下方非 Critical 反馈;这些条目保持开放,留待人工跟进。纯增长触发期间维护者反馈照常流动(按作者的批次预算仅在完成 5 个产生改动的轮次后生效)。(评论 @qwen-code /retry 可开启新的计数窗口。)

Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。


🧠 Handled by Qwen Code · model/模型 qwen3.8-max

@danialzivehdadr

This comment has been minimized.

@danialzivehdadr

This comment has been minimized.

@danialzivehdadr

This comment has been minimized.

@danialzivehdadr

This comment has been minimized.

@qwen-code-ci-bot qwen-code-ci-bot 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.

Partially reviewed — gaps disclosed.

Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.

Not explored to full depth (tool budget reached): "PR #8853 converts foreground tool-loop-protection stops…": none — all planned checks completed within budget.; chunk 4: tests not executed — the review worktree has no node_modules , so verification was static (source reads + greps) rather than a vitest run.; chunk 4: cross-chunk consumers of the refreshed compactedReplay in web-shell not traced (structurally outside a chunk agent's duties).; "PR #8853 converts foreground tool-loop-protection stops…": none — all checks I started were completed within budget.; "PR #8853 converts foreground tool-loop-protection stops…": none — finished within budget., and 17 more.

Not reviewed: reverse audit — did not converge within the reverse-audit round cap of 5.

中文说明

仅完成部分审查,审查缺口已披露。

未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。

未探索到全部深度(达到工具调用预算):"PR #8853 converts foreground tool-loop-protection stops…"none — all planned checks completed within budget.;chunk 4:tests not executed — the review worktree has no node_modules , so verification was static (source reads + greps) rather than a vitest run.;chunk 4:cross-chunk consumers of the refreshed compactedReplay in web-shell not traced (structurally outside a chunk agent's duties)."PR #8853 converts foreground tool-loop-protection stops…"none — all checks I started were completed within budget."PR #8853 converts foreground tool-loop-protection stops…"none — finished within budget.,另有 17 条。

未审查:反向审计——在 5 轮的反审轮数上限内未收敛。

— qwen3.8-max via Qwen Code /review (v0.21.11)

Comment on lines +3392 to +3397
// Channel classification opts the turn out of loop-detected
// rejection, so it rides the same worker authorization as the
// display projection; a forged key from any other caller is dropped
// here and again at the bridge admission strip.
const trustedChannelPrompt =
channelWorkerAuthorized && channelPrompt === true;

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] R10-1: The route gates qwen.channel.prompt behind channel-worker authorization, but the sibling classification input — the first-class delivery body field (~line 3339) — is parsed and forwarded as context.channelDelivery with no worker-authorization check, and Session.prompt computes channelTurn = channelDelivery !== undefined || channelPromptTurn, treating both inputs identically — so the ungated field reproduces exactly the bypass this gate exists to prevent. parseChannelDelivery is structural-only validation; ChannelDeliveryAuthorizationStore.authorizePrompt records unconditionally (it is delivery-time bookkeeping, not an admission gate); the existing test 'validates delivery and forwards it only through trusted prompt context' proves context.channelDelivery is set for a request with no worker token. The route is non-strict mutate() — open on loopback in the no-token default, so any local client reaches it. — Failure scenario: any local client POSTs {prompt: [...], delivery: {kind:'channel', target:{...}}} with no authorization token → the bridge re-injects DAEMON_CHANNEL_DELIVERY_META_KEY from the trusted context → channelTurn=true → the turn never rejects with the structured LOOP_DETECTED error no matter how badly it loops, and the repeated-failure guard runs 'off' even under enforce — reintroducing through the sibling field the exact ungated opt-out this PR gates qwen.channel.prompt to close. Pre-PR, delivery-marked turns ran the configured guard mode; both bypass effects are introduced by this diff, and the diff's own acpAgent-strip rationale ("an untrusted client marking its own prompt as a channel turn must not reach the session") is contradicted by the sibling input staying open in the same handler. Bounding: the per-turn cap and global-duplicate detectors ignore guard mode, so a runaway self-marked turn stays bounded; effects are confined to the caller's own turn.

Suggested fix: gate the delivery context flag on the same worker authorization (spread channelDelivery into the sendPrompt context only when channelWorkerAuthorized), or narrow Session's channelTurn so only the authenticated channelPromptTurn input disables rejection/guard mode. If self-classification via the public delivery feature is deliberate product behavior, align the new comments/rationale with that decision instead.

中文说明

[Critical] R10-1:该路由把 qwen.channel.prompt 置于 channel-worker 授权之后,但同一分类的姊妹输入——一等公民的 delivery 请求体字段(约 3339 行)——在没有任何 worker 授权检查的情况下被解析并作为 context.channelDelivery 转发,而 Session.prompt 计算 channelTurn = channelDelivery !== undefined || channelPromptTurn,对两个输入一视同仁——因此这个未设防的字段恰好复现了本门控要防止的绕过。parseChannelDelivery 只做结构校验;ChannelDeliveryAuthorizationStore.authorizePrompt 无条件记录(它是投递时簿记,不是准入门控);既有测试 'validates delivery and forwards it only through trusted prompt context' 证明无 worker token 的请求也会设置 context.channelDelivery。该路由是非严格 mutate()——无 token 默认配置下对 loopback 开放,任何本地客户端都可访问。— 失败场景:任何本地客户端不带授权 token POST {prompt: [...], delivery: {kind:'channel', target:{...}}} → bridge 从受信 context 重新注入 DAEMON_CHANNEL_DELIVERY_META_KEYchannelTurn=true → 无论该轮如何循环都不会以结构化 LOOP_DETECTED 错误拒绝,且重复失败守卫在 enforce 下也保持 'off'——经由姊妹字段重新引入了本 PR 正要堵住的无门控豁免。PR 前 delivery 标记的轮次运行配置的守卫模式;两个绕过效果都是本 diff 引入的,且 diff 自身的 acpAgent 剥离理由("不可信客户端不得把自己的 prompt 标记为 channel turn")与同一处理器中保持开放的姊妹输入相矛盾。边界说明:单轮上限与全局重复检测不受守卫模式影响,失控的自标记轮次仍然有界;效果限于调用者自己的轮次。

建议修复:把 delivery 上下文标志置于同样的 worker 授权之下(仅在 channelWorkerAuthorized 时才把 channelDelivery 放入 sendPrompt context),或收窄 SessionchannelTurn,只让经过认证的 channelPromptTurn 输入豁免拒绝/守卫模式。如果公共 delivery 功能的自我分类是刻意的产品行为,请让新注释/理由与该决定保持一致。

— qwen3.8-max via Qwen Code /review (v0.21.11)

Comment on lines +7099 to +7104
const retryableTurnError =
lastTurnError &&
lastTurnError.kind === 'error' &&
isRetryableTurnErrorKind(lastTurnError.errorKind)
? lastTurnError
: undefined;

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] R10-2: The new isRetryableTurnErrorKind filter is applied here in the blocks effect but not at handleRetry's failure path (~line 9913), where rearmFailedTurnErrorRetry(nextTurnError, …) is still called with the unfiltered getRetryableTurnError result — so a retry whose own turn is loop-stopped arms retry state (retryableTurnErrorIdRef, identity refs, setShowRetryHint(true)) on the very error class this PR de-retries. Probe-verified in the App.test.tsx harness: after the loop-stopped retry lands, the retry button stays hidden (message.retryable is false for loop_detected) but the Ctrl+Y global shortcut calls handleRetry() directly, whose guards (showRetryHintRef, retryableTurnErrorIdRef, identity match, lastSubmittedPromptRef) all pass — sendPrompt was observed called a third time, resubmitting the loop-stopped prompt. With the filter applied at the re-arm site the probe flips to two calls. — Failure scenario: a turn fails with a retryable error → the user retries → the retried turn is loop-stopped (newly possible as a rejection) → the catch re-arms on the loop error → Ctrl+Y resubmits the loop-stopped prompt, which tends to re-loop and be stopped again — the exact outcome the added comment above ("no retry affordance is offered for them") says is prevented.

Suggested fix: mirror this blocks-effect filter at the handleRetry catch re-arm site —

const nextTurnError = getRetryableTurnError(store.getSnapshot().blocks);
if (
  nextTurnError &&
  nextTurnError.kind === 'error' &&
  isRetryableTurnErrorKind(nextTurnError.errorKind)
) {
  rearmFailedTurnErrorRetry(nextTurnError, store.getSnapshot().blocks);
}
中文说明

[Critical] R10-2:新的 isRetryableTurnErrorKind 过滤器在 blocks effect 此处生效,但在 handleRetry 的失败路径(约 9913 行)没有生效——那里仍用未过滤的 getRetryableTurnError 结果调用 rearmFailedTurnErrorRetry(nextTurnError, …)——因此一次自身轮次被循环保护停止的重试,会在本 PR 明确取消重试的错误类别上重新武装重试状态(retryableTurnErrorIdRef、identity refs、setShowRetryHint(true))。已在 App.test.tsx 测试装置中探针验证:循环停止的重试落地后,重试按钮保持隐藏(loop_detectedmessage.retryable 为 false),但 Ctrl+Y 全局快捷键直接调用 handleRetry(),其守卫(showRetryHintRefretryableTurnErrorIdRef、identity 匹配、lastSubmittedPromptRef)全部通过——观察到 sendPrompt 第三次被调用,重新提交了被循环停止的 prompt。在重新武装处加上过滤器后探针翻转为两次调用。— 失败场景:某轮以可重试错误失败 → 用户重试 → 重试轮被循环保护停止(本 PR 使其可能以拒绝形式出现)→ catch 在循环错误上重新武装 → Ctrl+Y 重新提交被循环停止的 prompt,大概率再次循环并再次被停止——正是上方新增注释("不会为其提供重试入口")声明要防止的结果。

建议修复:在 handleRetry catch 的重新武装处镜像此 blocks effect 过滤器(见上方代码)。

— qwen3.8-max via Qwen Code /review (v0.21.11)

Comment on lines +7099 to +7104
const retryableTurnError =
lastTurnError &&
lastTurnError.kind === 'error' &&
isRetryableTurnErrorKind(lastTurnError.errorKind)
? lastTurnError
: undefined;

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] R10-3: Stale failedTurnErrorRetryRef payload survives a filtered loop error and is later consumed by an unrelated retryable error. Probe-verified on the real DaemonSessionProvider: the prompt promise rejects BEFORE the loop turn_error block commits to the store — structural, not a race: settle rejects on a microtask while the transcript event flushes on a setTimeout(0) macrotask (blockAtCatch=false, true after the dispatch flush). So in handleRetry's catch the snapshot walk still returns the OLD error err1, rearmFailedTurnErrorRetry early-returns on retryableTurnError.id === failedRetry.errorId, and the stashed payload {errorId: err1.id, text: P1} survives. The blocks effect now filters the loop error (this hunk) and never calls rearm — and its only clearing branch requires retryableTurnError truthy — so the payload is never cleared. — Failure scenario: a later retryable turn error the backward walk reaches (e.g. a queued prompt's deadline terminal — turn_error with no errorKind, so isRetryableTurnErrorKind(undefined) is true, and no user block follows it) runs rearm first in the effect and consumes the stale payload: restores P1 into lastSubmittedPromptRef, arms retry on err3, and because previousIdentity is read after rearm overwrites the identity ref the mismatch cleanup is skipped → Ctrl+Y/button resubmits P1 — the loop-stopped prompt — misattributed to an error from a turn the user never submitted. The window is unbounded until a new submit or session change.

Suggested fix: gate the catch-time failedTurnErrorRetryRef save on the terminal being retryable (the thrown DaemonHttpError carries code: 'LOOP_DETECTED'), and/or have the blocks effect clear failedTurnErrorRetryRef when lastTurnError exists but is filtered out by isRetryableTurnErrorKind.

中文说明

[Critical] R10-3:过期的 failedTurnErrorRetryRef 载荷能在被过滤的循环错误之后存活,并被之后一个无关的可重试错误消费。已在真实 DaemonSessionProvider 上探针验证:prompt promise 会在 loop turn_error block 提交进 store 之前被拒绝——这是结构性的而非竞态:settle 在微任务中拒绝,而转录事件在 setTimeout(0) 宏任务中刷新(blockAtCatch=false,dispatch 刷新后为 true)。因此 handleRetry 的 catch 中快照遍历仍返回旧错误 err1,rearmFailedTurnErrorRetryretryableTurnError.id === failedRetry.errorId 提前返回,暂存的载荷 {errorId: err1.id, text: P1} 得以存活。blocks effect 现在会过滤掉循环错误(本 hunk)且不再调用 rearm——而其唯一的清理分支要求 retryableTurnError 为真——因此载荷永远不会被清理。— 失败场景:之后出现一个反向遍历可达的可重试轮次错误(例如排队 prompt 的截止期终止事件——不带 errorKindturn_errorisRetryableTurnErrorKind(undefined) 为 true,且其后没有用户 block)在 effect 中先运行 rearm 并消费过期载荷:把 P1 恢复进 lastSubmittedPromptRef,在 err3 上武装重试,且由于 previousIdentity 在 rearm 覆写 identity ref 之后才读取,失配清理被跳过 → Ctrl+Y/按钮重新提交 P1——被循环停止的 prompt——并被错误归因到一个用户从未提交的轮次的错误上。该窗口在新提交或会话切换之前是无限期的。

建议修复:把 catch 时 failedTurnErrorRetryRef 的暂存置于"终止事件可重试"的门控之下(抛出的 DaemonHttpError 携带 code: 'LOOP_DETECTED'),和/或让 blocks effect 在 lastTurnError 存在但被 isRetryableTurnErrorKind 过滤时清理 failedTurnErrorRetryRef

— qwen3.8-max via Qwen Code /review (v0.21.11)

Comment thread packages/web-shell/client/App.tsx Outdated
// Same walk as the retry decision above, so turn_complete and the
// retry affordance never disagree about whether the current turn has
// a turn error (e.g. across a trailing background notification).
lastTurnErrorIdRef.current = lastTurnError?.id ?? null;

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] R10-4: Widening turn_complete reporting from the canRetry-gated retryableTurnErrorIdRef to this ungated walk result drops the "already retried" suppression along with the loop-detected filter it was introduced to bypass. A/B-probe-verified against the built merge base with the identical test in both trees: BASE passes (a cancelled retry turn reports error: undefined); on this PR the same probe fails — the cancelled retry turn dispatches a second turn_complete carrying the PREVIOUS turn's error. — Failure scenario: turn 1 fails with retryable err1 → turn_complete(err1) fires, retry armed → user retries (retriedTurnErrorIdRef = err1.id; retry: true suppresses the user echo) → the retry turn is cancelled (Esc) before emitting content → streamingState transitions responding→idle while rendered blocks still end with err1 (settle precedes the transcript flush — the same microtask/setTimeout(0) ordering verified for R10-3) → the turn_complete effect reads lastTurnErrorIdRef = err1.id and dispatches turn_complete { error: err1 } for the cancelled retry turn — a duplicate, misattributed failure signal to the host (onSessionChange), contradicting this comment's "never disagree" claim (no retry affordance is shown while turn_complete still reports the error). Pre-PR reported no error here. The suggested fix flips the A/B probe green with all 438 App.test.tsx tests passing.

Suggested change
lastTurnErrorIdRef.current = lastTurnError?.id ?? null;
lastTurnErrorIdRef.current =
lastTurnError && lastTurnError.id !== retriedTurnErrorIdRef.current
? lastTurnError.id
: null;

(loop-detected errors are unaffected — they are never retried, so the ref never matches them.)

中文说明

[Critical] R10-4:把 turn_complete 上报从受 canRetry 门控的 retryableTurnErrorIdRef 扩展为此处未门控的遍历结果,在绕过 loop-detected 过滤的同时也丢掉了"已重试过"的抑制。已用同一测试对构建后的 merge base 做 A/B 探针验证:BASE 通过(被取消的重试轮上报 error: undefined);在本 PR 上同一探针失败——被取消的重试轮第二次派发了携带上一轮错误的 turn_complete。— 失败场景:第 1 轮以可重试错误 err1 失败 → turn_complete(err1) 触发、重试武装 → 用户重试(retriedTurnErrorIdRef = err1.idretry: true 抑制用户回声)→ 重试轮在产出内容前被取消(Esc)→ streamingState 在渲染 blocks 仍以 err1 结尾时从 responding→idle(settle 先于转录刷新——与 R10-3 验证的微任务/setTimeout(0) 时序相同)→ turn_complete effect 读到 lastTurnErrorIdRef = err1.id,为被取消的重试轮派发 turn_complete { error: err1 }——向宿主(onSessionChange)发出重复且归因错误的失败信号,与本注释"永不分歧"的声明矛盾(界面不显示重试入口,turn_complete 却仍上报错误)。PR 前此处不上报错误。建议修复使 A/B 探针翻绿,且 App.test.tsx 全部 438 个测试通过。

(loop-detected 错误不受影响——它们从不被重试,ref 永远不会匹配到它们。)

— qwen3.8-max via Qwen Code /review (v0.21.11)

Comment on lines +4673 to +4678
stopReason: rejectOnLoopDetected
? cancelledOrThrowLoopDetected(
pendingSend.signal,
toolLoopState,
)
: getAbortAwareEndTurnStopReason(pendingSend.signal),

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] R10-5: The cancellation-wins-over-loop-rejection invariant is tested at two of the three foreground cancelledOrThrowLoopDetected call sites, but not at this one (the per-turn-cap / duplicate-tool toolRun.loopDetected branch). Mutation-verified: replacing cancelledOrThrowLoopDetected(pendingSend.signal, toolLoopState) here with an unconditional throw keeps Session.test.ts 607/607 green. A follow-up audit additionally verified the existing cancel-race test never reaches cancelledOrThrowLoopDetected at all (the pre-existing abort short-circuit returns 'cancelled' first), so even the site that appears covered is not. The proposed race test discriminates: it passes on this PR and fails under the mutation. — Concrete cost: a future edit dropping the signal.aborted check at this branch rejects a user-cancelled turn with LOOP_DETECTED instead of resolving stopReason: 'cancelled' — Web Shell presents a loop error for a turn the user cancelled — and nothing in the suite catches the regression.

Suggested fix: add a Session.test.ts race test mirroring the existing two — gate the cap-tripping batch (held stream or messageRewriter stub), cancel after the cap trips, assert resolves.toEqual({ stopReason: 'cancelled' }) plus logLoopDetectedSpy still called.

中文说明

[Suggestion] R10-5:取消优先于循环拒绝的不变量在三个前台 cancelledOrThrowLoopDetected 调用点中的两个有测试,但此处(单轮上限/重复工具 toolRun.loopDetected 分支)没有。突变验证:把此处的 cancelledOrThrowLoopDetected(pendingSend.signal, toolLoopState) 替换为无条件 throw,Session.test.ts 仍 607/607 全绿。后续审计还验证了既有取消竞态测试根本到不了 cancelledOrThrowLoopDetected(既有的 abort 短路先返回 'cancelled'),因此看似有覆盖的调用点实际也没有。建议的竞态测试具有区分力:在本 PR 上通过,在该突变下失败。— 具体代价:未来若删除此分支的 signal.aborted 检查,用户已取消的轮次会以 LOOP_DETECTED 拒绝而不是返回 stopReason: 'cancelled'——Web Shell 会对用户取消的轮次显示循环错误——且测试套件中没有任何东西能捕获该回归。

建议修复:新增一个与既有两个同构的 Session.test.ts 竞态测试——门控触发上限的批次(挂起流或 messageRewriter 桩),在上限触发后取消,断言 resolves.toEqual({ stopReason: 'cancelled' })logLoopDetectedSpy 仍被调用。

— qwen3.8-max via Qwen Code /review (v0.21.11)

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.

Deferred (not declined on merit) — this round runs in Critical-only mode: the deterministic growth brake engaged for this PR (src 75 / test 765 net lines beyond the counting window's 400/400 budgets), and AGENTS.md lands only Critical fixes after ~5 review rounds (this is round 10). The finding stands as reported — the cancellation-wins-over-loop-rejection invariant is untested at the per-turn-cap / duplicate-tool cancelledOrThrowLoopDetected site — and stays open for human follow-up or a follow-up PR adding the suggested Session.test.ts race test.

中文说明

延期处理(并非否定其价值)——本轮处于仅 Critical 模式:本 PR 的确定性增长刹车已触发(本计数窗口净增长 源码 75 / 测试 765 行,超出 400/400 预算),且 AGENTS.md 规定约 5 轮审查后只落地 Critical 修复(本轮为第 10 轮)。该发现所述成立——单轮上限/重复工具 cancelledOrThrowLoopDetected 调用点上"取消优先于循环拒绝"的不变量确实没有测试——保持开放,留待人工跟进或后续 PR 补充建议的 Session.test.ts 竞态测试。

Comment on lines +5744 to +5746
const compactedReplay = refreshed.compactedReplay ?? [];
expect(compactedReplay.some((event) => event.type === 'turn_error')).toBe(
false,

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] R10-6: This drop-on-successful-turn test asserts only the absence of turn_error[].some(...) is false and undefined ?? [][], so an empty or degraded compactedReplay satisfies it vacuously. It is the only refresh test in the suite with neither a persisted-page anchor nor a length assertion; the sibling drop test anchors compactedReplay[0] on the persisted fixture and asserts toHaveLength(1), with an explicit comment that a stale branch rebuilding the replay from anything else ships green without it. — Concrete cost: a regression in the supersede path — refreshedReplayFieldsFor dropping the persisted page along with the stale terminal, the idle-guard failing with the ?? [] fallback swallowing an absent compactedReplay, or the bounded-read catch/break returning an empty in-memory replay — ships green: after a LOOP_DETECTED stop and a successful recovery prompt, refresh renders an empty transcript instead of the persisted history, and this test stays green.

Suggested fix: mirror the sibling drop test — assert compactedReplay[0] matches the persisted-page fixture and expect(compactedReplay).toHaveLength(1) before the absence assertion (the recovery turn_complete clears turnErrorEvent via mutateTurnState, so the replay should be exactly the one-event persisted page).

中文说明

[Suggestion] R10-6:该"成功轮次后丢弃"测试只断言 turn_error 不存在——[].some(...)falseundefined ?? [][],因此空或退化的 compactedReplay 也能空泛地满足它。它是套件中唯一既无持久化页锚点也无长度断言的刷新测试;姊妹丢弃测试把 compactedReplay[0] 锚定到持久化 fixture 并断言 toHaveLength(1),且有明确注释说明:从其他来源重建 replay 的陈旧分支若没有该锚点将会静默通过。— 具体代价:supersede 路径的回归——refreshedReplayFieldsFor 把持久化页连同陈旧终止事件一起丢弃、空闲守卫失败且 ?? [] 回退吞掉缺失的 compactedReplay、或有界读取的 catch/break 返回空的内存 replay——都会静默通过:LOOP_DETECTED 停止加一次成功恢复 prompt 之后,刷新会渲染空转录而非持久化历史,而本测试仍是绿的。

建议修复:对齐姊妹丢弃测试——在缺失断言之前断言 compactedReplay[0] 匹配持久化页 fixture 且 expect(compactedReplay).toHaveLength(1)(恢复轮的 turn_complete 经由 mutateTurnState 清除 turnErrorEvent,因此 replay 应恰好是单事件的持久化页)。

— qwen3.8-max via Qwen Code /review (v0.21.11)

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.

Deferred (not declined on merit) — this round runs in Critical-only mode: the deterministic growth brake engaged for this PR (src 75 / test 765 net lines beyond the counting window's 400/400 budgets), and AGENTS.md lands only Critical fixes after ~5 review rounds (this is round 10). The finding stands as reported — the drop-on-successful-turn test is satisfied vacuously by an empty compactedReplay and should anchor the persisted page like its sibling — and stays open for human follow-up or a follow-up PR.

中文说明

延期处理(并非否定其价值)——本轮处于仅 Critical 模式:本 PR 的确定性增长刹车已触发(本计数窗口净增长 源码 75 / 测试 765 行,超出 400/400 预算),且 AGENTS.md 规定约 5 轮审查后只落地 Critical 修复(本轮为第 10 轮)。该发现所述成立——"成功轮次后丢弃"测试确实会被空的 compactedReplay 空泛满足,应像姊妹测试一样锚定持久化页——保持开放,留待人工跟进或后续 PR。

Comment on lines +5037 to +5039
const bridge = makeBridge({
sessionShellCommandEnabled: true,
maxJournalEvents: 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] R10-7: This journal-truncation test pins maxJournalEvents: 1 but never asserts that truncation/the history_truncated marker actually occurred. Verified: its assertions (replay length 2, persisted-page anchor, loop turn_error last) pass identically whether or not truncation happens — all three shell events are bookkeeping either way. normalizeMaxJournalEvents throws below 1 rather than clamping, so cap=1 is honored today; but if a future change floors/clamps small caps, no eviction/marker occurs and the test still ships green — and a regression deleting the 'history_truncated' exclusion in isRefreshAppendTurnContent would then pass the bridge suite, since nothing else exercises a truncated live journal through the refresh-append guard (the other history_truncated tests are compacted-replay-window tests with no turnErrorEvent append). — Concrete cost: silent degradation into a duplicate of the user-shell test above it; the only guard-level marker-branch coverage is lost without any test failing.

Suggested fix: pin the premise before loadSession — assert eviction/the marker actually happened. Note the premise is not observable from the test's current surfaces (the bounded branch hardcodes liveJournal: []), so an observation needs to be added (a journal snapshot/limits surface, or an assertion at the wiring/engine level), not just an extra expect on the existing response.

中文说明

[Suggestion] R10-7:该日志截断测试固定了 maxJournalEvents: 1,却从未断言截断/history_truncated 标记确实发生。已验证:无论截断是否发生,其断言(replay 长度 2、持久化页锚点、loop turn_error 居末)都同样通过——三种 shell 事件在两种情形下都是簿记事件。normalizeMaxJournalEvents 对小于 1 的值抛错而非钳制,因此当前 cap=1 生效;但若未来变更对小的 cap 做下限/钳制,逐出/标记不再发生而测试仍静默通过——届时删除 isRefreshAppendTurnContent'history_truncated' 豁免的回归也能通过 bridge 套件,因为没有其他测试让截断的实时日志经过刷新追加守卫(其余 history_truncated 测试是无 turnErrorEvent 追加的压缩 replay 窗口测试)。— 具体代价:静默退化为上方 user-shell 测试的重复;唯一的守卫层标记分支覆盖在没有任何测试失败的情况下丢失。

建议修复:在 loadSession 之前钉住前提——断言逐出/标记确实发生。注意该前提从测试现有表面不可观测(有界分支硬编码 liveJournal: []),因此需要增加观测手段(日志快照/上限表面,或接线/引擎层的断言),而不只是在现有响应上多加一个 expect。

— qwen3.8-max via Qwen Code /review (v0.21.11)

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.

Deferred (not declined on merit) — this round runs in Critical-only mode: the deterministic growth brake engaged for this PR (src 75 / test 765 net lines beyond the counting window's 400/400 budgets), and AGENTS.md lands only Critical fixes after ~5 review rounds (this is round 10). The finding stands as reported — the journal-truncation test never asserts the truncation premise — and stays open for human follow-up or a follow-up PR. Note the suggested fix itself requires adding a new observation surface (journal snapshot/limits or wiring-level assertion), which is out of scope for a Critical-only round.

中文说明

延期处理(并非否定其价值)——本轮处于仅 Critical 模式:本 PR 的确定性增长刹车已触发(本计数窗口净增长 源码 75 / 测试 765 行,超出 400/400 预算),且 AGENTS.md 规定约 5 轮审查后只落地 Critical 修复(本轮为第 10 轮)。该发现所述成立——日志截断测试确实从未断言截断前提——保持开放,留待人工跟进或后续 PR。注意建议的修复本身需要新增观测表面(日志快照/上限或接线层断言),超出仅 Critical 轮次的范围。

Comment on lines +5353 to +5355
// The queued terminal must not overwrite the session-scoped summary
// either: it belongs to a prompt that never ran.
expect(bridge.getSessionSummary(session.sessionId).turnError).toMatchObject(

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] R10-8: The three queued-terminal tests pin only the turnError half of the documented "queued terminals must not mutate session-scoped turn state" invariant; the retryAllowed half — named in the same production comment ("consumed by the retry-admission check … otherwise a queued failure would arm a retry the active prompt didn't earn") — is asserted nowhere (grep retryAllowed in this file returns nothing). Mutation-verified: moving entry.retryAllowed = true outside the if (mutateTurnState) gate (bridge.ts:1739) keeps the full suite 611/611 green; a probe (held active turn → queued prompt's deadline expires while still queued → active turn succeeds → retry: true resubmit) flips under the mutation. — Concrete cost: with the gate broken, a queued deadline/removal terminal arms the one-shot retry flag even when the active turn succeeded; the client's next retry: true resubmit passes isRetry = requestedRetry && entry.retryAllowed — the turn runs classified as a retry, DAEMON_RETRY_META_KEY is stamped, and the user_message_chunk echo is suppressed, so peer clients' transcripts lose the prompt line.

Suggested fix: in the queued-deadline and queued-removed tests, exercise the retry-admission surface after the queued terminal lands — resubmit with { retry: true } and assert the prompt is NOT treated as a retry (user echo present / no qwen.daemon.retry meta) — or expose retryAllowed on the summary/status fixture and assert it is false.

中文说明

[Suggestion] R10-8:三个排队终止测试只钉住了"排队终止不得变更会话级轮次状态"这一文档化不变量的 turnError 一半;retryAllowed 一半——生产代码同一注释中点名("由重试准入检查消费……否则排队失败会武装一次活动 prompt 未曾赢得的重试")——没有任何断言(本文件 grep retryAllowed 为空)。突变验证:把 entry.retryAllowed = true 移出 if (mutateTurnState) 门控(bridge.ts:1739),整个套件仍 611/611 全绿;一个探针(活动轮挂起 → 排队 prompt 的截止期在仍排队时到期 → 活动轮成功 → retry: true 重新提交)在该突变下翻转。— 具体代价:门控被破坏时,即使活动轮成功,排队的截止/移除终止也会武装一次性重试标志;客户端下一次 retry: true 重交会通过 isRetry = requestedRetry && entry.retryAllowed——该轮以重试身份运行、被打上 DAEMON_RETRY_META_KEY,且 user_message_chunk 回声被抑制,其他客户端的转录将丢失该 prompt 行。

建议修复:在排队截止与排队移除测试中,于排队终止落地后验证重试准入表面——以 { retry: true } 重交并断言 prompt 未被当作重试(用户回声存在 / 无 qwen.daemon.retry meta)——或在 summary/status fixture 上暴露 retryAllowed 并断言其为 false

— qwen3.8-max via Qwen Code /review (v0.21.11)

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.

Deferred (not declined on merit) — this round runs in Critical-only mode: the deterministic growth brake engaged for this PR (src 75 / test 765 net lines beyond the counting window's 400/400 budgets), and AGENTS.md lands only Critical fixes after ~5 review rounds (this is round 10). The finding stands as reported — the queued-terminal tests pin only the turnError half of the invariant, and the retryAllowed half is mutation-verified untested — and stays open for human follow-up or a follow-up PR adding the suggested retry-admission assertions.

中文说明

延期处理(并非否定其价值)——本轮处于仅 Critical 模式:本 PR 的确定性增长刹车已触发(本计数窗口净增长 源码 75 / 测试 765 行,超出 400/400 预算),且 AGENTS.md 规定约 5 轮审查后只落地 Critical 修复(本轮为第 10 轮)。该发现所述成立——排队终止测试只钉住了不变量的 turnError 一半,retryAllowed 一半经突变验证确实无测试——保持开放,留待人工跟进或后续 PR 补充建议的重试准入断言。

Comment on lines +15295 to +15300
mockChat.sendMessageStream = vi
.fn()
.mockResolvedValueOnce(streamForBatch(1, 4))
.mockResolvedValueOnce(streamForBatch(2, 4))
.mockResolvedValueOnce(streamForBatch(3, 1))
.mockResolvedValueOnce(createEmptyStream());

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] R10-9: This Goal repeated-failure-guard graceful test omits the execute (9) and sendMessageStream (3) count assertions its foreground sibling — the enforce-mode test this same diff changed to expect the LOOP_DETECTED rejection, using the identical 4/4/1 failure fixture — asserts; the unconsumed fourth createEmptyStream() additionally absorbs a spurious extra model round-trip. Guard thresholds verified (REPEATED_TOOL_FAILURE_THRESHOLD = 8, batch threshold 2): the stop lands after batch 3 — 9 executions / 3 model calls. The proposed assertions were added temporarily during verification and pass on this PR. — Concrete cost: a regression making the goal path stop one batch early (8 executions / 2 model calls) or perform one extra model round-trip around the stop leaves all four current assertions green (logLoopDetected fired, finishTurn called, no dispatch, stop message present) — goal turns silently do less work than configured, or burn an extra model call per stopped turn, with no test red.

// after the finishTurn wait, mirroring the foreground enforce test:
expect(execute).toHaveBeenCalledTimes(9);
expect(mockChat.sendMessageStream).toHaveBeenCalledTimes(3);
中文说明

[Suggestion] R10-9:该 Goal 重复失败守卫优雅停止测试缺少其前台姊妹测试——同一 diff 改为期望 LOOP_DETECTED 拒绝、使用相同 4/4/1 失败 fixture 的 enforce 模式测试——所拥有的 execute(9 次)与 sendMessageStream(3 次)计数断言;未被消费的第四个 createEmptyStream() 还会吸收一次多余的模型往返。守卫阈值已核实(REPEATED_TOOL_FAILURE_THRESHOLD = 8、批次阈值 2):停止落在第 3 批之后——9 次执行 / 3 次模型调用。验证时临时加入上述断言,在本 PR 上通过。— 具体代价:使 goal 路径提前一批停止(8 次执行 / 2 次模型调用)或在停止前后多一次模型往返的回归,四个现有断言全部保持绿色(logLoopDetected 触发、finishTurn 被调用、无 dispatch、停止消息存在)——goal 轮次静默地少做配置的工作量,或每次停止多烧一次模型调用,且无测试变红。

建议修复:在 finishTurn 等待之后加入上述两个断言(与前台 enforce 测试对齐),使第四个流保持纯防御性。

— qwen3.8-max via Qwen Code /review (v0.21.11)

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.

Deferred (not declined on merit) — this round runs in Critical-only mode: the deterministic growth brake engaged for this PR (src 75 / test 765 net lines beyond the counting window's 400/400 budgets), and AGENTS.md lands only Critical fixes after ~5 review rounds (this is round 10). The finding stands as reported — the Goal graceful-stop test omits the execute (9) / sendMessageStream (3) count assertions its foreground enforce sibling has — and stays open for human follow-up or a follow-up PR adding the two assertions.

中文说明

延期处理(并非否定其价值)——本轮处于仅 Critical 模式:本 PR 的确定性增长刹车已触发(本计数窗口净增长 源码 75 / 测试 765 行,超出 400/400 预算),且 AGENTS.md 规定约 5 轮审查后只落地 Critical 修复(本轮为第 10 轮)。该发现所述成立——Goal 优雅停止测试确实缺少其前台 enforce 姊妹测试拥有的 execute(9 次)/ sendMessageStream(3 次)计数断言——保持开放,留待人工跟进或后续 PR 补充这两个断言。

Comment on lines +9 to +11
describe('channel prompt classification wire key', () => {
it('is identical across channel-base and acp-bridge', () => {
expect(CHANNEL_PROMPT_META_KEY).toBe(BRIDGE_CHANNEL_PROMPT_META_KEY);

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] R10-10: This new wire-key test pins only the classification-key twin; the equally load-bearing authorization-key twin — channel-base's CHANNEL_PROMPT_AUTHORIZATION_META_KEY ('qwen.daemon.channelPromptAuthorization', written into _meta by DaemonChannelBridge in the same object as the pinned key) vs the cli-local CHANNEL_WORKER_PROMPT_AUTHORIZATION_META_KEY read by the route — remains unpinned, identical by value today with no import path between the packages. Both endpoints change in this diff (the token is now always presented; channelWorkerAuthorized now feeds the new trustedChannelPrompt). — Concrete cost: if either side of the authorization pair is ever renamed, isChannelWorkerPromptAuthorized never matches → channelWorkerAuthorized is false for every channel-worker prompt → both trustedPromptDisplayText and trustedChannelPrompt are silently dropped → channel turns reclassify as foreground (interactive guard + LOOP_DETECTED rejection instead of graceful delivery), observable only in production. (The display-text twin is transitively pinned by the existing 'accepts channel display text only from the workspace worker' server test — only the authorization pair needs pinning.)

Suggested fix: add a second assertion in this test (cli can import both sides) pinning channel-base's CHANNEL_PROMPT_AUTHORIZATION_META_KEY toBe the serve module's CHANNEL_WORKER_PROMPT_AUTHORIZATION_META_KEY, with a matching import.

中文说明

[Suggestion] R10-10:该新 wire-key 测试只钉住了分类键的孪生对;同样承重的授权键孪生对——channel-base 的 CHANNEL_PROMPT_AUTHORIZATION_META_KEY'qwen.daemon.channelPromptAuthorization'DaemonChannelBridge 将其与被钉住的键写入同一 _meta 对象)与路由读取的 cli 本地 CHANNEL_WORKER_PROMPT_AUTHORIZATION_META_KEY——仍未被钉住,两者当前值相同且包之间没有 import 路径。本 diff 同时改动了两端(token 现在总是被出示;channelWorkerAuthorized 现在喂给新的 trustedChannelPrompt)。— 具体代价:若授权键对的任一侧被重命名,isChannelWorkerPromptAuthorized 永不匹配 → 每个 channel-worker prompt 的 channelWorkerAuthorized 都为 false → trustedPromptDisplayTexttrustedChannelPrompt 都被静默丢弃 → channel 轮次被重新分类为前台轮(交互式守卫 + LOOP_DETECTED 拒绝,而非优雅投递),且只能在生产中观察到。(display-text 孪生对已被既有 'accepts channel display text only from the workspace worker' 服务端测试间接钉住——只需钉住授权键对。)

建议修复:在本测试中增加第二个断言(cli 可以 import 两侧),把 channel-base 的 CHANNEL_PROMPT_AUTHORIZATION_META_KEY 与 serve 模块的 CHANNEL_WORKER_PROMPT_AUTHORIZATION_META_KEY 钉为相等,并补充相应 import。

— qwen3.8-max via Qwen Code /review (v0.21.11)

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.

Deferred (not declined on merit) — this round runs in Critical-only mode: the deterministic growth brake engaged for this PR (src 75 / test 765 net lines beyond the counting window's 400/400 budgets), and AGENTS.md lands only Critical fixes after ~5 review rounds (this is round 10). The finding stands as reported — the authorization-key twin (CHANNEL_PROMPT_AUTHORIZATION_META_KEY vs CHANNEL_WORKER_PROMPT_AUTHORIZATION_META_KEY) is unpinned — and stays open for human follow-up or a follow-up PR adding the second wire-key assertion.

中文说明

延期处理(并非否定其价值)——本轮处于仅 Critical 模式:本 PR 的确定性增长刹车已触发(本计数窗口净增长 源码 75 / 测试 765 行,超出 400/400 预算),且 AGENTS.md 规定约 5 轮审查后只落地 Critical 修复(本轮为第 10 轮)。该发现所述成立——授权键孪生对(CHANNEL_PROMPT_AUTHORIZATION_META_KEYCHANNEL_WORKER_PROMPT_AUTHORIZATION_META_KEY)确实未被钉住——保持开放,留待人工跟进或后续 PR 补充第二个 wire-key 断言。

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 Addressed the latest review feedback (round 5/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 5/100 轮)。改动内容与我反驳保留之处如下:

Autofix round summary — PR #8853 (review round 10)

Critical-only mode is active for this PR (the deterministic growth brake engaged: the diff grew src 75 / test 765 net lines beyond this counting window's 400/400 budgets). This round verifies and fixes all four Critical findings from the automated review and defers the six non-Critical Suggestions with recorded reasons (they remain open for human follow-up). No base-conflict resolution was requested (--conflict false).

Critical findings — all fixed

  • R10-1 — delivery body field bypassed the channel-turn gate (packages/cli/src/serve/routes/session.ts / Session.ts). Verified: the public delivery field flowed into context.channelDelivery without worker authorization, and Session.prompt computed channelTurn = channelDelivery !== undefined || channelPromptTurn, so any local client could opt its own turn out of loop-detected rejection and force the repeated-failure guard 'off' — the exact bypass this PR gates qwen.channel.prompt to close. Fix: narrowed the classification to the authenticated CHANNEL_PROMPT_META_KEY marker (channelPromptTurn) only. The delivery meta still schedules the delivery on end_turn (the public feature is unchanged — the route and the existing 'validates delivery and forwards it only through trusted prompt context' contract are untouched), but it no longer classifies the turn, so delivery-marked turns run the configured guard mode and reject like any foreground turn. Real channel turns are unaffected: the channel bridges always set CHANNEL_PROMPT_META_KEY, and the HTTP worker path carries the worker-authorized marker. The two tests that pinned the vulnerable behavior were flipped into regression tests (both fail on the pre-fix code), and the now-inaccurate comments in Session.ts, acpAgent.ts, acpAgent.test.ts, and the design doc were corrected.
  • R10-2 — unfiltered re-arm in handleRetry's catch (packages/web-shell/client/App.tsx). Verified: the catch re-arm used the unfiltered getRetryableTurnError walk, so a loop-stopped retry could arm retry state (retryableTurnErrorIdRef, identity refs, retry hint) on the loop_detected error class this PR de-retries, and Ctrl+Y resubmitted the loop-stopped prompt. Fix: mirrored the blocks-effect isRetryableTurnErrorKind filter at the catch re-arm site, as suggested. Added a discriminating regression test (fails pre-fix: Ctrl+Y resubmits; passes post-fix: two sends only).
  • R10-3 — stale failedTurnErrorRetryRef survived a filtered loop error (App.tsx). Verified: the catch stashed the retry payload before the loop turn_error block committed (structural microtask-vs-flush ordering), and nothing cleared it, so a later unrelated retryable turn error could consume the stale payload and resubmit the loop-stopped prompt misattributed to a turn the user never submitted. Fix: gated the catch-time stash on the rejection not being a loop-detected turn rejection (the SDK turn error carries the LOOP_DETECTED code as its body), as suggested — with no stash created for a loop-stopped retry, neither ordering can leave a stale payload for a later error to consume. Added a discriminating regression test covering the structural ordering plus the later-error consumption (fails pre-fix, passes post-fix).
  • R10-4 — turn_complete dropped the "already retried" suppression (App.tsx). Verified: the widened lastTurnErrorIdRef walk reported the previous turn's error again when a retry turn settled before newer content landed (settle precedes the transcript flush), producing a duplicate, misattributed turn_complete to the host. Fix: applied the suggested suppression — the walk result is only reported when it is not the error the user already retried (retriedTurnErrorIdRef); loop-detected errors are never retried, so they still always surface. Added a discriminating regression test (fails pre-fix, passes post-fix).

Suggestions — deferred (Critical-only mode)

R10-5 through R10-10 are all valuable, mutation-verified test-hardening findings. They are deferred this round — not declined on merit — because Critical-only mode is active (growth brake) and AGENTS.md lands only Critical fixes after ~5 review rounds (this is round 10); each deferral is answered on its own thread and stays open for human follow-up or a follow-up PR. R10-7 additionally asks for a new observation surface, which is out of scope for a Critical-only round.

Conflict notes

None — --conflict false; no merge performed.

Verification

  • npm run build — passed
  • npm run typecheck — passed
  • npm run lint — passed
  • npx prettier --check on all changed files — passed
  • vitest packages/web-shell client/App.test.tsx — 440 passed (437 existing + 3 new regression tests)
  • vitest packages/web-shell full suite — 3492 passed
  • vitest packages/cli src/acp-integration/session/Session.test.ts — 607 passed (includes the two flipped delivery-classification regression tests)
  • vitest packages/cli acpAgent.test.ts + server.test.ts + channel-delivery-authorization.test.ts + channel-prompt-wire-key.test.ts — 1335 passed
  • vitest packages/acp-bridge full suite — 1390 passed (unchanged by this round, re-run as the PR's own coverage)
  • Discrimination checks: the 3 new App tests and the 2 flipped Session tests all fail against the pre-fix code and pass with the fix
  • Integration tests after npm run bundle — not run: the touched behavior (Session classification, web-shell retry state) is exercised by the unit suites above and has no integration-harness coverage (LOOP_DETECTED/delivery surfaces do not appear anywhere under integration-tests/)
中文说明

Autofix 轮次总结 — PR #8853(审查第 10 轮)

本 PR 已进入仅处理 Critical 的模式(确定性增长刹车已触发:本计数窗口内 diff 净增长达 源码 75 / 测试 765 行,超出 400/400 预算)。本轮验证并修复了自动审查的全部 4 个 Critical 发现,并将 6 个非 Critical 的 Suggestion 记录原因后延期处理(保持开放,留待人工跟进)。未要求解决 base 冲突(--conflict false)。

Critical 发现 — 全部修复

  • R10-1 — delivery 请求体字段绕过了 channel-turn 门控(packages/cli/src/serve/routes/session.ts / Session.ts)。 已验证:公共 delivery 字段在没有任何 worker 授权的情况下流入 context.channelDelivery,而 Session.prompt 计算 channelTurn = channelDelivery !== undefined || channelPromptTurn,因此任何本地客户端都能让自己的轮次豁免 loop-detected 拒绝、并强制重复失败守卫为 'off' ——正是本 PR 给 qwen.channel.prompt 设门控要堵住的绕过。修复:把分类收窄为仅认经过认证的 CHANNEL_PROMPT_META_KEY 标记(channelPromptTurn)。delivery meta 仍会在 end_turn 时调度投递(公共功能不变——路由与既有 'validates delivery and forwards it only through trusted prompt context' 契约均未改动),但不再参与分类,因此 delivery 标记的轮次运行配置的守卫模式、并像任何前台轮次一样拒绝。真实 channel 轮次不受影响:channel bridge 总是设置 CHANNEL_PROMPT_META_KEY,HTTP worker 路径携带经 worker 认证的标记。两个钉住漏洞行为的测试被翻转为回归测试(在修复前代码上均失败),Session.tsacpAgent.tsacpAgent.test.ts 与设计文档中不再准确的注释也已更正。
  • R10-2 — handleRetry catch 中未过滤的重新武装(packages/web-shell/client/App.tsx)。 已验证:catch 的重新武装使用未过滤的 getRetryableTurnError 遍历结果,因此一次自身被循环保护停止的重试可以在本 PR 明确取消重试的 loop_detected 错误类别上武装重试状态(retryableTurnErrorIdRef、identity refs、重试提示),Ctrl+Y 会重新提交被循环停止的 prompt。修复:按建议在 catch 重新武装处镜像 blocks effect 的 isRetryableTurnErrorKind 过滤器。新增具有区分力的回归测试(修复前失败:Ctrl+Y 重新提交;修复后通过:仅两次发送)。
  • R10-3 — 过期的 failedTurnErrorRetryRef 在被过滤的循环错误后存活(App.tsx)。 已验证:catch 在 loop turn_error block 提交进 store 之前就暂存了重试载荷(结构性的微任务/刷新时序),且没有任何路径清理它,因此之后一个无关的可重试轮次错误可能消费该过期载荷,重新提交被循环停止的 prompt 并错误归因到一个用户从未提交的轮次。修复:按建议把 catch 时的暂存置于"终止事件不是 loop-detected 拒绝"的门控之下(SDK 轮次错误的 body 携带 LOOP_DETECTED code)——循环停止的重试不再产生暂存载荷,任何时序下都不会有过期载荷被后续错误消费。新增覆盖结构性时序与后续错误消费的区分性回归测试(修复前失败,修复后通过)。
  • R10-4 — turn_complete 丢掉了"已重试过"的抑制(App.tsx)。 已验证:扩展后的 lastTurnErrorIdRef 遍历会在重试轮于新内容落地前结束(settle 先于转录刷新)时再次上报上一轮的错误,向宿主发出重复且归因错误的 turn_complete。修复:采用建议的抑制方式——仅当遍历结果不是用户已重试过的错误(retriedTurnErrorIdRef)时才上报;loop-detected 错误从不被重试,因此始终会上报。新增具有区分力的回归测试(修复前失败,修复后通过)。

Suggestion — 延期处理(仅 Critical 模式)

R10-5 至 R10-10 均为有价值的、经突变验证的测试加固发现。本轮延期处理——并非否定其价值——因为当前处于仅 Critical 模式(增长刹车已触发),且 AGENTS.md 规定约 5 轮审查之后只落地 Critical 修复(本轮为第 10 轮);每个延期都会在各自线程中回复说明,并保持开放,留待人工跟进或后续 PR。R10-7 还要求新增观测表面,超出仅 Critical 轮次的范围。

冲突说明

无 —— --conflict false;未执行合并。

验证

  • npm run build — 通过
  • npm run typecheck — 通过
  • npm run lint — 通过
  • 对所有改动文件执行 npx prettier --check — 通过
  • vitest packages/web-shell client/App.test.tsx — 440 通过(437 个既有 + 3 个新回归测试)
  • vitest packages/web-shell 完整套件 — 3492 通过
  • vitest packages/cli src/acp-integration/session/Session.test.ts — 607 通过(含两个翻转后的 delivery 分类回归测试)
  • vitest packages/cli acpAgent.test.ts + server.test.ts + channel-delivery-authorization.test.ts + channel-prompt-wire-key.test.ts — 1335 通过
  • vitest packages/acp-bridge 完整套件 — 1390 通过(本轮未改动,作为该 PR 自身覆盖重跑)
  • 区分力检查:3 个新 App 测试与 2 个翻转的 Session 测试在修复前代码上全部失败、修复后全部通过
  • npm run bundle 之后的集成测试 — 未运行:所触碰的行为(Session 分类、web-shell 重试状态)已由上述单元套件覆盖,且没有集成测试装置覆盖(integration-tests/ 下完全不存在 LOOP_DETECTED/delivery 相关表面)

Deferred non-Critical feedback

Critical-only mode is active: the PR's diff grew src 75 / test 765 net lines beyond this counting window's baseline (budgets: 400/400). The workflow excluded the non-Critical feedback below from this round's actionable sections; the items remain open for human follow-up. Maintainer feedback continues to flow unaffected during a growth-only engagement (the per-author batch budget applies only after 5 change-producing rounds). (@qwen-code /retry starts a fresh counting window.)

中文说明

已进入仅处理 Critical 的模式:本计数窗口内 diff 净增长已达 源码 75 / 测试 765 行(预算 400/400)。本轮可执行区域已排除下方非 Critical 反馈;这些条目保持开放,留待人工跟进。纯增长触发期间维护者反馈照常流动(按作者的批次预算仅在完成 5 个产生改动的轮次后生效)。(评论 @qwen-code /retry 可开启新的计数窗口。)

Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。

Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. · 有空请复审;第 100 轮后本 bot 停止并将 PR 交给人工。


🧠 Handled by Qwen Code · model/模型 qwen3.8-max

@qwen-code-ci-bot qwen-code-ci-bot 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.

Partially reviewed — gaps disclosed.

Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.

Not explored to full depth (tool budget reached): "You are review agent reverse-audit — Reverse audit agent…": none — all checks above completed within budget.; "You are review agent reverse-audit — Reverse audit agent…": none significant — all planned checks complete. Tool calls ~27 used.; "You are review agent reverse-audit — Reverse audit agent…": none — all checks above completed within budget.; "You are review agent reverse-audit — Reverse audit agent…": none — finished within budget with all checks above completed.; "You are review agent reverse-audit — Reverse audit agent…": I did not run the web-shell test suite to dynamically confirm the stash/re-arm sequencing (relied on static tracing); and I did not audit other packages' consum…, and 15 more.

Not reviewed: reverse audit — did not converge within the reverse-audit round cap of 5.

中文说明

仅完成部分审查,审查缺口已披露。

未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。

未探索到全部深度(达到工具调用预算):"You are review agent reverse-audit — Reverse audit agent…"none — all checks above completed within budget."You are review agent reverse-audit — Reverse audit agent…"none significant — all planned checks complete. Tool calls ~27 used."You are review agent reverse-audit — Reverse audit agent…"none — all checks above completed within budget."You are review agent reverse-audit — Reverse audit agent…"none — finished within budget with all checks above completed."You are review agent reverse-audit — Reverse audit agent…"I did not run the web-shell test suite to dynamically confirm the stash/re-arm sequencing (relied on static tracing); and I did not audit other packages' consum…,另有 15 条。

未审查:反向审计——在 5 轮的反审轮数上限内未收敛。

— qwen3.8-max via Qwen Code /review (v0.21.11)

* (`available_commands_update`, `current_mode_update`) that settings and
* approval-mode refreshes fan out to idle sessions.
*/
const REFRESH_APPEND_BOOKKEEPING_EVENT_TYPES = new Set([

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] R11-1: REFRESH_APPEND_BOOKKEEPING_EVENT_TYPES still misses a dozen idle-reachable workspace-event types that are journaled onto every session bus via publishWorkspaceEvent/broadcastWorkspaceEvent: tool_toggled, workspace_initialized, mcp_server_restarted, mcp_server_restart_refused, settings_reloaded, trust_change_requested, memory_changed, agent_changed, git_status_changed, git_branch_changed, github_setup_completed, auth_device_flow_*. This is the same mechanism family as R6-1/R6-7; the set's own doc comment mandates auditing the full broadcastWorkspaceEvent vocabulary ("every idle non-turn event belongs here"). — Failure scenario: probe-verified on this commit: a foreground prompt trips loop protection → while idle the user toggles a tool / edits memory / changes an agent / git status refreshes (git_status_changed fires from a background watcher with zero user action) → the event lands in the session journal → on page refresh isRefreshAppendTurnContent returns true for it → the in-memory loop turn_error is not appended → the refreshed transcript shows the failed turn with no loop-protection terminal while the summary still advertises it (hasLoopTerminalInRefreshedReplay=false with PR code; flips to true when the missing type is added to the set).

Suggested change
const REFRESH_APPEND_BOOKKEEPING_EVENT_TYPES = new Set([
const REFRESH_APPEND_BOOKKEEPING_EVENT_TYPES = new Set([
'pending_prompt_added',
'pending_prompt_completed',
'prompt_cancelled',
'model_switched',
'model_switch_failed',
'approval_mode_changed',
'language_changed',
'session_metadata_updated',
'session_cwd_changed',
'artifact_changed',
'settings_changed',
'extensions_changed',
'mcp_server_changed',
'mcp_server_added',
'mcp_server_removed',
'user_shell_command',
'user_shell_result',
'tool_toggled',
'workspace_initialized',
'mcp_server_restarted',
'mcp_server_restart_refused',
'settings_reloaded',
'trust_change_requested',
'memory_changed',
'agent_changed',
'git_status_changed',
'git_branch_changed',
'github_setup_completed',
// plus the dynamic `auth_device_flow_*` family, or replace the allowlist
// with a positive turn-content/ownership predicate

(also add a regression test that publishes one of these after a loop terminal — e.g. via bridge.publishWorkspaceEvent — and asserts the terminal survives loadSession.)

中文说明

[Critical] R11-1:REFRESH_APPEND_BOOKKEEPING_EVENT_TYPES 仍然遗漏了约十二种空闲可达的工作区事件类型——它们会经由 publishWorkspaceEvent/broadcastWorkspaceEvent 写入每个会话总线的日志:tool_toggledworkspace_initializedmcp_server_restartedmcp_server_restart_refusedsettings_reloadedtrust_change_requestedmemory_changedagent_changedgit_status_changedgit_branch_changedgithub_setup_completedauth_device_flow_*。这与 R6-1/R6-7 是同一机制族;该集合自身的注释明确要求审计完整的 broadcastWorkspaceEvent 词汇表("每个空闲非轮次事件都属于这里")。— 失败场景:已在当前提交上探针验证:前台 prompt 触发循环保护 → 空闲时用户切换工具 / 编辑记忆 / 变更 agent / git 状态刷新(git_status_changed 由后台监视器触发,无需任何用户操作)→ 事件进入会话日志 → 刷新页面时 isRefreshAppendTurnContent 对其返回 true → 内存中的 loop turn_error 不被追加 → 刷新后的转录显示失败轮次却没有循环保护终止事件,而摘要仍在宣告它(PR 代码下 hasLoopTerminalInRefreshedReplay=false;把缺失类型加入集合后翻转为 true)。建议按上方代码补全集合(或用"按轮次内容正向判断"的谓词替换允许清单),并补充回归测试:在循环终止事件之后发布其中一个事件(例如通过 bridge.publishWorkspaceEvent),断言该终止事件在 loadSession 后仍然存在。

— qwen3.8-max via Qwen Code /review (v0.21.11)

Comment on lines +4677 to +4682
stopReason: rejectOnLoopDetected
? cancelledOrThrowLoopDetected(
pendingSend.signal,
toolLoopState,
)
: getAbortAwareEndTurnStopReason(pendingSend.signal),

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] R10-5: The cancellation-wins-over-loop-rejection invariant is race-tested at two of the three foreground cancelledOrThrowLoopDetected call sites (repeated-failure branch, Stop-continuation branch) but not at this per-turn-cap branch. — Failure scenario: if this branch were changed to throw unconditionally (bare throw createLoopDetectedTurnError(...)), a user cancelling at the moment the turn trips the tool-call cap would receive a LOOP_DETECTED rejection instead of { stopReason: 'cancelled' } — the client reports loop guidance for a turn the user cancelled — and the suite stays green. Suggested fix: add a variant of the cancellation-race test that trips the explicit per-turn cap (two-call batch against a one-call cap) with cancelPendingPrompt() landing before this branch settles, asserting { stopReason: 'cancelled' }.

中文说明

[Suggestion] R10-5:"取消优先于循环拒绝"这一不变量在前台三个 cancelledOrThrowLoopDetected 调用点中的两个(重复失败分支、Stop 续延分支)有竞态测试,但这个单轮上限分支没有。— 失败场景:若该分支改为无条件抛出(直接 throw createLoopDetectedTurnError(...)),用户在轮次触发工具调用上限的瞬间取消时,会收到 LOOP_DETECTED 拒绝而不是 { stopReason: 'cancelled' }——客户端对一个用户已取消的轮次显示循环保护提示——而整个测试套件仍为绿色。建议修复:新增取消竞态测试变体,用两次调用的批次触发显式单轮上限(上限设为 1),让 cancelPendingPrompt() 在该分支落定之前到达,断言 { stopReason: 'cancelled' }

— qwen3.8-max via Qwen Code /review (v0.21.11)

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.

Deferred — the Critical-only growth brake is active on this PR (five change-producing rounds complete; the test diff already exceeds this counting window's growth budget), and per AGENTS.md only Critical fixes land once a PR passes ~5 review rounds. The cancellation-wins invariant is already race-tested at the other two cancelledOrThrowLoopDetected sites, so this is hardening against a speculative future change, not a current defect. Recorded here so it is not silently dropped; suggested as follow-up work.

中文说明

延后处理——本 PR 已激活 Critical-only 增长刹车(已完成 5 个产生改动的轮次;测试 diff 已超出本计数窗口的增长预算),且按 AGENTS.md,PR 超过约 5 轮审查后仅合入 Critical 修复。“取消获胜”不变量在另外两个 cancelledOrThrowLoopDetected 调用点已有竞态测试,因此这是针对假想未来改动的加固,而非当前缺陷。已在此记录避免被悄悄丢弃;建议作为后续工作。

Comment on lines +5745 to +5747
expect(compactedReplay.some((event) => event.type === 'turn_error')).toBe(
false,
);

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] R10-6: This drop-on-subsequent-successful-turn test asserts only the absence of turn_error, without anchoring compactedReplay[0] on the persisted page — unlike its sibling drop test, which pins compactedReplay[0] content + length with the explicit rationale "a stale branch that rebuilds the replay from anything else ships green without this". — Failure scenario: a regression in replay composition that returns a tail-only or rebuilt replay (dropping the persisted page, containing no turn_error) satisfies some(...) === false and ships green while a page refresh silently loses persisted transcript history.

Suggested change
expect(compactedReplay.some((event) => event.type === 'turn_error')).toBe(
false,
);
expect(compactedReplay[0]).toMatchObject({
type: 'session_update',
data: expect.objectContaining({
content: { type: 'text', text: 'persisted turn content' },
}),
});
expect(compactedReplay).toHaveLength(1);
expect(compactedReplay.some((event) => event.type === 'turn_error')).toBe(
false,
);
中文说明

[Suggestion] R10-6:这个"后续成功交互轮次后丢弃"的测试只断言了 turn_error 的缺失,没有把 compactedReplay[0] 锚定到持久化页——而它的兄弟 drop 测试钉住了 compactedReplay[0] 的内容与长度,并明确注释"任何从其他来源重建 replay 的陈旧分支在没有此断言时会静默通过"。— 失败场景:replay 组合发生回归、返回仅含尾部或重建的 replay(丢弃持久化页、其中不含 turn_error)时,some(...) === false 依然成立、测试绿灯通过,而页面刷新会悄悄丢失持久化的转录历史。建议按上方代码镜像兄弟测试的锚定。

— qwen3.8-max via Qwen Code /review (v0.21.11)

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.

Deferred — the Critical-only growth brake is active on this PR (five change-producing rounds complete; the test diff already exceeds this counting window's growth budget), and per AGENTS.md only Critical fixes land once a PR passes ~5 review rounds. Anchoring compactedReplay[0] would mirror the sibling drop test and is a reasonable strengthening, but it is test-only hardening with no current defect. Recorded here so it is not silently dropped; suggested as follow-up work.

中文说明

延后处理——本 PR 已激活 Critical-only 增长刹车(已完成 5 个产生改动的轮次;测试 diff 已超出本计数窗口的增长预算),且按 AGENTS.md,PR 超过约 5 轮审查后仅合入 Critical 修复。为 compactedReplay[0] 加锚可以镜像孪生 drop 测试,是合理的加固,但属于纯测试加固、无当前缺陷。已在此记录避免被悄悄丢弃;建议作为后续工作。

});
const bridge = makeBridge({
sessionShellCommandEnabled: true,
maxJournalEvents: 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] R10-7: This journal-truncation test pins maxJournalEvents: 1 but never asserts that truncation/the history_truncated marker actually occurred; its assertions (replay length 2, persisted-page anchor, loop terminal last) pass identically with or without eviction. — Failure scenario: if the eviction logic changed so the cap no longer evicts (no marker unshifted), the test would still pass while the history_truncated exemption in isRefreshAppendTurnContent is silently untested. Suggested fix: assert the truncation premise — e.g. collect until a history_truncated marker is observed on the bus before calling loadSession.

中文说明

[Suggestion] R10-7:这个日志截断测试钉住了 maxJournalEvents: 1,却从未断言截断/history_truncated 标记真的发生了;其断言(replay 长度为 2、持久化页锚点、loop 终止事件在末尾)在有无驱逐的情况下都同样通过。— 失败场景:如果驱逐逻辑改变、上限不再驱逐(不再 unshift 标记),该测试仍然通过,而 isRefreshAppendTurnContent 中对 history_truncated 的豁免则在无人察觉的情况下失去测试覆盖。建议修复:断言截断前提——例如在调用 loadSession 之前收集直到在总线上观察到 history_truncated 标记。

— qwen3.8-max via Qwen Code /review (v0.21.11)

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.

Deferred — the Critical-only growth brake is active on this PR (five change-producing rounds complete; the test diff already exceeds this counting window's growth budget), and per AGENTS.md only Critical fixes land once a PR passes ~5 review rounds. Asserting the history_truncated truncation premise is a reasonable strengthening, but it guards against a hypothetical eviction-logic regression, not a current defect. Recorded here so it is not silently dropped; suggested as follow-up work.

中文说明

延后处理——本 PR 已激活 Critical-only 增长刹车(已完成 5 个产生改动的轮次;测试 diff 已超出本计数窗口的增长预算),且按 AGENTS.md,PR 超过约 5 轮审查后仅合入 Critical 修复。断言 history_truncated 截断前提是合理的加固,但它防范的是假想的驱逐逻辑回归,而非当前缺陷。已在此记录避免被悄悄丢弃;建议作为后续工作。

Comment on lines +5355 to +5357
expect(bridge.getSessionSummary(session.sessionId).turnError).toMatchObject(
{ code: 'LOOP_DETECTED' },
);

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] R10-8: The three queued-terminal tests pin only the turnError half of the documented "queued terminals must not mutate session-scoped turn state" invariant; the retryAllowed half — named in the same production comment at the broadcastTurnError mutateTurnState gate — is asserted nowhere in this file (grep for retryAllowed in the new-test region returns nothing). — Failure scenario: a regression arming entry.retryAllowed from a queued terminal (a prompt that never ran) ships green while the summary's retry semantics desync — retry admission consumes retryAllowed at bridge prompt admission. Suggested fix: extend one queued-terminal test to also assert the summary's retryAllowed remains unset/unchanged after the queued terminal.

中文说明

[Suggestion] R10-8:三个排队终止测试只钉住了"排队终止不得变更会话级轮次状态"这一文档化不变量的 turnError 一半;retryAllowed 一半——在 broadcastTurnErrormutateTurnState 门控处的同一段生产代码注释中被点名——在整个文件中没有任何断言(对新测试区域 grep retryAllowed 无任何匹配)。— 失败场景:若回归使排队终止(一个从未运行的 prompt)置位 entry.retryAllowed,测试仍为绿色,而摘要的重试语义已脱节——重试准入会在 bridge prompt 准入时消费 retryAllowed。建议修复:在某个排队终止测试中同时断言排队终止之后摘要的 retryAllowed 保持未置位/不变。

— qwen3.8-max via Qwen Code /review (v0.21.11)

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.

Deferred — the Critical-only growth brake is active on this PR (five change-producing rounds complete; the test diff already exceeds this counting window's growth budget), and per AGENTS.md only Critical fixes land once a PR passes ~5 review rounds. Extending a queued-terminal test to assert the retryAllowed half is worthwhile, but it is test-only hardening against a hypothetical regression, not a current defect. Recorded here so it is not silently dropped; suggested as follow-up work.

中文说明

延后处理——本 PR 已激活 Critical-only 增长刹车(已完成 5 个产生改动的轮次;测试 diff 已超出本计数窗口的增长预算),且按 AGENTS.md,PR 超过约 5 轮审查后仅合入 Critical 修复。扩展排队终止测试以断言 retryAllowed 一半是有价值的,但属于针对假想回归的纯测试加固,而非当前缺陷。已在此记录避免被悄悄丢弃;建议作为后续工作。

expect(container.querySelector('[data-testid="retry"]')).toBeNull();
await act(async () => {
window.dispatchEvent(
new KeyboardEvent('keydown', { key: 'y', ctrlKey: true }),

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] R11-12: Both new Ctrl+Y assertions (here and at ~12527) are negative-only (sendPrompt stays at 2 calls), and there is no positive control for the e.key === 'y' branch anywhere in the package — Ctrl+O and Ctrl+L have positive tests, Ctrl+Y does not. Probe-verified: deleting the 'y' branch from the global-shortcut effect leaves both tests green. — Failure scenario: if that branch is removed or rewired, sendPrompt stays at 2 trivially and both tests still pass — silently voiding the only two tests that exercise handleRetry via the shortcut while the hint is hidden. Suggested fix: add a positive control (arm a real retry, dispatch Ctrl+Y, assert sendPrompt is called), or assert the unarmed path's observable side effect — handleRetry's else branch dispatches { type: 'status', text: t('retry.none') }, which proves the handler ran and the gate blocked the send.

中文说明

[Suggestion] R11-12:两个新的 Ctrl+Y 断言(此处与 ~12527)都只有否定面(sendPrompt 保持 2 次调用),且整个包中没有针对 e.key === 'y' 分支的正向对照——Ctrl+O 与 Ctrl+L 都有正向测试,Ctrl+Y 没有。已探针验证:从全局快捷键 effect 中删除 'y' 分支后两个测试仍为绿色。— 失败场景:若该分支被移除或改接,sendPrompt 平凡地保持 2 次、两个测试照样通过——在提示隐藏时经由快捷键触发 handleRetry 的仅有这两个测试,它们被悄悄作废。建议修复:增加正向对照(武装一个真实重试、派发 Ctrl+Y、断言 sendPrompt 被调用),或断言未武装路径的可观察副作用——handleRetry 的 else 分支会派发 { type: 'status', text: t('retry.none') },证明处理器确实运行且门控拦截了发送。

— qwen3.8-max via Qwen Code /review (v0.21.11)

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.

Deferred — the Critical-only growth brake is active on this PR (five change-producing rounds complete; the test diff already exceeds this counting window's growth budget), and per AGENTS.md only Critical fixes land once a PR passes ~5 review rounds. Adding a positive control for the Ctrl+Y branch (or asserting the retry.none status dispatch) is reasonable, but it is test-only hardening with no current defect. Recorded here so it is not silently dropped; suggested as follow-up work.

中文说明

延后处理——本 PR 已激活 Critical-only 增长刹车(已完成 5 个产生改动的轮次;测试 diff 已超出本计数窗口的增长预算),且按 AGENTS.md,PR 超过约 5 轮审查后仅合入 Critical 修复。为 Ctrl+Y 分支增加正向对照(或断言 retry.none 状态派发)是合理的,但属于无当前缺陷的纯测试加固。已在此记录避免被悄悄丢弃;建议作为后续工作。

expect(container.querySelector('[data-testid="retry"]')).not.toBeNull();
});

it('does not rearm a retry when the retried turn is loop-stopped', async () => {

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] R11-13: The PR's changed turn_complete surfacing (loop errors always surface via the new lastTurnErrorIdRef) is pinned only for a fresh loop turn; the retried-turn case is unpinned. This test produces exactly that transcript (original error → retried → loop turn_error for the retried turn) but renders without onSessionChange and never drives streamingState off idle. Probe-verified: a mutation suppressing loop errors only after a retry ships green at 440/440. — Failure scenario: a future change re-suppressing loop errors for retried turns (e.g. reverting lastTurnErrorIdRef to the retryable filter) → the host embedding the web shell never learns the retried turn failed → every test still passes. Suggested fix: pass onSessionChange, drive streamingState responding→idle around the loop block for the retried turn, and assert turn_complete carries error: expect.objectContaining({ message: 'Turn error (block turn-error-loop)' }).

中文说明

[Suggestion] R11-13:本 PR 改变的 turn_complete 上报行为(loop 错误经由新的 lastTurnErrorIdRef 总是上报)只为"全新 loop 轮次"钉住;"被重试轮次"的情形没有钉住。本测试恰好产生了那种转录(原始错误 → 重试 → 被重试轮次的 loop turn_error),但渲染时未接 onSessionChange,也从未把 streamingState 驱离 idle。已探针验证:一个"仅在重试之后抑制 loop 错误"的变异在 440/440 全绿的情况下通过。— 失败场景:未来若重新抑制被重试轮次的 loop 错误(例如把 lastTurnErrorIdRef 回退为 retryable 过滤器)→ 内嵌 web shell 的宿主永远不知道被重试的轮次失败了→ 所有测试仍通过。建议修复:传入 onSessionChange,围绕被重试轮次的 loop 块把 streamingState 从 responding 驱动到 idle,断言 turn_complete 携带 error: expect.objectContaining({ message: 'Turn error (block turn-error-loop)' })

— qwen3.8-max via Qwen Code /review (v0.21.11)

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.

Deferred — the Critical-only growth brake is active on this PR (five change-producing rounds complete; the test diff already exceeds this counting window's growth budget), and per AGENTS.md only Critical fixes land once a PR passes ~5 review rounds. Driving streamingState responding→idle with onSessionChange for the retried-turn shape is a new test scenario; the fresh-turn surfacing is already pinned and no current defect exists. Recorded here so it is not silently dropped; suggested as follow-up work.

中文说明

延后处理——本 PR 已激活 Critical-only 增长刹车(已完成 5 个产生改动的轮次;测试 diff 已超出本计数窗口的增长预算),且按 AGENTS.md,PR 超过约 5 轮审查后仅合入 Critical 修复。为被重试轮次形状驱动 streamingState responding→idle 并接入 onSessionChange 是一个新的测试场景;全新轮次的上报已被钉住,且不存在当前缺陷。已在此记录避免被悄悄丢弃;建议作为后续工作。

type: 'turn_complete',
sessionId: 'session-1',
error: expect.objectContaining({
message: 'Turn error (block turn-error-with-notification)',

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] R11-16: The turn_complete widening to the unfiltered lastTurnErrorIdRef walk is pinned only for loop/fresh/single-error turns; the case that motivated decoupling the report from the retry affordance — a second errored turn reaching the identity-reset branch — has no test. Probe-verified: a plausible re-coupling (report gated on canRetry || loop_detected) drops the second turn's failure report with all 440 tests green. Note the shape that bites: two fresh submits do NOT diverge (the submit handler resets identity refs); the diverging case is a RETRIED turn that errors again (or a slash-command second turn), where pre-PR reported error: undefined and post-PR reports the new error. — Failure scenario: a refactor re-coupling the report to affordance state — plausible, since the new comment asserts the two walks "never disagree" — drops the second turn's failure report (error: undefined for a turn that errored) while the suite stays green. Suggested fix: add a test in the diverging shape: turn 1 errors (A); retry; the retried turn errors (B) with blocks committed before the rejection settles; drive responding→idle; assert turn_complete reports B.

中文说明

[Suggestion] R11-16:turn_complete 放宽到未过滤的 lastTurnErrorIdRef 遍历,只为 loop/全新/单错误轮次钉住;促使上报与重试提示解耦的那个场景——第二个出错轮次进入身份重置分支——没有测试。已探针验证:一个合理的重新耦合(上报以 canRetry || loop_detected 为门控)会在全部 440 个测试绿灯的情况下丢弃第二个轮次的失败上报。注意真正起作用的形状:两次全新提交并不会产生差异(提交处理器会重置身份 refs);产生差异的是"再次出错的重试轮次"(或斜杠命令的第二轮次)——PR 之前上报 error: undefined,PR 之后上报新错误。— 失败场景:若重构把上报重新耦合到提示状态——由于新注释声称两个遍历"永不失配",这种重构很可能发生——第二个轮次的失败上报被丢弃(出错轮次收到 error: undefined),而套件仍为绿色。建议修复:按差异形状新增测试:轮次 1 出错(A);重试;被重试轮次出错(B),且块在拒绝落定前已提交;驱动 responding→idle;断言 turn_complete 上报 B。

— qwen3.8-max via Qwen Code /review (v0.21.11)

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.

Deferred — the Critical-only growth brake is active on this PR (five change-producing rounds complete; the test diff already exceeds this counting window's growth budget), and per AGENTS.md only Critical fixes land once a PR passes ~5 review rounds. The diverging retry-then-error-again shape is a new test scenario with sizable setup; the fresh/loop/single-error shapes are already pinned and no current defect exists. Recorded here so it is not silently dropped; suggested as follow-up work.

中文说明

延后处理——本 PR 已激活 Critical-only 增长刹车(已完成 5 个产生改动的轮次;测试 diff 已超出本计数窗口的增长预算),且按 AGENTS.md,PR 超过约 5 轮审查后仅合入 Critical 修复。“重试后再次出错”的分歧形状是一个搭建不小的新测试场景;fresh/loop/单错误形状已被钉住,且不存在当前缺陷。已在此记录避免被悄悄丢弃;建议作为后续工作。

Comment on lines +15346 to +15347
expect(mockGoalRuntime.dispatch).not.toHaveBeenCalled();
// The graceful stop keeps the user-visible stop message: it is

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] R11-19 (location 1 of 2 — the per-turn-cap Goal test at ~15236 shares this gap): Both Goal graceful tests pin settlement + emitted stop message but not the preserved-history half their foreground twin pins — the addHistory call carrying the failed functionResponse parts + the System: stop/context text written by #preserveStoppedToolRun/#preserveUnsentMessageHistory. The preserve calls run unconditionally before the graceful/reject branch; only the emit is gated. — Failure scenario: a regression gating the preserve calls on interactivity (or mis-assembling the history) ships green on both Goal branches — the emitted sessionUpdate chunk still satisfies the assertion, finishTurn still settles — while the next Goal continuation restarts against a clean context and retries the identical failing approach, re-burning autonomous iterations. For the cap test the over-cap batch is skipped whole, making the preserved context the ONLY artifact of the stop. Suggested fix: mirror the foreground twin — assert addHistory received role: 'user' parts containing the last batch's functionResponse plus the stop/context text.

中文说明

[Suggestion] R11-19(共 2 处,此处为第 1 处——~15236 的单轮上限 Goal 测试同样存在此缺口):两个 Goal 优雅停止测试钉住了结算 + 发出的停止消息,但没有钉住其前台孪生测试所钉住的"保留历史"一半——即携带失败 functionResponse 部件 + System: 停止/上下文文本(由 #preserveStoppedToolRun/#preserveUnsentMessageHistory 写入)的 addHistory 调用。preserve 调用在优雅/拒绝分支之前无条件执行;只有消息发出是被门控的。— 失败场景:若回归把 preserve 调用门控在交互式上(或错误组装历史),两个 Goal 分支仍为绿色——发出的 sessionUpdate 块仍满足断言、finishTurn 仍结算——而下一个 Goal 续延会在干净的上下文上重启、重试完全相同的失败路径,白白消耗自主迭代。对上限测试而言,超限批次被整体跳过,被保留的上下文是该停止的唯一产物。建议修复:镜像前台孪生测试——断言 addHistory 收到 role: 'user' 的部件,其中包含最后一批的 functionResponse 与停止/上下文文本。

— qwen3.8-max via Qwen Code /review (v0.21.11)

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.

Deferred — the Critical-only growth brake is active on this PR (five change-producing rounds complete; the test diff already exceeds this counting window's growth budget), and per AGENTS.md only Critical fixes land once a PR passes ~5 review rounds. Mirroring the foreground twin's addHistory assertions for the Goal graceful tests is reasonable test strengthening, but there is no current defect. Recorded here so it is not silently dropped; suggested as follow-up work.

中文说明

延后处理——本 PR 已激活 Critical-only 增长刹车(已完成 5 个产生改动的轮次;测试 diff 已超出本计数窗口的增长预算),且按 AGENTS.md,PR 超过约 5 轮审查后仅合入 Critical 修复。为 Goal 优雅停止测试镜像前台孪生的 addHistory 断言是合理的测试加固,但没有当前缺陷。已在此记录避免被悄悄丢弃;建议作为后续工作。

await vi.waitFor(() => {
expect(mockGoalRuntime.finishTurn).toHaveBeenCalledWith(permit);
});
expect(mockGoalRuntime.dispatch).not.toHaveBeenCalled();

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] R11-19 (location 2 of 2 — see the sibling comment on the repeated-failure-guard Goal test): this Goal per-turn-cap graceful test pins settlement only; the preserved-history half its foreground twin pins (the addHistory call carrying the preserved LOOP_DETECTED_CONTEXT_MESSAGE — here the ONLY artifact of the stop, since the over-cap batch is skipped whole) is unasserted. Dropping the #preserveStoppedToolRun call on this branch ships green; the next Goal continuation then re-emits the same over-cap batch with no note that it was halted. Suggested fix: assert addHistory received the preserved loop-detected context text, as the cancellation-wins and foreground preservation paths attempt to pin.

中文说明

[Suggestion] R11-19(共 2 处,此处为第 2 处——另一处见重复失败守卫 Goal 测试上的孪生评论):这个 Goal 单轮上限优雅停止测试只钉住了结算;其前台孪生测试所钉住的"保留历史"一半(携带被保留的 LOOP_DETECTED_CONTEXT_MESSAGEaddHistory 调用——由于超限批次被整体跳过,它是该停止的唯一产物)没有断言。若删除该分支上的 #preserveStoppedToolRun 调用,测试仍为绿色;下一个 Goal 续延会再次发出同样的超限批次,且没有任何它曾被停止的记录。建议修复:断言 addHistory 收到了被保留的 loop-detected 上下文文本,与取消获胜及前台保留路径试图钉住的方式一致。

— qwen3.8-max via Qwen Code /review (v0.21.11)

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.

Deferred — the Critical-only growth brake is active on this PR (five change-producing rounds complete; the test diff already exceeds this counting window's growth budget), and per AGENTS.md only Critical fixes land once a PR passes ~5 review rounds. Same gap family as the sibling comment (R11-19 location 1): the preserved-history assertion for the Goal per-turn-cap test is reasonable strengthening, but there is no current defect. Recorded here so it is not silently dropped; suggested as follow-up work.

中文说明

延后处理——本 PR 已激活 Critical-only 增长刹车(已完成 5 个产生改动的轮次;测试 diff 已超出本计数窗口的增长预算),且按 AGENTS.md,PR 超过约 5 轮审查后仅合入 Critical 修复。与孪生评论(R11-19 第 1 处)属同一缺口族:为 Goal 单轮上限测试补充被保留历史断言是合理的加固,但没有当前缺陷。已在此记录避免被悄悄丢弃;建议作为后续工作。

qwen-code-dev-bot and others added 2 commits August 14, 2026 02:50
…tection-error

# Conflicts:
#	packages/acp-bridge/src/compactionEngine.ts
#	packages/acp-bridge/src/eventBus.ts
…llowlist (#8853)

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

Copy link
Copy Markdown
Collaborator

🤖 Addressed the latest review feedback (round 6/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 6/100 轮)。改动内容与我反驳保留之处如下:

Autofix review round summary — PR #8853

Conflict resolution (--conflict true)

Merged origin/main (21 commits, up to c9cb53398d). Two content conflicts, both the same functional collision between this PR and main:

  • packages/acp-bridge/src/compactionEngine.ts and packages/acp-bridge/src/eventBus.ts: this PR added the cheap liveJournalSnapshot() split (journal-only snapshot for the refresh-append guard); main independently added the liveReplayMode ('full' | 'summary') dual-journal design. Resolved by combining both: snapshot(liveReplayMode) now delegates to liveJournalSnapshot(liveReplayMode), which selects the full/summary journal and its recordId anchor; the CompactionEngine interface and the EventBus passthrough gained the optional mode parameter (default 'full', preserving this PR's guard semantics at both existing call sites).

Verified by the full acp-bridge suite (which covers both summary-mode replay and the refresh-append guard), build, typecheck, and lint.

Feedback dispositions

Fixed — Critical

  • R11-1 (rc:3780540805)REFRESH_APPEND_BOOKKEEPING_EVENT_TYPES missed idle-reachable workspace-event types, allowing a page refresh after idle activity (tool toggle, memory edit, background git_status_changed, device flow, …) to silently drop a pending loop-protection turn_error from the refreshed transcript. Verified against the code: audited every publishWorkspaceEvent / broadcastWorkspaceEvent call site and every direct session-bus publish; the missing set was exactly the eleven literals named plus the five members of the closed DeviceFlowEventEmission union (auth_device_flow_started/throttled/authorized/failed/cancelled). Fix: added all 16 types to the allowlist with a short audit note. Regression coverage: extended the existing parameterized keeps the turn error on refresh when %s bookkeeping lands after it family with all 16 types, published through the real workspace fan-out path (bridge.publishWorkspaceEvent) after the loop terminal, asserting the refreshed replay still ends with the turn_error anchored on the persisted page. Mutation-verified: removing one allowlist entry makes the corresponding case fail; restored, green. Deliberately NOT added: session_rewound (a rewind changes the replay basis, so it must keep blocking the append), session_died / session_closed (terminal/entry-removing states, not idle bookkeeping). The reviewer's alternative (replace the allowlist with a positive turn-content predicate) is a larger redesign, out of scope for a Critical-only round.

Deferred — 18 automated-reviewer suggestions (Critical-only growth brake)

The deterministic brake is active: five change-producing rounds are complete and the PR's test diff already exceeds this counting window's growth budget (net +1036 test lines vs 400 budget). Per AGENTS.md, once a PR passes ~5 review rounds only Critical fixes land; remaining suggestions are deferred with a recorded reason on each thread (see the per-thread replies) so nothing is silently dropped. All 18 are test-strengthening suggestions with no current production defect claimed:

  • R10-5 (rc:3780540811), R10-6 (rc:3780540814), R10-7 (rc:3780540819), R10-8 (rc:3780540822), R10-9 (rc:3780540824), R10-10 (rc:3780540825), R11-2 (rc:3780540831), R11-3 (rc:3780540836), R11-7 (rc:3780540842), R11-8 (rc:3780540843), R11-9 (rc:3780540848), R11-10 (rc:3780540849), R11-11 (rc:3780540853), R11-12 (rc:3780540858), R11-13 (rc:3780540860), R11-16 (rc:3780540865), R11-19 loc 1 (rc:3780540866), R11-19 loc 2 (rc:3780540868).

Recommended first follow-up items (flagged in their thread replies): R11-3 (cheap, probe-verified to make two negative assertions load-bearing) and R10-10 (pins the load-bearing cross-package authorization-key literal twin).

Review body (CHANGES_REQUESTED, @qwen-code-ci-bot)

The review's actionable substance was the Critical R11-1, fixed above. The remaining body is reviewer coverage disclosure (integration suite skipped in CI, reverse-audit round cap reached), not code findings requiring changes. Integration tests were not run this round: the touched behavior (the refresh-append allowlist) is exercised by the acp-bridge unit suites above, not exclusively through the bundled CLI / integration harness.

Verification

  • npm run build — passed
  • npm run typecheck — passed
  • npm run lint — passed
  • packages/acp-bridge npx vitest run — 28 files, 1427 passed (includes the 16 new parameterized refresh-append cases; run again after restoring the mutation: green)
  • packages/cli npx vitest run src/acp-integration/session/Session.test.ts src/acp-integration/acpAgent.test.ts src/commands/channel/channel-prompt-wire-key.test.ts — 3 files, 1015 passed
  • packages/cli npx vitest run src/serve/server.test.ts src/commands/channel/daemon-worker.test.ts — 2 files, 1025 passed
  • packages/web-shell npx vitest run client/App.test.tsx — 440 passed
  • packages/channels/base npx vitest run — 19 files, 1030 passed
  • packages/sdk-typescript npx vitest run test/unit — 31 files, 1528 passed
  • Mutation check on the fix (temporary): removed git_status_changed from the allowlist → the matching new test case failed (1 failed | 638 skipped), confirming the regression test is load-bearing; restored the entry and re-ran green.
  • Settings source unchanged → npm run generate:settings-schema not applicable.

Commits this round: one merge of origin/main (conflicts resolved as described) + fix(acp): cover idle workspace fan-out events in the refresh-append allowlist (#8853).

中文说明

Autofix 审查轮次总结 — PR #8853

冲突解决(--conflict true

已合并 origin/main(21 个提交,至 c9cb53398d)。两处内容冲突,均源于本 PR 与 main 的同一功能性碰撞:

  • packages/acp-bridge/src/compactionEngine.tspackages/acp-bridge/src/eventBus.ts:本 PR 新增了低开销的 liveJournalSnapshot() 拆分(供刷新追加分守卫使用的仅日志快照);main 独立新增了 liveReplayMode'full' | 'summary')双日志设计。解决方式为两者合并:snapshot(liveReplayMode) 现委托给 liveJournalSnapshot(liveReplayMode),后者按模式选择 full/summary 日志及其 recordId 锚点;CompactionEngine 接口与 EventBus 透传方法新增可选模式参数(默认 'full',在两个现有调用点保持本 PR 的守卫语义)。

已通过完整 acp-bridge 测试套件(同时覆盖 summary 模式回放与刷新追加分守卫)、build、typecheck 与 lint 验证。

反馈处置

已修复 — Critical

  • R11-1(rc:3780540805)REFRESH_APPEND_BOOKKEEPING_EVENT_TYPES 遗漏了空闲可达的工作区事件类型,导致循环保护 turn_error 挂起期间一旦发生空闲活动(切换工具、编辑记忆、后台 git_status_changed、设备流等),页面刷新会从刷新后的转录中悄悄丢弃该终止事件。已对照代码核实:审计了每一处 publishWorkspaceEvent / broadcastWorkspaceEvent 调用点及所有直接的会话总线发布;缺失集合恰为点名的 11 个字面量,外加封闭 DeviceFlowEventEmission 联合类型的 5 个成员(auth_device_flow_started/throttled/authorized/failed/cancelled)。修复:将全部 16 个类型加入允许清单,并附简短审计说明。回归覆盖:在既有参数化测试族 keeps the turn error on refresh when %s bookkeeping lands after it 中补入全部 16 个类型,均在循环终止事件之后经由真实的工作区扇出路径(bridge.publishWorkspaceEvent)发布,并断言刷新后的 replay 仍以锚定在持久化页上的 turn_error 结尾。已做变异验证:移除任一允许清单条目会使对应用例失败;恢复后转绿。刻意未加入:session_rewound(回退会改变回放基线,必须继续阻止追加)、session_died / session_closed(终止/条目移除状态,非空闲簿记)。评审建议的替代方案(用正向"轮次内容"谓词替换允许清单)属于更大的重新设计,超出 Critical-only 轮次的范围。

延后 — 18 条自动评审建议(Critical-only 增长刹车)

确定性刹车已激活:已完成 5 个产生改动的轮次,且本 PR 测试 diff 已超出本计数窗口的增长预算(净增测试 +1036 行,预算 400)。按 AGENTS.md,PR 超过约 5 轮审查后仅合入 Critical 修复;其余建议均已在各自线程中记录延后原因(见逐线程回复),确保不被悄悄丢弃。18 条均为测试加固建议,均未声称存在当前生产缺陷:

  • R10-5(rc:3780540811)、R10-6(rc:3780540814)、R10-7(rc:3780540819)、R10-8(rc:3780540822)、R10-9(rc:3780540824)、R10-10(rc:3780540825)、R11-2(rc:3780540831)、R11-3(rc:3780540836)、R11-7(rc:3780540842)、R11-8(rc:3780540843)、R11-9(rc:3780540848)、R11-10(rc:3780540849)、R11-11(rc:3780540853)、R11-12(rc:3780540858)、R11-13(rc:3780540860)、R11-16(rc:3780540865)、R11-19 第 1 处(rc:3780540866)、R11-19 第 2 处(rc:3780540868)。

建议的后续跟进优先项(已在对应线程回复中标注):R11-3(成本低,且经探针验证能使两个否定断言承重)与 R10-10(钉住承重的跨包授权键字面量孪生)。

评审主体(CHANGES_REQUESTED,@qwen-code-ci-bot

该评审的可执行实质内容为 Critical R11-1,已在上方修复。其余主体为评审覆盖范围披露(集成测试套件在 CI 中被跳过、反向审计达到轮次上限),并非需要改动的代码发现。本轮未运行集成测试:所触及的行为(刷新追加允许清单)由上述 acp-bridge 单元测试套件覆盖,并非仅经由打包 CLI / 集成测试框架行使。

验证

  • npm run build — 通过
  • npm run typecheck — 通过
  • npm run lint — 通过
  • packages/acp-bridge npx vitest run — 28 个文件,1427 通过(含 16 个新的参数化刷新追加用例;变异恢复后重跑:绿色)
  • packages/cli npx vitest run src/acp-integration/session/Session.test.ts src/acp-integration/acpAgent.test.ts src/commands/channel/channel-prompt-wire-key.test.ts — 3 个文件,1015 通过
  • packages/cli npx vitest run src/serve/server.test.ts src/commands/channel/daemon-worker.test.ts — 2 个文件,1025 通过
  • packages/web-shell npx vitest run client/App.test.tsx — 440 通过
  • packages/channels/base npx vitest run — 19 个文件,1030 通过
  • packages/sdk-typescript npx vitest run test/unit — 31 个文件,1528 通过
  • 针对本修复的变异检查(临时):从允许清单移除 git_status_changed → 对应新测试用例失败(1 failed | 638 skipped),确认回归测试承重;恢复该条目后重跑转绿。
  • 未改动 settings 源 → npm run generate:settings-schema 不适用。

本轮提交:一次 origin/main 合并(冲突按上述方式解决)+ fix(acp): cover idle workspace fan-out events in the refresh-append allowlist (#8853)

Deferred non-Critical feedback

Critical-only mode is active: 5 change-producing rounds are complete and the PR's diff grew src 95 / test 1036 net lines beyond this counting window's baseline (budgets: 400/400). The workflow excluded the non-Critical feedback below from this round's actionable sections; the items remain open for human follow-up. Maintainer feedback is deferred only after its author has used 2 regular feedback batches in this window's Critical-only tail; authors at that budget, if any, are named below. (@qwen-code /retry starts a fresh counting window.)

中文说明

已进入仅处理 Critical 的模式:已完成 5 个产生改动的轮次,且本计数窗口内 diff 净增长已达 源码 95 / 测试 1036 行(预算 400/400)。本轮可执行区域已排除下方非 Critical 反馈;这些条目保持开放,留待人工跟进。维护者反馈仅在其本人于本窗口 Critical-only 阶段已使用 2 批常规反馈预算后才会延后;达到预算的作者(如有)在下方点名。(评论 @qwen-code /retry 可开启新的计数窗口。)

Base-conflict check · 基分支冲突检查: conflicted with main — resolved in this push. · 与 main 有冲突——已在本次推送中解决。

Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. · 有空请复审;第 100 轮后本 bot 停止并将 PR 交给人工。


🧠 Handled by Qwen Code · model/模型 qwen3.8-max

…tection-error

# Conflicts:
#	packages/cli/src/acp-integration/session/Session.ts
@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 Addressed the latest review feedback (round 7/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 7/100 轮)。改动内容与我反驳保留之处如下:

Autofix review-response summary (PR #8853)

Feedback triage

All actionable sections of this round's feedback were empty: no reviews, no inline comments, no issue-level comments, no failed checks, and no still-red checks. The Deferred non-Critical feedback section records that Critical-only mode engaged (five change-producing rounds completed; net growth of src 118 / test 1073 lines beyond the counting window's budgets), but it lists no deferred items. Per the workflow rules it was treated as an audit record only — no code changes, thread resolutions, or comment replies were made for it.

Conflict resolution (--conflict true)

Merged origin/main, which advanced by two commits since the branch's last merge: feat(telemetry): Trace main agent invocations (#9107) and feat(daemon): track background shells in activeWork (#9042). Thirty-seven files merged cleanly; one file conflicted: packages/cli/src/acp-integration/session/Session.ts (7 hunks).

Both sides modified the same prompt/capture plumbing for different reasons, so each hunk was resolved by keeping both intents rather than taking one side:

  • Main renamed the response-capture plumbing: the optional channelDeliveryCapture?: ChannelDeliveryCapture became a mandatory responseCapture: AgentResponseCapture that additionally carries an AgentOutputMessageCapture for the new main-agent invocation tracing. The resolution adopts main's type and naming at every renamed declaration, signature, and call site.
  • This PR threads the loop-detection rejection path (rejectOnLoopDetected, channelTurn, and the channelPromptTurn classification) through that same chain. The resolution keeps those additions alongside main's rename: rejectOnLoopDetected follows responseCapture in #handleStopHookLoop and the #runStopContinuation options type, and is passed next to responseCapture at all four call sites.
  • In sendPrompt, main's responseCapture construction is kept and the PR's channelPromptTurn computation plus its rationale comment are preserved immediately after it, since the rejection gate and guard-mode selection below consume it.

Post-merge verification of the resolution: the diff of the merged file against origin/main's version is exactly this PR's loop-detection changes adapted to the new capture naming — nothing from either side was dropped, and no conflict markers remain. #executePrompt/#executePromptInner signatures (which git auto-merged) were audited and are consistent: responseCapture: AgentResponseCapture, rejectOnLoopDetected = false, goalTurn, channelTurn = false.

No feedback-driven code changes were made this round; the only commit is the merge itself.

Verification

  • npm run build — passed (fresh dist/ artifacts confirmed)
  • npm run typecheck — passed (all packages)
  • npm run lint — passed
  • vitest run in packages/cli (Session.test.ts, Session.review-lease.test.ts, Session.worktree.test.ts) — 627 passed
  • vitest run in packages/cli (acpAgent.test.ts, active-work-reporter.test.ts, serve/server.test.ts) — 1359 passed, 1 skipped
  • vitest run in packages/cli (commands/channel/channel-prompt-wire-key.test.ts) — 1 passed
  • vitest run in packages/acp-bridge (bridge.test.ts) — 641 passed
  • vitest run in packages/core (services/backgroundShellRegistry.test.ts) — 61 passed
  • vitest run in packages/web-shell (App.test.tsx, adapters/transcriptToMessages.test.ts, hooks/useMessages.test.ts) — 575 passed
  • vitest run in packages/channels/base (DaemonChannelBridge.test.ts) — 60 passed
  • Integration tests were not run: the merged behavior is fully exercised by the unit suites above, not only through the bundled CLI or integration harness.
中文说明

Autofix 评审响应总结(PR #8853

反馈分类

本轮反馈的所有可执行区域均为空:没有评审意见、没有行内评论、没有 issue 级评论、没有失败的检查,也没有持续失败的检查。Deferred non-Critical feedback(延后的非 Critical 反馈)区域记录了 Critical-only(仅处理 Critical)模式已触发(已完成 5 个产生改动的轮次;计数窗口内净增长达源码 118 / 测试 1073 行,超出预算),但该区域未列出任何被延后的条目。按照工作流规则,它仅作为审计记录处理——未针对它做任何代码改动、线程解决或评论回复。

冲突解决(--conflict true

已合并 origin/main。自本分支上次合并以来 main 前进了两个提交:feat(telemetry): Trace main agent invocations (#9107)feat(daemon): track background shells in activeWork (#9042)。37 个文件自动合并成功;1 个文件存在冲突:packages/cli/src/acp-integration/session/Session.ts(7 个冲突块)。

双方出于不同目的修改了同一套 prompt/capture 管线,因此每个冲突块都按"同时保留双方意图"解决,而不是简单取某一边:

  • main 重命名了响应捕获管线:可选的 channelDeliveryCapture?: ChannelDeliveryCapture 变为必选的 responseCapture: AgentResponseCapture,后者额外携带用于新增主 agent 调用追踪的 AgentOutputMessageCapture。解决方案在所有被重命名的声明、签名和调用点采用了 main 的类型与命名。
  • 本 PR 在同一链路上穿入了循环检测拒绝路径(rejectOnLoopDetectedchannelTurn 以及 channelPromptTurn 分类)。解决方案在 main 重命名的同时保留了这些新增内容:在 #handleStopHookLoop#runStopContinuation 的 options 类型中,rejectOnLoopDetected 紧跟在 responseCapture 之后;在全部 4 个调用点,它与 responseCapture 相邻传递。
  • sendPrompt 中,保留了 main 的 responseCapture 构造,并在其后紧接保留本 PR 的 channelPromptTurn 计算及其说明注释,因为下方的拒绝门控与守卫模式选择会使用它。

解决结果的合并后验证:合并后文件相对 origin/main 版本的差异恰好是本 PR 的循环检测改动(适配了新的捕获命名)——双方的内容均未丢失,且没有残留任何冲突标记。#executePrompt/#executePromptInner 的签名(由 git 自动合并)已审计且一致:responseCapture: AgentResponseCapturerejectOnLoopDetected = falsegoalTurnchannelTurn = false

本轮没有做任何由反馈驱动的代码改动;唯一的提交就是该合并本身。

验证

  • npm run build — 通过(已确认生成新的 dist/ 产物)
  • npm run typecheck — 通过(所有包)
  • npm run lint — 通过
  • packages/cli 中运行 vitest run(Session.test.ts、Session.review-lease.test.ts、Session.worktree.test.ts)— 627 个通过
  • packages/cli 中运行 vitest run(acpAgent.test.ts、active-work-reporter.test.ts、serve/server.test.ts)— 1359 个通过,1 个跳过
  • packages/cli 中运行 vitest run(commands/channel/channel-prompt-wire-key.test.ts)— 1 个通过
  • packages/acp-bridge 中运行 vitest run(bridge.test.ts)— 641 个通过
  • packages/core 中运行 vitest run(services/backgroundShellRegistry.test.ts)— 61 个通过
  • packages/web-shell 中运行 vitest run(App.test.tsx、adapters/transcriptToMessages.test.ts、hooks/useMessages.test.ts)— 575 个通过
  • packages/channels/base 中运行 vitest run(DaemonChannelBridge.test.ts)— 60 个通过
  • 未运行集成测试:上述单元测试套件已完整覆盖合并后的行为,该行为并非只能通过打包后的 CLI 或集成测试框架验证。

Deferred non-Critical feedback

Critical-only mode is active: 5 change-producing rounds are complete and the PR's diff grew src 118 / test 1073 net lines beyond this counting window's baseline (budgets: 400/400). The workflow excluded the non-Critical feedback below from this round's actionable sections; the items remain open for human follow-up. Maintainer feedback is deferred only after its author has used 2 regular feedback batches in this window's Critical-only tail; authors at that budget, if any, are named below. (@qwen-code /retry starts a fresh counting window.)

中文说明

已进入仅处理 Critical 的模式:已完成 5 个产生改动的轮次,且本计数窗口内 diff 净增长已达 源码 118 / 测试 1073 行(预算 400/400)。本轮可执行区域已排除下方非 Critical 反馈;这些条目保持开放,留待人工跟进。维护者反馈仅在其本人于本窗口 Critical-only 阶段已使用 2 批常规反馈预算后才会延后;达到预算的作者(如有)在下方点名。(评论 @qwen-code /retry 可开启新的计数窗口。)

Base-conflict check · 基分支冲突检查: conflicted with main — resolved in this push. · 与 main 有冲突——已在本次推送中解决。

Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. · 有空请复审;第 100 轮后本 bot 停止并将 PR 交给人工。


🧠 Handled by Qwen Code · model/模型 qwen3.8-max

@chiga0 chiga0 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 Review — PR #8853

PR type: Bug Fix + Feature (surface loop-detection as structured turn_error)
Review approach: Two-phase (blind Phase 1 → cross-validate with existing 100 comments across 9 bot rounds + wenshao's review)


Summary

The core change is sound: converting a silent loop-detection stop into a structured turn_error with errorKind: 'loop_detected' is strictly better for users than the previous behaviour (turn completes silently while tools are skipped). The trust chain for CHANNEL_PROMPT_META_KEY — strip at ACP boundary → re-arm only for trusted parents → re-inject via bridge context — is correctly implemented end-to-end. The refresh-persistence model (turnErrorEvent + liveJournalSnapshot guard + REFRESH_APPEND_BOOKKEEPING_EVENT_TYPES) is well-designed in principle, though the guard's correctness depends on the allowlist staying in sync with the event-bus vocabulary.


Cross-Validation

Finding Existing Review My Assessment
REFRESH_APPEND_BOOKKEEPING_EVENT_TYPES under-inclusive for idle session events R4-6 / R6-1 / R8-4 / R9-5 (Critical) Confirmed — the set is hard to keep exhaustive; any new idle event type omitted silently breaks loop-error replay after refresh. Allowlist approach is fragile without an automated completeness test.
broadcastTurnComplete clears turnErrorEvent unconditionally on queued terminals R8-3 (Critical, ~line 1540) Not confirmed at HEADbroadcastTurnComplete now gates the clear on mutateTurnState && published !== undefined; the earlier unconditional clear was addressed.
isRefreshAppendTurnContent treats synthetic history_truncated marker as turn content R9-3 (Critical) Not confirmed at HEAD — the function's first statement is if (event.type === 'history_truncated') return false;; the marker is correctly excluded from the turn-content test.
Goal turns incorrectly get rejectOnLoopDetected = true R8-1 (Critical) Not confirmed at HEADrejectOnLoopDetected = !channelPromptTurn && goalTurn === undefined; goal-turn continuations pass goalTurn as a non-undefined value, so the flag defaults to false for them; #runStopContinuation is guarded by the same flag (options.rejectOnLoopDetected ? cancelledOrThrowLoopDetected : getAbortAwareEndTurnStopReason).
App.tsx retryableTurnErrorIdRef / lastTurnErrorIdRef scan discrepancy on background-notification user blocks R9-4 (Critical) Plausible — the lastTurnErrorIdRef walk in the streaming→idle effect uses a different scan than getRetryableTurnError; requires deeper App.tsx knowledge to confirm definitively.
qwen.daemon.channelDelivery untrusted pass-through R9-1 (Critical) Plausible — the acpAgent.ts diff strips only CHANNEL_PROMPT_META_KEY; DAEMON_CHANNEL_DELIVERY_META_KEY stripping depends on pre-existing code not visible in the diff.
Queued-prompt deadline terminal resets compaction journal while turnErrorEvent is intact R9-6 (Critical) PlausiblepublishPromptTerminal's queued-guard checks the live journal for newer turn content, but a deadline expiry fires its terminal boundary which resets the journal before the check can see any post-error content.
Newerror.body !== 'LOOP_DETECTED' guard in App.tsx is likely dead code New finding (see inline comment) — the bridge now intercepts structured turn errors before the HTTP response reaches the web-shell, so the isDaemonTurnError path is unreachable for loop_detected. Even if reachable, error.body is an object, not the string 'LOOP_DETECTED'.

Additional Independent Audit Coverage

  • extractJsonRpcErrorField vs extractJsonRpcErrorDetail: both helper functions are correct and non-overlapping (detail reads data.message, field reads arbitrary data[key]). The code-gating note (R1-4) about preferring structured code only when structuredErrorKind !== undefined is a valid suggestion.
  • loopType serialization chain: LoopType enum → RequestError.data.loopType (string) → extractJsonRpcErrorField(err, 'loopType')turn_error.data.loopTypeDaemonTurnErrorData.loopType?: string (SDK). Chain is complete and type-safe.
  • cancelledOrThrowLoopDetected / createLoopDetectedTurnError integration: the RequestError(-32603) shape is correctly consumed by extractJsonRpcErrorField in the bridge, which checks err.data[field]. No JSON-RPC framing mismatch.
  • Test coverage quality: Session.test.ts and bridge.test.ts add meaningful new tests. The loop-rejection recovery test (send a follow-up turn after loop-stop) is particularly valuable for proving liveness. Gaps noted by existing reviewers (notification-queue drain path, queued-terminal clear path, end-to-end errorKind propagation through transcript-to-messages) are real.

Overall, several Critical findings from earlier rounds appear addressed at HEAD; the remaining open Criticals centre on (a) allowlist completeness, (b) the turnErrorId scan in App.tsx, and (c) the queued-terminal/compaction-reset interaction.

// retried turn itself was stopped for loop protection, so
// the stashed prompt must not be re-offered — resubmitting
// it tends to re-loop.
if (error.body !== 'LOOP_DETECTED') {

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.

[Nit — likely dead code] error.body !== 'LOOP_DETECTED' compares error.body against a plain string, but the bridge now intercepts structured turn errors before they become HTTP errors: when extractJsonRpcErrorField(err, 'errorKind') returns a truthy value, bridge.ts calls cancelPendingForSession(sessionId) and returns early — no error response is forwarded to the web-shell client, so sendPromptFn never throws a DaemonTurnError for loop_detected. Even in an edge case where this path were reached, error.body would be an object (the parsed JSON-RPC error body), not the literal string 'LOOP_DETECTED', so the guard would always evaluate to true and failedTurnErrorRetryRef.current would always be stashed.

In practice the retry stash being set doesn't matter here because the same effect already excludes loop_detected from retryableTurnError via isRetryableTurnErrorKind, so the UI correctly suppresses the retry affordance either way. But the dead guard may mislead future readers into believing the stash is conditionally cleared. Consider replacing with a comment explaining that loop-detected errors are unreachable on this path post-bridge-interception, or moving the guard to wherever failedTurnErrorRetryRef is read if defence-in-depth is desired.

@chiga0 chiga0 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.

After independent re-verification of the R9 Critical findings at HEAD (f291d235):

  • R9-1 (channelDelivery untrusted pass-through): FixedacpAgent.ts now extracts, deletes, and re-gates both CHANNEL_PROMPT_META_KEY and DAEMON_CHANNEL_DELIVERY_META_KEY behind privateParentState === "trusted".
  • R9-2 (daemon trust bypass): Low risk — the trusted-parent gate is a high-privilege boundary; the current defence-in-depth at the bridge + acpAgent layers is sufficient.
  • R9-3 (history_truncated treated as turn content): Not an issueisRefreshAppendTurnContent opens with if (event.type === "history_truncated") return false.
  • R9-4 (lastTurnErrorIdRef scan inconsistency): Not an issue — both retryableTurnError and lastTurnError derive from the same getRetryableTurnError(blocks) call; they are consistent.
  • R9-5 (idle session_update subtypes unhandled): Not an issueisIdleBookkeepingSessionUpdate explicitly handles available_commands_update and current_mode_update.
  • R9-6 (queued terminal/compaction timing): journal check in publishPromptTerminal runs before the broadcast that folds the journal, so the ordering is safe.

Core design is sound. Approving.

@ytahdn

ytahdn commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator Author

@qwen-code /verify

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

Sandboxed verification: ✅ passed — merge-ready (agent verdict) - workflow run

Ran the PR in an isolated, token-free container: A/B against the base build, mock-free harness assertions, targeted gates. Advisory evidence for human reviewers — not a review, an approval, or a CI check.

Scripted assertions: 3262 passed · 0 failed · 3262 total

中文 — 判定:✅ 通过 · 可合入(agent 判定)

沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查

脚本断言:3262 通过 · 0 失败 · 3262 总计

Verification report

PR 8853 — fix(web-shell): surface loop detection turn errors

Verdict: merge-ready — 3262/3262 scripted assertions passed (0 unexpected failures). Verified head: f291d2356adfc8136a3923d421ef18738d5fb3a7 (merge of PR into base 53a7f2fd1bd439f16be3269b4945460628d2a39b).

中文摘要
  • 结论: merge-ready。3262 项脚本化断言全部通过,0 项意外失败。
  • A/B 结论: 中心行为(load-protection 停止前台轮次时以结构化 turn_error{code: LOOP_DETECTED, errorKind: loop_detected} 拒绝,而非 base 上的 end_turn 正常结束)在 base 与 head 之间精确翻转:4 个拒绝型用例在 base 上以 promise resolved "{ stopReason: 'end_turn' }" instead of rejecting 失败、在 head 上通过;2 个非歧视性正对照在两侧均通过(见下表及 01-*/02-* 截图)。8 个新引入守卫的变异矩阵 8/8 被各自的钉住测试杀死,且均死于预期断言(见 03-*)。
  • Findings: 无阻断项。两条非阻断观察:serve 路由未从转发 _meta 剥离 qwen.daemon.channelDelivery(下游 bridge 准入剥离与 acpAgent 边界剥离已闭合该路径,且 delivery 不再参与轮次分类,无行为影响);refresh-append 白名单未包含 session_died/session_rewound/session_branched/session_closed 等生命周期终止事件,它们会阻断追加——这是 fail-closed 设计,会话已不再空闲/存活,无害。
  • 未覆盖: 逐 commit 归因(shallow clone 仅可达 PR head 一个 commit,快照列 17 个);真实模型/浏览器级 Web Shell 复现(仅 jsdom 单元层);Windows/macOS;全仓库门禁(仅受影响 workspace);非 live 会话的持久化历史 turn_error。

Central claim + A/B

Central claim: a foreground ACP turn stopped by tool-loop protection now rejects with a structured turn error (RequestError(-32603, …, {code:'LOOP_DETECTED', errorKind:'loop_detected', loopType})) that the bridge publishes as turn_error, instead of completing end_turn gracefully as on base; non-interactive turns (cron, background-notification, channel-classified, goal) keep the graceful handling, and cancellation wins the race.

A/B cells (same 6 tests, identical harness, two trees — head repo vs tmp/base-tree at HEAD^1; the base tree ran the head Session.test.ts with the single new export LOOP_DETECTED_TURN_ERROR_MESSAGE inlined as its literal, the only head-only import):

cell base (53a7f2fd) head (f291d235) oracle
stops an ACP prompt after repeated invalid tool parameters with fresh ids REDpromise resolved "{ stopReason: 'end_turn' }" instead of rejecting GREEN session.prompt() rejection with LOOP_DETECTED/loop_detected/loopType
rejects a delivery-marked turn when loop protection stops it RED (same behavioral mismatch) GREEN delivery meta alone must not exempt the turn
drains cron work queued mid-turn when the turn rejects on loop protection RED (cron drain spy called 1×, expected 2×) GREEN rejection path keeps the pre-existing drain invariant
rejects a foreground turn whose Stop continuation trips loop protection RED (resolved instead of rejecting) GREEN stop-hook continuation chain also rejects
stops a turn on repeated identical calls below the cap (positive control) GREEN GREEN detection itself is pre-existing; must pass on both arms
keeps a Goal turn graceful when loop protection stops it (positive control) GREEN GREEN goal exemption must hold on both arms

Witnesses: evidence/01-ab-head-six-of-six-green.png (head arm, 6/6 ✓ + Tests 6 passed | 609 skipped (615)), evidence/02-ab-base-four-behavioral-reds.png (base arm, 4 × with the exact end_turn instead of rejecting assertion + 4 failed | 2 passed). The two green-on-base controls prove the base harness was live (it could pass), so the four reds are the change, not the harness.

Secondary claim — refresh persistence: the bridge appends the in-memory pending turn_error to the bounded persisted-history replay on refresh, gated by an idle-bookkeeping allowlist. Pinned by 40 bridge tests (37 "keeps the turn error on refresh when …" per-event-type cases + 3 "drops …" cases), all green on head (within the 641-test bridge gate); disabling the append (mutant M6) fails exactly the 37 "keeps" cases and leaves the 3 "drops" green — the oracle also asserts exact replay length (no duplication) and anchors on the persisted page so the in-memory fallback cannot satisfy it.

Secondary claim — trust gating: qwen.channel.prompt and qwen.daemon.channelDelivery are stripped from untrusted callers at both boundaries (daemon prompt route, standalone ACP acpAgent) and re-injected only from trusted context (worker authorization validated in the route; private-parent state in acpAgent). Audited all producers/consumers of both keys: producers are the authenticated channel bridges and the two trusted re-injection sites; consumers are Session.prompt classification (channel key only) and delivery scheduling (delivery key only — and delivery no longer classifies). Mutant M7 (drop the acpAgent strip) is killed by strips channel classification from untrusted callers; the route-side forgery case is pinned by strips spoofed channel-prompt classification and injects only trusted context (bridge gate) and the route's own worker-authorization check (isChannelWorkerPromptAuthorized binds a daemon-generated bearer token to the workspace cwd).

Findings

No blocking findings. Two non-blocking observations:

  1. Serve route does not strip qwen.daemon.channelDelivery from forwarded _meta (packages/cli/src/serve/routes/session.ts strips its three sibling keys but not this one). Verified closed anyway: the bridge admission strip (bridge.ts sendPrompt) deletes it before forwarding, the standalone ACP boundary deletes and trust-gates it, and — the load-bearing part of this PR — the delivery meta no longer participates in loop-protection classification (Session.ts classifies on CHANNEL_PROMPT_META_KEY only, pinned by the "rejects a delivery-marked turn" cell). No behavioral consequence; noted for symmetry with the other three keys.
  2. Lifecycle terminals are not in the refresh-append allowlist (session_died, session_rewound, session_branched, session_closed, session_snapshot): they therefore block the append (fail-closed). Audited the full idle vocabulary instead: all 12 workspace fan-out types (tool_toggledgithub_setup_completed), all 5 auth_device_flow_* types, the session bookkeeping types, and the idle session_update subtypes (user-shell source, available_commands_update, current_mode_update) are covered — confirmed by the per-type bridge tests and by my enumeration of every publishWorkspaceEvent producer. Blocking on lifecycle terminals is harmless (the session is no longer idle/live in those states); flagging only because the allowlist's own comment demands an audit of the vocabulary.

Mutation matrix (vacuity + guard pinning)

Each mutant applied in a scratch worktree at the same merge head, run against its pinning tests, then restored. Expected outcome = KILLED (vitest red on the intended assertion). Positive controls: the unmutated head run is green (6/6 A/B head cell; 3242-test gates), so a kill is meaningful.

# guard mutated mutation result died on
M1 foreground rejection gate !channelPromptTurn && goalTurn === undefinedfalse KILLED (4 red) promise resolved "{ stopReason: 'end_turn' }" instead of rejecting ×4
M2 channel-turn exemption gate → goalTurn === undefined KILLED channel turn rejected … instead of resolving
M3 goal-turn exemption gate → !channelPromptTurn KILLED goal turn rejected instead of graceful
M4 drain-on-rejection drop rejectedByLoopProtection || KILLED cron drain spy 1× instead of 2×
M5 cancellation precedence if (signal.aborted) return 'cancelled'if (false) KILLED cancelled turn rejected with loop error instead of resolving
M6 bridge refresh-append if (!hasNewerTurnContent)if (false) KILLED (37 red / 3 green) all "keeps the turn error on refresh" cases; "drops" cases correctly stay green
M7 acpAgent untrusted strip drop both delete meta[…] lines KILLED forwarded meta retained forged channel keys
M8 web-shell retry suppression isRetryableTurnErrorKindtrue KILLED retry button rendered (expected <button> to be null)

8/8 killed, zero survivors, zero false kills (every failure message quoted above is the behavioral assertion the test exists for, not a collection/import error — the first matrix run was invalidated by a worktree node_modules resolution gap and re-run after symlinking the package-local third-party dep dirs; see Methodology). Witness: evidence/03-mutation-matrix-eight-of-eight-killed.png. M1 doubles as the vacuity proof: reverting the central hunk turns the four central tests red on the intended assertion.

Targeted gates (head)

suite result
packages/acp-bridge bridge.test.ts 641 passed
packages/cli Session.test.ts 615 passed (solo rerun; first run's exit=1 was a coverage-.tmp race between my two concurrent CLI vitest runs, all 615 tests green in it too)
packages/cli acpAgent.test.ts + server.test.ts 1350 passed (408 + 942)
packages/cli channel-prompt-wire-key.test.ts 1 passed
packages/web-shell App.test.tsx + transcriptToMessages.test.ts + useMessages.test.ts 575 passed
packages/channels/base DaemonChannelBridge.test.ts 60 passed

Total 3242/3242. The mutation matrix (8 expected kills) and the base-arm expectation (4 expected reds) are encoded as passing assertions in assertions.json (3242 + 6 head cells + 6 base-arm checks + 8 matrix kills = 3262).

Not covered

  • Per-commit attribution: the checkout is depth-2; git rev-list HEAD^1..HEAD^2 reaches only the PR head commit while the snapshot lists 17 commits. The aggregate HEAD^1..HEAD diff is what was verified; per-commit claims (e.g. the round-3/round-4 hardening commits) were not individually exercised.
  • Real-model / browser E2E: loop trips were driven through the real Session/bridge code paths with deterministic fake model clients (the PR's own harness), and Web Shell behavior at the jsdom unit level. A live-model browser reproduction is nondeterministic by the PR's own admission and was not attempted.
  • Windows/macOS: not tested (matches the PR's tested-on table).
  • Repo-wide gates: only affected workspaces were run; npm run build had already completed at HEAD in CI (tsc typecheck included).
  • Non-live sessions: persisted historical turn errors for archived sessions are unchanged by design and untested here.
  • TUI: no terminal-UI surface changed; no TUI capture applicable.

Methodology

Environment: CI verify container (node 22, bookworm), tree at refs/pull/8853/merge (HEAD 8629695eba, base 53a7f2fd1b, PR head f291d2356a). Harnesses drove compiled-from-source TS via the packages' vitest configs, whose resolve.alias maps @qwen-code/* to the worktree's own sources — verified, so the base/mutant trees genuinely ran base/mutated code. Scratch worktrees under tmp/ resolved third-party deps by walking up to the root node_modules; the two package-local dep dirs that matter (packages/core/node_modules ajv-v8, packages/cli/node_modules, packages/web-shell/node_modules) were symlinked in (lockfile untouched by the PR; contents identical base vs head). Realpath assertion: @qwen-code/qwen-code-core/channel-base root symlinks point into the head tree, but the PR's diff touches neither packages/core nor the lockfile, and vitest aliases bypass them for the code under test. The base cell ran the head Session.test.ts with the one new export inlined; -t filters selected the six cells. Raw logs, harness scripts, and per-mutant logs are in logs/ and harness/ beside this report; the three PNGs in evidence/ were produced with scripts/verify-capture.mjs from live re-runs (02's compact view greps the live run's outcome lines after ANSI stripping).

Evidence images

01-ab-head-six-of-six-green

02-ab-base-four-behavioral-reds

03-mutation-matrix-eight-of-eight-killed

Harness scripts and raw logs are in the workflow run artifacts (7-day retention).

Qwen Code · sandboxed verification

@yiliang114 yiliang114 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.

Approved at f291d23 after walking the full daemon→bridge→client chain and the prior R1–R9 findings.

Correctness (verified in diff)

  • Loop stops reject only interactive foreground turns: gate is !channelPromptTurn && goalTurn === undefined in Session.prompt, with rejectOnLoopDetected=false defaults on the cron/notification call sites. Goal turns, channel turns, cron and background notifications keep graceful end_turn handling (Session.ts + new tests for each, incl. repeated-failure-guard goal stop).
  • Error kind/code preserved end-to-end: RequestError(-32603, …, {code:'LOOP_DETECTED', errorKind:'loop_detected', loopType})broadcastTurnError now prefers structured data.errorKind/data.code for ANY structured error (not just loop_detected) → SDK DAEMON_ERROR_KINDS gains loop_detected and DaemonTurnErrorData.loopType → web-shell renders from block.errorKind. Older daemons without errorKind fall back to the raw message with no crash.
  • Channel classification is trusted-only on all three layers: acpAgent strips qwen.channel.prompt and qwen.daemon.channelDelivery from untrusted callers and re-injects only for trusted parents; the daemon prompt route re-derives channelPrompt from authenticated worker authorization; bridge sendPrompt strips and re-injects from the trusted context flag. Delivery meta alone no longer opts a turn out of rejection (closes R6-2/R9-1/R9-2).
  • Cancel-wins race preserved via cancelledOrThrowLoopDetected after #preserveStoppedToolRun, so failed tool cards survive; loop rejection drains cron/notification queues in prompt()'s finally so no queued automatic work is stranded.
  • Refresh/replay: turnErrorEvent replay uses the cheap in-flight liveJournalSnapshot() (no replay-window flatten); the allowlist covers prompt-queue bookkeeping, config events, user-shell streams, available_commands_update/current_mode_update, workspace fan-out, device-flow union, and the synthetic history_truncated marker. Queued terminals no longer clear/set turn state (mutateTurnState gating) and a queued boundary folding newer content drops the stale record before the fold. Admission and new terminals clear turnErrorEvent, so no duplicate or trailing error cards.

UI/i18n

  • error.loopDetected registered in EN + ZH; loop errors render once with no retry affordance (isRetryableTurnErrorKind is now the single policy source shared by App.tsx and the adapter), retry lineage is cut when the retried turn itself loop-stops, and turn_complete still reports non-retryable errors via lastTurnErrorIdRef.

Tests: matrix pins refresh retention across every idle-event class, stale-drop paths, spoofed-classification stripping, structured-detail preservation, and all graceful/reject turn types.

Nits (non-blocking)

  • P3: REFRESH_APPEND_BOOKKEEPING_EVENT_TYPES must stay in sync with future idle-reachable event types; failure mode is graceful (error drops on refresh, never misplaced) and the audit comment documents it, but a registry-side test asserting every idle bus event type is classified would harden it further.
  • P3: LOOP_DETECTED_TURN_ERROR_MESSAGE remains the fallback text for pre-errorKind clients — fine, just noting it's the only English surface left.

CI on f291d23: all checks green except review-pr itself, which is still in progress (this pipeline).

@ytahdn
ytahdn dismissed stale reviews from qwen-code-ci-bot and wenshao August 14, 2026 06:13

dismiss

@ytahdn
ytahdn added this pull request to the merge queue Aug 14, 2026
Merged via the queue into main with commit 3013420 Aug 14, 2026
89 of 90 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

autofix/takeover Summon the autofix loop to manage this PR (remove to release; needs triage+)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants