feat(web-shell): run read-only info commands immediately mid-turn - #8496
feat(web-shell): run read-only info commands immediately mid-turn#8496wenshao wants to merge 8 commits into
Conversation
/stats, /about (/status) and /context were silently swallowed while a turn was streaming, because their local user echo would act as a turn boundary in applyTurnCollapse and split the active turn. Their output is a status block, which is not a turn boundary and is not counted in turn metrics, so only the echo needs to be skipped mid-turn. Add appendLocalUserEchoIfIdle, which echoes when idle and skips the echo while streaming without blocking the command, and switch these three commands to it so their results render inline immediately even during an active turn.
|
@qwen-code /takeover |
🖼️ 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 1 render-shaping file:
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 |
|
🤝 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,首轮处理将由下一次定时扫描执行(通常几分钟内)。移除 |
|
✅ Qwen Triage finished — CI landed green on ✅ Qwen Triage 已完成 —— |
|
Thanks for the PR! Template looks good ✓ Problem: observed, not theoretical. I confirmed it in the base code: Direction: aligned. Long turns are precisely when users want token/session info, and dropping the command with zero feedback is a real UX papercut. Supporting signal from the claude-code CHANGELOG: Size: not applicable to the core gate — all changes are in Approach: scope feels right. One small pure helper ( Risk: no high-risk path matches; no elevated risk signals. Moving on to code review. 🔍 中文说明感谢贡献! 模板完整 ✓ 问题:已观测到的真实问题,不是理论假设。我在基线代码中确认了:回合进行中 方向:对齐。长回合进行时恰恰是用户最想查看 token/会话信息的时机,命令毫无反馈地消失是真实的体验问题。claude-code CHANGELOG 中有旁证: 规模:不涉及核心模块门槛——全部改动都在 方案:范围合理。一个小的纯函数 helper( 风险:未命中高风险路径;无升级风险信号。 进入代码审查 🔍 — Qwen Code · qwen3.8-max Reviewed at |
Code reviewClean pass — I verified the load-bearing claims against the base code rather than taking the description at face value:
The change is minimal and idiomatic: one small pure helper reusing Test evidence (PR's own CI — unattended run, no local execution)The platform-matrix and CLI-integration jobs were skipped by CI's Final CI results for
One row per check name (latest run); skipped checks omitted; failures sort first. / 每个检查名一行(取最新一次运行),省略 skipped,失败项排在最前。 Sandboxed verification would settle the remaining gap: 中文说明代码审查审查通过——我对照基线代码逐条验证了关键论断,而不是只看 PR 描述:
改动最小且符合惯例:一个复用 测试证据(PR 自身 CI——无人值守运行,不在本地执行 PR 代码)平台矩阵与 CLI 集成 job 被 CI 的 沙箱验证可以补上剩余缺口: — Qwen Code · qwen3.8-max Reviewed at |
|
Confidence: 4/5 — clean, minimal, well-reasoned change with every mechanism verified against the base code; the only reservations are non-blocking (no App-level test for the new wiring, and live-browser behavior rests on the pending web-shell E2E smoke rather than a recorded session). Stepping back: this is exactly the kind of PR the gate should wave through. The problem is real and reproduced in the base code — mid-turn info commands vanish with zero feedback, at the moment users most want them. My independent take before reading the diff was the same shape the PR landed on: run the read-only commands immediately, suppress only the echo (the turn boundary), touch nothing else. The implementation matches that — one small pure helper in the existing echo module, three one-line call-site swaps, and a deliberate, accurately-described scope boundary around the other echo-style commands. Every load-bearing claim checked out against the code, not just the description: status blocks are never turn boundaries, stay visible on collapse, don't pollute turn metrics, and the tail-follow survives the missing echo. The diff carries no drive-by edits; in six months this reads as an obvious improvement, not something to curse at. The two reservations are genuine but small: a component test pinning "mid-turn Verdict: approve. One CI job (web-shell E2E Smoke) is still queued, so approval is deferred until CI lands green on 中文说明置信度:4/5 —— 干净、最小、论证充分的改动,每个机制都对照基线代码验证过;唯一的保留意见是非阻塞的(新接线缺少 App 层测试,真实浏览器行为目前取决于排队中的 web-shell E2E smoke,而非录制的会话)。 整体来看:这正是门禁应该放行的 PR。问题真实存在,且我在基线代码中复现了它——回合进行中信息类命令毫无反馈地消失,而这恰恰是用户最需要它们的时刻。我在读 diff 之前的独立方案与 PR 最终形态一致:只读命令立即执行、只抑制回显(回合边界)、其他一概不动。实现与此吻合——现有回显模块中一个小的纯函数 helper、三处一行替换,以及对其他回显类命令刻意且描述准确的范围边界。 每个关键论断都经受了代码级验证,而不只是相信描述:status 块绝不是回合边界、折叠后保持可见、不污染回合统计,缺少回显行时底部跟随依然生效。diff 没有夹带任何无关改动;六个月后回头看,这是显而易见的改进,而不是让人咒骂的东西。两个保留意见真实但很小:用一个组件测试钉住"回合中 结论:批准。由于还有一个 CI job(web-shell E2E Smoke)在排队,批准将推迟到该提交的 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. ✅
|
🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下: Autofix review round — no code change requiredThe PR is already approved ("LGTM, looks ready to ship — CI landed green") with all checks green and no failing or still-red checks. The only new feedback item is the automated web-shell visual preview informational comment, which notes that this PR edits a render-shaping file ( After inspecting the change and the visual harness, I'm declining that suggestion with evidence rather than adding a scenario, for the reasons below. No source change is warranted this round. Why a visual screenshot scenario is not appropriate for this change1. This is a timing/interaction change, not a static visual change. The PR makes the read-only info commands ( 2. The only mid-turn visual state is byte-nondeterministic. To show the changed behavior, a scenario would have to hold the session mid-turn so
The harness's 3. This matches the existing codebase convention. The 4. The behavior is already covered. The new Adding a static screenshot scenario here would introduce a flaky, diff-noise-producing test and grow the diff of an already-approved PR without asserting the actual behavior — the opposite of what the visual suite is designed for. If mid-turn behavioral coverage is wanted later, a recorded 中文说明Autofix 评审轮次 —— 无需修改代码本 PR 已被批准("LGTM,看起来可以合入 —— 评审后 CI 全绿"),所有检查均为绿色,没有失败或持续失败的检查。唯一的新反馈条目是自动化的 web-shell 视觉预览信息性评论:它注意到本 PR 修改了一个影响渲染的文件( 在检查了改动内容与视觉测试脚手架(harness)之后,我基于如下证据婉拒该建议,而不是添加场景。本轮不值得修改源码。 为什么视觉截图场景不适合本改动1. 这是一个时序/交互改动,而非静态视觉改动。 本 PR 让只读信息命令( 2. 唯一的回合中视觉状态是字节级不确定的。 要展示被改变的行为,场景必须让会话停留在回合中,使
harness 的 3. 这与现有代码库约定一致。 同一文件中的 4. 该行为已有测试覆盖。 新增的 在此添加一个静态截图场景,只会引入一个不稳定、只会产生差异噪声的测试,并使一个已被批准的 PR 的 diff 膨胀,却无法断言实际行为 —— 这与视觉测试套件的设计初衷背道而驰。如果之后需要回合中的行为覆盖,录制式的 Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。 🧠 Handled by Qwen Code · model/模型 |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Test Plan (not a blocker): 7 tests pass — this review observed 2764 passed; 303 tests pass — this review observed 2764 passed.
中文说明
Test Plan(非阻断):7 tests pass — this review observed 2764 passed; 303 tests pass — this review observed 2764 passed。
— qwen3.8-max via Qwen Code /review (v0.21.5)
…fo commands Address PR review. A status dispatch finalizes the active assistant block by default, so running /stats, /about or /context mid-turn would fragment the streaming answer around the status card and drop later usage frames. Add an optional clearActiveText flag to the status event and pass false from these three command dispatches, covered by reducer-level tests. Also collapse the echo gate into a single body (the new helper now delegates to appendOrDeferLocalUserMessage), add App-level wiring tests for the responding/idle behavior of /stats and /about, and surface failed getStats via reportError instead of swallowing them.
|
🔀 Base updated: red check(s) [Test (ubuntu-latest, Node 22.x)] pass on current main — merged current main via update-branch; CI will re-run. 中文说明🔀 已更新 base:红色检查 [Test (ubuntu-latest, Node 22.x)] 在当前 main 上通过 —— 已通过 update-branch 合入当前 main,CI 将重新运行。 |
|
🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下: No code change this round — every finding is already fixed at HEAD, re-verified with full checksAll four inline findings from the round-1 review run (anchored at Point-by-point dispositions1. rc:3709814016 — [Critical] mid-turn status dispatch finalizes the in-flight assistant block → already fixed in code, re-verified.
2. rc:3709814017 — [Suggestion] duplicated idle gate with inverted polarity → already fixed in code, re-verified. 3. rc:3709814023 — [Suggestion] wiring untested at the App level → already fixed in code, re-verified. 4. rc:3709814027 — [Suggestion] failed 5. Review-level note "Test Plan (not a blocker)" — informational, no change warranted. 6. Maintainer reply comments rc:3712312856 / rc:3712313081 / rc:3712313381 / rc:3712313647 — content not recoverable, no action taken from them. Thread-resolution noteBecause this round commits nothing, the workflow's deterministic guard deliberately does not auto-resolve any review thread this round (threads are only resolved while the live PR head is the exact commit a round pushed and verified). The four finding threads therefore stay open even though all four findings are fixed at HEAD — a maintainer may resolve them directly, and any future round that pushes a commit can resolve them through the normal path. Verification (commands actually run this round, at HEAD
|
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline. Not reviewed: verification and reverse audit — each ran and opened its brief, but neither was launched with the prompt the CLI built — the launches were written by hand, so the posted findings cannot be counted as verified, and what the agents were actually asked is not what this skill certifies. Test Plan (not a blocker): client/App.test.tsx — no such file or directory; 7 tests pass — this review observed 1446, 2782 passed; 303 tests pass — this review observed 1446, 2782 passed.
中文说明
已审查。 建议见行内评论。 未审查:验证与反向审计——两者都运行并打开了各自的 brief,但都不是用 CLI 构建的 prompt 启动的——启动 prompt 是手写的,发布的发现不能算作已验证,agent 实际被要求做的也不是本 skill 所认证的内容。 Test Plan(非阻断):client/App.test.tsx — no such file or directory; 7 tests pass — this review observed 1446, 2782 passed; 303 tests pass — this review observed 1446, 2782 passed。
— qwen3.8-max via Qwen Code /review (v0.21.5)
| appendStatusBlock(next, event.type, event.text, event, { | ||
| clearActiveText: event.clearActiveText, | ||
| }); |
There was a problem hiding this comment.
[Critical] The clearActiveText: false opt-out skips the entire clearActiveText(state) call — including state.activeUserBlockId = undefined. All three new dispatch sites pass the flag unconditionally, also when idle. Pre-PR, an idle status dispatch reset activeUserBlockId; post-PR, running /stats, /about or /context (or clicking the status-bar context indicator) while idle leaves the local command echo block as the active user block indefinitely.
Failure scenario: Web Shell client B is idle; its user runs /stats → echo block E becomes activeUserBlockId; the result dispatch leaves the pointer at E. A peer client (TUI or a second web tab on the same daemon session) then submits a prompt: the bridge echo arrives as a mergeable user.text.delta (no sourceRecordIds, no qwenDiscreteMessage), canMergeTextDelta passes, and the peer's prompt text is appended onto E — rendering /stats<peer prompt> in one user block. Since applyTurnCollapse bounds turns by user messages, the peer's entire turn (assistant text, tool steps, token usage) groups under the /stats echo's turn, corrupting turn boundaries and per-turn metrics. Verified by a reducer probe at the reviewed commit: the PR arm merged into one user block (/statsfix the bug) where the pre-PR control arm produced separate user blocks.
Suggested fix — probe-verified (flips the repro back to separate blocks while keeping this PR's reducer tests green): in appendStatusBlock, keep the assistant/thought block but drop the user pointer on the opt-out path:
appendBlock(state, block);
if (opts.clearActiveText !== false) clearActiveText(state);
else state.activeUserBlockId = undefined;(Alternative: pass clearActiveText: false from App.tsx only while streaming — idle dispatches have no streaming block to protect.)
中文说明
clearActiveText: false 选项跳过了整个 clearActiveText(state) 调用——包括 state.activeUserBlockId = undefined。三处新的 dispatch 都无条件传入该标志,空闲时也是如此。本 PR 之前,空闲时的 status dispatch 会重置 activeUserBlockId;现在,空闲时运行 /stats、/about 或 /context(或点击状态栏的 context 指示器)会让本地命令回显块无限期地保持为活跃用户块。
失败场景:Web Shell 客户端 B 空闲时运行 /stats → 回显块 E 成为 activeUserBlockId;结果 dispatch 使指针一直停留在 E。此时同一 daemon 会话上的对端客户端(TUI 或第二个网页标签页)提交提示词:桥的回显以可合并的 user.text.delta 到达(无 sourceRecordIds、无 qwenDiscreteMessage),canMergeTextDelta 通过,对端的提示词文本被追加到 E 上——一个用户块渲染出 /stats<对端提示词>。由于 applyTurnCollapse 以用户消息为回合边界,对端的整个回合(assistant 文本、工具步骤、token 用量)都会归入 /stats 回显所在的回合,破坏回合边界与逐回合统计。已在被审提交上用 reducer 探针验证:PR 分支合并为一个用户块(/statsfix the bug),而 PR 前的对照组产生独立的用户块。
建议修复(已用探针验证——复现恢复为独立块,且本 PR 的 reducer 测试仍全绿):在 appendStatusBlock 中保留 assistant/thought 块,但在 opt-out 路径上清掉用户指针:
appendBlock(state, block);
if (opts.clearActiveText !== false) clearActiveText(state);
else state.activeUserBlockId = undefined;(备选方案:仅在流式时才从 App.tsx 传 clearActiveText: false——空闲 dispatch 没有需要保护的流式块。)
— qwen3.8-max via Qwen Code /review (v0.21.5)
There was a problem hiding this comment.
| echoLocalCommandIfIdle(commandText); | ||
| sessionActions | ||
| .getContextUsage({ detail }) |
There was a problem hiding this comment.
[Suggestion] /context — the third command this PR converts to mid-turn execution — has no test anywhere in web-shell for this new wiring (no references to getContextUsage/showContextUsage in any test file), while /stats and /about each got four new App-level tests. All three entry points (keyboard /context, the status-bar context indicator, and the in-chat context-detail click) funnel through showContextUsage.
Concrete cost: a future edit reverting showContextUsage to echoOrDeferLocalCommand(commandText) (suppressing /context mid-turn again) or dropping clearActiveText: false from its dispatch leaves the entire suite green — the exact regression this PR fixes would ship.
Suggested fix — mirror the new /stats tests in the App read-only local commands mid-turn describe:
// streamingState = 'responding': submit /context
// -> expect getContextUsage called, appendLocalUserMessage NOT called,
// dispatch receives objectContaining({ type: 'status', clearActiveText: false })
// streamingState = 'idle': submit /context -> expect the echo appended中文说明
/context 是本 PR 改造为回合中执行的第三个命令,但 web-shell 中没有任何测试覆盖这段新接线(任何测试文件中都没有 getContextUsage/showContextUsage 的引用),而 /stats 和 /about 各新增了四个 App 级测试。三个入口(键盘 /context、状态栏 context 指示器、聊天内 context 详情点击)都汇聚到 showContextUsage。
具体代价:未来某次编辑把 showContextUsage 改回 echoOrDeferLocalCommand(commandText)(重新在回合中抑制 /context),或从其 dispatch 中删掉 clearActiveText: false,整个测试套件仍会全绿——本 PR 所修复的回归会原样合入。
建议修复——在 App read-only local commands mid-turn describe 中仿照新增的 /stats 测试:
// streamingState = 'responding':提交 /context
// -> 断言 getContextUsage 被调用、appendLocalUserMessage 未被调用、
// dispatch 收到 objectContaining({ type: 'status', clearActiveText: false })
// streamingState = 'idle':提交 /context -> 断言回显被追加— qwen3.8-max via Qwen Code /review (v0.21.5)
| .catch((error: unknown) => { | ||
| reportError(error, 'Failed to load stats'); | ||
| }); |
There was a problem hiding this comment.
[Suggestion] This failure path changed from swallowed (.catch(() => {})) to reported via reportError, but no test pins the new reporting behaviour — there is no 'Failed to load stats' assertion and no getStats rejection mock in the suite.
Failure scenario: a revert to .catch(() => {}) passes the suite and load failures become invisible again (no toast, no console) — the very regression the previous review round fixed on this line. With the command no longer waiting for idle, getStats() rejection is now a realistic mid-turn race. The suite already establishes this guard pattern for /goal ("reports a failure to open a goal's session instead of swallowing it").
Suggested fix:
mockSessionActions.getStats.mockRejectedValueOnce(new Error('stats unavailable'));
// submit /stats, then assert the error reaches reportError (console.error/toast spy),
// matching the existing goal-failure test's style中文说明
这条失败路径从被吞掉(.catch(() => {}))改为通过 reportError 上报,但没有任何测试固定新的上报行为——套件中既没有对 'Failed to load stats' 的断言,也没有 getStats 的 rejection mock。
失败场景:把代码改回 .catch(() => {}) 也能通过套件,加载失败会再次变得不可见(无 toast、无 console)——这正是上一轮评审在这行代码上修复的回归。由于命令不再等待空闲,getStats() reject 现在是现实的回合中竞态。套件中已有同样的守卫模式(/goal 的 "reports a failure to open a goal's session instead of swallowing it")。
建议修复:
mockSessionActions.getStats.mockRejectedValueOnce(new Error('stats unavailable'));
// 提交 /stats,然后断言错误到达了 reportError(console.error/toast spy),
// 与现有 goal 失败测试的写法保持一致— qwen3.8-max via Qwen Code /review (v0.21.5)
doudouOUC
left a comment
There was a problem hiding this comment.
Reviewed — no blockers. Suggestions are inline. Test Plan (not a blocker): client/App.test.tsx — no such file or directory.
中文说明
已审查——无阻断问题。 建议见行内评论。 Test Plan(非阻断):client/App.test.tsx — no such file or directory。
— claude-opus-4-6 via Qwen Code /review (v0.21.5)
| echoLocalCommandIfIdle(text); | ||
| Promise.all([ | ||
| workspaceActions.loadPreflight().catch(() => null), |
There was a problem hiding this comment.
[Suggestion] The /about handler's Promise.all(...).then(...) chain has no .catch(), while the adjacent /stats handler was explicitly updated in this same diff to use reportError. — Concrete cost: if serializeStatusMessage, store.dispatch, or resumeChatBottomFollow throws synchronously inside the .then() callback, the rejection is unhandled — no error toast, no console diagnostic, silent failure. The /about path is the lone outlier among the three read-only display commands after this diff.
Append .catch((error: unknown) => { reportError(error, 'Failed to load status info'); }) to the Promise chain, matching /stats and /context.
中文说明
/about 处理器的 Promise.all(...).then(...) 链缺少 .catch(),而相邻的 /stats 处理器在本 diff 中已显式更新为使用 reportError。— 具体代价:如果 .then() 回调内的 serializeStatusMessage、store.dispatch 或 resumeChatBottomFollow 同步抛出异常,该 rejection 将无人处理——没有错误 toast、没有控制台诊断信息,静默失败。本 diff 后 /about 是三个只读显示命令中唯一的遗漏。
在 Promise 链末尾追加 .catch((error: unknown) => { reportError(error, 'Failed to load status info'); }),与 /stats 和 /context 保持一致。
— claude-opus-4-6 via Qwen Code /review (v0.21.5)
…aming (QwenLM#8496) The clearActiveText: false opt-out skipped the whole clearActiveText call, leaving the local command echo as the active user block indefinitely. A peer client's prompt echo then merged into it, corrupting turn boundaries. Keep the streaming assistant/thought block open on the opt-out path but still drop the user pointer. Also report /about load failures like /stats and /context already do, and pin the new /context wiring plus the /stats failure path with tests.
|
🤖 Addressed the latest review feedback (round 1/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 1/100 轮)。改动内容与我反驳保留之处如下: Round summaryAddressed all four inline findings from this review round: one Critical and three Suggestions. Committed as Feedback and dispositions
The review-level notes ("verification and reverse audit were not launched with the CLI-built prompt"; Test Plan ConflictNone ( Verification
中文说明本轮总结本轮处理了全部四条行内评审发现:一条 Critical、三条 Suggestion。已以 反馈与处理
评审层面的备注("verification 与 reverse audit 不是用 CLI 构建的 prompt 启动的";Test Plan 中 冲突无( 验证
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.
Reviewed — no blockers. Suggestions are inline. Test Plan (not a blocker): client/App.test.tsx — no such file or directory; 7 tests pass — this review observed 1448, 2785 passed; 303 tests pass — this review observed 1448, 2785 passed.
中文说明
已审查——无阻断问题。 建议见行内评论。 Test Plan(非阻断):client/App.test.tsx — no such file or directory; 7 tests pass — this review observed 1448, 2785 passed; 303 tests pass — this review observed 1448, 2785 passed。
— qwen3.8-max via Qwen Code /review (v0.21.5)
| // Mid-turn runs must not finalize the streaming block. | ||
| clearActiveText: false, |
There was a problem hiding this comment.
[Suggestion] The read-only command result dispatch — store.dispatch([{ type: 'status', text, ..., clearActiveText: false }]) followed by resumeChatBottomFollow('smooth') — is duplicated verbatim at the three sites this diff adds (showContextUsage ~5899, /stats ~7594, /status|about ~7669), each copy carrying the load-bearing flag. — Concrete cost: the invariant "a read-only result dispatch must pass clearActiveText: false" is enforced only by convention at three sites. When a fourth read-only command gains mid-turn support, or one of the copies is edited and drops the flag, the in-flight assistant/thought block silently finalizes mid-stream — the exact regression this PR exists to fix (the streaming answer splits at the status block; subsequent assistant.usage frames no longer attach to the active block). Suggested fix — centralize the flag + follow-resume pair in one callback next to echoLocalCommandIfIdle, called from all three .then(...) bodies (serializers stay at the call sites):
const dispatchReadOnlyStatus = useCallback(
(text: string) => {
store.dispatch([{ type: 'status', text, clearActiveText: false }]);
resumeChatBottomFollow('smooth');
},
[store, resumeChatBottomFollow],
);中文说明
只读命令的结果派发——store.dispatch([{ type: 'status', text, ..., clearActiveText: false }]) 后跟 resumeChatBottomFollow('smooth')——在本 diff 新增的三处调用点(showContextUsage ~5899、/stats ~7594、/status|about ~7669)被逐字复制,每处都携带这个关键标志。具体代价:「只读结果派发必须传 clearActiveText: false」这一不变量仅靠三处调用点的约定维持。当第四个只读命令获得回合中执行支持、或某一处副本被编辑而漏掉该标志时,会在流式中途悄悄收尾正在进行的 assistant/thought 块——正是本 PR 要修复的回归(流式回答在 status 块处被切断,后续 assistant.usage 帧不再挂到活跃块上)。建议修复——把「标志 + 恢复底部跟随」收进 echoLocalCommandIfIdle 旁的一个回调中(见英文部分代码),三个 .then(...) 主体统一调用它,序列化仍留在各调用点。
— qwen3.8-max via Qwen Code /review (v0.21.5)
| .catch((error: unknown) => { | ||
| reportError(error, 'Failed to load status info'); | ||
| }); |
There was a problem hiding this comment.
[Suggestion] This new /about (/status) error-handling branch has no test, while the sibling new /stats catch is pinned by 'reports /stats load failures instead of swallowing them'. — Failure scenario: if a follow-up change drops this catch or replaces reportError with a swallow, the suite stays green and a failing /about regresses to an unhandled promise rejection with zero user feedback — the exact pre-PR behaviour this diff fixes for /stats only. Note: each Promise.all member catches to null, so this catch is reachable only via a throw inside the .then body — mirror the /stats failure test by making collectSystemInfo/serialization throw once, then assert console.error is called with '[web-shell]' and a message containing 'Failed to load status info'.
中文说明
新增的 /about(/status)错误捕获没有测试覆盖,而同期新增的 /stats 捕获已有 'reports /stats load failures instead of swallowing them' 测试固定。失败场景:后续改动若删除此 catch 或将 reportError 换回静默吞掉,测试套件仍为绿色,/about 失败会退化为无任何用户反馈的未处理 promise rejection——正是本 diff 仅为 /stats 修复的改动前行为。注意:Promise.all 的每个成员都已 .catch(() => null),因此这个 catch 只能通过 .then 主体内部抛错来触达——测试应仿照 /stats 失败用例,让 collectSystemInfo/序列化过程抛错一次,然后断言 console.error 以 '[web-shell]' 和包含 'Failed to load status info' 的消息被调用。
— qwen3.8-max via Qwen Code /review (v0.21.5)
QwenLM#8496) Address the round-3 review feedback on the mid-turn read-only commands: - The read-only result dispatch (status block with clearActiveText: false plus the follow-resume) was copied verbatim at the /context, /stats, and /about sites, leaving the load-bearing flag enforced by convention at three places. Centralize it in one dispatchReadOnlyStatus callback next to echoLocalCommandIfIdle; the three .then bodies now call it with their serialized text. - Pin the /about catch the way the sibling /stats catch is pinned: make collectSystemInfo throw once and assert the failure surfaces through console.error instead of becoming an unhandled rejection with zero user feedback.
|
🤖 Addressed the latest review feedback (round 2/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 2/100 轮)。改动内容与我反驳保留之处如下: Round summaryRound 3 on PR #8496 raised two inline Suggestions from the automated reviewer; both are resolved in commit Feedback points and decisions
No finding was declined, deferred, or escalated. Verification
Integration tests after 中文说明本轮概要PR #8496 的第 3 轮审查中,自动审查器提出了两条行内建议,均已在分支 反馈点与处理决定
没有发现被拒绝、推迟或升级处理。 验证
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/模型 |
|
🔀 Base updated: red check(s) [review-pr] pass on current main — merged current main via update-branch; CI will re-run. 中文说明🔀 已更新 base:红色检查 [review-pr] 在当前 main 上通过 —— 已通过 update-branch 合入当前 main,CI 将重新运行。 |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed — no blockers. Suggestions are inline. Test Plan (not a blocker): client/App.test.tsx — no such file or directory; 7 tests pass — this review observed 1447, 2794 passed; 303 tests pass — this review observed 1447, 2794 passed.
中文说明
已审查——无阻断问题。 建议见行内评论。 Test Plan(非阻断):client/App.test.tsx — no such file or directory; 7 tests pass — this review observed 1447, 2794 passed; 303 tests pass — this review observed 1447, 2794 passed。
— qwen3.8-max via Qwen Code /review (v0.21.5)
| ]); | ||
| }); | ||
|
|
||
| it('keeps the assistant block active when clearActiveText is false', () => { |
There was a problem hiding this comment.
[Suggestion] R4-3: The clearActiveText: false opt-out is only tested with an active assistant block; the thought-block half of the documented contract (types.ts: "without finalizing the active assistant/thought block") is unpinned — all three new reducer tests use assistant.text.delta only. — Failure scenario: named surviving mutation — adding clearActiveThought(state); to the reducer's opt-out else-branch keeps the entire sdk-typescript suite green (verified by applying the mutation: 1449 tests still pass), while a mid-turn status insertion would then finalize a streaming thought block (a thinking model still streaming thought when the user runs /stats), splitting it around the command output — the exact regression the flag exists to prevent. Suggested fix: add a mirror test with thought events.
it('keeps the thought block active when clearActiveText is false', () => {
// user.text.delta → thought.text.delta → { type: 'status', clearActiveText: false }
// → thought.text.delta → assistant.done
// assert: a single thought block with merged text, still active after the status event
});中文说明
clearActiveText: false 选项目前只用「活跃的 assistant 块」场景测试过;文档约定(types.ts:"不会收尾活跃的 assistant/thought 块")中 thought 块那一半没有任何测试固定——三条新 reducer 测试全部只使用 assistant.text.delta。失败场景:已点名的可存活变异——在 reducer 的 opt-out else 分支中加入 clearActiveThought(state);,整个 sdk-typescript 套件仍然全绿(已实际施加该变异验证:1449 个测试全部通过),而回合中插入 status 块此时会收尾正在流式的 thought 块(思考模型仍在流式输出 thought 时用户运行 /stats),把思考内容切成围绕命令输出的碎片——这正是该标志要避免的回归。建议修复:补充一个 thought 事件的镜像测试。
— qwen3.8-max via Qwen Code /review (v0.21.5)
| }); | ||
|
|
||
| describe('App read-only local commands mid-turn', () => { | ||
| it('runs /stats immediately while streaming and skips the echo', async () => { |
There was a problem hiding this comment.
[Suggestion] R4-4: All three mid-turn tests pin only the dispatch envelope (type + clearActiveText), never the text payload — and nothing else pins it: serializeStatsMessage / serializeStatusMessage / serializeContextUsageMessage have zero unit tests, and the fully mocked store means no render-level assertion sees the text either. — Failure scenario: named surviving mutation — replacing dispatchReadOnlyStatus(serializeStatsMessage(result, statsView)) (and the /about / /context equivalents) with dispatchReadOnlyStatus('') keeps all eight new tests green while /stats//about//context render empty status blocks mid-turn — the user-visible output of this feature ships untested. Suggested fix: pin the serialized payload in at least one test per command, e.g.
expect(mockStore.dispatch).toHaveBeenCalledWith([
expect.objectContaining({
type: 'status',
clearActiveText: false,
text: serializeStatsMessage(statsFixture, 'summary'),
}),
]);中文说明
三条回合中测试都只固定了 dispatch 的外壳(type + clearActiveText),从未固定 text 载荷——也没有其它测试固定它:serializeStatsMessage / serializeStatusMessage / serializeContextUsageMessage 没有任何单元测试,且 store 被完全 mock,渲染层断言同样看不到这段文本。失败场景:已点名的可存活变异——把 dispatchReadOnlyStatus(serializeStatsMessage(result, statsView))(以及 /about、/context 的对应调用)替换为 dispatchReadOnlyStatus(''),全部 8 个新测试仍然通过,而回合中 /stats//about//context 会渲染出空的 status 块——本功能的用户可见输出在无人测试的情况下上线。建议修复:每条命令至少在一个测试中固定序列化后的载荷,例如断言 text 等于对已知 fixture 调用序列化函数的结果(或至少包含某个已知字段)。
— qwen3.8-max via Qwen Code /review (v0.21.5)
What this PR does
In the Web Shell, the read-only info commands
/stats,/about(same handler as/status) and/contextnow run immediately while a turn is streaming. Previously they were silently swallowed mid-turn: the composer cleared and nothing happened until the user re-ran the command after the turn finished. The command echo (the/statsuser row) is still only appended when no turn is in flight, because that user row acts as a turn boundary and would split the active turn; only the echo is skipped mid-turn, and the command output itself renders inline as before.Why it's needed
Long turns are exactly when users want to check token usage or session info, and today typing
/statsor clicking the status-bar context indicator mid-turn produces zero feedback — the command just vanishes. It is safe to run these commands during a turn: they are read-only queries, and their results render as status blocks, which are not turn boundaries in the transcript turn-collapse logic, are not counted in turn metrics (tool/thinking/token counters), and stay visible when the turn is collapsed — identical to their behavior when idle. Skipping the echo also avoids finalizing the in-flight assistant block mid-stream, which appending a local user message would do.Reviewer Test Plan
How to verify
/stats(or/about,/context, or click the context indicator in the status bar) and press Enter./statsecho row is absent mid-turn (it only appears when running the command while idle).cd packages/web-shell && npx vitest run client/utils/localCommandQueue.test.ts(7 tests pass), plus the full App suitenpx vitest run client/App.test.tsx(303 tests pass),npm run typecheck, ESLint and Prettier.Evidence (Before & After)
Before (mid-turn): typing
/statscleared the composer with no toast, no output, and no queueing — the command was dropped. After (mid-turn): the stats output renders inline immediately with no echo row; idle behavior unchanged. No screenshots captured; verified via the unit suites above.Tested on
Environment (optional)
Local automated verification on macOS: vitest unit tests, TypeScript typecheck, ESLint, Prettier. No manual browser session was recorded.
Risk & Scope
/tools(bare listing),/bug,/model --voice,/extensions installusage errors are still suppressed mid-turn; daemon-forwarded commands still go through the queue/blocked paths. No Playwright e2e was added.Linked Issues
None.
中文说明
本 PR 做了什么
在 Web Shell 中,只读信息类命令
/stats、/about(与/status同一处理分支)和/context现在可以在回合进行中立即执行。此前它们在回合中会被静默吞掉:输入框被清空,但什么都不发生,用户必须等回合结束后重新输入。命令回显(即/stats那行用户消息)仍然只在没有回合进行时才追加,因为该用户行会作为回合边界把正在进行的回合切成两段;回合中只跳过回显,命令输出本身照常内联渲染。为什么需要
长回合进行时恰恰是用户最想查看 token 用量或会话信息的时机,而现在回合中输入
/stats或点击状态栏的 context 指示器没有任何反馈——命令直接消失。这些命令在回合中执行是安全的:它们都是只读查询,其结果以 status 块渲染,而 status 块在 transcript 的回合折叠逻辑中不是回合边界、不计入回合统计(tool/thinking/token 计数),回合折叠后也保持可见——与空闲时的行为完全一致。跳过回显还避免了在流式中途收尾正在进行的 assistant 块(追加本地用户消息会触发这一行为)。Reviewer Test Plan
如何验证
/stats(或/about、/context,或点击状态栏的 context 指示器)并回车。/stats回显行(回显只在空闲时执行命令时出现)。cd packages/web-shell && npx vitest run client/utils/localCommandQueue.test.ts(7 个测试通过),以及完整 App 套件npx vitest run client/App.test.tsx(303 个测试通过)、npm run typecheck、ESLint、Prettier。Evidence (Before & After)
改动前(回合中):输入
/stats后输入框被清空,无 toast、无输出、不排队——命令被丢弃。改动后(回合中):stats 输出立即内联渲染,无回显行;空闲时行为不变。未截图,以上述单元测试套件验证为准。Tested on
Environment (optional)
macOS 本地自动化验证:vitest 单元测试、TypeScript typecheck、ESLint、Prettier。未录制手动浏览器会话。
Risk & Scope
/tools(无参列表)、/bug、/model --voice、/extensions install用法错误仍在回合中被抑制;转发 daemon 的命令仍走排队/阻塞路径。未新增 Playwright e2e。Linked Issues
无。