Skip to content

fix(acp-bridge): close prompt-terminal follow-ups from the PR #7400 self-review - #7453

Merged
doudouOUC merged 2 commits into
QwenLM:mainfrom
doudouOUC:fix/daemon-prompt-terminal-followup
Jul 22, 2026
Merged

fix(acp-bridge): close prompt-terminal follow-ups from the PR #7400 self-review#7453
doudouOUC merged 2 commits into
QwenLM:mainfrom
doudouOUC:fix/daemon-prompt-terminal-followup

Conversation

@doudouOUC

Copy link
Copy Markdown
Collaborator

What this PR does

Closes the post-merge self-review follow-ups from PR #7400 (the daemon prompt-terminal exactly-once work). Four behavioral fixes plus documentation hardening:

  1. A removed RUNNING prompt no longer loses its terminal. Removing a running prompt via the API now keeps it on the pending list (hidden from the pending-prompts API by a flag, and idempotent on repeat removal) until it actually settles. If the session closes, is killed, or crashes before the agent cooperates with the cancel, the teardown flush can still see the prompt and publishes its terminal before the event bus closes — previously the terminal was published into an already-closed bus and silently dropped, hanging any SSE consumer waiting on that prompt. (fix(acp-bridge): guarantee exactly-once prompt terminal events in daemon serve mode #7400 (comment))
  2. A queued prompt's failure no longer pollutes session-level turn state. The turn-error broadcast now only mutates the session's turnError / retryAllowed when the failing prompt was actually running. A queued prompt's terminal (deadline expiry, teardown flush) publishes the event alone, so the session summary no longer advertises an error for a turn that never ran, and the retry path isn't armed by it. The gate is the prompt's own running state rather than the session's active-prompt id, because on the normal settle path the active-prompt id is already cleared before the terminal publishes — gating on it would misclassify genuine active-turn failures. (fix(acp-bridge): guarantee exactly-once prompt terminal events in daemon serve mode #7400 (comment))
  3. Queued deadline expiry now rejects with the typed error. A prompt whose deadline expires while still queued previously rejected the caller with a generic AbortError; the pre-dispatch abort check now propagates the typed PromptDeadlineExceededError, so queued and running expiry reject identically as the PR fix(acp-bridge): guarantee exactly-once prompt terminal events in daemon serve mode #7400 description promised. (fix(acp-bridge): guarantee exactly-once prompt terminal events in daemon serve mode #7400 (comment))
  4. The prompt-deadline module is a pure leaf again. Dropped the re-export that made a small helper module transitively pull in the entire bridge; the error class is re-exported from the server barrel via the bridge boundary instead. Existing import sites are unchanged. (fix(acp-bridge): guarantee exactly-once prompt terminal events in daemon serve mode #7400 (comment))

Documentation/nit follow-ups in the same pass: the deadline comment now spells out the accepted trade-off that releasing the FIFO lets the next prompt overlap a still-wedged call on the same ACP session (#7400 (comment)); the duplicate-terminal dedup log moved to the debug channel since dedup is the designed steady state, not an anomaly (#7400 (comment)); the teardown-flush doc notes the expected trailing prompt_cancelled after a running prompt's terminal (#7400 (comment)); and the load-bearing registration order of the terminal broadcast before the deferred close now has a do-not-reorder comment (#7400 (comment)).

Why it's needed

These items were flagged in the PR #7400 self-review as "will fix" but the PR merged first. The first three are real behavioral defects: a hung SSE consumer on session teardown after removing a running prompt, a misleading session summary plus an unearned retry arm after a queued failure, and an untyped rejection that makes deadline expiry indistinguishable from user removal for programmatic callers.

Reviewer Test Plan

How to verify

  • Removed-running-prompt terminal: start a prompt against an agent that ignores cancellation, remove it via the pending-prompt DELETE API (observe it disappear from the pending-prompts GET and a repeat DELETE report not-removed), then close the session. Expect exactly one turn_error{code:'session_closed'} terminal for that promptId on the session event stream, ordered before the session_closed frame. Before this fix the terminal never arrived.
  • Queued-failure turn state: let a queued prompt's deadline expire behind a wedged head prompt. Expect the session summary's turnError to stay unset, while the queued prompt still gets its exactly-once deadline terminal and the caller's promise rejects with PromptDeadlineExceededError (previously a generic AbortError).
  • Unit coverage: cd packages/acp-bridge && npx vitest run src/bridge.test.ts — 429 tests pass, including a new removed-running-prompt teardown test and extended queued-deadline assertions covering both fixes above.
  • npm run build and npm run typecheck pass.

Evidence (Before & After)

N/A — daemon/bridge internals, no TUI change. Test output:

 Test Files  1 passed (1)
      Tests  429 passed (429)

Tested on

OS Status
🍏 macOS
🪟 Windows ⚠️
🐧 Linux ⚠️

Environment (optional)

Unit tests via vitest; no sandbox.

Risk & Scope

  • Main risk or tradeoff: a removed running prompt now stays on the internal pending list until it settles. All internal consumers were audited — queue-depth accounting and the stop-guard scan count only queued entries, the pending-prompts API filters the flag, and the settle path splices the entry while skipping the duplicate completed event — but any future code iterating the raw list must be aware removed entries can be present.
  • Not validated / out of scope: reclaiming a wedged agent's channel (tracked separately in PR fix(acp-bridge): guarantee exactly-once prompt terminal events in daemon serve mode #7400); the stale session/update interleave after a deadline release is documented as an accepted trade-off, not changed.
  • Breaking changes / migration notes: none. The queued-expiry rejection type changes from AbortError to PromptDeadlineExceededError, matching the documented contract from PR fix(acp-bridge): guarantee exactly-once prompt terminal events in daemon serve mode #7400.

Linked Issues

Fixes #7451

中文说明

本 PR 做了什么

闭环 PR https://github.com/QwenLM/qwen-code/pull/7400(daemon prompt 终态 exactly-once 工作)合并后 self-review 中标记的待修项。四项行为修复加文档加固:

  1. 被移除的运行中 prompt 不再丢失终态。 通过 API 移除运行中的 prompt 后,该条目现在保留在待处理列表上(通过标志对 pending-prompts API 隐藏,重复移除幂等返回未移除),直到真正结算。如果会话在 agent 配合取消之前关闭、被杀或崩溃,teardown flush 仍能看到该 prompt 并在事件总线关闭前发布其终态——此前终态会被发布进已关闭的总线并被静默丢弃,导致等待该 prompt 的 SSE 消费者永久挂起。
  2. 排队 prompt 的失败不再污染会话级 turn 状态。 turn-error 广播现在仅在失败的 prompt 确实处于运行状态时才修改会话的 turnError / retryAllowed。排队 prompt 的终态(deadline 过期、teardown flush)只发布事件本身,因此会话摘要不再为一个从未运行的 turn 报告错误,retry 路径也不会被它武装。门控使用 prompt 自身的运行状态而非会话的 active-prompt id,因为正常结算路径上 active-prompt id 在终态发布前已被清空——用它做门控会误判真实的活跃 turn 失败。
  3. 排队中 deadline 过期现在以类型化错误拒绝。 此前 deadline 在排队期间过期的 prompt 会以通用 AbortError 拒绝调用方;pre-dispatch abort 检查现在传播类型化的 PromptDeadlineExceededError,使排队与运行中的过期拒绝行为一致,符合 PR fix(acp-bridge): guarantee exactly-once prompt terminal events in daemon serve mode #7400 描述的承诺。
  4. prompt-deadline 模块恢复为纯叶子模块。 删除了使这个小工具模块传递性拉入整个 bridge 的 re-export;错误类改为经由 bridge 边界从 server barrel 导出。现有导入点不变。

同批文档/nit 跟进:deadline 注释现在明确说明释放 FIFO 会让下一个 prompt 与仍然卡死的调用在同一 ACP 会话上重叠这一已接受的权衡;重复终态去重日志移至 debug 通道(去重是设计内的稳态而非异常);teardown-flush 文档注明运行中 prompt 终态帧之后出现尾随 prompt_cancelled 属预期;终态广播先于延迟关闭的注册顺序这一承重不变式现在有 do-not-reorder 注释保护。

为什么需要

这些项在 PR #7400 self-review 中标记为"将修复",但 PR 先合并了。前三项是真实的行为缺陷:移除运行中 prompt 后会话 teardown 时 SSE 消费者挂起、排队失败后会话摘要误导且 retry 被无端武装、无类型拒绝使程序化调用方无法区分 deadline 过期与用户移除。

审阅者测试计划

如何验证

  • 被移除运行中 prompt 的终态:对一个无视取消的 agent 启动 prompt,通过 pending-prompt DELETE API 移除它(观察它从 pending-prompts GET 消失、重复 DELETE 报告未移除),然后关闭会话。预期该 promptId 在会话事件流上恰有一条 turn_error{code:'session_closed'} 终态,且排序在 session_closed 帧之前。修复前该终态永远不会到达。
  • 排队失败的 turn 状态:让排队 prompt 的 deadline 在卡死的队头 prompt 后过期。预期会话摘要的 turnError 保持未设置,同时排队 prompt 仍获得 exactly-once 的 deadline 终态,调用方 promise 以 PromptDeadlineExceededError 拒绝(此前是通用 AbortError)。
  • 单元覆盖:cd packages/acp-bridge && npx vitest run src/bridge.test.ts —— 429 个测试通过,包括新增的移除运行中 prompt 的 teardown 测试与覆盖上述两项修复的扩展排队 deadline 断言。
  • npm run buildnpm run typecheck 通过。

证据(前后对比)

N/A —— daemon/bridge 内部改动,无 TUI 变化。

风险与范围

  • 主要风险或权衡:被移除的运行中 prompt 现在保留在内部待处理列表上直到结算。所有内部消费方已审计——队列深度统计与 stop-guard 扫描只计数排队条目,pending-prompts API 过滤该标志,结算路径在跳过重复 completed 事件的同时移除条目——但未来任何遍历原始列表的代码需注意可能存在已移除条目。
  • 未验证/范围外:回收卡死 agent 的 channel(PR fix(acp-bridge): guarantee exactly-once prompt terminal events in daemon serve mode #7400 中单独跟踪);deadline 释放后的陈旧 session/update 交错已记录为已接受的权衡,未改变。
  • 破坏性变更/迁移说明:无。排队过期的拒绝类型从 AbortError 变为 PromptDeadlineExceededError,与 PR fix(acp-bridge): guarantee exactly-once prompt terminal events in daemon serve mode #7400 记录的契约一致。

关联 Issue

Fixes #7451

…self-review

Keep a removed RUNNING prompt visible to the teardown flush via a removed flag so its terminal still publishes when the session closes before the agent cooperates; gate broadcastTurnError's session turn-state mutation to running prompts; propagate the typed PromptDeadlineExceededError from the pre-dispatch abort check; document the deadline FIFO-release overlap trade-off, the trailing prompt_cancelled after flush, and the result.then/finally ordering invariant; route the dedup log to the debug channel; drop the prompt-deadline re-export that pulled the bridge into a leaf module.

Fixes QwenLM#7451
@doudouOUC
doudouOUC requested a review from wenshao July 21, 2026 16:25
@doudouOUC
doudouOUC enabled auto-merge July 21, 2026 16:25
@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Thanks for the PR!

Template looks good ✓

Problem: observed bug with evidence. Issue #7451 documents three real behavioral defects from the PR #7400 self-review — a removed RUNNING prompt losing its terminal on session teardown (hung SSE consumer), a queued prompt's failure polluting session-level turn state, and an untyped queued deadline rejection. All three are traced to specific code paths with linked review discussions. Not theoretical.

Direction: aligned. These are follow-up fixes to an already-merged PR (#7400) that shipped the prompt-terminal exactly-once guarantees. Closing the gaps identified in self-review is the right thing to do. No auth/sandbox/model-selection/telemetry/public-contract concerns.

Size: cross-package (acp-bridge + cli), 128 production lines (bridge.ts 107, bridgeTypes.ts 7, server.ts 6, prompt-deadline.ts 8) + 59 test lines. Well under any threshold. Not applicable for escalation.

Approach: the scope feels right — four tightly related fixes from the same review pass, each minimal and targeted. The removed flag approach for keeping a running prompt visible to the teardown flush is the simplest path. The mutateTurnState gate on pendingEntry.state === 'running' is clean. The typed error propagation in the pre-dispatch abort check is a one-liner. The re-export move restores prompt-deadline.ts as a pure leaf. No unrelated changes or drive-by refactors.

Moving on to code review. 🔍

中文说明

感谢贡献!

模板完整 ✓

问题:已观测到的 bug,有证据。Issue #7451 记录了 PR #7400 self-review 中的三个真实行为缺陷——被移除的 RUNNING 状态 prompt 在会话 teardown 时丢失终态(SSE 消费者挂起)、排队 prompt 的失败污染会话级 turn 状态、以及无类型的排队 deadline 拒绝。三者均追溯到具体代码路径并附有 review 讨论链接。非理论性问题。

方向:对齐。这些是已合并 PR #7400(prompt 终态 exactly-once 保证)的后续修复。闭环 self-review 中发现的问题是正确做法。无 auth/sandbox/模型选择/遥测/公共契约方面的顾虑。

规模:跨包(acp-bridge + cli),128 行生产代码(bridge.ts 107、bridgeTypes.ts 7、server.ts 6、prompt-deadline.ts 8)+ 59 行测试。远低于任何阈值。无需升级。

方案:范围合理——同一 review 中紧密相关的四项修复,每项都最小且有针对性。用 removed 标志保持运行中 prompt 对 teardown flush 可见是最简路径。mutateTurnStatependingEntry.state === 'running' 做门控很干净。pre-dispatch abort 检查中的类型化错误传播是一行代码。re-export 移动恢复了 prompt-deadline.ts 的纯叶子模块地位。无无关改动或顺手重构。

进入代码审查 🔍

Qwen Code · qwen3.7-max

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

@qwen-code-ci-bot

qwen-code-ci-bot commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator

🩺 serve daemon A/B

Built the PR base vs this PR head 666f6cd, 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-ci-bot

Copy link
Copy Markdown
Collaborator

Code Review

Independent proposal: given the three behavioral gaps (lost terminal for removed running prompt, queued-failure turn-state pollution, untyped queued deadline rejection), I would have: (1) kept the removed running entry on the pending list with a flag instead of splicing, filtering it from the API; (2) gated the turnError/retryAllowed mutation on the prompt's own running state; (3) checked the abort reason in the pre-dispatch check and thrown the typed error; (4) moved the re-export to the server barrel to restore the leaf module.

Comparison with the diff: the PR's approach matches this exactly. No simpler path was missed.

Walked all 12 pendingPromptList consumers to verify the removed flag doesn't break anything:

  • flushPromptTerminals iterates all entries including removed — correct, that's the point of the fix.
  • Queue-depth accounting counts only state === 'queued'; removed running prompts are state === 'running', not counted.
  • Stop-guard scan checks state === 'queued' and not aborted; removed running prompts don't match.
  • getPendingPrompts filters !p.removed.
  • removePendingPrompt checks target.removed for idempotent repeat removals.
  • result.finally splices the entry and skips the completed event when pendingEntry.removed (the pending_prompt_completed{state:'removed'} was already published by removePendingPrompt).
  • pendingPromptCount is managed via releasePromptSlot in result.finally, unaffected by the flag.

The mutateTurnState gate uses pendingEntry.state === 'running' rather than activePromptId — the comment explains why: on the normal settle path, settleActivePromptState clears activePromptId before the terminal publishes, so gating on it would misclassify genuine active-turn failures. Correct.

The dedup log move from writeStderrLine to writeServeDebugLine is appropriate — dedup is the designed steady state. The do-not-reorder comment on result.then / result.finally guards a load-bearing invariant. The deadline FIFO trade-off comment documents the accepted stale-interleave behavior.

No critical blockers. No AGENTS.md violations.

Real-Scenario Testing

This is a daemon/bridge internal change with no TUI impact. The specific bug fixes require a wedged agent (ignores cancel()) and precise timing conditions (session close before agent cooperates, deadline expiry while queued) — reproducible only with mock channels in unit tests.

Daemon startup (dev build):

$ node scripts/daemon-dev.js
qwen daemon dev
  daemon:   http://127.0.0.1:4170
  workspace: /home/github-runner/actions-runner-test-4/_work/qwen-code/qwen-code

qwen serve: daemon log → ~/.qwen/debug/daemon/daemon.log
qwen serve listening on http://127.0.0.1:4170 (mode=http-bridge, workspace=...)
qwen serve: bound to workspace "..."
qwen serve: startup timing: processToListenMs=1405 runQwenServeToListenMs=968
qwen serve: session reaper started (interval 60000ms, idle threshold 1800000ms)
qwen serve: /acp WebSocket transport enabled on /acp

$ curl -s http://127.0.0.1:4170/health
{"status":"ok"}

Unit tests (429 pass, including new coverage):

$ cd packages/acp-bridge && npx vitest run src/bridge.test.ts

 ✓ src/bridge.test.ts (429 tests) 5184ms

 Test Files  1 passed (1)
      Tests  429 passed (429)
   Duration  9.35s

New test: still publishes a terminal for a removed RUNNING prompt when the session closes before the agent cooperates — verifies the terminal is published before session_closed, the API hides the removed prompt, and repeat removal is a no-op.

Extended assertions in the queued-deadline test: turnError stays undefined on the session summary, and the caller's promise rejects with PromptDeadlineExceededError (not generic AbortError).

Typecheck:

$ cd packages/acp-bridge && npx tsc --noEmit
(clean)

Note: npm run build fails on the main branch due to pre-existing ink type errors in packages/cli/src/ui/selection/ — unrelated to this PR. The acp-bridge package builds and typechecks cleanly.

中文说明

代码审查

独立方案: 针对三个行为缺陷(被移除的运行中 prompt 丢失终态、排队失败的 turn 状态污染、无类型的排队 deadline 拒绝),我的方案是:(1) 用标志保留被移除的运行中条目在待处理列表上而非立即移除,API 层过滤;(2) 以 prompt 自身的运行状态门控 turnError/retryAllowed 修改;(3) 在 pre-dispatch 检查中检查 abort reason 并抛出类型化错误;(4) 将 re-export 移至 server barrel 恢复叶子模块。

与 diff 对比: PR 方案与上述完全一致。没有遗漏更简路径。

遍历了 pendingPromptList 的全部 12 个消费方,验证 removed 标志不会破坏任何逻辑:

  • flushPromptTerminals 遍历所有条目(含已移除)——正确,这正是修复的目的。
  • 队列深度统计只计数 state === 'queued';被移除的运行中 prompt 是 state === 'running',不被计数。
  • Stop-guard 扫描检查 state === 'queued' 且未 abort;被移除的运行中 prompt 不匹配。
  • getPendingPrompts 过滤 !p.removed
  • removePendingPrompt 检查 target.removed 实现幂等重复移除。
  • result.finally 移除条目并在 pendingEntry.removed 时跳过 completed 事件(removePendingPrompt 已发布 pending_prompt_completed{state:'removed'})。
  • pendingPromptCountresult.finally 中的 releasePromptSlot 管理,不受标志影响。

mutateTurnState 门控使用 pendingEntry.state === 'running' 而非 activePromptId——注释解释了原因:正常结算路径上 settleActivePromptState 在终态发布前清空 activePromptId,用它做门控会误判真实的活跃 turn 失败。正确。

去重日志从 writeStderrLine 移至 writeServeDebugLine 是合理的——去重是设计内的稳态。result.then / result.finally 上的 do-not-reorder 注释保护了承重不变式。deadline FIFO 权衡注释记录了已接受的陈旧交错行为。

无关键阻塞项。无 AGENTS.md 违规。

真实场景测试

这是 daemon/bridge 内部改动,无 TUI 影响。具体 bug 修复需要卡死的 agent(忽略 cancel())和精确的时序条件(agent 配合前会话关闭、排队期间 deadline 过期)——只能在单元测试中用 mock channel 复现。

daemon 启动正常,health 端点响应正常。429 个单元测试全部通过,包括新增的移除运行中 prompt 的 teardown 测试和扩展的排队 deadline 断言。acp-bridge 包 typecheck 通过。

注:npm run buildmain 分支上因 packages/cli/src/ui/selection/ 中预存的 ink 类型错误而失败——与本 PR 无关。

Qwen Code · qwen3.7-max

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

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Confidence: 5/5 — clean across every stage; would merge without hesitation.

This is a responsible self-review follow-up to a merged PR. The author shipped the prompt-terminal exactly-once guarantees in #7400, then flagged four gaps in the post-merge review and closed them here. The result is tight, well-commented code that fixes three real behavioral defects and hardens the documentation around load-bearing invariants.

The removed flag approach is the simplest way to keep a running prompt visible to the teardown flush while hiding it from the API. I walked all 12 consumers of pendingPromptList — none are broken by the flag. The mutateTurnState gate on pendingEntry.state === 'running' is correct and the comment explains the subtle reason activePromptId can't be used. The typed error propagation is a one-liner that makes queued and running expiry reject identically, as the original PR promised. The re-export move restores prompt-deadline.ts as a pure leaf.

429 unit tests pass, including a new test that exercises the exact failure mode (removed running prompt + session close before agent cooperates) and extended assertions for the queued-deadline path. The daemon starts and serves health checks. If I had to maintain this in six months, I'd thank the author for the comments.

中文说明

置信度:5/5 — 每个阶段都干净;毫不犹豫地合并。

这是对已合并 PR 负责任的 self-review 后续。作者在 #7400 中交付了 prompt 终态 exactly-once 保证,然后在合并后 review 中标记了四个缺口并在此闭环。结果是紧凑、注释良好的代码,修复了三个真实行为缺陷并加固了承重不变式的文档。

removed 标志方案是保持运行中 prompt 对 teardown flush 可见同时对 API 隐藏的最简方式。遍历了 pendingPromptList 的全部 12 个消费方——无一被标志破坏。mutateTurnStatependingEntry.state === 'running' 做门控是正确的,注释解释了为何不能用 activePromptId 这一微妙原因。类型化错误传播是一行代码,使排队与运行中的过期拒绝行为一致,符合原 PR 的承诺。re-export 移动恢复了 prompt-deadline.ts 的纯叶子模块地位。

429 个单元测试通过,包括一个新测试(精确演练了被移除的运行中 prompt + agent 配合前会话关闭这一失败模式)和排队 deadline 路径的扩展断言。daemon 启动正常并响应 health 检查。如果六个月后维护这段代码,我会感谢作者的注释。

Qwen Code · qwen3.7-max

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

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

LGTM, looks ready to ship. ✅

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

Reviewed — no blockers. Suggestions are inline.

— qwen3.7-max via Qwen Code /review

Comment thread packages/acp-bridge/src/bridge.ts
@doudouOUC

Copy link
Copy Markdown
Collaborator Author

@qwen-code /takeover

@qwen-code-dev-bot qwen-code-dev-bot added the autofix/takeover Summon the autofix loop to manage this PR (remove to release; needs triage+) label Jul 22, 2026
@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

Copy link
Copy Markdown
Collaborator

🤖 Addressed the latest review feedback (round 1/100). What changed, and what I pushed back on:

Review feedback addressed

[rc:3624165877] Missing test for the promote-then-remove-then-settle path

Classification: Suggestion (automated reviewer)

Decision: Implemented.

Changes: Added a new test does not publish a duplicate completed event when a promoted-then-removed running prompt settles in packages/acp-bridge/src/bridge.test.ts. The test:

  1. Enqueues two prompts — the first blocks, the second queues behind it (isQueued = true).
  2. Releases the first prompt so the second promotes to running.
  3. Removes the second prompt while it is running (removed = true, entry stays on the list).
  4. Releases the second prompt so it settles cooperatively.
  5. Asserts exactly one pending_prompt_completed event for the second prompt with state: 'removed' — no duplicate state: 'completed' event from the result.finally path.
  6. Asserts the formal terminal is still published exactly once.

This directly exercises the isQueued && !pendingEntry.removed guard added in this PR. Without the guard, a regression would silently re-introduce a duplicate pending_prompt_completed{state:'completed'} event after the correct {state:'removed'} event.

Conflict notes: None (--conflict false).

Verification:

  • npm run build
  • npm run typecheck
  • npm run lint
  • npx vitest run src/bridge.test.ts (430 tests) ✅
中文说明

已处理的评审反馈

[rc:3624165877] 缺少"先提升、再移除、再结算"路径的测试

分类: 建议(自动评审器)

决定: 已实现。

变更:packages/acp-bridge/src/bridge.test.ts 中新增测试 does not publish a duplicate completed event when a promoted-then-removed running prompt settles。该测试:

  1. 入队两个 prompt——第一个阻塞,第二个排在其后(isQueued = true)。
  2. 释放第一个 prompt,使第二个提升为 running 状态。
  3. 在第二个 prompt 处于 running 状态时将其移除(removed = true,条目仍保留在列表中)。
  4. 释放第二个 prompt,使其协作式结算。
  5. 断言第二个 prompt 恰好只有一个 pending_prompt_completed 事件且 state: 'removed'——result.finally 路径不会产生重复的 state: 'completed' 事件。
  6. 断言正式终端事件仍恰好发布一次。

该测试直接覆盖了本 PR 新增的 isQueued && !pendingEntry.removed 守卫。如果没有该守卫,回归将静默地重新引入一个重复的 pending_prompt_completed{state:'completed'} 事件(在正确的 {state:'removed'} 事件之后)。

冲突说明: 无(--conflict false)。

验证:

  • npm run build
  • npm run typecheck
  • npm run lint
  • npx vitest run src/bridge.test.ts(430 个测试)✅

Base-conflict check: no conflict with main.

Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human.


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

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

No issues found. LGTM! ✅

— qwen3.7-max via Qwen Code /review

@wenshao

wenshao commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

Local verification — reproduced BEFORE → AFTER on the real bridge ✅

I built this PR locally and independently reproduced all four fixes on the real compiled createAcpSessionBridge, driven end-to-end over a genuine in-memory ACP JSON-RPC round-trip (fake agent, no model). Adds the 🐧 Linux signal the PR left as ⚠️.

Verdict: merge-ready. Every fix reproduces the defect on the parent build and disappears on the head build, every fix hunk is individually load-bearing, and nothing else regressed.

verification report card

What I ran (head 666f6cd95, base a106ec5f2)

① Unit suite reproducedacp-bridge/src/bridge.test.ts430 passed / 0 failed (incl. the 3 new PR tests). This suite is not a mock of the code under test: it drives the real bridge factory over @agentclientprotocol/sdk framing with an in-memory channel.

② Mutation teeth — I reverted each of the four fix hunks one at a time (restoring the source to a byte-identical md5 between runs). Every target test flips to FAIL, so the fixes are load-bearing and the tests aren't vacuous:

Fix reverted Target test result Assertion that fails
Fix 1a — running prompt kept on list (else target.removed = true → always splice) ❌ FAIL terms expected [] to have a length of 1
Fix 1b — dup-completed guard (isQueued && !removedisQueued) ❌ FAIL completedForPromoted to have a length of 1 but got 2
Fix 2 — turn-state gate (state === 'running'true) ❌ FAIL summary.turnError expected { … } to be undefined
Fix 3 — typed rejection (drop the PromptDeadlineExceededError throw) ❌ FAIL expected DOMException{AbortError} to be an instance of PromptDeadlineExceededError

③ Runtime A/B probe — a standalone Node script drives the bundled production bridge through all four scenarios, run against a parent build and a head build of src/bridge.ts (core kept external, no rebuild):

runtime A/B before vs after

  • BEFORE (parent): 8/13 — S1 publishes 0 terminals for the removed-running prompt on session close (the hung-SSE-consumer defect), S2 leaves turnError = {code:'prompt_deadline_exceeded'} on the session summary, S3 rejects the queued caller with a generic DOMException(AbortError).
  • AFTER (head): 13/13 — S1 delivers exactly one turn_error{code:'session_closed'} ordered before session_closed; S2 keeps turnError undefined; S3 rejects with the typed PromptDeadlineExceededError.
  • Note on S4 (Fix 1b, the duplicate-completed guard): it reads identical (3/3) in both arms here, because the double-publish only becomes reachable once Fix 1a keeps the running prompt on the list. It's therefore isolated by mutant M1b above rather than by the parent↔head diff — which is the correct signal (no regression introduced, guard proven necessary).

④ Fix 4 — pure-leaf refactor — head prompt-deadline.ts no longer re-exports the bridge. Bundled in isolation it drops from 1989 B / 2 modules (pulling the acp-session-bridge shim → whole @qwen-code/acp-bridge) to 465 B / 1 self-contained module. acp-bridge tsc --noEmit is clean, and the cli side of the moved exports is green: server.test.ts 767 passed, fast-path.test.ts 68 passed.

Method / reproducibility

Worktree at the PR head with a node_modules link-farm; @qwen-code/qwen-code-core kept external so it resolves to the already-built dist (no core rebuild). The runtime probe is an esbuild bundle of src/bridge.ts for the parent and the head commit, driven by one shared probe script. Mutation testing reverts a single hunk per run and restores to the original md5. Env: Linux, Node 22.

🇨🇳 中文版(点击展开)

本地验证 —— 在真实 bridge 上复现了修复前→修复后 ✅

我在本地构建了此 PR,并独立地在真实编译产物 createAcpSessionBridge 上复现了全部四项修复:通过真实的内存 ACP JSON-RPC 往返(fake agent,无模型)端到端驱动。补齐了 PR 中标记为 ⚠️🐧 Linux 信号。

结论:可以合并。 每项修复在父提交构建上都能复现缺陷、在 head 构建上都消失;每处修复代码都被证明是承重的;其余部分无回归。

我运行了什么(head 666f6cd95,base a106ec5f2

① 复现单元测试 —— acp-bridge/src/bridge.test.ts430 通过 / 0 失败(含 PR 新增的 3 个测试)。该套件并非对被测代码打桩:它通过 @agentclientprotocol/sdk 的真实帧、用内存 channel 驱动真实 bridge 工厂。

② 变异测试(teeth) —— 我逐一回退四处修复代码(每次运行之间把源文件还原到逐字节一致的 md5)。每个目标测试都翻转为 FAIL,说明修复是承重的、测试不是空壳:

回退的修复 目标测试结果 失败断言
Fix 1a —— 运行中 prompt 保留在列表(else target.removed = true → 直接 splice) ❌ 失败 terms expected [] to have a length of 1
Fix 1b —— 重复 completed 门控(isQueued && !removedisQueued ❌ 失败 completedForPromoted length 1 but got 2
Fix 2 —— turn 状态门控(state === 'running'true ❌ 失败 summary.turnError expected { … } to be undefined
Fix 3 —— 类型化拒绝(删除 PromptDeadlineExceededError 抛出) ❌ 失败 expected DOMException{AbortError} to be an instance of PromptDeadlineExceededError

③ 运行时 A/B 探针 —— 一个独立 Node 脚本驱动打包后的生产 bridge跑完四个场景,分别针对 src/bridge.ts 的父提交构建与 head 构建运行(core 保持 external,不重新构建):

  • 修复前(父提交):8/13 —— S1 在会话关闭时对被移除的运行中 prompt 发布了 0 条终态(即 SSE 消费者挂起缺陷);S2 在会话摘要上残留 turnError = {code:'prompt_deadline_exceeded'};S3 用通用 DOMException(AbortError) 拒绝排队调用方。
  • 修复后(head):13/13 —— S1 恰好发布一条 turn_error{code:'session_closed'},且排在 session_closed 之前;S2 的 turnError 保持 undefined;S3 用类型化的 PromptDeadlineExceededError 拒绝。
  • 关于 S4(Fix 1b,重复 completed 门控):它在这里的两个 arm 中都是 3/3 相同,因为只有当 Fix 1a 把运行中 prompt 保留在列表上之后,双重发布才变得可达。因此它由上面的变异体 M1b 隔离验证,而非由 父↔head 的差异体现 —— 这正是正确信号(未引入回归、门控确有必要)。

④ Fix 4 —— 纯叶子模块重构 —— head 的 prompt-deadline.ts 不再 re-export bridge。单独打包时它从 1989 B / 2 个模块(会拉入 acp-session-bridge shim → 整个 @qwen-code/acp-bridge)降到 465 B / 1 个自包含模块acp-bridge tsc --noEmit 干净;被移动导出的 cli 侧也全绿:server.test.ts 767 通过fast-path.test.ts 68 通过

方法 / 可复现性

在 PR head 上建 worktree + node_modules 软链农场;@qwen-code/qwen-code-core 保持 external,从已构建的 dist 解析(不重新构建 core)。运行时探针是对父提交和 head 提交的 src/bridge.ts 各做一次 esbuild 打包,由同一个探针脚本驱动。变异测试每次只回退一处、并还原到原始 md5。环境:Linux,Node 22。

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 Reviewed the latest feedback — no changes needed. Why, point by point:

Autofix review triage — no action taken

PR: #7453 · Base: main · Head: 666f6cd95 · Conflict: none

Feedback classified

feedback.md surfaces a single new item since the last evaluation
(2026-07-21T17:10:19Z): an issue-level comment from maintainer @wenshao.
There are no new reviews with findings, no new inline comments, no failed
checks, and no still-red checks
.

# Source Type Decision
1 @wenshao (issue comment, 2026-07-22T05:28Z) Positive local-verification report No action — approval, not a change request

For completeness, the rest of the thread is also non-actionable:

  • Automated reviewer (2026-07-22T04:19Z): latest /review pass is
    APPROVED — "No issues found. LGTM! ✅". No findings to address.
  • Prior [Suggestion] — missing test for the promote-then-remove-then-settle
    path (2026-07-21T17:10Z, previous round):
    already implemented. Head commit
    666f6cd95 ("test(acp-bridge): cover promote-then-remove-then-settle
    duplicate completed guard") adds the test does not publish a duplicate completed event when a promoted-then-removed running prompt settles
    (bridge.test.ts, +92 lines). Nothing further required.
  • Remaining comments are bot status messages and a /takeover command — not
    review feedback.

Why no change

The @wenshao comment is an independent verification report, not a request for
changes. It names no defect, raises no suggestion, and requests no
modification. Its findings are entirely affirmative:

  • Verdict stated as "merge-ready."
  • ① Unit suite reproduced on the real compiled bridge: bridge.test.ts
    → 430 passed / 0 failed (including the 3 new PR tests).
  • ② Mutation testing reverted each of the four fix hunks one at a time;
    every target test flips to FAIL, confirming each hunk is load-bearing and the
    tests are not vacuous.
  • ③ Runtime A/B probe on the bundled production bridge: BEFORE (parent)
    8/13 → AFTER (head) 13/13, with the S4 duplicate-completed guard correctly
    isolated by mutant M1b rather than the parent↔head diff.
  • ④ Fix 4 pure-leaf refactor confirmed: prompt-deadline.ts no longer
    re-exports the bridge, dropping from 1989 B / 2 modules to 465 B / 1
    self-contained module; tsc --noEmit clean and the cli-side suites green.

Because there is no defect to fix, no outstanding suggestion to implement, and
no failing check to clear, no code change is warranted. The PR head is left
as-is.

中文说明

Autofix 评审分诊 —— 未采取任何操作

PR: #7453 · 基线分支: main · Head: 666f6cd95 · 冲突:

反馈分类

feedback.md 自上次评估(2026-07-21T17:10:19Z)以来只浮现出一条新条目:维护者 @wenshao 的一条 issue 级评论。没有带新发现的评审(review)、没有新的行内评论、没有失败的检查、也没有持续失败的检查

# 来源 类型 决定
1 @wenshao(issue 评论,2026-07-22T05:28Z) 正面的本地验证报告 不处理 —— 这是认可,而非修改请求

为完整起见,主题串中的其余内容同样不可操作:

  • 自动评审器(2026-07-22T04:19Z): 最近一次 /reviewAPPROVED —— “No issues found. LGTM! ✅”。没有需要处理的发现。
  • 先前的 [Suggestion] —— 缺少 promote-then-remove-then-settle 路径的测试(2026-07-21T17:10Z,上一轮): 已经实现。Head 提交 666f6cd95(“test(acp-bridge): cover promote-then-remove-then-settle duplicate completed guard”)新增了测试 does not publish a duplicate completed event when a promoted-then-removed running prompt settlesbridge.test.ts,+92 行)。无需再做任何事。
  • 其余评论为机器人状态消息和一条 /takeover 命令 —— 不属于评审反馈。

为何不做修改

@wenshao 的评论是一份独立的验证报告,而非修改请求。它没有指出任何缺陷、没有提出任何建议、也没有要求任何修改。其结论完全是肯定的:

  • 结论明确为“可以合并(merge-ready)”。
  • ① 单元测试套件 在真实编译产物上复现:bridge.test.ts → 430 通过 / 0 失败(含 PR 新增的 3 个测试)。
  • ② 变异测试 逐一回退四处修复代码,每个目标测试都翻转为 FAIL,确认每处修复都是承重的、测试并非空壳。
  • ③ 运行时 A/B 探针 在打包后的生产 bridge 上运行:修复前(父提交)8/13 → 修复后(head)13/13,其中 S4 的重复 completed 门控由变异体 M1b 正确隔离,而非由 父↔head 差异体现。
  • ④ Fix 4 纯叶子模块重构 已确认:prompt-deadline.ts 不再 re-export bridge,从 1989 B / 2 个模块降到 465 B / 1 个自包含模块;tsc --noEmit 干净,cli 侧测试套件全绿。

由于没有需要修复的缺陷、没有待实现的建议、也没有需要消除的失败检查,因此无需做任何代码修改。PR head 保持原样。

Base-conflict check: no conflict with main.


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

@wenshao

wenshao commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

Code review — fix(acp-bridge): close prompt-terminal follow-ups from the PR #7400 self-review

Traced each of the four behavioral fixes against the surrounding bridge logic (not just the diff). All four are correct, and the accompanying tests genuinely exercise the invariants they claim. Recommend merge. Details below.

Overview

Closes the four "will fix" items from the PR #7400 self-review plus doc hardening: (1) a removed RUNNING prompt stays on the pending list (hidden via a removed flag) so the teardown flush can still publish its terminal; (2) queued-prompt terminals no longer mutate session-level turnError/retryAllowed; (3) queued deadline expiry now rejects with the typed PromptDeadlineExceededError; (4) prompt-deadline.ts is restored to a pure leaf module.

Correctness — verified

  • Fix 2 (mutateTurnState gate), broadcastTurnError / publishPromptTerminal:1201. The gate is pendingEntry.state === 'running', and the "why not activePromptId" comment is accurate: settleActivePromptState (bridge.ts:1572) only clears activePromptId/promptActive, never pendingEntry.state. Confirmed state is 'queued' | 'running' (bridgeTypes.ts:595) with exactly two assignments — init and the queued→running promotion — never reset. So the active turn always presents state === 'running' when its terminal lands, and only queued deadline/flush terminals lose turn-state mutation. That's precisely the intended narrowing; no genuine active-turn failure stops setting turnError.

  • Fix 3 (typed rejection), dispatch pre-check bridge.ts:5143. Traced end-to-end: onDeadline aborts with the PromptDeadlineExceededError; a later flush abort() is a no-op on an already-aborted controller, so signal.reason stays the typed error. When the FIFO releases, the pre-dispatch check throws signal.reason; sendPrompt returns result (bridge.ts:5508) so the caller rejects with the typed error, and the result.then reject handler routes non-AbortError to kind:'error' (deduped by the latch). Queued and running expiry now reject identically. ✔

  • Fix 1 (removed RUNNING prompt). Audited all six pendingPromptList iterations for removed-entry leakage: flushPromptTerminals:1223 intentionally wants them; the queue-depth gauge (:4677) and stop-guard scan (:5098) filter on state === 'queued'; getPendingPrompts filters !removed; removePendingPrompt guards target.removed for idempotency; and the settle splice suppresses the duplicate completed via isQueued && !pendingEntry.removed (:5444). No consumer double-counts or double-emits. The wedged-agent path is also right: the removal abort() doesn't settle racedPromise (agent + transport both still pending), so no terminal is published until the close-time flush — exactly what the new test asserts.

  • Fix 4 (re-export). Verified the chain resolves: the class is defined in packages/acp-bridge/src/bridgeErrors.ts, re-exported through the acp-session-bridge.ts shim; server.ts:230-231 now splits the two exports; prompt-deadline.ts drops its acp-session-bridge import and is a pure leaf again. No remaining importer pulls the class from prompt-deadline.js.

Conventions / style

Matches the file's heavy invariant-comment idiom, and the load-bearing ordering (result.then before result.finally, terminal-before-close) is now guarded by a do-not-reorder comment. Moving the dedup log to writeServeDebugLine is the right call — dedup is steady state, not an anomaly.

Test coverage

Strong. The two new tests cover the removed-running teardown terminal and the promoted-then-removed duplicate-completed suppression; the queued-deadline test gains both the turnError === undefined and typed-rejection assertions. Note: I reviewed statically and did not run the suite locally (checkout is on an unrelated branch); the PR reports 429 passing and the assertions match the traced control flow.

Minor / non-blocking

  • broadcastTurnError gains a trailing positional boolean mutateTurnState. Single call site with a clear comment, so fine — but a named/options form would read better at a glance if the signature grows again.
  • Design note worth a doc line: a removed RUNNING prompt whose agent ignores the cancel and completes publishes a turn_complete (not cancelled) terminal — the queue view says removed while the formal terminal says complete. Defensible (it reflects the actual outcome, and is consistent with "only queued removal publishes the cancelled terminal" at :7149), but a client keying off the terminal expecting cancelled after a DELETE could be surprised. One sentence in the pending-prompt API doc would remove the ambiguity.

No correctness, security, or performance concerns.


🤖 Generated with Claude Code — Claude Opus 4.8 (1M context)

@doudouOUC
doudouOUC added this pull request to the merge queue Jul 22, 2026
Merged via the queue into QwenLM:main with commit 1436094 Jul 22, 2026
47 checks passed
@doudouOUC
doudouOUC deleted the fix/daemon-prompt-terminal-followup branch July 22, 2026 07:07
chiga0 pushed a commit that referenced this pull request Jul 23, 2026
…elf-review (#7453)

* fix(acp-bridge): close prompt-terminal follow-ups from PR #7400 self-review

Keep a removed RUNNING prompt visible to the teardown flush via a removed flag so its terminal still publishes when the session closes before the agent cooperates; gate broadcastTurnError's session turn-state mutation to running prompts; propagate the typed PromptDeadlineExceededError from the pre-dispatch abort check; document the deadline FIFO-release overlap trade-off, the trailing prompt_cancelled after flush, and the result.then/finally ordering invariant; route the dedup log to the debug channel; drop the prompt-deadline re-export that pulled the bridge into a leaf module.

Fixes #7451

* test(acp-bridge): cover promote-then-remove-then-settle duplicate completed guard (#7453)

---------

Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com>
yiliang114 added a commit to he-yufeng/qwen-code that referenced this pull request Jul 23, 2026
)

* fix(cli): correct queued message display style and ordering

Mid-turn steer messages (user input queued while the model is
responding) had two display bugs:

1. They rendered with notification styling (● icon) instead of
   user-input styling (> prefix) because accept() added them to
   UI history as MessageType.NOTIFICATION.

2. They appeared below the model's reply because accept() was
   only called in the finally block after the entire response
   stream completed, appending the user message after all model
   response items.

Fix: use MessageType.USER with sentToModel: true for steer
messages, and settle the steer input on the first stream event
(after the user-content push lands but before model-response
events are committed to UI history). Pass steer inputs through
to recursive sendMessageStream calls so all takeSteerInput paths
benefit from early settlement. Add a WeakSet guard to
settleSteerInput for idempotency across recursive invocations.

* test(core): add ordering test for early steer settlement

Verify that accept() is called after the first stream event is
pulled but before subsequent events reach the consumer, pinning
the settle-before-content timing that ensures queued user
messages render above the model's reply.

* fix(cli): use sentToModel: false for steer messages, address review

- Use sentToModel: false instead of true: steer messages are injected
  into an existing tool-result turn, not standalone user turns.
  sentToModel: true would make isRealUserTurn() count them as real
  turns, inflating the rewind turn index.
- Remove unnecessary as HistoryItemWithoutId cast.
- Add post-cleanup assertion in ordering test to verify the WeakSet
  guard prevents double-settlement.

* fix(cli): align resumed mid-turn steer display with live session (#7381)

Resume path now renders mid_turn_user_message as MessageType.USER with
sentToModel: false, matching the live-session styling. Add a comment
documenting the intentional sentToModel: false choice.

* fix(cli): exclude steer messages from user-turn filters (#7381)

Steer messages (sentToModel: false) were counted as real user turns by
five downstream consumers that filter on type === 'user' without checking
sentToModel, breaking cancel auto-restore, telemetry turn count, prompt
recall, away-recap thresholds, and resume collapse boundaries.

Add sentToModel !== false guards at each site.

* test(cli): add coverage for sentToModel !== false guards (#7381)

* test(cli): add coverage for sentToModel !== false guard in input-history filter (#7381)

* test(cli): add coverage for sentToModel !== false guard in YOLO turn-count telemetry (#7381)

* fix(cli): restore corrupted docs and classify steer items as synthetic (#7381)

* fix(docs): restore corrupted autogenerated input names in GitHub Action docs (#7381)

* fix(cli): deduplicate findLastUserItemIndex and add steerInput forwarding test (#7381)

* fix(cli): keep code-block copy numbering continuous across steer items (#7381)

* test(core): add Hook continuation steerInput forwarding test

Verify that steerInput is forwarded through the Stop-hook
continuation path and settled early on the first content event
of the continuation turn, matching the existing Steer
continuation coverage.

* fix(cli): sync selection test fixtures with ink FrameCell/ReadonlyFrame types (#7381)

* fix(core): align cron day wildcard semantics (#7464)

Co-authored-by: destire-mio <248462155+destire-mio@users.noreply.github.com>

* feat(core): keep completed background agents resident (#7426)

* feat(core): keep background agents resident

* fix(core): harden background continuation boundaries

* docs(core): move per-spawn cleanup comment to subagentDispose

The comment describing the per-spawn cleanup (which stays undefined on
the fork-resume path) had drifted above the launchModel declaration,
where it no longer applied and could mislead readers. Relocate it to the
subagentDispose assignment in the non-fork branch it actually documents.

* fix(core): close finishing window and release resident on error in background GOAL path

- Non-worktree GOAL completion drained the message queue but never called
  registry.beginFinishing(), unlike the worktree path. A send_message racing
  the terminal transition could be accepted (status still running,
  finishingAgents empty) and then orphaned by complete(). Call beginFinishing()
  after the empty drain to reject the racing message instead.
- The completion catch block never reset keepResident, so a throw from
  patchAgentMeta/registry.complete left the runtime resident but finalized as
  failed — a zombie that cleanupRuntime never reclaimed. Reset keepResident in
  the catch so the finally block disposes it.

---------

Co-authored-by: Claude <noreply@anthropic.com>

* ci(autofix): continue environment-specific fixes (#7444)

* ci(autofix): continue environment-specific fixes

* docs(autofix): align verification wording

* docs(autofix): require bundle before integration tests

* docs(autofix): scope surrogate verification rules

* docs(autofix): require focused tests before integration checks

* docs(autofix): clarify review verification guidance

* fix(acp-bridge): close prompt-terminal follow-ups from the PR #7400 self-review (#7453)

* fix(acp-bridge): close prompt-terminal follow-ups from PR #7400 self-review

Keep a removed RUNNING prompt visible to the teardown flush via a removed flag so its terminal still publishes when the session closes before the agent cooperates; gate broadcastTurnError's session turn-state mutation to running prompts; propagate the typed PromptDeadlineExceededError from the pre-dispatch abort check; document the deadline FIFO-release overlap trade-off, the trailing prompt_cancelled after flush, and the result.then/finally ordering invariant; route the dedup log to the debug channel; drop the prompt-deadline re-export that pulled the bridge into a leaf module.

Fixes #7451

* test(acp-bridge): cover promote-then-remove-then-settle duplicate completed guard (#7453)

---------

Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com>

* fix(core): strip Qwen-internal daemon secrets from agent-spawned child env (#7256)

* fix(core): strip Qwen-internal daemon secrets from agent-spawned child env

Shell subprocesses (and the monitor tool and stdio MCP servers) inherited
the full daemon process.env, including QWEN_SERVER_TOKEN (the serve-daemon
bearer credential), so an agent-run command like printenv QWEN_SERVER_TOKEN
could read an internal secret. Add a shared sanitizeChildEnv() that removes
Qwen-internal daemon/server tokens (QWEN_SERVER_TOKEN, QWEN_DAEMON_TOKEN)
before spawning, and apply it at the shell child_process + PTY paths,
monitor.ts, and the mcp-client stdio transport.

The denylist is deliberately narrow: it does NOT strip third-party
credentials (GH_TOKEN, AWS_*, NPM_TOKEN, ...) that real shell workflows
legitimately inherit -- only Qwen-internal secrets. Exported from the
package root so the desktop denylists can consolidate onto it later.

Fixes #6601.

* test(core): cover daemon-secret stripping on monitor and mcp-client spawn sites

* test(core): replace process.env instead of mutating in shell sanitization tests

The file restores process.env by reference in afterEach, so in-place key
mutations leaked into later tests. Use the replacement pattern already used
by setupConflictingPathEnv.

* docs(core): align JSDoc @param names with actual function signatures (#7492)

Fix 6 instances where JSDoc @param tags had drifted from their
corresponding function signatures — parameters were renamed, removed,
or undocumented over time but the doc blocks were not updated.

Closes #7446

* feat(serve): support forced MCP reconnects (#7488)

* feat(serve): support forced MCP reconnects

* test(serve): cover forced MCP reconnect options

---------

Co-authored-by: 克竟 <dingbingzhi.dbz@alibaba-inc.com>

* fix(cli): insert newline on Shift+Enter and stop streaming thinking-block flicker (#7397)

* fix(cli): re-push Kitty keyboard flags onto the alternate screen in VP mode

In VP mode the app renders on the alternate screen (`alternateScreen: true`),
but the Kitty keyboard progressive-enhancement flags were pushed only once at
startup on the main screen. The Kitty spec tracks these flags per screen
buffer, so the alternate screen's stack stays empty and the terminal never
reports modifiers: Shift+Enter arrives as a bare Enter (submit) or, when the
terminal emits an ESC-prefixed variant, as an orphaned Escape that trips the
empty-buffer double-Esc rewind prompt — so Shift+Enter can never insert a
newline in VP mode even on Kitty-capable terminals (e.g. cmux).

Re-push the flags onto the alternate screen right after Ink enters it (Ink
writes the enter-alt-screen sequence synchronously inside render(), so the
push is correctly ordered). Ink discards the alternate screen and its flag
stack on unmount, leaving the startup main-screen push balanced by the
existing disableKittyProtocol() on cleanup.

Generated with AI

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

* fix(cli): stabilize streaming thinking block height to stop flicker

The pending "Thinking…" block renders the tail of the reasoning stream in a
content-sized box. As the model emits paragraph separators, a blank line
enters and leaves the tail window (and `trimEnd` drops trailing blanks), so the
visible line count oscillates and the block flickers 2→3→5 rows during
streaming.

Track the tallest height the block has reached for the current thought and
never render fewer rows than that (capped at the streaming window size),
padding at the top so the newest line stays pinned to the bottom. The tracker
resets when streaming ends or when the buffer shrinks (a new thought replaced
it), so height is monotonic within a thought without leaking across thoughts.

Generated with AI

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

* fix(cli): decode xterm modifyOtherKeys Shift/Ctrl/Alt+Enter so it inserts a newline

Terminals such as Ghostty report Shift+Enter as the xterm modifyOtherKeys
sequence `ESC [ 27 ; <mods> ; <key> ~` (e.g. `ESC [ 27 ; 2 ; 13 ~`) when the
Kitty keyboard protocol is not negotiated — which is the default, since Kitty
detection does not always succeed. Two bugs kept this from inserting a newline:

1. The CSI-u parser read the leading `27` marker as the key code (matching the
   Escape key code 27) instead of the real key code in the third parameter, so
   with Kitty enabled Shift+Enter was mistaken for Escape and tripped the
   double-Esc rewind prompt.
2. The reassembly path that stitches readline's shredded CSI fragments back
   together was gated behind `kittyProtocolEnabled`, so with Kitty disabled the
   `ESC [ 27 ; 2 ;` head plus the stray `13~` tail leaked into the composer as
   literal text and no newline was inserted.

Decode the third parameter as the real key code for the `27;…~` form, and route
those sequences through the reassembly buffer even when Kitty is off (only the
`ESC [ 27` marker opts in, so keys readline already parses cleanly are
untouched). Shift/Ctrl/Alt+Enter now insert a newline in both VP and non-VP
mode regardless of Kitty negotiation.

Generated with AI

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

* fix(cli): anchor VP viewport to the top until a conversation turn exists

On a fresh VP-mode session the virtualized list holds the banner plus startup
notices (tips / MOTD / info), so it is longer than one item. Keying the initial
scroll anchor off list length alone selected scroll-to-end, which pinned the
banner to the bottom of the full-height viewport and left the top half of the
screen blank.

Anchor to the top until there is an actual conversation turn (a user/user_shell
history item or a pending response), then resume scroll-to-end so the latest
output stays in view. Startup notices no longer count as content that forces
bottom alignment.

Generated with AI

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

* fix(cli): stabilize streaming thinking window against availableTerminalHeight drift

The grow-only streaming thinking window still flickered because its line cap was
derived from availableTerminalHeight. While a thought streams the terminal keeps
constrainHeight on, so availableTerminalHeight (and the derived maxLines) drifts
up and down as sibling pending content grows, and the grow-only clamp
`min(maxLines, …)` shrank the block whenever it dipped.

Use a constant window height (MAX_STREAMING_THINKING_VISUAL_LINES) for the
pending window instead. The window is only a few lines, so a fixed cap cannot
meaningfully overflow (VP scrolls anyway), and the height stays stable while
still growing monotonically within a thought.

Generated with AI

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

* Revert "fix(cli): anchor VP viewport to the top until a conversation turn exists"

This reverts commit fbe86a9e159b75ea1f5b689cc327599c9dc91090.

* fix(cli): guard modifyOtherKeys detection against keypresses without a sequence

The modifyOtherKeys prefix check ran on every keypress, but some synthetic
keypresses (and the useKeypress test harness) emit a key with no `sequence`,
so `key.sequence.startsWith(...)` threw an unhandled rejection. Use optional
chaining so a missing sequence is simply not a modifyOtherKeys start.

Generated with AI

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

* test(cli): mock pushKittyProtocolFlags in gemini.test.tsx kitty mock

The kittyProtocolDetector mock omitted the newly added pushKittyProtocolFlags
export. Add it so the mock stays in sync with the real module and a VP-mode
startup path exercised through this suite cannot hit an undefined call.

Generated with AI

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

---------

Co-authored-by: 秦奇 <gary.gq@alibaba-inc.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(web-shell): open singleton subagent details (#7495)

Co-authored-by: ytahdn <ytahdn@gmail.com>

* fix(web-shell): avoid redundant git status requests (#7496)

Co-authored-by: ytahdn <ytahdn@gmail.com>

* fix(agent): ignore empty working_dir placeholders (#7343)

* fix(agent): ignore empty working_dir placeholders

* test(agent): align empty working_dir expectations

* feat(prompts): allow overriding core identity via QWEN_SYSTEM_IDENTITY_MD (#7478)

* feat(prompts): update prompts.ts for QWEN_SYSTEM_IDENTITY_MD

* feat(prompts): update prompts.test.ts for QWEN_SYSTEM_IDENTITY_MD

* fix(prompts): address CR on QWEN_SYSTEM_IDENTITY_MD

Keep getDefaultCoreIdentitySentence private, fail loud on path
resolution errors, use trimEnd, and resolve identity only on the
default-prompt branch.

* test(prompts): align identity override tests with CR feedback

Sample default identity from live prompt, cover trimEnd trailing
whitespace, and assert homedir resolution failures throw.

---------

Co-authored-by: 易良 <1204183885@qq.com>

* fix(cli): yield to single-slot background agents (#7258)

Co-authored-by: hogeheer <267467744+hogeheer499-commits@users.noreply.github.com>

* docs(autofix): require evidenced pre-commit verification, not a bare "verified" (#7486)

* docs(autofix): require evidenced pre-commit verification, not a bare "verified"

The skill already said to run build/typecheck/lint/Vitest before
committing, but softly — and #7408 committed a fix with a TS error the
gate then rejected while its summary claimed "verified all 3 commits".
A self-assessment the gate contradicts wastes a whole round.

Strengthens the address-review contract from "run the checks" to:
- actually run them, do not assert them from reading the diff;
- if typecheck or a touched-package test fails, do NOT commit — treat
  the feedback as unresolved (failure.md);
- end address-summary.md with a `## Verification` section listing each
  command run and its result; a bare "verified" is not acceptable.

The framing is structural, not etiquette: the deterministic gate re-runs
the same commands and discards the round on any failure, so skipping them
only moves the rejection later. Pinned by a test so it cannot soften back.

This is the checkable half of "audit before committing" — the
undirected/reverse-audit-until-clean practice does not transfer to an
unsupervised agent (no verifiable stopping condition, and it worsens the
timeouts seen on large PRs), but "run the gate's own checks first and
show the evidence" does.

* fix(autofix): clarify Verification section precedes collapsed Chinese translation (#7486)

---------

Co-authored-by: wenshao <wenshao@example.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com>

* feat(autofix): stop a PR that fails to push for N rounds in a row (#7482)

* feat(autofix): stop a PR that fails to push for N rounds in a row

Under takeover the round cap is 100, which is right for a PR that needs
many PRODUCTIVE rounds. It is wrong for one that fails every round: #6723
ran 7 consecutive failed rounds (3 agent timeouts at 50 min, 4 gate
rejections whose fix broke tests) over 8 hours, heading for round 100,
because it is a 5700-line, 47-file, 5-day-old PR racing a fast-moving
main — every round re-resolves a conflict it cannot finish or that fails
the gate. Retrying at the same per-round budget will not converge; a
human has to rebase or split it.

Adds CONSECUTIVE_FAILURE_CAP (5), distinct from the total round cap. The
handoff step already runs only when a round did NOT push, so it counts
the unbroken run of prior failure markers — stopping at the first push
("Addressed the latest review feedback") or legitimate no-op ("no
changes needed"), either of which proves progress and resets the streak.
At the cap it forces the terminal round even under takeover, with a
handoff that names the real fix (rebase/split, then /retry). Cause-
agnostic: a timeout and a gate rejection both count.

* fix(autofix): address review feedback on consecutive-failure circuit breaker (#7482)

- Fix misleading comment: the walk is oldest-first (API order) with
  reset-on-success, not newest-first with early stop
- Prefer the already-fetched ic.json over a redundant gh api call,
  falling back to the API only when the file is missing
- Filter eval markers by re-arm window (win=) so pre-re-arm failures
  do not immediately re-terminate a re-armed PR
- Add test coverage for the MARK_ROUND == MAX_ROUNDS guard and for
  window-scoped streak counting

* fix(autofix): exempt transient model errors from consecutive-failure breaker (#7482)

---------

Co-authored-by: wenshao <wenshao@example.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>

* feat(core): restore background agent roster (#7459)

* feat(core): restore background agent roster

* fix(web-shell): add list_agents to TOOL_DISPLAY_NAMES

The new list_agents core wire tool was added to core's ToolNames but not
to the web-shell TOOL_DISPLAY_NAMES map, causing toolFormatting.drift.test.ts
to fail (expected ['list_agents'] to deeply equal []). Add the missing
'ListAgents' display-name entry so the browser panel shows a friendly name
instead of the raw wire name and the drift guard passes.

* fix(cli): reload old-session background agents on failed resume rollback

When /resume fails after core has swapped but before the UI swap, the catch
block rolls core back to the old session via startNewSession(oldSessionId).
However the forward path already called resetBackgroundStateForSessionSwitch,
which cleared the old session's in-memory background agents. The rollback did
not reload them, so list_agents returned empty for the old session (whose
sidecars are still on disk) until the next process start or successful resume.

Reload the old session's paused background agents after rolling core back, so
the restored roster matches on-disk state. Placed after startNewSession so the
loadPausedBackgroundAgents current-session guard is satisfied; best-effort via
.catch so it never blocks the rollback path.

* fix(web-shell): add zh translation for list_agents tool name

The toolFormatting test 'has a zh translation for every tool in the
display-name map' failed with expected ['list_agents'] to deeply equal []
because list_agents was added to TOOL_DISPLAY_NAMES without a matching
toolName.list_agents zh-CN entry. Add the translation to restore parity.

* fix(cli): resolve CI failures for background-agent roster restore

- Add toolDisplayName.ListAgents translations (en, zh, zh-TW, ca) so the
  new list_agents tool has a zh entry; fixes i18n/index.test.ts.
- Add loadPausedBackgroundAgents and consumePendingRecoveredAgentsNotice
  to the acpAgent worktree test config mock, which loadSession now calls
  via #restoreBackgroundAgentsOnResume; fixes acpAgent.worktree.test.ts.

* refactor(core): extract incompatible-isolation blocked reason to a const

Move the incompatible-isolation blocked-reason string out of an inline
literal into a module-level INCOMPATIBLE_ISOLATION_BLOCKED_REASON const,
matching its four sibling reasons so the text is discoverable by
constant-name grep and edited alongside the others.

* fix(core): preserve retained activity state on failed agent revive

Address review feedback on the background-agent roster restore:

- On a failed completed-agent revive, restore UI state with a non-empty
  guard instead of `??`. Because `restorePausedEntry` resets the paused
  entry's `recentActivities` to `[]`, the previous `failedEntry?.field ??
  completedEntry.field` kept that empty array and dropped the pre-revive
  snapshot (the UI Progress section rendered empty). Applied consistently
  to pendingMessages, recentActivities, and pendingApprovals.

Add regression coverage for previously untested paths:

- failed revive preserves pre-revive recentActivities
- terminal-agent cap admits only the newest MAX_RETAINED_TERMINAL_AGENTS
  completed sidecars on restore
- /resume rollback reloads the old session's background agents
- headless resume prepends the recovered-agents notice to the prompt

* test(cli): cover interrupted-turn continuation not consuming recovered-agents notice

Add ACP and headless regression tests asserting an interrupted-turn
continuation does not consume the one-shot recovered-agents notice
(the !isContinue / !continueInterrupted guards), so it is delivered on
the user's next ordinary prompt. Mirrors the existing slash-command
coverage.

---------

Co-authored-by: Claude <noreply@anthropic.com>

* feat(cli): support custom skill directories via settings (#7395)

* feat(cli): support custom skill directories via settings (#7394)

Add skills.directories setting that accepts an array of additional
directory paths to scan for skills (SKILL.md files). Paths support
~ expansion. Directories are scanned recursively at user level,
after the default ~/.qwen/skills/ directory.

Example settings.json:
{
  "skills": {
    "directories": ["~/.agent/skills", "~/.claude/skills"]
  }
}

Changes:
- settingsSchema.ts: add skills.directories array setting
- core Config: add customSkillDirs param and getCustomSkillDirs()
- SkillManager: append custom dirs to user-level skill base dirs
- CLI config: read skills.directories and pass to core Config

* fix(cli): regenerate settings schema for skills.directories (#7394)

* fix(core): address review feedback for custom skill directories (#7395)

- Use optional chaining for getCustomSkillDirs() to prevent TypeError
  on partial Config mocks (workspace-skill-management, workspace-skills-status)
- Reuse expandHomeDir utility instead of inline tilde expansion
- Fix inaccurate 'scanned recursively' wording to 'one level deep'
- Correct JSDoc: paths are raw, expansion happens in SkillManager
- Trim whitespace from custom dir entries in CLI layer
- Add tests for custom dir expansion, dedup, and partial config safety

* fix(core): address review feedback for custom skill directories (#7395)

* fix(core): address review feedback for custom skill directories (#7395)

* test(core): add relative path resolution test for custom skill dirs (#7395)

* fix(cli): add Array.isArray guard for skills.directories and safe mode test (#7395)

* fix(skills): address review feedback on custom skill directories (#7395)

- Add bare mode test for skills.directories guard
- Include resolved absolute path in relative directory warning
- Clarify that dedup applies to default user dirs, not bundled skills
- Regenerate settings schema

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com>
Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com>

* fix(core): add image modality support for qwen3.8-max and kimi-k3 models (#7491)

* fix(core): add image modality support for qwen3.8-max models

qwen3.8-max-preview supports image input but was falling through to the
catch-all text-only rule because no pattern matched it. This caused the
vision bridge to unnecessarily transcribe images via a secondary model
instead of sending them directly to the primary model.

* fix(core): also add image modality for kimi-k3

Kimi K3 officially supports image + video input but was falling through
to the catch-all text-only rule, same issue as qwen3.8-max.

* fix(dingtalk): preserve non-bot mention context (#7473)

* fix(dingtalk): preserve non-bot mention context

* test(dingtalk): cover plural mentions, staffId fallback, and edge cases (#7473)

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>

* fix(core): harden the usage salvage around session deletion (#7425)

Post-merge review follow-ups on #7391 (three findings):

- Salvage the archived transcript in the active-branch deletion too:
  when both copies co-exist (an interrupted archive) and the fresh
  active transcript carries no telemetry, the archived copy holds the
  session's usage history and was deleted unsalvaged. The dedup guard
  makes the extra call a no-op whenever the active copy already wrote.
- Enforce the "never blocks deletion" contract at the call site: a
  salvageUsageBestEffort wrapper catches and warns, so the guarantee is
  structural rather than an implementation detail of
  persistUsageBeforeTranscriptDeletion. The new failure-tolerance test
  (salvage rejects -> deletion still succeeds) fails without the
  wrapper — the bare await let the rejection escape through
  removeSessionFiles' rethrowing catch.
- Clear the salvage module mock in beforeEach so the wiring test's
  invocationCallOrder assertions can never read stale calls.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* fix(core): make fork subagents discoverable (#7460)

* test(core): cover Shell truncation without an artifact (#7470)

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

* fix(ci): autofix route checks existing labels on non-trigger label events (#7481)

* fix(ci): autofix route checks existing labels on non-trigger label events

When triage adds multiple labels in sequence, per-issue concurrency
cancels earlier runs. If the last label is not a trigger label
(e.g. scope/build-system), the surviving run skips the issue phase
even though the issue already has autofix/approved +
status/ready-for-agent.

Before ignoring a non-trigger label event, check ISSUE_LABELS_JSON
for both required labels. If present and the issue is open, proceed
with the issue phase. Trust was already established when the trigger
labels were applied (both require triage+ permission).

* fix(ci): require trusted sender for label fallback

* feat(cli): preserve semantic text when copying VP selections (#7286)

* docs(cli): define semantic copy fidelity scope

* docs(cli): address semantic frame review gaps

* docs(cli): preserve soft-wrap source separators

* feat(cli): preserve semantic selection copy

* fix(cli): address semantic copy review findings

* fix(cli): preserve clipped semantic boundaries

* fix(cli): limit separator carrier joiner to visible width in wrap metadata

The greedy /\s+/ match in wrapTextWithMetadata could capture more
source whitespace than the separator carrier row actually consumed
(e.g. a tab following a space), causing duplicated whitespace in
semantic copy. Limit the match to visibleLine.length characters and
add a mixed space/tab regression test.

---------

Co-authored-by: 秦奇 <gary.gq@alibaba-inc.com>

* test(core): stub the registry methods agent.ts actually calls (#7538)

The shared stubRegistry in agent.test.ts was missing six methods that
agent.ts reaches: bridgeApprovalEvents, getQueuedCount,
registerResidentAgent, restartCompletedAgent, unregisterResidentAgent and
waitForMessages.

That is not a benign omission. The background body wraps its work in a
try/catch that routes any throw into registry.fail(), so a missing method
never surfaces as 'not a function' — it silently converts a successful
run into a failed one. On the GOAL completion path
unregisterResidentAgent is called immediately before complete(), so the
TypeError replaced the completion entirely:

  registry.fail('fork-...', 'registry2.unregisterResidentAgent is not a
  function', ...)

That is what broke 'runs a non-interactive fork through the background
registry' on main. #7460 added the registry.complete assertion, which
exposed the incomplete stub — before it, nothing checked whether the
background body finished successfully and the TypeError was swallowed.

Stub all six with their real return shapes (unregisterResidentAgent
returns boolean, bridgeApprovalEvents returns the unsubscribe callback
agent.ts later invokes, waitForMessages resolves to a list) and assert
registry.fail was not called before asserting completion, so a future
gap reports the actual error instead of 'complete: 0 calls'.

* perf(startup): lazy-load Google GenAI SDK on first use (#7512)

* perf(startup): lazy-load Google GenAI SDK on first use

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

* codex: address PR review feedback (#7512)

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

* codex: address PR review feedback (#7512)

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

---------

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

* fix(vscode): use file picker image paths for vision input (#7493)

* fix(vscode): use image paths from file picker

* fix(vscode): keep image picker paths raw

* fix(vscode): resolve image picker paths on submit

* fix(vscode): send picked images as vision context

* fix(vscode): encode prompt image file URIs

* fix(vscode): address image path review comments

* test(vscode): cover image file reference edge cases

* fix(cli): open the actual serve fallback port (#7501)

* fix(cli): open actual serve fallback port

* test(cli): match serve URL to fallback listener

* docs(cli): clarify serve listen error handling

---------

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>

* fix(ci): don't let one failing scenario sink the whole visual preview (#7511)

The web-shell visuals render runs every screenshot and flow in a single
`test:e2e:visuals`, and that step had no `continue-on-error`, while the compose
and upload steps had no `if: always()`. So one failing or timing-out scenario
failed the job, the artifact was never uploaded, and the publish workflow had
nothing to post — the entire preview vanished even when every other scenario
passed and its PNG was already on disk. A flow (a long multi-click sequence) is
the most fragile scenario kind, so the fragile one silently takes down the
deterministic screenshots. PR #7498 hit exactly this: 29 scenarios passed, one
new channel-management flow timed out, and the PR got no preview and no comment
at all.

Make the after-capture step `continue-on-error` so the passing captures survive
and the later steps still compose and upload them. The publish job only runs on
a `success` conclusion, so the job must stay green — but a masked failure must
not read as a clean preview. Ship the step's real `.outcome` (which
continue-on-error does NOT mask, unlike `.conclusion`) to the publisher as
`render-status.txt`, and have the comment builder use it: an empty preview whose
render failed says "one or more scenarios failed to render" and is explicitly
NOT the reassuring green check or the coverage-gap prompt (both imply the render
ran); a partial preview is labelled partial above the shots that did render. A
missing status file (older run) defaults to complete, so this only ever adds a
warning, never suppresses a real preview.

The failing scenario still needs fixing — it's now surfaced in the comment
rather than by silently deleting everyone else's preview.

Co-authored-by: wenshao <wenshao@example.com>

* feat(web-shell): add selective shadow DOM isolation (#7551)

Co-authored-by: 钉萁 <dingqi.jww@alibaba-inc.com>

* feat(web-shell): add renderChatHeader slot for custom session header (#7553)

* fix(cli): say review coverage gaps in the author's units, not chunk ids (#7550)

The posted review body rendered coverage disclosures with the run's own
bookkeeping as subjects: bare chunk ids, unsorted, one per subject. On a
run that certified nothing (PR #7268) the body enumerated all 49 chunk ids
across two sentences while opening with "Reviewed. Suggestions are
inline." — the opener certified the exact thing every following sentence
took back, and nothing on the PR page maps a chunk id to code.

Three changes, all render-time — the structural entries, the caps, the
caller-echo dedup and the stderr remediation still key on chunk ids, which
is where the id is the selector a reader can act on:

- Coverage now returns the plan's chunk→files table (DiffChunk.files was
  already in the plan JSON; the coverage type slice dropped it).
- compose-review renders chunk gaps through describeChunkGap: every
  planned chunk collapses to "the entire diff", a narrow gap with known
  files names the files, and anything wider is counted against the plan's
  total. Applied to the receipt sentence, the uncoverable sentence (bare
  CLI entries only — caller-authored entries render verbatim) and the
  grouped per-cause sentences.
- The COMMENT opener may no longer say "Reviewed." over a disclosure set
  that denies it: when no chunk is both covered and undisclosed — or no
  chunk universe could be read at all — it opens with a zero-certified
  warning instead. A rewritten launch demonstrably read its chunk, so
  coverage alone is not the test; certified is covered with no disclosure
  against it.

Co-authored-by: verify <verify@local>

* fix(autofix): retry a skipped-Prepare instead of stranding the PR terminal (#7490)

* fix(autofix): retry a skipped-Prepare instead of stranding the PR terminal

A base/infra failure BEFORE the agent runs was misread as an agent crash
and terminated the PR forever. When an early step fails — installing or
building the trusted base, checkout, node setup — the `Prepare branch and
feedback` step is skipped, so NEWEST is empty, and the report step's
"crashed before reading feedback" branch fired: MARK_ROUND=MAX_ROUNDS,
terminal, scan skips it on every future tick.

Observed: a web-shell TypeScript break on `main` failed `Install
dependencies and build` (which builds the trusted base) across a whole
scan batch, and SIX healthy PRs were stranded terminal at round=100 in
one run — including ones at round 9 and 11 that had nothing to do with
the break. `round=100` there is a terminal sentinel, not 100 attempts.

NEWEST-empty now splits on steps.prepare.outcome:
- 'skipped' (an earlier step failed, the agent never ran) is infra/base
  and transient: retry with a sentinel ts so the feedback stays live,
  incrementing the round so a PERSISTENTLY broken base is still bounded
  and stops at the cap (recoverable with /retry).
- 'success'/'failure' (Prepare ran, no feedback produced) is a genuine
  pre-read agent crash: unchanged terminal behaviour.

This is the reverse of the asymmetry #7482 addresses: that bounds a
crash AFTER reading that retried forever; this stops a transient failure
BEFORE reading from going terminal after one.

* docs(autofix): note a pre-Prepare cancel also retries intentionally (#7490)

* fix(autofix): also retry a cancelled/empty prepare outcome, not just skipped

A previous review comment on this PR noted that a job cancelled before
Prepare should retry too. It was right about the intent but the code did
not do it: `steps.prepare.outcome` is 'cancelled' for a cancel and '' for
a job that stopped before Prepare entered the step context — both DISTINCT
from 'skipped', so `== 'skipped'` sent them to the terminal branch, the
same over-termination this PR exists to fix.

Match on "not a real Prepare run" (`!= 'success' && != 'failure'`)
instead, so skipped, cancelled, and empty all retry; only a Prepare that
actually ran to a verdict (success/failure) with no feedback stays
terminal — the genuine pre-read agent crash. Test extended to drive the
cancelled and empty cases (retry) and both real-run outcomes (terminal);
mutation-verified that reverting to `== 'skipped'` reddens the cancelled
case.

* test(autofix): update the pre-read-crash case for the broadened retry

The prior commit broadened NEWEST-empty retry to skipped/cancelled/empty
but left the older 'replays the handoff decision' test asserting the old
terminal behaviour for an unset PREPARE_OUTCOME (which now retries). That
test's terminal cases now set PREPARE_OUTCOME=success/failure explicitly —
the only outcomes that still terminate — so it exercises the genuine
pre-read agent crash rather than the infra/cancel path.

* test(autofix): anchor the skipped-Prepare extraction past the CONSEC block

CI reddened `retries a skipped-Prepare` after main's consecutive-failure
cap (#7482) merged into this branch: that block was inserted between this
decision block and the report `{`, and it calls `gh api`. The test's
`{`-anchored regex over-captured through it, so the extracted script ran
the unstubbed `gh api` and failed. Anchor the end on the same
`# Consecutive-failure` comment the sibling gate-crash test already uses,
so the extraction stops at this decision block's own closing `fi`.

* fix(autofix): exempt skipped-Prepare from the consecutive-failure breaker

A broken base build skips Prepare, producing no API error file — so the
consecutive-failure breaker ran on the new retry path and, after 5
scans, re-introduced the exact mass-stranding this PR exists to prevent.
Exempt pre-agent infra failures (skipped/cancelled/empty outcome) from
the breaker, mirroring the transient 429/5xx exemption: same failure
class (not the PR's fault, self-heals, hits the whole batch). The round
cap + sentinel-ts /retry recovery already bounds a persistently broken
base.

Also trim "checkout" from the retry headlines (checkout failures do not
land in this branch) and hoist the duplicated MARK_TS assignment.

* fix(autofix): reset the consecutive-failure streak on prior infra-failure markers

The streak walker counted prior infra-failure headlines ("AutoFix could
not start —…") as failures, inflating the consecutive-failure count on
subsequent rounds.  A PR with 3 real agent failures, then 3 rounds of
base-build infra failures, then 1 more real failure would trip the
cap-5 breaker even though only 4 rounds were the PR's fault.

Add the two infra-failure headline patterns as reset strings in the
streak walker, alongside the existing push and no-op resets.  The
genuine agent-crash headline ("AutoFix could not start evaluation —…")
is deliberately excluded — it is a real failure and must still count.

* fix(autofix): clarify infra-failure headlines and else-branch comment (#7490)

Address review nits: the retry headline now mentions cancelled runs,
the cap headline says 'reached the round cap' instead of overstating
'could not start for N rounds', the else-branch comment says 'prepare
itself crashed' instead of 'agent crash', and the streak-reset pattern
is simplified now that both infra headlines share the same prefix.

---------

Co-authored-by: wenshao <wenshao@example.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>

* fix(cli): keep role codenames and brief paths out of the posted review body (#7560)

The posted body still carried two operator registers #7550 left in place:
roster role subjects rendered their internal codenames ("Agent 1c:
Cross-file tracer", "Test coverage matrix (whole-diff)"), and an unread
brief's disclosure interpolated its filesystem path. And when verify and
the reverse audit failed the same way, the body said it twice, in two
near-identical sentences.

- Every Brief now carries a publicLabel — the dimension said as what it
  checks ("the cross-file consistency pass") — and coverage's structural
  disclosures carry it as publicSubject beside the internal subject, plus
  a path-free publicReason for unread briefs. The internal label and the
  path stay on stderr, where they are the selector an operator acts on;
  every dedup and certification check still keys on the internal subject.
- compose-review renders the public fields and groups by the reason the
  body PRINTS, so two unread briefs share one path-free sentence instead
  of repeating it per role.
- verificationGaps merges verify and reverse-audit failures of the same
  delivery shape into one sentence with both subjects and both
  consequences; mixed shapes keep their precise per-role texts, and the
  per-role rebuild commands stay on stderr either way.

Co-authored-by: verify <verify@local>

* fix(autofix): retry an agent timeout instead of advancing past its feedback (#7563)

A timeout evaluated NOTHING — the agent ran out of budget before finishing,
so nothing was committed and the feedback is unaddressed. It was treated as
an evaluated verdict (real ts, watermark advances), which strands that
feedback: the next scan sees "nothing new" and never retries. Observed on
#7471 (round 13/100), a heavily-reviewed 1871-line PR: rounds 11 and 13
timed out, but round 12 pushed — so a timeout is transient far more often
than not, and advancing past it left the round-13 feedback unhandled.

run-agent.mjs now drops an `agent-timeout` signal on result.timedOut, and
the handoff routes it like a pre-verdict crash: sentinel ts (feedback stays
live) and a retry, with a headline that names the real fix at the cap
(split the PR or raise the budget). A PR that PERSISTENTLY times out is
bounded by the round cap and the consecutive-failure cap, so this cannot
loop forever — it just stops treating a one-off budget blip as a verdict.

The loop guard stays terminal (a tool-call loop is a real defect, not a
budget blip). An API error still routes to its own model-key handoff; the
timeout signal is written only when NOT an API error.

Co-authored-by: wenshao <wenshao@example.com>

* feat(serve): add workspace-level generation (#7552)

* feat(serve): add workspace-level generation

* docs(serve): document workspace generation capability

* fix(serve): align workspace generation contracts

---------

Co-authored-by: ytahdn <ytahdn@gmail.com>

* ci: matrix ECS runner update + sudo install + repository_dispatch trigger (#7513)

* ci: matrix ECS runner update with sudo install

- Use matrix strategy (ecs-update-sg, ecs-update-64c) to update both
  physical ECS hosts in parallel (fail-fast: false).
- Always use sudo npm install -g so the package lands in /usr/local
  (system-wide PATH) instead of the runner user's home directory.
- Move concurrency to job level (matrix context not available at
  workflow level per actionlint).
- Add repository_dispatch trigger for release-driven updates.
- Register new runner labels in actionlint.yaml.

* fix(ci): use dispatch version for runner update

---------

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>

* fix(web-shell): include managed id in artifact open requests (#7570)

Co-authored-by: 钉萁 <dingqi.jww@alibaba-inc.com>

* feat(serve): persist workspace channel configuration (#7514)

* feat(serve): persist workspace channel configuration

* fix(serve): harden channel settings snapshots

* fix(serve): validate startup channel names

* fix(serve): reserve all channel name

---------

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>

* fix(sdk-python): require canonical form in validate_session_id (#7532)

uuid.UUID() accepts several non-canonical spellings — braced
{...}, urn:uuid:..., and dash-less hex — so validate_session_id let them
through after the RFC 4122 variant check. The value is then forwarded to
the CLI verbatim as --session-id/--resume, producing a malformed session
id downstream rather than a clear error at the SDK boundary.

Reject anything whose canonical form differs from the input. Case is
deliberately not part of the comparison: UUID() lowercases, and an
all-uppercase spelling is still valid canonical input.

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>

* fix(web-shell): sync background agent status (#7561)

* fix(web-shell): sync background agent status

* fix(web-shell): harden background agent reconciliation

---------

Co-authored-by: ytahdn <ytahdn@gmail.com>

* feat(core): propagate trusted daemon invocation context (#7279)

* feat(core): propagate trusted daemon invocation context

* test(cli): update ACP startup expectation

* refactor(core): centralize ACP capability env key

* test(cli): update worktree ACP core mock

* test(integration): run daemon context smoke on PRs

* test(ci): update no-AK smoke expectation

* test(core): cover invocation context isolation

* fix(cli): compare ACP capability safely

* fix(docs): restore GitHub action input names

* fix(core): sanitize private ACP capability from child env

* fix(core): reuse private ACP capability env constant

* test(cli): cover malformed trusted invocation context

* test(acp-bridge): assert exact child environment

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: 易良 <1204183885@qq.com>

* fix(feishu): await stream cancels in media download teardown (#7465)

* fix(feishu): await stream cancels in media download teardown

downloadMedia left two reject paths' stream teardown unawaited:

- the oversize-stream path called reader.cancel() without awaiting, so a
  cancel error during teardown became an unhandled rejection (fatal under
  Node's default --unhandled-rejections=throw);
- the Content-Length reject path returned without cancelling resp.body,
  leaving the connection pinned until GC.

Both were already fixed for the sibling DingTalk downloader in #7361 (which
was itself modelled on this Feishu code), so this brings Feishu to parity.
Adds a regression test that pins the reader.cancel() await via a rejecting
cancel, plus an assertion that the Content-Length path releases the body.

* test(feishu): cover a rejecting body.cancel() on the Content-Length path

Mirrors the existing reader.cancel() teardown test for the other reject
path, per review feedback. Removing the await on resp.body?.cancel()
flips execution onto the 'rejected: size ... exceeds' branch and the
test fails.

* fix(autofix): make the review-address report wrapper lines bilingual (#7569)

The agent's address-summary.md / no-action.md already ends with a
collapsed Chinese translation, but the workflow-appended wrapper lines
around it — the "Addressed/Reviewed the latest feedback" lead-in, the
"Base-conflict check" line, and the "Re-review when you have a moment"
footer — were English-only and sat outside that block. So the posted
comment was only half translated, unlike the takeover-ack comments
(full collapsed Chinese block) and the "model/模型" sign-off in this
same report (already inline-bilingual).

Give each wrapper line an inline Chinese translation, matching the
model/模型 idiom. The English halves are preserved verbatim — the
streak-reset detector globs on "Addressed the latest review feedback"
and "no changes needed", and a test extracts these lines — so behaviour
is unchanged and old English-only comments still match. A new test pins
each English-Chinese pair so a future reword that drops the Chinese
fails. The terminal handoff/failure comment is left English-only for
now (SKILL.md keeps it so by design); that is a separate change.

Co-authored-by: wenshao <wenshao@example.com>

* feat(cli): post the review body bilingually when the PR description is Chinese (#7564)

When the PR author writes Chinese, the posted /review body was
English-only. fetch-pr now records whether the PR description contains
Han characters (prDescriptionHasHan, detected from the same gh pr view
call and stamped into the plan report), and compose-review renders the
body bilingually off that flag: the English body leads, the complete
Chinese version rides collapsed in a <details><summary>中文说明</summary>
block, and the model footer stays outside the fold. The signal is the
CLI's own — the caller cannot toggle the register of a certified body —
and a local plan has no field, so nothing changes for terminal-only
reviews.

Every deterministic body fragment carries an en/zh pair end to end:
compose-review's clause templates and describeChunkGap phrases, the
coverage disclosures (reasons, publicLabel role subjects via a new
publicLabelZh, the path-free unread-brief reason) and the Step 4/5 gap
texts including the combined same-shape sentence. Fragments with no
deterministic translation — model-written findings, caller echoes,
interpolated errors — ride verbatim in both halves. verificationGaps now
returns structural {subject, reason, subjectZh, reasonZh} entries, which
also removes compose-review's last recover-the-boundary-from-prose parse.

SKILL.md instructs the same format for the model-authored inline
comments: English finding first (marker and suggestion block stay in the
English half — tooling filters on them), full Chinese translation
collapsed beneath, footer last.

Co-authored-by: verify <verify@local>

* feat(autofix): auto-rerun a check that died on infrastructure, once (#7562)

* feat(autofix): auto-rerun a check that died on infrastructure, once

A failed check can be red because the machine died, not the code — a
self-hosted runner losing the server, the disk filling. #7490's E2E
failed with "runner lost communication with the server" and went green
on a rerun. The scan now reruns such a check's failed jobs automatically.

Detection is a conservative annotation whitelist (INFRA_FAILURE_SIGNATURES)
— only unambiguous machine failures, never a test-level timeout, which
could be a real regression. The one-shot guard is run_attempt, not a
marker: a run already retried to attempt 2 and still infra-failing is
persistent, so it is left for a human; after a rerun the attempt
increments, so the next scan will not rerun it. Every step is fail-safe
(any API error → no rerun), it runs only when the PR actually has a
failed check, and the gate carries the same review-address carve-out as
the other check selectors so the loop never reruns its own runs.

This is the transient-infra sibling of #7554 (stale-base): that merges
current main when a check is base-inherited; this reruns when a check
died on the runner. Neither touches a check that is a genuine failure.

Note: rerun-failed-jobs needs the PAT to hold `actions: write`.

* fix(autofix): use POSIX ERE groups in infra-failure regex, cover all signatures in tests (#7562)

* fix(autofix): also treat a git fetch/clone transport death as infra

#6506's checkout died mid-transfer — "fetch-pack: invalid index-pack
output" and "RPC failed; curl 92 ... CANCEL" — which then hung the job
into the 20m limit. That is infra, not the PR (it only touches a doc),
and a re-run made it green. But the infra-signature whitelist did not
cover it, so the auto-rerun did not fire and it waited on a human.

Add `invalid index-pack output` and `RPC failed` — the two canonical
git-transport-death phrases — to INFRA_FAILURE_SIGNATURES. A co-present
job-timeout line does not block the match (one matching line classifies
the run), and a BARE timeout with no transport signature is still left
alone, since it can be a real regression. Both new signatures are pinned
in the test's per-signature loop, plus a case on #6506's real composite
annotation and a bare-timeout-is-not-rerun guard.

* fix(autofix): paginate annotations and filter Autofix runs in infra-rerun loop (#7562)

---------

Co-authored-by: wenshao <wenshao@example.com>
Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>

* fix(serve): detect stale SSE cursors across daemon restarts via epoch token; preserve turn attribution and surface compaction failures in replay (#7458)

* fix(daemon): epoch-token restart detection, compaction attribution, and degraded-snapshot signaling (DAEMON-001/007/008)

* fix(acp-bridge): field-level turn attribution merge and replayDegraded bridge test (#7458)

* fix(serve): skip bus epoch lookup for virtual subagent SSE streams (#7458)

The REST SSE route looked up the bus epoch for every session id, but
virtual subagent sessions ride their own bus and their compound ids are
not in the bridge's byId map, so the lookup threw and aborted the
subscription — breaking subagent event streams. Skip the lookup for the
virtual path and degrade a torn-down real session to a headerless stream
(mirrors the /acp route). Also bumps the daemon browser SDK bundle budget
(167KB -> 168KB) for the epoch fields and declares eventEpoch on
DaemonSession so the create/attach path drops its inline type cast.

* fix(serve): stamp eventEpoch on accepted continuations and surface replayDegraded in the SDK (#7458)

Address three review suggestions:
- POST /session/:id/continue now returns eventEpoch alongside lastEventId,
  mirroring the prompt 202 envelope so continuation-seeded SSE cursors
  detect daemon restarts (DAEMON-001)
- DaemonSessionClient exposes replayDegraded from the load response so SDK
  consumers can prefer the full transcript over a degraded snapshot
- add /acp dispatch-level regression test for the degraded-snapshot stderr
  breadcrumb (fires only when snapshot.degraded is set)

* test(cli): fix load-reply race in the degraded-breadcrumb transport test

Await each session/load reply frame before opening the session stream so
the GET cannot race conn.ownSession() into a 403; addresses the review
Critical on the deg-0 arm.

* fix(serve): allow and expose X-Qwen-Event-Epoch in CORS headers

Cross-origin SSE clients must send the epoch header through preflight and
read it from the response, or stale-cursor detection (DAEMON-001) is
silently disabled for every CORS client.

---------

Co-authored-by: qwen-code-bot <qwen-code-bot@users.noreply.github.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen Autofix <qwen-autofix[bot]@users.noreply.github.com>

* feat(core): Align GenAI telemetry with ARMS (#7536)

* feat(core): align GenAI telemetry with ARMS

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

* fix(core): remove estimated token usage splits

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

* fix(core): address GenAI telemetry review feedback

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>

* fix(serve): avoid TOCTOU race dropping live sessions from list response (#7556)

* Initial plan

* fix(serve): avoid TOCTOU race dropping live sessions from list response

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: 易良 <1204183885@qq.com>

* fix(cli): prevent monitor turns after task_stop (#7573)

---------

Co-authored-by: 秦奇 <gary.gq@alibaba-inc.com>
Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com>
Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
Co-authored-by: Qwen Code Autofix <qwen-code-autofix[bot]@users.noreply.github.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: destire-mio <qppque@gmail.com>
Co-authored-by: destire-mio <248462155+destire-mio@users.noreply.github.com>
Co-authored-by: Dragon <52599892+DragonnZhang@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: 易良 <1204183885@qq.com>
Co-authored-by: jinye <djy1989418@126.com>
Co-authored-by: chinesepowered <nlai@rediffmail.com>
Co-authored-by: ovochouovo <18212194+ovochouovo@users.noreply.github.com>
Co-authored-by: Edenman <67549719+BZ-D@users.noreply.github.com>
Co-authored-by: 克竟 <dingbingzhi.dbz@alibaba-inc.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: ytahdn <1294726970@qq.com>
Co-authored-by: ytahdn <ytahdn@gmail.com>
Co-authored-by: Truraly <94105924+Truraly@users.noreply.github.com>
Co-authored-by: zjgzx1988 <zjgzx1988@hotmail.com>
Co-authored-by: hogeheer499-commits <hogeheer499@gmail.com>
Co-authored-by: hogeheer <267467744+hogeheer499-commits@users.noreply.github.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: wenshao <wenshao@example.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: Nothing Chan <chenliu.cl@alibaba-inc.com>
Co-authored-by: 钉萁 <dingqi.jww@alibaba-inc.com>
Co-authored-by: yuanyuanAli <135116774+yuanyuanAli@users.noreply.github.com>
Co-authored-by: verify <verify@local>
Co-authored-by: qqqys <qys177@gmail.com>
Co-authored-by: callmeYe <512217680@qq.com>
Co-authored-by: Qwen Autofix <qwen-autofix[bot]@users.noreply.github.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
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

4 participants