Skip to content

feat(goal): stop autonomous continuation at a token budget the user re-arms - #9891

Merged
qqqys merged 12 commits into
QwenLM:mainfrom
qqqys:goal/d1-token-budget
Aug 26, 2026
Merged

feat(goal): stop autonomous continuation at a token budget the user re-arms#9891
qqqys merged 12 commits into
QwenLM:mainfrom
qqqys:goal/d1-token-budget

Conversation

@qqqys

@qqqys qqqys commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

Arms every newly created Goal with an autonomous spend window — GOAL_DEFAULT_TOKEN_BUDGET, 30M tokens on the tokensUsed metric the recorder already bills per Goal turn — and stops autonomous continuation when the window is spent. The gate sits in queueContinuation, the single point every autonomous continuation is minted through, so one check bounds turn cadence, verifier-rejection retries, checkpoint cycles, and loop families not yet discovered. A spent budget settles the Goal as usage_limited with the new limitKind: 'token_budget' instead of minting the continuation. User-driven turns never pass through that gate and are never blocked by the budget.

The budget is an authorization quantum, not a fault. Resuming a budget-stopped Goal moves the ceiling to tokensUsed + grant — the meter is never reset — and an edit of a spent Goal re-arms the same way, so both explicit user actions buy another window. An unattended runaway stops and stays stopped, because nobody is there to resume it. Goals persisted before budgets existed restore unbounded; a host can opt out with a non-finite grant, which arms nothing rather than persisting a value the JSON journal cannot carry (Infinity does not survive JSON.stringify).

Supporting changes: the reducer's evidence-limited resume refusal now matches the two evidence kinds instead of any limitKind, so a budget-stopped Goal is not misread as evidence-limited; the SDK GoalLimitKind union and the webui mapper whitelist carry the new kind across the wire; the unpermitted get_goal summary reports tokenBudget beside the tokensUsed it already exposed.

Why it's needed

A Goal run currently has no autonomous termination path: every stop is either the model completing, one specific enumerated bound, or a human typing /goal pause. Both runaway sessions analyzed in this series ended the third way — one burned 8.6M tokens in 34 minutes before a human killed it (#9877 documents the loop that drove it). The merged precision fixes (#9165, #9835, #9880) each remove one discovered loop family; a budget is the blunt bound that covers the undiscovered ones. This is the same first line of defense the comparable systems ship — a hard stop-block cap in one, an arithmetic token budget in the other — and it deliberately does not try to be clever: it bounds autonomy, and hands the "keep going" decision back to the user at a fixed spend interval.

The retry-bound half of #9877 lands here as promised in #9880: terminal-proposal retries burn tokens like every other continuation, so the budget bounds them without a second counter.

Reviewer Test Plan

How to verify

  • cd packages/core && npx vitest run src/goals/ — 404 tests, 16 files. New coverage: the runtime stop-and-re-arm loop (stops autonomous continuation when the budget is spent, and resume re-arms it), the opt-out grant, and eight reducer transitions (create/replace stamping, resume/edit re-arm, unbounded goals never retrofitted, evidence kinds still refused, persistence round-trip incl. malformed budgets).
  • cd packages/webui && npx vitest run src/daemon/session/mappers.test.ts — 38 tests; token_budget crosses the wire, unknown kinds still dropped.
  • Mutation probes run during development: deleting the queueContinuation gate fails exactly the budget-stop test (110 others green); forcing rearmedTokenBudget to return nothing fails exactly the three re-arm tests (187 others green).
  • Typecheck: tsc --noEmit clean in packages/webui; packages/core carries 4 pre-existing errors on the merge base (verified identical with the diff stashed), none in src/goals/; sdk builds clean.

Evidence (Before & After)

N/A (runtime bound; no UI change — the existing usage-limited surfaces render the stop, and Resume is offered by the existing status-only gate once #9840 lands, see Risk).

Tested on

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

Environment (optional)

N/A (unit tests only).

Risk & Scope

  • Main risk or tradeoff: the default changes behavior for long legitimate runs — a Goal that spends 30M tokens now stops once and waits for /goal resume instead of running to completion unattended. That interruption is the feature: one explicit user action per spend window. The grant size is a named constant with the sizing rationale in its doc comment.
  • Not validated / out of scope: per-goal budget overrides at create time (/goal set … --budget) and surfacing remaining budget to the model for a graceful wind-down — both deliberately deferred (D2/D3 in the series plan). Web Shell currently ships a limitKind-based Resume gate that would withhold Resume for budget-stopped Goals until fix(goal): resume an evidence-limited Goal from a fresh window #9840 (status-only gate) merges; sequencing either order is safe, the affordance is just conservative in the gap.
  • Breaking changes / migration notes: none. Old persisted Goals restore unbounded; the wire change is a widened optional enum.

Linked Issues

中文说明

这个 PR 做了什么

为每个新创建的 Goal 武装一个自主消费窗口——GOAL_DEFAULT_TOKEN_BUDGET,即 recorder 已按 Goal turn 计费的 tokensUsed 口径下的 3000 万 token——窗口耗尽时停止自主续跑。闸门设在 queueContinuation,即所有自主续跑被铸造的唯一通道,因此一个检查就能约束 turn 节奏、verifier 拒绝重试、checkpoint 循环,以及尚未被发现的循环家族。预算耗尽时,runtime 将 Goal 落为 usage_limited 并带上新的 limitKind: 'token_budget',而不是铸造续跑。用户驱动的 turn 不经过该闸门,永远不会被预算阻塞。

预算是一种授权额度,不是故障。恢复一个因预算停止的 Goal 会把上限移到 tokensUsed + grant——计量表永不重置——编辑一个预算耗尽的 Goal 也以同样方式重新武装,因此两种显式用户动作都购买了另一个窗口。无人值守的失控运行会停止并保持停止,因为没有人去恢复它。预算出现之前持久化的 Goal 恢复后不受限;宿主可以用非有限的 grant 选择退出,此时不武装任何预算,而不是持久化一个 JSON 日志无法承载的值(Infinity 无法通过 JSON.stringify)。

配套变更:reducer 的证据受限恢复拒绝现在只匹配两种证据类 limitKind,而不是任意 limitKind,因此预算停止的 Goal 不会被误读为证据受限;SDK 的 GoalLimitKind 联合类型与 webui mapper 白名单让新类型跨越线上传输;无许可的 get_goal 摘要在已有的 tokensUsed 旁报告 tokenBudget

为什么需要

Goal 运行目前没有任何自主终止路径:所有停止要么是模型完成、要么是某个具体的枚举边界、要么是人工输入 /goal pause。本系列分析的两个失控 session 都以第三种方式结束——其中一个在 34 分钟内烧掉 860 万 token 才被人工终止(#9877 记录了驱动它的循环)。已合入的精确修复(#9165#9835#9880)各自消灭一个已发现的循环家族;预算是覆盖未发现家族的钝性边界。这也是同类系统的第一道防线——一家是 stop-block 硬上限,另一家是算术 token 预算——它刻意不追求聪明:约束自主性,并以固定的消费间隔把「是否继续」交还给用户。

#9877 的重试上界一半在此落地,兑现 #9880 的承诺:终局提案的重试和其他续跑一样燃烧 token,预算无需第二个计数器即可约束它们。

评审验证计划

如何验证

  • cd packages/core && npx vitest run src/goals/——404 个测试,16 个文件。新增覆盖:runtime 的停止-重新武装循环、退出 grant,以及八个 reducer 转换(create/replace 盖章、resume/edit 重新武装、无预算 Goal 永不追加、证据类仍被拒绝、持久化往返含畸形预算)。
  • cd packages/webui && npx vitest run src/daemon/session/mappers.test.ts——38 个测试;token_budget 跨线传输,未知类型仍被丢弃。
  • 开发期间的变异检验:删除 queueContinuation 闸门恰好挂预算停止测试(其余 110 绿);强制 rearmedTokenBudget 返回空恰好挂三个重新武装测试(其余 187 绿)。
  • 类型检查:packages/webuitsc --noEmit 干净;packages/core 在合并基线上有 4 个已存在错误(stash 验证与 diff 无关),无一在 src/goals/;sdk 构建干净。

证据(前后对比)

N/A(运行时边界;无 UI 变化——现有 usage-limited 界面渲染停止状态,#9840 合入后现有 status-only 闸门会提供 Resume,见风险)。

已测试平台

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

风险与范围

  • 主要风险或权衡:默认值改变了长时间合法运行的行为——消费 3000 万 token 的 Goal 现在会停止一次并等待 /goal resume,而不是无人值守地跑到完成。这个中断正是特性本身:每个消费窗口需要一次显式用户动作。grant 大小是带有量纲推导注释的命名常量。
  • 未验证/范围外:创建时的按 Goal 预算覆盖(/goal set … --budget)与向模型暴露剩余预算以实现优雅收尾——均刻意推迟(系列计划中的 D2/D3)。Web Shell 当前的 limitKind Resume 闸门在 fix(goal): resume an evidence-limited Goal from a fresh window #9840(status-only 闸门)合入前会对预算停止的 Goal 隐藏 Resume;两种合入顺序都安全,间隙期内只是保守。
  • 破坏性变更/迁移说明:无。旧持久化 Goal 恢复后不受限;线上变更是扩宽的可选枚举。

关联 Issue

…e-arms

A Goal run in this repository has no autonomous termination path: every
stop so far is either the model completing, a specific enumerated bound,
or a human typing /goal pause. The two runaway sessions that motivated
this series both ended the third way -- one after 8.6M tokens in 34
minutes. Precision fixes remove the loop families we have found; this
adds the bound that covers the families we have not.

Every newly created Goal is armed with an autonomous spend window
(GOAL_DEFAULT_TOKEN_BUDGET, 30M tokens on the `tokensUsed` metric the
recorder already bills per turn). The gate sits in `queueContinuation`,
the single point every autonomous continuation is minted through, so one
check bounds turn cadence, verifier-rejection retries, checkpoint cycles,
and loops not yet discovered. When the window is spent the runtime
settles the Goal as `usage_limited` with the new `limitKind:
'token_budget'` instead of minting the continuation. User-driven turns
never pass through the gate and are never blocked.

The budget is an authorization quantum, not a fault: resuming a
budget-stopped Goal moves the ceiling to `tokensUsed + grant` -- the
meter itself is never reset -- and the same re-arm applies to an edit of
a spent Goal, so both explicit user actions buy another window. An
unattended runaway stops and stays stopped, because nobody is there to
resume it. Goals persisted before budgets existed restore unbounded, and
a host can opt out with a non-finite grant, which arms nothing rather
than persisting a value the JSON journal cannot carry.

The reducer's evidence-limited resume refusal now matches the two
evidence kinds instead of any `limitKind`, so a budget-stopped Goal is
not misread as evidence-limited. The SDK union and the webui mapper
whitelist carry the new kind across the wire, and the unpermitted
get_goal summary reports `tokenBudget` beside the `tokensUsed` it
already exposed.

Mutation probes: deleting the continuation gate fails exactly the
budget-stop test (110 others green); forcing the re-arm helper to return
nothing fails exactly the three re-arm tests (187 others green).
@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 24, 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 24, 2026

Copy link
Copy Markdown
Collaborator

Gate re-run at the current head 17fc1b86. Two substantive commits landed since the last reviewed pass (51d5aeb7): the test pin the sponsored /verify report asked for, and a coverage note on the budget meter — plus main merges. The prior approval was dismissed when those commits landed, so this pass re-reviews from the top. The gate still passes.

Template looks good ✓

Problem: observed, not theoretical. #9877 documents a Goal runaway that burned 8.6M tokens in 34 minutes before a human killed it; this is the budget half of that fix, promised in #9880. Unchanged since the prior pass — and the maintainer's local real-stack verification in the thread below confirms the stop fires in practice.

Direction: aligned. The comparable systems converged on the same first line of defense — claude-code's CHANGELOG carries --max-budget-usd halting background subagents once the cap is reached, plus per-session spawn caps added explicitly to stop runaway loops. Bounding Goal autonomy at a fixed spend interval with an explicit user re-arm is the same shape, and it extends machinery that already landed (#9165 limitKind, #9583 per-turn metering) instead of adding a parallel system.

Size: core paths touched (packages/core/src/goals/**, plus SDK and webui wire plumbing) — 333 production logic lines (goal-runtime 172, goal-reducer 79, goal-protocol 64, sdk types 8, goal-tools 6, webui mappers 4), 440 test lines, 0 generated/schema. Below the 500-line awareness threshold; as a core change it proceeds under the 100%-confidence bar.

Approach: scope unchanged and still right — one gate in queueContinuation (the single minting point for autonomous continuations), re-arm by moving the ceiling instead of resetting the meter, old persisted Goals restoring unbounded, per-goal overrides and a model-visible remaining budget deliberately deferred. The delta since the last pass is exactly what the /verify report asked for: the budget-stop resume test now pins evidenceCursor retention (the report's surviving M4 mutant no longer survives), and the meter-coverage note landed on the constant. The only carry-over nit remains the one-line unrelated reflow in uiTelemetry.test.ts.

Risk: no elevated risk signals (no high-risk paths matched).

Moving on to code review. 🔍

中文说明

在当前 head 17fc1b86 上重跑闸门。自上次审查(51d5aeb7)以来新增两个实质提交:受资助 /verify 报告要求的测试钉住,以及预算常量的计量覆盖说明——外加 main 合并。先前批准已随新提交被驳回,故本轮从头重审。闸门仍然通过。

模板完整 ✓

问题:已观测到,不是理论性的。#9877 记录了一次 34 分钟烧掉 860 万 token 的 Goal 失控;本 PR 是 #9880 承诺的"预算"一半。与上轮一致——且线程下方维护者的真实栈本地验证证实停止确实触发。

方向:对齐。同类系统收敛到同样的第一道防线——claude-code CHANGELOG 中有到达上限即停止后台 subagent 的 --max-budget-usd,以及明确为止住失控循环而加的会话级生成上限。以固定消费间隔约束 Goal 自主性、由用户显式重新武装,是同样的形状;且扩展的是已合入机制(#9165 limitKind#9583 按 turn 计量),而非新增平行系统。

规模:触及核心路径(packages/core/src/goals/**,外加 SDK 与 webui 线上传输管道)——生产逻辑 333 行(goal-runtime 172、goal-reducer 79、goal-protocol 64、sdk types 8、goal-tools 6、webui mappers 4)、测试 440 行、生成/schema 0 行。低于 500 行关注阈值;作为核心改动按 100% 置信标准推进。

方案:范围不变且仍然合理——闸门设在 queueContinuation(所有自主续跑的唯一铸造点),重新武装通过抬高上限而非重置计量表,旧持久化 Goal 恢复后不受限,按 Goal 覆盖与模型可见剩余预算明确推迟。自上轮以来的增量恰是 /verify 报告所要求的:预算停止恢复测试现在钉住 evidenceCursor 保留(报告中存活的 M4 变异体不再存活),计量覆盖说明也落在常量上。唯一遗留小点仍是 uiTelemetry.test.ts 的一行无关重排。

风险:无升级风险信号(未命中高风险路径)。

进入代码审查 🔍

Qwen Code · qwen3.8-max

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

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

Code review

The head moved only on test and comment lines since the last reviewed pass (51d5aeb7): the budget-stop resume test gained the evidenceCursor: { recordId: 'r-100' } assertion the /verify report's Finding #1 asked for, and the budget constant gained a meter-coverage note. I re-verified the mechanism at this head anyway rather than carrying the conclusion forward:

  • The chokepoint still holds. All eleven queueContinuation call sites — turn-cadence tail, verifier_reject retries, checkpoint cycles, start-failure recovery, permit-release requeues, the dispatch tail after resume — pass the budget check, which sits behind the re-entry guards and ahead of continuationQueued = true. beginTurn (the user-driven turn path) issues permits directly and never sees the gate, so the budget never blocks an explicit user action — stated and honored. The restart boundary works through the same gate: a crash-recovered ACTIVE Goal whose spend already crossed the ceiling re-settles on bindHostqueueContinuation without minting a turn.
  • The reducer ordering is right. The token_budget resume branch runs before the evidence-window branch, so a budget-stopped Goal never hits the cursor reset; isEvidenceLimited matches the two evidence kinds by value. The new cursor-retention assertion closes the one axis the /verify mutation matrix found unpinned (its M4 mutant — narrowing reverted and branch dead, silently unciting pre-stop evidence — now fails this test).
  • Re-arm and persistence semantics verified. Finite grant on a spent Goal moves the ceiling to tokensUsed + grant on resume and edit; unspent ceilings and unbounded Goals are left alone; a non-finite opt-out clears the field (deletion handled in transitionGoal, since Infinity does not survive the JSON journal); the parser validates tokenBudget with Number.isFinite, rejecting negative, NaN, and Infinity in persisted snapshots; pre-budget Goals restore unbounded. The two pre-existing usage_limited settle sites stay folded into the shared helpers — no behavior change there.
  • Production wiring. The single createGoalRuntime call site (config.ts) passes no grant, so the 30M default arms everywhere, with the recorder as tokenLedger — spend is Goal-scoped at record time. SDK union and webui mapper whitelist carry token_budget across the wire; unknown kinds still dropped.

No critical blockers. One standing nit, non-blocking: the one-line uiTelemetry.test.ts reflow is still the only line in the diff not in service of the budget.

sequenceDiagram
    participant P1 as User
    participant P2 as Goal runtime
    participant P3 as Journal
    participant P4 as Web Shell resume gate
    P2->>P2: finishTurn bills tokensUsed
    P2->>P2: queueContinuation checks the budget
    alt budget spent
        P2->>P3: record usage_limited, limitKind token_budget
        P2-->>P1: Goal stops and waits
        P4-->>P4: Resume offered (status-only gate)
        P1->>P2: resume or edit
        P2->>P2: ceiling moves to tokensUsed plus grant
        P2->>P2: continuation admitted again
    else budget unspent
        P2->>P2: mint the next turn as before
    end
Loading
Files changed (12 of 12 shown)
File What changed
packages/core/src/goals/goal-protocol.ts budget constant with sizing and meter-coverage rationale, stop-reason helper, shared spent predicate, token_budget kind, tokenBudget field
packages/core/src/goals/goal-reducer.ts grant stamping on create/replace, re-arm on resume/edit, token_budget resume branch ahead of the evidence branch, budget parse validation
packages/core/src/goals/goal-reducer.test.ts transition tests incl. exact-ceiling, opt-out clearing, no-retrofit, evidence-window re-arm, persistence round-trip, and the new cursor-retention pin
packages/core/src/goals/goal-runtime.ts the gate in queueContinuation, the stop-and-settle path, shared settle helpers, grant wired into dispatch
packages/core/src/goals/goal-runtime.test.ts stop-and-re-arm loop, default-30M pin, exact ceiling, failed-settle-write path, Infinity opt-out
packages/core/src/goals/goal-tools.ts unpermitted get_goal summary reports tokenBudget
packages/core/src/goals/goal-tools.test.ts summary expectation updated
packages/core/src/telemetry/uiTelemetry.test.ts unrelated one-line prettier reflow
packages/sdk-typescript/src/daemon/types.ts GoalLimitKind union widened
packages/web-shell/client/utils/goalGate.test.ts pins that a budget-stopped Goal is resumable through the status-only gate
packages/webui/src/daemon/session/mappers.ts whitelist carries token_budget across the wire
packages/webui/src/daemon/session/mappers.test.ts wire pass-through test; unknown-kind drop retained

CI test evidence

All PR-CI runs on this commit are settled and green:

Check Conclusion
Test (ubuntu-latest, Node 22.x) ✅ success
Test (macos-latest, Node 22.x) skipped (lane off for PRs)
Test (windows-latest, Node 22.x) skipped (lane off for PRs)
Integration Tests (CLI, No Sandbox) skipped (fork PR)
precheck-pr / precheck ✅ success
Desktop Shell (ubuntu-22.04) ✅ success
Desktop Shell (windows-2022) ✅ success
Live Host (macos-latest) ✅ success
web-shell E2E Smoke (ubuntu-latest, Node 22.x) ✅ success
Capture web-shell visuals (ubuntu-latest, Node 22.x) ✅ success
Dependency CVE audit ✅ success
Secret scan (TruffleHog) ✅ success

The green ubuntu unit run is the decisive one: it executes the 414-test goals suite that pins this change, including the new cursor-retention assertion. One note for anyone remembering the red from the round-7 window: at b8af58bd the Windows lane failed, but the annotations sit entirely in packages this PR never touches (dws-event-stream, SessionMessageHandler, and a node-repl EBUSY rmdir flake) — part of the standing repo-wide Windows failures main has since answered by turning the PR trigger for those lanes off in ci.yml (the very merge this head carries). Pre-existing platform noise, not this PR. Not verified: the integration CLI suite, skipped by design on fork PRs — this change has no CLI-surface behavior it would cover anyway.

Sandboxed verification is already closing the behavioural gap — no new trigger needed: the maintainer-sponsored @qwen-code /verify passed 49/49 at the prior head 51d5aeb7 with A/B proof against the base build, the mutation matrix, and the wire oracle, and a fresh run against this exact head is in flight now. The delta between the two heads is the test pin and a comment, so the prior A/B carries; the in-flight run confirms it on the commit under review.

中文说明

代码审查

自上次审查(51d5aeb7)以来,head 只在测试与注释行上移动:预算停止恢复测试补上了 /verify 报告 Finding #1 要求的 evidenceCursor: { recordId: 'r-100' } 断言,预算常量补上了计量覆盖说明。尽管如此,本轮仍在该 head 上重新核实了机制本身,而不是直接沿用旧结论:

  • 单一通道依然成立。 全部 11 个 queueContinuation 调用点——turn 节奏尾部、verifier_reject 重试、checkpoint 循环、启动失败恢复、permit 释放后的重新排队、resume 后的 dispatch 尾部——都经过预算检查;检查位于重入守卫之后、continuationQueued = true 之前。beginTurn(用户驱动 turn 路径)直接签发 permit,永不经过闸门,预算永不阻塞显式用户动作——声明与实现一致。重启边界同样经由该闸门:崩溃恢复的、消费已越过上限的 ACTIVE Goal 在 bindHostqueueContinuation 时重新落停,不铸造任何 turn。
  • reducer 分支顺序正确。 token_budget 恢复分支先于证据窗口分支执行,预算停止的 Goal 永不触发游标重置;isEvidenceLimited 按值匹配两种证据类。新增的游标保留断言闭合了 /verify 变异矩阵发现的唯一未钉住轴(其 M4 变异体——收窄还原且分支失效、悄悄丢弃停止前证据——现在会挂在该测试上)。
  • 重新武装与持久化语义已核实。 有限 grant 在 Goal 消费耗尽时把上限移到 tokensUsed + grant(resume 与 edit 均适用);未耗尽的上限与无上限 Goal 不受影响;非有限退出以删除字段表达(transitionGoal 处理删除,因 Infinity 无法通过 JSON 日志存活);解析器用 Number.isFinite 校验 tokenBudget,拒绝负数、NaNInfinity;预算出现前的 Goal 恢复后不受限。两处既有 usage_limited 落停点保持折叠进共享 helper——无行为变化。
  • 生产接线。 唯一 createGoalRuntime 调用点(config.ts)不传 grant,因此 3000 万默认值全局武装,recorder 充当 tokenLedger——消费在记录时即按 Goal 归账。SDK 联合与 webui mapper 白名单让 token_budget 跨线传输;未知类型仍被丢弃。

无关键阻塞。唯一遗留小点(非阻塞):uiTelemetry.test.ts 的一行重排仍是 diff 中唯一不服务于预算的改动。

(时序图与上方英文一致;文件表见上。)

CI 测试证据

本提交的所有 PR-CI 运行均已结束且为绿(表格见英文部分)。决定性的绿色运行是 ubuntu 单测:它执行了锁定本改动的 414 个 goals 测试,包括新增的游标保留断言。给记得第 7 轮红条的人一个说明:b8af58bd 上 Windows 通道确实失败过,但注解全部落在本 PR 从未触及的包(dws-event-streamSessionMessageHandler,以及 node-replEBUSY rmdir 抖动)——属于仓库范围的既有 Windows 失败,main 已在 ci.yml 中关闭这些通道的 PR 触发(本 head 正携带该合并)。属既有平台噪音,与本 PR 无关。未验证:集成 CLI 套件——按设计在 fork PR 上跳过,且本改动没有它会覆盖的 CLI 面行为。

沙箱验证已在闭合行为缺口——无需新触发:维护者资助的 @qwen-code /verify 已在上一 head 51d5aeb7 上以 49/49 通过(含对 base 构建的 A/B 证明、变异矩阵、线上传输预言),针对本 head 的新一轮运行正在进行中。两个 head 之间的增量是测试钉住与一条注释,因此先前 A/B 结论延续;进行中的运行将在被审查提交上确认它。

Qwen Code · qwen3.8-max

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

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

Confidence: 4/5 — clean across every stage at the current head; the only residuals are the one-line unrelated reflow and the in-flight /verify run on this exact commit (its predecessor passed 49/49 at the prior head, and the delta since is a test pin and a comment).

Stepping back: this pass had less to decide than it looks. The head moved since the dismissed approval, but the movement is the review feedback being absorbed, not new risk: the follow-up test pins exactly the evidence-cursor axis the sponsored /verify run flagged as unpinned — the one thing that was genuinely open — and the other commit is a comment that documents what the meter bills. Both merges from main just keep the base honest.

Going back to my independent proposal — gate at the single minting point, ceiling-forward re-arm, no meter reset, no retrofit, kind widening down the wire, absent-field opt-out — this PR still matches it point for point, and the re-verification at this head found no drift: the gate sits behind every re-entry guard, the user-turn path bypasses it by construction, the reducer's branch order keeps budget stops out of the evidence cursor reset, and the parser is strict about what a persisted budget may be. Every edit in the diff is in service of the bound except one reflow line that round 7 already recorded and deferred under convergence. If I had to maintain this in six months I'd thank the author — the constant carries its sizing rationale, the helpers carry their failure-mode reasoning, and the tests read like the design doc.

What keeps it at 4 rather than 5: the uiTelemetry.test.ts churn, and the fact that the A/B counterfactual at this exact SHA is still running rather than settled — though with a green suite, a prior 49/49 /verify on the mechanism, and a test-only delta, that is confirmation pending rather than doubt. Approving, pinned to the reviewed commit.

中文说明

置信:4/5 —— 当前 head 上每个阶段都干净;仅剩的两点是一行无关重排,以及针对本提交的 /verify 运行尚在进行(其前身在上一 head 上以 49/49 通过,且此后的增量只是一个测试钉住与一条注释)。

退一步看:本轮要决定的其实比表面更少。自被驳回的批准以来 head 确实移动了,但移动是评审反馈被吸收,而非新增风险:后续测试恰好钉住了受资助 /verify 运行指出的未钉住轴——证据游标保留——那是真正未闭合的一点;另一个提交是说明计量表计费范围的注释。两次 main 合并只是让基线保持真实。

回到我的独立方案——闸门设在唯一铸造点、上限前移式重新武装、计量表不重置、不向旧 Goal 追加、类型沿传输链路扩展、以字段缺省表达退出——本 PR 依然逐点吻合,且本 head 上的重新核实未发现漂移:闸门位于所有重入守卫之后,用户 turn 路径在构造上绕过它,reducer 分支顺序使预算停止永不进入证据游标重置,解析器对持久化预算的取值严格。除第 7 轮已记录并按收敛姿态延后的一行重排外,diff 中每一处改动都服务于该边界。半年后维护这段代码我会感谢作者——常量带着量纲推导,helper 带着失败模式推理,测试读起来像设计文档。

停在 4 而非 5 的原因:uiTelemetry.test.ts 的扰动,以及针对本 SHA 的 A/B 反事实尚在进行而非落定——但套件为绿、机制已有 49/49 的 /verify、增量仅测试,这属于"确认在路上"而非疑虑。批准,锚定在被审查的提交上。

Qwen Code · qwen3.8-max

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

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

✅ Defer withdrawn — the blocking condition no longer holds.

The earlier defer stood because the reviewed commit had no green test run anywhere (main's base was broken at contentGenerator.test.ts, so the pinning suite never executed on CI). Since then the base was fixed and the PR re-based through the takeover loop; the re-run at the new head found a fully green CI, including the ubuntu unit suite that executes the stop-and-re-arm loop. The re-run's Stage 3 comment carries the verdict and the approval is pinned to that commit — see the staged comments above.

中文说明

✅ 推迟已撤回——阻塞条件已不存在。

此前的推迟是因为被审查的提交在任何地方都没有绿色的测试运行(main 基线在 contentGenerator.test.ts 处损坏,锁定套件从未在 CI 执行)。此后基线已修复,PR 经接管循环重新变基;新 head 上的重跑发现 CI 全绿,包括执行停止-重新武装循环的 ubuntu 单测。重跑的 Stage 3 评论载有结论,批准已锚定在该提交上——见上方各阶段评论。

Qwen Code · 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 — no test suite could run: a pre-existing compile error at the merge base (packages/core contentGenerator.test.ts:776, 'countTokens' missing on ContentGenerator) halts the workspace build before the test phase; the break is byte-identical at the merge base and in files this diff does not touch, so it is not filed against the PR, but no suite verified this change locally.

中文说明

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

未审查:build-and-test — no test suite could run: a pre-existing compile error at the merge base (packages/core contentGenerator.test.ts:776, 'countTokens' missing on ContentGenerator) halts the workspace build before the test phase; the break is byte-identical at the merge base and in files this diff does not touch, so it is not filed against the PR, but no suite verified this change locally。

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

Comment thread packages/core/src/goals/goal-reducer.ts
Comment thread packages/core/src/goals/goal-reducer.ts
Comment thread packages/core/src/goals/goal-reducer.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/core/src/goals/goal-protocol.ts
Comment thread packages/core/src/goals/goal-runtime.ts
Comment thread packages/core/src/goals/goal-runtime.ts Outdated
@qqqys
qqqys dismissed qwen-code-ci-bot’s stale review August 24, 2026 11:23

Superseded by subsequent commit bdf0b3e. Current head requires a fresh review.

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

🖼️ web-shell visual preview

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

Screenshots · before / after

⚠️ No preview: one or more scenarios failed to render on this head — see the workflow run. This is not "no visual change" — a scenario that times out or throws produces no image. Fix the failing scenario (or a genuine regression it caught) and the preview returns on the next push.

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

Qwen Code · web-shell visuals

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed. Suggestions are inline.

Not explored to full depth (tool budget reached): "agent 4": none — no check was cut short..

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

  • packages/core/src/goals/goal-protocol.ts:30 — [review] 30M-token budget undocumented in docs/users/features/headless.md (deferred by the code-age rule)
  • packages/core/src/goals/goal-runtime.ts:349 — [review] stopForSpentBudget post-journal staleness re-check has no test (deferred by the code-age rule)
中文说明

已审查。 建议见行内评论。

未探索到全部深度(达到工具调用预算):"agent 4"none — no check was cut short.

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

— 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
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.test.ts
Comment thread packages/core/src/goals/goal-reducer.ts Outdated
@wenshao

wenshao commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

@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 24, 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-dev-bot

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

Copy link
Copy Markdown
Collaborator

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

中文说明

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

@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 轮)。改动内容与我反驳保留之处如下:

Round 3 summary — PR #9891

Commit: 202d1579f2fix(goal): unify spent-budget checks and settle on failed writes (5 files, +198/−90, all inside the PR's existing packages/core/src/goals/ footprint). No base merge performed (--conflict false).

Feedback dispositions

Finding Type Decision
[rc:3844308024] R1-4 — three copies of the budget-spent comparison Suggestion Fixed — new shared predicate isGoalTokenBudgetSpent in goal-protocol.ts (which already owns GOAL_DEFAULT_TOKEN_BUDGET), now used by the runtime's stop gate, the runtime's settle re-check, and the reducer's rearmedTokenBudget. The local isTokenBudgetSpent closure is deleted.
[rc:3844308030] R1-5 — stopForSpentBudget is the third copy of the usage_limited settle Suggestion Fixed — extracted the shared settle core into usageLimitedSnapshot (build), journalUsageLimitedSettle (persist), and commitUsageLimitedSettle (commit + broadcast); all three sites (recordVerificationOutcome, recordCheckpointFailure, stopForSpentBudget) now call them, keeping their distinct re-entry guards and flag resets (nextVerifierFeedback cleared by the verification settle, kept by the checkpoint and budget settles, comment preserved).
[rc:3844308035] R1-8 — settle-write rejection swallowed, Goal stranded "active/idle" with zero signal Suggestion FixedstopForSpentBudget now catches the failed settle write and still applies + broadcasts the in-memory usage_limited snapshot (the finding's minimum fix). Spend fails safe either way; the visible state now matches reality, and the user's next action (e.g. resume) surfaces the persistence loss through the normal GoalPersistenceUnavailableError path. Covered by a new regression test that fails the journal's third write.
[rc:3844308048] R2-1 — four comments scope re-arming to "budget-stopped" Goals, stale since the generic-resume re-arm Suggestion Fixed — all four docs (CreateGoalRuntimeOptions.tokenBudgetGrant, GoalRecord.tokenBudget, GoalControlTransition.tokenBudgetGrant, rearmedTokenBudget) now say "a resume/edit of a Goal whose ceiling is spent", matching the tested behavior.
[rc:3844308075] R2-2 — no test pins the literal 30_000_000 Suggestion Fixed — the default-budget test now also asserts expect(GOAL_DEFAULT_TOKEN_BUDGET).toBe(30_000_000) with a comment explaining why the wiring assertion alone is not enough.
[rc:3844308091] R2-3 — exact-boundary case untested for both predicates Suggestion Fixed — one runtime test (spend exactly equal to the grant → usage_limited at tokensUsed === tokenBudget, no second turn) and one reducer test (resume at exact ceiling re-arms to 2_000).

Round-1 findings re-verified as already fixed by bdf0b3e206 (maintainer replies confirmed, code re-read this round): [rc:3842817415] (web-shell gate now kind-matches + drift guard), [rc:3842817429] (generic resume re-arms spent ceilings, incl. paused/blocked), [rc:3842817434] (non-finite opt-out clears a restored spent ceiling), [rc:3842817458] (persisted round-trip covers a full token_budget record), [rc:3842817463] (default-budget wiring test).

Not requested this round (reviewer's convergence deferrals, recorded only): the headless.md documentation of the 30M default, and a test for stopForSpentBudget's post-journal staleness re-check. Left untouched.

Not actionable locally: [ic:5394773549] (web-shell visual preview failed to render). It is an informational preview comment — no required check is red, the workflow artifacts are not reachable from this runner, and this round's change is core-only runtime logic with no web-shell rendering impact (the PR's web-shell change is a pure predicate). The preview should regenerate on the next push; if it still fails, the workflow run logs are needed to diagnose.

Mutation probes (each new behavior witnessed)

  1. >=> in isGoalTokenBudgetSpent: exactly the two new exact-boundary tests fail (runtime + reducer); restored → green.
  2. In-memory settle fallback removed from stopForSpentBudget's catch: shows the budget stop even when the settle write fails fails; restored → green.
  3. GOAL_DEFAULT_TOKEN_BUDGET rescaled to 3_000_000: the literal pin fails; restored → green.

The settle extraction and predicate unification are behavior-preserving; their witnesses are the pre-existing usage_limited settle tests across the verifier/checkpoint/budget paths, all re-run green.

Verification

  • npm run build — passed
  • npm run typecheck — passed (0 errors)
  • npm run lint — passed (full repo)
  • npx prettier --check on the five changed files — clean (goal-runtime.ts fixed with --write before commit)
  • npx vitest run src/goals/ in packages/core — 16 files, 411/411 passed
  • npx vitest run client/utils/goalGate.test.ts in packages/web-shell (drift guard reads goal-reducer.ts) — 11/11 passed
  • Not run: integration tests (behavior fully exercised by core unit tests, not bundled-CLI-only) and npm run generate:settings-schema (no settings source touched)
中文说明

第 3 轮处理总结 — PR #9891

提交:202d1579f2fix(goal): unify spent-budget checks and settle on failed writes(5 个文件,+198/−90,全部位于本 PR 既有的 packages/core/src/goals/ 足迹内)。未执行 base 合并(--conflict false)。

反馈处理

发现 类型 决定
[rc:3844308024] R1-4 — 「预算已耗尽」比较存在三份副本 Suggestion 已修复 — 在 goal-protocol.ts(已拥有 GOAL_DEFAULT_TOKEN_BUDGET)中新增共享谓词 isGoalTokenBudgetSpent,runtime 的停止闸门、runtime 的落定复查、以及 reducer 的 rearmedTokenBudget 三处统一使用;删除了局部的 isTokenBudgetSpent 闭包。
[rc:3844308030] R1-5 — stopForSpentBudgetusage_limited 落定流程的第三份副本 Suggestion 已修复 — 将共享落定核心提取为 usageLimitedSnapshot(构造)、journalUsageLimitedSettle(持久化)、commitUsageLimitedSettle(提交+广播);三处调用点(recordVerificationOutcomerecordCheckpointFailurestopForSpentBudget)统一调用,各自保留独立的重入守卫与标志位重置(验证落定清除 nextVerifierFeedback,checkpoint 与预算落定保留,原注释保留)。
[rc:3844308035] R1-8 — 落定写入被拒绝时错误被吞掉,Goal 以「active/idle」状态搁浅且毫无信号 Suggestion 已修复stopForSpentBudget 现在捕获失败的落定写入,仍在内存中应用并广播 usage_limited 快照(即该发现给出的最低修复)。两种情况下消费都保持故障安全;可见状态从此与真实状态一致,用户的下一个动作(如恢复)会经由正常的 GoalPersistenceUnavailableError 路径暴露持久化失败。新增回归测试覆盖了日志第三次写入失败的场景。
[rc:3844308048] R2-1 — 四处注释把重新武装限定为「预算已停止的 Goal」,自通用恢复重新武装后已过时 Suggestion 已修复 — 四处文档(CreateGoalRuntimeOptions.tokenBudgetGrantGoalRecord.tokenBudgetGoalControlTransition.tokenBudgetGrantrearmedTokenBudget)统一改为「上限已耗尽的 Goal 的恢复/编辑」,与已测行为一致。
[rc:3844308075] R2-2 — 没有任何测试钉住字面量 30_000_000 Suggestion 已修复 — 默认预算测试新增断言 expect(GOAL_DEFAULT_TOKEN_BUDGET).toBe(30_000_000),并附注释说明为何仅有「接线断言」不够。
[rc:3844308091] R2-3 — 两个谓词的精确边界情形均无测试 Suggestion 已修复 — 新增一个 runtime 用例(消费恰好等于 grant → 在 tokensUsed === tokenBudgetusage_limited、无第二个 turn)和一个 reducer 用例(恰好耗尽时恢复,重新武装到 2_000)。

第一轮发现经复核确认已由 bdf0b3e206 修复(维护者已回复确认,本轮重新阅读了代码):[rc:3842817415](web-shell 门控改为按类型匹配 + 漂移守卫)、[rc:3842817429](通用恢复对已耗尽上限重新武装,含 paused/blocked)、[rc:3842817434](非有限退出模式清除恢复时带回的已耗尽上限)、[rc:3842817458](持久化往返用例覆盖完整 token_budget 记录)、[rc:3842817463](默认预算接线测试)。

本轮不要求处理(审查方收敛姿态下的延后项,仅记录):headless.md 中对 3000 万默认值的文档说明、以及 stopForSpentBudget 日志后陈旧复查的测试。均未改动。

本地无法处理:[ic:5394773549](web-shell 视觉预览渲染失败)。这是一条信息性预览评论 —— 没有任何必需检查变红,本 runner 无法访问该 workflow 的产物,且本轮改动为纯 core 运行时逻辑,不涉及 web-shell 渲染(本 PR 的 web-shell 改动是纯谓词)。预览应会在下次推送时重新生成;若仍失败,需要该 workflow 的运行日志才能诊断。

变异探针(每个新行为都有见证)

  1. isGoalTokenBudgetSpent 中的 >= 改为 >:恰好只有两个新的精确边界测试失败(runtime + reducer);还原后全绿。
  2. 删除 stopForSpentBudget catch 中的内存落定回退:shows the budget stop even when the settle write fails 失败;还原后全绿。
  3. GOAL_DEFAULT_TOKEN_BUDGET 改为 3_000_000:字面量钉住断言失败;还原后全绿。

落定提取与谓词统一是行为保持的重构;其见证是既有的覆盖验证者/checkpoint/预算路径的 usage_limited 落定测试,全部重跑全绿。

验证

  • npm run build — 通过
  • npm run typecheck — 通过(0 错误)
  • npm run lint — 通过(全仓库)
  • 对五个改动文件执行 npx prettier --check — 干净(goal-runtime.ts 在提交前经 --write 修正)
  • packages/core 中执行 npx vitest run src/goals/ — 16 个文件,411/411 通过
  • packages/web-shell 中执行 npx vitest run client/utils/goalGate.test.ts(漂移守卫读取 goal-reducer.ts)— 11/11 通过
  • 未执行:集成测试(行为已由 core 单元测试完整覆盖,并非仅经打包 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-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

No issues found. LGTM! ✅

Not explored to full depth (tool budget reached): "agent 1c": (none — all planned checks completed; ~15 of ~61 tool calls used).

中文说明

未发现问题。LGTM!✅

未探索到全部深度(达到工具调用预算):"agent 1c"(none — all planned checks completed; ~15 of ~61 tool calls used)

— 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 review round — no action taken

Outcome: no change. This round's feedback contained nothing actionable, and the listed check failures are workflow-infrastructure cancellations that this PR's code cannot cause. The branch remains at 202d1579f2 with a clean working tree and no new commit.

What this round triaged

The prepared feedback (feedback.md) listed no reviews, no inline comments, and no issue-level comments newer than the last evaluation (2026-08-24T14:17:55Z). Prior rounds already addressed the full finding history: every one of the 33 inline review comments carries a "Fixed in …" or "Covered now …" reply pointing at round-1 commit bdf0b3e206 or round-2 commit 202d1579f2 (current HEAD), and the automated reviewer's latest submitted review is an APPROVED ("No issues found. LGTM! ✅").

The only entries under "Failed checks" were 13 identical Signal the reviewed fork PR: CANCELLED items. The "Still-red checks" section was empty.

Diagnosis of the cancelled checks (evidence-based)

Signal the reviewed fork PR is the single signal job of .github/workflows/qwen-autofix-fork-signal.yml. Reading that workflow file shows it cannot fail because of this PR's code:

  • It is deliberately powerless — permissions: {}, no checkout, and no repository code executed. Its only step echoes two lines; its purpose is to complete success so the base-context bridge (qwen-autofix-fork-bridge.yml) can dispatch the real review scan where secrets exist.
  • Fork-triggered runs execute the workflow file from the base branch, not the PR head, so a PR cannot alter this gate even in principle.
  • It runs under a per-PR concurrency group with cancel-in-progress: true, which coalesces trusted reviews by design: when a new review is submitted, any queued or in-flight signal run for the same PR is cancelled.

The check data matches that coalescing behavior exactly:

  • All 13 signal runs, plus 13 companion route jobs from the Qwen Autofix workflow, were cancelled inside a ~28-second burst (2026-08-25T02:20:24Z → 02:20:52Z), each job living ~2 seconds before cancellation — a rapid sequence of review submissions cancelling one another's signals.
  • Across all 293 checks recorded for this PR there is not a single FAILURE conclusion (248 SKIPPED / 26 CANCELLED / 19 SUCCESS). Nothing is red because of the code.
  • This address-review round running at all is itself proof the signal → bridge chain delivered the review to the autofix lane despite the cancellations.

Finally, that workflow is the autofix loop's own CI machinery, which address-review rounds are not permitted to modify — but no modification is needed or wanted; the observed behavior is its documented, intended behavior.

Diff growth note

The window report (source 13 / test 95 net lines vs 400/400 budget, 0 prior rounds over budget) is informational only — well within budget, and no Growth audit required section was present. No action taken.

Verification

No code changed this round, so no build/typecheck/lint/test commands were run; there was no commit to verify. Working tree confirmed clean; HEAD unchanged at 202d1579f2. The cancelled checks require no code response and will not reappear as a result of any change here.

中文说明

Autofix 审查轮次 — 未采取任何操作

结论:无改动。 本轮的反馈中没有任何可处理的内容,所列的检查失败属于工作流基础设施层面的取消(cancellation),本 PR 的代码不可能导致这些取消。分支保持在 202d1579f2,工作树干净,没有产生新的提交。

本轮分类处理的内容

预处理后的反馈(feedback.md)中,在上次评估时间(2026-08-24T14:17:55Z)之后没有任何 review、行内评论或 issue 级评论。此前的轮次已经处理了全部发现记录:33 条行内审查评论每一条都有"Fixed in …"或"Covered now …"的回复,分别指向第一轮提交 bdf0b3e206 或第二轮提交 202d1579f2(当前 HEAD),且自动审查器最近一次提交的 review 状态为 APPROVED("No issues found. LGTM! ✅")。

"Failed checks" 下唯一的条目是 13 条相同的 Signal the reviewed fork PR: CANCELLED。"Still-red checks"(持续失败的检查)部分为空。

对被取消检查的诊断(基于证据)

Signal the reviewed fork PR.github/workflows/qwen-autofix-fork-signal.yml 中唯一的 signal 任务。阅读该工作流文件可以确认,它不可能因为本 PR 的代码而失败:

  • 它被刻意设计为无权限——permissions: {}不检出代码、不执行任何仓库代码。它唯一的步骤只输出两行文字;其作用是让运行以 success 完成,从而让 base 上下文的桥接工作流(qwen-autofix-fork-bridge.yml)在拥有 secrets 的环境中派发真正的审查扫描。
  • 由 fork 触发的运行执行的是 base 分支上的工作流文件,而不是 PR head 上的,因此即便在原理上,PR 也无法改动这个关卡。
  • 它运行在按 PR 分组的并发组下,且设置了 cancel-in-progress: true,这是**有意将可信审查合并(coalesce)**的设计:当一个新的 review 被提交时,同一 PR 正在排队或运行中的 signal 运行会被取消。

检查数据与上述合并行为完全吻合:

  • 全部 13 个 signal 运行,以及 Qwen Autofix 工作流中配套的 13 个 route 任务,都在约 28 秒的集中时段内(2026-08-25T02:20:24Z → 02:20:52Z)被取消,每个任务在被取消前只存活约 2 秒——这是一连串快速提交的 review 相互取消了对方 signal 的结果。
  • 在本 PR 记录的全部 293 个检查中,没有一条 FAILURE 结论(248 条 SKIPPED / 26 条 CANCELLED / 19 条 SUCCESS)。没有任何因代码而变红的检查。
  • 本轮 address-review 能够运行起来这件事本身,就证明尽管存在这些取消,signal → bridge 链路仍然成功地把审查送达了 autofix 通道。

最后,该工作流属于 autofix 循环自身的 CI 机制,address-review 轮次本就无权修改——但实际上也完全不需要、不应该修改;观察到的行为正是其文档中记载的、预期内的行为。

关于 diff 增长

窗口报告(源码净增 13 行 / 测试净增 95 行,预算为 400/400,此前 0 轮超出预算)仅为信息性提示——远低于预算,且没有 Growth audit required(需要增长审计)部分。未采取任何操作。

验证

本轮没有改动任何代码,因此未运行 build/typecheck/lint/测试命令;没有需要验证的提交。已确认工作树干净,HEAD 保持为 202d1579f2 未变。被取消的检查不需要任何代码层面的响应,也不会因为此处的任何改动而再次出现。

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


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

@wenshao

wenshao commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /resolve

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

Qwen Code attempted to resolve merge conflicts but the run did not complete successfully.

Check the workflow run for full logs.

@github-actions

Copy link
Copy Markdown
Contributor

Qwen Code review request accepted. Review is queued in workflow run.

@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

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

Round summary — PR #9891 (address-review, conflict: false)

One commit landed this round: b8af58bdff docs(core): spell out the Goal budget meter's coverage (#9891).

Both maintainer verification reports recommend merge and raise no blocking findings. The round implemented the one actionable suggestion, attempted the second, and triaged the rest. No inline review comments and no review bodies existed this round; every point below comes from issue-level comments and is answered here.

Feedback dispositions

[ic:5410964915] Note 1 — the meter bills Goal-turn model calls only; worth one clause in the constant's doc comment → ACT.
Verified in code before editing: tokensUsed is fed exclusively through chatRecordingService.recordAssistantTurn, which accumulates tokens only when the assistant record carries a Goal-turn goalContext stamp (accumulateGoalTurnTokens); per-turn side queries and checkpoint-verifier calls pass no goalContext and are never billed. Added exactly one sentence to the GOAL_DEFAULT_TOKEN_BUDGET doc comment in packages/core/src/goals/goal-protocol.ts: the meter bills Goal-turn model calls only — per-turn side queries and checkpoint-verifier calls are unmetered — so real provider spend at a stop runs above this window.

[ic:5410964915] Note 6 — the uiTelemetry.test.ts reflow is unrelated churn → ATTEMPTED REVERT, THEN DEFERRED (probe evidence below).
The revert to origin/main content was staged, but the repository's own pre-commit hook (lint-staged → prettier --write, see scripts/pre-commit.js and the lint-staged block in the root package.json) deterministically re-collapses the wrapped line on every commit that stages the file, so the hunk dropped out of the commit. Root cause: packages/core/src/telemetry/uiTelemetry.test.ts:1412 sits exactly at printWidth 80, and the wrapped form on origin/main is prettier-dirty under the repo's own config resolution:

  • git checkout origin/main -- <file> then npx prettier --check <file> at the repo path → Code style issues found (exit 1)
  • npx prettier --write <file> → emits the one-line form, byte-identical to this PR's version
  • with the one-line form, npx prettier --check → clean (exit 0)

Removing the hunk from this PR would require committing with --no-verify or editing prettier/editorconfig/CI machinery — all forbidden by the autofix boundary rules. The real fix is a one-line normalization of that line on main (apply prettier --write there); after that lands, this PR's hunk vanishes on the next rebase. Deferred to the follow-up queue.

Correction on the landed commit message: b8af58bdff says "Also revert an unrelated one-line reflow in uiTelemetry.test.ts", but the commit does not contain that revert — the hook stripped it during the commit itself, after the message was drafted. Amending is forbidden by the autofix rules, so the record is corrected here instead: the commit contains the doc-comment sentence only.

[ic:5410964915] Note 4 — edit of a spent Goal leaves status: usage_limited with limitKind/lastReason cleared (bare "Usage limited" chip, no "Last check" line until resume) → DEFER.
Verified real in code and verified pre-existing: the edit branch of reduceGoalControl clears limitKind/lastReason without touching status, identically on origin/main. This PR's re-arm makes the path more reachable but does not change those semantics. Changing edit semantics is a behavior decision outside this PR's footprint → deferred to the follow-up queue.

[ic:5410348736] — Web Shell transcript card labels every usage_limited stop "Goal aborted" while the strip says "Usage limited" → DEFER.
Verified real and pre-existing: GoalStatusMessage.tsx maps the aborted kind to the goal.aborted i18n key for every usage_limited stop regardless of limitKind; this PR touches neither the mapping nor the i18n keys. Fix lives outside this PR's footprint → deferred to the follow-up queue.

[ic:5410348736] / [ic:5410964915] — overshoot is one turn wide (the crossing turn completes first) → DECLINE.
By design: the gate checks isGoalTokenBudgetSpent in queueContinuation before minting the next continuation, and billing happens in finishTurn, so the turn that crosses the ceiling completes first. Overshoot is bounded by one turn; both verifications confirm this matches the design intent. No change requested.

[ic:5410348736] — the PR description's "Risk" paragraph about Web Shell withholding Resume until #9840 is stale → DECLINE (not actionable in code).
The description text is PR metadata, not code in this checkout, and #9840 is already merged into this branch. Updating the PR body is a GitHub write owned by the workflow/maintainer, not by this round.

[ic:5410348736] — headless budget stop ends with result: success (exit 0), visible only via goal_state events → DECLINE.
Consistent with every other usage_limited route today, as the verification itself notes. Not a defect introduced here.

[ic:5410964915] Note 3 — pre-budget Goals stay unbounded forever (no retrofit on resume/edit) → DECLINE.
Documented, intended behavior (the tokenBudget field comment spells it out) and verified by the maintainer's scenario 10. No change requested.

[ic:5410964915] Note 5 — the mapper widening has no reader yet → DECLINE.
The verification explicitly accepts it as forward-looking plumbing, and the new wire test pins it. No change requested.

[ic:5411506533] (qwen-code-ci-bot) — review pipeline did not complete; listed checks CANCELLED → NO ACTION.
All listed checks are CANCELLED (a transient pipeline abort), not red failures; the feedback's "Still-red checks" section is empty and a fresh Qwen Code CI run was in progress at round time. There is no code finding to address; retrying the review is the workflow's/maintainer's lever (@qwen-code /review).

Mutation probes

Not applicable this round: the landed commit adds no guard, branch, or behavior — one doc-comment sentence (the pre-commit hook's prettier pass confirmed it format-clean by leaving it untouched). The attempted test revert was itself probe-verified (prettier evidence above).

Conflict

None — --conflict false; origin/main was not merged.

Verification

  • npm run build — passed (exit 0)
  • npm run typecheck — passed (exit 0)
  • npm run lint — passed (exit 0; run with the doc-comment change in the tree)
  • cd packages/core && npx vitest run src/telemetry/uiTelemetry.test.ts src/goals/ — 17 files, 488 tests passed (includes the restored-then-hook-reverted uiTelemetry file and all Goal suites that consume the commented constant)
  • cd packages/core && npx vitest run src/telemetry/uiTelemetry.test.ts — final-state re-run on the landed tree, 53 tests passed
  • npm run generate:settings-schema — not required (no settings source changed)
  • Integration tests after npm run bundle — not required (the round changes only a doc comment; no bundled-CLI-only behavior is affected)
中文说明

本轮摘要 — PR #9891(address-review,无冲突)

本轮落地一个提交:b8af58bdff docs(core): spell out the Goal budget meter's coverage (#9891)

两位维护者的验证报告均建议合入,且没有阻塞性发现。本轮实现了一条可执行建议,尝试了第二条(未能落地,见下),其余均已分类处理。本轮没有行内评审意见、也没有评审主体(review body),以下所有条目均来自 issue 级评论,并在此逐一答复。

反馈处置

[ic:5410964915] 备注 1 —— 计量表只统计 Goal turn 的模型调用;值得在常量文档注释里补一句 → 已处理。
编辑前已在代码中核实:tokensUsed 完全经由 chatRecordingService.recordAssistantTurn 入账,且仅当 assistant 记录带有 Goal turn 的 goalContext 标记时才累计(accumulateGoalTurnTokens);按 turn 的 side query 与 checkpoint-verifier 调用不携带 goalContext,从不计费。在 packages/core/src/goals/goal-protocol.tsGOAL_DEFAULT_TOKEN_BUDGET 文档注释中恰好补了一句:计量表只对 Goal turn 的模型调用计费——按 turn 的 side query 与 checkpoint-verifier 调用不在计量范围内——因此触顶停止时的真实供应商消耗会高于该窗口。

[ic:5410964915] 备注 6 —— uiTelemetry.test.ts 的重排是无关扰动 → 已尝试还原,随后改为延期(检验证据见下)。
还原到 origin/main 内容的修改已入暂存区,但仓库自身的 pre-commit 钩子(lint-staged → prettier --write,见 scripts/pre-commit.js 与根 package.jsonlint-staged 配置)会在每次暂存该文件的提交里确定性地把折行重新并为一行,导致该文件的改动从提交中消失。根因:packages/core/src/telemetry/uiTelemetry.test.ts:1412 恰好处于 printWidth 80 的边界,而 origin/main 上的折行形式在仓库自身的配置解析下是 prettier 不合规的:

  • git checkout origin/main -- <file> 后在仓库路径执行 npx prettier --check <file> → 报告 Code style issues found(exit 1)
  • npx prettier --write <file> → 输出单行形式,与本 PR 的版本逐字节一致
  • 对单行形式执行 npx prettier --check → 干净(exit 0)

要在本 PR 中去掉这个 hunk,只能 --no-verify 提交或修改 prettier/editorconfig/CI 机制——这些都被 autofix 的边界规则禁止。真正的修复是在 main 上对该行做一次单行规范化(在那边执行 prettier --write);落地后,本 PR 的这个 hunk 会在下次 rebase 时自动消失。已延期到后续跟进队列。

对已落地提交信息的更正: b8af58bdff 的信息写着「同时还原 uiTelemetry.test.ts 中被标记为扰动的单行重排」,但该提交并不包含这一还原——钩子在提交过程中(信息起草之后)将其剥掉了。autofix 规则禁止 amend,因此在此更正记录:该提交只包含文档注释的那一句。

[ic:5410964915] 备注 4 —— 对耗尽的 Goal 执行 edit 会保留 status: usage_limited 且清空 limitKind/lastReason(只剩一个 "Usage limited" 标签,resume 之前没有「Last check」行)→ 延期。
已在代码中核实为真实且为既有行为:reduceGoalControledit 分支清空 limitKind/lastReason 而不改动 status,在 origin/main 上完全相同。本 PR 的重新武装让这条路径更容易触达,但没有改变这些语义。修改 edit 语义是超出本 PR 范围的行为决策 → 延期到后续跟进队列。

[ic:5410348736] —— Web Shell 转录卡片把所有 usage_limited 停止标成 "Goal aborted",而状态条写 "Usage limited" → 延期。
已核实为真实且为既有行为:GoalStatusMessage.tsx 对所有 usage_limited 停止(无论 limitKind)都把 aborted 种类映射到 goal.aborted 这个 i18n 键;本 PR 既没有碰该映射,也没有碰 i18n 键。修复在本 PR 范围之外 → 延期到后续跟进队列。

[ic:5410348736] / [ic:5410964915] —— 超调恰为一个 turn(越线的那一轮先跑完)→ 不采纳。
设计如此:闸门在 queueContinuation 中铸造下一次续跑之前检查 isGoalTokenBudgetSpent,而计费发生在 finishTurn,因此越过上限的那一轮会先完成。超调被限定在一个 turn 内;两份验证都确认这与设计意图一致。未要求改动。

[ic:5410348736] —— PR 描述「风险」一段关于 Web Shell 在 #9840 合入前隐藏 Resume 的说法已过时 → 不采纳(代码层面无法处理)。
描述文字是 PR 元数据,不是本检出中的代码,且 #9840 已合入本分支。更新 PR 正文属于工作流/维护者负责的 GitHub 写操作,不属于本轮职责。

[ic:5410348736] —— headless 模式下预算停止以 result: success(exit 0)结束,只通过 goal_state 事件可见 → 不采纳。
与现有所有其它 usage_limited 路径一致,验证报告本身也这么写。并非本处引入的缺陷。

[ic:5410964915] 备注 3 —— 预算出现之前的 Goal 永远不受限(resume/edit 都不追加上限)→ 不采纳。
这是已声明的预期行为(tokenBudget 字段注释中写明),且维护者的场景 10 已验证。未要求改动。

[ic:5410964915] 备注 5 —— mapper 的放宽目前还没有读者 → 不采纳。
验证报告明确接受其作为前瞻性管道,且新增的线上测试已锁定。未要求改动。

[ic:5411506533](qwen-code-ci-bot)—— 评审流水线未完成;所列检查均为 CANCELLED → 无需处理。
所列检查全部为 CANCELLED(流水线瞬时中止),并非红色失败;反馈中的「Still-red checks」部分为空,且轮次进行时一轮新的 Qwen Code CI 正在跑。没有需要处理的代码发现;重试评审是工作流/维护者的操作(@qwen-code /review)。

变异检验

本轮不适用:落地的提交没有新增任何守卫、分支或行为——只有一句文档注释(pre-commit 钩子的 prettier 检查未改动它,确认其格式干净)。尝试的测试还原本身已经过检验(见上方 prettier 证据)。

冲突

无 —— --conflict false;未合并 origin/main

验证

  • npm run build —— 通过(exit 0)
  • npm run typecheck —— 通过(exit 0)
  • npm run lint —— 通过(exit 0;在包含文档注释改动的状态下运行)
  • cd packages/core && npx vitest run src/telemetry/uiTelemetry.test.ts src/goals/ —— 17 个文件,488 个测试通过(包含先还原后被钩子撤销的 uiTelemetry 文件,以及所有使用该常量的 Goal 套件)
  • cd packages/core && npx vitest run src/telemetry/uiTelemetry.test.ts —— 在最终落地状态复跑,53 个测试通过
  • npm run generate:settings-schema —— 不需要(未改动 settings 源)
  • npm run bundle 后的集成测试 —— 不需要(本轮只改了一句文档注释,不涉及仅经打包 CLI 暴露的行为)

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

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


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

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

⚠️ Downgraded from Approve to Comment: CI failing: Test (windows-latest, Node 22.x). Reviewed.

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

  • packages/core/src/telemetry/uiTelemetry.test.ts:1412 — [review] uiTelemetry.test.ts reflow churn still in diff despite HEAD commit message claiming a revert
中文说明

⚠️ 已从批准降级为评论:CI failing: Test (windows-latest, Node 22.x)。 已审查。

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

— 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: · 已审阅最新反馈——无需改动。逐点说明原因如下:

No code changes this round — the red Windows lane is a pre-existing failure on main, not caused by this PR.

Feedback triage

1. Review [rv:5022705540] — "Downgraded from Approve to Comment: CI failing: Test (windows-latest, Node 22.x)" → investigated, deferred to the follow-up queue (deferred-findings.json).

Evidence that the failure is not this PR's:

  • The failing check (check run 97905573087) failed on four tests, none of which this PR touches:
    • packages/vscode-ide-companion/src/webview/handlers/SessionMessageHandler.test.ts — 2 tests (lines 946, 1243)
    • packages/node-repl/src/node-repl.semantics.test.ts — 1 test (line 374)
    • packages/channels/dws/src/dws-event-stream.test.ts — 1 test (line 78)
  • The same four failures reproduce on unrelated PRs against the same base — check runs 97906137460 (feat/provider-model-discovery-v2), 97905917420 (feat/workflow-generated-dir), 97911604501 (feat/session-pr-gh-create). The failures live in the base branch, not in this diff.
  • Context: the Windows test lane was dark since 2026-07-02 and was only revived today (fix(ci): give the macOS and Windows lanes a trigger again #9370); fix: repair the Windows and macOS test lane failures #9728 repaired 72 platform-assumption tests on the revived lane. These four are the remainder.
  • Root causes (all Windows-specific):
    • SessionMessageHandler: the assertions hard-code POSIX file URLs (file:///workspace/export.html); on the Windows runner the same absolute path resolves against the current drive, yielding file:///D:/workspace/.... The neighboring test at line 587 already builds its expected URL with pathToFileURL for exactly this reason.
    • node-repl: the test's finally block runs fs.rmSync on the kernel's cwd before the afterEach disposes the manager; on Windows the still-running kernel process holds the directory (EBUSY).
    • dws-event-stream: the stale stderr error is not superseded by the terminal DWS event consumer stopped (0). error because stdout/stderr interleaving differs on Windows.

Disposition: deferred to the follow-up queue rather than fixed here, because all three files are outside this PR's footprint; a dedicated Windows-lane repair PR is the right vehicle.

2. Deferred under convergence posture (round 7): uiTelemetry.test.ts reflow churn → not modified this round (recorded, not requested). One factual note for whoever picks it up: both forms are stable under prettier 3.6.1 — the one-line form is exactly 80 columns (printWidth) and prettier --check accepts both — so this is cosmetic churn, not a format-check violation.

3. Issue-level comment [ic:5414320648] → stale fallback notice from an earlier review-pipeline run that failed before posting; it is superseded by the completed review in item 1. Nothing to address in code.

Verification

  • npx vitest run src/goals/goal-reducer.test.ts src/goals/goal-runtime.test.ts src/goals/goal-tools.test.ts src/telemetry/uiTelemetry.test.ts (packages/core) — 4 files, 303 tests passed
  • npx vitest run src/daemon/session/mappers.test.ts (packages/webui) — 38 tests passed
  • npx vitest run client/utils/goalGate.test.ts (packages/web-shell) — 4 tests passed
  • CI at this PR's head (b8af58bdff): Test (ubuntu-latest, Node 22.x) and Test (macos-latest, Node 22.x) are green; only the Windows lane is red, with the pre-existing failures above. The Windows lane cannot run on this Linux runner; the workflow's independent CI remains the final gate.
  • No code changes this round, so build/typecheck/lint are unchanged from the verified head.
中文说明

本轮不做任何代码修改——红灯的 Windows 测试通道是 main 上的既有失败,并非本 PR 引起。

反馈分类处理

1. 评审 [rv:5022705540] —— "已从批准降级为评论:CI 失败:Test (windows-latest, Node 22.x)" → 已调查,延后到后续跟进队列(deferred-findings.json)。

证明该失败与本 PR 无关的证据:

  • 失败的检查(check run 97905573087)挂在 4 个测试上,且这 4 个测试均不属于本 PR 的改动范围:
    • packages/vscode-ide-companion/src/webview/handlers/SessionMessageHandler.test.ts —— 2 个测试(第 946、1243 行)
    • packages/node-repl/src/node-repl.semantics.test.ts —— 1 个测试(第 374 行)
    • packages/channels/dws/src/dws-event-stream.test.ts —— 1 个测试(第 78 行)
  • 同样的 4 个失败在与本 PR 无关的其他 PR 上、基于同一 main 基线同样复现 —— check run 97906137460(feat/provider-model-discovery-v2)、97905917420(feat/workflow-generated-dir)、97911604501(feat/session-pr-gh-create)。失败位于基线分支,而不在本 PR 的 diff 中。
  • 背景:Windows 测试通道自 2026-07-02 起长期停摆,今天才由 fix(ci): give the macOS and Windows lanes a trigger again #9370 重新启用;fix: repair the Windows and macOS test lane failures #9728 修复了重启后暴露的 72 个平台假设类测试。这 4 个是剩余的失败。
  • 根因(均为 Windows 特有):
    • SessionMessageHandler:断言硬编码了 POSIX 形式的文件 URL(file:///workspace/export.html);在 Windows 运行器上,同一绝对路径会基于当前盘符解析,得到 file:///D:/workspace/...。相邻的第 587 行测试正是因为这个原因已经改用 pathToFileURL 构造期望值。
    • node-repl:测试的 finally 块在 afterEach 释放管理器之前就对其内核工作目录执行 fs.rmSync;在 Windows 上仍在运行的内核进程占用该目录,导致 EBUSY
    • dws-event-stream:由于 Windows 上 stdout/stderr 的交错顺序不同,过期的 stderr 错误没有被终止态的 DWS event consumer stopped (0). 错误覆盖。

处理方式:延后到跟进队列而不在本 PR 中修复,因为这三个文件都在本 PR 的改动范围之外;应由专门的 Windows 通道修复 PR 来处理。

2. 收敛姿态下延后(第 7 轮):uiTelemetry.test.ts 的换行重排(reflow) → 本轮不修改(仅记录,本轮不要求处理)。为后续处理者补充一个事实:两种写法在 prettier 3.6.1 下都是稳定的——单行写法恰好 80 列(等于 printWidth),prettier --check 对两种写法都通过——因此这只是格式观感上的多余改动,并不违反格式检查。

3. Issue 级评论 [ic:5414320648] → 是早前一次评审流水线在发布前失败时留下的兜底提示;第 1 条中已完成的评审已经取代了它。代码层面无需处理。

验证

  • npx vitest run src/goals/goal-reducer.test.ts src/goals/goal-runtime.test.ts src/goals/goal-tools.test.ts src/telemetry/uiTelemetry.test.ts(packages/core)—— 4 个文件、303 个测试全部通过
  • npx vitest run src/daemon/session/mappers.test.ts(packages/webui)—— 38 个测试通过
  • npx vitest run client/utils/goalGate.test.ts(packages/web-shell)—— 4 个测试通过
  • 本 PR 头部提交(b8af58bdff)的 CI:Test (ubuntu-latest, Node 22.x)Test (macos-latest, Node 22.x) 均为绿色;仅 Windows 通道为红灯,且失败均为上述既有问题。本 Linux 运行器无法执行 Windows 通道;工作流的独立 CI 仍是最终验证关口。
  • 本轮没有代码改动,build/typecheck/lint 相对已验证的头部提交没有变化。

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.

中文说明

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

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

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

@qqqys

qqqys commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator Author

@qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 26, 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: 76 passed · 0 failed · 76 total

Flakiness gate: ✅ 6 changed test file(s) x 5 identical rounds, no divergence

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

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

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

抖动门:✅ 6 changed test file(s) x 5 identical rounds, no divergence

Verification report

PR 9891 Deep Verification (follow-up round) — feat(goal): stop autonomous continuation at a token budget the user re-arms

Verdict: merge-ready — 76/76 scripted assertions passed (fail: 0). Central claim re-proven load-bearing by A/B against the new base. Verified head: 17fc1b86f52d33389ec565e7d6899fa1e4207ded (merge commit e4d88f9785, base tip 0756be0ce7). The previous round's single finding is fixed by this round's delta and re-measured below; one nit-level finding on a commit-message/status mismatch.

中文摘要
  • 结论merge-ready。76/76 脚本化断言通过,0 失败。
  • 上一轮结论状态:上一轮唯一的 finding(预算停止后 resume 保留证据窗口无任何测试钉住)已被本轮增量提交修复——test(core): pin evidence window retention 加入的断言在变异矩阵中恰好杀死上一轮存活的组合变异 M4(失败信息正是光标 r-100 vs r-200 不符),正向对照 M6 证明该钉扎本身是活的。见「上一轮 finding 状态表」与「变异矩阵」。
  • A/B 结论:中心声明在新基线上再次被证实承重(见 A/B 表与 01-…02-… 截图)。注意本轮 base 已前进,main 在此期间大幅改动了 goals 子系统(树级 diff +1210/−113,含两次冲突合并),因此本轮是在演化后的子系统之上重新验证合并结果,而非沿用上一轮数据。
  • 变异矩阵:8 行(含两行正向对照)全部符合编码预期,15/15 裁决通过(03-…06-… 截图)。删除闸门挂 3 个预算停止测试;废掉重新武装助手挂 8 个;isEvidenceLimited 收窄单独还原仍无人观察(冗余防御,与上轮一致)。
  • findings:仅 1 条 nit:docs(core) 提交声称还原了 uiTelemetry.test.ts 的无关单行 reflow,但最终合并 diff 中 reflow 仍在;实测 head 一侧是 prettier 合规形态、base 一侧反而不合规,故残留差异是格式化归一无害变更,只是提交信息与合并结果不一致。
  • 未覆盖:逐提交归因(浅克隆,快照 12 个提交仅 3 个可达;两个 base 顶点之间的树级 diff 可用,已用于确认 main 侧变化);真实模型流量下的 30M 默认规模(沙箱内以缩放 grant 验证机制);用户驱动 turn 的活体 session 验证(仅结构验证);浏览器端渲染;SDK 测试套件(以 typecheck 闸门代替);抖动门由工作流在我的时钟之外执行。

Previous-finding status (follow-up round)

Previous round verified head 51d5aeb7f3 (merge 251f7b159b, base ef18a73885), verdict merge-ready, 49/49. Delta since: 353da2ee (retention pin), b8af58bd (budget-meter coverage doc + claimed uiTelemetry revert), four main merges (two with conflicts in goal-reducer.ts, goal-runtime.ts, goal-runtime.test.ts, goal-tools.ts).

# finding severity status at the new head
1 Budget-stop resume preserving the evidence window was pinned by nothing (matrix row M4 survived; resume could flow through the evidence branch and reset evidenceCursor) Suggestion (coverage) fixed — commit 353da2ee added evidenceCursor: { recordId: 'r-100' } to re-arms a budget-stopped Goal on resume… (the exact candidate fixture the previous report proposed, against control cursor r-200). Re-measured, not inferred: the M4 combination mutant (narrowing reverted AND branch dead) is now killed by exactly that one test, failing on "recordId": "r-100" expected vs "r-200" received — the behavioural mismatch the test exists to catch; mutant M6 (cursor reset smuggled into the budget branch, branch otherwise intact) is also killed by exactly the same test, proving the pin is live on its own axis rather than pinned by an earlier branch.

The previous round's four corrections were also re-measured at the new head (they concern the description, which is unchanged) — see Corrections below.

Central claim and A/B proof

Claim under test: when tokensUsed reaches the armed tokenBudget, the runtime refuses to mint an autonomous continuation at queueContinuation and settles the Goal as usage_limited with limitKind: 'token_budget'; an explicit resume re-arms the ceiling to tokensUsed + grant without resetting the meter.

Harness: ab-budget-gate.mjs drives the real built createGoalRuntime (compiled dist/) with fake journal/host/ledger seams only. Identical scenario on both arms — including passing tokenBudgetGrant: 1_000 to the base build, which ignores it, keeping the scenario byte-identical. Captures: 01-ab-budget-gate-head-20-20.png, 02-ab-budget-gate-base-8-8.png.

cell environment observable oracle result
head (17fc1b86 dist, CI build) grant 1000; spends 1500, then 1000, then 999 settles usage_limited + limitKind: 'token_budget' at 1500 and exactly 1000; meter 1500 never reset; journal create, turn_finished, usage_limited; 1 turn minted; resume → active, ceiling 2500, meter still 1500, turn 2 minted; 999-spend stays active and mints the continuation (gate does not fire below the ceiling) 20/20 PASS
base (0756be0c dist, rebuilt in scratch worktree) same scenario stays active; no limitKind; 2 turns minted (continuation minted past the same spend); journal has no usage_limited; created Goal carries no tokenBudget 8/8 PASS

The flip is complete: base mints the continuation, head settles instead — re-established on the post-merge base, where main had independently evolved the goals subsystem (+1210/−113 lines across 12 goal files between the two base tips, measured by tree-diff of ef18a73885 vs 0756be0ce7).

Control purity: the PR touches no package.json/package-lock.json, so reusing the root node_modules for the base build is a clean control; the goals module graph has zero cross-package imports (verified by grep at the new head), and the harness prints the realpath of the loaded module plus a token_budget marker census (hits=2 in head dist, hits=0 in base dist), so no workspace symlink can silently load head code into the base cell. One environment quirk measured and bounded: a cold tsc --build in a scratch worktree initially reported 63 errors because per-package nested deps (ignore@7.0.5, ajv) live in packages/core/node_modules, which worktrees lack; after linking the lockfile-determined nested tree, both arms compile with exactly one identical error outside the PR surface (see Corrections #3) — scripted parity check in logs/build-parity.txt.

Production wiring re-traced (static, new head): the single production call site (config.ts:8135) passes no tokenBudgetGrant, so the 30M default arms; its tokenLedger is the recorder, whose takeGoalTurnTokens (chatRecordingService.ts:2015) consumes totalTokenCount accumulated only for records carrying a Goal permit (accumulateGoalTurnTokens at :2055, gated on data.goalContext) — so Goal-turn model calls are metered while per-turn side queries and verifier calls are not, matching the new doc comment's coverage claim. The gate remains the single mint point after the main merges: flushContinuation is called only from queueContinuation (the start-failure path promotes a queued user turn or re-enters queueContinuation); all five beginTurn call sites (client.ts ×2, ACP Session.ts ×2, nonInteractiveCli.ts) are user-turn paths and carry no gate.

Corrections (description vs measured — no code change requested)

  1. "404 tests, 16 files" → measured 435 tests, 16 files at the merge head (previous round: 414; main-merge tests and this round's retention pin added the rest). Gate green either way.
  2. Mutation probes "deleting the gate fails exactly the budget-stop test (110 others green)" / re-arm "fails exactly three tests (187 others green)" → at the current suite: gate deletion fails 3 (432 green); re-arm neutralized fails 8 (427 green). Same direction as the previous round's correction; the numbers moved with the suite.
  3. "packages/core carries 4 pre-existing errors on the merge base" → cold builds of both arms in this container emit exactly one identical error, src/services/shellExecutionService.ts(13,27): TS7016 (@lydell/node-pty types behind its exports map), zero errors in any PR-touched file (scripted parity check). The previous round measured 0/0; the delta is container npm-layout, not the PR — the load-bearing fact (base/head parity, PR files clean) holds in both rounds.
  4. Risk note "Web Shell currently ships a limitKind-based Resume gate … until fix(goal): resume an evidence-limited Goal from a fresh window #9840 merges" → still moot at the new base: canResumeGoal decides by status alone (goalGate.ts:50), with the base test "decides by status alone, never by stop metadata" present at HEAD^1. The PR's added goalGate assertion (usage_limited + token_budget resumable) passed 4/4.

Findings

1. (Nit) Claimed uiTelemetry revert is not in the final merge result

Commit b8af58bd says it "revert[s] an unrelated one-line reflow in uiTelemetry.test.ts flagged as churn in review" — but the aggregate diff HEAD^1..HEAD still carries exactly that reflow (the …totalRequests).toBe(2) join, packages/core/src/telemetry/uiTelemetry.test.ts:1412). Measured characterization: the joined line is exactly 80 columns, prettier --check passes on the head form, and flags the base worktree's multi-line form as unformatted (prettier itself joins it when piped the base file) — the earlier false-clean reading was .prettierignore skipping a path passed from the repo root. So the residual one-line diff is prettier normalizing a pre-existing base violation: harmless, and the revert, had it survived, would have left the tree failing the repo's own format gate. The mismatch is between the commit message and the merged state; no code change needed — if anything, the message is the thing to amend. The suite itself is green (53/53).

Mutation matrix (head worktree @​ e4d88f9785, packages/core src/goals/, 435 tests)

Reproducible: run-mutations.sh + logs/mutation-*.txt. Capture: 03-mutation-matrix-435.png; scripted adjudication of every row expectation: 06-matrix-adjudication-15-15.png (15/15). Positive controls land in both mutated files (PC and M2/M5/M6 caught in goal-reducer.ts; M1 caught in goal-runtime.ts), so survivors are not dead-harness artifacts.

row mutation result classification
control unmutated HEAD 435 green baseline
M1 delete the queueContinuation budget gate 3 red / 432 green (exactly the three budget-stop runtime tests) load-bearing
M2 rearmedTokenBudget() always returns {} 8 red / 427 green (all re-arm/opt-out transitions + runtime stop-and-re-arm) load-bearing
M3 isEvidenceLimited reverted to presence-matching survived, 435 green redundant defence (the earlier token_budget branch closes the same hazard — unchanged from the previous round)
M4 M3 + token_budget resume branch dead 1 red / 434 green — the retention pin, failing on r-100 vs r-200 killed this round (previous round: survived = coverage gap; closed by 353da2ee)
M5 branch dead alone, narrowing kept 2 red / 433 green (resume tests' lastReason/limitKind assertions) branch individually load-bearing
M6 budget branch made to reset evidenceCursor (branch otherwise intact) 1 red / 434 green — the same retention pin positive control: the new pin catches its own axis
PC createGoal never stamps the grant 5 red / 430 green (stamping tests, default-arm test, all stop tests) positive control for the reducer file

Wire oracle and restart boundary

Wire oracle (wire-limit-kind.mts, run under tsx; producer = real head runtime settling a spent budget, wire = real JSON.stringify → JSON.parse, consumer = webui's real updateConnectionFromDaemonEvent via update._meta.goalState). Capture: 04-wire-oracle-limit-kind.png. 11/11 PASS: token_budget + status + lastReason survive producer → wire → mapper; both pre-existing kinds still cross; an unknown kind is dropped while the Goal still maps; Infinity demonstrably becomes null through JSON — why opt-out persists as an absent field. Live-gate control: deleting token_budget from the mapper whitelist turns exactly the new webui test red (1 failed / 37 passing).

Restart boundary (restart-boundary.mjs, head). Capture: 05-restart-boundary.png. 12/12 PASS: a journal holding an ACTIVE Goal whose spend already crossed the ceiling (the daemon-crash shape) re-settles to usage_limited/token_budget on restore() + bindHost() with zero turns minted and the settle journaled; a persisted budget stop stays stopped with zero turns and no redundant re-settle write; the persistence parser round-trips a budget stop deep-equal, rejects a negative budget, and restores a pre-budget Goal unbounded (no retrofitted default).

Targeted gates

gate result
packages/core src/goals/ suite (HEAD worktree + main tree, clean) 435/435 passed (16 files)
packages/core src/telemetry/uiTelemetry.test.ts (the one other touched file) 53/53 passed
packages/webui src/daemon/session/mappers.test.ts 38/38 passed (matches PR claim)
packages/web-shell client/utils/goalGate.test.ts 4/4 passed
tsc --noEmit webui 0 errors (PR claim held)
npm run typecheck sdk-typescript exit 0 (PR claim held)
prettier --check on the 12 changed files clean, live-gate proven (a planted violation in a scratch file was flagged, then removed — previous round's open item)
Cold-build parity core @​ base vs @​ head identical single error (shellExecutionService.ts:13), zero in PR files (logs/build-parity.txt)

Not covered

  • Per-commit attribution. Shallow depth-2 checkout (git rev-parse --is-shallow-repository = true); git rev-list HEAD^1..HEAD^2 returns 1 at the boundary while the snapshot lists 12 commits → intermediate commits (353da2ee, b8af58bd, the four merges) unreachable. Their effects were verified in the aggregate state (the retention pin exists and kills M4; the doc comment matches the ledger code; the claimed uiTelemetry revert does not appear — Findings pre-release: fix ci #1), not via isolated per-commit diffs. The old-base→new-base comparison was possible as a tree-diff between the two base tip objects (ef18a73885 vs 0756be0ce7) even with grafted history.
  • Real model traffic. Journal/host/ledger are seams around real runtime code; the 30M default's sizing (vs the 8.6M runaway) is the documented rationale, not measured. Mechanism verified with scaled grants, including the exact-ceiling and below-ceiling gradient.
  • The doc comment's unmetered-path claim was verified by tracing the permit gate (accumulateGoalTurnTokens fires only on records with goalContext), not by measuring side-query traffic.
  • User-driven turns on a live session — verified structurally (all five beginTurn call sites are user-turn paths; the gate sits only in queueContinuation).
  • Browser rendering of the stop — wire mapping proven at the function level; no webui render pass.
  • sdk-typescript test suite — the SDK diff is type-level (union widening + doc); the typecheck gate substitutes.
  • Flakiness gate — the workflow runs the changed-test-file repeat rounds outside the agent clock; this round's changed test files are the same six as last round.

Methodology

Environment: CI merge-ref checkout (HEAD = merge e4d88f9785, HEAD^1 = base tip 0756be0ce7, HEAD^2 = verified PR head 17fc1b86f5), node_modules preinstalled by the workflow. A/B: git worktree add tmp/base-tree HEAD^1, symlinked the lockfile-determined per-package node_modules (nested ignore@7.0.5/ajv copies the worktree would otherwise miss), rebuilt only packages/core with the repo's TypeScript, then drove both dists with ab-budget-gate.mjs (realpath + marker-census witnesses printed per arm). Mutation matrix: fresh HEAD worktree (tmp/mutant-tree), single-point source mutants applied/reverted per row with occurrence-count guards, npx vitest run src/goals/ per row (~7 s each), expectations adjudicated by adjudicate-matrix.mjs. Wire harness: npx tsx against real core dist + real webui source. Raw logs in logs/ (A/B arms, per-row matrix output, gate outputs, typechecks, build logs, parity check); harnesses live in this directory for rerun. All counts in assertions.json map to executed scripted checks; expected-failure cells (base arm, mutant kills) are encoded as passing assertions. Both scratch worktrees removed after capture; the main tree was never modified (verified clean with git status).

Flakiness gate log

rounds=5 files=6 skipped=0
file packages/core/src/goals/goal-reducer.test.ts: (cd packages/core) npx --no-install vitest run ./src/goals/goal-reducer.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
file packages/core/src/goals/goal-tools.test.ts: (cd packages/core) npx --no-install vitest run ./src/goals/goal-tools.test.ts
file packages/core/src/telemetry/uiTelemetry.test.ts: (cd packages/core) npx --no-install vitest run ./src/telemetry/uiTelemetry.test.ts
file packages/web-shell/client/utils/goalGate.test.ts: (cd packages/web-shell) npx --no-install vitest run ./client/utils/goalGate.test.ts
file packages/webui/src/daemon/session/mappers.test.ts: (cd packages/webui) npx --no-install vitest run ./src/daemon/session/mappers.test.ts


per-file results (P=pass F=fail I=infra-exit, one letter per run):
  packages/core/src/goals/goal-reducer.test.ts: PPPPP
  packages/core/src/goals/goal-runtime.test.ts: PPPPP
  packages/core/src/goals/goal-tools.test.ts: PPPPP
  packages/core/src/telemetry/uiTelemetry.test.ts: PPPPP
  packages/web-shell/client/utils/goalGate.test.ts: PPPPP
  packages/webui/src/daemon/session/mappers.test.ts: PPPPP

verdict: pass
summary: 6 changed test file(s) x 5 identical rounds, no divergence

--- per-invocation detail (full copy in the artifact) ---
round 1 · packages/core/src/goals/goal-reducer.test.ts: P (exit 0)
round 1 · packages/core/src/goals/goal-runtime.test.ts: P (exit 0)
round 1 · packages/core/src/goals/goal-tools.test.ts: P (exit 0)
round 1 · packages/core/src/telemetry/uiTelemetry.test.ts: P (exit 0)
round 1 · packages/web-shell/client/utils/goalGate.test.ts: P (exit 0)
round 1 · packages/webui/src/daemon/session/mappers.test.ts: P (exit 0)
round 2 · packages/core/src/goals/goal-reducer.test.ts: P (exit 0)
round 2 · packages/core/src/goals/goal-runtime.test.ts: P (exit 0)
round 2 · packages/core/src/goals/goal-tools.test.ts: P (exit 0)
round 2 · packages/core/src/telemetry/uiTelemetry.test.ts: P (exit 0)
round 2 · packages/web-shell/client/utils/goalGate.test.ts: P (exit 0)
round 2 · packages/webui/src/daemon/session/mappers.test.ts: P (exit 0)
round 3 · packages/core/src/goals/goal-reducer.test.ts: P (exit 0)
round 3 · packages/core/src/goals/goal-runtime.test.ts: P (exit 0)
round 3 · packages/core/src/goals/goal-tools.test.ts: P (exit 0)
round 3 · packages/core/src/telemetry/uiTelemetry.test.ts: P (exit 0)
round 3 · packages/web-shell/client/utils/goalGate.test.ts: P (exit 0)
round 3 · packages/webui/src/daemon/session/mappers.test.ts: P (exit 0)
round 4 · packages/core/src/goals/goal-reducer.test.ts: P (exit 0)
round 4 · packages/core/src/goals/goal-runtime.test.ts: P (exit 0)
round 4 · packages/core/src/goals/goal-tools.test.ts: P (exit 0)
round 4 · packages/core/src/telemetry/uiTelemetry.test.ts: P (exit 0)
round 4 · packages/web-shell/client/utils/goalGate.test.ts: P (exit 0)
round 4 · packages/webui/src/daemon/session/mappers.test.ts: P (exit 0)
round 5 · packages/core/src/goals/goal-reducer.test.ts: P (exit 0)
round 5 · packages/core/src/goals/goal-runtime.test.ts: P (exit 0)
round 5 · packages/core/src/goals/goal-tools.test.ts: P (exit 0)
round 5 · packages/core/src/telemetry/uiTelemetry.test.ts: P (exit 0)
round 5 · packages/web-shell/client/utils/goalGate.test.ts: P (exit 0)
round 5 · packages/webui/src/daemon/session/mappers.test.ts: P (exit 0)

Evidence images

01-ab-budget-gate-head-20-20

02-ab-budget-gate-base-8-8

03-mutation-matrix-435

04-wire-oracle-limit-kind

05-restart-boundary

06-matrix-adjudication-15-15

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-dev-bot

Copy link
Copy Markdown
Collaborator

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

Autofix review round — no code changes

The only new feedback was the automated reviewer's partial-review disclosure (rv:5028193271): the Integration Tests (CLI, No Sandbox) lane was skipped in CI and its suite did not run locally during review. There were no findings, no inline comments, and no failed checks, so no code change was requested or needed. Instead, this round closed the disclosed verification gap by running the exact lane locally.

Why the lane was skipped in CI

The lane is merge-queue-only by design (.github/workflows/ci.yml: if: github.event_name == 'merge_group'), so it never runs on PR pushes. It will run automatically when this PR enters the merge queue.

Local run of the exact lane

After a fresh npm run build && npm run bundle from HEAD:

  • env -u SANDBOX npm run test:integration:cli:sandbox:none (the exact script the CI lane runs)
  • Result: 38 files — 32 passed, 1 failed, 5 self-skipped; 211 tests — 192 passed, 1 failed, 18 skipped (328s). The self-skipped files are the benchmark/environment-gated ones (qwen-daemon-vs-cli-benchmark, qwen-daemon-startup-benchmark, stdin-context, read_many_files, save_memory).
  • The Goal surface this PR changes is covered by this lane: cli/qwen-serve-routes.test.ts — including the GET /goals serve → bridge → ACP-child round trip — passed 36/36.

The single failure is environmental, not caused by this PR

  1. cli/qwen-config-dir.test.ts test 1d runs the CLI with QWEN_HOME unset, so the CLI falls back to the default ~/.qwen and exits with EACCES: permission denied, mkdir '/home/github-runner/.qwen'.
  2. On this autofix runner /home/github-runner is owned by root while the tests run as uid 1000 (node); a direct mkdir /home/github-runner/.qwen probe fails with the identical EACCES outside of any test.
  3. The same test file re-run with a writable HOME passes 7/7.
  4. This PR's diff touches no file in the failing path (packages/cli/** and integration-tests/** are untouched — the PR only changes packages/core/src/goals/**, one telemetry test, packages/sdk-typescript/src/daemon/types.ts, packages/webui/src/daemon/session/mappers.ts, and packages/web-shell/client/utils/goalGate.test.ts), so any branch hits the same permission wall on this machine.

The merge-queue lane runs on clean runners with writable home directories, so this failure does not predict a CI failure there.

Verification

  • npm run build && npm run bundle — passed (fresh artifacts from HEAD 17fc1b86f5)
  • env -u SANDBOX npm run test:integration:cli:sandbox:none — 32/38 files passed; the sole failure is environmental (evidence above)
  • vitest cli/qwen-config-dir.test.ts with writable HOME — 7/7 passed
  • packages/core focused vitest (src/goals/**, src/telemetry/uiTelemetry.test.ts) — 488 passed (17 files)
  • packages/webui focused vitest (src/daemon/session/mappers.test.ts) — passed
  • packages/web-shell focused vitest (client/utils/goalGate.test.ts) — passed
  • npm run typecheck — passed
  • npm run lint — passed (0 errors/warnings)

No code changes and no commits; the working tree and PR head are unchanged.

中文说明

Autofix 审查轮次 — 无代码变更

本次唯一的新反馈是自动审查器披露的部分审查缺口(rv:5028193271):Integration Tests (CLI, No Sandbox) 这一 CI 检查被跳过,且审查时也未在本地运行该套件。本轮没有任何审查发现(findings)、内联评论或失败的检查,因此没有需要也没有必要进行的代码变更。作为替代,本轮通过在本地运行完全相同的检查来弥补所披露的验证缺口。

该检查为何在 CI 中被跳过

该检查按设计仅在合并队列中运行(.github/workflows/ci.ymlif: github.event_name == 'merge_group'),因此在 PR 推送时从不运行。当本 PR 进入合并队列时它会自动运行。

在本地运行完全相同的检查

在从 HEAD 重新执行 npm run build && npm run bundle 之后:

  • env -u SANDBOX npm run test:integration:cli:sandbox:none(与 CI 检查完全相同的脚本)
  • 结果:38 个文件 — 32 通过、1 失败、5 个自跳过;211 个测试 — 192 通过、1 失败、18 个跳过(328 秒)。自跳过的文件为基准测试/受环境门控的文件(qwen-daemon-vs-cli-benchmarkqwen-daemon-startup-benchmarkstdin-contextread_many_filessave_memory)。
  • 本 PR 改动的 Goal 相关功能由该检查覆盖:cli/qwen-serve-routes.test.ts(包含 GET /goals 的 serve → bridge → ACP 子进程完整往返)36/36 全部通过。

唯一失败的测试属于环境问题,与本 PR 无关

  1. cli/qwen-config-dir.test.ts 的测试 1d 在未设置 QWEN_HOME 的情况下运行 CLI,CLI 因此回退到默认的 ~/.qwen,并以 EACCES: permission denied, mkdir '/home/github-runner/.qwen' 退出。
  2. 在本 autofix 运行器上,/home/github-runner 属主为 root,而测试以 uid 1000(node)运行;在任何测试之外直接执行 mkdir /home/github-runner/.qwen 探测,得到完全相同的 EACCES 错误。
  3. 使用可写的 HOME 重新运行同一测试文件,7/7 全部通过。
  4. 本 PR 的 diff 未触及失败路径中的任何文件(packages/cli/**integration-tests/** 完全未被改动 — PR 仅改动 packages/core/src/goals/**、一个遥测测试、packages/sdk-typescript/src/daemon/types.tspackages/webui/src/daemon/session/mappers.ts 以及 packages/web-shell/client/utils/goalGate.test.ts),因此任何分支在这台机器上都会撞上同样的权限墙。

合并队列中的该检查运行在家目录可写的干净运行器上,因此该失败不预示 CI 中会出现同样的失败。

验证

  • npm run build && npm run bundle — 通过(基于 HEAD 17fc1b86f5 的新鲜产物)
  • env -u SANDBOX npm run test:integration:cli:sandbox:none — 38 个文件中 32 个通过;唯一失败为环境问题(证据见上)
  • 使用可写 HOME 运行 vitest cli/qwen-config-dir.test.ts — 7/7 通过
  • packages/core 定向 vitest(src/goals/**src/telemetry/uiTelemetry.test.ts)— 488 通过(17 个文件)
  • packages/webui 定向 vitest(src/daemon/session/mappers.test.ts)— 通过
  • packages/web-shell 定向 vitest(client/utils/goalGate.test.ts)— 通过
  • npm run typecheck — 通过
  • npm run lint — 通过(0 错误/警告)

无代码变更、无提交;工作树与 PR head 均保持不变。

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


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

@qqqys
qqqys added this pull request to the merge queue Aug 26, 2026
Merged via the queue into QwenLM:main with commit 6cab1f7 Aug 26, 2026
77 checks passed
@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

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

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

Autofix address-review round — no code changes

Every feedback item newer than the last evaluation (2026-08-26T08:18:19Z) was inspected, and none requests a code change:

  • Review (rv:5028585561) — the automated reviewer's latest review is APPROVED: "LGTM, looks ready to ship. ✅". It carries no findings.
  • Issue comment (ic:5422821966)@qwen-code-ci-bot was invoked with /triage; this is a command to the verification pipeline, not feedback on the code.
  • Issue comment (ic:5422841671) — informational: sandboxed verification is running; its report will be posted by the pipeline itself when the run completes.
  • Issue comment (ic:5423057365) — the previous autofix round's own status report ("no changes needed"); not new feedback.

Additionally:

  • No inline comments are newer than the last evaluation; the 37 historical inline findings were all handled in earlier rounds.
  • No failed or still-red checks: every completed check in this window is SUCCESS or SKIPPED (skips are by-design conditional lanes, e.g. merge-queue-only integration tests).
  • No base conflict to resolve (--conflict false), so no merge of origin/main was performed.
  • Diff growth this window (source 4 / test 1 net lines) is far inside the budgets (400/400); no growth audit or critical-only mode is in effect.

No commit was made and no thread was resolved. The branch stays at 17fc1b86f5, awaiting the sandboxed verification report.

中文说明

🤖 已审阅最新反馈——无需改动。逐点说明如下:

Autofix address-review 轮次——无代码改动

已检查所有晚于上次评估时间(2026-08-26T08:18:19Z)的反馈条目,均不要求代码改动:

  • 评审(rv:5028585561)——自动评审器的最新评审状态为 APPROVED:"LGTM, looks ready to ship. ✅"。其中不包含任何发现项。
  • Issue 评论(ic:5422821966)——通过 /triage 调用了 @qwen-code-ci-bot;这是发给验证流水线的命令,而非针对代码的反馈。
  • Issue 评论(ic:5422841671)——信息性内容:沙箱验证正在运行,运行结束后流水线会自动发布验证报告。
  • Issue 评论(ic:5423057365)——上一轮 autofix 自身的状态报告("无需改动"),不属于新反馈。

此外:

  • 没有晚于上次评估时间的内联评论;历史上的 37 条内联发现项均已在此前的轮次中处理完毕。
  • 没有失败或持续标红的检查:本窗口内所有已完成的检查均为 SUCCESSSKIPPED(跳过项是按设计仅在特定条件下运行的通道,例如仅在合并队列中运行的集成测试)。
  • 没有需要解决的基线冲突(--conflict false),因此未执行 origin/main 的合并。
  • 本窗口的 diff 增长(源码净 4 行 / 测试净 1 行)远低于预算(400/400);未触发增长审计或仅处理 Critical 模式。

未创建任何提交,也未将任何讨论线程标记为已解决。分支保持在 17fc1b86f5,等待沙箱验证报告发布。

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


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

@wenshao

wenshao commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

Post-merge verification of #9891 — real stack, on the tree that actually landed

I re-ran a full local verification against the current head 17fc1b86f5. The PR was squash-merged as 6cab1f7f8b at 09:15Z while this run was in progress, so this is a post-merge confirmation rather than a merge gate. I checked that what I tested is what landed: git diff 17fc1b86f5 6cab1f7f8b is empty across packages/core/src/goals/, sdk-typescript/src/daemon/types.ts, webui/.../mappers.ts and web-shell/.../goalGate.ts.

Verdict: the merged code does what it says. The budget stops autonomous continuation, resume/edit re-arms it, the meter is never reset, user turns are never blocked, pre-budget Goals stay unbounded, and the new token_budget resume path preserves the evidence window that an evidence-limited resume discards. No regression against the merge base. Three follow-up notes below, none of them defects in this change.

Setup

  • head = 17fc1b86f5 (= merged tree), base = merge-base 0756be0ce7. Both npm install && npm run build && npm run bundle, run from the production bundle dist/cli.js. macOS 26.6.2 / Node v24.18.1.
  • A loopback OpenAI-compatible model that never proposes completion and reports an honest usage block. GOAL_DEFAULT_TOKEN_BUDGET was never shrunk — I sized the per-call spend instead (1,000,000/call for the headless legs → the real 30,000,000 ceiling falls exactly on turn 30; 3,000,000/call for the TUI and Web Shell legs → turn 10).
  • Four surfaces: headless CLI (-o stream-json), interactive TUI (real pty → xterm.js), Web Shell (real qwen serve daemon + Chromium, with GET /session/:id/goal as the wire check), and a decision table driven straight against each arm's built packages/core/dist.

What is new since my two earlier reports (Aug 25)

Those ran against 51d5aeb7. Since then the branch absorbed #9975 (checkpoint-stall bound) through a conflicted merge of goal-reducer.ts / goal-runtime.ts / goal-tools.ts, plus 353da2ee8 (evidence-window pin) and b8af58bd (meter-coverage doc). This round targets exactly that.

# Claim Measured on head Base arm
1 A new Goal arms the default ceiling create stamps tokenBudget: 30000000 no tokenBudget field
2 Autonomous continuation stops at the ceiling stop on turn 30 at tokensUsed 30,000,000exact hit, zero overshootusage_limited / limitKind: token_budget, lastReason names the ceiling, no 31st turn minted, exit 0 in 12s ran 94 turns / 94,000,000 (3.1×) before a different bound stopped it — see note 1
3 Resume re-arms ahead of the meter ceiling 30,000,000 → 60,000,000 (= tokensUsed + 30M), tokensUsed untouched, 30 more turns, second stop exactly at 60,000,000 n/a
4 A budget resume keeps the evidence window (the 353da2ee8 pin) evidenceCursor identical across all 124 goal_state records of the Goal's whole life — both stops and the resume n/a
5 An evidence_catalog resume still restarts the window (#9975 path intact) cursor repointed c4c1754c → 29779058, checkpointStalls 3 → cleared
6 User turns are never blocked or metered prompt on a stopped Goal → answered, exit 0, num_turns 1; Goal stays usage_limited, turnCount and tokensUsed unchanged n/a
7 Pre-budget Goals restore unbounded base-created Goal resumed by the head bundle: no tokenBudget retrofitted, ran to 169 turns / 169,000,000 until my wall-time cap
8 get_goal reports the ceiling unpermitted summary: "tokensUsed":60000000,"tokenBudget":60000000 + the reason
9 The kind crosses the wire GET /session/:id/goallimitKind:"token_budget", tokenBudget:30000000 → after Resume 60000000, cursor unchanged
10 TUI stop at 10 turns; footer ! /goal usage limited; /goal shows the 30,000,000 reason; /goal resume → 10 more turns → /goal now shows 20 turns and the 60,000,000 reason 63 turns / 189,000,000 in 37s, still Goal running until I typed /goal pause
11 Web Shell strip Usage limited · Waiting, Resume offered, reason in the transcript card; Resume re-armed to 60M and the second stop rendered
12 The model cannot buy its own window the provider saw 28 tool declarations per Goal turn; the only Goal tools are get_goal and update_goal. No control action (resume/edit/replace) is reachable from the model — only goalCommand.ts and the Web Shell UI issue them

Decision table over the built reducer

9,603 enumerated transitions (status × limitKind × lastReason × budget shape × stalls × grant × action), run against each arm's compiled core/dist:

  • 601 rows carry no tokenBudget, no grant and no token_budget kind — 0 differ. That is the zero-regression bucket, quantified.
  • 8 rows differ under the enum widening alone, all of them usage_limited + token_budget + resume: base (presence semantics) repoints the evidence cursor, head keeps it. That is precisely the bug the narrowing fixes.
  • Every documented semantic reproduces, including the feat(goal): stop a Goal whose checkpoints stall three times in a row #9975 interaction:
current: tokensUsed=1500  tokenBudget=1000 (spent)  evCursor=r-old  checkpointStalls=2  grant=5000

resume  budget-stopped                        -> active         budget 6500  stalls 2     cursor r-old         <- window kept
resume  evidence_catalog-stopped + spent      -> active         budget 6500  stalls clear cursor r-cursor-new  <- window restarted AND re-armed
resume  checkpoint_request-stopped + spent    -> active         budget 6500  stalls clear cursor r-cursor-new
resume  legacy prose (pre-limitKind) + spent  -> active         budget 6500  stalls 2     cursor r-old
resume  paused / blocked + spent              -> active         budget 6500  stalls 2     cursor r-old
resume  ceiling UNSPENT                       -> active         budget 1000  (untouched)
resume  host opted out (Infinity grant)       -> active         budget absent
resume  no grant supplied                     -> active         budget 1000  (never retrofitted)
edit    budget-stopped + spent                -> usage_limited  budget 6500  (see note 2)
create / replace with grant 5000              -> active         budget 5000
resume  an ACTIVE Goal                        -> throws GoalInvalidTransitionError
  • Persistence guard: tokenBudget round-trips for finite non-negative numbers; -1, NaN, "1000" rejected; absent restores unbounded.

Mutation probes

Against the PR's own suite (source) and against the shipped bundle driven by the real CLI:

Probe Suite Real runtime (patched dist)
Delete the queueContinuation gate 3 red / 432 green Goal is still stamped tokenBudget: 30000000 and blows straight through it — 94 turns / 94,000,000, byte-for-byte the base arm's behaviour. The single gate is the entire stop.
Neuter rearmedTokenBudget 8 red / 427 green one /goal resume becomes a 708× resume → usage_limited ping-pong; turnCount frozen at 30, tokensUsed frozen, zero model calls, journal growing. The re-arm is load-bearing, not decorative.
Revert isEvidenceLimited to presence semantics + drop the token_budget resume branch 1 red — exactly the test 353da2ee8 added the budget resume silently discards the evidence window (cursor 23182fed → 561e7b79) while everything else, including the 60M re-arm, looks identical. Invisible without that pinned assertion.

Suites and typecheck on head: packages/core src/goals/ 435 passed / 16 files; packages/webui mappers 38 passed; packages/web-shell goalGate + GoalStatusStrip 10 passed; tsc --noEmit clean in packages/core (head and base — the 4 pre-existing errors named in the description are gone from today's main) and in packages/webui. The uiTelemetry.test.ts reflow flagged as churn is now a no-op against current main (git diff origin/main on that file is empty).

Screenshots

TUI — the Goal stops itself at the 30,000,000 ceiling and /goal explains why:

TUI first stop

TUI — after /goal resume: 10 more turns, and the ceiling the second stop quotes has moved to 60,000,000:

TUI resume re-arm

TUI, base arm — same model, same objective: 63 turns / 189,000,000 tokens and still running until a human typed /goal pause:

TUI base unbounded

Web Shell (real daemon) — Usage limited · Waiting, Resume offered, reason in the transcript card:

Web Shell stop

Web Shell — both windows in one view: stop at 30,000,000, Resume, stop again at 60,000,000:

Web Shell both stops

Follow-up notes — none of them defects in this change

  1. The description's premise has drifted slightly. "A Goal run currently has no autonomous termination path" was true when this was written; feat(goal): stop a Goal whose checkpoints stall three times in a row #9975 landed on main since and did stop my base runaway — at 94 turns / 94,000,000, via evidence_catalog. It is a path-specific bound, though: the TUI base arm reached 63 turns / 189,000,000 without ever tripping it. So the budget still fires 3.1× earlier and covers runs the stall bound never sees — the case for it is intact, the sentence is just stale.
  2. /goal edit re-arms the ceiling but does not restart the Goal. Measured: edit of a budget-stopped Goal moves tokenBudget 30,000,000 → 60,000,000 and bumps the revision, but leaves status: usage_limited with limitKind/lastReason cleared — so the run does not continue and the UI shows a bare "Usage limited" chip with no "Last check" line until the user also resumes. Pre-existing edit semantics; worth one word in the description, which reads as though an edit alone buys a running window.
  3. Downgrade behaviour, for the record. A build without this change rejects any journal record carrying tokenBudget and silently falls back to the previous record. That is the established convention for every Goal field addition — I checked that the same base build also rejects an unknown field but happily accepts limitKind and checkpointStalls, which predate it. Nothing to do; just the cost of the strict-key parser.

Also confirmed resolved from my earlier round: the meter's coverage (b8af58bd) is now spelled out in the constant's doc comment, and the "Goal aborted" / "Goal set" labels the Web Shell puts on a usage_limited stop and on a resume come from GoalStatusMessage.tsx, which is byte-identical between base and head — pre-existing on main, untouched here.

中文说明

#9891 合入后验证 —— 真实环境,针对真正落地的那棵树

我对当前 head 17fc1b86f5 重新做了一遍完整的本地验证。本 PR 已于 09:15Z 以 6cab1f7f8b squash 合入——正好在这次构建进行中——因此这是一份合入后确认,而非合并门禁。我核对过「我测的」与「合入的」是同一份:git diff 17fc1b86f5 6cab1f7f8bpackages/core/src/goals/sdk-typescript/src/daemon/types.tswebui/.../mappers.tsweb-shell/.../goalGate.ts为空

结论:合入的代码与描述一致。 预算会停止自主续跑,resume/edit 重新武装,计量表从不重置,用户驱动的 turn 永不被阻塞,预算出现之前的 Goal 保持无界,且新的 token_budget 恢复路径会保留证据窗口——而证据类恢复则会重置它。与合并基线对比无回归。下方三条后续备注,均非本次改动的缺陷。

环境

  • head = 17fc1b86f5(即合入树),base = merge-base 0756be0ce7。两者均 npm install && npm run build && npm run bundle,从生产包 dist/cli.js 运行。macOS 26.6.2 / Node v24.18.1。
  • 本机回环上的 OpenAI 兼容假模型,从不提出完成,返回诚实的 usageGOAL_DEFAULT_TOKEN_BUDGET 从未被调小——我调的是每次调用的消耗量(headless 腿 100 万/次 → 真实的 3000 万上限恰好落在第 30 turn;TUI 与 Web Shell 腿 300 万/次 → 第 10 turn)。
  • 四个面:headless CLI(-o stream-json)、交互 TUI(真 pty → xterm.js)、Web Shell(真实 qwen serve daemon + Chromium,并以 GET /session/:id/goal 做线协议判据),以及直接驱动两条臂已构建packages/core/dist 的决策表。

相对 8 月 25 日两份报告的新增部分

那两份跑的是 51d5aeb7。此后分支通过一次带冲突的合并吸收了 #9975(checkpoint 停滞边界,冲突文件为 goal-reducer.ts / goal-runtime.ts / goal-tools.ts),外加 353da2ee8(证据窗口断言)与 b8af58bd(计量口径文档)。本轮正是针对这些。

# 断言 head 实测 base 臂
1 新建 Goal 武装默认上限 create 盖上 tokenBudget: 30000000 tokenBudget 字段
2 自主续跑在上限处停止 30 turn 停止,tokensUsed 30,000,000——精确命中,零超调——usage_limited / limitKind: token_budgetlastReason 写明上限,不铸造第 31 turn,12 秒 exit 0 跑了 94 turn / 9400 万(3.1 倍)后被另一个边界停下——见备注 1
3 恢复把上限移到计量表之前 上限 30,000,000 → 60,000,000(= tokensUsed + 3000 万),tokensUsed 不变,再跑 30 turn,第二次停止精确落在 60,000,000 不适用
4 预算恢复保留证据窗口353da2ee8 锁定的点) 该 Goal 全生命周期 124goal_state 记录里 evidenceCursor 完全一致——两次停止与那次恢复都是 不适用
5 evidence_catalog 恢复仍重启窗口(#9975 路径完好) cursor 重指 c4c1754c → 29779058checkpointStalls 3 → 清空
6 用户 turn 既不被阻塞也不计量 对已停 Goal 发消息 → 正常应答,exit 0,num_turns 1;Goal 仍为 usage_limitedturnCounttokensUsed 均不变 不适用
7 预算出现之前的 Goal 恢复后无界 base 创建的 Goal 用 head 包恢复:不追加 tokenBudget,一路跑到 169 turn / 1.69 亿,直到我的 wall-time 上限
8 get_goal 向模型报告上限 无许可摘要:"tokensUsed":60000000,"tokenBudget":60000000 及原因
9 类型跨线传输 GET /session/:id/goallimitKind:"token_budget"tokenBudget:30000000;点 Resume 后变 60000000,cursor 不变
10 TUI 10 turn 后停止;底栏 ! /goal usage limited/goal 显示 3000 万的原因;/goal resume 后再跑 10 turn,/goal 显示 20 turns 与 6000 万的原因 37 秒 63 turn / 1.89 亿,仍是 Goal running,直到我敲 /goal pause
11 Web Shell 状态条 Usage limited · Waiting提供 Resume,转录卡片显示原因;点 Resume 重新武装到 6000 万,第二次停止正常渲染
12 模型无法自己购买窗口 每个 Goal turn 供应商收到 28 个工具声明,其中 Goal 相关只有 get_goalupdate_goal。模型触达不到任何控制动作(resume/edit/replace)——只有 goalCommand.ts 与 Web Shell UI 会发出

已构建 reducer 上的决策表

枚举 9,603 条转换(status × limitKind × lastReason × 预算形态 × stalls × grant × action),分别跑在两条臂编译后的 core/dist 上:

  • 601 条「不带 tokenBudget、不带 grant、不带 token_budget 类型」的输入行——0 条不同。 这就是零回归的量化证明。
  • 仅因枚举放宽而不同的有 8 条,全部是 usage_limited + token_budget + resume:base(按存在性判定)会重指证据 cursor,head 保留。这正是本次收窄要修的问题。
  • 每一条已声明语义都复现了,包括与 feat(goal): stop a Goal whose checkpoints stall three times in a row #9975 的交互(表见上方英文代码块:预算恢复保留窗口;证据类恢复重启窗口并同时重新武装;未耗尽上限不动;退出时清空;从不追加;对 ACTIVE 的 resume 抛错)。
  • 持久化守卫:tokenBudget 对有限非负数往返正常;-1NaN"1000" 被拒;缺省则恢复为无界。

变异检验

同时打在 PR 自带套件(源码)与已发布 bundle(由真实 CLI 驱动)上:

检验 套件 真实运行时(打补丁的 dist
删掉 queueContinuation 闸门 3 红 / 432 绿 Goal 仍被盖上 tokenBudget: 30000000 却径直冲过去——94 turn / 9400 万,与 base 臂表现完全一致。这一个闸门就是全部的停止能力。
rearmedTokenBudget 失效 8 红 / 427 绿 一次 /goal resume 变成 708 次 resume → usage_limited 乒乓turnCount 冻结在 30,tokensUsed 冻结,零模型调用,journal 持续膨胀。重新武装是承重件,不是装饰。
isEvidenceLimited 退回存在性语义 + 删掉 token_budget 恢复分支 1 红——恰好是 353da2ee8 新增的那条 预算恢复会静默丢弃证据窗口(cursor 23182fed → 561e7b79),而其余一切(含 6000 万重新武装)看起来毫无差别。没有那条断言就看不出来。

head 上的套件与类型检查:packages/coresrc/goals/ 435 通过 / 16 文件packages/webui mappers 38 通过packages/web-shellgoalGate + GoalStatusStrip 10 通过tsc --noEmitpackages/core(head 与 base 均是——描述里提到的 4 个既存错误在今天的 main 上已消失)与 packages/webui 均干净。被指为无关扰动的 uiTelemetry.test.ts 重排,相对当前 main 已是空差异

截图

见上方英文部分:TUI 3000 万触顶停止并给出原因;/goal resume 后上限移到 6000 万的第二次停止;base 臂 63 turn / 1.89 亿仍在跑直到人工 pause;Web Shell 停止并提供 Resume;Web Shell 一屏内的两次停止。

后续备注 —— 均非本次改动的缺陷

  1. 描述的前提略有漂移。 「Goal 运行目前没有任何自主终止路径」在写下时成立;此后 feat(goal): stop a Goal whose checkpoints stall three times in a row #9975 已合入 main,并且确实停下了我的 base 失控运行——在 94 turn / 9400 万 处,以 evidence_catalog 落停。但它是路径特定的边界:TUI 的 base 臂跑到 63 turn / 1.89 亿 都没触发它。所以预算依然早 3.1 倍生效,并覆盖停滞边界永远看不到的运行——立论仍然成立,只是那句话过时了。
  2. /goal edit 重新武装上限,但不会重启 Goal。 实测:对预算停止的 Goal 执行 edit,tokenBudget 30,000,000 → 60,000,000 且 revision 递增,但 status 仍是 usage_limitedlimitKind/lastReason 被清空——于是运行不会继续,且在用户再执行一次 resume 之前,界面只显示一个没有「Last check」行的 "Usage limited" 标签。这是既有的 edit 语义;描述读起来像是「一次 edit 就买到了一个正在运行的窗口」,值得改一个词。
  3. 降级行为,备案。 不含本改动的构建会拒绝任何携带 tokenBudget 的 journal 记录,并静默回退到上一条。这是每一次 Goal 字段新增的既定惯例——我核对过:同一个 base 构建同样拒绝未知字段,却能正常接受早于它的 limitKindcheckpointStalls。无需处理,只是严格键解析器的代价。

另外,上一轮的两点已确认收敛:计量口径(b8af58bd)已写进常量的文档注释;Web Shell 给 usage_limited 停止打的 "Goal aborted" 标签与给 resume 打的 "Goal set" 标签来自 GoalStatusMessage.tsx,该文件在 base 与 head 之间逐字节相同——是 main 上的既有问题,本 PR 未触碰。


Verified locally on macOS 26.6.2 / Node v24.18.1 — harness: scripted OpenAI-compatible server, production dist/cli.js bundles of both arms, real qwen serve daemon, Playwright/Chromium Web Shell, node-pty TUI capture, and a 9,603-row decision table driven against each arm's compiled core/dist.

pull Bot pushed a commit to TKaxv-7S/qwen-code that referenced this pull request Aug 26, 2026
…wenLM#10125)

A Goal whose objective cannot be satisfied as written -- it contradicts
itself, names a target that verifiably does not exist, or needs an
action no tool can perform -- had no sanctioned way to say so. Blocked
proposals stop immediately only for user authority or an external
change; everything else is treated as a repeated technical blocker and
must recur on three consecutive turns before the verifier sees it. So an
impossible objective burned turns until the token budget (QwenLM#9891) or a
human stopped it. The session that motivated this series ran 34 minutes
on an objective ("验证下版本") too under-specified to ever complete. CC's
stop evaluator can answer `impossible` and end the loop; this is the
counterpart.

`blockerKind: 'infeasible'` joins authority, external and repeated. It
is not a new status: the Goal settles as `blocked`, which every surface
already renders and which resumes into `/goal edit` -- the only fix for
an objective that cannot hold.

Three rules keep it from becoming an "I think this can't be done" exit:

- It bypasses the three-turn repetition rule. Waiting three turns to
  report an impossibility is the runaway this kind exists to end, and
  the evidence bar below is what earns the early exit.
- The cited evidence must include an external_fact. User input can
  authorise a stop (that is `authority`) but cannot make an objective
  impossible, and assistant prose saying so is exactly what must not
  count. Like the other immediate blockers it must also cite every newer
  record, so a contradicting fact cannot be left out.
- The verifier policy accepts it only for self-contradiction, a target
  that verifiably does not exist, or an action outside the tools, and
  rejects difficulty, uncertainty, obtainable information, or a
  preference to ask.

An accepted infeasible stop appends a fixed next step to lastReason, so
the stopped Goal tells the user what to do, not only what went wrong.

Mutation probes (goal-evidence + goal-runtime + goal-tools, 194 tests),
each killing exactly one test: infeasible routed through the repetition
audit; policy sentence removed; external_fact requirement removed; next
step suffix dropped; 'infeasible' removed from the tool schema enum.
pull Bot pushed a commit to TKaxv-7S/qwen-code that referenced this pull request Aug 26, 2026
…oal (QwenLM#10132)

QwenLM#9891 stops a Goal the moment its autonomous token budget is spent: the
continuation gate refuses the next turn and settles usage_limited. That
bounds runaway spend, but it cuts the model off mid-thought -- whatever
it had learned in the last window is stranded in the transcript, and
the user who resumes gets no hand-off.

A spent window now buys exactly one more continuation, flagged
`windDown`, whose prompt says the budget is spent, forbids new work,
and asks for a concise hand-off: what was accomplished with evidence
refs, what remains, the one concrete next step. When that turn
finishes, the gate stops the Goal as before. A hand-off turn that finds
the objective already met and proposes completion still completes the
Goal: the stop only ever refuses a continuation, never a verdict.

Exactly one per window, and persisted: the wind-down turn's own
turn_finished record stamps `GoalRecord.windDownTurnId`, so the gate
can tell "hand-off delivered" from "hand-off owed" across a restart
with no extra journal write and no new state cause. A hand-off the
host dropped undelivered leaves no marker and is minted again; a
restart that interrupted the hand-off turn grants it again for the
same reason -- the user never got one. Re-arming the budget on resume
or edit clears the marker, so each window owes its own.

The flag rides the host boundary like verifierFeedback, through all
three hosts, and the prompt block sits after the authoritative
objective line and above verifier feedback; the ordinary prompt is
byte-identical to before.

Mutation probes (goal-runtime + goal-reducer, 218 tests): finishTurn
never stamps the marker -> 3 fail; gate ignores the marker -> 4; gate
never grants -> 6; re-arm keeps the old marker -> 3; parse never
restores it -> 2. Each host hop deleted -> exactly one test fails in
that host's suite (useMessageQueue, useGeminiStream, nonInteractiveCli,
Session).
@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Released in v0.22.2.

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.

5 participants