Skip to content

fix(cli): make the non-VP transcript scrollable during multi-agent runs - #6015

Merged
wenshao merged 9 commits into
QwenLM:mainfrom
chiga0:fix/non-vp-scrolling
Jun 30, 2026
Merged

fix(cli): make the non-VP transcript scrollable during multi-agent runs#6015
wenshao merged 9 commits into
QwenLM:mainfrom
chiga0:fix/non-vp-scrolling

Conversation

@chiga0

@chiga0 chiga0 commented Jun 29, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

Fixes two independent root causes that make the non-VP (default) transcript impossible to scroll up during rich runs (e.g. /review's multi-agent fan-out). The view either snaps straight back to the bottom, or the wheel recalls composer history / drives the background-agent panel instead of scrolling.

Two focused commits:

  1. Stop scroll snap-back during parallel-agent runs. Ink clears the whole terminal (incl. scrollback) on every repaint once the non-<Static> live frame exceeds the terminal height (shouldClearTerminalForFrame). The pure-parallel inline panel rendered the unfiltered tool calls, so running subagents were shown inline and in LiveAgentPanel below the composer — two full rosters that push the live frame past the viewport; the per-second elapsed/token ticks then fire the clear continuously. The branch now routes through the same inlineToolCalls hand-off as every other group (running subagents are owned by the panel), with an availableTerminalHeight windowing backstop on InlineParallelAgentsDisplay.

  2. Keep native scrollback usable in non-VP mode. Enabling SGR mouse tracking (?1002h) makes the host terminal stop doing native scrollback on the wheel (Terminal.app even diverts the wheel to arrow keys, which then drive composer input history and the panel). A collapsed thinking block armed mouse tracking just for click-to-expand, unconditionally — so any non-VP session with thinking blocks could no longer scroll. useMouseEvents now gates on VP mode by default (ui.useTerminalBuffer); surfaces that genuinely own the wheel opt out via a new bypassVpGate (ScrollableList, the alternate-screen ThinkingViewer). The thinking-block click handler does not, so in non-VP it stays dormant and native scrollback is preserved (the block still expands via Alt+T). Single chokepoint → future non-VP mouse subscribers can't reintroduce the regression.

Why it's needed

PR #5799 only corrected the footer-height accounting; it neither de-duplicated the parallel-agent rosters nor stopped mouse tracking from hijacking the wheel, so non-VP scrolling stayed broken during /review-style runs. Root cause was confirmed against the real (forked) ink renderer: a non-<Static> frame taller than the terminal triggers clearTerminal on every render; below it, ink does an in-place log-update and native scrollback survives.

Reviewer Test Plan

How to verify

Automated — from the repo root:

npx vitest run \
  packages/cli/src/ui/components/messages/InlineParallelAgentsDisplay.test.tsx \
  packages/cli/src/ui/components/messages/ToolGroupMessage.test.tsx \
  packages/cli/src/ui/hooks/useMouseEvents.test.tsx \
  packages/cli/src/ui/components/HistoryItemDisplay.test.tsx \
  packages/cli/src/ui/components/shared/ScrollableList.test.tsx
# Tests  97 passed

Added regression tests: pure-parallel live-phase dedup + header total honesty + windowing backstop; useMouseEvents VP-gate (off in non-VP, on in VP, bypassVpGate overrides); thinking-block subscribes without bypassVpGate.

Manual (the live symptom), short terminal, non-VP: run /review on a PR. Before: scroll-up snaps back / wheel recalls history. After: the transcript scrolls.

Evidence (Before & After)

The before/after is encoded programmatically (the live TUI symptom needs a real TTY + API key, which CI lacks):

  • Snap-back: the dedup test asserts that during the live phase a pure-parallel group renders no running-agent rows inline (panel owns them) — before, the unfiltered branch rendered a second full roster, inflating the non-<Static> frame past the viewport and triggering clearTerminal every repaint. The windowing test asserts the inline frame height never exceeds its availableTerminalHeight budget.
  • Wheel hijack: the useMouseEvents VP-gate test asserts mouse tracking is not enabled in non-VP without bypassVpGate (native scrollback preserved), is enabled in VP, and that bypassVpGate overrides. The thinking-block test pins that it subscribes without the bypass, i.e. stays gated.

Tested on

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

Verified locally on macOS (the 5 suites above, plus tsc-diff = zero new type errors and eslint clean on the changed files). Windows/Linux not run locally — covered by CI.

Risk & Scope

  • Non-VP loses mouse click-to-expand on thinking blocks (Alt+T keyboard expand, already advertised on the block, is unchanged). VP is unaffected.
  • Parallel-agent inline panel no longer duplicates running agents during the live phase; terminal (completed/failed/cancelled) rows still render inline as the scrollback summary, header keeps the full done/total.
  • No settings/API changes.

Linked Issues

Refs #5798

中文说明

修复非 VP(默认)模式下 transcript 无法上滚的两个独立根因(/review 多 agent 场景必现):

  1. 上滚被弹回:Ink 在「非 <Static> 动态帧 > 终端行数」时每帧 clearTerminal(连 scrollback 一起清)。并行 agent 组的 inline 面板用了未过滤的 toolCalls,导致 running 子 agent 在顶部 inline 和底部 LiveAgentPanel 重复渲染两份名册,把动态帧撑过视口;每秒 tick 持续触发清屏。改为走与其它组相同的 inlineToolCalls 交接(running 由面板独占),并给 InlineParallelAgentsDisplayavailableTerminalHeight 窗口化兜底。

  2. 滚轮被劫持:开启 SGR 鼠标追踪(?1002h)会让终端不再对滚轮做原生 scrollback(Terminal.app 还把滚轮转成方向键 → 回放 composer 历史 / 操作面板)。collapsed thinking 块为了点击展开无条件开了鼠标追踪,于是非 VP 下只要有 thinking 块就滚不动。useMouseEvents 改为默认仅在 VP(ui.useTerminalBuffer)下启用;真正需要滚轮的面板(ScrollableList、alt-screen 的 ThinkingViewer)用新增的 bypassVpGate 显式豁免。thinking 块点击不豁免 → 非 VP 休眠、原生 scrollback 保住(仍可 Alt+T 展开)。单点收口,杜绝未来回归。

PR #5799 只修了 footer 高度核算,未消除重复名册、也未阻止鼠标追踪劫持滚轮,故问题仍在。根因已对真实(fork 版)ink 渲染器实证。

🤖 Generated with Qwen Code

@qwen-code-ci-bot

qwen-code-ci-bot commented Jun 29, 2026

Copy link
Copy Markdown
Collaborator

Thanks for the PR, @chiga0!

Template is complete ✓ — all required sections present with substantive content.

On direction: this fixes a genuine usability regression. Non-VP scrolling breaking during /review-style multi-agent runs is a real problem for anyone using the default transcript view. The fix is squarely within core mission — the transcript must be readable. Refs #5798 confirms the issue is tracked. No direction concerns.

On approach: the scope is tight and well-justified. Two independent root causes, two focused commits, each change traceable to a specific mechanism in ink's rendering pipeline. The bypassVpGate opt-in pattern is the right shape — default-deny mouse tracking in non-VP, explicit opt-out for surfaces that own the wheel (ScrollableList, ThinkingViewer alt-screen). The dedup path through inlineToolCalls reuses the existing hand-off rather than adding a parallel filter. The availableTerminalHeight windowing backstop is a defensive cap for the degenerate case. Nothing in the diff looks extraneous — every edit serves one of the two root causes.

Moving on to code review. 🔍

中文说明

感谢贡献,@chiga0

模板完整 ✓ — 所有必需部分都有实质性内容。

方向:这修复了一个真实的可用性回退。在 /review 等多 agent 运行期间,非 VP 模式无法滚动是默认 transcript 视图用户的实际痛点。修复完全在核心使命范围内——transcript 必须可读。Refs #5798 确认问题已被追踪。方向没有问题。

方案:范围紧凑且理由充分。两个独立根因,两个聚焦的 commit,每处改动都能追溯到 ink 渲染管道中的具体机制。bypassVpGate opt-in 模式是正确的形状——非 VP 默认拒绝鼠标追踪,真正拥有滚轮的面板(ScrollableListThinkingViewer alt-screen)显式豁免。通过 inlineToolCalls 的去重路径复用了现有交接,而非新增平行过滤器。availableTerminalHeight 窗口化兜底是退化场景的防御性上限。diff 中没有多余的改动——每处编辑都服务于两个根因之一。

进入代码审查 🔍

Qwen Code · qwen3.7-max

@qwen-code-ci-bot

qwen-code-ci-bot commented Jun 29, 2026

Copy link
Copy Markdown
Collaborator

Code Review

Independent proposal (before reading the diff): given the two symptoms — scroll snap-back during parallel-agent runs and wheel hijacking in non-VP — I would have: (1) stopped the inline parallel panel from duplicating running agents that LiveAgentPanel already owns, shrinking the non-<Static> frame below the viewport threshold; (2) gated useMouseEvents on VP mode (ui.useTerminalBuffer) with a bypass for surfaces that unconditionally own the wheel (the in-app scroller and alternate-screen modals). That is exactly what this PR does.

Findings: no blockers.

The VP gate in useMouseEvents.ts is clean — enabled now requires vpGateOpen (isVpMode || bypassVpGate) in addition to isActive && isRawModeSupported. The SettingsContext read via useContext is the right place; ui.useTerminalBuffer is the canonical VP signal. The new MouseEventsOptions interface with bypassVpGate is a clear, single-chokepoint API that future mouse subscribers can't accidentally bypass.

The dedup in ToolGroupMessage.tsx correctly routes the pure-parallel branch through inlineToolCalls.filter(isSubagentToolEntry) instead of the raw toolCalls. The return null short-circuit when all agents are panel-owned (live phase, all running) prevents an empty container from rendering. totalAgentCount keeps the header's done/total honest. Forwarding availableTerminalHeight only during the live phase (isPending ? availableTerminalHeight : undefined) is the right call — committed-phase rows live in <Static> with no snap-back risk, and capping them would permanently hide completed agents behind "+N more" in scrollback.

The height windowing in InlineParallelAgentsDisplay.tsxrowBudget = max(1, availableTerminalHeight - 2), slicing from the end to show most recent — is the right backstop. The wrap="truncate-end" additions on all Text elements enforce the 1-line-per-row invariant that the budget math depends on. The degenerate-case handling (budget ≤ 1 drops rows and indicator, keeping only the header with the full tally) is well-reasoned.

Tests are comprehensive: pure-parallel live-phase dedup (all-running → null, mixed → only terminal shown inline, committed → full roster), header tally honesty, height windowing with exact-fit boundary pinning (> not >=), overflow indicator, VP gate on/off/bypass, thinking-block subscribing without bypassVpGate, and ThinkingViewer subscribing with it. The existing mechanism tests correctly pass bypassVpGate: true so they continue exercising ref counting independent of the VP gate.

Testing

Unit tests: 104/104 passed across all 6 affected test files (verified on PR branch in worktree):

 ✓ src/ui/hooks/useMouseEvents.test.tsx (4 tests)
 ✓ src/ui/components/messages/InlineParallelAgentsDisplay.test.tsx (12 tests)
 ✓ src/ui/components/ThinkingViewer.test.tsx (1 test)
 ✓ src/ui/components/messages/ToolGroupMessage.test.tsx (45 tests)
 ✓ src/ui/components/shared/ScrollableList.test.tsx (14 tests)
 ✓ src/ui/components/HistoryItemDisplay.test.tsx (28 tests)
 Test Files  6 passed (6)
      Tests  104 passed (104)

Typecheck: all 7 packages clean (exit 0).

Build: clean (exit 0). Only pre-existing lint warnings in vscode-ide-companion (unrelated).

Tmux test (PR branch dev build):

$ npm run dev -- -p 'say hello'

> @qwen-code/qwen-code@0.19.3 dev
> node scripts/dev.js -p say hello

Warning: QWEN_HOME points to "..." but no settings.json was found there.
DEV is set to true, but the React DevTools server is not running.
Hello! How can I help you today?

The PR branch starts cleanly without runtime errors and responds normally. The actual scroll snap-back symptom requires a live multi-agent run (/review on a PR) with a configured API key — this CI environment has no API key, so the interactive scroll behavior could not be exercised here. The regression is covered by unit tests (dedup, VP gate, height cap, thinking-block contract) but live TUI confirmation requires manual reviewer testing.

中文说明

代码审查

独立提案(阅读 diff 前):根据两个症状——并行 agent 运行时滚动弹回和非 VP 滚轮劫持——我会做两件事:(1) 阻止 inline 并行面板重复 LiveAgentPanel 已拥有的运行中 agent,缩小非 <Static> 帧;(2) 在 useMouseEvents 中加入 VP 模式门控(ui.useTerminalBuffer),并为无条件拥有滚轮的面板提供 bypass。这正是 PR 的做法。

发现: 无阻塞问题。

VP 门控逻辑正确,enabled 现在需要 vpGateOpen(isVpMode || bypassVpGate)加上原有的 isActive && isRawModeSupporteduseContext(SettingsContext) 是正确的位置,ui.useTerminalBuffer 是规范的 VP 信号。新的 MouseEventsOptions 接口带 bypassVpGate 是清晰的单一收口点 API,未来的鼠标订阅者无法意外绕过。

ToolGroupMessage.tsx 的去重正确地通过 inlineToolCalls 路由而非原始 toolCalls。全运行中时 return null 防止空容器。totalAgentCount 保持 header 准确。仅在 live 阶段转发 availableTerminalHeightisPending ? ... : undefined)是正确的——committed 阶段的行已在 <Static> 中无弹回风险,加 cap 会在 scrollback 中永久隐藏已完成的 agent。

InlineParallelAgentsDisplay.tsx 的高度窗口化是正确的兜底。wrap="truncate-end" 强制了预算数学依赖的每行 1 行不变量。退化场景处理(预算 ≤ 1 仅保留 header)合理。

测试全面:纯并行 live 阶段去重、header 计数、高度窗口化精确边界(>>=)、溢出指示器、VP 门控开/关/bypass、thinking 块不使用 bypass、ThinkingViewer 使用 bypass。

测试

单元测试: PR 分支 worktree 中 6 个受影响测试文件全部 104/104 通过。

Typecheck: 全部 7 个包干净(exit 0)。

构建: 干净(exit 0)。仅 vscode-ide-companion 中有预存的 lint 警告(无关)。

Tmux 测试(PR 分支 dev build): PR 分支启动正常,无运行时错误,正常响应。实际滚动弹回症状需要带配置 API key 的真实多 agent 运行(/review),此 CI 环境无 API key,故交互滚动行为无法在此验证。回归已由单元测试全面覆盖,但 live TUI 确认需要审查者手动测试。

Qwen Code · qwen3.7-max

@qwen-code-ci-bot

qwen-code-ci-bot commented Jun 29, 2026

Copy link
Copy Markdown
Collaborator

Looking at the whole picture: the PR identifies two distinct, well-understood root causes (ink shouldClearTerminalForFrame clearing scrollback when the non-<Static> frame exceeds the viewport, and SGR mouse tracking hijacking the wheel) and fixes both with minimal, targeted changes. The independent proposal I formed before reading the diff matches what the PR does — there's no simpler path missed.

The code is straightforward and follows project conventions. No over-abstraction, no speculative features, no drive-by refactors. Each edit in the diff is necessary for the stated goal. The bypassVpGate opt-in pattern is a single chokepoint that future mouse subscribers can't accidentally bypass — good defensive design without being over-engineered. The height windowing backstop is similarly well-bounded: it guards only the live phase, leaves committed scrollback untouched, and handles degenerate budgets gracefully.

Tests are thorough and well-reasoned. The exact-fit boundary test (> not >=) pins the precise regression point. The thinking-block contract test and ThinkingViewer test are mirror images — one pins "does NOT bypass", the other pins "DOES bypass" — making it impossible for either side to silently regress. The existing mechanism tests were correctly updated to opt out of the new gate rather than silently changing behavior.

The live TUI symptom couldn't be reproduced on this CI (no API key), but the unit tests cover the mechanism thoroughly. A reviewer with a configured environment should do a quick /review on any PR in a short terminal to confirm the scroll works — that's the one gap.

Approving. This is a clean, focused bugfix that solves a real problem without adding complexity the codebase doesn't need.

中文说明

纵观全貌:PR 识别了两个独立、理解透彻的根因(ink shouldClearTerminalForFrame 在非 <Static> 帧超过视口时清除 scrollback,以及 SGR 鼠标追踪劫持滚轮),并以最小化、有针对性的改动修复了两者。我在阅读 diff 前形成的独立提案与 PR 的做法一致——没有遗漏更简单的路径。

代码简洁,遵循项目规范。无过度抽象,无投机性功能,无顺手重构。diff 中的每处编辑都是实现目标所必需的。bypassVpGate opt-in 模式是单一收口点,未来的鼠标订阅者无法意外绕过——好的防御性设计而非过度工程。高度窗口化兜底同样边界清晰:仅守护 live 阶段,不影响 committed scrollback,优雅处理退化预算。

测试全面且推理清晰。精确边界测试(>>=)钉住了确切的回归点。thinking 块契约测试和 ThinkingViewer 测试互为镜像——一个钉住「不绕过」,另一个钉住「绕过」——使任一侧都无法悄悄回归。现有机制测试正确地 opt-out 了新门控而非悄悄改变行为。

Live TUI 症状无法在此 CI 复现(无 API key),但单元测试全面覆盖了机制。有配置环境的审查者应在短终端中快速运行 /review 确认滚动正常——这是唯一的缺口。

批准。这是一个干净、聚焦的 bug 修复,解决了真实问题,没有添加代码库不需要的复杂度。

Qwen Code · qwen3.7-max

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM, looks ready to ship. ✅

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

⚠️ Downgraded from Approve to Comment: CI still running.

No critical issues found. Two minor suggestions below.

— qwen3.7-max via Qwen Code /review

Comment thread packages/cli/src/ui/components/messages/InlineParallelAgentsDisplay.tsx Outdated
Comment thread packages/cli/src/ui/components/messages/ToolGroupMessage.tsx Outdated

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

No review findings. Downgraded from Approve to Comment: CI is still running (1 check pending).

— glm-5.2 via Qwen Code /review

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] InlineParallelAgentsDisplay.tsx:16 — Module docstring states "Rendered in the committed phase only" but this PR changes ToolGroupMessage to render the component in both phases (live phase with filtered inlineToolCalls + totalAgentCount; committed phase with full list). Consider updating the docstring to reflect dual-phase usage.

— qwen3.7-max via Qwen Code /review

Comment thread packages/cli/src/ui/components/messages/ToolGroupMessage.tsx Outdated
Comment thread packages/cli/src/ui/components/ThinkingViewer.tsx

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

⚠️ Downgraded from Approve to Comment: CI still running. No critical issues found; four suggestions below.

— qwen3.7-max via Qwen Code /review

Comment thread packages/cli/src/ui/hooks/useMouseEvents.ts
Comment thread packages/cli/src/ui/components/messages/ToolGroupMessage.tsx
doudouOUC
doudouOUC previously approved these changes Jun 29, 2026

@doudouOUC doudouOUC left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

No issues found. LGTM! ✅

Two well-targeted fixes for non-VP scroll snap-back and wheel hijack. Root-cause analysis is thorough, the height windowing and VP gate logic handle all edge cases correctly, and test coverage is strong (100/100 tests pass across 5 suites). The 2 low-confidence observations (missing singular overflow text test, missing background agent status test) are trivial branches with minimal risk.

— bailian/glm-5.2 via Qwen Code /review

秦奇 and others added 5 commits June 30, 2026 00:34
In non-VP mode ink clears the whole terminal (including scrollback) on every
repaint once the non-<Static> live frame exceeds the terminal height (see
ink's shouldClearTerminalForFrame). During a parallel-agent run the
pure-parallel inline panel rendered the UNFILTERED toolCalls, so running
subagents were shown inline AND in LiveAgentPanel below the composer — two
full rosters that push the live frame past the viewport. The per-second
elapsed/token ticks then fire that clear continuously, so scroll-up snaps
straight back to the bottom and flickers.

Route the pure-parallel branch through the same inlineToolCalls hand-off as
every other group: during the live phase render only the agents the panel is
not showing (terminal rows en route to <Static>), keeping the header total
honest via totalAgentCount. Add an availableTerminalHeight backstop to
InlineParallelAgentsDisplay that windows the rows ("+N more") so the panel can
never exceed its height budget.

Refs QwenLM#5798

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
…crollback

Enabling SGR mouse tracking (?1002h) makes the host terminal stop doing native
scrollback on the wheel — it hands wheel events to the app (Terminal.app even
diverts them to arrow keys, which then drive composer input history and the
background-agent panel). That is only acceptable when the app itself owns the
wheel: VP mode (ScrollableList) or an alternate-screen modal (ThinkingViewer),
neither of which relies on main-screen scrollback. But a collapsed thinking
block armed mouse tracking just for click-to-expand, unconditionally, so any
non-VP session with thinking blocks could no longer scroll its transcript.

Make useMouseEvents gate on VP mode by default: mouse tracking turns on only
when `ui.useTerminalBuffer` is set. Surfaces that legitimately own the wheel
pass the new `bypassVpGate` option (ScrollableList, ThinkingViewer). The
thinking-block click handler does not, so in non-VP it stays dormant and native
terminal scrollback is preserved (the block still expands via Alt+T). This is a
single chokepoint, so future non-VP mouse subscribers can't reintroduce the
regression.

Refs QwenLM#5798

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
…filters

Address review feedback on the parallel-agent snap-back fix:

- InlineParallelAgentsDisplay: the height backstop kept Math.max(1, budget-2)
  rows, so at availableTerminalHeight <= 2 it rendered header + indicator + 1
  data row (3 lines) and overflowed the very budget meant to cap it, which can
  re-trigger the shouldClearTerminalForFrame snap-back. Reserve the header and
  (when overflowing) the indicator first, then window the remaining rows; at a
  budget of 1 keep only the header (its label still states the total). Total
  rendered height now never exceeds the budget. Adds budget=1/2 regression tests.

- ToolGroupMessage: isPureParallelAgentGroup already guarantees every entry is
  a subagent, so the isSubagentToolEntry filters on inlineToolCalls and on
  totalAgentCount were no-ops; use inlineToolCalls and toolCalls.length directly.

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Address /review feedback:

- ToolGroupMessage: only forward availableTerminalHeight to
  InlineParallelAgentsDisplay during the live phase (isPending). The height
  backstop guards the non-<Static> live frame; once committed the rows live in
  <Static> with no snap-back risk, and MainContent passes staticAreaMaxItemHeight
  (>=100) for committed items. Forwarding that let the cap fire on scrollback and
  could permanently hide completed agents behind a static "+N more". Pass
  undefined when committed, per the component's documented contract.

- InlineParallelAgentsDisplay.test: add a "generous budget" case (10 agents,
  budget 20) pinning the `rows.length + 1 > budget` boundary so a regression to
  >= would be caught instead of silently truncating when there is room.

- ThinkingViewer.test (new): pin that the modal wheel handler subscribes WITH
  bypassVpGate: true (mirror of the HistoryItemDisplay test that pins the
  opposite contract), so a future refactor dropping the flag can't silently
  break wheel scrolling in non-VP mode.

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Address /review test-coverage feedback:

- ToolGroupMessage.test: add a contrast case that renders the same many-agent
  group with isPending={true} vs isPending={false} at the same tight
  availableTerminalHeight, asserting the overflow indicator appears only in the
  live phase and every agent renders in the committed phase. Pins the
  load-bearing `isPending ? availableTerminalHeight : undefined` conditional.

- InlineParallelAgentsDisplay.test: add the budget=3 boundary case (the first
  budget at which a single data row appears: rowsFit = budget - 2 = 1), so an
  off-by-one in the windowing arithmetic is caught at the transition point
  between budget=2 (zero rows) and budget=3 (one row).

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
@chiga0
chiga0 force-pushed the fix/non-vp-scrolling branch from 3b02006 to 0ee8cc2 Compare June 29, 2026 16:34
…stop relies on

Address /review feedback: the windowing math (rowsFit = availableTerminalHeight
- 2) counts exactly one terminal line per AgentRow. That invariant — held today
by the wrap="truncate-end" Texts in AgentRow — was undocumented, so a future
change adding multi-line content (wrapped activity column, progress bar) would
silently overflow the budget and re-trigger the shouldClearTerminalForFrame
snap-back this PR fixes. Add the invariant at both the AgentRow definition and
its render site. Comment-only; no behavior change.

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Comment thread packages/cli/src/ui/components/messages/InlineParallelAgentsDisplay.test.tsx Outdated
Address /review feedback: the "generous budget" case used budget=20 with 10
agents, but `11 > 20` and `11 >= 20` are both false, so a regression from `>` to
`>=` in `rows.length + 1 > availableTerminalHeight` would go undetected. Use
budget=11 (= rows.length + 1) instead: `11 > 11` is false (no windowing) while
`11 >= 11` is true (windowing fires a spurious "+1 more"). The not.toContain(
'more agent') assertion catches the flip — the line count coincides at 11 either
way, so it is the indicator, not the height, that pins the boundary.

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Qwen Code review did not complete successfully. Qwen review aborted with an API error before posting comments. See workflow logs.

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

No review findings. Downgraded from Approve to Comment: CI still running.

— qwen3.7-max via Qwen Code /review

…e usage

Address /review feedback: the module docstring claimed the panel is "Rendered in
the committed phase only", but this PR routes it through ToolGroupMessage's
inlineToolCalls hand-off so it renders in BOTH phases — live phase showing only
terminal agents (running/background owned by LiveAgentPanel) under an
availableTerminalHeight windowing cap, committed phase showing the full roster
uncapped. Update the docstring to describe the dual-phase behaviour and the
height backstop. Comment-only; no behaviour change.

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
@chiga0

chiga0 commented Jun 29, 2026

Copy link
Copy Markdown
Collaborator Author

已处理 review (qwen-code-ci-bot, 15:09) body 中关于 InlineParallelAgentsDisplay 模块 docstring 的建议:该 PR 让此面板在 live/committed 两个阶段都会渲染(live 仅显示终态 agent 并受 availableTerminalHeight 窗口化兜底,committed 显示完整名册且无 cap),原 docstring「Rendered in the committed phase only」已过时。已在 commit 58601ae 更新为双阶段说明。

至此该轮自动评审的全部 findings(含此条 review-body 建议)均已落实,CI 实质检查(Test 等)全绿、无未解决 review thread。

Comment thread packages/cli/src/ui/components/messages/InlineParallelAgentsDisplay.tsx Outdated
Address /review [Critical]: the height backstop budgets the header, the optional
"+N more" overflow indicator, and each AgentRow as exactly 1 terminal line, but
the header Text, the indicator Text, and the AgentRow trailing (elapsed · tokens)
Text lacked wrap="truncate-end". On a narrow terminal any of them could wrap to
2+ lines, pushing the rendered frame past availableTerminalHeight and re-firing
the shouldClearTerminalForFrame snap-back this PR fixes — and it made the
documented "all Text elements use truncate-end" invariant false. Add
wrap="truncate-end" to every Text in the panel (header, indicator, glyph,
trailing) so the 1-line invariant actually holds. No behaviour change at normal
widths (the labels already fit); tightens the degenerate-width guarantee.

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

No review findings. Downgraded from Approve to Comment: CI still running.

— qwen3.7-max via Qwen Code /review

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

No issues found. LGTM! ✅

Well-designed fix for two independent non-VP scroll regressions — the dedup of running agents through inlineToolCalls and the VP gate on SGR mouse tracking are both clean, focused solutions. Test coverage is thorough (103 tests passing), covering boundary conditions, phase transitions, and the VP-gate mechanism.

— qwen3.7-max via Qwen Code /review

@DragonnZhang DragonnZhang left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Automated code review — no high-confidence critical findings in the changed code at this commit.

Reviewed InlineParallelAgentsDisplay.tsx height windowing, useMouseEvents.ts VP gate, and ToolGroupMessage.tsx routing. The > strict boundary in rows.length + 1 > availableTerminalHeight correctly handles the exact-fit case. Forwarding availableTerminalHeight only during the live phase (not committed) is load-bearing to preserve full scrollback history. No critical issues found.


Generated by Claude Code

@wenshao

wenshao commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /triage

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM, looks ready to ship. ✅

@wenshao
wenshao added this pull request to the merge queue Jun 30, 2026
Merged via the queue into QwenLM:main with commit 8b4871b Jun 30, 2026
48 checks passed
@wenshao

wenshao commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator

✅ Maintainer local verification — real binary + real components

Built the real qwen binary from this PR's head (14b4171, 9 commits ahead of merge-base 565e4bc; npm ci && npm run build, v0.19.3) and verified both root causes with a real PTY (node-pty) and the real React/Ink components — not just unit tests. Verdict: both fixes do exactly what they claim; recommend merge.


1. Automated suites — 104/104 green

The 5 suites in the test plan, plus the new ThinkingViewer.test.tsx:

Suite Tests
InlineParallelAgentsDisplay.test.tsx 12
ToolGroupMessage.test.tsx 45
useMouseEvents.test.tsx 4 (3 VP-gate + 1 ref-count)
HistoryItemDisplay.test.tsx 28
ScrollableList.test.tsx 14
ThinkingViewer.test.tsx 1
Total 104 passed

One ScrollableList wheel test (routes wheel SGR events…, pre-existing, unchanged by this PR) timed out once at the default 5 s under post-build CPU load; it passes in isolation (5.16 s) and in a clean full run. Environmental, not a regression.

2. Mutation tests — the regression tests are load-bearing

Reverting each source file to base while keeping the PR's tests (vitest, runtime):

Reverted to base Result
useMouseEvents.ts non-VP without bypass: does NOT enable mouse mode fails (base always enables)
ToolGroupMessage.tsx ❌ the 3 live-phase dedup tests fail (committed-phase test still passes — correct)
InlineParallelAgentsDisplay.tsx ❌ the 4 windowing-backstop tests fail (no-cap / exact-fit tests still pass — correct)

3. Fix 2 (mouse-tracking VP gate) — real-binary A/B under a PTY 🎯

Drove the real TUI under node-pty, made a fake OpenAI endpoint stream reasoning_content so a genuine thinking block commits (Thought for 1s), and captured every byte the app writes. Probe = \x1b[?1002h (ENABLE_SGR_MOUSE) — when it's emitted the host terminal stops doing native wheel scrollback.

Scenario Build \x1b[?1002h emitted? Meaning
non-VP (default) base YES — right when the thought committed (enableAfterAnswer=true) wheel hijacked = the bug (#5798)
non-VP (default) fixed NO native scrollback preserved ✅
VP (useTerminalBuffer) fixed YES — at boot viewport (ScrollableList) owns the wheel; VP unaffected ✅

This is exactly the regression and exactly the fix: the only non-VP main-screen mouse subscriber is the collapsed thinking block (ClickableThinkMessage, 1 of the 3 useMouseEvents callers). On base it arms ?1002h the moment the block commits; the gate makes it stay dormant in non-VP. The base binary's built dist was confirmed ungated (const enabled = isActive && isRawModeSupported) before the run.

4. Fix 1 (parallel-agent roster dedup + height backstop) — real-component A/B

Rendered the real ToolGroupMessage + InlineParallelAgentsDisplay in the live phase (isPending) and printed the actual frame. Reverting the two source files flips base↔fixed (vitest aliases to src).

4a. Dedup — group of 1 completed + 2 running, budget 6:

BASE (4 lines)                          FIXED (2 lines)
 Parallel agents · 3 · 1/3 done          Parallel agents · 3 · 1/3 done
 ✔ AUDIT_DONE                            ✔ AUDIT_DONE
 ○ SECURITY_RUNNING   ← duplicated
 ○ PERF_RUNNING       ← duplicated       (running agents owned by LiveAgentPanel)

Base renders the running agents inline and in LiveAgentPanel below the composer — two full rosters. Fixed shows only the terminal agent inline; the header tally stays honest (3 · 1/3 done).

4b. Height backstop8 completed agents, budget 5:

BASE (9 lines — overflows budget)       FIXED (5 lines — == budget)
 Parallel agents · 8 · 8/8 done          Parallel agents · 8 · 8/8 done
 ✔ AGENT_0 … ✔ AGENT_7  (all 8)          … +5 more agents
                                         ✔ AGENT_5 / AGENT_6 / AGENT_7

Base's live (non-<Static>) frame overshoots the terminal height — precisely the condition that makes ink clearTerminal on every repaint and snaps the scroll back. The windowing cap holds the frame at the budget.


Methodology notes / honesty

  • Fix 2 is a full real-binary A/B (actual qwen process, real PTY, real bytes).
  • Fix 1 is verified at the real-component level (the exact ToolGroupMessage/InlineParallelAgentsDisplay the binary renders, driven through the live-phase code path) plus mutation. The dedup logic is pure (isPending-driven, not env-gated), so the rendered frames match what the binary draws; the frame-size reduction shown is the direct cause of the snap-back the PR fixes. I did not stage a live multi-agent /review because the deterministic component A/B already pins the mechanism.
  • VP mode confirmed unaffected (mouse still armed); the only intentional non-VP behavior loss is click-to-expand on thinking blocks, which still expand via Alt+T (advertised on the block).
中文版(完整对应)

✅ 维护者本地验证 — 真实二进制 + 真实组件

从本 PR head(14b4171,领先 merge-base 565e4bc 9 个提交;npm ci && npm run build,v0.19.3)构建了真实 qwen 二进制,并用真实 PTY(node-pty)+ 真实 React/Ink 组件验证了两个根因 —— 不只是跑单测。结论:两处修复都名副其实,建议合并。

1. 自动化测试 — 104/104 全绿

测试计划里的 5 个套件 + 新增的 ThinkingViewer.test.tsx

套件 用例数
InlineParallelAgentsDisplay.test.tsx 12
ToolGroupMessage.test.tsx 45
useMouseEvents.test.tsx 4(3 个 VP-gate + 1 个引用计数)
HistoryItemDisplay.test.tsx 28
ScrollableList.test.tsx 14
ThinkingViewer.test.tsx 1
合计 104 通过

有一个 ScrollableList 滚轮测试(routes wheel SGR events…本 PR 未改动的存量测试)在构建后 CPU 高负载下以默认 5s 超时挂了一次;单独跑(5.16s)和干净的整套跑都通过。属环境抖动,非回归。

2. 变异测试 — 回归测试确实承重

把每个源文件还原到 base、保留 PR 的测试(vitest 运行时):

还原到 base 结果
useMouseEvents.ts non-VP without bypass: does NOT enable mouse mode 挂(base 永远启用鼠标)
ToolGroupMessage.tsx ❌ 3 个 live 阶段去重测试挂(committed 阶段测试仍过 —— 正确)
InlineParallelAgentsDisplay.tsx ❌ 4 个窗口化兜底测试挂(无预算 / 恰好放下 的测试仍过 —— 正确)

3. Fix 2(鼠标追踪 VP 门)— PTY 下的真实二进制 A/B 🎯

用 node-pty 驱动真实 TUI,用伪 OpenAI 端点流式返回 reasoning_content 让真实 thinking 块落定(Thought for 1s),抓取 app 写出的每个字节。探针 = \x1b[?1002hENABLE_SGR_MOUSE)—— 一旦发出,宿主终端就不再对滚轮做原生 scrollback。

场景 构建 是否发出 \x1b[?1002h 含义
非 VP(默认) base —— 恰在 thought 落定时(enableAfterAnswer=true 滚轮被劫持 = bug(#5798
非 VP(默认) fixed 原生 scrollback 保住 ✅
VP(useTerminalBuffer fixed —— 启动即开 视口(ScrollableList)独占滚轮;VP 不受影响 ✅

这正是回归、也正是修复:非 VP 主屏唯一的鼠标订阅者就是折叠的 thinking 块(ClickableThinkMessage,3 个 useMouseEvents 调用方之一)。base 上它在块落定瞬间开 ?1002h;门控让它在非 VP 下休眠。跑之前已确认 base 二进制的 dist 是未门控版(const enabled = isActive && isRawModeSupported)。

4. Fix 1(并行 agent 名册去重 + 高度兜底)— 真实组件 A/B

在 live 阶段(isPending)渲染真实的 ToolGroupMessage + InlineParallelAgentsDisplay 并打印实际帧。还原这两个源文件即可在 base↔fixed 间切换(vitest 别名到 src)。

4a. 去重 —— 1 完成 + 2 运行中 的组,预算 6:

BASE(4 行)                            FIXED(2 行)
 Parallel agents · 3 · 1/3 done          Parallel agents · 3 · 1/3 done
 ✔ AUDIT_DONE                            ✔ AUDIT_DONE
 ○ SECURITY_RUNNING   ← 重复
 ○ PERF_RUNNING       ← 重复             (运行中的 agent 由 LiveAgentPanel 独占)

base 把运行中的 agent 在 inline 下方的 LiveAgentPanel 各渲染一份 —— 两份完整名册。fixed 只在 inline 显示终态 agent;表头计数仍诚实(3 · 1/3 done)。

4b. 高度兜底 —— 8 个完成 的 agent,预算 5:

BASE(9 行 —— 超预算)                  FIXED(5 行 —— == 预算)
 Parallel agents · 8 · 8/8 done          Parallel agents · 8 · 8/8 done
 ✔ AGENT_0 … ✔ AGENT_7(全 8 个)        … +5 more agents
                                         ✔ AGENT_5 / AGENT_6 / AGENT_7

base 的 live(非 <Static>)帧超过终端高度 —— 这正是让 ink 每帧 clearTerminal、把上滚弹回的条件。窗口化兜底把帧压在预算之内。

方法论说明 / 诚实交代

  • Fix 2 是完整的真实二进制 A/B(真实 qwen 进程、真实 PTY、真实字节)。
  • Fix 1真实组件层验证(二进制实际渲染的那两个组件,走 live 阶段代码路径)+ 变异。去重逻辑是纯函数(由 isPending 驱动、不受环境门控),故渲染帧与二进制所绘一致;所示帧高缩减就是本 PR 所修 snap-back 的直接成因。未实搭 live 多 agent /review,因为确定性的组件 A/B 已经钉死了机制。
  • 已确认 VP 模式不受影响(鼠标仍开启);非 VP 唯一有意舍弃的是 thinking 块的点击展开,而它仍可用 Alt+T 展开(块上已标注)。

Verified locally on macOS (Darwin 25.5.0, node v22.22.2) in an isolated worktree at PR head. Raw PTY byte captures + per-suite logs retained.

chiga0 pushed a commit to chiga0/qwen-code that referenced this pull request Jun 30, 2026
Resolve conflict in ToolGroupMessage.tsx: combine this branch's
transcript full-detail guard (skip the dense parallel-agent panel so
each agent renders its own full ToolMessage) with main's QwenLM#6015 change
that routes the pure-parallel-agent group through the shared
`inlineToolCalls` hand-off (avoids the non-VP scrollback snap-back,
QwenLM#5798). Both now apply: `!fullDetail` gates entry, then the
`inlineToolCalls.length === 0` early return from main runs inside.

Also drop the stale `CompactModeProvider` wrapper that main's new
"thinking-block mouse tracking is VP-gated" test added in
HistoryItemDisplay.test.tsx — this branch retired the CompactMode
context, so the block now renders via renderWithProviders directly.

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
// Click-to-expand needs SGR mouse tracking. We do NOT pass `bypassVpGate`, so
// useMouseEvents enables it only in VP mode; in non-VP the click handler
// stays dormant and native terminal scrollback is preserved (the block still
// expands via Alt+T — the "option+t to expand" affordance it already shows).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] The comment says "Click-to-expand" and "the block still expands via Alt+T," but the click handler at line 134 calls openThinkingViewer(...) — it opens the modal viewer, not inline expansion. Alt+T (AppContainer.tsx:3290) calls setThoughtExpanded — it toggles inline expansion only. These are distinct features. After the VP gate, the ThinkingViewer modal becomes completely inaccessible in non-VP mode: no keyboard shortcut opens it, and the click handler is dormant. The comment's "the block still expands via Alt+T" is misleading because it implies the click's functionality (modal) is covered by Alt+T (inline), when they are different.

Suggested change
// expands via Alt+T — the "option+t to expand" affordance it already shows).
// Click-to-open-viewer needs SGR mouse tracking. We do NOT pass `bypassVpGate`, so
// useMouseEvents enables it only in VP mode; in non-VP the click handler
// stays dormant and native terminal scrollback is preserved. The block still
// expands inline via Alt+T, but the full-screen modal viewer is only
// accessible in VP mode (no keyboard shortcut opens it in non-VP).

— glm-5.2 via Qwen Code /review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants