fix(acp): emit standard context usage updates - #8528
Conversation
|
✅ Qwen Triage finished — CI landed green on ✅ Qwen Triage 已完成 —— |
Verification reportEnvironment
Reproduction and resultOn unmodified After the fix, a compiled and bundled {
"sessionUpdate": "usage_update",
"used": 321,
"size": 128000
}The same integration run confirmed that the existing Boundary coverage confirms that usage is per-round rather than cumulative, total tokens are used only when prompt tokens are absent, and replay/subagent/unresolved-model frames do not overwrite the main live session indicator. Tests executed
EvidenceThe deterministic full-bundle subprocess result above is the direct, repeatable wire-level evidence. The focused bridge test also sends No screenshot is attached. This is an ACP protocol-frame emission change rather than a Qwen-rendered UI change, and JetBrains was unavailable in the local test environment. A JetBrains screenshot would therefore not be reproducible here; the real subprocess protocol assertion directly validates the behavior JetBrains consumes. 中文验证报告验证报告环境
复现与结果在未修改的 修复后,测试初始化并认证了编译、打包后的 {
"sessionUpdate": "usage_update",
"used": 321,
"size": 128000
}同一次集成测试还确认,现有 边界覆盖确认:用量按轮次报告而非累计;仅当 prompt token 缺失时才回退到 total token;历史回放、子 agent 以及模型配置尚未解析的帧不会覆盖主实时会话的指示器。 已执行测试
证据上述确定性的完整打包子进程结果是直接、可重复的线协议证据。聚焦 bridge 测试还通过内存 ACP 连接发送 未附截图。此改动修复的是 ACP 协议帧发送,而非 Qwen 自身渲染的 UI,并且本地测试环境没有 JetBrains,因此无法在这里生成可复现的 JetBrains 截图;真实子进程协议断言直接验证了 JetBrains 所消费的行为。 |
|
Thanks for the PR! Template: complete — motivation, before/after evidence, test plan, risk & scope, and the bilingual summary are all there. ✓ Problem: real and well-documented. Linked issue #8513 observed the gap against a released build (0.21.5): JetBrains AI Assistant renders context occupancy from the standard ACP Direction: aligned. ACP is a first-class integration surface for Qwen Code, Size: small and honest — 35 production lines in one file ( Approach: the scope feels right. Emission lands exactly where the per-round usage is already reported, keeps the private frame for current consumers, and deliberately excludes replay frames, subagent rounds, and unresolved values — matching the semantics the issue requested (per-round occupancy, not cumulative). Nothing to cut. Risk: one elevated signal — the change sits under Moving on to code review. 🔍 中文说明感谢贡献! 模板:完整——动机、before/after 证据、测试计划、风险范围与双语总结齐备。✓ 问题:真实且记录充分。关联 issue #8513 在正式发布版本(0.21.5)上观测到此缺口:JetBrains AI Assistant 通过 ACP 标准 方向:对齐。ACP 是 Qwen Code 的一等集成面, 规模:小而实在——单文件 35 行生产代码( 方案:范围合理。发送点恰好落在每轮用量既有的上报位置,保留了供现有消费者使用的私有帧,并明确排除历史回放、子 agent 轮次与未解析的用量值——符合 issue 要求的语义(按轮次的占用量,而非累计值)。没有可砍的部分。 风险:有一个升级信号——改动位于 revert 历史分析标记为高风险的 进入代码审查。🔍 — Qwen Code · qwen3.8-max Reviewed at |
Code reviewI read the diff against the issue and the surrounding code, and this lands exactly where it should:
No critical findings; the production change is 35 lines and every one of them serves the stated goal. sequenceDiagram
participant P1 as Session model round
participant P2 as MessageEmitter
participant P3 as ACP stream
participant P4 as ACP client (JetBrains)
P1->>P2: emitUsageMetadata - usage, durationMs
P2->>P3: private agent_message_chunk _meta.usage
alt live main round and window resolved
P2->>P3: standard usage_update - used, size
P3->>P4: renders context occupancy
else replay, subagent or unresolved values
P2-->>P3: no standard frame emitted
end
Files changed (5)
Testing evidence — the PR's own CI (this run executes no PR code)The one red check, Final CI results for
One row per check name (latest run); skipped checks omitted; failures sort first. / 每个检查名一行(取最新一次运行),省略 skipped,失败项排在最前。 What the signal does and doesn't cover: the emitter unit tests and the bridge test run inside the ubuntu Node suite (still pending at this snapshot). The wire-level proof — the full-bundle Sandboxed verification would settle this: Not verified here: real JetBrains rendering (no JetBrains in CI; the author also noted it unavailable) — the protocol-frame assertion is the closest available oracle for that. 中文说明代码审查:改动落在了它该在的位置—— 测试证据:唯一红项 沙盒验证: — Qwen Code · qwen3.8-max Reviewed at |
|
Confidence: 4/5 — clean, minimal, well-targeted change; the last point is CI's, not the code's: the main Node suite is still in flight at review time, and the wire-level subprocess test only runs in the merge queue. Stepping back: this is what a good fix for #8513 looks like. My independent take before reading the diff was "emit the standard frame where the private usage already flows, guard replay and subagent rounds" — and that is exactly what landed, in 35 production lines, reusing the file's existing live-round invariant and the bridge's already-registered update type. Nothing simpler comes to mind, and nothing in the diff is beside the point: tests at three layers (unit, bridge, full-bundle subprocess) and one doc line. The problem is genuinely observed — JetBrains AI Assistant shows no context indicator while peer agents do — and the semantics chosen are the right ones: per-round occupancy rather than a cumulative sum, fallback only when prompt tokens are absent, no frame when the window is unresolved, and replay/subagent rounds kept away from the parent session's indicator. The private Qwen frame is untouched, so existing consumers keep working. Reservations, named plainly: the full-bundle wire assertion exists but is merge-queue-only CI-wise, so the author's local macOS run and the in-process unit coverage are the pre-merge evidence; a Verdict: approve. The main suite is still running, so approval is deferred until CI lands green on this commit; if anything comes back red, the deferred approval is withheld and this status comment will say so. 中文说明回顾全局:这正是 #8513 该有的修复形态。我在看 diff 之前的独立想法是"在私有用量既有的流动位置发出标准帧,并挡住回放与子 agent 轮次"——最终落地的正是这个方案:35 行生产代码,复用了文件里既有的实时轮次不变量和 bridge 早已注册的更新类型。想不出更简的做法,diff 里也没有任何偏离主题的内容:三层测试(单元、bridge、完整打包子进程)加一行文档。 问题是真实观测到的——JetBrains AI Assistant 不显示上下文指示器而同类 agent 可以——语义选择也是对的:按轮次的占用量而非累计值,仅在缺少 prompt token 时回退,窗口未解析时不发帧,回放/子 agent 轮次不碰父会话指示器,私有 Qwen 帧原样保留,现有消费者不受影响。 保留意见如实说:完整打包的线级断言虽然存在,但 CI 上只在合并队列运行,因此合并前的证据是作者的 macOS 本地运行加进程内单测;如果维护者希望合并前补齐,可以触发 结论:approve。主套件仍在运行,因此批准推迟到该提交的 CI 全绿后自动执行;若有红项,推迟的批准将被收回,并在本状态评论中说明。 — Qwen Code · qwen3.8-max Reviewed at |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship — CI landed green after the review. ✅
|
Qwen Code review timed out. Qwen review timed out after 10800 seconds (of the 180-minute budget). For large PRs, retry with a longer timeout by commenting: |
|
@qwen-code /takeover |
|
🤝 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 循环现在管理此 PR —— 将持续处理新的评审反馈与 base 冲突,直到移除标签或达到轮次上限。本 PR 来自 fork,首轮处理将由下一次定时扫描执行(通常几分钟内)。移除 |
|
🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下: Autofix review round: no action needed. The new feedback since the last evaluation contains no actionable findings:
Current state: latest review is 中文说明Autofix 审查轮次:无需处理。 自上次评估以来的新反馈不包含任何可执行的发现:
当前状态:最新审查为 Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。 🧠 Handled by Qwen Code · model/模型 |
|
@qwen-code /verify |
|
Sandboxed verification: The verification ran, but its result artifact could not be retrieved for publishing, so there is nothing to report here. The run log still has the agent output; re-run 中文 — 判定:
|
|
🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下: Autofix review round: no action needed. The new feedback since the last evaluation (2026-08-04T13:33:26Z) contains no actionable findings, so no code changes were made this round:
The PR remains at commit 中文说明Autofix 审查轮次:无需处理。 自上次评估(2026-08-04T13:33:26Z)以来的新反馈不包含任何可执行的发现,因此本轮未做任何代码改动:
PR 保持在分支 Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。 🧠 Handled by Qwen Code · model/模型 |
Maintainer verification recovery: merge-readyThe sandboxed verification itself completed successfully at PR head 中文摘要维护者结论:可以合并(
Load-bearing A/B evidenceThe mock-free harness ran a real bundled ACP subprocess over stdio JSON-RPC (
This proves the central behavior is load-bearing: the standard frame flips from 0 to 1 while the pre-existing private frame remains unchanged. The fake-server request count is a positive control that both arms actually executed a model round. Additional verification
Scope not covered
Artifact publication incidentThe automated comment incorrectly ended as “results unavailable” even though the agent verdict was Merge recommendation: merge-ready. The only PR-level follow-up is optional test coverage for the negative |
Deep verification verdict: MERGE-READY — 16/16 scripted assertions passedVerified PR head: 中文验证摘要结论:可合并。 16/16 个脚本断言通过,未发现阻断性或建议级问题。 中央 A/B 结论见下方 Central claim and load-bearing A/B:同一份真实 bundle ACP 子进程探针在 base 上精确失败于“标准 未覆盖:JetBrains UI 的实际渲染、Windows/Linux 本地执行。本机无容器运行时,因此本地验证采用静态安全审计后在三个独立 worktree 中执行。官方沙箱 Central claim and load-bearing A/BClaim: after each live main-session model round, the bundled ACP server emits the standard The same PR-head integration probe was run against three independently installed and built trees. It launches the real bundled
This is load-bearing evidence rather than a test-only comparison: the base bundle completed the same model round and satisfied the private usage checks, then failed specifically because no standard frame existed. The PR-head and latest-main integration bundles passed the identical probe. Boundary and downstream verification
An additional scratch-only adversarial test exercised 15 cases on both repaired arms:
Both arms passed 15/15. Downstream consumers audited
FindingsNone. I found no correctness, compatibility, security, or performance issue attributable to this PR. The full ACP suites emitted existing Not covered
MethodologyThe host had no Docker, Colima, or Podman runtime. Before executing PR code, I audited the complete five-file diff and confirmed it changes no manifest, lockfile, install script, arbitrary command execution, or external network path. I then created three isolated worktrees, ran an independent Screenshot evidenceReal bundled ACP A/B — base fails specifically on the missing standard frame; PR head and latest-main integration pass: Emitter, bridge, and typecheck gates — 8/8: Full ACP regressions and static audit — 3/3: Adversarial numeric boundaries and send failure ordering — both repaired arms pass: |
|
| Cell | Commit | Observable oracle | Result |
|---|---|---|---|
| PR head | 26d2adfd6f254f76b2cd288a70680d39011e240f |
Receives { sessionUpdate: "usage_update", used: 321, size: 128000 }; private usage remains present and agrees on input tokens |
PASS |
| Base control | 5173052e37d4244c0c35b0ab1d5a0843a26f60cb |
The identical head oracle fails because zero standard usage_update frames are observed |
EXPECTED FAIL (expected 0 to be greater than 0) |
The wrapper asserted 4/4 expected A/B outcomes. Evidence: 01-real-acp-base-vs-head.png.
Findings
Medium — standard usage_update becomes a persistent daemon debug transcript block
Reproduction:
cd packages/sdk-typescript
../../node_modules/.bin/vitest run test/unit/usage-update-debug.verify.test.ts --retry=0The real SDK normalizeDaemonEvent() maps the unrecognized ACP session-update kind through its default branch to a debug event. reduceDaemonTranscriptEvents() then appends a persistent kind: 'debug' transcript block containing the raw usage JSON. The dedicated reproduction passed 1/1.
Relevant flow:
- ACP bridge publishes the new standard frame unchanged.
packages/sdk-typescript/src/daemon/ui/normalizer.tshas nousage_updatecase, so the default branch createsdebug.packages/sdk-typescript/src/daemon/ui/transcript.tsappendsdebugevents to transcript state.
This means each live model round can add a debug record to daemon transcript consumers; tool loops can add more than one per user turn. The ACP protocol change is valid, but downstream daemon normalization must either consume usage_update as structured session state or explicitly drop it from transcript projection.
Evidence: 04-daemon-usage-update-debug-transcript.png.
Medium — fallback/override usage can be paired with the wrong context-window size
Reproduction/dataflow assertion:
node check-fallback-context-size.mjsThe fallback resolver obtains a distinct contentGeneratorConfig, but only its modalities are propagated. The MODEL_FALLBACK event carries fromModel, toModel, status, and index — not the fallback context-window size. After the fallback stream completes, Session passes only usageMetadata and elapsed duration to MessageEmitter. MessageEmitter then reads:
this.ctx.config.getContentGeneratorConfig()?.contextWindowSizewhich is the main session model configuration, not necessarily the model that produced the usage. The same mismatch risk exists for continuation paths using an independent model override.
Concrete impact: if a 128K primary model falls back to a 1M model and the fallback reports promptTokenCount: 100000, the emitted standard frame can be { used: 100000, size: 128000 } (~78%) instead of using the fallback model's 1M window (~10%).
The dataflow harness observed 1 present prerequisite and 3 missing links: fallback event size, Session propagation, and emitter resolution from the producing model.
Evidence: 05-fallback-context-window-dataflow.png.
Additional verification
| Gate | Result |
|---|---|
| Head/base full repository build logs | Completed through the final workspace on both arms; the outer commands returned 0, but explicit exit-code files were not retained |
| Head/base bundle generation | PASS / PASS |
MessageEmitter.test.ts |
28/28 passed |
| ACP bridge targeted forwarding test | 1/1 passed |
| Adversarial usage/context-window boundary matrix | 15/15 passed |
packages/cli typecheck |
PASS |
packages/acp-bridge typecheck |
PASS |
| Effective diff whitespace check | PASS |
| Artifact consistency summary checks | 22/22 passed |
The boundary matrix covered zero, negative, fractional, non-finite, unsafe-integer, and missing values; prompt-token precedence; total-token fallback; and durationMs = 0 as a live round. Evidence: 02-usage-update-boundary-matrix.png.
The 22/22 summary is an artifact-consistency check over retained logs, OIDs, expected A/B outcomes, and evidence files; it is not 22 independent behavioral scenarios. It includes successful reproduction of both findings. Evidence: 03-verification-gates-summary.png.
Not covered
- JetBrains AI Assistant's final UI rendering. This round verifies the ACP wire behavior and bridge path, not the IDE presentation layer.
- Windows and Linux execution.
- A real external model provider. The bundled ACP child was real, but token usage came from a deterministic loopback OpenAI-compatible fixture.
- A full provider-level forced-fallback E2E. The fallback finding is proven by production dataflow inspection plus an executable source-contract harness, not by running a live provider fallback.
- The complete repository test suite.
- Cost reporting and replay-time restoration of a historical context indicator, outside this PR's stated behavior.
Methodology
PR metadata was resolved explicitly from QwenLM/qwen-code; the exact base/head OIDs were checked out as detached worktrees. Dependencies were installed offline with lifecycle scripts disabled, followed only by the repository-declared patch-package postinstall. PR code ran under macOS sandbox-exec with an environment rebuilt from env -i and an isolated verification HOME; the file policy allowed system runtime files, public repository/worktree data, npm artifacts, and temporary output while denying access to host credential directories by default. Network access was not sandbox-restricted to loopback, but every executed model request was configured to the deterministic 127.0.0.1 fixture and the environment contained no host proxy or credential variables. Dependency lockfiles were identical, and workspace links resolved inside each respective worktree. Raw harnesses, logs, assertions.json, verdict.txt, and PNG evidence are retained in the local verification artifact directory.
Local verification artifacts were produced from exact detached base/head worktrees; this comment is advisory and does not approve or merge the PR.
|
Released in v0.21.8. |









What this PR does
This PR emits the standard ACP
usage_updatenotification after each live main-session model round.usedreports the latest prompt context occupancy (falling back to the provider's total token count when prompt usage is absent), whilesizecomes from the active model's configured context window.The existing Qwen-specific
agent_message_chunk._meta.usagenotification remains unchanged for current consumers. History replay, unresolved/invalid usage values, and subagent model rounds do not emit the standard frame, so they cannot overwrite the parent session's live context indicator.It also adds deterministic direct-ACP and bridge coverage and documents context usage as a JetBrains integration feature.
Why it's needed
ACP clients such as JetBrains AI Assistant render context-window occupancy from the standard
usage_updatenotification. Qwen Code previously exposed token counts only through a private_meta.usageextension, so those clients could not display the indicator even though Qwen already had the required per-round token count and context-window size.Reviewer Test Plan
How to verify
npx vitest run packages/cli/src/acp-integration/session/emitters/MessageEmitter.test.ts; all 28 tests should pass, including non-cumulative occupancy, replay/subagent exclusion, provider fallback, and unresolved-model handling.npx vitest run packages/cli/src/acp-integration; all 1,216 ACP tests should pass.npm test --workspace @qwen-code/acp-bridge -- --run src/bridge.test.ts -t "publishes ACP usage updates"; the standard payload should be forwarded unchanged.npm run build && npm run bundle, thenenv QWEN_SANDBOX=false npx vitest run --root ./integration-tests cli/acp-integration.test.ts -t "receives private usage metadata and standard ACP usage updates". This starts the bundleddist/cli.js --acpchild, a local OpenAI-compatible HTTP server, and a real ACP JSON-RPC session; it should receive both the private usage frame and{ "sessionUpdate": "usage_update", "used": 321, "size": 128000 }.Evidence (Before & After)
Before: the regression test observed no standard frames (
expected [] to deeply equal [...]).After: the real bundled ACP subprocess test receives
{ "sessionUpdate": "usage_update", "used": 321, "size": 128000 }, and verifies the private frame still reports the same input-token count.No screenshot is attached: this patch changes ACP wire output rather than Qwen-rendered UI, and JetBrains was unavailable in the local environment. The deterministic full-bundle subprocess assertion is the direct evidence for the client-visible protocol behavior.
Tested on
Environment (optional)
macOS 26.3.2 arm64, Node.js v22.23.2, npm 10.9.8, no sandbox, deterministic local OpenAI-compatible fixture.
Risk & Scope
usage_updateremain unaffected.Linked Issues
Fixes #8513
中文说明
本 PR 的改动
本 PR 会在主会话每一轮实时模型调用结束后发送 ACP 标准的
usage_update通知。used表示最新一轮 prompt 的上下文占用量(若提供方未返回 prompt 用量,则回退到其总 token 数),size来自当前模型配置的上下文窗口大小。现有供当前消费者使用的 Qwen 私有
agent_message_chunk._meta.usage通知保持不变。历史回放、无法解析或非法的用量值,以及子 agent 模型轮次都不会发送标准帧,因此不会覆盖父会话的实时上下文指示器。同时新增了确定性的 ACP 直连与 bridge 覆盖,并在 JetBrains 集成文档中记录上下文用量功能。
为什么需要
JetBrains AI Assistant 等 ACP 客户端通过标准
usage_update通知渲染上下文窗口占用。Qwen Code 之前只通过私有_meta.usage扩展暴露 token 数,因此即使内部已有每轮 token 数和上下文窗口大小,这些客户端也无法显示指示器。审查者测试计划
验证方法
npx vitest run packages/cli/src/acp-integration/session/emitters/MessageEmitter.test.ts;28 个测试应全部通过,其中包括非累计占用、排除历史回放/子 agent、提供方回退以及模型尚未解析时的处理。npx vitest run packages/cli/src/acp-integration;1,216 个 ACP 测试应全部通过。npm test --workspace @qwen-code/acp-bridge -- --run src/bridge.test.ts -t "publishes ACP usage updates";标准载荷应原样转发。npm run build && npm run bundle,再运行env QWEN_SANDBOX=false npx vitest run --root ./integration-tests cli/acp-integration.test.ts -t "receives private usage metadata and standard ACP usage updates"。该测试会启动打包后的dist/cli.js --acp子进程、本地 OpenAI 兼容 HTTP 服务器以及真实 ACP JSON-RPC 会话;应同时收到私有用量帧和{ "sessionUpdate": "usage_update", "used": 321, "size": 128000 }。证据(修复前与修复后)
修复前:回归测试未观察到任何标准帧(
expected [] to deeply equal [...])。修复后:真实打包 ACP 子进程测试收到
{ "sessionUpdate": "usage_update", "used": 321, "size": 128000 },并验证私有帧仍报告相同的输入 token 数。未附截图:此补丁改变的是 ACP 线协议输出,而非 Qwen 自身渲染的 UI,并且本地环境没有 JetBrains。确定性的完整打包子进程断言是客户端可见协议行为的直接证据。
测试平台
环境(可选)
macOS 26.3.2 arm64、Node.js v22.23.2、npm 10.9.8、未启用 sandbox、确定性的本地 OpenAI 兼容测试夹具。
风险与范围
usage_update的客户端不受影响。关联 Issue
Fixes #8513