feat(cli): add daemon Todo stop guard - #6945
Conversation
cb40333 to
1982e29
Compare
|
Please do not rebase or force-push to an active PR as it invalidates existing review comments. Note for future reference, the bots always squash all changes into a single commit automatically as part of the integration. 中文请勿对活跃的 PR 执行 rebase 或 force-push,因为这会使已有的评审评论失效。另外,供日后参考:作为集成流程的一部分,机器人始终会自动将所有改动压缩(squash)为单个提交。 |
doudouOUC
left a comment
There was a problem hiding this comment.
Full review (approach + code). Verdict: approach is sound, implementation quality is high; nothing blocking.
- Right layer: daemon/ACP-only
Sessionguard, TUI/core/TodoWrite untouched; not shipping it as a built-in Stop hook is the right call given the FIFO/mid-turn/background-baseline coupling and thedisableAllHookssemantics. - The trust model is well-conceived: only a successful top-level structured
TodoWriteToolresult arms; args, replayed history, disk state, and MCP shadow tools are rejected; every hard terminal path suspends; budget is bounded per uninterrupted stage with default-off + safe/bare/plan force-off + in-memory-only. - Design doc matches the implementation closely — I cross-checked stop ordering, tool-closure, compaction preservation, and bridge compatibility sections against the code.
- Verified locally on this branch:
daemon-todo-stop-guard.test.ts18/18, fullSession.test.ts343/343 (incl. the 75 guard scenarios),bridgeClient.test.ts+compactionEngine.test.ts118/118.
Findings inline: 1 should-fix (silently swallowed guard-release notification), 2 minor, 2 nits, 1 docs suggestion.
One non-blocking maintainability note: Session.ts grows 6.4k → 7.4k lines with guard touch-points spread across ~40 sites, and #runStopContinuation's beforeSend closure mutates six outer variables (including reassigning nextMessage from inside the callback). Correctness is currently carried by the (excellent) test suite; consider extracting the stop-ordering orchestration into its own module in a follow-up PR.
Re-triage at
|
| Category | Lines |
|---|---|
| Production (core paths) | 10 (settingsSchema.ts) |
| Production (non-core) | ~1,940 (Session, guard state machine, bridge, acpAgent, compaction) |
| Tests | ~5,100 |
| Docs | ~223 |
| Schema | 5 |
Core module exposure is minimal — just one settings schema entry. No escalation needed.
Approach: the scope is large but justified. The guard must coordinate with every session lifecycle path (stop hooks, FIFO, mid-turn input, background agents, monitors, wakeups, cron, retry, rewind, cancellation, compaction). A simpler implementation would just miss edge cases. The safety posture is conservative: bounded to 2 attempts, no Todo text in telemetry, explicit suspension on abort/cancel, and a fresh todo_write required per prompt.
Rebase integrity: clean. The merge-conflict commit only touches context offsets in the experimental settings table — no semantic drift in PR logic.
Moving to code review. 🔍
中文说明
在 fe64ed2a58 的重新审查
自上次在 03d4ffe6ca 的审查以来,分支已 rebase 到最新 main。唯一的新提交是 fe64ed2a58("codex: 解决 PR 合并冲突")——纯合并冲突解决,无逻辑变更。PR 自身的六个提交在 rebase 后 patch 等价。
模板完整 ✓
问题: 已观测且有文档。关联 issue #6946 描述了真实缺口。这不是理论性加固。
方向: 对齐。功能需显式启用,仅作用于 daemon,在 safe/bare/plan 模式下强制关闭。
规模: 核心模块暴露极小(10 行)。非核心生产代码约 1,940 行,测试约 5,100 行。无需 escalation。
方案: 范围大但合理。Guard 必须与所有 Session 生命周期路径协调。安全姿态保守。Rebase 完整性:干净,无语义漂移。
进入代码审查 🔍
— Qwen Code · qwen3.7-max
Reviewed at fe64ed2a58de9a8fc772c47e4d72b80aff8d2768 · re-run with @qwen-code /triage
ytahdn
left a comment
There was a problem hiding this comment.
LGTM. 高质量的实现——状态机设计严谨,guard 模块封装干净,Session 集成正确处理了 mid-turn input、queued prompt、background agents、cancel、compaction、replay 等复杂协调场景。测试覆盖充分(75 guard 场景 + bridge 协议 + E2E streaming replay)。
核心确认:
decide()/commitContinuation()/decideToolClosure()边界条件正确- 用户输入优先级正确:mid-turn 和 queued prompt 先于 guard continuation
- 硬终止路径(cancel/safe/plan)正确 suspend guard
- status event 不暴露 Todo 文本,仅发送 count
- bridge abort listener 正确清理
todoStopGuardAwaitingQueuedPrompt
作者自审到位,open comments 均为非阻塞项。后续可考虑将 #runStopContinuation 的 stop-ordering orchestration 提取为独立模块(beforeSend closure 6+ 外部变量确实需要更好的封装)。
— qwen3.7-plus via Qwen Code /review
Re-verification at
|
| Check | Result |
|---|---|
| Test (ubuntu-latest, Node 22.x) | ✅ pass (24m50s) |
| web-shell E2E Smoke | ✅ pass (5m47s) |
| precheck-pr | ✅ pass |
| Classify PR | ✅ pass |
All CI green at the new head. The previous local verification (1431 unit tests + 4 bundled-daemon E2E on both Linux and macOS at earlier heads) covered the same PR logic that is present after this rebase.
Rebase integrity
The compare between old triage head and new head shows only upstream main commits being folded in. The PR's own six commits are patch-equivalent. The merge-conflict commit (fe64ed2a58) only adjusts markdown column widths in the experimental settings documentation table — todoStopGuard appears exactly once, no duplicate keys, no semantic drift.
中文说明
在 fe64ed2a58 的复验
在干净 rebase 到最新 main 后重新审查。此前在 03d4ffe6ca 的代码审查已覆盖完整 diff——两位独立审查者、7+ 轮、所有 Critical 发现已解决。此后唯一的新提交是 fe64ed2a58("codex: 解决 PR 合并冲突"),仅调整 experimental.artifact 设置表格的 markdown 列宽。未触及 PR 源码逻辑。
代码审查 — 与此前审查一致。状态机、Session 集成、abort 路径缺口修复、通知溢出修复均已验证。
CI 状态:ubuntu 测试(24m50s)、web-shell E2E smoke、precheck 全部通过。
Rebase 完整性:干净。PR 自身的六个提交 patch 等价。合并冲突提交仅调整 markdown 列宽。
— Qwen Code · qwen3.7-max
Reviewed at fe64ed2a58de9a8fc772c47e4d72b80aff8d2768 · re-run with @qwen-code /triage
✅ Local build + real-test verification (Linux)I built and exercised this PR end-to-end on Linux, which the PR description lists as not-yet-tested. Everything is green — including a live run of the bundled Environment: Linux x64 · Node Results
Live daemon runBeyond the suite, I ran the real bundled daemon against a fake model endpoint with
Notes for merge
LGTM from a build/test standpoint on Linux. 中文说明✅ 本地构建 + 真实测试验证(Linux)我在 Linux 上对本 PR 做了端到端的构建与运行验证(PR 描述里 Linux 标注为未测试)。全部通过,其中包括用启用 guard 的 bundle 版 环境: Linux x64 · Node 结果
真实 daemon 运行除测试套件外,我用真实的 bundle daemon 连到一个 fake 模型端点,开启
合并参考
从 Linux 的构建/测试角度看,LGTM。 🤖 Built and tested locally with Claude Code (Opus 4.8, 1M context) on Linux x64. Every command above was run against the PR head; screenshots are captured from those runs. |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Test-only diff chunks (2, 5, 11) were reviewed by the whole-diff Test Coverage Matrix agent and mapped to ~60 test cases covering all acceptance criteria; the territory fan-out did not re-review them.
Not reviewed: chunk 2, chunk 5, chunk 11 — no territory agent reported covering these.
— qwen3.7-max via Qwen Code /review
ytahdn
left a comment
There was a problem hiding this comment.
Reviewed with focus on blocker re-check and core source changes.
Blocker re-check:
- Session.ts:2719 (systemMessage dropped on mid-turn continue) — still stands, but severity is Suggestion-level, not Critical. The systemMessage is informational content meant for the user; dropping it silently is collateral damage from the
continue. Consider emitting it before thecontinue, or adding a comment stating the drop is intentional. - Session.ts:2737 (stopHookIterationCount off-by-one) — still stands, Nice-to-have. Self-correcting on the next iteration.
Core guard module (daemon-todo-stop-guard.ts): No defects. State transitions verified correct across all paths. decide() / commitContinuation() / decideToolClosure() edge cases all handled properly.
Session integration: No Critical defects. All abort/cancel paths properly call suspend(). commitContinuation timing correct (after stream starts). Compaction preserves guard state. Replay events properly emitted. beforeSend closure mutations are sequential and correct (single-threaded, no race).
Overall: High-quality implementation. Author's self-review is thorough. The two open blockers are real but minor — neither blocks merge.
— qwen3.7-plus via Qwen Code /review
Code review — daemon Todo Stop GuardCompanion to my earlier build/test verification comment (Linux, PR head Verdict: LGTM, non-blocking findings only. The state machine held up against every race I could construct on paper. One new [Suggestion] below (a liveness gap the existing findings don't cover), one nit, and my read on the two findings already on the thread. What I traced and confirmed
New findings1. [Suggestion] A stream error inside a related automatic turn can strand deferred automatic work until the next user action. 2. [Nit] The tool-closure attempt's status event reads On the findings already on the thread
Quality / security / tests
中文说明代码审查 — daemon Todo Stop Guard这是对我之前构建/测试验证评论(Linux,PR head 结论:LGTM,仅有非阻塞发现。 我在纸面上构造的所有竞态下状态机都成立。下面有一个既有评论未覆盖的新 [Suggestion](liveness 缺口)、一个 nit,以及我对已有两个发现的判断。 我核实过的关键路径
新发现1. [Suggestion] 相关自动轮次内的流错误可能把延迟的自动工作卡到下一次用户操作。 通知 catch( 2. [Nit] tool-closure 尝试的状态事件文案是 对已有发现的判断
质量 / 安全 / 测试
|
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed — no blockers. Suggestions are inline.
— qwen3.7-max via Qwen Code /review
✅ Re-verification at new head
|
| Area | Command | Result |
|---|---|---|
| Bundle | npm run bundle → node dist/cli.js --version |
✅ boots · 0.19.10 |
| Guard state machine | vitest daemon-todo-stop-guard.test.ts |
✅ 18 passed |
| Session + ACP agent | vitest Session.test.ts acpAgent.test.ts (+guard) |
✅ 607 passed |
| ACP bridge suite | vitest run (14 files) |
✅ 824 passed |
| Daemon streaming E2E | vitest --root integration-tests qwen-serve-streaming (QWEN_SANDBOX=false, bundled dist/cli.js) |
✅ 4 passed |
| Format | prettier --check (19 changed files) |
✅ all match |
| Lint | eslint --max-warnings 0 (15 changed .ts) |
✅ 0 warn / 0 err |
| Settings schema | generate:settings-schema → git diff |
✅ no drift |
| Types — acp-bridge | tsc --noEmit |
✅ 0 errors |
| Types — cli (PR files) | tsc --noEmit, siblings → source |
✅ 0 errors in touched files |
1431 unit tests (607 CLI + 824 bridge) + 4 bundled-daemon E2E, all green. Every command ran against bdbc6267b in an isolated worktree.
The newest commit is load-bearing (controlled experiment)
bdbc6267b adds one focused regression test, accounts for a slow blocking Stop hook after handling mid-turn input. To prove the fix — not just the test — is real, I reverted only the ~20-line Session.ts change back to its parent (c05f2e3d8) while keeping the new test:
- Fixed head: the test passes.
- Fix reverted, test kept: the test fails with
expected 4 to be 1— the Stop hook fires 4× instead of once, because the pre-fixcontinuere-runs the whole stop loop instead of preserving the hook's single invocation (itssystemMessageand cap accounting are lost). The fulldaemon Todo Stop Guarddescribe goes 75 passed / 1 failed — exactly one scenario flips, so the regression is surgical. - Restored: green again, tree clean.
Live bundled daemon at the new head
The streaming E2E spawns the real bundled dist/cli.js serve against a fake model, arms the guard, admits a prompt with no SSE subscriber, then reconnects and replays. Observed at bdbc6267b:
- Exactly 4 marker-bearing model calls (1 original + 2 bounded guard attempts + 1 closure).
- Attempt sequence
[1, 2, 2], three replayabletodo_stop_guardstatus events, exhaustion status emitted. - Todo text never leaks into any guard event;
turn_completereplays correctly after reconnect.
This matches my first-commit run exactly — the review-feedback refactor + mid-turn fix did not regress the bounded-continuation loop, the exhaustion status, the no-leak guarantee, or disconnected-client replay.
What the two new commits changed (and how it behaves)
Session.ts— a mid-turn queued prompt arriving while a Stop hook is running now falls through to process the hook's output, so itssystemMessageand blocking-cap accounting survive;guardDecisionis suppressed and the turn ends after the hook is honored. Continuation events are also gated oncommitContinuationsucceeding (no emit for an attempt that never commits).bridge.ts— the fire-and-forget queued-prompt release now logs failures to stderr instead of swallowing them (the thread finding).
Notes for merge / environment honesty
- Two workspace-wide build steps (
npm run build, fullnpm run typecheck) fail in my symlinked worktree, but only on pre-existing, PR-unrelated debt: a stale@qwen-code/channel-base.d.tsfor the (untouched)dingtalkpackage, aChannelMemoryCallbacksmismatch in non-PR files, and a@lydell/node-ptydeclaration artifact. None touch the guard; a cleannpm ci(as in my Linux pass) never hits them. The bundle (esbuild-from-source), the acp-bridge typecheck, and a source-mapped cli typecheck of the PR files are all clean. - Opt-in posture unchanged:
default: false,requiresRestart: true,showInDialog: false; forced off in safe / bare / Approval-plan modes. - Out of scope (unchanged): Windows (the streaming suite skips non-POSIX) and process-crash recovery (intentionally unsupported per the design doc).
LGTM at bdbc6267b from a build/test standpoint — the review-feedback commits are correct, tested, and load-bearing.
中文说明
✅ 在新 head bdbc6267b 上的复验(macOS)
本次在 当前 PR head 上重跑了完整的本地构建 + 真实测试验证。我之前的构建/测试验证和代码审查都钉在首个提交 1982e29a0。此后新增了两个提交:
c05f2e3d8— 处理 PR review 反馈bdbc6267b— fix(cli): 在 mid-turn 输入下保留 Stop hook 输出
这两个提交正是回应 bot 的 [Critical](mid-turn drain 丢弃 Stop hook 的决定/systemMessage)以及线程里 fire-and-forget release 的发现,所以本次复验的重点就是确认它们通过、且最新的修复确实起作用。全部通过,且该修复是「load-bearing」的。本次运行在 macOS(我第一次是 Linux)。
结果 —— 在 bdbc6267b 上全绿
(见上方验证矩阵截图)
| 项目 | 命令 | 结果 |
|---|---|---|
| 打包 | npm run bundle → node dist/cli.js --version |
✅ 可启动 · 0.19.10 |
| Guard 状态机 | vitest daemon-todo-stop-guard.test.ts |
✅ 18 通过 |
| Session + ACP agent | vitest Session.test.ts acpAgent.test.ts(含 guard) |
✅ 607 通过 |
| ACP bridge 套件 | vitest run(14 文件) |
✅ 824 通过 |
| Daemon streaming E2E | vitest --root integration-tests qwen-serve-streaming(QWEN_SANDBOX=false,bundle 版 dist/cli.js) |
✅ 4 通过 |
| 格式 | prettier --check(19 个改动文件) |
✅ 全部符合 |
| Lint | eslint --max-warnings 0(15 个改动 .ts) |
✅ 0 warn / 0 err |
| Settings schema | generate:settings-schema → git diff |
✅ 无漂移 |
| 类型 — acp-bridge | tsc --noEmit |
✅ 0 报错 |
| 类型 — cli(PR 文件) | tsc --noEmit,sibling → 源码 |
✅ 改动文件 0 报错 |
1431 个单测(607 CLI + 824 bridge)+ 4 个 bundle daemon E2E 全绿。所有命令都在隔离 worktree 中针对 bdbc6267b 运行。
最新提交是 load-bearing 的(对照实验)
bdbc6267b 新增了一个聚焦回归测试 accounts for a slow blocking Stop hook after handling mid-turn input。为证明「修复本身」而非仅仅测试是真实的,我只把 Session.ts 的约 20 行改动回退到其父提交(c05f2e3d8),保留新测试:
(见上方对照实验截图)
- 修复态: 测试通过。
- 回退修复、保留测试: 测试报
expected 4 to be 1—— Stop hook 触发了 4 次而非 1 次,因为修复前的continue会重跑整个 stop 循环,而不是保留 hook 的单次调用(其systemMessage与 cap 计数被丢弃)。整个daemon Todo Stop Guarddescribe 变为 75 通过 / 1 失败 —— 只有 1 个场景翻红,说明回归是外科手术式的、精准的。 - 恢复后: 重新全绿,工作树干净。
新 head 上的真实 bundle daemon
streaming E2E 会拉起真实 bundle 的 dist/cli.js serve 连到 fake 模型,开启 guard,在没有 SSE 订阅者时接纳一个 prompt,然后重连并回放。在 bdbc6267b 实测:
(见上方 live guard demo 截图)
- 恰好 4 次带 marker 的模型调用(1 次原始 + 2 次有界 guard 尝试 + 1 次 closure)。
- attempt 序列
[1, 2, 2],3 个可回放todo_stop_guard状态事件,发出耗尽状态。 - guard 事件中从不泄露 Todo 文本;重连后
turn_complete正确回放。
这与我第一次(首提交)的实测完全一致 —— review 反馈引入的重构 + mid-turn 修复没有破坏有界 continuation 循环、耗尽状态、无泄露保证或断线客户端回放。
两个新提交改了什么(以及行为)
Session.ts—— 当 mid-turn 排队 prompt 在 Stop hook 运行期间 到达时,现在会继续向下处理 hook 的输出,使其systemMessage和阻塞 cap 计数得以保留;guardDecision被抑制,hook 被处理后结束该 turn。continuation 事件也改为仅在commitContinuation成功时才发(未 commit 的尝试不发事件)。bridge.ts—— fire-and-forget 的排队 prompt release 失败现在会写入 stderr 而不是静默吞掉(线程里的发现)。
合并参考 / 环境说明(如实)
- 两个全仓构建步骤(
npm run build、完整npm run typecheck)在我的 symlink worktree 中失败,但只因与本 PR 无关的既有欠账:(未改动的)dingtalk包引用了过期的@qwen-code/channel-base.d.ts、非 PR 文件里的ChannelMemoryCallbacks不匹配,以及@lydell/node-pty声明缺失。它们都不涉及 guard;干净的npm ci(如我 Linux 那次)不会触发。bundle(esbuild 直接编译源码)、acp-bridge typecheck,以及对 PR 文件做 source-mapped 的 cli typecheck 均干净。 - opt-in 姿态不变:
default: false、requiresRestart: true、showInDialog: false;在 safe / bare / Approval-plan 模式下强制关闭。 - 未覆盖(不变):Windows(streaming 套件在非 POSIX 上跳过)与进程崩溃恢复(按设计明确不支持)。
从构建/测试角度,在 bdbc6267b 上 LGTM —— review 反馈的两个提交正确、有测试覆盖、且是 load-bearing 的。
🤖 Built and tested locally with Claude Code (Opus 4.8, 1M context) on macOS (arm64). Every command above was run against PR head bdbc6267b; screenshots are captured from those runs.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
|
Confidence: 4/5 — clean rebase, CI green, all prior review findings addressed. This is a re-triage after the branch was rebased onto latest The previous two passes by independent reviewers covered the full implementation in depth: the Two non-blocking items from the previous review remain open for follow-up work (an automatic-turn liveness suggestion and a status-event wording nit) — appropriate to defer after 7+ review rounds. Approving. ✅ 中文说明置信度:4/5 — 干净的 rebase,CI 全绿,所有先前审查发现已解决。 这是在分支 rebase 到最新 此前两位独立审查者已深入覆盖完整实现。所有 Critical 发现已通过针对性回归测试解决。新 head 上的 CI 全绿。 两条非阻塞建议留给后续工作处理。 批准 ✅ — Qwen Code · qwen3.7-max Reviewed at |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
|
@qwen-code /resolve |
|
Qwen Code attempted to resolve merge conflicts but the run did not complete successfully. Check the workflow run for full logs. |
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
03d4ffe to
fe64ed2
Compare
|
@qwen-code /triage |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline. 2 Suggestion-level finding(s) could not be anchored to the diff; see the terminal output. Not reviewed: coverage — no plan was given, so this run cannot show that any of the diff was read.
— qwen3.7-max via Qwen Code /review
wenshao
left a comment
There was a problem hiding this comment.
Not reviewed: Verification agent — no generated brief exists.
Not reviewed: Verification agent — no generated brief exists.
— Codex GPT-5 via Qwen Code /review
PR Review: feat(cli): add daemon Todo Stop GuardOverviewThis PR adds an opt-in Todo Stop Guard feature for daemon/ACP sessions that automatically continues execution when a model stops naturally with unfinished top-level Todo items. The implementation is well-designed with appropriate safety boundaries. ✅ Strengths1. Security & Safety Design
2. State Machine Implementation
3. Priority Handling
This ensures user input always wins over automatic continuation. 4. Test Coverage
5. Documentation
|





What this PR does
This PR adds an opt-in, daemon/ACP-only Todo Stop Guard. After a successful top-level
todo_writeleaves unfinished items, a daemon Session may automatically continue the current work chain at most twice instead of silently ending on a natural model stop. The guard is disabled by default, does not affect TUI or the shared Core agent loop, and is force-disabled in safe, bare, and plan modes.The implementation reuses the existing Session continuation loop and coordinates external Stop hooks, mid-turn input, FIFO prompts, background agents and commands, monitors, wakeups, notifications, cron turns, retries, rewinds, reconnect replay, cancellation, compaction, and hard terminal paths. Guard status is emitted as replayable metadata without exposing Todo text, and the ACP bridge now reports whether a complete non-aborted prompt is already queued so user input always wins over automatic continuation.
The change also adds the experimental setting schema, daemon configuration documentation, a design document, focused state and Session coverage, bridge protocol coverage, and a bundled daemon streaming regression test.
Why it's needed
Long-running daemon clients can disconnect or wait asynchronously while the accepted task continues in the Session. A model may naturally stop even though its current top-level Todo list still contains pending work, leaving the daemon chain incomplete and forcing a client or user to notice and manually continue it. A bounded, explicitly enabled Session guard closes that gap while preserving permissions, queued user input priority, existing Stop-hook semantics, and resource-protection termination behavior.
Reviewer Test Plan
How to verify
qwen servewithexperimental.todoStopGuarddisabled and confirm natural stops and external Stop hooks behave exactly as before.todo_writeleaves an unfinished item, and confirm the Session makes no more than two additional main-model calls, emits replayabletodo_stop_guardstatus events, and never exposes Todo text in those events.todo_writebefore the Guard can run.disableAllHooksdoes not disable an explicitly enabled built-in Guard.Automated verification completed locally after rebasing onto the latest
main: 638 CLI tests, 513 ACP bridge tests, and 4 bundledqwen servestreaming integration tests passed. ESLint, Prettier, generated settings schema checks, diff checks, the full repository build and typecheck, the CLI-only build, and bundle generation also passed.Evidence (Before & After)
Before: an accepted daemon task can naturally end with trusted top-level Todos still pending, and no built-in Session mechanism schedules bounded continuation.
After: when the experimental setting is enabled, the Session performs at most two consecutive automatic continuations without new user input, exposes each attempt as a replayable status event, yields to user and related background input, and reports an explicit exhausted status if unfinished items remain.
Tested on
Environment (optional)
macOS, Node.js 22.22.3, bundled
dist/cli.js,QWEN_SANDBOX=falsefor daemon integration coverage.Risk & Scope
experimental.todoStopGuard: trueconfiguration and a daemon restart.Linked Issues
Closes #6946
中文说明
本 PR 做了什么
本 PR 增加了一个需显式启用、仅作用于 daemon/ACP 的 Todo Stop Guard。顶层
todo_write成功执行且仍有未完成事项时,daemon Session 可以在模型自然停止后最多自动继续当前工作链两次,而不是静默结束。Guard 默认关闭,不影响 TUI 或共享的 Core agent loop,并且在 safe、bare 和 plan 模式中强制关闭。实现复用了现有 Session continuation loop,并协调了外部 Stop hooks、mid-turn 输入、FIFO prompt、后台 Agent 和命令、Monitor、wakeup、notification、cron 轮次、retry、rewind、断线回放、取消、压缩和硬终止路径。Guard 状态以可回放 metadata 发送且不暴露 Todo 文本;ACP bridge 还会报告是否已有完整且未取消的 prompt 排队,确保用户输入始终优先于自动 continuation。
本次变更还增加了实验设置 schema、daemon 配置文档、设计文档、状态机与 Session 的聚焦测试、bridge 协议测试,以及基于 bundle 的 daemon streaming 回归测试。
为什么需要它
长时间运行的 daemon 客户端可能在已接纳任务仍由 Session 执行时断连或异步等待。模型可能在当前顶层 Todo 列表仍有 pending 工作时自然停止,导致 daemon 工作链不完整,并要求客户端或用户主动发现后手工继续。有界且显式启用的 Session Guard 可以弥补这个缺口,同时保持权限系统、排队用户输入优先级、现有 Stop-hook 语义和资源保护终止行为不变。
Reviewer 测试计划
如何验证
experimental.todoStopGuard的情况下启动qwen serve,确认自然停止和外部 Stop hooks 的行为与以前完全一致。todo_write且留下未完成事项的 prompt,确认 Session 最多增加两次主模型调用、发送可回放的todo_stop_guard状态事件,并且事件不暴露 Todo 文本。todo_write才能运行 Guard。disableAllHooks不会关闭已显式启用的内建 Guard。rebase 到最新
main后,本地自动验证已经完成:638 个 CLI 测试、513 个 ACP bridge 测试和 4 个 bundle 版本的qwen servestreaming 集成测试通过。ESLint、Prettier、生成 settings schema 的检查、diff 检查、全仓 build 和 typecheck、CLI-only build 和 bundle 生成也均通过。证据(Before & After)
Before:已接纳的 daemon 任务可能在可信顶层 Todos 仍有未完成事项时自然结束,Session 没有内建机制安排有界 continuation。
After:启用实验设置后,Session 在没有新用户输入时最多连续自动继续两次,将每次尝试作为可回放状态事件暴露,让行用户输入和相关后台输入,并在仍有未完成事项时发送明确的耗尽状态。
测试平台
环境(可选)
macOS、Node.js 22.22.3、bundle 后的
dist/cli.js,daemon 集成覆盖使用QWEN_SANDBOX=false。风险与范围
experimental.todoStopGuard: true并重启 daemon。关联 Issues
关联并在合并时关闭 #6946。