feat: support session media references end-to-end - #9127
Conversation
|
Thanks for the PR! Template looks good ✓ Problem: real and clearly described, not theoretical hardening — base64 image bytes are currently duplicated across prompt JSON, the mid-turn queue, SSE events, the replay ring, and transcripts; queued/injected images lose their preview across refresh, and stale media can block a mid-turn drain. There's no linked issue, but the failure mode follows directly from how the existing mid-turn queue carries payloads, and the added design doc states the problem precisely. Direction: aligned. This fixes user-visible defects in the web-shell session experience and removes a genuine memory/wire cost, with a sensible seam: bytes stored once in the daemon, resolved to inline base64 only at the ACP boundary. CHANGELOG check: no direct upstream equivalent (Claude Code is a TUI with no web-shell media surface), but there is sustained upstream investment in image/attachment robustness, so the area is relevant. Size: touches core paths across six packages (daemon serve routes, ACP bridge, Approach: cohesive — every part serves the stated goal of a stable, replay-safe identity for uploaded images. The capability gate plus retained legacy shapes keep older clients and daemons compatible, and the two mechanical deltas I checked ( Risk: one elevated signal — Moving on to code review. 🔍 中文说明感谢贡献! 模板完整 ✓ 问题: 真实且描述清晰,不是理论性加固——base64 图片字节目前在 prompt JSON、mid-turn 队列、SSE 事件、replay ring 和 transcript 中重复传递;排队/注入的图片在刷新后丢失预览,失效媒体还可能阻塞 mid-turn drain。没有关联 issue,但该失效模式可以直接从现有 mid-turn 队列承载 payload 的方式推导出来,新增的设计文档也准确描述了问题。 方向: 对齐。修复了 web-shell 会话体验中用户可见的缺陷,并消除了真实的内存/网络开销,边界选择合理:字节只在 daemon 存储一次,仅在 ACP 边界处解析为 inline base64。CHANGELOG 检查:上游没有直接对应物(Claude Code 是 TUI,没有 web-shell 媒体面),但上游在图片/附件健壮性上有持续投入,该方向是相关的。 规模: 触及六个包的核心路径(daemon serve 路由、ACP bridge、 方案: 内聚——所有部分都服务于"为上传图片提供稳定、可安全 replay 的身份"这一目标。capability gate 加保留的旧版结构兼容旧客户端和旧 daemon;我检查过的两处机械改动( 风险: 一个升级信号—— 进入代码审查 🔍 — Qwen Code · qwen3.8-max Reviewed at |
|
🤝 Takeover engaged: the autofix loop now manages this PR — it will address new review feedback and resolve base conflicts until the label is removed or the round cap is reached. Remove the 中文说明🤝 已接管:autofix 循环现在管理此 PR —— 将持续处理新的评审反馈与 base 冲突,直到移除标签或达到轮次上限。移除 |
Code Coverage Summary
CLI Package - Full Text ReportCore Package - Full Text ReportFor detailed HTML reports, please see the 'coverage-reports-22.x-ubuntu-latest' artifact from the main CI run. |
🖼️ web-shell visual previewRendered against a mock daemon (no real backend): the PR base vs this PR head Screenshots · before / afterℹ️ No screenshot changed against the PR base — but this PR edits 9 render-shaping files:
Either the change has no visual effect (logic, plumbing, a state the scenarios never reach), or no scenario renders this UI — in which case the preview cannot see it, and an empty result is a coverage gap rather than a clean bill of health. To make it visible, add a scenario to Full-resolution recordings (.webm) are attached to the workflow run. — Qwen Code · web-shell visuals |
🩺 serve daemon A/BBuilt the PR base vs this PR head
|
| field | PR base (before) | this PR (after) |
|---|---|---|
features[] |
— | "session_media" |
— Qwen Code · serve A/B
Code reviewBefore reading the diff I sketched my own proposal from the title + motivation: a bounded session-scoped media store keyed by unguessable ids, authenticated upload/download routes, a reference wire shape gated by capability negotiation, resolution to inline base64 only at the ACP boundary, references in transcripts, SDK hydration with a bounded cache, per-message degradation, and full legacy compatibility. The PR's architecture matches this essentially point for point — which is a good sign the seams were chosen correctly rather than retrofitted. What stood out reading the implementation:
No correctness blockers found in the static review. Three non-blocking items:
I also verified the loose ends a cross-package change usually drops: every caller of the changed sequenceDiagram
participant P1 as Web Shell composer
participant P2 as SDK DaemonSessionClient
participant P3 as Daemon serve routes
participant P4 as Session bridge
participant P5 as SessionMediaStore in tmp dir
participant P6 as ACP child
P1->>P2: upload image before send
P2->>P3: POST session media bytes
P3->>P4: storeSessionMedia
P4->>P5: put bytes, mint mediaId
P5-->>P2: reference (mediaId, mime, size)
P2->>P3: prompt or mid-turn message with reference
P3->>P4: assertReferences, queue keeps reference only
P4->>P5: resolveContent at dispatch time
P5-->>P4: inline base64 for the child
P4->>P6: ACP prompt with inline bytes
P4-->>P2: echoes and snapshots carry references
P2->>P3: GET media by id, bounded LRU cache
P3-->>P2: bytes for preview and replay
Files changed (29 of 63 shown)
TestingThis is an unattended CI run — PR code is never executed here. Evidence below is the PR's own CI, read through the API at the reviewed commit. The main unit suite landed red: 2 test failures, both caused by this diff (details after the table).
The two unit-test failures are PR-caused, in files this PR does not touch:
Both look mechanical to fix (update the replay expectation, add the three routes to the catalog), but they leave the suite red on the reviewed commit. Everything else is green: 19,626 CLI tests pass, and the core, SDK, web-shell (3,543), webui, and acp-bridge suites are all green. Separately, the red The test side of the diff is otherwise unusually thorough (3,653 added test lines): every claimed behavior is pinned, including the failure paths — media removed on rejected/aborted admissions but kept on uncertain ones, orphan cleanup after session switch, per-message degradation at drain and settle, TTL reaping, close/put accounting races, unsafe data-URI refusal, and legacy capability fallbacks. The two failures above are the flip side: the diff changed replay metadata and added routes without updating the pre-existing tests that pinned the old shapes. Sandboxed verification would settle what CI cannot: 中文说明代码审查在读 diff 之前,我先根据标题和动机写了独立方案:以不可猜测 id 为键、有边界的会话级媒体存储,带鉴权的上传/下载路由,经 capability 协商的引用线格式,仅在 ACP 边界解析为 inline base64,transcript 存引用,SDK 用有界缓存做 hydration,按消息降级,完整旧版兼容。PR 的架构与这套方案基本逐点对齐——说明边界是选对了,而不是事后补的。 实现中突出的点:
静态审查未发现正确性阻塞项。三个非阻塞点:
还核对了跨包改动常见的遗漏: (流程图见上方 mermaid:上传 → 引用入队 → 分发时解析 → ACP 子进程收 inline 字节,echo/快照/replay 走引用 + 按需下载。) 测试这是无人值守 CI 运行——此处从不执行 PR 代码。以下证据来自 PR 自身 CI(通过 API 在受审 commit 上读取)。主单测套件结果为红:2 个测试失败,均由本 diff 导致(详见表格下方)。 两个单测失败由 PR 导致,且都在本 PR 未触碰的文件中:
两者修复看起来都是机械性的(更新回放断言、把三条路由加入目录),但受审 commit 上套件为红。其余全绿:CLI 19,626 个测试通过,core、SDK、web-shell(3,543)、webui、acp-bridge 套件全部通过。 另外,红色的 diff 中的测试覆盖在其他方面少见的全面(新增 3,653 行测试):每个声明的行为都有对应测试钉住,包括失败路径。上面两个失败是它的另一面:diff 修改了回放元数据并新增了路由,却没有更新钉住旧形状的既有测试。 沙箱验证可以补足 CI 无法覆盖的部分: — Qwen Code · qwen3.8-max Reviewed at |
|
Confidence: 2/5 — the review itself went well, but the PR's own CI is red on the reviewed commit, with two test failures clearly caused by the diff. Stepping back: this is a well-built PR. The architecture matches what I'd have proposed from the problem statement alone, the bounds and lifecycle are complete at every level, the genuinely hard races are handled on purpose (and documented), degradation never blocks the drain, legacy compatibility is real, and the test coverage is exceptional. If the suite were green I'd be writing a 4/5 approval with the sandboxed-lane suggestion attached. It isn't green. Two pre-existing tests fail on this commit, both broken by the diff, both in files the PR never touches:
The fixes look mechanical — update the replay expectation, add the three routes to the catalog — and the drift guard doing its job here is the system working as intended. But a red unit suite on the head commit is a hard stop: no approval this run, and no deferred approve-on-green marker either. Everything else from the review stands as non-blocking: the duplicated Requesting changes for the two test failures. Once CI is green this should be right back on the approval track. 中文说明置信度:2/5 —— review 本身顺利,但受审 commit 上 PR 自身 CI 为红,两个测试失败明确由本 diff 导致。 整体来看:这是一个构建良好的 PR。架构与我仅根据问题陈述独立提出的方案一致,各层边界与生命周期完整,真正困难的竞态被有意识地处理并附文档,降级从不阻塞 drain,旧版兼容真实有效,测试覆盖极为出色。如果套件是绿的,我会写一份 4/5 的批准并附上沙箱验证建议。 但它不是绿的。受审 commit 上两个既有测试失败,均由 diff 破坏,且都在 PR 未触碰的文件中:
修复看起来是机械性的——更新回放断言、把三条路由加入目录——漂移护栏在此生效正是机制按预期工作。但 head commit 上单测为红是硬性停止:本轮不批准,也不发出延迟批准标记。 review 中的其余意见均为非阻塞: 因两个测试失败请求修改。CI 转绿后本 PR 应回到批准轨道。 — Qwen Code · qwen3.8-max Reviewed at |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Needs two test fixes before this can merge — the suite is red on the head commit, both failures caused by this diff:
src/acp-integration/session/history-replayer.test.ts— mid-turn replay now carries thesource: 'mid_turn_message_injected'/qwenDiscreteMessage: truemeta this PR adds; the old expectation needs updating.src/serve/server/telemetry-catalog.test.ts— the route drift guard counts 54 session routes; the three new media routes need adding to the catalog.
Both look mechanical. Full notes in my review comments above — the rest of the change is in good shape. 🙏
|
🔀 Base updated: red check(s) [Test (ubuntu-latest, Node 22.x), review-pr] pass on current main — merged current main via update-branch; CI will re-run. 中文说明🔀 已更新 base:红色检查 [Test (ubuntu-latest, Node 22.x), review-pr] 在当前 main 上通过 —— 已通过 update-branch 合入当前 main,CI 将重新运行。 |
ytahdn
left a comment
There was a problem hiding this comment.
Not reviewed: test coverage (Agent 5 / test-matrix) — not launched per user request (test-related agents excluded).
Not reviewed: build-and-test (Agent 7) — not launched per user request; CI suite is red on the reviewed commit (2 failures caused by this diff, re-verified as still standing).
Not reviewed: adversarial personas 6a/6b/6c — not launched per user request (subagent cap of 10).
Not reviewed: chunk-territory agents 1-24 — replaced by 9 whole-diff dimension agents, each of which read all 24 chunks (subagent cap of 10); per-territory receipt coverage differs from the standard 3B topology.
Not reviewed: the executable-script lint — the report is stale or its diff could not be verified; re-run qwen review script-lint.
Not reviewed: the entire diff, the linked-issue fidelity pass, the whole-diff test-coverage check, the removed-behavior audit, the cross-file consistency pass, the build-and-test check — its prompt was built, but no agent on record was launched with it.
Not reviewed: reverse audit — no auditor was launched with a prompt this skill builds — the pass that hunts what the rest of the review missed ran, if at all, without the method its brief carries.
Not reviewed: verification — its prompt was built, but no agent was launched with it, so the posted findings cannot be counted as verified.
[Critical] Suite is red on the reviewed commit: packages/cli/src/acp-integration/session/history-replayer.test.ts still expects the pre-PR replay shape. This PR's mid-turn replay now carries source: 'mid_turn_message_injected' / qwenDiscreteMessage: true meta, so the old expectation fails; the diff does not touch this test file (re-verified at the reviewed commit).
[Critical] Suite is red on the reviewed commit: packages/cli/src/serve/server/telemetry-catalog.test.ts still asserts toHaveLength(54) for registered session routes, but this PR registers three new media routes (POST/GET/DELETE /session/:id/media[/:mediaId]); the diff adds the routes but does not update the catalog (re-verified at the reviewed commit).
中文说明
未审查:test coverage (Agent 5 / test-matrix) — not launched per user request (test-related agents excluded)。
未审查:build-and-test (Agent 7) — not launched per user request; CI suite is red on the reviewed commit (2 failures caused by this diff, re-verified as still standing)。
未审查:adversarial personas 6a/6b/6c — not launched per user request (subagent cap of 10)。
未审查:chunk-territory agents 1-24 — replaced by 9 whole-diff dimension agents, each of which read all 24 chunks (subagent cap of 10); per-territory receipt coverage differs from the standard 3B topology。
未审查:the executable-script lint — the report is stale or its diff could not be verified; re-run qwen review script-lint。
未审查:整个 diff、关联 issue 一致性检查、全 diff 测试覆盖检查、删除行为审计、跨文件一致性检查、构建与测试验证——它的 prompt 已构建,但没有任何 agent 有记录用它启动过。
未审查:反向审计——没有审计 agent 是用本 skill 构建的 prompt 启动的——负责搜寻评审其余部分遗漏问题的这道工序,即便运行过,也缺失了 brief 承载的方法。
未审查:验证——它的 prompt 已构建,但没有 agent 用它启动,发布的发现不能算作已验证。
[Critical] Suite is red on the reviewed commit: packages/cli/src/acp-integration/session/history-replayer.test.ts still expects the pre-PR replay shape. This PR's mid-turn replay now carries source: 'mid_turn_message_injected' / qwenDiscreteMessage: true meta, so the old expectation fails; the diff does not touch this test file (re-verified at the reviewed commit).
[Critical] Suite is red on the reviewed commit: packages/cli/src/serve/server/telemetry-catalog.test.ts still asserts toHaveLength(54) for registered session routes, but this PR registers three new media routes (POST/GET/DELETE /session/:id/media[/:mediaId]); the diff adds the routes but does not update the catalog (re-verified at the reviewed commit).
— DeepSeek/deepseek-v4-flash via Qwen Code /review (v0.21.11)
|
@qwen-code /takeover |
|
🔄 Takeover re-armed: the round counter starts a fresh window (previous rounds no longer count toward the cap); management continues. 中文说明🔄 已重新武装:轮次计数开启新窗口(此前轮次不再计入上限),托管继续。 |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
Not reviewed: chunk 15 round-1 auditor — did not read the Session.ts cron emission path to fully confirm no live emitter attaches qwenDiscreteMessage to user.image.delta parts — stopped at the agent tool budget.
Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
Not explored to full depth (tool budget reached): "You are review agent reverse-audit — Reverse audit agent…": cron-path / channel emitter check for user_message_chunk image parts with qwenDiscreteMessage (Session.ts cron handler not read) — verification that no live emi…; "You are review agent reverse-audit — Reverse audit agent…": did not read the Session.ts cron emission path to fully confirm no live emitter attaches qwenDiscreteMessage to user_message_chunk image parts (all other em…; "You are review agent reverse-audit — Reverse audit agent…": none — all checks above completed within budget.; "You are review agent reverse-audit — Reverse audit agent…": none — all checks above completed within budget.; "You are review agent reverse-audit — Reverse audit agent…": none — all checks above completed within budget., and 8 more.
Not reviewed: reverse audit — did not converge within the reverse-audit round cap of 3.
中文说明
仅完成部分审查,审查缺口已披露。
未审查:chunk 15 round-1 auditor — did not read the Session.ts cron emission path to fully confirm no live emitter attaches qwenDiscreteMessage to user.image.delta parts — stopped at the agent tool budget。
未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。
未探索到全部深度(达到工具调用预算):"You are review agent reverse-audit — Reverse audit agent…":cron-path / channel emitter check for user_message_chunk image parts with qwenDiscreteMessage (Session.ts cron handler not read) — verification that no live emi…;"You are review agent reverse-audit — Reverse audit agent…":did not read the Session.ts cron emission path to fully confirm no live emitter attaches qwenDiscreteMessage to user_message_chunk image parts (all other em…;"You are review agent reverse-audit — Reverse audit agent…":none — all checks above completed within budget.;"You are review agent reverse-audit — Reverse audit agent…":none — all checks above completed within budget.;"You are review agent reverse-audit — Reverse audit agent…":none — all checks above completed within budget.,另有 8 条。
未审查:反向审计——在 3 轮的反审轮数上限内未收敛。
— qwen3.8-max via Qwen Code /review (v0.21.11)
|
🤖 AutoFix ran out of time before finishing (timeout (1080000ms)) (attempt 1/100) — it will retry on the next scan.
See the Qwen Autofix agent step logs for model/tool output. Run log: https://github.com/QwenLM/qwen-code/actions/runs/31794317939 🧠 Handled by Qwen Code · model/模型 |
…age-injection # Conflicts: # packages/acp-bridge/src/bridge.ts # packages/webui/src/daemon/session/actions.test.ts # packages/webui/src/daemon/session/actions.ts
…a test The three new media routes (POST/GET/DELETE /session/:id/media[/:mediaId]) were registered but missing from legacySessionTelemetryRoutes, tripping the route drift guard; add them as handler_resolved like their sibling routes. The mid-turn history-replay expectation now carries the replay meta this PR adds (source: mid_turn_message_injected, qwenDiscreteMessage: true). Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
Not explored to full depth (tool budget reached): "Context: PR #9127 adds session-scoped media references…": none — all 24 assigned diff ranges read in full (no truncated reads), and every candidate above was verified against the worktree source at the reviewed commit.; chunk 8: could not execute npx vitest run src/sessionMedia.test.ts — the review worktree has no node_modules ; conclusions rest on code-tracing only.; "This PR adds session-scoped media references end-to-end:…": did not execute the TUI-resume scenario end-to-end (finding 1 rests on source-level tracing).; "This PR adds session-scoped media references end-to-end:…": did not trace the desktop qwen-agent.ts drain provider against the new items contract beyond confirming the CLI child's fallback parse (it is a provider, an…; chunk 6: could not execute vitest run src/bridgeClient.test.ts / typecheck — the review worktree has no node_modules installed ( NO_VITEST ), so verification was sta…, and 1 more.
中文说明
仅完成部分审查,审查缺口已披露。
未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。
未探索到全部深度(达到工具调用预算):"Context: PR #9127 adds session-scoped media references…":none — all 24 assigned diff ranges read in full (no truncated reads), and every candidate above was verified against the worktree source at the reviewed commit.;chunk 8:could not execute npx vitest run src/sessionMedia.test.ts — the review worktree has no node_modules ; conclusions rest on code-tracing only.;"This PR adds session-scoped media references end-to-end:…":did not execute the TUI-resume scenario end-to-end (finding 1 rests on source-level tracing).;"This PR adds session-scoped media references end-to-end:…":did not trace the desktop qwen-agent.ts drain provider against the new items contract beyond confirming the CLI child's fallback parse (it is a provider, an…;chunk 6:could not execute vitest run src/bridgeClient.test.ts / typecheck — the review worktree has no node_modules installed ( NO_VITEST ), so verification was sta…,另有 1 条。
— qwen3.8-max via Qwen Code /review (v0.21.11)
Addresses the Critical review findings on the session-media PR: - Reject image/svg+xml uploads and serve stored media with Content-Disposition: attachment and X-Content-Type-Options: nosniff (same-origin XSS vector on the daemon/Web Shell origin). - Keep the retained-media TTL sweep running when the session reaper is disabled (sessionReapIntervalMs <= 0) on the default 60s cadence. - Record only the inline bytes the media references actually cover: the gate now counts image blocks only (references are image-only), and the strip keeps unrelated inline parts (e.g. @-mentioned files). - Show '[User message with attachments]' on TUI resume for image-only mid-turn messages recorded with an empty displayText. - Exempt mid-turn injected echoes from the Web Shell status-noise and plan-JSON filters. - Degrade refresh-rebuilt queue rows to summary-only when media hydration failed, so editing cannot silently discard attachments. - Retry cross-session media removal without the clientId when the daemon rejects the stale persisted id (invalid_client_id). - Register session_media in the integration capabilities baseline. Each fix carries a regression test that fails on the pre-fix code. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
Not explored to full depth (tool budget reached): "agent reverse-audit (round 2)": verify whether the daemon HTTP enqueue route's body-size limit bounds inline (non-reference) image blocks admitted into MidTurnQueueEntry.content — bridge.ts …; "agent reverse-audit (round 2)": verify how the Web Shell renders echoed/recorded inline image blocks ( <img> vs iframe/object/data-URL DOM insertion) to decide whether the admitted SVG is act…; "agent reverse-audit (round 1)": confirm whether http-bridge mode routes through an intermediary gateway that can emit post-commit 5xx.
Not reviewed: reverse audit — did not converge within the reverse-audit round cap of 3.
中文说明
仅完成部分审查,审查缺口已披露。
未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。
未探索到全部深度(达到工具调用预算):"agent reverse-audit (round 2)":verify whether the daemon HTTP enqueue route's body-size limit bounds inline (non-reference) image blocks admitted into MidTurnQueueEntry.content — bridge.ts …;"agent reverse-audit (round 2)":verify how the Web Shell renders echoed/recorded inline image blocks ( <img> vs iframe/object/data-URL DOM insertion) to decide whether the admitted SVG is act…;"agent reverse-audit (round 1)":confirm whether http-bridge mode routes through an intermediary gateway that can emit post-commit 5xx。
未审查:反向审计——在 3 轮的反审轮数上限内未收敛。
— qwen3.8-max via Qwen Code /review (v0.21.12)
Addresses the remaining Critical review findings on the session-media PR: - Cap media content blocks at 256 on the mid-turn and prompt routes and resolve each distinct mediaId once per resolveContent call — an unbounded array of duplicate references amplified one small request into gigabytes of heap at dispatch. - Record a '[User message with attachments]' placeholder for inline-media-only mid-turn messages with no references, keeping '' only for the reference shape that replay projects. - Restore the same placeholder for image-only ordinary prompts on TUI resume instead of dropping the message from the restored history. - Treat a mid-turn injected echo as renderable when its items carry a non-empty text block, so the degraded-media echo (messages: [''] plus the placeholder text block) is not discarded as malformed. - Release session media in killSession's force-kill and closing-session fallback branches instead of degrading to the crash-path detach retention. - Remove the unreachable duplicate return in DaemonSessionClient.load(). Each fix carries a regression test that fails on the pre-fix code. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
…age-injection # Conflicts: # packages/sdk-typescript/scripts/build.js # packages/web-shell/client/components/QueuedPromptDisplay.tsx # packages/web-shell/client/hooks/useQueuedPrompts.midTurnReconcile.test.tsx # packages/web-shell/client/hooks/useQueuedPrompts.ts # packages/web-shell/client/midTurnDedup.test.ts # packages/web-shell/client/midTurnDedup.ts # packages/webui/src/daemon/session/actions.ts
- Stop deleting a session-media blob when one queued prompt / mid-turn message referencing it is removed: the store has no reference counting, so siblings, replay metadata, or other clients may still hold the same mediaId. Blobs now live until session close / TTL sweep or an explicit removeSessionMedia. - Reject duplicate mediaId occurrences in one message at assertReferences (covers the prompt and mid-turn admission paths); the serializer expands every reference at dispatch, so repeats amplified one upload into an unbounded payload even though only one read is needed. - Align the mid-turn display-text and reference-persistence gates: compute the same willPersistReferences condition before finalizing displayText, so a partially-referenced message records the attachments placeholder instead of an empty displayText with no references. - Keep the webui mid_turn_message_injected sidechannel alive for degraded image-only echoes whose items carry only the placeholder text block, mirroring the SDK normalizer's hasRenderableItemContent.
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
Not reviewed: build-and-test — 'Integration Tests (CLI, No Sandbox)' was skipped in CI and its suite (integration-tests/cli/qwen-serve-routes.test.ts, amended by this diff) did not run locally.
Not explored to full depth (tool budget reached): "agent reverse-audit (round 1)": none — all checks completed. Per instructions, I will not write "none"; I'll simply omit it.## Reverse audit — chunk 12 (round 1).
Not reviewed: "agent verify (round 2)" — pointed at diff lines it never opened: it made tool calls, but none of them read the diff.
[Critical] R6-13 (packages/acp-bridge/src/bridge.ts, promoteMidTurnMessage): the fallback admission is registered AFTER the failed prompt's result.finally releases the slot and runs maybeCloseIdleSession; on a fully detached session the deferred close authorizes teardown (closeIfChildUnheld synchronously sets activeWorkCloseInFlight) before sendFallback runs, so the fallback rejects session_closing and the accepted mid-turn message is silently dropped (stderr only). Probe-verified: the fallback never reached the child, session count went to 0. Suggested fix: reserve/re-hold the slot for the fallback before releasePromptSlot, or have the fallback re-check closing state and re-queue/surface a terminal instead of swallowing session_closing.
[Critical] R5-3 (round-5 blocker, still stands; existing thread packages/web-shell/client/hooks/useQueuedPrompts.ts): a hydration-failure placeholder degrades a refresh-rebuilt mid-turn row to payloadCompleteness:'summary-only' permanently — no reconciliation path ever upgrades it, so edit/restore refuse the row forever although the daemon still holds the media. The author's automation deferred this; it remains open at head db1ad9f.
[Critical] R5-8 (round-5 blocker, still stands; existing thread packages/acp-bridge/src/transcript-replay.ts): reference-only replay updates are dropped by non-hydrating consumers (offline transcripts / non-SDK ACP clients lose the images with no diagnostic and complete:true). Deferred by the author's automation; still open at head db1ad9f.
[Critical] R5-9 (round-5 blocker, still stands; existing thread packages/web-shell/client/hooks/useQueuedPrompts.ts): a media message promoted at the turn boundary echoes into the transcript WITHOUT its images — the media path never creates a visible queue row for the pending_prompt_started handler to find. Deferred; still open at head db1ad9f.
[Critical] R5-10 (round-5 blocker, still stands; existing thread packages/web-shell/client/hooks/useQueuedPrompts.ts): a media admission dropped by a session switch during upload/enqueue is silently lost — the catch's session-changed early return skips both the editor restore and the not-enqueued cleanup, and the pendingMidTurnAdmissionsRef entry leaks. Deferred; still open at head db1ad9f.
中文说明
仅完成部分审查,审查缺口已披露。
未审查:build-and-test — 'Integration Tests (CLI, No Sandbox)' was skipped in CI and its suite (integration-tests/cli/qwen-serve-routes.test.ts, amended by this diff) did not run locally。
未探索到全部深度(达到工具调用预算):"agent reverse-audit (round 1)":none — all checks completed. Per instructions, I will not write "none"; I'll simply omit it.## Reverse audit — chunk 12 (round 1)。
未审查:"agent verify (round 2)"——启动 prompt 为它指定了 diff 中的行,但它从未打开:有工具调用,却没有一次读取 diff。
[Critical] R6-13 (packages/acp-bridge/src/bridge.ts, promoteMidTurnMessage): the fallback admission is registered AFTER the failed prompt's result.finally releases the slot and runs maybeCloseIdleSession; on a fully detached session the deferred close authorizes teardown (closeIfChildUnheld synchronously sets activeWorkCloseInFlight) before sendFallback runs, so the fallback rejects session_closing and the accepted mid-turn message is silently dropped (stderr only). Probe-verified: the fallback never reached the child, session count went to 0. Suggested fix: reserve/re-hold the slot for the fallback before releasePromptSlot, or have the fallback re-check closing state and re-queue/surface a terminal instead of swallowing session_closing.
[Critical] R5-3 (round-5 blocker, still stands; existing thread packages/web-shell/client/hooks/useQueuedPrompts.ts): a hydration-failure placeholder degrades a refresh-rebuilt mid-turn row to payloadCompleteness:'summary-only' permanently — no reconciliation path ever upgrades it, so edit/restore refuse the row forever although the daemon still holds the media. The author's automation deferred this; it remains open at head db1ad9f.
[Critical] R5-8 (round-5 blocker, still stands; existing thread packages/acp-bridge/src/transcript-replay.ts): reference-only replay updates are dropped by non-hydrating consumers (offline transcripts / non-SDK ACP clients lose the images with no diagnostic and complete:true). Deferred by the author's automation; still open at head db1ad9f.
[Critical] R5-9 (round-5 blocker, still stands; existing thread packages/web-shell/client/hooks/useQueuedPrompts.ts): a media message promoted at the turn boundary echoes into the transcript WITHOUT its images — the media path never creates a visible queue row for the pending_prompt_started handler to find. Deferred; still open at head db1ad9f.
[Critical] R5-10 (round-5 blocker, still stands; existing thread packages/web-shell/client/hooks/useQueuedPrompts.ts): a media admission dropped by a session switch during upload/enqueue is silently lost — the catch's session-changed early return skips both the editor restore and the not-enqueued cleanup, and the pendingMidTurnAdmissionsRef entry leaks. Deferred; still open at head db1ad9f.
— qwen3.8-max via Qwen Code /review (v0.21.12)
|
🤖 Addressed the latest review feedback (round 4/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 4/100 轮)。改动内容与我反驳保留之处如下: Round-6 review feedback — address summary (PR #9127)This round implements all eight Critical findings with reproduce-first Implemented (Critical)R6-13 + R5-6 — promoted mid-turn media fallback race and reordering (
|
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
Not reviewed: build-and-test — 'Integration Tests (CLI, No Sandbox)' was skipped in CI and its suite (integration-tests/cli/qwen-serve-routes.test.ts, amended by this diff) did not run locally.
Not explored to full depth (tool budget reached): chunk 1: running the 7 new bridge tests ( npx vitest run src/bridge.test.ts -t media ) — worktree has no node_modules and a full npm install exceeded the budget; "agent reverse-audit (round 2)": none — completed the investigation..
Not reviewed: reverse audit — stopped before round 3 by the review time budget.
中文说明
仅完成部分审查,审查缺口已披露。
未审查:build-and-test — 'Integration Tests (CLI, No Sandbox)' was skipped in CI and its suite (integration-tests/cli/qwen-serve-routes.test.ts, amended by this diff) did not run locally。
未探索到全部深度(达到工具调用预算):chunk 1:running the 7 new bridge tests ( npx vitest run src/bridge.test.ts -t media ) — worktree has no node_modules and a full npm install exceeded the budget;"agent reverse-audit (round 2)":none — completed the investigation.。
未审查:反向审计——评审时间预算不足,未能开始第 3 轮。
— qwen3.8-max via Qwen Code /review (v0.21.12)
|
🤖 Addressed the latest review feedback (round 5/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 5/100 轮)。改动内容与我反驳保留之处如下: AutoFix round summary — PR #9127 (round 7)Critical-only growth mode is active for this window (test lines over budget), but every round-7 finding is attached to the CHANGES_REQUESTED review and therefore non-deferrable by the deterministic filter. This round implements the Critical plus the highest-value, most minimal suggestions (the ~8-finding batch bound), and defers the remainder with in-thread replies. Resolved in this round (9 findings, one commit)
Deferred / escalated (36 findings — in-thread replies posted, threads left open)
Conflict notes
VerificationCommands actually run this round (all after the final code state):
Not run: integration tests (not required — touched behavior is covered by the workspace suites above; the amended integration suite's capabilities test is unchanged this round). 中文说明AutoFix 轮次总结 — PR #9127(第 7 轮)本窗口已激活仅 Critical 的增长控制(测试行数超预算),但第 7 轮的全部发现都挂在 CHANGES_REQUESTED 审查上,确定性过滤器无法将其延后。本轮实现了 Critical 及价值最高、改动最小的建议(受约 8 条的批次上限约束),其余以线程内回复延后。 本轮已解决(9 条发现,一次提交)
延后 / 升级(36 条发现——已在线程内回复,线程保持开放)
冲突说明
验证本轮实际执行的命令(均为最终代码状态之后):
未执行:集成测试(非必需——改动的行为已由上述 workspace 套件覆盖;被修改的集成套件能力测试本轮未变动)。 Deferred non-Critical feedbackCritical-only mode is active: the PR's diff grew src 202 / test 1040 net lines beyond this counting window's baseline (budgets: 400/400). The workflow excluded the non-Critical feedback below from this round's actionable sections; the items remain open for human follow-up. Maintainer feedback continues to flow unaffected during a growth-only engagement (the per-author batch budget applies only after 5 change-producing rounds). ( 中文说明已进入仅处理 Critical 的模式:本计数窗口内 diff 净增长已达 源码 202 / 测试 1040 行(预算 400/400)。本轮可执行区域已排除下方非 Critical 反馈;这些条目保持开放,留待人工跟进。纯增长触发期间维护者反馈照常流动(按作者的批次预算仅在完成 5 个产生改动的轮次后生效)。(评论 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/模型 |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
Not reviewed: reverse audit — stopped before round 3 by the review time budget.
中文说明
仅完成部分审查,审查缺口已披露。
未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。
未审查:反向审计——评审时间预算不足,未能开始第 3 轮。
— qwen3.8-max via Qwen Code /review (v0.21.12)
|
🤖 Addressed the latest review feedback (round 6/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 6/100 轮)。改动内容与我反驳保留之处如下: Round 8 autofix summary — PR #9127Critical-only mode is active (growth brake engaged). This round implements the Implemented (8 Critical findings)
Also in scope: the marker literal in Deferred to the next round
VerificationCommands actually run (this round):
中文说明第 8 轮 autofix 总结 — PR #9127仅处理 Critical 的模式已激活(增长刹车已触发)。本轮按批次上限(约 8 项)实现了 8 个 Critical 修复;每个修复都附带回归测试,并已验证这些测试在本轮之前的代码上会失败。3 个 Critical 与全部 Suggestion 明确顺延到下一轮(见 已实现(8 个 Critical 发现)
同属本轮范围: 顺延到下一轮
验证本轮实际执行的命令:
Deferred non-Critical feedbackCritical-only mode is active: 5 change-producing rounds are complete and the PR's diff grew src 272 / test 1469 net lines beyond this counting window's baseline (budgets: 400/400). The workflow excluded the non-Critical feedback below from this round's actionable sections; the items remain open for human follow-up. Maintainer feedback is deferred only after its author has used 2 regular feedback batches in this window's Critical-only tail; authors at that budget, if any, are named below. ( 中文说明已进入仅处理 Critical 的模式:已完成 5 个产生改动的轮次,且本计数窗口内 diff 净增长已达 源码 272 / 测试 1469 行(预算 400/400)。本轮可执行区域已排除下方非 Critical 反馈;这些条目保持开放,留待人工跟进。维护者反馈仅在其本人于本窗口 Critical-only 阶段已使用 2 批常规反馈预算后才会延后;达到预算的作者(如有)在下方点名。(评论 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/模型 |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
Not reviewed: build-and-test — 'Integration Tests (CLI, No Sandbox)' was skipped in CI and its suite did not run locally.
Not explored to full depth (tool budget reached): chunk 23: none — no check was cut short. (I verified by code reading rather than executing the vitest suites; the worktree has no installed dependencies and every expecta….
Not reviewed: reverse audit — stopped before round 3 by the review time budget.
中文说明
仅完成部分审查,审查缺口已披露。
未审查:build-and-test — 'Integration Tests (CLI, No Sandbox)' was skipped in CI and its suite did not run locally。
未探索到全部深度(达到工具调用预算):chunk 23:none — no check was cut short. (I verified by code reading rather than executing the vitest suites; the worktree has no installed dependencies and every expecta…。
未审查:反向审计——评审时间预算不足,未能开始第 3 轮。
— qwen3.8-max via Qwen Code /review (v0.21.12)
|
🤖 Could not produce a passing fix for this feedback (round 7/100). This item now needs a human; the loop stays engaged and still picks up new feedback and base conflicts, but will not retry this item on its own. What I found before stopping: Round failure: infrastructure-blocked verification (not a code defect)This round attempted the requested Exact failing command and real outputDaemon log breadcrumb during each failure: Root cause (evidence-backed)The live-conversation root resolves from the OS home directory, not QWEN_HOME: Run log: https://github.com/QwenLM/qwen-code/actions/runs/31969012026 🧠 Handled by Qwen Code · model/模型 |
|
🤖 Could not produce a passing fix for this feedback (round 8/100). This item now needs a human; the loop stays engaged and still picks up new feedback and base conflicts, but will not retry this item on its own. What I found before stopping: See the Qwen Autofix agent step logs for model/tool output. Run log: https://github.com/QwenLM/qwen-code/actions/runs/31969622445 🧠 Handled by Qwen Code · model/模型 |
|
🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下: 🛑 Maintainer decision needed: this PR's diff is not convergingNo code changes were made this round. This round's feedback contains zero
The decision a maintainer needs to make — how to land or restructure this PR:
Base conflict, deliberately not touched this round: the branch currently To resume automated rounds after deciding, comment 中文说明🛑 需要维护者决策:本 PR 的 diff 已无法收敛本轮未做任何代码改动。 本轮反馈中没有任何可执行的条目——没有 review、没有行内评论、没有 issue 级评论、没有失败或持续失败的检查;被延后的非 Critical 部分仅是审计记录,且未列出任何条目。唯一的条目是工作流自身的增长刹车,其规则禁止本轮继续做代码修复:
需要维护者做出的决策——本 PR 如何落地或重构:
基分支冲突,本轮刻意未处理: 当前分支与 决策后如需恢复自动化轮次,请评论 Deferred non-Critical feedbackCritical-only mode is active: 5 change-producing rounds are complete and the PR's diff grew src 485 / test 2098 net lines beyond this counting window's baseline (budgets: 400/400). The workflow excluded the non-Critical feedback below from this round's actionable sections; the items remain open for human follow-up. Maintainer feedback is deferred only after its author has used 2 regular feedback batches in this window's Critical-only tail; authors at that budget, if any, are named below. ( 中文说明已进入仅处理 Critical 的模式:已完成 5 个产生改动的轮次,且本计数窗口内 diff 净增长已达 源码 485 / 测试 2098 行(预算 400/400)。本轮可执行区域已排除下方非 Critical 反馈;这些条目保持开放,留待人工跟进。维护者反馈仅在其本人于本窗口 Critical-only 阶段已使用 2 批常规反馈预算后才会延后;达到预算的作者(如有)在下方点名。(评论 Base-conflict check · 基分支冲突检查: conflicts with main (no review fix needed, but a rebase/merge is required before merge). · 与 main 有冲突(无需评审修复,但合并前需 rebase/merge)。 🧠 Handled by Qwen Code · model/模型 |
…age-injection # Conflicts: # packages/cli/src/serve/routes/session.ts # packages/cli/src/serve/server/telemetry.test.ts
|
🤖 Addressed the latest review feedback (round 9/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 9/100 轮)。改动内容与我反驳保留之处如下: 🤖 Resolved the base conflict this round — no review findings were actionable. Details below. · 本轮解决了基分支冲突——没有可执行的评审条目。详情如下: What happened this roundThis round's feedback contains zero actionable findings — no reviews, no The workflow's growth brake also fired: the PR's diff has stayed over the The one requested action was base-conflict resolution ( Conflict resolutions (2 files, 4 hunks)
Semantic merge repair (1 test, no production change)
Both shipped behaviors are intentional and coexist: a structured refusal Verification
Still openThe growth-brake decision remains with the maintainer (see the handoff): 中文说明本轮做了什么本轮反馈中没有任何可执行的条目——没有 review、没有行内评论、没有 issue 级评论、没有失败或持续失败的检查。被延后的非 Critical 部分仅是审计记录且未列出任何条目,因此按 Critical-only 模式,其中内容一律未触碰(不改代码、不解决线程、不回复)。 工作流的增长刹车也已触发:本 PR 的 diff 在本计数窗口内已连续 5 轮以上超出预算且仍未收缩。该条目属于维护者决策,而非 bot 的工作——详见单独的交接说明。本轮没有做任何由评审发现驱动的代码修复。 本轮唯一被请求的操作是解决基分支冲突( 冲突解决(2 个文件、4 处冲突块)
语义合并修复(1 个测试,无生产代码改动)
两个已上线的行为都是有意设计且可以共存:结构化拒绝保留通道(main 的保证),无应答的卡死仍会强制杀并立即释放 media(本 PR 的保证)。测试现改为模拟真正的卡死——子进程对关闭通知永不应答,关闭请求超时(非明确拒绝),走强制杀回退路径。已验证:更新后的测试通过,main 的「spares the channel when a kill meets a definitive close refusal」测试仍通过,acp-bridge 全套测试为绿。 验证
仍然开放的事项增长刹车的决策仍待维护者定夺(见交接说明):接受当前状态并将尾部条目延后、拆分 PR、或重新设计方案。上一轮交接中提到的基分支冲突已由本轮的合并提交解决。 Deferred non-Critical feedbackCritical-only mode is active: 5 change-producing rounds are complete and the PR's diff grew src 485 / test 2098 net lines beyond this counting window's baseline (budgets: 400/400). The workflow excluded the non-Critical feedback below from this round's actionable sections; the items remain open for human follow-up. Maintainer feedback is deferred only after its author has used 2 regular feedback batches in this window's Critical-only tail; authors at that budget, if any, are named below. ( 中文说明已进入仅处理 Critical 的模式:已完成 5 个产生改动的轮次,且本计数窗口内 diff 净增长已达 源码 485 / 测试 2098 行(预算 400/400)。本轮可执行区域已排除下方非 Critical 反馈;这些条目保持开放,留待人工跟进。维护者反馈仅在其本人于本窗口 Critical-only 阶段已使用 2 批常规反馈预算后才会延后;达到预算的作者(如有)在下方点名。(评论 Base-conflict check · 基分支冲突检查: conflicted with main — resolved in this push. · 与 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/模型 |
|
👋 Takeover released: the autofix loop will no longer engage this PR (an in-flight round, if any, completes its bounded work). Re-apply 中文说明👋 已释放:autofix 循环不再介入此 PR(在飞的一轮如有,将完成其有界工作)。重新打上 |
da3417f to
82778c8
Compare
|
Please do not rebase or force-push to an active PR as it invalidates existing review comments. Note for future reference, the bots always squash all changes into a single commit automatically as part of the integration. 中文请勿对活跃的 PR 执行 rebase 或 force-push,因为这会使已有的评审评论失效。另外,供日后参考:作为集成流程的一部分,机器人始终会自动将所有改动压缩(squash)为单个提交。 |
82778c8 to
6586d11
Compare
|
Superseded by #9310, which starts from the same reviewed commit and keeps the discussion clean. Please continue review on the replacement PR. |
* feat: support session media references end-to-end * fix(cli): catalog session media routes and update mid-turn replay meta test The three new media routes (POST/GET/DELETE /session/:id/media[/:mediaId]) were registered but missing from legacySessionTelemetryRoutes, tripping the route drift guard; add them as handler_resolved like their sibling routes. The mid-turn history-replay expectation now carries the replay meta this PR adds (source: mid_turn_message_injected, qwenDiscreteMessage: true). Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(daemon): harden session media per review feedback Addresses the Critical review findings on the session-media PR: - Reject image/svg+xml uploads and serve stored media with Content-Disposition: attachment and X-Content-Type-Options: nosniff (same-origin XSS vector on the daemon/Web Shell origin). - Keep the retained-media TTL sweep running when the session reaper is disabled (sessionReapIntervalMs <= 0) on the default 60s cadence. - Record only the inline bytes the media references actually cover: the gate now counts image blocks only (references are image-only), and the strip keeps unrelated inline parts (e.g. @-mentioned files). - Show '[User message with attachments]' on TUI resume for image-only mid-turn messages recorded with an empty displayText. - Exempt mid-turn injected echoes from the Web Shell status-noise and plan-JSON filters. - Degrade refresh-rebuilt queue rows to summary-only when media hydration failed, so editing cannot silently discard attachments. - Retry cross-session media removal without the clientId when the daemon rejects the stale persisted id (invalid_client_id). - Register session_media in the integration capabilities baseline. Each fix carries a regression test that fails on the pre-fix code. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(daemon): bound media content and fix resume/echo edge cases Addresses the remaining Critical review findings on the session-media PR: - Cap media content blocks at 256 on the mid-turn and prompt routes and resolve each distinct mediaId once per resolveContent call — an unbounded array of duplicate references amplified one small request into gigabytes of heap at dispatch. - Record a '[User message with attachments]' placeholder for inline-media-only mid-turn messages with no references, keeping '' only for the reference shape that replay projects. - Restore the same placeholder for image-only ordinary prompts on TUI resume instead of dropping the message from the restored history. - Treat a mid-turn injected echo as renderable when its items carry a non-empty text block, so the degraded-media echo (messages: [''] plus the placeholder text block) is not discarded as malformed. - Release session media in killSession's force-kill and closing-session fallback branches instead of degrading to the crash-path detach retention. - Remove the unreachable duplicate return in DaemonSessionClient.load(). Each fix carries a regression test that fails on the pre-fix code. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(daemon): resolve round-4 session media findings Addresses the round-4 Critical review findings on the session-media PR: - Move the mid-turn media-reference validation below the idempotent retry-ack rings so a same-id retry whose media was already removed (delete racing an in-flight POST, or a refresh re-enqueueing from the snapshot) settles idempotently instead of failing with session_media_gone (410). - Keep image/* (unknown mime type) prompt images inline instead of uploading them: the media route matches concrete image types only, so the upload POST 400s and the whole submission hard-failed, regressing pre-upload behavior for untyped images. - Project the degraded-media drain echo's placeholder text block when the echo text is empty, so the Web Shell shows the unavailability notice instead of rendering an empty bubble. Each fix carries a regression test that fails on the pre-fix code. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(daemon): resolve round-5 session-media critical findings (QwenLM#9127) - Stop deleting a session-media blob when one queued prompt / mid-turn message referencing it is removed: the store has no reference counting, so siblings, replay metadata, or other clients may still hold the same mediaId. Blobs now live until session close / TTL sweep or an explicit removeSessionMedia. - Reject duplicate mediaId occurrences in one message at assertReferences (covers the prompt and mid-turn admission paths); the serializer expands every reference at dispatch, so repeats amplified one upload into an unbounded payload even though only one read is needed. - Align the mid-turn display-text and reference-persistence gates: compute the same willPersistReferences condition before finalizing displayText, so a partially-referenced message records the attachments placeholder instead of an empty displayText with no references. - Keep the webui mid_turn_message_injected sidechannel alive for degraded image-only echoes whose items carry only the placeholder text block, mirroring the SDK normalizer's hasRenderableItemContent. * fix(daemon): resolve round-6 session-media critical findings (QwenLM#9127) * fix(daemon): resolve round-7 session-media critical findings (QwenLM#9127) * fix(daemon): resolve round-8 session-media critical findings (QwenLM#9127) * fix(daemon): correct session media recovery and queue isolation * fix(daemon): remove media with deleted queue items * fix(daemon): bound repeated media in queue drains --------- Co-authored-by: 钉萁 <dingqi.jww@alibaba-inc.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
What this PR does
This PR adds session-scoped media references across the daemon, ACP bridge, TypeScript SDK, and Web Shell. Images are uploaded once and represented by a media ID plus metadata while they move through prompt submission, mid-turn queues, injected-message echoes, reconciliation snapshots, transcripts, and replay. The bridge resolves references to inline bytes only when dispatching content to the ACP agent.
Queued and injected image messages now preserve their previews across refresh, remain separate messages when drained together, and reconcile consistently when a running turn settles. The implementation keeps legacy inline-image and pending-prompt clients compatible, bounds daemon storage and SDK hydration caches, and degrades an unavailable image without blocking later queued messages. Deleting a queued image message also deletes its uploaded media; reusing one media ID across multiple messages is intentionally unsupported.
Why it's needed
Passing base64 image data through JSON, SSE, queue snapshots, and transcript replay duplicates large payloads in memory and over the wire. It also made image state fragile: queued or user-sent images could disappear after refresh, injected image echoes could differ from normal user messages, and stale media could block an entire mid-turn drain. Session media references provide one bounded storage location and a stable replay-safe identity for each uploaded image.
Reviewer Test Plan
How to verify
Evidence (Before & After)
Before: base64 image bytes were repeated across transport and replay paths; queued images could disappear after refresh, and multiple drained messages could temporarily render as one combined message.
After: transport and replay use bounded session media IDs, queued messages remain distinct, image previews survive refresh, and unavailable media is isolated to the affected message.
Tested on
Environment (optional)
Node.js 22; targeted Vitest suites (2,571 tests), full workspace build, and full workspace typecheck.
Risk & Scope
Linked Issues
N/A
中文说明
本 PR 的改动
本 PR 在 daemon、ACP bridge、TypeScript SDK 和 Web Shell 中加入会话级媒体引用。图片只上传一次,在 prompt 发送、mid-turn 队列、插入消息回显、reconciliation snapshot、transcript 和 replay 中以 media ID 与元数据传递;仅在向 ACP agent 分发内容时由 bridge 解析为 inline bytes。
带图片的排队消息和插入消息现在可以在刷新后保留预览,多条消息一起 drain 时仍保持独立,并在运行中的 turn settle 时保持一致的 reconciliation 行为。实现同时兼容旧版 inline 图片和 pending-prompt 客户端,对 daemon 存储与 SDK hydration cache 设置边界,并确保单张不可用图片不会阻塞后续排队消息。删除带图片的排队消息时会同时删除其已上传媒体;本功能明确不支持在多条消息之间复用同一个 media ID。
为什么需要
在 JSON、SSE、队列快照和 transcript replay 中传递 base64 图片会在内存和网络中重复大体积数据,也会让图片状态变得脆弱:排队或用户发送的图片可能在刷新后消失,插入图片的回显可能与普通用户消息不一致,失效媒体还可能阻塞整个 mid-turn drain。会话媒体引用为每张上传图片提供单一、有边界且可安全 replay 的稳定身份。
Reviewer 测试计划
验证方式
证据(改动前后)
改动前:base64 图片数据会在 transport 与 replay 路径中重复;排队图片可能在刷新后消失,多条 drain 消息也可能暂时显示成一条合并消息。
改动后:transport 与 replay 使用有边界的 session media ID,排队消息保持独立,图片预览可在刷新后恢复,不可用媒体只影响对应消息。
测试平台
环境(可选)
Node.js 22;定向 Vitest 测试共 2,571 个、全 workspace build、全 workspace typecheck。
风险与范围
关联 Issue
无