fix(web-shell): localize vision bridge notices - #10003
Conversation
|
✅ Qwen Triage finished — CI landed green on ✅ Qwen Triage 已完成 —— |
|
Thanks for the PR! Template looks good ✓ — every required heading is present, including the bilingual section. Problem: real, and I confirmed it in code rather than taking the description at face value. The daemon builds the notice with a hardcoded English-only formatter in the vision-bridge service, while Web Shell ships a zh-CN catalog — so any Chinese session that triggers a bridge attempt shows English by construction. That's an observed gap, not a theoretical one. Two caveats: there's no linked issue and the Before/After section is prose rather than a screenshot or tmux capture, so the visual before/after is the author's claim, not yet independent evidence. I'll lean on a sandboxed lane below to close that. Direction: aligned. Web Shell is an actively-invested surface — the CHANGELOG has a standing "Web Shell Experience" section (#9303, #9632, #9640, #9672, and more) — and the project is explicitly bilingual, so localizing a disclosure notice fits rather than pulls focus. Keeping the English text as the ACP/Channel fallback (so the "your image was sent to another model" disclosure is never lost) is the right call. Size: this spans three packages (channels, cli, web-shell), which puts it under the core-infrastructure confidence bar, so here's the breakdown — production logic ≈ 143 lines, tests ≈ 328 lines, design doc ≈ 23 lines. Well under any size threshold; the breadth is intrinsic to making a message discrete across its consumers, not scope creep. Approach: sound. The structured Risk: Stage 1e matched — the CLI edits sit under Moving on to code review. 🔍 中文说明感谢贡献! 模板完整 ✓ —— 所有必需小节齐全,包含中文说明。 问题:真实存在,且我通过读代码确认,而非仅采信描述。daemon 端用视觉桥接服务里一个硬编码的纯英文 formatter 生成通知,而 Web Shell 自带 zh-CN 语言目录——因此任何触发桥接的中文会话必然显示英文。这是已观测的缺口,不是理论问题。两点保留:未关联 issue,且 Before/After 部分是文字叙述而非截图或 tmux 抓取,所以视觉上的前后对比目前只是作者陈述,尚非独立证据。我会用下面的沙箱验证通道来补齐。 方向:对齐。Web Shell 是持续投入的界面——CHANGELOG 有固定的 "Web Shell Experience" 小节(#9303、#9632、#9640、#9672 等)——且项目本身是双语的,本地化一条披露通知是契合而非分散焦点。将英文文本保留为 ACP/Channel 回退(确保"图片已发送至其他模型"的披露永不丢失)是正确取舍。 规模:横跨三个包(channels、cli、web-shell),因此纳入核心基础设施的信心门槛,行数拆分如下——生产逻辑约 143 行、测试约 328 行、设计文档约 23 行。远低于任何规模阈值;广度来自"让一条消息在多个消费者间保持离散"的固有需求,不是范围蔓延。 方案:合理。结构化 风险:Stage 1e 命中——CLI 改动位于 进入代码审查 🔍 — Qwen Code · qwen3.8-max Reviewed at |
|
Code review — I read the base code around every touch point rather than the diff in isolation, and I grepped every consumer of
sequenceDiagram
participant P1 as Session (cli)
participant P2 as MessageEmitter (cli)
participant P3 as ACP session update
participant P4 as Web Shell SystemMessage
participant P5 as Channel Bridge
participant P6 as Rewrite Middleware
P1->>P2: emitVisionBridgeNotice(text, result)
P2->>P3: agent_message_chunk with English fallback text plus metadata
P3->>P4: project as discrete system message
P4-->>P4: localize from metadata, else fall back to English
P3->>P5: discrete notice
P5-->>P5: forward English text as textChunk
P3->>P6: discrete notice
P6-->>P6: skip rewrite, pass through unchanged
Test evidence — this is the PR's own CI read via the API (I do not run PR code here). As of this pass there are no failures: everything that has completed is green. The main unit suite and the web-shell visual capture are still in flight, and I'm not polling them. One observation: the macOS and Windows unit suites were skipped on this run, so ubuntu is the unit-test signal for now. Final CI results for
One row per check name (latest run); skipped checks omitted; failures sort first. / 每个检查名一行(取最新一次运行),省略 skipped,失败项排在最前。 Sandboxed verification would settle the one claim CI can't: Real-scenario (tmux) testing: N/A for this unattended CI run — live-behavior signal comes from the sandboxed lane above, not from driving the product here. 中文说明代码审查——我没有孤立地读 diff,而是读了每个改动点周围的基线代码,并 grep 了所有
(时序图见上,中文概述:daemon 端 Session 经 MessageEmitter 发出"英文回退文本 + 结构化元数据"的 ACP 更新;Web Shell 投影为离散系统消息并按元数据本地化、异常则回退英文;通道桥把英文作为 textChunk 转发;改写中间件跳过不改写。) 测试证据——以上是通过 API 读取的 PR 自身 CI(我不会在此运行 PR 代码)。截至本次无失败:所有已完成的检查均为绿色。主单元测试套件与 web-shell 视觉抓取仍在进行,我不会轮询。一点观察:本次 macOS 与 Windows 的单元测试套件被跳过,因此目前 ubuntu 是单元测试信号。 沙箱验证可以敲定 CI 无法敲定的一点: 真实场景(tmux)测试:本次无人值守 CI 运行为 N/A——实时行为信号来自上面的沙箱通道,而非在此驱动产品。 — Qwen Code · qwen3.8-max Reviewed at |
🖼️ web-shell visual previewRendered against a mock daemon (no real backend): the PR base vs this PR head Screenshots · before / afterFull-resolution recordings (.webm) are attached to the workflow run. — Qwen Code · web-shell visuals |
|
Confidence: 4/5 — a correctly-scoped fix whose least obvious edit (the channel-bridge Stepping back: this matches the approach I'd have proposed before reading the diff — keep a stable English ACP fallback, attach structured metadata, and let Web Shell localize from that metadata, because the daemon can't know the client's chosen language. The breadth across three packages is forced, not scope creep: making a message discrete means every consumer of Every change is necessary and test-backed, the defensive fallback for malformed metadata is the right failure mode for a privacy disclosure, and CI is green so far with no failures. The two soft items are the VSCode IDE companion surface (behavior changes from streamed-chunk to discrete-persisted; looks benign, but the PR doesn't enumerate it) and a duplicated model-id regex that could be shared by exporting the core helper. Neither blocks. Because the main unit suite and the web-shell visual capture are still in flight, I'm not approving this instant — approval is deferred until CI lands green on 中文说明信心:4/5 —— 范围恰当的修复,其最不起眼的一处改动(通道桥的 退一步看:这与我在读 diff 之前会提出的方案一致——保留稳定的英文 ACP 回退、附加结构化元数据、由 Web Shell 依据元数据本地化,因为 daemon 无法得知客户端所选语言。横跨三个包的广度是被迫的,而非范围蔓延:让一条消息保持离散,意味着每个 每处改动都是必要且有测试支撑的;对畸形元数据的防御性回退,对于一条隐私披露而言是正确的失败模式;CI 目前全绿、无失败。两个偏软的问题:VSCode IDE 伴侣界面(行为从流式块变为离散持久化;看起来无害,但 PR 未将其列入范围),以及一个可通过导出 core helper 来共享的重复模型名正则。二者均不阻塞。 由于主单元测试套件与 web-shell 视觉抓取仍在进行,我不在此刻批准——批准将推迟到 CI 在该提交上转绿,届时收尾步骤会发布绑定该提交的批准;若有检查转红或提交移动,则会改为暂缓。 — Qwen Code · qwen3.8-max Reviewed at |
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. |
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. ✅
ytahdn
left a comment
There was a problem hiding this comment.
整体评估 / Overall assessment
在 head f655db7a 上完整审了 17 个文件(+485/−9),并在 head tree 上逐条交叉验证;同时枚举了 qwenDiscreteMessage / _meta.source 的全部消费者(含 compactionEngine、SDK transcript reducer、IDE companion、first-output 基准)。结论:没有 Critical/Important。设计干净、实现与设计文档一致、兼容路径全部守住——发现 5 个 🟢 硬化项和 3 个 💡,其中两个与 bot 审查的次要观察重合(均已独立复核)。
bot 的审查就在当前 head 上,其两个非阻塞观察我逐一验证如下:通道桥的 textChunk 分支确为承重墙(基线里两个桥都会丢弃非后台通知的离散消息,不加分支英文披露会从 Channel 响应中消失);IDE companion 的行为变化确认无害(见第 4 条)。
Reviewed all 17 files (+485/−9) at head f655db7a and cross-verified every candidate finding against the head tree; also enumerated all consumers of qwenDiscreteMessage / _meta.source (compactionEngine, SDK transcript reducer, IDE companion, first-output benchmark). Verdict: no Critical/Important. The design is clean, the implementation matches the design doc, and every compatibility path holds — 5 🟢 hardening items and 3 💡 suggestions below, two of which overlap with the bot's minor observations (independently re-verified).
The bot reviewed this exact head; I verified both of its non-blocking observations: the channel-bridge textChunk branch is genuinely load-bearing (in the base code both bridges drop discrete messages that are not background-notification responses, so without the branch the English disclosure would silently disappear from Channel responses), and the IDE-companion behavior change is benign (item 4 below).
🟢 1. 空字符串 modelName/modelEndpoint 绕过了"畸形→回退"契约
packages/web-shell/client/components/messages/SystemMessage.tsx:61-69
校验的其余部分是穷尽的(status 枚举 :57-60、计数的有限/整数/非负与 egress 布尔 :73-85、模板键只由已校验枚举构造 :86),但 typeof x === 'string' 放行 '':modelName: '' 会渲染出 Vision bridge () failed / 视觉桥接()失败: 而不是回退英文。规范 daemon 只发 truthy 值所以实际不会发生,但该函数的自述职责就是拒绝畸形形状。修复:typeof x === 'string' && x.trim() !== ''。
EN: The rest of the validation is exhaustive, but typeof x === 'string' admits '' — modelName: '' renders Vision bridge () failed instead of falling back to the daemon English text. A well-formed daemon never sends empties, but the function's stated job is to reject malformed shapes. Treat empty/whitespace-only strings as absent.
🟢 2. 模型名推导正则在两个包各存一份
packages/cli/src/acp-integration/session/emitters/MessageEmitter.ts:222 vs packages/core/src/services/visionBridge/vision-bridge-service.ts:115-117(私有 displayVisionModelId)
两处逐字节相同(已核对),同一帧内英文文案与元数据同源于 bridgeResult,今天不会漂移;但 PR 自述的主要风险正是"英文兼容文案与本地化目录必须语义对齐",而 core 的 helper 是私有的。建议从 vision-bridge-service 导出 displayVisionModelId 并在 emitter 复用,把"不会漂移"变成结构性保证。
EN: The regex modelId.replace(/^[^:]+:/, '') is byte-for-byte identical to core's private displayVisionModelId and both derive from the same bridgeResult, so nothing can drift within a frame today — but the PR's own stated risk is exactly this alignment, and the core helper is private. Export displayVisionModelId and reuse it in the emitter to make non-drift structural.
🟢 3. 投影只以 source 为键,未校验 SDK 合并守卫依赖的离散不变量
packages/web-shell/client/adapters/transcriptToMessages.ts:526 vs SDK 守卫 packages/sdk-typescript/src/daemon/ui/transcript.ts:838,848
SDK reducer 只在 qwenDiscreteMessage === true 时保持块离散。若某个半坏的 daemon 只打了 source 没打标志,SDK 会把通知文本并入相邻 assistant 流,随后适配器把整个合并块投影成一条系统通知——assistant 回复被"吞进"通知。当前 daemon 永远两个字段一起打,且 background_notification 投影也是同样的宽松形状(一致性没问题);可选加固:投影时同时要求 qwenDiscreteMessage === true,或留注释说明有意宽松。
EN: Projection keys only on source, while the SDK merge guard keys on qwenDiscreteMessage === true. If a half-broken daemon stamped the source without the flag, the SDK would merge the notice into the adjacent assistant stream and the adapter would project the merged block as one system notice — swallowing the reply into the notice. Today the daemon always stamps both (and the background_notification projection is equally lenient, so it is consistent); optionally require qwenDiscreteMessage === true in the projection too, or comment the deliberate leniency.
🟢 4. IDE companion 行为变化已验证无害——建议确认 + 补一条路由测试
packages/vscode-ide-companion/src/services/qwenSessionUpdateHandler.ts:75-98
新通知携带 qwenDiscreteMessage: true 后,companion 把它从流式块改道为独立 onMessage(source=vision_bridge_notice)。已核实:英文文案照常显示、以独立消息呈现、不再并入流式 assistant 回复;daemon 侧语义里 prompt 级通知本就不持久化为 transcript 记录,故重载会话后消失与既有语义一致——无害,甚至与"保持独立"的目标吻合。两点建议:作者快速确认 companion 端渲染可接受(该表面不在 PR 范围声明内);qwenSessionUpdateHandler.test.ts 补一条新 source 的路由测试把行为钉住。
EN: With qwenDiscreteMessage: true, the companion reroutes the notice from a stream chunk to a standalone onMessage (source=vision_bridge_notice). Verified benign: the English text still displays as a standalone message, no longer folded into the streamed assistant reply; disappearing on conversation reload matches the daemon's semantics (prompt-level notices are never persisted as transcript records). Two asks: a quick author confirmation that the companion rendering is acceptable (this surface is outside the PR's scope statement), and a routing test in qwenSessionUpdateHandler.test.ts to pin the new behavior.
🟢 5. 测试缺口(按风险排序)
- 元数据缺失的回退路径两端都无测试:adapter 层
source === 'vision_bridge_notice'但无visionBridgeNotice(transcriptToMessages.ts:536-538 省略 data;SystemMessage.tsx:53-55 返回 undefined 回退 content),以及 SystemMessage 从未以data={undefined}测过。 - EN 语义平价无守护:EN 目录文案在第二个包里逐字复制了 daemon
formatVisionBridgeNotice的措辞(i18n.tsx:509-517 vs vision-bridge-service.ts:264-290);一条"同输入下 t('visionBridge.*') 输出等于 daemon formatter"的测试可锁住披露等价。 emitVisionBridgeNotice无直接单测:modelId缺失→省略modelName、modelEndpoint缺失→省略、egressOccurred: undefined→false三个分支未覆盖;Session.test 只断言了字段齐全的 'skipped' 形状。- 改写中间件的交错顺序(chunk、通知、chunk 同一 turn)未测——逻辑按 update 门控所以安全,但值得一条。
- 边缘形状:多个通知同 turn、负数计数(NaN 有测试、负数没有)、非对象 data(已处理未测试)。
EN: Test gaps, by risk: (a) the missing-metadata fallback is untested at both layers (adapter with source but no visionBridgeNotice; SystemMessage never rendered with data={undefined}); (b) no EN parity guard — the EN catalog duplicates the daemon formatter wording in a second package, and a test asserting t('visionBridge.*') equals the daemon formatter for the same inputs would lock the disclosure equivalence; (c) emitVisionBridgeNotice has no direct unit tests for the optional-field branches (absent modelId/modelEndpoint, undefined egress), and Session.test only asserts the fully-populated 'skipped' shape; (d) the interleaved rewrite ordering (chunk, notice, chunk) is untested — safe by per-update gating but worth one test; (e) edge shapes: multiple notices per turn, negative counts (NaN is tested, negatives are not), non-object data.
💡 建议 / Suggestions
- first-output 基准已正确排除本通知(经
qwenDiscreteMessage === true,integration-tests/cli/_first-output-benchmark.ts:196-205);把'vision_bridge_notice'也加进NON_MODEL_OUTPUT_SOURCES可让两道守卫与其他离散来源保持一致。/ The benchmark already excludes the notice via the discrete flag; adding'vision_bridge_notice'toNON_MODEL_OUTPUT_SOURCESkeeps the two guards consistent with the other discrete sources. - 两个通道桥的内容类型守卫不对称:AcpBridge.ts:406-412 检查
content?.type === 'text',DaemonChannelBridge.ts:740-742 依赖的getTextContent不查 type。实践无害(daemon 对该 source 只发文本),建议对齐。/ Asymmetric content-type guards between the bridges (AcpBridge checkscontent.type; DaemonChannelBridge'sgetTextContentdoes not) — harmless in practice, worth aligning. - 仅通知成篇的 turn(如取消的 prompt)老化后会折进"已处理"步骤、披露需展开才可见——这是设计文档的明确要求且可恢复,只作为产品取舍在此留痕。/ A notice-only turn (e.g. cancelled prompt) folds into the processed-step once it ages out — the disclosure stays restorable and this is exactly what the design doc mandates; noted only to keep the trade-off conscious.
🎉 值得点名 / Highlights
- 通道桥 textChunk 分支是承重墙:基线中两个桥都会丢弃非后台通知的离散消息,此分支保住了"图片已发送至其他模型"的 Channel 披露(已对照基线验证)。
- 单一事实源:同一
bridgeResult同时喂英文文案与结构化元数据,同帧内不可能漂移;发射点保留 try/catch(Session.ts),元数据故障不会破坏 turn。 - 校验单一咽喉点 + 枚举构造模板键:没有未校验字段能进入字符串;任何畸形都降级为 daemon 英文披露——对一条隐私披露是正确的失败模式。
- 分离性由三层共同保证:投影重置 assistant/thinking 合并簿记、SDK
canMergeTextDelta双向拒并、三条流式快路径都拒绝非 assistant/thinking 尾部从而强制确定性重投影——通知在任何路径都不会被当作普通 assistant 文本,也不可能成为最终回答。 - EN 目录文案与 daemon formatter 输出逐字一致,英文用户零行为变化;本地化发生在渲染期而非投影期,切换语言无需重投影。
- compactionEngine 与 SDK reducer 的通用离散处理使得 resync/replay 机制零改动即兼容。
EN: The channel-bridge textChunk branch is load-bearing — without it the disclosure would vanish from Channel responses (verified against base). Single source of truth: the same bridgeResult feeds both the English text and the metadata, and the emission stays inside the existing try/catch so a metadata failure cannot break the turn. A single validation choke point with enum-built template keys — no unvalidated field reaches the string, and any malformed shape degrades to the daemon's English disclosure, the right failure mode for a privacy notice. Separation is enforced at three layers (projection merge-bookkeeping reset, SDK canMergeTextDelta blocking both directions, and streaming fast paths that all reject non-assistant tails), so the notice can never be treated as ordinary assistant text or become the final answer. The EN catalog is byte-identical to the daemon formatter output (zero change for EN users), and render-time localization makes locale switching free. Generic discrete handling in compactionEngine and the SDK reducer meant zero changes to resync/replay.
yiliang114
left a comment
There was a problem hiding this comment.
Approved. Notices carry structured status metadata (status/counts/model/endpoint/egress) with the English text kept as compatibility fallback; web-shell validates every field strictly and falls back to the raw text on any malformed shape, normalizes egress to 1/0 for the i18n templates, and renders the notice as a discrete system message; channel clients keep receiving the English disclosure via textChunk. Bot approved, no open threads.
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed — no blockers. Suggestions are inline.
中文说明
已审查——无阻断问题。 建议见行内评论。
— qwen3.8-max via Qwen Code /review (v0.22.0)
| return !isFinalAnswer; | ||
| } | ||
| return false; | ||
| return item.message.source === 'vision_bridge_notice'; |
There was a problem hiding this comment.
[Suggestion] The doc comment above isHideableStep (unchanged by this diff) now contradicts the code: it states that "The final content and any other system/shell/insight rows (errors, cancellations, command output) are kept visible even when the turn is collapsed", but this change makes the vision_bridge_notice system row hideable unconditionally. The parenthetical becomes ambiguous too — the bridge's own cancelled notice is exactly a "cancellation" row that does get folded. A maintainer extending the collapse logic will trust the documented invariant and either "fix" the intentional fold or add the next discrete system source assuming it is never hidden, producing silently inconsistent fold behavior. Update the doc comment to list vision bridge notices among the hideable steps.
中文说明
isHideableStep 上方的文档注释(本 diff 未修改)现在与代码矛盾:注释声称"最终内容以及其他所有 system/shell/insight 行(错误、取消、命令输出)在 turn 折叠时仍保持可见",但本次修改使 vision_bridge_notice 这一 system 行可以无条件被隐藏。括号中的说明也因此变得含糊——vision bridge 自身的"已取消"通知恰恰是会被折叠的"取消"行。后续扩展折叠逻辑的维护者可能会信任文档所述的不变量,要么"修复"这一有意的折叠行为,要么在假设系统行永不隐藏的前提下添加下一个离散系统消息来源,导致折叠行为悄悄不一致。建议更新文档注释,将 vision bridge 通知列入可隐藏步骤。
— qwen3.8-max via Qwen Code /review (v0.22.0)
| convertedCount: result.convertedCount, | ||
| omittedCount: result.omittedCount, | ||
| ...(result.modelId | ||
| ? { modelName: result.modelId.replace(/^[^:]+:/, '') } |
There was a problem hiding this comment.
[Suggestion] The provider-prefix strip here (result.modelId.replace(/^[^:]+:/, '')) duplicates core's private displayVisionModelId (in packages/core/src/services/visionBridge/vision-bridge-service.ts) — the byte-identical regex that produces the model name inside the English fallback text emitted in this very same update. The two derivations are kept in sync by convention alone; the new Session test ties them for exactly one prefixed shape. If core's display-name logic ever changes (the module already carries \0-suffixed selector variants of model ids), the English fallback and the structured modelName metadata silently diverge — the localized Web Shell notice would then name a different model than the daemon's English text for the same event, so the privacy-relevant egress disclosure ("your image was sent to X") disagrees between two renderings of one event. Export displayVisionModelId from the core vision-bridge service and call it here instead of re-inlining the regex:
// core: export function displayVisionModelId(modelId: string): string
...(result.modelId
? { modelName: displayVisionModelId(result.modelId) }
: {}),中文说明
这里的 provider 前缀剥离(result.modelId.replace(/^[^:]+:/, ''))与 core 中私有的 displayVisionModelId(位于 packages/core/src/services/visionBridge/vision-bridge-service.ts)重复——该正则逐字节相同,用于生成同一条 update 中英文回退文本里的模型名。两处推导仅靠约定保持同步,新增的 Session 测试也只针对一种带前缀格式将二者绑定。一旦 core 的显示名逻辑变更(该模块已经存在带 \0 后缀的模型 id selector 变体),英文回退文本与结构化 modelName 元数据会悄悄分叉——本地化的 Web Shell 通知将与 daemon 英文文本就同一事件显示不同的模型名,即隐私相关的出站披露("你的图片已发送至 X")在同一事件的两种渲染中互相矛盾。建议从 core 的 vision-bridge service 导出 displayVisionModelId,在此调用它,而不是重新内联这段正则:
// core: export function displayVisionModelId(modelId: string): string
...(result.modelId
? { modelName: displayVisionModelId(result.modelId) }
: {}),— qwen3.8-max via Qwen Code /review (v0.22.0)
| 'toast.dismissShort': 'Dismiss', | ||
| 'insight.ready': 'Insight report generated successfully!', | ||
| 'request.cancelled': 'Request cancelled.', | ||
| 'visionBridge.model': 'vision model', |
There was a problem hiding this comment.
[Suggestion] The four new EN catalog entries are exercised by zero tests — every new vision bridge SystemMessage test renders with 'zh-CN' — and no drift guard ties them to core's formatVisionBridgeNotice templates, of which they are a verbatim re-implementation (web-shell intentionally cannot import core; toolFormatting.drift.test.ts is the established drift-guard precedent for exactly such forced copies). Because formatVisionBridgeNotice(data, t) renders the EN catalog rather than the fallback for an English-locale user with valid metadata, a one-line mutation — e.g. interpolating modelName where target belongs in visionBridge.skipped — ships a degraded or incorrect English egress notice with the whole suite green; likewise, future core wording edits will silently diverge from what English users see. Mirror at least one rendering case per status with language 'en' asserting the exact EN strings, and add a toolFormatting.drift.test.ts-style drift test comparing the EN catalog output against core's formatVisionBridgeNotice for every status × egress combination.
中文说明
这四条新增的英文(EN)目录条目没有任何测试覆盖——所有新的 vision bridge SystemMessage 测试都使用 'zh-CN' 渲染——而且没有任何漂移防护把它们与 core 的 formatVisionBridgeNotice 模板绑定,尽管这里是逐字重新实现(web-shell 有意不能导入 core;toolFormatting.drift.test.ts 正是此类强制复制的既有漂移防护先例)。由于对持有有效元数据的英文语言环境用户,formatVisionBridgeNotice(data, t) 渲染的是 EN 目录而非回退文本,一行突变——例如把 visionBridge.skipped 中本应插值 target 的位置换成 modelName——就会在整个测试套件保持绿色的情况下发布降级或错误的英文出站通知;同样,未来 core 的措辞修改也会与英文用户实际看到的文案悄悄脱节。建议至少为每种状态镜像一个使用 'en' 语言渲染的用例并断言精确的英文字符串,同时按 toolFormatting.drift.test.ts 的风格补一个漂移测试,针对每种状态 × 出站组合比较 EN 目录输出与 core 的 formatVisionBridgeNotice。
— qwen3.8-max via Qwen Code /review (v0.22.0)
| 'request.cancelled': '请求已取消。', | ||
| 'visionBridge.model': '视觉模型', | ||
| 'visionBridge.skipped': (v) => | ||
| `视觉桥接已取消。${v?.egressOccurred === 1 ? `你的图片及提示词/上下文已发送至 ${v?.target ?? ''}。` : ''}`, |
There was a problem hiding this comment.
[Suggestion] The clause-absent sides of these conditional templates are never exercised in either catalog (the EN catalog carries the same shapes): there is no egressOccurred: false case for skipped/ok, no omittedCount: 0 case for ok, and no egressOccurred: true case for failed. All existing assertions use toContain on the egress-present strings, which cannot detect an unwanted appended clause. This is a verified mutation survivor: in the verifier's scratch tree, changing ZH visionBridge.skipped to always include the egress clause left the suite green (30/30 tests passing before and after the mutation), while a probe test asserting the clause is absent for egressOccurred: false FAILED against the mutant — rendering 视觉桥接已取消。你的图片及提示词/上下文已发送至 qwen3.6-plus (idealab.alibaba-inc.com)。 — and passed against the restored code. An always-include mutation would therefore ship a false data-egress claim ("your image was sent to X") — the exact fact this metadata exists to convey accurately — with every test green. Add the missing cases in both catalogs (skipped + egressOccurred: false, ok + omittedCount: 0 + egressOccurred: false, failed + egressOccurred: true), asserting the full rendered text so a spuriously appended clause fails.
中文说明
这些条件模板中"子句缺失"一侧的分支在两个语言目录中都从未被覆盖(EN 目录具有相同结构):skipped/ok 没有 egressOccurred: false 的用例,ok 没有 omittedCount: 0 的用例,failed 没有 egressOccurred: true 的用例。现有断言全部使用 toContain 检查含出站信息的字符串,无法检测被错误附加的子句。这是一个已被验证的突变存活:在验证器的临时工作树中,把 ZH 的 visionBridge.skipped 改为始终包含出句子句后,整个套件依然全绿(突变前后均为 30/30 通过);而一个断言 egressOccurred: false 时不应出现该子句的探针测试在突变体上失败——渲染出 视觉桥接已取消。你的图片及提示词/上下文已发送至 qwen3.6-plus (idealab.alibaba-inc.com)。——在恢复后的代码上通过。也就是说,"始终包含"的突变会在所有测试保持绿色的情况下发布虚假的数据出站声明("你的图片已发送至 X")——而这一事实正是该元数据存在以准确传达的。建议在两个语言目录中补齐缺失用例(skipped + egressOccurred: false、ok + omittedCount: 0 + egressOccurred: false、failed + egressOccurred: true),并对完整渲染文本做断言,使被错误附加的子句导致测试失败。
— qwen3.8-max via Qwen Code /review (v0.22.0)
|
Released in v0.22.2. |
What this PR does
Vision Bridge status notices now carry structured status metadata while retaining the existing English ACP text as a compatibility fallback. Web Shell renders the notice in the selected language, keeps it separate from surrounding assistant output, and includes it in the existing processed-step collapse so expanding restores the notice.
Channel-based clients continue receiving the English disclosure text, and message rewriting no longer folds the notice into rewritten assistant content.
Why it's needed
Web Shell previously displayed the daemon's hard-coded English Vision Bridge notice even in Chinese sessions. Treating the notice as ordinary assistant text also allowed adjacent output or message rewriting to merge it, while marking it discrete without preserving the Channel path could hide the disclosure that an image was sent to another model.
Reviewer Test Plan
How to verify
In a Chinese Web Shell session with a text-only primary model and a configured Vision Bridge model, send an image and trigger a cancelled, failed, or successful bridge attempt. Confirm the notice is localized, preserves the selected model and endpoint disclosure, remains separate from assistant output, hides under the completed turn's “已处理” row when collapsed, and reappears when expanded. Switch to English and confirm the equivalent English wording. Confirm a Channel response still includes the English Vision Bridge disclosure.
Evidence (Before & After)
Before: Chinese Web Shell sessions displayed the English Vision Bridge notice, and discrete handling could remove the disclosure from Channel responses.
After: Web Shell localizes the notice from structured metadata, malformed metadata falls back to the original English text, completed turns fold and restore the notice correctly, and Channel responses retain the disclosure.
Tested on
Environment (optional)
Local workspace build and unit tests with Node.js 22.
Risk & Scope
Linked Issues
N/A
中文说明
本 PR 做了什么
Vision Bridge 状态通知现在会携带结构化状态元数据,同时保留原有英文 ACP 文本作为兼容回退。Web Shell 会按当前语言渲染通知,确保通知不会与前后 assistant 输出合并,并将其纳入现有“已处理”步骤折叠;展开后通知会恢复显示。
Channel 客户端继续收到英文披露文本,消息改写也不会再把通知合并进改写后的 assistant 内容。
为什么需要
此前 Web Shell 即使处于中文环境,也会展示 daemon 硬编码的英文 Vision Bridge 通知。通知作为普通 assistant 文本时还可能与相邻输出或消息改写结果合并;而直接标记为离散消息但不保留 Channel 路径,又可能隐藏图片已发送至其他模型的披露信息。
Reviewer 测试计划
验证方式
在中文 Web Shell 会话中使用纯文本主模型并配置 Vision Bridge 模型,发送图片并触发取消、失败或成功的桥接结果。确认通知已本地化、保留所选模型和 endpoint 披露、与 assistant 输出保持独立、在已完成 turn 收起时隐藏到“已处理”行中,并在展开后恢复。切换为英文后确认显示对应英文文案。确认 Channel 响应仍包含英文 Vision Bridge 披露。
前后证据
之前:中文 Web Shell 会话展示英文 Vision Bridge 通知,离散消息处理还可能让 Channel 响应丢失披露。
之后:Web Shell 根据结构化元数据本地化通知;元数据异常时回退原始英文文本;已完成 turn 会正确收起和恢复通知;Channel 响应保留披露。
已测试平台
环境(可选)
Node.js 22 的本地 workspace build 与单元测试。
风险与范围
关联 Issue
N/A