Skip to content

feat(goal): tell the model the objective changed, once, when it changed - #10013

Merged
qqqys merged 7 commits into
QwenLM:mainfrom
qqqys:goal/b3-objective-updated
Aug 27, 2026
Merged

feat(goal): tell the model the objective changed, once, when it changed#10013
qqqys merged 7 commits into
QwenLM:mainfrom
qqqys:goal/b3-objective-updated

Conversation

@qqqys

@qqqys qqqys commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

Splits the last line of the Goal continuation prompt into the half that is true every turn and the half that is true once.

The line today reads: "The objective in that data block is the current one and supersedes any earlier Goal objective in this conversation, including one you already started working on." The standing half is a guard — objective-shaped text reaches the model from places the runtime does not control (earlier turns, tool output, file contents), so the data block has to be asserted as the only objective on every turn. That half stays, minus the change-implication.

The other half warns that the objective changed. It went out identically on turn 2 and turn 40 of Goals whose objective never moved, so by the time an edit finally landed, the warning had been noise for the whole run. It becomes a separate line, sent only on the first continuation after the objective actually changed, and it now says something actionable: stop work that only served the previous objective.

The runtime decides by remembering the (goalId, revision) pair whose objective it last handed to a host. A different pair means an edit bumped the revision or a replace minted a new Goal over the old one; no previous pair means this Goal's first continuation, which supersedes nothing. Keying on the revision alone would miss replace, whose new Goal starts at revision 1 exactly like a fresh create. A start the host refused puts the announcement back, so a retried continuation still carries the notice. The flag rides the host boundary the same way verifierFeedback already does, through all three hosts.

Why it's needed

A warning that fires on every turn regardless of whether anything happened is not a warning. /goal edit is the one moment in a Goal's life when the model is holding an objective that is now wrong — it may have a plan, partial output, and tool state built for it — and that is exactly the moment the current prompt says nothing different from the previous forty. This is the objective-drift line of work (#9581 collected the three hosts' prompts into one renderer, #9834 converged their contract); this PR is the part that makes the drift signal legible when drift actually occurs.

Only continuations announce an objective — a user turn carries the user's own text — so the state is per-continuation and held in memory rather than on the record. Losing it across a daemon restart costs one prompt line, while the objective itself travels in the data block on every turn and get_goal stays authoritative.

Reviewer Test Plan

How to verify

  • cd packages/core && npx vitest run src/goals/ — 404 tests, 16 files. New: two prompt-rendering cases (the notice appends only when flagged, and sits above verifier feedback so it is read before feedback about a turn taken under the old objective), and five runtime cases covering the one-shot semantics end to end: never for an unchanged Goal (including its first continuation), once after edit then quiet, once after replace, never across pause/resume, and redelivery when the host refuses the start.
  • cd packages/cli && npx vitest run src/ui/hooks/useMessageQueue.test.ts src/ui/hooks/useGeminiStream.test.tsx src/nonInteractiveCli.test.ts src/acp-integration — 40 + 233 + 130 + 1739 pass. useGeminiStream.test.tsx held the only hardcoded copy of the old line; it now asserts the reworded standing guard.
  • Mutation probes run during development: dropping the roll-back on a refused start fails exactly the redelivery test (113 others green); firing whenever any previous pair exists fails 3; keying on the revision alone fails exactly the replace test (113 others green).
  • npx tsc --noEmit clean for goal code in both packages/core and packages/cli. prettier + eslint clean on the nine changed files.

Evidence (Before & After)

Prompt tail on an ordinary continuation, before and after — one line shorter, and no longer claims a change happened:

- The objective in that data block is the current one and supersedes any earlier Goal objective in this conversation, including one you already started working on.
+ The objective in that data block is the current one and supersedes any other Goal objective text in this conversation.

First continuation after /goal edit, after this PR (the standing guard, then the notice):

The objective in that data block is the current one and supersedes any other Goal objective text in this conversation.
The Goal objective changed since your last turn: the objective above replaces the one you were working on. Stop work that only served the previous objective, and carry over only what also serves this one.

Tested on

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

Environment (optional)

N/A (unit tests only).

Risk & Scope

  • Main risk or tradeoff: the prompt text changes for every Goal turn, so a model tuned to the exact old wording would see a different tail. The change is a strict reduction of an existing sentence plus a conditional line, and the golden-prompt tests pin both renderings byte for byte.
  • Not validated / out of scope: the in-memory announcement is lost across a daemon restart, so an edit whose continuation was in flight during a restart delivers no notice — a deliberate trade rather than a new persisted record field, documented at the declaration. Nothing here changes what get_goal returns or how objectives are stored.
  • Breaking changes / migration notes: none. objectiveUpdated is optional on the host boundary; a host that ignores it renders exactly the standing prompt.

Linked Issues

中文说明

这个 PR 做了什么

把 Goal 续跑提示词的最后一行拆成「每轮都成立的那一半」和「只成立一次的那一半」。

现在这行是:「数据块中的 objective 是当前的,并取代本对话中任何更早的 Goal objective,包括你已经开始处理的那个。」 常驻的那一半是护栏——形似 objective 的文本会从运行时无法控制的地方到达模型(更早的轮次、工具输出、文件内容),所以必须每轮都声明数据块是唯一的 objective。这一半保留,去掉其中暗示「发生了变更」的措辞。

另一半警告 objective 变了。它在 objective 从未变动的 Goal 的第 2 轮和第 40 轮上一模一样地发出,所以等到真的发生一次编辑时,这条警告已经当了整轮运行的噪声。现在它成为独立的一行,只在 objective 真正变更后的第一次续跑发出,并且给出可执行的指令:停止只服务于旧 objective 的工作。

运行时的判断依据是记住「上一次交给 host 的那个 objective 所属的 (goalId, revision) 组合」。组合不同意味着 edit 抬高了 revision,或者 replace 在旧 Goal 之上铸造了新 Goal;没有上一个组合则说明这是该 Goal 的首次续跑,不取代任何东西。只看 revision 会漏掉 replace——它的新 Goal 和全新创建一样从 revision 1 开始。被 host 拒绝的启动会把宣告状态放回去,因此重试的续跑仍然带着这条通知。该标志沿用 verifierFeedback 已有的方式穿过 host 边界,三个 host 一致。

为什么需要

一条无论是否发生变化都每轮发出的警告,不是警告。/goal edit 是 Goal 生命中唯一一个「模型手里的 objective 已经作废」的时刻——它可能已经有了针对旧 objective 的计划、部分产出和工具状态——而恰恰在这个时刻,当前的提示词与之前四十轮说的一模一样。这是 objective 漂移这条线的收尾(#9581 把三个 host 的提示词收进单一渲染器,#9834 收敛了它们的契约),本 PR 让漂移信号在漂移真正发生时可被识别。

只有续跑会宣告 objective——用户轮次带的是用户自己的文本——所以该状态按续跑维护并保存在内存中,而不是记录字段上。跨 daemon 重启丢失它的代价是少发一行提示,而 objective 本身每轮都在数据块里,get_goal 依然是权威。

评审验证计划

如何验证

  • cd packages/core && npx vitest run src/goals/——404 个测试,16 个文件。新增:两个提示词渲染用例(仅在置位时追加该行;该行位于 verifier feedback 之上,因为反馈针对的是在旧 objective 下进行的那一轮,必须先读到变更通知),以及五个 runtime 用例端到端覆盖一次性语义:objective 未变的 Goal 永不发出(含首次续跑)、edit 后发一次随即安静、replace 后发一次、pause/resume 全程不发、host 拒绝启动时重新投递。
  • cd packages/cli && npx vitest run src/ui/hooks/useMessageQueue.test.ts src/ui/hooks/useGeminiStream.test.tsx src/nonInteractiveCli.test.ts src/acp-integration——40 + 233 + 130 + 1739 全过。useGeminiStream.test.tsx 是旧文案唯一的硬编码副本,现已改为断言重新措辞后的常驻护栏。
  • 开发期间的变异检验:去掉拒绝启动时的回滚,恰好挂重新投递测试(其余 113 绿);只要存在上一个组合就发出,挂 3 个;只看 revision,恰好挂 replace 测试(其余 113 绿)。
  • packages/corepackages/clinpx tsc --noEmit 在 goal 相关代码上干净;九个改动文件 prettier + eslint 干净。

证据(前后对比)

普通续跑的提示词末尾,前后对比——少一行,且不再声称发生了变更:

- The objective in that data block is the current one and supersedes any earlier Goal objective in this conversation, including one you already started working on.
+ The objective in that data block is the current one and supersedes any other Goal objective text in this conversation.

/goal edit 之后的第一次续跑(常驻护栏,随后是通知):

The objective in that data block is the current one and supersedes any other Goal objective text in this conversation.
The Goal objective changed since your last turn: the objective above replaces the one you were working on. Stop work that only served the previous objective, and carry over only what also serves this one.

已测试平台

Linux ✅;macOS / Windows ⚠️(CI 覆盖)。

环境(可选)

N/A(仅单元测试)。

风险与范围

  • 主要风险或权衡:提示词文本对每个 Goal 轮次都发生变化,因此依赖旧措辞的模型会看到不同的结尾。该变更是对现有句子的严格削减加一条条件行,黄金提示词测试对两种渲染都做了逐字节固定。
  • 未验证/范围外:内存中的宣告状态会随 daemon 重启丢失,因此一次 edit 的续跑若恰好在重启期间在途,则不会发出通知——这是刻意的取舍而非新增持久化记录字段,已在声明处注明。本 PR 不改变 get_goal 的返回内容,也不改变 objective 的存储方式。
  • 破坏性变更/迁移说明:无。objectiveUpdated 在 host 边界上是可选的;忽略它的 host 渲染出的就是常驻提示词。

关联 Issue

The continuation prompt ended every turn with "the objective in that
data block is the current one and supersedes any earlier Goal
objective in this conversation, including one you already started
working on." That sentence does two jobs, and only one of them is
true every turn.

The standing half is a guard: objective-shaped text reaches the model
from places the runtime does not control -- earlier turns, tool
output, file contents -- so the data block has to be asserted as the
only objective on every turn. That half stays.

The other half warns that the objective changed. It went out on turn 2
and turn 40 of Goals whose objective never moved, so by the time an
edit finally landed the warning had been identical noise for the whole
run and carried nothing. It is now a separate line sent only on the
first continuation after the objective actually changed.

The runtime decides by remembering the (goalId, revision) pair whose
objective it last handed to a host: a different pair means an edit
bumped the revision or a replace minted a new Goal over the old one,
and no previous pair means this Goal's first continuation, which
supersedes nothing. Keying on the revision alone would miss replace,
whose new Goal starts at revision 1 like a fresh create. A start the
host refused puts the announcement back, so a retried continuation
still carries the notice.

Only continuations announce an objective -- a user turn carries the
user's own text -- so the state is per-continuation and held in
memory: losing it across a restart costs one prompt line, while the
objective itself travels in the data block every turn and get_goal
stays authoritative.

Mutation probes: dropping the roll-back on a refused start fails the
redelivery test (113 green); firing whenever any previous pair exists
fails 3; keying on the revision alone fails the replace test (113
green).
@qqqys

qqqys commented Aug 25, 2026

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 Aug 25, 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. This is a fork PR, so the first round comes from the next scheduled scan (usually within minutes). Remove the autofix/takeover label (or comment @qwen-code /takeover stop) to release.

中文说明

🤝 已接管:autofix 循环现在管理此 PR —— 将持续处理新的评审反馈与 base 冲突,直到移除标签或达到轮次上限。本 PR 来自 fork,首轮处理将由下一次定时扫描执行(通常几分钟内)。移除 autofix/takeover 标签(或评论 @qwen-code /takeover stop)即可释放。

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

Qwen Triage finishedview run. See the stage comments in this thread for the result.

Qwen Triage 已完成 —— 查看运行。结果见本线程中的各阶段评论。

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

Re-run on the current head after the autofix review rounds.

  • Template: complete, bilingual ✓
  • Problem: real, and verifiable in main as it stands — goal-continuation-prompt.ts ships "…supersedes any earlier Goal objective … including one you already started working on" identically on every continuation turn, so on the one turn where a change actually happened (/goal edit) the line carries zero information. Observed design gap in the current prompt contract, not a theoretical concern.
  • Direction: aligned. Final installment of the objective-drift series; both predecessors (refactor(goal): render Goal continuation prompts from one core renderer #9581 one renderer, fix(goal): converge the three continuation prompts on one guarded contract #9834 one contract) are already merged. No new scope invented.
  • Size: core paths touched (packages/core/src/goals/** plus three host files in packages/cli). 189 production lines (131 core renderer/runtime, 58 host plumbing) vs 749 test lines; no generated/schema changes. Well under every escalation threshold.
  • Approach: minimal — the flag rides the existing verifierFeedback/windDown host plumbing, and the announcement state is in-memory with the daemon-restart tradeoff documented at the declaration. One note for the record: since the first review round the implementation moved from (goalId, revision)-pair keying to content keying plus host delivery marks (markTurnDelivered) — strictly better, since a no-op edit can't fire a false notice and a turn dropped before the model sees it leaves the notice owed. The PR description, however, still tells the old design story ("remembering the (goalId, revision) pair", "five runtime cases" — there are now 17 runtime + 2 renderer cases). Worth refreshing before merge; not blocking.
  • Risk: packages/cli/src/acp-integration/session/Session.ts matches the high-risk path list (acp-integration correlates with post-merge reverts). The change there is +31/−1 — an optional field on the turn record plus a delivery mark at the exact site where the model request is issued — and Stage 2 reviewed it at full depth, leaning on the PR's own CI plus the new Session tests to settle.

Moving on to code review. 🔍

中文说明

在 autofix 处理完评审意见后,对当前 head 重新运行。

  • 模板:完整、双语 ✓
  • 问题:真实存在,且可在 main 现有代码中核实——goal-continuation-prompt.ts 在每次续跑时都原样发出"…取代任何更早的 Goal objective……包括你已经开始处理的那个",因此在变更真正发生的那一轮(/goal edit)上这句话不携带任何信息。这是当前提示词契约中可观测到的设计缺陷,不是理论性问题。
  • 方向:对齐。objective 漂移系列的收尾;前两部(refactor(goal): render Goal continuation prompts from one core renderer #9581 单一渲染器、fix(goal): converge the three continuation prompts on one guarded contract #9834 单一契约)均已合入。没有新增范围。
  • 规模:触及核心路径(packages/core/src/goals/**packages/cli 的三个 host 文件)。生产代码 189 行(核心渲染器/运行时 131 行,host 接线 58 行),测试 749 行;无生成/schema 变更。远低于任何升级阈值。
  • 方案:最小化——标志沿用 verifierFeedback/windDown 已有的 host 接线,宣告状态保存在内存中,跨 daemon 重启的取舍已在声明处注明。一点说明:自首轮评审后,实现已从 (goalId, revision) 组合键改为按内容键控加 host 投递确认(markTurnDelivered)——这是严格更优的设计:无实际变化的 edit 不会误发通知,模型未看到就被丢弃的轮次会把通知留给下一次续跑。但 PR 描述仍在讲旧设计("记住 (goalId, revision) 组合"、"五个运行时用例"——现在实际是 17 个运行时 + 2 个渲染器用例)。建议合入前刷新描述;不阻塞。
  • 风险packages/cli/src/acp-integration/session/Session.ts 命中高风险路径清单(acp-integration 与合入后回滚相关)。该处改动为 +31/−1——轮次记录上的一个可选字段,加在模型请求真正发出位置的投递确认——Stage 2 已全深度审查,并依赖该 PR 自身的 CI 与新增 Session 测试来定论。

进入代码审查。🔍

Qwen Code · qwen3.8-max

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

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

Code review

My independent proposal before reading the diff: track the last objective the model actually received, fire a one-shot notice when the next continuation differs, and commit the announcement on real delivery rather than acceptance — because every host resolves startGoalTurn at enqueue time. The PR matches this and then goes one step further in two places I'd have gotten subtly wrong:

  • Content keying, not identity+revision. objectiveUpdated compares the new objective text against announcedObjective, so an edit that bumps the revision without changing the text stays silent. A (goalId, revision) key would have fired a false "stop work" notice for a no-op edit.
  • Delivery marks instead of rollback-on-settle. Each host calls markTurnDelivered at its real send site; settle commits the announcement only if that mark landed. A turn dropped before the model sees it (TUI Escape, ACP cancelPendingPrompt) leaves the notice owed to its replacement; a delivered turn that settles through releaseTurn (ACP degraded-persistence fallback) keeps it.

What I verified by reading the code against the worktree:

  • Single decision point. The only production startGoalTurn call site is flushContinuation in goal-runtime.ts, so every announcement decision flows through the one place the diff touches. User turns go through beginTurn and never carry the notice, matching the stated rationale.
  • Every send site marks delivery. TUI: immediately before sendMessageStream, after every early-return path. Headless: all three buildGoalContinuationParts sites — the slash continuation and both promotion branches — each pinned by its own test, two of them asserting the mark precedes the send via invocation order. ACP: at the exact site where the model request is issued, gated on origin === 'runtime', with the catch path settling undelivered.
  • Settle is race-safe and idempotent. The refused-start discard sits inside the isCurrentPermit guard; settleCurrentTurnAnnouncement no-ops on a second call; a delivery mark for a stale turn key is ignored (tested). All three settle sites (finishTurn, releaseTurn, dispatch invalidatesPermit) agree on commit-if-delivered, discard-otherwise.
  • Lifecycle resets are right. clear and terminal complete reset announcedObjective (a fresh Goal after either is a new work item — pinned by the replace-over-accepted and cleared-then-create tests); blocked keeps it (suspended, not ended — pinned by blocked → edit → resume firing).
  • Consumer audit. AppContainer.tsx's host is a pass-through into enqueueGoalTurn, so it needs no change; every GoalRuntime double in the repo is a partial cast and every markTurnDelivered call site is wrapped in try/catch, so the new interface method breaks nothing; no leftover copy of the old supersede line exists anywhere, including docs.

No critical findings. One suggestion: the PR description is stale relative to the implementation — it still describes the earlier (goalId, revision)-pair design, "five runtime cases" (there are now 17 + 2 renderer cases), and mutation probes aimed at the old keying. The code's own docblocks tell the true story well; the body should catch up before merge so the changelog-of-record isn't misleading.

The one piece that changed since the previous review round is the delivery-mark lifecycle — worth the picture:

sequenceDiagram
    participant P1 as Goal runtime
    participant P2 as Host (TUI, headless, ACP)
    participant P3 as Model
    P1->>P1: flushContinuation compares against the last delivered objective
    P1->>P2: startGoalTurn, accepted with the flag attached
    P2->>P3: continuation prompt sent
    P2->>P1: markTurnDelivered with the turn key
    alt turn settles after delivery
        P1->>P1: commit the announcement, notice never fires again
    else dropped before delivery
        P1->>P1: discard the announcement, notice stays owed to the replacement
    end
Loading

CI test evidence

Fetched once for the reviewed commit via the check-runs API — no polling. There are no red checks on this head: all three pull_request workflow runs (Qwen Code CI, Security Checks, SDK Java) completed successfully, and zero remain pending. The skipped macOS/Windows/integration checks are the repo baseline pattern, verified against the recently merged #10086 where the identical set skips — nothing here is caused by this PR.

Check Conclusion
Test (ubuntu-latest, Node 22.x) success
Test (macos-latest, Node 22.x) skipped (repo baseline)
Test (windows-latest, Node 22.x) skipped (repo baseline)
Integration Tests (CLI, No Sandbox) skipped (repo baseline)
web-shell E2E Smoke (ubuntu-latest, Node 22.x) success
Desktop Shell (ubuntu-22.04) success
Desktop Shell (windows-2022) success
Dependency CVE audit success
Secret scan (TruffleHog) success

Sandboxed verification would settle the remaining behavioural claim: @qwen-code /verify — that on the built artifact the notice crosses the host boundary exactly when the objective changed from what the model last received, A/B against the base build. The unit suite pins exact flag sequences at the runtime boundary and the host-side marks via invocation-order spies, which is dense, but it is not an A/B against base. Naming it explicitly because this PR touches the high-risk acp-integration path; the author has write access, so a maintainer can trigger it directly.

Not verified: the model-facing effect of the reworded prompt — that is a prompt-engineering claim no CI here can measure; the golden tests pin both renderings byte-for-byte and the runtime tests pin the flag mechanics instead. TUI capture: N/A — the change has no terminal surface, and CI-path runs never execute PR code.

中文说明

代码审查

读 diff 之前我独立给出的方案是:记住模型上一次真正收到的 objective,下一次续跑不同时发一次性通知,并以真实投递而非接受为准来提交宣告——因为所有 host 都在入队时就让 startGoalTurn 解析。PR 与这个方案一致,并且在两个我大概率会做错的地方更进一步:

  • 按内容键控,而不是身份+revision。 objectiveUpdated 用新的 objective 文本与 announcedObjective 比较,因此只抬 revision 不改文本的 edit 保持安静。(goalId, revision) 键会在无实际变化的 edit 上误发"停止工作"通知。
  • 投递确认取代结算即回滚。 每个 host 在真实发送点调用 markTurnDelivered;结算时只有确认已投递才提交宣告。模型未看到就被丢弃的轮次(TUI 的 Escape、ACP 的 cancelPendingPrompt)把通知留给替补续跑;已投递但经由 releaseTurn 结算的轮次(ACP 降级持久化回退)保留宣告。

对照 worktree 逐点核实:

  • 单一决策点。 生产中唯一的 startGoalTurn 调用在 goal-runtime.tsflushContinuation,所有宣告决策都经过 diff 触及的这一个位置。用户轮次走 beginTurn,从不携带通知,与理由陈述一致。
  • 每个发送点都标记投递。 TUI:紧贴 sendMessageStream 之前、所有提前返回之后。无头模式:全部三处 buildGoalContinuationParts 站点——slash 续跑与两个晋升分支——各有专属测试固定,其中两个用调用顺序断言标记先于发送。ACP:模型请求真正发出的位置,按 origin === 'runtime' 门控,异常路径按未投递结算。
  • 结算无竞态且幂等。 拒绝启动时的丢弃位于 isCurrentPermit 守卫内;settleCurrentTurnAnnouncement 二次调用为空操作;过期轮次键的投递标记被忽略(有测试)。三个结算点(finishTurn、releaseTurn、dispatch 的 invalidatesPermit)语义一致:已投递则提交,否则丢弃。
  • 生命周期重置正确。 clear 与终态 complete 重置 announcedObjective(其后的新 Goal 是新工作项——由 replace 覆盖已接受 Goal、清除后新建两个测试固定);blocked 保留(暂停而非结束——由 blocked → edit → resume 发出通知的测试固定)。
  • 消费者审计。 AppContainer.tsx 的 host 只是透传到 enqueueGoalTurn,无需改动;仓库中所有 GoalRuntime 替身都是部分 cast,且每个 markTurnDelivered 调用点都包在 try/catch 中,新接口方法不会破坏任何东西;旧的「取代」行在任何地方(含文档)均无残留。

无严重问题。一条建议:PR 描述相对实现已过时——仍在描述早先的 (goalId, revision) 组合设计、"五个运行时用例"(现在是 17 + 2 个渲染器用例),以及针对旧键控的变异检验。代码自身的文档块把真实设计讲得很清楚;建议合入前把描述更新一致,免得记录失真。

自上轮评审以来变化的部分是投递确认生命周期,值得图示(见正文时序图):运行时在 flush 时与上次投递的 objective 比较并附标志,host 发送提示词后以轮次键确认投递;结算时已投递则提交宣告、通知不再发出,未投递则丢弃宣告、通知留给替补。

CI 测试证据

对受审提交经 check-runs API 一次性拉取,未轮询。该 head 上没有红色检查:三个 pull_request 工作流(Qwen Code CI、Security Checks、SDK Java)全部成功完成,且无挂起。macOS/Windows/集成测试的跳过是仓库基线模式——已对照最近合入的 #10086 核实,那里跳过的是同一组——与本 PR 无关。

沙箱验证可以定论剩余的行为主张:@qwen-code /verify——在构建产物上,通知恰好在 objective 相对模型上次收到的内容发生变化时越过 host 边界,并与 base 构建 A/B 对照。单元测试已按精确标志序列固定运行时边界、按调用顺序固定 host 侧标记,密度很高,但不是对 base 的 A/B。因本 PR 触及高风险的 acp-integration 路径而明确列出;作者有写权限,维护者可直接触发。

未验证:改写后提示词对模型行为的影响——那是提示词工程主张,这里的 CI 无法度量;黄金测试逐字节固定两种渲染,运行时测试固定标志机制。TUI 抓取:N/A——改动没有终端界面,且 CI 路径从不执行 PR 代码。

Qwen Code · qwen3.8-max

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

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

Confidence: 4/5 — solid across every stage; the one nit is the stale PR description, not the code.

Stepping back: this is what a well-run review loop looks like from the other side. The previous round raised the hard question — acceptance is not delivery — and the answer wasn't a patch but a small, coherent redesign: content keying so no-op edits stay silent, and markTurnDelivered at each host's real send site so settle can tell a model-seen turn from a queued one. My independent proposal before reading the diff was the simpler version of exactly this; the PR's version is strictly better, and the 19 new tests pin the semantics scenario by scenario — including the nasty corners (refused start, drop-before-delivery, stale delivery mark, replace over an undelivered Goal, blocked → edit → resume). 189 production lines, every downstream consumer named and checked, CI green on the reviewed head with no red anywhere.

Two things I'm doing after posting this, transparently: approving pinned to the reviewed commit, and dismissing this bot's two earlier changes requested reviews — they stood on superseded commits whose feedback was addressed and resolved thread by thread, and leaving them up would keep the PR gated on a state that no longer exists.

Reservations, for the record: the PR body still describes the pre-review design (revision-pair keying, five runtime cases) and should be refreshed before merge — suggestion only. And the prompt-engineering claim itself (that a one-shot notice reduces drift better than a standing line) is not something any test here can prove; if a maintainer wants A/B evidence on the built artifact before merge, @qwen-code /verify is named in Stage 2. Neither blocks.

中文说明

置信度:4/5——各阶段均扎实;唯一的小问题是 PR 描述过时,与代码无关。

退一步看:这是评审循环从另一侧看应有的样子。上一轮提出了那个尖锐的问题——"接受"不等于"投递"——而回答不是打补丁,是一次小而自洽的重新设计:按内容键控使无实际变化的 edit 保持安静;每个 host 在真实发送点调用 markTurnDelivered,使结算能区分"模型看过的轮次"与"只是排队的轮次"。我在读 diff 之前独立给出的方案正是这套设计的简化版;PR 的版本严格更优,且 19 个新测试按场景逐一固定语义——包括所有刁钻角落(启动被拒、投递前丢弃、过期投递标记、replace 覆盖未投递的 Goal、blocked → edit → resume)。生产代码 189 行,每个下游消费者都已点名核查,受审提交上 CI 全绿、无任何红色。

发出本评论后我会做两件事,明示如下:批准并固定到受审提交;撤销本机器人早先的两条"需要修改"评审——它们针对的是已被取代的提交,其反馈已逐线程处理并 resolve,若继续保留,PR 将被一个已不存在的状态挡住。

保留意见,记录在案:PR 描述仍在讲评审前的设计(revision 组合键控、五个运行时用例),建议合入前刷新——仅为建议,不阻塞。另外,提示词工程主张本身(一次性通知比常驻行更能减少漂移)不是这里的任何测试能证明的;如果维护者希望合入前拿到构建产物上的 A/B 证据,Stage 2 已点名 @qwen-code /verify。两者均不阻塞。

Qwen Code · qwen3.8-max

Reviewed at 2263d48a0797f1e1743794390589cb196228bb38 · 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 — CI landed green after the review. ✅

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Partially reviewed — gaps disclosed.

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

Not reviewed: reverse audit — stopped before round 5 by the review time budget.

中文说明

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

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

未审查:反向审计——评审时间预算不足,未能开始第 5 轮。

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

Comment thread packages/core/src/goals/goal-runtime.ts Outdated
Comment thread packages/core/src/goals/goal-runtime.ts Outdated
Comment thread packages/core/src/goals/goal-runtime.ts Outdated
Comment thread packages/cli/src/ui/hooks/useMessageQueue.ts
@qwen-code-dev-bot

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

Copy link
Copy Markdown
Collaborator

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

中文说明

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

@doudouOUC doudouOUC left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Round 2 review — COMMENT

Reviewed at: 6a39f6ddfb936d2658b7870d55a92887fa8bf324 (unchanged since round 1)
Gap disclosed: Git fetch (getaddrinfo failure on Windows) blocked worktree creation, so no build, test, or reverse audit ran. Findings are based on the diff read from gh pr diff --repo QwenLM/qwen-code 10013.


Previous round-1 findings — verification

R1-1 (Critical) — host refusal rollback ⚠️ PROBABLY ADDRESSED (can't fully verify without the file on disk)

The PR adds announcedObjective = previouslyAnnounced inside handleStartFailure, which covers the synchronous throw path. The async rejection path would need the existing .then(undefined, handleStartFailure) chain to exercise the same rollback — I cannot see the surrounding code to confirm that chain is present because the worktree was not created. For the portion that is visible, the rollback is race-safe via isCurrentPermit(startedPermit). The inference from the redelivers when the host never took the prompt test (which passes startGoalTurn rejection via failures.push(new Error(...))) is that the async path does route through handleStartFailure — the test passes, so the plumbing works. Marking Probably Addressed pending a reviewer confirming the full .then(undefined, handleStartFailure) site.

R1-2 (Suggestion) — announcedObjective never reset on completion/clear 🔴 STILL OPEN

announcedObjective is set on every startGoalTurn and only rolled back inside handleStartFailure. It is never reset when a Goal completes or is cleared. The consequence:

  • create Goal A → continuation → announcedObjective = (A, 1)
  • complete Goal Acreate Goal B → first continuation of B → objectiveUpdated: true (because A.goalId !== B.goalId)

The model was not "working on" Goal A at that point — A was completed. Telling it "the objective changed since your last turn — stop work that only served the previous objective" is misleading; the model should get the normal prompt without the change notice. The same issue applies to clear followed by create.

The test suite has no case for create → complete → create → continuation (or clear), so this gap is untested. The fix would be resetting announcedObjective = undefined in the complete and clear dispatch handlers.

R1-3 (Suggestion) — keys on (goalId, revision) pair, not content 🔊 DESIGN CHOICE, still open

The PR author's rationale is clear and consistent: revision alone misses replace (whose new Goal starts at revision 1), and the replace test proves the pair-based key works. The niche case of editing an objective to the same text (content unchanged, revision bumped) is a false positive — the notice fires but nothing meaningful changed. This is a minor edge case unlikely to occur in practice, so it is a Suggestion rather than a Critical.

R1-4 (Suggestion) — no test for host threading hooks 🔴 STILL OPEN

The 7 new tests cover the runtime (5 cases) and the renderer (2 golden-prompt cases), but not the full propagation through the three host implementations (TUI useMessageQueueuseGeminiStream, headless nonInteractiveCli, ACP Session). The type system catches mis-keyed spreads, but there is no behavioral test verifying that the objectiveUpdated flag actually reaches the wire on the first continuation after an edit. The useGeminiStream.test.tsx change only updates the hardcoded prompt copy — it does not test the flag propagation.


New findings (round 2)

R2-1 (Suggestion) — restart action not covered

The dispatch handler for restart is not shown in the diff (it was outside the changed range). If restart creates a new Goal at revision 1 with a new goalId, the first continuation would correctly fire the notice. If restart reuses the same goalId, the notice might not fire when it should. The test suite covers edit, replace, pause, resume, and create, but not restart. Recommend adding a test case for restart or documenting that it is out of scope.

R2-2 (Suggestion) — abandon action not covered

Same concern as restart. If abandon clears the Goal state, announcedObjective is stale for the next Goal. Recommend adding a test case.


Summary

ID Severity Title Status
R1-1 Critical Host refusal rollback Probably Addressed
R1-2 Suggestion Reset on completion/clear Still open
R1-3 Suggestion Pair key vs content key Design choice, open
R1-4 Suggestion No host plumbing test Still open
R2-1 Suggestion restart not covered New
R2-2 Suggestion abandon not covered New

Remediation focus: R1-2 (reset announcedObjective on complete/clear) and R1-4 (a behavioral test through one host) would close the Suggestion-level gaps.

— Qwen Code via /review (v0.22.0+)

…enLM#10013)

Address review findings on the objective-updated notice:

- Commit the announcement when a continuation reaches the model, not when
  a host merely accepts it: every host resolves startGoalTurn at enqueue
  time, and a turn dropped before delivery (TUI Escape, ACP cancel,
  headless preempt) left the one-shot notice lost for good, while a turn
  dropped after acceptance but before the model saw it made a replace
  fire the notice for an objective the model never received. Hosts now
  confirm delivery through markTurnDelivered; undelivered turns leave
  their notice owed to the replacement continuation.
- Key the notice on objective content instead of the (goalId, revision)
  pair so a no-op or trim-equivalent edit no longer asserts a change
  that did not happen.
- Reset the announcement when a Goal ends verifier-accepted or is
  cleared, so a fresh Goal's first continuation never claims it
  replaces completed work.
- Add flag witnesses for the four host-side threading hops and for each
  delivery-confirmation call site.
@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

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

Review round summary — PR #10013

Commit: 72f17cdd62 on goal/b3-objective-updated (no base merge; --conflict false).

All four inline findings are resolved in code. Each claimed defect was first reproduced with a failing test on the pristine commit (6a39f6ddfb), then fixed, and every new guard was mutation-probed before committing.

Findings

[rv:5019395176] Review body — "Partially reviewed — gaps disclosed" (CHANGES_REQUESTED)

Informational: the review body carries no findings of its own; its actionable content is the four inline findings below, all addressed. The disclosed gaps (integration tests skipped in CI for the PR build, reverse audit stopped by the review time budget) are workflow-level review disclosures, not defect claims.

[rc:3853394241] [Critical] Rollback only covers host refusal, not accepted-but-undelivered drops — Resolved

Reproduced on the pristine commit with two failing tests (drop arm: releaseTurn after an accepted notice-carrying continuation re-minted objectiveUpdated=false; misfire arm: a replace over an accepted-but-undelivered Goal fired the notice for an objective the model never received).

Root cause: announcedObjective was committed at hand-off, but every host resolves startGoalTurn at enqueue time — acceptance is not delivery. Fix follows the reviewer's "track delivery rather than acceptance" option:

  • announcedObjective is now committed only when a continuation is delivered (new GoalRuntime.markTurnDelivered(turnKey), called by each host at its model-request site) or finishes (finishTurn is itself delivery evidence and keeps the fake-host test contract).
  • A continuation dropped before delivery — releaseTurn, dispatch invalidation (edit/replace/pause/clear), or a refused startGoalTurn — discards its pending announcement whole, so the replacement continuation re-derives the notice it carried.
  • A delivered turn settling through releaseTurn (the ACP #settleGoalTurn degraded-persistence catch branch named by the finding) keeps its announcement because delivery was confirmed at send time — that path does not un-announce.
  • The mid-turn invalidation window (delivered turn paused/edited before it finishes) commits the delivered announcement instead of silently dropping it, so pause/resume does not re-fire the notice.

Witnesses: redelivers the notice when an accepted turn is dropped before delivery, stays quiet when the dropped notice was carried back by its replacement, does not fire for a Goal that replaced one never handed to the model, does not fire for a delivered turn that settles through releaseTurn, keeps the announcement of a delivered turn across a mid-turn pause, does not leak a refused turn's announcement into a promoted user turn (pins the handleStartFailure discard), plus delivery-confirmation assertions in all three host suites.

Two pre-existing tests pinned the old semantics and were updated with it: the replace witness now delivers the superseded Goal's turn first (a replace over a Goal the model never saw is exactly the misfire being fixed), and the host-refusal redelivery witness finishes the create turn first (the notice is only owed once the prior objective was handed over).

[rc:3853394260] [Suggestion] announcedObjective never reset on completion/clear — Resolved

Reproduced on the pristine commit (a Goal created after a completed one fired the one-shot notice on its very first continuation, contradicting the diff's own "first continuation supersedes nothing" contract). The announcement now resets in recordVerificationOutcome's accept branch and on the clear dispatch action. The accept reset is witnessed independently via a replace directly over a verifier-accepted Goal (no clear in between, so only the accept-time reset can keep the old announcement from firing). usage_limited deliberately keeps the announcement: that Goal resumes with the same objective the model already holds.

[rc:3853394276] [Suggestion] Flag keys on the (goalId, revision) pair, so no-op edits fire the notice — Resolved

Reproduced on the pristine commit (identical-text and trim-equivalent edits fired the notice). The flag is now keyed on objective content: announcedObjective stores the objective text the model last received, and flushContinuation compares it with the text the continuation is about to hand over. A genuine edit still fires (witnessed); the goal-continuation-prompt.ts docstring was aligned with the new semantics. Chosen over rejecting no-op edits in reduceGoalControl because it keeps dispatch semantics unchanged for all callers (CLI, ACP, serve route) and is the smaller root-cause fix.

[rc:3853394290] [Suggestion] No test verifies objectiveUpdated survives the four host-side hops — Resolved

One flag case per boundary, in the existing exact-match style:

  • useMessageQueue.test.ts — enqueue with objectiveUpdated: true; the toEqual expectation now includes the flag.
  • useGeminiStream.test.tsx — queued Goal turn with the flag set; the exact-match rendered prompt includes the notice line, and the new delivery-confirmation call is asserted with the turn key.
  • Session.test.tsstartGoalTurn input with the flag; the rendered prompt sent to the model contains the notice line, and markTurnDelivered is asserted.
  • nonInteractiveCli.test.ts — new test injects the flag through the bound host; the rendered continuation contains the notice line and the delivery confirmation is spied.

Verification

  • Reproduction (pre-fix commit 6a39f6ddfb): 6 focused tests FAIL — drop loss, post-redelivery quiet, replace misfire, clear reset, accept reset, no-op edit; the two markTurnDelivered guards fail with the missing API. Recorded before implementing.
  • Mutation probes before committing (each removed one new guard/branch, ran the witness, then restored): 14/14 caught — releaseTurn settle, invalidation settle, finishTurn commit, handleStartFailure discard, content keying, clear reset, accept reset, ACP call site, ACP enqueue spread, TUI call site, TUI dequeue read, headless call site, headless enqueue spread, useMessageQueue spread. Every witness FAILED with its guard removed and the full suites are green with all guards restored.
  • npm run build — passed (all workspaces; one transient first-run gap where packages/webui/dist was missing despite exit 0 was repaired by rebuilding that workspace, and a re-run of the root build is green with the dist present).
  • npm run typecheck — passed (first run caught two mock-typing errors in the new Session test assertions; fixed and re-run clean).
  • npm run lint — passed.
  • npx prettier --check on changed files — passed after --write on two test files (formatting only, re-verified green).
  • cd packages/core && npx vitest run src/goals/ — 16 files, 413 passed.
  • cd packages/core && npx vitest run src/goals/goal-runtime.test.ts — 123 passed.
  • cd packages/cli && npx vitest run src/ui/hooks/useMessageQueue.test.ts — 40 passed.
  • cd packages/cli && npx vitest run src/nonInteractiveCli.test.ts — 130 passed | 1 skipped.
  • cd packages/cli && npx vitest run src/ui/hooks/useGeminiStream.test.tsx — 233 passed.
  • cd packages/cli && npx vitest run src/ui/AppContainer.test.tsx — 157 passed.
  • cd packages/cli && npx vitest run src/acp-integration/session/Session.test.ts — 693 passed.
  • Integration tests: not run — the touched behavior is exercised through the real goal runtime and all three host wirings in the unit suites above, not only through the bundled CLI or integration harness.
  • npm run generate:settings-schema: not needed (no settings source changed).
中文说明

审查轮次总结 — PR #10013

提交:goal/b3-objective-updated 分支上的 72f17cdd62(未合并 base;--conflict false)。

四条 inline 发现全部在代码中解决。每个被声明的缺陷都先在原始提交(6a39f6ddfb)上用失败的测试复现,然后修复,并且每个新守卫都在提交前经过了变异探针验证。

发现

[rv:5019395176] 审查正文 — “Partially reviewed — gaps disclosed”(CHANGES_REQUESTED)

信息性:审查正文本身没有发现;其可操作的内容是下面四条 inline 发现,均已处理。已披露的缺口(PR 构建在 CI 中跳过了集成测试、反向审计因审查时间预算而停止)属于工作流层面的审查披露,不是缺陷声明。

[rc:3853394241] [Critical] 回滚只覆盖 host 拒绝,未覆盖「已接受但未投递就被丢弃」— 已解决

在原始提交上用两个失败测试复现(丢失方向:携带通知的续跑被接受后 releaseTurn,重新铸造的续跑得到 objectiveUpdated=false;误报方向:对一个「已接受但未投递」的 Goal 执行 replace,却对模型从未收到的 objective 触发了通知)。

根因:announcedObjective 在移交(hand-off)时就被提交,但所有 host 都在入队时让 startGoalTurn resolve——接受不等于投递。修复采用审查者提出的「跟踪投递而非接受」方案:

  • announcedObjective 现在只在续跑被投递(新增 GoalRuntime.markTurnDelivered(turnKey),由各 host 在其模型请求发出点调用)或完成finishTurn 本身就是投递证据,并保持假 host 的测试契约)时才提交。
  • 投递前被丢弃的续跑——releaseTurn、dispatch 失效(edit/replace/pause/clear)、startGoalTurn 被拒绝——会整体丢弃其待提交宣告,使替代续跑重新推导出它所携带的通知。
  • 已投递的轮次经由 releaseTurn 结算(即发现中点名的 ACP #settleGoalTurn 持久化降级 catch 分支)时保留其宣告,因为投递已在发送时确认——该路径不会撤销宣告。
  • 轮中失效窗口(已投递的轮次在完成前被 pause/edit)会提交已投递的宣告而不是悄悄丢弃,因此 pause/resume 不会重新触发通知。

见证测试:redelivers the notice when an accepted turn is dropped before deliverystays quiet when the dropped notice was carried back by its replacementdoes not fire for a Goal that replaced one never handed to the modeldoes not fire for a delivered turn that settles through releaseTurnkeeps the announcement of a delivered turn across a mid-turn pausedoes not leak a refused turn's announcement into a promoted user turn(钉住 handleStartFailure 的丢弃),以及三个 host 测试套件中的投递确认断言。

两个既有测试钉住的是旧语义,随语义一并更新:replace 见证现在先让被取代 Goal 的轮次完成投递(对模型从未见过的 Goal 做 replace 正是要修复的误报);host 拒绝重投递见证现在先完成 create 轮次(只有上一个 objective 已移交,通知才谈得上欠付)。

[rc:3853394260] [Suggestion] announcedObjective 在完成/清除时从不复位 — 已解决

在原始提交上复现(一个已完成 Goal 之后创建的新 Goal,会在其首次续跑就触发一次性通知,与本 diff 自己的「首次续跑不取代任何东西」契约矛盾)。宣告现在会在 recordVerificationOutcome 的接受分支和 clear dispatch 动作中复位。接受复位通过「直接对验证者已接受的 Goal 执行 replace(中间没有 clear,因此只有接受时的复位能阻止旧宣告触发)」独立见证。usage_limited 刻意保留宣告:该 Goal 以模型已持有的同一 objective 恢复。

[rc:3853394276] [Suggestion] 标志以 (goalId, revision) 组合为键,无变更 edit 也会触发通知 — 已解决

在原始提交上复现(相同文本与仅空白差异的 edit 都触发了通知)。标志现在以 objective 内容为键:announcedObjective 存储模型最近收到的 objective 文本,flushContinuation 将其与即将移交的文本比较。真实 edit 仍然触发(有见证);goal-continuation-prompt.ts 的文档字符串已与新语义对齐。相比在 reduceGoalControl 中拒绝无变更 edit,选择此方案是因为它不改变任何调用方(CLI、ACP、serve 路由)的 dispatch 语义,是更小的根因修复。

[rc:3853394290] [Suggestion] 没有测试验证 objectiveUpdated 能通过四处 host 侧穿线 — 已解决

按现有精确匹配风格,每个边界一个标志用例:

  • useMessageQueue.test.ts — 以 objectiveUpdated: true 入队;toEqual 断言现在包含该标志。
  • useGeminiStream.test.tsx — 以置位标志驱动排队的 Goal 轮次;精确匹配的渲染提示词包含通知行,并以轮次键断言新的投递确认调用。
  • Session.test.ts — 带标志的 startGoalTurn 输入;发给模型的渲染提示词包含通知行,并断言 markTurnDelivered
  • nonInteractiveCli.test.ts — 新测试通过绑定的 host 注入标志;渲染的续跑包含通知行,并以 spy 验证投递确认。

验证

  • 复现(修复前提交 6a39f6ddfb):6 个聚焦测试失败——丢失方向、重投递后静默、replace 误报、clear 复位、accept 复位、无变更 edit;两个 markTurnDelivered 守卫因 API 缺失而失败。实现前已记录。
  • 提交前的变异探针(每次移除一个新守卫/分支,运行见证测试,然后恢复):14/14 全部捕获——releaseTurn 结算、失效结算、finishTurn 提交、handleStartFailure 丢弃、内容键控、clear 复位、accept 复位、ACP 调用点、ACP 入队展开、TUI 调用点、TUI 出队读取、headless 调用点、headless 入队展开、useMessageQueue 展开。每个见证测试在其守卫被移除时都失败,所有守卫恢复后完整测试套件全绿。
  • npm run build — 通过(全部工作区;首次运行出现过一次 packages/webui/dist 缺失的瞬时缺口(尽管退出码为 0),通过重建该工作区修复,根构建重跑为绿且 dist 存在)。
  • npm run typecheck — 通过(首次运行捕获了新 Session 测试断言中的两个 mock 类型错误;已修复并重跑通过)。
  • npm run lint — 通过。
  • 对变更文件执行 npx prettier --check — 对两个测试文件 --write 后通过(仅格式化,已重新验证为绿)。
  • cd packages/core && npx vitest run src/goals/ — 16 个文件,413 通过。
  • cd packages/core && npx vitest run src/goals/goal-runtime.test.ts — 123 通过。
  • cd packages/cli && npx vitest run src/ui/hooks/useMessageQueue.test.ts — 40 通过。
  • cd packages/cli && npx vitest run src/nonInteractiveCli.test.ts — 130 通过 | 1 跳过。
  • cd packages/cli && npx vitest run src/ui/hooks/useGeminiStream.test.tsx — 233 通过。
  • cd packages/cli && npx vitest run src/ui/AppContainer.test.tsx — 157 通过。
  • cd packages/cli && npx vitest run src/acp-integration/session/Session.test.ts — 693 通过。
  • 集成测试:未运行——被改动的行为通过真实 goal runtime 与全部三处 host 穿线在上述单元测试中验证,并非只能通过捆绑 CLI 或集成测试框架执行。
  • npm run generate:settings-schema:不需要(未改动任何 settings 源)。

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

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


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

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🔀 Base updated: red check(s) [Test (ubuntu-latest, Node 22.x)] pass on current main — merged current main via update-branch; CI will re-run.

中文说明

🔀 已更新 base:红色检查 [Test (ubuntu-latest, Node 22.x)] 在当前 main 上通过 —— 已通过 update-branch 合入当前 main,CI 将重新运行。

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Qwen Code review did not complete successfully. The review pipeline failed before a review could be posted. A transient error is retried automatically; if you are seeing this, retry with @qwen-code /review. See workflow logs.

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

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

Round summary — no code changes

Every actionable point in the round-2 review was verified against the current PR
head (7afc39a3f9, which contains 72f17cdd62 on top of the reviewed commit
6a39f6ddfb plus a merge of main). Each finding is either already addressed
by 72f17cdd62 and pinned by green tests, or targets behavior that does not
exist in this Goal system.

Key context: the round-2 review states it was reviewed at 6a39f6ddfb and
discloses that its git fetch failed (getaddrinfo failure), so it never saw the
branch's second commit 72f17cdd62 ("announce objective changes by delivery,
not acceptance"). That commit already implements exactly what the review's
remediation focus asks for. The statuses below were therefore checked against
the live code, not the diff the reviewer could read.

Finding dispositions

  • R1-1 (Critical) — host refusal rollback: verified addressed. Both the
    synchronous throw and the async rejection of startGoalTurn route through
    handleStartFailure (the try/catch and void started.catch(handleStartFailure)
    at the end of flushContinuation in goal-runtime.ts), which discards the
    in-flight announcement via settleCurrentTurnAnnouncement(false) so the retry
    re-derives the notice. Pinned by redelivers the notice when the host never took the prompt and does not leak a refused turn's announcement into a promoted user turn. Mutation probe: removing the rollback line fails the
    latter test; restoring it returns the suite to green.
  • R1-2 (Suggestion) — reset on completion/clear: addressed by the delivery
    commit.
    announcedObjective is reset when a terminal proposal
    (complete or blocked) is verifier-accepted in recordVerificationOutcome,
    and on clear dispatch. Pinned by stays off for a Goal created after a cleared one and stays off for a Goal that replaces a verifier-accepted one
    (the latter replaces directly over a completed Goal, so only the accept-time
    reset keeps it quiet). Mutation probe: removing the accept-time reset fails
    that test; restoring it returns green.
  • R1-3 (Suggestion) — pair key vs content key: resolved by design change.
    The notice is keyed on objective content (announcedObjective !== continuationContext in flushContinuation), not the (goalId, revision) pair,
    so an edit to the same or trim-equivalent text no longer fires a false
    positive. Pinned by stays off for edits that leave the objective text unchanged (shipshipship stay quiet; a real edit fires once).
  • R1-4 (Suggestion) — host plumbing tests: addressed. Behavioral witnesses
    exist through all three host implementations: TUI (useMessageQueue.test.ts
    asserts the flag threads into the queued turn; useGeminiStream.test.tsx
    asserts the rendered prompt contains the one-shot notice line and
    markTurnDelivered is called), headless (nonInteractiveCli.test.ts asserts
    the notice text reaches sendMessageStream and markTurnDelivered is called
    with the turn key), and ACP (Session.test.ts asserts the notice text in the
    prompt parts and markTurnDelivered called).
  • R2-1 (Suggestion) — restart action: declined (refuted). The
    GoalControlRequest union in packages/core/src/goals/goal-protocol.ts has
    exactly six actions: create, replace, edit, pause, resume, clear.
    There is no restart action in this Goal system, so there is nothing to
    cover or to document as out of scope.
  • R2-2 (Suggestion) — abandon action: declined (refuted). There is no
    abandon action and no abandoned status (GoalStatus is
    active | paused | blocked | usage_limited | complete). A Goal ends only via
    a verifier-accepted terminal proposal (complete/blocked), which resets the
    announcement — the path covered under R1-2.
  • [ic:5414313649] — informational bot notice that the review pipeline
    failed; not a code finding. Its consequence (the round-2 review reading a
    stale diff) is why the statuses above were stale.
  • Cancelled checks (Test macos-latest / windows-latest, review-pr) —
    CANCELLED, not red on a code failure; consistent with the disclosed
    reviewer-side fetch failure; nothing reproducible or fixable from code.

Verification

  • npm run build — passed (all packages; needed to rebuild workspace dist/
    after the main merge)
  • npx vitest run src/goals/goal-runtime.test.ts src/goals/goal-continuation-prompt.test.ts
    (packages/core) — 140 passed (2 files), including all 14
    objective-updated notice cases
  • npx vitest run src/ui/hooks/useMessageQueue.test.ts src/nonInteractiveCli.test.ts src/ui/hooks/useGeminiStream.test.tsx
    (packages/cli) — 403 passed, 1 skipped (3 files)
  • npx vitest run src/acp-integration/session/Session.test.ts (packages/cli) —
    693 passed
  • Mutation probe 1 (R1-2 witness): removed the accept-time
    announcedObjective = undefined reset →
    stays off for a Goal that replaces a verifier-accepted one FAILED (1 failed);
    restored → green
  • Mutation probe 2 (R1-1 witness): removed settleCurrentTurnAnnouncement(false)
    from the start-failure path →
    does not leak a refused turn's announcement into a promoted user turn FAILED;
    restored → green
  • git status — clean; HEAD unchanged at 7afc39a3f9; no commit this round
中文说明

本轮总结 — 无代码变更

第 2 轮评审中的每个可执行要点都已在当前 PR 头(7afc39a3f9,包含被评审提交
6a39f6ddfb 之上的 72f17cdd62 以及与 main 的合并)上逐一核实。每个发现要么已由
72f17cdd62 解决并被绿色测试固定,要么针对的是本 Goal 系统中根本不存在的行为。

关键背景:第 2 轮评审明确说明其评审对象是 6a39f6ddfb,并披露其 git 拉取失败
getaddrinfo 错误),因此它从未看到分支的第二个提交 72f17cdd62("按投递而非接受
宣告目标变更")。该提交恰好实现了评审补救重点所要求的全部内容。因此以下状态是对照
线上实际代码核实的,而不是对照评审者所能读到的那份旧 diff。

各发现的处理

  • R1-1(Critical)— 宿主拒绝时的回滚:已核实解决。 startGoalTurn 的同步
    throw 与异步拒绝都会进入 handleStartFailuregoal-runtime.ts
    flushContinuation 末尾的 try/catchvoid started.catch(handleStartFailure)),
    该函数通过 settleCurrentTurnAnnouncement(false) 丢弃进行中的宣告,使重试能够
    重新推导出该提示。由测试 redelivers the notice when the host never took the promptdoes not leak a refused turn's announcement into a promoted user turn 固定。变异探针:移除该回滚行会使后者失败;恢复后测试套件回到绿色。
  • R1-2(Suggestion)— 完成/清除时重置:已由投递提交解决。 当终止提案
    completeblocked)在 recordVerificationOutcome 中被验证者接受时,
    以及 clear 分发时,announcedObjective 都会被重置。由测试 stays off for a Goal created after a cleared onestays off for a Goal that replaces a verifier-accepted one 固定(后者直接在已完成的 Goal 上执行 replace,因此只有
    接受时刻的重置能使其保持静默)。变异探针:移除接受时刻的重置会使该测试失败;
    恢复后回到绿色。
  • R1-3(Suggestion)— 键用 (goalId, revision) 对还是内容:已通过设计变更解决。
    该提示现在以目标内容(flushContinuation 中的 announcedObjective !== continuationContext)为键,而不是 (goalId, revision) 对,因此把目标编辑成相同
    或仅空白差异的文本不再触发误报。由测试 stays off for edits that leave the objective text unchanged 固定(shipshipship 均保持静默;一次真正的
    编辑触发一次)。
  • R1-4(Suggestion)— 宿主穿线测试:已解决。 三种宿主实现均有行为级见证:
    TUI(useMessageQueue.test.ts 断言该标志被穿入排队的回合;
    useGeminiStream.test.tsx 断言渲染出的提示包含一次性变更提示行且调用了
    markTurnDelivered)、无头模式(nonInteractiveCli.test.ts 断言提示文本到达
    sendMessageStream 且以回合键调用了 markTurnDelivered)、ACP
    Session.test.ts 断言提示部件中包含该提示文本且调用了 markTurnDelivered)。
  • R2-1(Suggestion)— restart 动作:拒绝(已证伪)。
    packages/core/src/goals/goal-protocol.ts 中的 GoalControlRequest 联合类型
    恰好有六个动作:createreplaceeditpauseresumeclear
    本 Goal 系统中不存在 restart 动作,因此没有可覆盖的内容,也无需将其记为范围外。
  • R2-2(Suggestion)— abandon 动作:拒绝(已证伪)。 既不存在 abandon
    动作,也不存在 abandoned 状态(GoalStatus
    active | paused | blocked | usage_limited | complete)。Goal 只会经由验证者接受
    的终止提案(complete/blocked)结束,而该路径会重置宣告——即 R1-2 所覆盖的路径。
  • [ic:5414313649] — 机器人发布的通知,说明评审流水线失败;不是代码层面的发现。
    它的后果(第 2 轮评审读到过时的 diff)正是上述状态过时的原因。
  • 已取消的检查(Test macos-latest / windows-latest、review-pr)— 状态为
    CANCELLED(已取消),并非因代码失败而变红;与评审者披露的拉取失败一致;
    代码层面没有可复现或可修复的内容。

验证

  • npm run build — 通过(全部包;与 main 合并后需要重新构建各工作区的
    dist/
  • npx vitest run src/goals/goal-runtime.test.ts src/goals/goal-continuation-prompt.test.ts
    (packages/core)— 140 通过(2 个文件),包含全部 14 个
    objective-updated notice 用例
  • npx vitest run src/ui/hooks/useMessageQueue.test.ts src/nonInteractiveCli.test.ts src/ui/hooks/useGeminiStream.test.tsx
    (packages/cli)— 403 通过,1 跳过(3 个文件)
  • npx vitest run src/acp-integration/session/Session.test.ts(packages/cli)—
    693 通过
  • 变异探针 1(R1-2 见证):移除接受时刻的 announcedObjective = undefined
    重置 → stays off for a Goal that replaces a verifier-accepted one 失败
    (1 个失败);恢复后回到绿色
  • 变异探针 2(R1-1 见证):移除启动失败路径中的
    settleCurrentTurnAnnouncement(false)
    does not leak a refused turn's announcement into a promoted user turn 失败;
    恢复后回到绿色
  • git status — 干净;HEAD 仍为 7afc39a3f9,未变化;本轮无提交

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


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

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Partially reviewed — gaps disclosed.

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

Not explored to full depth (tool budget reached): "agent 1c": none — though I note the reachability window of the reported finding (queue-drain vs direct UserQuery admission timing in AppContainer.tsx ) is the part I coul…; "agent 3c": none — no check was cut short..

中文说明

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

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

未探索到全部深度(达到工具调用预算):"agent 1c"none — though I note the reachability window of the reported finding (queue-drain vs direct UserQuery admission timing in AppContainer.tsx ) is the part I coul…"agent 3c"none — no check was cut short.

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

Comment thread packages/core/src/goals/goal-runtime.ts Outdated
Comment thread packages/cli/src/nonInteractiveCli.ts
Comment thread packages/cli/src/nonInteractiveCli.ts
Comment thread packages/core/src/goals/goal-runtime.ts
Comment thread packages/core/src/goals/goal-runtime.ts Outdated
@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 AutoFix ran out of time before finishing (timeout (2700000ms)) (attempt 2/100) — it will retry on the next scan.

⚠️ This change was NOT pushed — any commit referenced below was made only in the runner workspace and has been discarded. What the agent reported:
Qwen failed during address-review: timeout (2700000ms).

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

中文说明

🤖 AutoFix 在完成前耗尽了时间(timeout (2700000ms))(第 2/100 次尝试)—— 将在下次扫描时重试。

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


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

…pdated

# Conflicts:
#	packages/cli/src/acp-integration/session/Session.test.ts
#	packages/cli/src/acp-integration/session/Session.ts
#	packages/cli/src/nonInteractiveCli.test.ts
#	packages/cli/src/nonInteractiveCli.ts
#	packages/cli/src/ui/hooks/useGeminiStream.test.tsx
#	packages/cli/src/ui/hooks/useGeminiStream.ts
#	packages/cli/src/ui/hooks/useMessageQueue.test.ts
#	packages/cli/src/ui/hooks/useMessageQueue.ts
#	packages/core/src/goals/goal-continuation-prompt.test.ts
#	packages/core/src/goals/goal-continuation-prompt.ts
#	packages/core/src/goals/goal-runtime.ts
… saw

finishTurn committed the in-flight continuation's announcement
unconditionally, but finishing a turn proves the permit was used, not
that the continuation prompt was sent under it. Two host paths claim a
queued continuation's permit and send other text instead: a Cron,
Notification or Teammate submission drained ahead of the TUI's own
drain, and a direct user query admitted through the same slot. Neither
renders the continuation nor marks it delivered, so the turn finished
with the notice unsent and the runtime recorded it as delivered. It now
commits on the host's delivery mark, exactly as releaseTurn already did.

The accept-time reset of the announcement was firing for every
accepted terminal proposal, including blocked, which is resumable: a
Goal edited while blocked and then resumed sent no notice for a real
change, while pause -> edit -> resume did. Only complete resets it; a
blocked Goal keeps the objective the model holds, as usage_limited does.

Tests: the notice suite now marks delivery before finishing, as real
hosts do; new cases pin finishing without the prompt, a delivery mark
with a stale turn key, blocked -> edit -> resume, and complete -> clear
-> create. Two headless cases drive the real runtime through both
continuation promotion sites so the delivery mark there is covered.

Mutation probes: unconditional commit -> 1 fail (143 green); mark
ignoring the turn key -> 1 (143); reset on every terminal -> 1 (143);
each headless promotion mark deleted -> exactly its test (132/133).
@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

2263d48a07 处理本轮全部 5 条(含 1 条 Critical),线程已逐条回复并 resolve;同时合入了 main(D2 #10132 / D2b #10125),三个 host 跳板上 objectiveUpdatedwindDown 并存。

  • Critical:finishTurn 无条件提交宣告。 完成一轮只证明 permit 被用过,不证明续跑提示词被发出——系统消息(Cron/Notification/Teammate)的 drain 和直接用户查询都会占用排队续跑的 permit 却发别的文本。现在与 releaseTurn 同一规则:只在 host 的送达标记存在时提交。TUI 的两条占用路径经核对都不打标记(标记点门控在 submitType === Goal)。
  • blocked 也重置宣告。 只在 complete 时重置;blocked 是挂起不是结束,与 usage_limited 一致——否则 blocked → edit → resume 收不到通知。
  • 三处测试缺口。 陈旧 turn key 的送达标记;headless 两个续跑晋升点各一个用例(让第一段经真实 finishTurn 调度出下一次续跑,一个走 finalize 分支、一个走工具 terminateTurn 分支);blocked → edit → resume 与 complete → clear → create。

变异检验:无条件提交 → 恰好挂 1(143 绿);标记忽略 key → 1(143);所有终态都重置 → 1(143);两个晋升点标记分别删除 → 各恰好挂自己的用例(132 绿)。goals 468/468;CLI 四套件 2063 + 133;goal 相关 tsc 0 错;prettier/eslint 干净。

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Partially reviewed — gaps disclosed.

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

Deferred under the convergence posture (round 3, not a blocker) — recorded, not requested in this round:

  • packages/cli/src/nonInteractiveCli.ts:655 — [review] delivery-mark wrapper written three times and already diverged
  • packages/core/src/goals/goal-runtime.ts:247 — [review] announcedObjective docstring contradicts finishTurn's settle-on-delivery semantics
  • packages/cli/src/ui/hooks/useGeminiStream.test.tsx:537 — [probe] mark-before-send ordering not asserted at three test sites
中文说明

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

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

收敛姿态下延后(第 3 轮,非阻断)——已记录,本轮不要求修改:共 3 条(原文未翻译,列表见上方英文部分)。

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

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

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

Autofix round report — PR #10013 (no code changes this round)

This round made no code changes. All five unresolved inline findings were already resolved in code by commit 2263d48a07 (fix(goal): commit the objective announcement only for turns the model saw), which landed after the findings were posted. Rather than re-implementing anything, this round re-verified each resolution against HEAD with execution evidence.

Per-finding verification

  • rc:3861371943 [Critical] — finishTurn committed the in-flight announcement unconditionally. Resolved: finishTurn now settles via settleCurrentTurnAnnouncement(currentTurnDelivered) in packages/core/src/goals/goal-runtime.ts — exactly the suggested change — so a turn whose continuation permit was claimed by a system message (claimSystemGoalTurn) or a direct user admission (claimDirectUserAdmission) settles without committing an announcement the model never saw; both claim paths go through this one settlement, as the finding required. The delivery mark fires only on a real continuation send (submitType === SendMessageType.Goal, useGeminiStream.ts), which neither claim path does. The fake-host notice suite now marks delivery before finishing (finishDelivered helper), and the new keeps the notice owed when a turn finishes under the permit without the prompt test pins the fix. Probe at HEAD: reverting the settle to unconditional fails exactly that test (1 failed / 143 skipped); restored.
  • rc:3861371966 [Suggestion] — promotion-site delivery mark at nonInteractiveCli.ts:2505 untested. Resolved: marks a follow-up continuation delivered after a tool-terminated segment drives the real finishTurn through that branch with a non-empty queue and asserts markTurnDelivered was called with the promoted turn's key before its prompt was sent. Probe: deleting this mark fails exactly that test while its twin passes; restored. Notice-content propagation is pinned separately by the pre-existing carries the objective-updated notice into a scheduled Goal continuation test, which exercises the same buildGoalContinuationParts path the promoted segment uses.
  • rc:3861371978 [Suggestion] — twin promotion site at nonInteractiveCli.ts:2536 untested. Resolved the same way by marks a follow-up continuation delivered when the finished turn schedules it. Probe: deleting this mark fails exactly that test while its twin passes; restored.
  • rc:3861371984 [Suggestion] — key-match guard in markTurnDelivered untested. Resolved: ignores a delivery mark carrying a stale turn key replays the finding's probe scenario (deliver objective A, edit to B, mark with the first turn's key, release the in-flight turn — notice still owed). Probe: dropping the key comparison fails exactly that test; restored.
  • rc:3861371990 [Suggestion] — announcement reset fired for blocked proposals too. Resolved: the reset now runs only for attempt.proposal.status === 'complete'; fires after an edit made while the Goal was blocked and stays off for a Goal created after a completed one was cleared pin both arms. Probe: making the reset unconditional fails exactly the blocked test; restored.

Verification

Every command below was run at HEAD 2263d48a07; the working tree is clean before and after (only the temporary mutation probes touched it, each reverted):

  • npm run build — passed (the checkout's packages/core/dist predated the branch checkout, so it was rebuilt to make the cli tests run against HEAD's runtime)
  • vitest, packages/core: src/goals/goal-runtime.test.ts + src/goals/goal-continuation-prompt.test.ts — 156 passed (2 files)
  • vitest, packages/cli: src/nonInteractiveCli.test.ts + src/ui/hooks/useGeminiStream.test.tsx + src/ui/hooks/useMessageQueue.test.ts + src/acp-integration/session/Session.test.ts — 1132 passed | 1 skipped (4 files)
  • Five mutation probes, one per finding (listed above); each reverted its fix, failed exactly its witness test, and was restored

Not addressed this round (deliberately)

  • The round-3 review (rv:5031374596) deferred three items under the convergence posture as "recorded, not requested in this round" (delivery-mark wrapper duplication, announcedObjective docstring wording, mark-before-send ordering assertions). They are left as the reviewer directed.
  • The "Signal the reviewed fork PR: CANCELLED" entries are workflow signaling jobs, not code checks; nothing in this checkout can address them.
  • No base-conflict handling was requested (--conflict false), and no merge was performed.
中文说明

Autofix 轮次报告 — PR #10013(本轮无代码改动)

本轮没有做任何代码改动。五条未解决的 inline findings 已经由提交 2263d48a07fix(goal): commit the objective announcement only for turns the model saw)在代码中解决,该提交在这些 findings 提出之后落地。因此本轮没有重新实现任何内容,而是逐条在 HEAD 上用执行证据复查了每个修复。

逐条验证

  • rc:3861371943 [Critical] — finishTurn 无条件提交在途续跑的宣告。 已解决:packages/core/src/goals/goal-runtime.ts 中的 finishTurn 现在通过 settleCurrentTurnAnnouncement(currentTurnDelivered) 结算——与建议的改法完全一致——因此被系统消息(claimSystemGoalTurn)或直接用户准入(claimDirectUserAdmission)认领了续跑 permit 的轮次,结算时不会提交模型从未见过的宣告;两条认领路径都经由这同一个结算点,符合该 finding 的要求。投递标记只在真实续跑发送时触发(submitType === SendMessageType.Goal,useGeminiStream.ts),而两条认领路径都不会触发。假 host 的通知测试套件现在在 finish 之前先标记投递(finishDelivered 辅助函数),新增测试 keeps the notice owed when a turn finishes under the permit without the prompt 钉住了该修复。在 HEAD 上的探针: 把结算改回无条件后,恰好该测试失败(1 failed / 143 skipped);已还原。
  • rc:3861371966 [Suggestion] — nonInteractiveCli.ts:2505 晋升点的投递标记无测试。 已解决: marks a follow-up continuation delivered after a tool-terminated segment 让真实 finishTurn 在队列非空时走过该分支,并断言 markTurnDelivered 以被晋升轮次的键被调用、且先于其提示词发送。探针: 删除该标记后恰好该测试失败、孪生测试通过;已还原。通知内容的传播由既有测试 carries the objective-updated notice into a scheduled Goal continuation 单独钉住,该测试走的是被晋升片段所用的同一条 buildGoalContinuationParts 路径。
  • rc:3861371978 [Suggestion] — nonInteractiveCli.ts:2536 的孪生晋升点无测试。 同样由 marks a follow-up continuation delivered when the finished turn schedules it 解决。探针: 删除该标记后恰好该测试失败、孪生测试通过;已还原。
  • rc:3861371984 [Suggestion] — markTurnDelivered 中的键匹配守卫无测试。 已解决: ignores a delivery mark carrying a stale turn key 复现了该 finding 的探针场景(投递 objective A,编辑为 B,用第一个轮次的键标记,释放当前在途轮次——通知仍欠付)。探针: 去掉键比较后恰好该测试失败;已还原。
  • rc:3861371990 [Suggestion] — 宣告复位对 blocked 提案也会触发。 已解决: 复位现在只在 attempt.proposal.status === 'complete' 时执行;fires after an edit made while the Goal was blockedstays off for a Goal created after a completed one was cleared 分别钉住两个分支。探针: 把复位改回无条件后恰好 blocked 测试失败;已还原。

验证

以下所有命令均在 HEAD 2263d48a07 上执行;工作区在前后都保持干净(只有临时变异探针改动过它,且每次均已还原):

  • npm run build — 通过(检出目录中的 packages/core/dist 早于分支检出,故重新构建,使 cli 测试运行在 HEAD 的运行时之上)
  • vitest,packages/core: src/goals/goal-runtime.test.ts + src/goals/goal-continuation-prompt.test.ts — 156 通过(2 个文件)
  • vitest,packages/cli: src/nonInteractiveCli.test.ts + src/ui/hooks/useGeminiStream.test.tsx + src/ui/hooks/useMessageQueue.test.ts + src/acp-integration/session/Session.test.ts — 1132 通过 | 1 跳过(4 个文件)
  • 五个变异探针,每个 finding 一个(见上文);每个探针还原对应修复后恰好使其见证测试失败,随后恢复原状

本轮有意不处理的内容

  • 第 3 轮审查(rv:5031374596)在收敛姿态下将三项延后,标注为「已记录,本轮不要求修改」(投递标记包装函数重复三次、announcedObjective 文档注释措辞、mark-before-send 顺序断言)。按审查者的指示保持原样。
  • 「Signal the reviewed fork PR: CANCELLED」条目是工作流的信号任务,不是代码检查;本检出目录中没有任何内容可以处理它们。
  • 未要求处理与 base 的冲突(--conflict false),因此没有执行任何合并。

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


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

@wenshao

wenshao commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /triage

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

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

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

Scripted assertions: 32 passed · 0 failed · 32 total

Flakiness gate: ⚠️ timeout — only 4 of 5 rounds fit the 15-minute budget; the completed rounds agreed

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

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

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

抖动门:⚠️ timeout — only 4 of 5 rounds fit the 15-minute budget; the completed rounds agreed

Verification report

PR #10013 Deep Verification — feat(goal): tell the model the objective changed, once, when it changed

Verdict: merge-ready — 32 scripted assertions executed, 32 passed, 0 failed.
Verified head: 2263d48a0797f1e1743794390589cb196228bb38 (git rev-parse HEAD^2), merged over base tip fc0e827658.

Scope selection

  • Central claim: the Goal continuation prompt announces an objective change exactly once — on the first continuation actually delivered to the model after the objective text really changed — and never on unchanged turns; the standing guard line stays on every turn with its change-implication removed.
  • Secondary claim 1 (delivery semantics): the announcement is committed on the host's delivery mark (markTurnDelivered), not on acceptance or finish: accepted-but-dropped, refused, or finished-without-prompt turns leave the notice owed; delivered turns that settle through releaseTurn commit it.
  • Secondary claim 2 (host wiring): all three hosts (TUI useMessageQueueuseGeminiStream, ACP Session, headless nonInteractiveCli) thread objectiveUpdated into the renderer and mark delivery on their real send paths.

Out of scope (listed under Not covered): live TUI/ACP runs against a real model, per-commit attribution, trial merge into current main.

中文摘要
  • 结论:merge-ready。32 项脚本化断言全部通过,0 失败。
  • A/B 结论:同一 9 轮 Goal 场景(create → 续跑 → 续跑 → edit → 续跑 → 续跑 → edit → 丢弃 → 续跑)分别驱动 head 与 base 的编译产物。base 的 9/9 条续跑提示词都携带「包括你已经开始处理的那个」这种暗示变更的措辞(哪怕 objective 从未变过);head 仅在 objective 真正变更后的首次投递续跑上发出一次性通知(3/9:两次 edit 各一次 + 一次被丢弃后的重投递),旧措辞 0/9,常驻护栏 9/9。该行为由 announcedObjective(按内容而非 (goalId, revision) 键控)与「仅投递(而非接受/完成)才提交宣告」两处机制共同承载,见 01-ab-cells-head-vs-base.png
  • 变异矩阵:9 个变异体中 8 个被新增测试精确杀死(含「无条件提交」「仅按 revision 键控」「忽略 turn key 的投递标记」「每种终态都重置」等);唯一存活的 M3(去掉拒绝启动时的回滚)经专用序列裁决为冗余防御——该子句在「先标记投递、随后拒绝同一轮」的交错下确实决定结果,但三个出厂 host 都不会产生该交错,见 03-m3-sequencer-clause-decides.png。正向对照充分(同一命令下多个变异体变红),说明存活是真实的而非测试未运行。
  • Findings:仅 2 条 nit —— ① 提交信息中「去掉拒绝回滚会挂重投递测试」的变异结论在最终代码上已过期(现该回滚为纵深防御),属描述更正,不需要改代码;② PR 正文引用的测试数(404 / 40+233+130+1739)因分支两次合并 main 已漂移,重测为 468 与 2197+1 skipped,全绿。
  • 未覆盖:逐提交归因(浅克隆仅 3 个提交可见)、与当前 main 的试合并(无凭据)、真实模型下的 TUI/ACP 实跑、Windows/macOS。

Central claim — A/B load-bearing proof

Harness ab-harness.mjs drives the compiled createGoalRuntime from each tree's packages/core/dist through one scripted Goal lifecycle and renders every continuation with the same tree's own renderGoalContinuationPrompt, exactly as the real hosts do. Module graphs are provably tree-local: both entry files realpath-checked inside their tree at load, and the dist graph imports only node:crypto plus relative siblings (no workspace symlinks cross the boundary). Scenario: create → 2 continuations → edit #1 → continuation → continuation → edit #2 → continuation accepted then dropped undelivered (releaseTurn) → replacement continuation. Because every finished turn auto-queues the next continuation, each edit lands while a turn is accepted-but-undelivered and preempts it — 9 continuations total.

cell build old change-implication line one-shot notice line standing guard (reworded) markTurnDelivered API result
control base fc0e8276 (worktree rebuild) 9/9 every turn 0/9 (does not exist) 0/9 absent behaved exactly as predicted — assertion passed
head 2263d48a (CI build) 0/9 3/9 — T4 (post edit #1), T7 (post edit #2), T8 (redelivery after T7 dropped) 9/9 present all 8 head assertions passed

Flip shape: every-turn change wording 9/9 → 0/9; actionable change notice 0 → exactly the turns where the objective changed and the model had not seen it, including the owed redelivery after an accepted turn was dropped before delivery. The T4 prompt tail on head matches the PR's claimed "after" text byte-for-byte. Witness: 01-ab-cells-head-vs-base.png; raw logs logs/ab-head.log, logs/ab-base.log.

Base-side control note: the base worktree's packages/core had no node_modules, so external resolution (ajv) fell through to the root's ajv@6. Both arms were equalized by symlinking the identical nested packages/core/node_modules (realpath /__w/qwen-code/qwen-code/packages/core/node_modules/ajv) into the base tree; the PR touches no package.json/lockfile, so the shared root install is a clean control.

Mutation matrix — the PR's new tests are not vacuous

Unmutated control green first: goal-runtime.test.ts + goal-continuation-prompt.test.ts = 156/156. Then one mutant per guard, same command; every revert restored and the tree re-verified clean (git status --short empty). Witness: 02-mutation-matrix-core.png.

mutant guard under test outcome
M1 finishTurn commits announcement unconditionally commit-on-delivery (finish path) killed — exactly 1 fail: keeps the notice owed when a turn finishes under the permit without the prompt
M2a fire whenever any previous announcement exists content keying killed — 11 fail
M2b revision-only keying (store+compare revision) content keying vs rejected design killed — 3 fail: fires after a replace…, stays off for edits that leave the objective text unchanged, does not leak a refused turn's announcement…
M3 drop refused-start roll-back settleCurrentTurnAnnouncement(false) in handleStartFailure survived 156/156 → adjudicated below
M4 delivery mark ignores turn key stale-key guard in markTurnDelivered killed — exactly 1 fail: ignores a delivery mark carrying a stale turn key
M5 reset announcement on every terminal proposal complete-only reset killed — exactly 1 fail: fires after an edit made while the Goal was blocked
M5b remove the complete reset entirely same guard, other direction killed — exactly 1 fail: stays off for a Goal that replaces a verifier-accepted one
M6 renderer reverted to base behavior (if (false), old wording) both new prompt tests + golden prompts killed — 4 fail (2 golden, 2 new), all on expected-vs-actual prompt text
M7 delete one headless promotion delivery mark (CLI) headless markGoalTurnDelivered call site killed — exactly 1 fail of 134: marks a follow-up continuation delivered after a tool-terminated segment (132 passed, 1 pre-existing skip)
M8 releaseTurn commits announcement unconditionally commit-on-delivery (release path) killed — 3 fail (the drop-before-delivery trio)

M3 survivor adjudication — redundant defence, not dead code. m3-sequencer.mjs constructs the one interleaving the clause can decide: a host that marks a turn delivered and then refuses it, so the next dispatch's settle(currentTurnDelivered) would cash the stale mark. Measured (03-m3-sequencer-clause-decides.png): clause present → refused announcement discarded, edit back to the held text stays quiet (objectiveUpdated=false); clause removed → refused announcement committed, notice re-fires (objectiveUpdated=true). The clause therefore decides an outcome — it is not dead code — but no shipped host can produce the interleaving (TUI marks on drain/submit of a queued turn, ACP marks at modelStarted, headless marks at promotion; all strictly after a successful startGoalTurn accept, and a refused turn is never queued). Classification: redundant defence, correct as it stands — with M1/M8 holding the finish/release settle sites and M4 the key guard, the set closes the hazard from every reachable direction.

Positive control: 9 mutants turned tests red through the identical runner command that observed the M3 survival, so "survived" here cannot mean "the suite never ran".

Targeted gates (re-measured at head)

gate result
packages/core npx vitest run src/goals/ 16 files, 468 passed / 0 failed (PR body said 404 — drift from two merges of main, see Corrections)
packages/cli named suites (useMessageQueue.test.ts, useGeminiStream.test.tsx, nonInteractiveCli.test.ts, src/acp-integration) 42 files, 2197 passed / 1 skipped / 0 failed (110.6 s)
packages/core tsc --noEmit clean (exit 0)
packages/cli tsc --noEmit clean (exit 0)
eslint on all 12 changed files clean; gate proven live (planted unused variable was reported, then removed)
prettier --check on all 12 changed files clean
residual old-line grep (including one you already started working on) 0 source occurrences at head

Corrections

  • Commit-message mutation claim is stale for the final code (description correction, no code change requested). The head commit body says "dropping the roll-back on a refused start fails the redelivery test (113 green)". At the final head that mutation survives 156/156: after the series moved the announcement commit from accept-time to delivery-time, the refused-start roll-back became defence-in-depth (see M3 adjudication). The redelivery property itself is fully pinned — it just moved onto the commit-on-delivery design (M1/M8 kill it). Nothing in the diff is wrong; only the narrated probe no longer matches the shipped code.
  • Test-count drift. PR body cites 404 core goal tests and 40 + 233 + 130 + 1739 CLI tests; at head the same commands yield 468 and 2197 + 1 skipped. The branch merged main twice after the description was written; everything is green.

Findings

  1. (nit) Redundant-defence clause carries a stale commit-message justification. settleCurrentTurnAnnouncement(false) in handleStartFailure is kept and correct, but the commit message still claims removing it fails a test it no longer fails. No action needed on the code; a maintainer reading the history should know the clause is belt-and-braces, not load-bearing.
  2. (nit) Description numbers drifted. As above; both counts re-measured green.

No blocking findings. In particular, the three consequence classes worth ruling out were ruled out: the notice can not fire for a change that did not happen (M2a/M2b/content-keying cells, incl. no-op and trim edits and edit-back-to-held-text), can not be lost for a change that did happen (redelivery cells T8, M1/M8, stale-key M4, blocked→edit→resume M5), and can not leak into user turns (covered by suite + traced: promoted user turns never receive a matching delivery mark).

Not covered

  • Per-commit attribution. Checkout is depth 2; only the merge commit, base tip, and PR head exist locally (git rev-parse --is-shallow-repository = true; git rev-list HEAD^1..HEAD^2 = 1 vs 7 commits in the metadata snapshot). The aggregate HEAD^1..HEAD diff is what was verified; the intermediate-commit mutation numbers quoted in commit bodies were not individually re-run.
  • Trial merge into current main. No GitHub token in this sandbox; the merge-ref base tip (fc0e8276) already postdates the snapshot's baseRefOid (5a883243…) because the branch merged main twice. Whether main moved further since is not observable here.
  • Live TUI/ACP/headless runs against a real model. Host wiring verified via the mock-free unit/integration suites and code tracing, not a live Goal session; the daemon-restart tradeoff (in-memory announcement lost) is documented in the PR and was not exercised — its stated cost is one missing prompt line while the objective still travels in the data block.
  • Repo-wide lint/test gates. Only the changed files (eslint/prettier) and the affected workspace suites were run, per scope.
  • Windows/macOS behavior (Linux container only).

Methodology

Environment: CI verify container (node:22-bookworm), repo at refs/pull/10013/merge depth 2; npm ci + npm run build pre-done at HEAD. A/B: base side rebuilt as git worktree add tmp/base-tree HEAD^1 and compiled with tsc -p … --noCheck into the worktree's own dist/; harnesses import dist modules by absolute file URL and assert realpath containment at load, so no head code can leak into the base cell (the only shared dependency, nested node_modules/ajv, is the identical realpath on both arms). Scenario and expectations are encoded in ab-harness.mjs (arm-specific; a base cell failing as predicted counts as a passed assertion). Mutations were applied to head source with the edit tool, run through npx vitest run <the two test files>, and reverted with git checkout -- (tree verified clean after each); the M3 adjudication patched a copy of the built dist instead of source, restoring from backup afterwards. Gates are the exact commands cited above; eslint liveness was proven by planting and removing a violation. Raw logs: logs/ab-head.log, logs/ab-base.log, logs/goal-runtime.js.dist.bak; harnesses: ab-harness.mjs, m3-sequencer.mjs; evidence images: evidence/01-ab-cells-head-vs-base.png, evidence/02-mutation-matrix-core.png, evidence/03-m3-sequencer-clause-decides.png.

Flakiness gate log

rounds=5 files=6 skipped=0
file packages/cli/src/acp-integration/session/Session.test.ts: (cd packages/cli) npx --no-install vitest run ./src/acp-integration/session/Session.test.ts
file packages/cli/src/nonInteractiveCli.test.ts: (cd packages/cli) npx --no-install vitest run ./src/nonInteractiveCli.test.ts
file packages/cli/src/ui/hooks/useGeminiStream.test.tsx: (cd packages/cli) npx --no-install vitest run ./src/ui/hooks/useGeminiStream.test.tsx
file packages/cli/src/ui/hooks/useMessageQueue.test.ts: (cd packages/cli) npx --no-install vitest run ./src/ui/hooks/useMessageQueue.test.ts
file packages/core/src/goals/goal-continuation-prompt.test.ts: (cd packages/core) npx --no-install vitest run ./src/goals/goal-continuation-prompt.test.ts
file packages/core/src/goals/goal-runtime.test.ts: (cd packages/core) npx --no-install vitest run ./src/goals/goal-runtime.test.ts


per-file results (P=pass F=fail I=infra-exit, one letter per run):
  packages/cli/src/acp-integration/session/Session.test.ts: PPPPP
  packages/cli/src/nonInteractiveCli.test.ts: PPPPP
  packages/cli/src/ui/hooks/useGeminiStream.test.tsx: PPPP
  packages/cli/src/ui/hooks/useMessageQueue.test.ts: PPPP
  packages/core/src/goals/goal-continuation-prompt.test.ts: PPPP
  packages/core/src/goals/goal-runtime.test.ts: PPPP

verdict: timeout
summary: only 4 of 5 rounds fit the 15-minute budget; the completed rounds agreed

--- per-invocation detail (full copy in the artifact) ---
round 1 · packages/cli/src/acp-integration/session/Session.test.ts: P (exit 0)
round 1 · packages/cli/src/nonInteractiveCli.test.ts: P (exit 0)
round 1 · packages/cli/src/ui/hooks/useGeminiStream.test.tsx: P (exit 0)
round 1 · packages/cli/src/ui/hooks/useMessageQueue.test.ts: P (exit 0)
round 1 · packages/core/src/goals/goal-continuation-prompt.test.ts: P (exit 0)
round 1 · packages/core/src/goals/goal-runtime.test.ts: P (exit 0)
round 2 · packages/cli/src/acp-integration/session/Session.test.ts: P (exit 0)
round 2 · packages/cli/src/nonInteractiveCli.test.ts: P (exit 0)
round 2 · packages/cli/src/ui/hooks/useGeminiStream.test.tsx: P (exit 0)
round 2 · packages/cli/src/ui/hooks/useMessageQueue.test.ts: P (exit 0)
round 2 · packages/core/src/goals/goal-continuation-prompt.test.ts: P (exit 0)
round 2 · packages/core/src/goals/goal-runtime.test.ts: P (exit 0)
round 3 · packages/cli/src/acp-integration/session/Session.test.ts: P (exit 0)
round 3 · packages/cli/src/nonInteractiveCli.test.ts: P (exit 0)
round 3 · packages/cli/src/ui/hooks/useGeminiStream.test.tsx: P (exit 0)
round 3 · packages/cli/src/ui/hooks/useMessageQueue.test.ts: P (exit 0)
round 3 · packages/core/src/goals/goal-continuation-prompt.test.ts: P (exit 0)
round 3 · packages/core/src/goals/goal-runtime.test.ts: P (exit 0)
round 4 · packages/cli/src/acp-integration/session/Session.test.ts: P (exit 0)
round 4 · packages/cli/src/nonInteractiveCli.test.ts: P (exit 0)
round 4 · packages/cli/src/ui/hooks/useGeminiStream.test.tsx: P (exit 0)
round 4 · packages/cli/src/ui/hooks/useMessageQueue.test.ts: P (exit 0)
round 4 · packages/core/src/goals/goal-continuation-prompt.test.ts: P (exit 0)
round 4 · packages/core/src/goals/goal-runtime.test.ts: P (exit 0)
round 5 · packages/cli/src/acp-integration/session/Session.test.ts: P (exit 0)
round 5 · packages/cli/src/nonInteractiveCli.test.ts: P (exit 0)

Evidence images

01-ab-cells-head-vs-base

02-mutation-matrix-core

03-m3-sequencer-clause-decides

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

Qwen Code · sandboxed verification

@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 dismissed stale reviews from themself August 27, 2026 03:11

Superseded: this review stood on an earlier commit whose feedback was addressed and resolved; the current head was fully re-reviewed and approved pinned to its SHA. Dismissing own stale request-changes on triage re-run.

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

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

Autofix round report — PR #10013 (no code changes)

This round made no code changes: the feedback batch contains nothing actionable.

Feedback triage

  • [ic:5433747724] (@qwen-code-ci-bot) — informational status marker for the maintainer-requested /triage verification run ("Sandboxed verification is running … the report will be posted here when the run completes"). It makes no finding, no defect claim, and no requested change, so there is nothing to reproduce or implement. The verification report itself will arrive as a new comment and will be triaged in a later round.

Window state

  • No new reviews, no new inline comments, and no failed or still-red checks since the last evaluation (2026-08-26T14:04:51Z). The check snapshot contains 0 failures (25 SUCCESS, 122 SKIPPED, 8 CANCELLED steps from superseded runs).
  • Diff growth this window is within budget (source 11 / test 258 lines vs 400/400; 0 prior over-budget rounds) — informational only, no growth audit required.
  • No retry context (no rejected attempt, no budget warning, no same-run repair) was present.
  • PR head is unchanged at 2263d48a07 on goal/b3-objective-updated; the working tree is clean, --conflict false so no base merge was performed, and no commit was made this round.

Verification

No code changed this round, so no build/typecheck/lint/test commands were required or run; only read-only inspection of git state, checks, and feedback was performed. The current head 2263d48a07 was committed and verified by an earlier round, and this round adds nothing on top of it.

中文说明

Autofix 轮次报告 — PR #10013(本轮无代码改动)

本轮未做任何代码改动:本批反馈中没有需要处理的内容。

反馈分类

  • [ic:5433747724](@qwen-code-ci-bot —— 这是维护者发起的 /triage 验证运行的信息性状态标记("沙箱验证正在运行……运行结束后验证报告会发布在这里")。它不构成任何发现、不提出任何缺陷主张、也不请求任何改动,因此没有可复现或可实现的内容。验证报告本身稍后会以新评论的形式到达,届时将在后续轮次中处理。

窗口状态

  • 自上次评估(2026-08-26T14:04:51Z)以来,没有新的 review、没有新的行内评论,也没有失败或持续红色的检查。检查快照中 0 个失败(25 个 SUCCESS、122 个 SKIPPED、8 个来自已被取代运行的 CANCELLED 步骤)。
  • 本窗口内的 diff 增长在预算之内(源码 11 行 / 测试 258 行,预算 400/400;此前 0 轮超预算)—— 仅为信息性提示,无需增长审计。
  • 没有重试上下文(没有被拒绝的尝试、没有时间预算警告、没有同轮修复提示)。
  • PR head 保持不变,仍为 goal/b3-objective-updated 分支上的 2263d48a07;工作树干净,--conflict false 因此未合并 base,本轮未产生任何提交。

验证

本轮没有代码改动,因此不需要也没有运行任何 build/typecheck/lint/测试命令;仅对 git 状态、检查与反馈做了只读检查。当前 head 2263d48a07 由更早的轮次提交并验证,本轮没有在其之上添加任何内容。

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


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

@wenshao

wenshao commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator

Maintainer verification — built and run locally against the real stack

I built both arms from source and drove the change through all three Goal hosts — a real qwen serve daemon (the ACP Session host), the real interactive TUI, and the real headless nonInteractiveCli — against a fake OpenAI provider that ledgers every rendered Goal continuation prompt byte for byte. 329 continuation prompts recorded in total.

Verdict: the PR does what it says, and every load-bearing line is load-bearing. Every externally observable claim in the description reproduced on the real stack. Four one-line mutations of the shipped bundle each break exactly the behaviour their line exists to protect, and three more source-level mutations reproduce the commit messages' own probe counts to the test. I found no correctness problem. Two minor notes and an honest coverage gap are at the bottom.

Arms

arm commit what it is
HEAD 2263d48a this PR
BASE 5a883243 the PR's merge-base with main

Net diff BASE→HEAD is exactly the 12 files in the PR (+930/−8). git merge-tree against today's main (2bd0ff92) is conflict-free, and none of the 18 commits main gained since the PR base touch any file this PR touches — so the stale base is not hiding a semantic conflict.

Environment: macOS 26.6.2 (darwin 25.6.0), Node v24.18.1, isolated QWEN_HOME per arm, fake provider on loopback, qwen serve --no-web --workspace <fixture> --token ….


1. The line itself, on the wire

prompt line, base vs head

Same 8 scenarios, 39 continuation prompts per arm:

  • BASE sends …supersedes any earlier Goal objective in this conversation, including one you already started working on. on 39 of 39 — including the 19 in the four scenarios where the objective never changed at all. This is exactly the "warning that is identical on turn 2 and turn 40" the PR describes.
  • HEAD sends the reduced standing line on 39 of 39, and the separate change notice on exactly 3 — the three turns where the objective really had just changed.

A separate 31-turn headless run of an unchanged Goal on HEAD: 31 continuation prompts, one distinct standing line, 0 notices. No drift over a long run.

2. Scenario matrix — real qwen serve daemon, ACP Session host

Each row is its own daemon session, driven over HTTP (POST /session/:id/goal for the control actions, POST /session/:id/prompt for /goal <objective>).

scenario matrix

# what the operator did objective really changed? HEAD notices reading
A nothing, 4 turns no 0 / 4 steady Goals are silent
B /goal edit → different text yes 1 / 5 fires on the first continuation after the edit (rev 2), quiet after
C /goal edit → same text, padded no (revision 1→2) 0 / 5 keyed on content: a no-op edit bumps the revision but says nothing
D /goal <new> → replace, new Goal yes (revision back to 1) 1 / 5 keying on revision alone would have missed this
E pause → edit → resume yes 1 / 5 fires once after resume
F pause → resume, no edit no 0 / 5 pause/resume alone announces nothing
G clear → create a new Goal new work item 0 / 5 a fresh Goal does not claim it replaces completed work
H edit, then a direct user query yes delivered (see note) the ACP host renders the continuation and appends the user text into one request, so the notice went out inside that request

BASE is 0 in every row for the trivial reason that the line does not exist there.

3. What a user actually sees

Real TUI, real keystrokes (/goal … then /goal edit …), captured through node-pty + xterm.js. The fake model's reply echoes what the prompt it just received actually carried, so the screenshot is evidence rather than decoration.

BASE — the standing line claims a change on every single turn, including turn 6 where a change actually happened:

base TUI

HEAD — turns 1–5 are change-free, turn 6 (the first after /goal edit) carries the notice, turns 7–8 go quiet again:

head TUI

The headless host was driven the same way (-p "/goal …"): HEAD emits the reworded standing line, BASE the old one, neither emits a spurious notice.

4. Mutation probes on the shipped bundle

One-line edits to dist/chunks/*.js, then the same five scenarios re-run through the real daemon. Cells are notices / continuation prompts.

mutation matrix

mutant one-line change what broke
M1 objectiveUpdated = announced !== undefined (drop the content compare) the noise the PR removes comes straight back: 3/4 on the steady Goal, 4/5 everywhere else
M2 delete markTurnDelivered() from Session.ts the notice never fires at all (0/5 on B and D) — acceptance really is not delivery
M3 clear no longer forgets the announcement a false notice on the first turn of the newly created Goal (G: 1/5)
M4 key on (goalId, revision) instead of objective text false notice on the no-op edit (C: 1/5) and the replace notice is lost (D: 0/5) — both halves of the PR's argument for content-keying, in one probe

Every mutant is caught by exactly the scenarios its line is responsible for, and by no others.

5. The commit messages' own mutation claims, reproduced

Source-level mutations against packages/core goal-runtime.test.ts (144 tests):

mutation result killed by
unconditional commit in finishTurn 1 failed / 143 passed keeps the notice owed when a turn finishes under the permit without the prompt
delivery mark ignores the turn key 1 failed / 143 passed ignores a delivery mark carrying a stale turn key
reset on every terminal, not just complete 1 failed / 143 passed fires after an edit made while the Goal was blocked
drop the content compare 11 failed / 133 passed the whole notice suite
key on the revision alone 3 failed / 141 passed replace · refused-turn leak · no-op edit

The three "1 failed / 143 green" figures match the second and third commit messages exactly.

6. Tests, lint, merge

  • packages/corenpx vitest run src/goals/16 files, 468 tests, all pass.
  • packages/clinpx vitest run src/ui/hooks/useMessageQueue.test.ts src/ui/hooks/useGeminiStream.test.tsx src/nonInteractiveCli.test.ts src/acp-integration42 files, 2196 passed, 1 skipped.
  • eslint and prettier --check clean on all 12 changed files. npm run build + npm run bundle clean on both arms.
  • The old sentence including one you already started working on no longer appears anywhere in the tree, and both new lines live only in the renderer plus its tests — no second copy to drift, and no docs reference the prompt text.
  • The shipped bundle confirms the flag is threaded through all three hosts: objectiveUpdated and markTurnDelivered are present in the ACP, headless and TUI chunks; both are absent from the BASE bundle.

Notes

  1. The Reviewer Test Plan is stale. It says packages/core … 404 tests, 16 files; the actual count on head is 468 across 16 files, because the two follow-up commits added tests. Worth refreshing the numbers before merge — nothing else in the plan is wrong.

  2. Pre-existing, not introduced here, and mildly in this PR's favour. When /goal edit lands while a continuation is already in flight, the daemon/ACP host preempts and the replacement request carries two Goal data blocks inside one user message — the stale one and the new one — followed by the standing line and (on HEAD) the notice. My matrix produced 7 such requests per arm, identical count on BASE and HEAD, so this is existing host behaviour. It does argue for the rewording though: supersedes any **other** Goal objective text in this conversation covers a stale block sitting in the same message, which any **earlier** Goal objective in this conversation arguably did not.

What I could not verify end to end

  • Scenario H, the specific race the third commit fixes — a queued continuation's permit claimed by text that is not the continuation prompt. Both natural attempts ended with the prompt delivered anyway: the ACP host bundles the continuation with the user text into one request, and the TUI queues a message typed during a running Goal turn rather than admitting it through the slot. Consistent with that, the bundle mutant that reverts this fix (settleCurrentTurnAnnouncement(true) in finishTurn) is behaviourally identical to HEAD across all 8 of my scenarios — and is killed by exactly the one unit test above. So the fix is covered, just not by my black-box matrix.
  • Ordering of the notice relative to verifierFeedback / windDown — code order plus the golden-prompt tests only; my fake model never proposed a terminal state, so no verifier feedback was ever rendered.
  • The documented in-memory trade-off (announcement lost across a daemon restart) was not exercised; it is deliberate and documented at the declaration.

LGTM from my side — I'd merge this after the test-count line in the body is refreshed.

中文说明

维护者本地验证 —— 在真实栈上构建并运行

我从源码构建了两条腿,并让改动跑通了全部三个 Goal host:真实的 qwen serve daemon(即 ACP Session host)、真实的交互式 TUI、以及真实的 headless nonInteractiveCli;模型侧换成一个假 OpenAI 供应商,它逐字节记录每一条渲染出来的 Goal 续跑提示词。总共记录了 329 条续跑提示词。

结论:这个 PR 做到了它声称的事,而且每一行关键代码都确实是承重的。 描述里所有可从外部观察的断言都在真实栈上复现;对已构建产物做的四个单行变异,各自恰好打破它对应那行代码所保护的行为;另外三个源码级变异逐条复现了 commit message 里自己写的探针计数。没有发现正确性问题。文末有两条小提示和一处诚实的覆盖缺口。

两条腿

commit 说明
HEAD 2263d48a 本 PR
BASE 5a883243 本 PR 与 main 的 merge-base

BASE→HEAD 的净差异恰好就是 PR 里那 12 个文件(+930/−8)。对今天的 main2bd0ff92)做 git merge-tree 无冲突,且自 PR base 以来 main 新增的 18 个 commit 没有任何一个碰过本 PR 改动的文件——所以「base 落后」并没有藏着语义冲突。

环境:macOS 26.6.2(darwin 25.6.0)、Node v24.18.1,每条腿独立 QWEN_HOME,假供应商跑在 loopback,qwen serve --no-web --workspace <fixture> --token …

1. 这一行本身(线上真实字节)

同样的 8 个场景,每条腿 39 条续跑提示词:

  • BASE39/39 上都发 …supersedes any earlier Goal objective in this conversation, including one you already started working on.——包括其中 19 条来自「objective 自始至终没变过」的四个场景。这正是 PR 描述里说的「第 2 轮和第 40 轮一模一样的警告」。
  • HEAD39/39 上发削减后的常驻护栏,另外只在恰好 3 条上追加变更通知——正是 objective 真的刚变过的那三轮。

另跑了一次 31 轮、objective 从未变动的 headless:31 条提示词、一种常驻文案、0 条通知。长跑不漂移。

2. 场景矩阵 —— 真实 qwen serve daemon,ACP Session host

每一行都是独立的 daemon 会话,通过 HTTP 驱动(控制动作走 POST /session/:id/goal/goal <objective>POST /session/:id/prompt)。

# 操作者做了什么 objective 真的变了吗 HEAD 通知数 读法
A 什么都不做,跑 4 轮 0 / 4 稳定的 Goal 保持安静
B /goal edit → 换成不同文本 1 / 5 edit 后第一条续跑(rev 2)发一次,之后安静
C /goal edit → 同一文本(前后加空格) 否(revision 1→2) 0 / 5 按内容判定:空编辑抬高了 revision,但没给模型任何新东西
D /goal <新目标> → replace,新 Goal (revision 回到 1) 1 / 5 只看 revision 会漏掉这一条
E pause → edit → resume 1 / 5 resume 后发一次
F pause → resume,不编辑 0 / 5 单纯 pause/resume 不宣告任何变更
G clear → 新建一个 Goal 新工作项 0 / 5 全新 Goal 不会声称自己取代了已完成的工作
H edit 之后紧接一条用户直发消息 已投递(见下) ACP host 会把续跑提示词与用户文本渲染进同一条请求,通知随该请求发出

BASE 每一行都是 0,原因很简单:那条线在 BASE 上根本不存在。

3. 用户实际看到的样子

真实 TUI、真实按键(先 /goal …,再 /goal edit …),通过 node-pty + xterm.js 抓图。假模型的回复会回显「它刚收到的这条提示词里究竟带了什么」,所以截图本身就是证据而不是配图。

  • BASE:常驻那一行在每一轮都声称发生了变更——包括真的发生了变更的第 6 轮,措辞与前五轮一字不差。
  • HEAD:第 1–5 轮不含变更措辞;第 6 轮(/goal edit 之后的第一条)带上通知;第 7–8 轮重新安静。

headless host 用同样方式驱动(-p "/goal …"):HEAD 发新措辞的常驻护栏,BASE 发旧的,两边都没有误发通知。

4. 对已构建产物做的变异探针

直接改 dist/chunks/*.js 的单行锚点,再用真实 daemon 重跑同样五个场景。单元格是「通知数 / 续跑提示词数」。

变异 单行改动 打破了什么
M1 objectiveUpdated = announced !== undefined(去掉内容比较) PR 要消除的噪声原样回来:稳定 Goal 3/4,其余场景 4/5
M2 删掉 Session.ts 里的 markTurnDelivered() 通知完全不再发出(B、D 均 0/5)——「被 host 接受」确实不等于「已投递」
M3 clear 不再清空宣告状态 新建 Goal 的第一条续跑出现误报通知(G:1/5)
M4 改回按 (goalId, revision) 判定 空编辑误报(C:1/5),同时 replace 的通知丢失(D:0/5)——一个探针同时证明了 PR 选择内容判定的两半理由

每个变异都恰好被它对应那行代码负责的场景抓住,且不会误伤其他场景。

5. 复现 commit message 里自己写的变异声明

源码级变异 + packages/coregoal-runtime.test.ts(144 个用例):

变异 结果 被哪个用例杀死
finishTurn 无条件提交 1 失败 / 143 通过 keeps the notice owed when a turn finishes under the permit without the prompt
投递标记忽略 turn key 1 失败 / 143 通过 ignores a delivery mark carrying a stale turn key
每个终态都重置(而非只有 complete 1 失败 / 143 通过 fires after an edit made while the Goal was blocked
去掉内容比较 11 失败 / 133 通过 整个通知用例组
只看 revision 3 失败 / 141 通过 replace · 被拒轮次泄漏 · 空编辑

三条「1 失败 / 143 绿」与第二、第三个 commit message 写的完全一致。

6. 测试、Lint、合并

  • packages/corenpx vitest run src/goals/——16 个文件、468 个用例全过
  • packages/clinpx vitest run src/ui/hooks/useMessageQueue.test.ts src/ui/hooks/useGeminiStream.test.tsx src/nonInteractiveCli.test.ts src/acp-integration——42 个文件、2196 通过、1 跳过
  • 12 个改动文件的 eslintprettier --check 全干净;两条腿的 npm run build + npm run bundle 均成功。
  • 旧句子 including one you already started working on 在整棵树里已不存在;两条新文案只活在渲染器与其测试里——没有第二份副本会漂移,文档也没有引用提示词原文。
  • 已构建产物证明标志确实穿过了全部三个 host:ACP、headless、TUI 三个 chunk 里都有 objectiveUpdatedmarkTurnDelivered;BASE 产物里两者都不存在。

提示

  1. Reviewer Test Plan 的数字过期了。 里面写 packages/core … 404 tests, 16 files,而 head 上的实际数字是 468(16 个文件不变),因为后两个 commit 又加了用例。合并前顺手刷新一下即可,计划里其余内容没有问题。

  2. 既有行为,不是本 PR 引入的,而且反而对本 PR 有利。/goal edit 恰好落在一条在途续跑上时,daemon/ACP host 会抢占,替换后的那条请求会在同一条 user 消息里携带两个 Goal 数据块——过期的那个和新的那个——后面才是常驻护栏(HEAD 上还有通知)。我的矩阵在两条腿上各产生 7 条这样的请求,数量完全一致,说明是既有行为。不过这恰好支持了这次改写:supersedes any **other** Goal objective text in this conversation 能覆盖「过期块就在同一条消息里」的情况,而旧措辞 any **earlier** Goal objective in this conversation 严格讲覆盖不到。

我没能端到端验证的部分

  • 场景 H,也就是第三个 commit 修的那个具体竞态——排队中的续跑 permit 被「不是续跑提示词」的其他文本占用。两次自然尝试最后都还是把提示词投递出去了:ACP host 会把续跑提示词和用户文本打包进同一条请求;而 TUI 在 Goal 轮次运行中会把用户输入排队,而不是从那个槽位放行。与此一致的是,把这个修复回退掉的产物变异(finishTurn 里改成 settleCurrentTurnAnnouncement(true))在我全部 8 个场景上与 HEAD 行为完全相同——但被上面那条单元用例精确杀死。所以这个修复是有覆盖的,只是我的黑盒矩阵覆盖不到。
  • 通知相对 verifierFeedback / windDown 的顺序——只有代码顺序与黄金提示词测试;我的假模型从不提出终态,因此从未渲染出 verifier feedback。
  • 文档中写明的内存态取舍(宣告状态随 daemon 重启丢失)未做实测;这是刻意取舍且已在声明处注明。

我这边 LGTM —— 建议把正文里的用例数刷新后合入。

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

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

No changes needed this round

The only feedback newer than the last evaluation is @wenshao's maintainer-verification comment — an LGTM with two minor notes. Neither note requires (or permits) a code change.

Point-by-point disposition

1. The LGTM verdict and verification matrix — no action. The comment confirms every load-bearing claim of the PR reproduced on the real stack and reports no correctness problem. Nothing to address.

2. Note 1 — stale test count in the Reviewer Test Plan (PR body). Verified as real, but the fix is a PR-body edit on GitHub, which this loop cannot perform: an address-review round can only land code commits, and this agent holds no GitHub write credentials (the workflow owns all GitHub writes and has no body-update channel for review rounds). So I am surfacing it here with the verified numbers instead of editing anything:

  • Verified on this checkout (2263d48a07): cd packages/core && npx vitest run src/goals/Test Files 16 passed (16), Tests 468 passed (468).
  • Suggested one-line fix for the Reviewer Test Plan section of the PR body: change the packages/core goals-suite count from 404 tests to 468 tests (the file count, 16, is unchanged).

Leaving this to the maintainer, matching their stated intent: "I'd merge this after the test-count line in the body is refreshed."

3. Note 2 — two Goal data blocks in one message when /goal edit preempts an in-flight continuation. No action. The maintainer's own matrix shows this is pre-existing host behaviour, byte-identical across both arms (7 such requests each), and they note it argues in favour of the new any other Goal objective text wording. Nothing to change, and nothing introduced here to fix.

State

  • Branch unchanged: HEAD remains 2263d48a07, no commits this round.
  • No inline comments in this feedback batch, so no thread replies or resolutions.
  • One focused Vitest run performed as verification: packages/core src/goals/ — 468 passed (see above).
中文说明

本轮无需改动

上次评估之后新增的反馈只有 @wenshao 的维护者验证评论——一条 LGTM,附带两条小提示。两条提示都不需要(也不允许)代码改动。

逐点处置

1. LGTM 结论与验证矩阵——无需处理。该评论确认本 PR 的每个关键断言都在真实栈上复现,且未发现正确性问题。没有需要处理的内容。

2. 提示 1——Reviewer Test Plan(PR 正文)中的用例数过期。 已核实属实,但修复方式是编辑 GitHub 上的 PR 正文,本循环无法执行:address-review 轮次只能推送代码 commit,而本 agent 没有任何 GitHub 写权限(所有 GitHub 写操作由工作流负责,且评审轮次没有更新 PR 正文的通道)。因此这里不做任何编辑,只附上已核实的数字予以呈报:

  • 已在当前检出(2263d48a07)上验证:cd packages/core && npx vitest run src/goals/Test Files 16 passed (16),Tests 468 passed (468)
  • 对 PR 正文 Reviewer Test Plan 一节的一行修复建议:把 packages/core goals 套件的数量从 404 tests 改为 468 tests(文件数 16 不变)。

交给维护者处理,与其表述的意图一致:"I'd merge this after the test-count line in the body is refreshed."(正文里的用例数刷新后我就合入。)

3. 提示 2——/goal edit 抢占在途续跑时,同一条消息里出现两个 Goal 数据块。 无需处理。维护者自己的矩阵显示这是既有的 host 行为,两条腿上逐字节一致(各 7 条此类请求),并且作者指出这反而支持新的 any other Goal objective text 措辞。没有可改之处,也不是本 PR 引入的问题。

状态

  • 分支未变动:HEAD 仍为 2263d48a07,本轮没有提交。
  • 本批反馈中没有行内评论,因此没有线程回复或 resolve 操作。
  • 本轮作为验证执行了一次聚焦 Vitest:packages/core src/goals/ —— 468 个用例全部通过(见上)。

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


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

@qqqys
qqqys enabled auto-merge August 27, 2026 06:48

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

@qqqys
qqqys added this pull request to the merge queue Aug 27, 2026
Merged via the queue into QwenLM:main with commit a0784a2 Aug 27, 2026
215 of 223 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants