qwen-code/scripts
Shaojin Wen cae09279fa
fix(cli): bound SubAgent display by visual height to prevent flicker (#3721)
* fix(cli): bound SubAgent display by visual height to prevent flicker

The SubAgent runtime display used hard-coded MAX_TASK_PROMPT_LINES=5 and
MAX_TOOL_CALLS=5 plus character-length truncation (`length > 80`). On narrow
terminals the soft-wrapped content overflowed the available height as the
tool-call list grew, forcing Ink to clear and redraw on every update.

Pull AgentExecutionDisplay onto the same visual-height/visual-width slicing
pattern that ToolMessage and ConversationMessages already use:

- Add `sliceTextByVisualHeight` to textUtils — counts soft wraps as visual
  rows, supports top/bottom overflow direction.
- AgentExecutionDisplay now derives maxTaskPromptLines / maxToolCalls from
  the assigned `availableHeight` and uses `truncateToVisualWidth` (CJK +
  emoji safe) instead of substring(0, 80). Compact mode is unchanged.
- Drop the 300 ms debounced `refreshStatic` AppContainer fired on every
  terminalWidth change — that was a flicker source on resize and the
  static area no longer needs the refresh.

Tests:
- textUtils.test.ts covers undefined maxHeight, top/bottom overflow, and
  soft-wrap counting.
- AgentExecutionDisplay.test.tsx asserts the height-bounded render keeps
  the prompt + tool list inside the assigned rows.
- AppContainer.test.tsx asserts width-only changes no longer clear the
  terminal.

* test(tui): add SubAgent flicker regression script and ANSI counter

Two reusable tools for measuring TUI flicker:

- `scripts/measure-flicker.mjs` — standalone Node script that counts the
  ANSI escape sequences which betray flicker (clearTerminalPair, clearScreen,
  eraseLine, cursorUp) inside any recorded raw stream (`script` log,
  `tmux pipe-pane` output, custom PTY capture). Supports baseline diff mode.

- `integration-tests/terminal-capture/subagent-flicker-regression.ts` —
  end-to-end ratchet that boots a mock OpenAI server, drives a real qwen
  process through an `agent` tool dispatch + 5 `read_file` SubAgent rounds,
  then reads PTY bytes and asserts ANSI-redraw counts stay below configured
  ceilings. Mirrors PR #43f128b20's resize-clear-regression pattern.

Reference numbers (60-col / 18-row terminal, fixed build):
  clearTerminalPair=5, clearScreen=10, eraseLine=440, cursorUp=132

The ratchet defaults to 10/20 ceilings — roughly 2× steady state — so
regressions like reverting sliceTextByVisualHeight or restoring the
width-driven refreshStatic trip the build.

Implementation notes captured in the script's docstring:
  - Strips HTTP_PROXY family env vars (NO_PROXY isn't honored by undici,
    so corp proxy would otherwise hijack the loopback request).
  - Drops `--bare` (bare mode hard-codes the registered tool set and
    rejects the `agent` tool); HOME is sandboxed to a temp dir instead.
  - Mock server speaks SSE because the CLI requests stream:true.

* fix(cli): address inline review on SubAgent flicker fix

Three issues from inline review on PR #3721:

1. **availableHeight as total budget (Critical).** The previous formula
   only constrained prompt + tool-call height, not the surrounding
   header / section labels / gaps / footer. Default and verbose mode
   could still overrun the parent-provided budget. Subtract a fixed-row
   overhead (10 rows running, 18 completed) before computing
   `maxTaskPromptLines` / `maxToolCalls`. Add unit tests that assert the
   rendered frame line-count stays within `availableHeight` for both
   running and completed states.

2. **Ratchet that actually distinguishes fix from no-fix.** The previous
   `clearTerminalPair` / `clearScreen` ceilings passed for both fixed
   and unfixed builds. Add an `eraseLine` upper bound (default 460) —
   that's the metric whose drop reflects the in-place-update efficiency
   the visual-height fix delivers (no-fix observed 469, with-fix 434).
   Refresh docstring with the current numbers and a coverage map that
   honestly states what this ratchet does and does not exercise.

3. **Keypress scope.** `useKeypress` was active on every mounted
   `AgentExecutionDisplay`, including completed/historical instances in
   chat history — Ctrl+E / Ctrl+F would toggle them all in lock-step
   and cause large scrollback reflows. Gate `isActive` on
   `data.status === 'running'`. Test mock now also honors
   `{ isActive }` so the new "completed displays ignore Ctrl+E"
   regression is enforceable.

* fix(cli): address round-2 inline review on SubAgent flicker

Three follow-up issues from inline review on PR #3721:

1. **sliceTextByVisualHeight reservedRows early-return (Critical).**
   The early return compared `visualLineCount <= targetMaxHeight` and
   ignored `reservedRows`, so a caller asking us to keep one row free
   for a footer could still receive the full input back with
   `hiddenLinesCount: 0` even though only `targetMaxHeight - reservedRows`
   content rows were actually available. Compare against
   `visibleContentHeight` instead and add a regression test for the
   `'a\nb\nc' / 3 / reservedRows: 1` case the reviewer flagged.

2. **Footer hint and rendered prompt now share one slicing result
   (Suggestion).** Previously `hasMoreLines` looked at
   `data.taskPrompt.split('\n').length` (hard newlines only), but the
   prompt body was already truncated by `sliceTextByVisualHeight` (which
   counts soft wraps). A long single-line prompt could be visually
   truncated without the footer ever surfacing the "ctrl+f to show
   more" hint. Lift the slice into the parent component and feed both
   the rendered `TaskPromptSection` and the footer's `hasMoreLines`
   from the same `hiddenLinesCount`.

3. **Running → completed transition test (Critical).** The previous
   "completed displays ignore Ctrl+E" test rendered already-completed
   data, so `useKeypress` was inactive from the start and Ctrl+E was a
   no-op trivially. It missed the real path: a running subagent gets
   expanded, then completes while preserving the expanded
   `displayMode` — which is exactly when the completed-state budget
   has to hold the layout. Replace the test with a `rerender`-based
   one that runs the full transition, asserts the completed expanded
   frame stays within `availableHeight`, and asserts the post-transition
   Ctrl+E is a no-op. Bumped `COMPLETED_FIXED_OVERHEAD` from 18 to 22
   to accommodate the ExecutionSummary + ToolUsage block accounting
   that the new transition test exposed.

* fix(cli): gate SubAgent useKeypress on isFocused for parallel runs

Per @yiliang114's review on PR #3721 — `data.status === 'running'` alone
fixes the historical/scrollback case but two SubAgents running in parallel
both stay `running`, so a single Ctrl+E / Ctrl+F still toggles them in
lock-step and the dual reflow brings back the flicker the gating was meant
to prevent. The component already receives `isFocused` from ToolMessage
(via SubagentExecutionRenderer) for the inline confirmation prompt — reuse
it on the keypress hook:

  isActive: data.status === 'running' && isFocused

Adds a regression test that renders a running SubAgent with
`isFocused={false}` and asserts Ctrl+E is a no-op (frame unchanged).

---------

Co-authored-by: wenshao <wenshao@U-K7F6PQY3-2157.local>
2026-04-29 22:34:55 +08:00
..
installation start qwen after installation 2026-03-11 04:01:22 -07:00
tests 📦 Release qwen-code CLI as a Standalone Bundled Package (#866) 2025-10-24 17:08:59 +08:00
benchmark-api-latency.mjs feat(cli): add API preconnect to reduce first-call latency (#3318) 2026-04-27 06:54:55 +08:00
build.js feat(core): detect tool validation retry loops and inject stop directive (#3178) 2026-04-18 10:24:46 +08:00
build_package.js chore: consistently import node modules with prefix (#3013) 2025-08-25 20:11:27 +00:00
build_sandbox.js fix(sandbox): fall back to 'latest' tag when image name has no colon (#2962) 2026-04-18 09:07:05 +08:00
build_vscode_companion.js Sync upstream Gemini-CLI v0.8.2 (#838) 2025-10-23 09:27:04 +08:00
check-build-status.js Merge tag 'v0.3.0' into chore/sync-gemini-cli-v0.3.0 2025-09-11 16:26:56 +08:00
check-i18n.ts feat(cli): add Traditional Chinese (zh-TW) as a UI language option (#3569) 2026-04-24 21:34:46 +08:00
check-lockfile.js Sync upstream Gemini-CLI v0.8.2 (#838) 2025-10-23 09:27:04 +08:00
clean.js fix(scripts): remove duplicate bundle rmSync in clean script (#2964) 2026-04-18 09:13:34 +08:00
copy_bundle_assets.js feat: replace qwen-settings-config with bundled qc-helper skill 2026-03-27 12:03:00 +08:00
copy_files.js revert: remove unused script modifications 2026-02-10 14:34:36 +08:00
create_alias.sh fix: ambiguous literals (#461) 2025-08-27 15:23:21 +08:00
dev.js feat: replace qwen-settings-config with bundled qc-helper skill 2026-03-27 12:03:00 +08:00
esbuild-shims.js 📦 Release qwen-code CLI as a Standalone Bundled Package (#866) 2025-10-24 17:08:59 +08:00
generate-git-commit-info.js # 🚀 Sync Gemini CLI v0.2.1 - Major Feature Update (#483) 2025-09-01 14:48:55 +08:00
generate-settings-schema.ts fix(scripts): avoid 'undefined Options: ...' for enums without description (#2963) 2026-04-18 09:19:23 +08:00
get-release-version.js 📦 Release qwen-code CLI as a Standalone Bundled Package (#866) 2025-10-24 17:08:59 +08:00
lint.js Sync upstream Gemini-CLI v0.8.2 (#838) 2025-10-23 09:27:04 +08:00
local_telemetry.js Merge tag 'v0.3.0' into chore/sync-gemini-cli-v0.3.0 2025-09-11 16:26:56 +08:00
measure-flicker.mjs fix(cli): bound SubAgent display by visual height to prevent flicker (#3721) 2026-04-29 22:34:55 +08:00
pre-commit.js Sync upstream Gemini-CLI v0.8.2 (#838) 2025-10-23 09:27:04 +08:00
prepare-package.js fix: upgrade @lydell/node-pty to 1.2.0-beta.10 to fix PTY FD leak 2026-04-01 07:55:56 +08:00
sandbox_command.js refactor: unify sandbox configuration naming and improve telemetry config 2026-02-11 11:08:15 +08:00
start.js Rename GEMINI_CLI_NO_RELAUNCH to QWEN_CODE_NO_RELAUNCH 2025-12-11 11:14:12 +01:00
telemetry.js Merge tag 'v0.3.0' into chore/sync-gemini-cli-v0.3.0 2025-09-11 16:26:56 +08:00
telemetry_gcp.js fix(mcp): update OAuth client names and improve MCP commands 2026-02-08 10:46:48 +08:00
telemetry_utils.js Merge branch 'main' into feat/sandbox-config-improvements 2026-03-06 14:38:39 +08:00
test-rewind-e2e.sh fix(test): update rewind E2E Test 1 assertion after isRealUserTurn fix (#3622) 2026-04-26 06:49:42 +08:00
test-windows-paths.js chore: consistently import node modules with prefix (#3013) 2025-08-25 20:11:27 +00:00
unused-keys-only-in-locales.json feat(cli): add Traditional Chinese (zh-TW) as a UI language option (#3569) 2026-04-24 21:34:46 +08:00
version.js chore(channels): make plugin-example private and remove from release workflow 2026-04-01 20:43:45 +08:00