Commit graph

5 commits

Author SHA1 Message Date
Shaojin Wen
69da115dcf
feat(cli): combine elapsed + timeout in shell time indicator (#3512)
Some checks are pending
Qwen Code CI / Lint (push) Waiting to run
Qwen Code CI / Test (push) Blocked by required conditions
Qwen Code CI / Test-1 (push) Blocked by required conditions
Qwen Code CI / Test-2 (push) Blocked by required conditions
Qwen Code CI / Test-3 (push) Blocked by required conditions
Qwen Code CI / Test-4 (push) Blocked by required conditions
Qwen Code CI / Test-5 (push) Blocked by required conditions
Qwen Code CI / Test-6 (push) Blocked by required conditions
Qwen Code CI / Test-7 (push) Blocked by required conditions
Qwen Code CI / Test-8 (push) Blocked by required conditions
Qwen Code CI / Post Coverage Comment (push) Blocked by required conditions
Qwen Code CI / CodeQL (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run
* feat(cli): combine elapsed + timeout in shell time indicator

Render shell tools that have an explicit timeout as
`(elapsed · timeout N)` inline with the Running… status from t=0,
instead of splitting the information across the right-aligned elapsed
indicator and the ShellStatsBar row.

- formatters: add a `hideTrailingZeros` option so whole seconds render
  as `5s` rather than `5.0s` while fractional values like `5.5s` stay
  intact
- ToolElapsedTime: accept optional `timeoutMs`; when set, skip the 3s
  quiet threshold and render the combined `(elapsed · timeout N)` label
- ToolMessage: extract `timeoutMs` from AnsiOutputDisplay and feed it
  to ToolElapsedTime
- ShellStatsBar: drop its `timeoutMs` field (now inline); keeps
  `+N lines` and memory usage only
- Unify both modes on `formatDuration` so hour-range output is
  consistent (`1h 2m 6s` across timeout and no-timeout paths)

* feat(cli): thread shell timeoutMs through compact tool group display

The combined `(elapsed · timeout N)` format introduced in the previous
commit was only wired through the expanded ToolMessage path. Compact
tool groups kept rendering ToolElapsedTime without timeoutMs, so shell
tools displayed in compact mode silently dropped the timeout budget.

- CompactToolGroupDisplay: add getShellTimeoutMs() to pull timeoutMs
  off the active tool's AnsiOutputDisplay result (same shape used by
  ToolMessage) and feed it to ToolElapsedTime
- add CompactToolGroupDisplay.test.tsx covering the three paths:
  ansi display with timeoutMs, ansi display without timeoutMs, and
  non-ansi resultDisplay (string)
2026-04-23 08:52:37 +08:00
Shaojin Wen
5fedf10419
feat(cli): add tool execution progress messages (#3155)
* feat(cli): add tool execution progress messages with per-tool elapsed time, shell stats, and terminal progress bar

- Show per-tool elapsed time (Ns) next to spinner after 3 seconds of execution,
  covering all tools (not just shell), by piping existing core startTime through
  to the UI layer via IndividualToolCallDisplay.executionStartTime
- Add shell output statistics bar below ANSI output showing +N lines overflow
  count, byte size, and explicit timeout when set by user
- Add terminal tab progress bar via OSC 9;4 sequences for iTerm2, Ghostty, and
  ConEmu, with tmux/screen DCS passthrough support
- Extend AnsiOutputDisplay with optional totalLines/totalBytes/timeoutMs fields
- Add ShellStatsBar component for rendering shell output statistics

* fix(cli): address review feedback — use formatDuration for timeout, pass displayHeight to ShellStatsBar

- Use existing formatDuration() from formatters.ts instead of inline
  timeout formatting for correct precision (e.g., "2m 3s" not "2m")
- Add displayHeight prop to ShellStatsBar so +N lines overflow
  calculation respects actual terminal height, not hardcoded DEFAULT_HEIGHT

* fix(cli): guard terminal progress bar against non-TTY stdout

Check process.stdout.isTTY in isProgressBarSupported() so escape sequences
are not emitted when stdout is piped, redirected to log files, or running
in CI environments where TERM_PROGRAM may be set but stdout is not a TTY.

Also add defensive isProgressBarSupported() guard in the effect cleanup.

* fix(cli): format tool elapsed time with minutes/hours for long-running tools

Previously showed raw seconds (e.g. "3600s") for long-running tools.
Now formats as "3s" for under a minute, "1m 30s" for minutes, and
"2h 15m" for hours, while keeping compact integer seconds for short
durations.

* fix(cli): audit fixes for terminal progress and shell output stats

Three issues found by post-merge audit:

- useTerminalProgress: WT_SESSION was wrongly used to exclude Windows
  Terminal. WT 1.6+ actually supports OSC 9;4 progress sequences (per
  Microsoft docs), so treat it as a positive indicator like iTerm2 and
  Ghostty.

- useTerminalProgress: add process.on('exit'|'SIGINT'|'SIGTERM') handler
  that writes PROGRESS_CLEAR. Without it, killing the CLI mid-tool (Ctrl+C,
  SIGTERM) left the terminal tab stuck showing an indeterminate progress
  indicator because React cleanup never ran. Mirrors the useBracketedPaste
  cleanup pattern.

- shell.ts: ANSI totalBytes used token.text.length (character count),
  inconsistent with the string path's Buffer.byteLength(..., 'utf-8').
  Multi-byte chars (CJK, emoji) now count as their true UTF-8 byte length
  in both paths.

* refactor(cli): right-align tool elapsed time, extract to its own component

Move the executing-tool elapsed-seconds indicator out of
ToolStatusIndicator (where it sat immediately after the spinner on the
left edge) and into a new right-aligned ToolElapsedTime component.

The left placement caused layout jitter: every second the elapsed text
width would change (e.g. "9s" → "10s" → "1m" → "1m 15s"), shifting the
tool name and description horizontally. Right-aligning the elapsed keeps
the tool name anchored and only the far-right timer moves.

- New packages/cli/src/ui/components/shared/ToolElapsedTime.tsx owns the
  setInterval + formatElapsed logic.
- ToolStatusIndicator is now pure status again; the executionStartTime
  prop is gone from it.
- ToolMessage and CompactToolGroupDisplay mount ToolElapsedTime as the
  last flex child of the status row, with marginLeft=1.
- ToolInfo gains flexGrow=1 so the description fills the middle and the
  timer sits flush at the right edge of the row.

* fix(core): measure tool elapsed from executing-transition, not validating-entry

trackedCall.startTime is stamped when a tool is first registered with the
scheduler (validating state), then preserved through awaiting_approval,
scheduled, and executing transitions. Using it for the executing-row
elapsed display meant any approval-wait time was counted as execution
time — a tool that waited 30s for user approval would flash "30s"
immediately when it actually began running.

Add a separate executionStartTime on ExecutingToolCall, stamped at the
moment of the transition into 'executing', and pipe that through
useReactToolScheduler into IndividualToolCallDisplay.executionStartTime.
startTime is kept as-is for durationMs bookkeeping.

Also stops piping executionStartTime for validating/scheduled states,
since those don't have a meaningful execution duration yet.

* fix(cli): only hook 'exit' for terminal progress cleanup, not SIGINT/SIGTERM

Registering SIGINT/SIGTERM handlers that neither re-raise nor exit
inhibits Node's default termination behavior. If this hook were ever the
only signal handler in play, Ctrl+C would leave the process hanging.

Drop the signal handlers and rely on 'exit' alone. Other parts of the
CLI already own the signal-to-shutdown path (gemini.tsx, telemetry
shutdown, sharedTokenManager, etc.) and ultimately call process.exit(),
which fires 'exit' and runs this cleanup. SIGKILL cannot be cleaned up
either way.

* fix(cli): thread executionStartTime through agent-view tool groups

The main TUI renders per-tool elapsed time via IndividualToolCallDisplay.
executionStartTime, but the agent-view adapter
(agentHistoryAdapter.ts) constructed its display items without this
field, so sub-agent tool groups never showed the elapsed indicator.

Thread it through the sub-agent event pipeline:

- AgentToolOutputUpdateEvent gains an optional executionStartTime,
  emitted once per callId by agent-core.onToolCallsUpdate the first time
  a call is seen in the scheduler's 'executing' state (carrying
  ExecutingToolCall.executionStartTime). This also fires for tools that
  produce no live output, so their elapsed indicator appears too.
- AgentInteractive tracks executionStartTimes in a callId→timestamp map,
  analogous to liveOutputs/shellPids. First TOOL_OUTPUT_UPDATE with a
  value wins; later events that re-carry it are ignored. Cleared on
  TOOL_RESULT.
- AgentChatView passes the map as the new fifth argument to
  agentMessagesToHistoryItems.
- The adapter reads the map for Executing tools and sets
  IndividualToolCallDisplay.executionStartTime, matching the main-view
  plumbing. Agent-view tool_groups now render the same elapsed-time
  indicator the main view does.

Adds three test cases covering set-when-executing, skip-when-completed,
and skip-when-map-absent.

* fix(core): skip stats accounting for string shell chunks

totalLines/totalBytes are only emitted alongside AnsiOutputDisplay in
the ANSI-array branch of updateOutput. Computing split('\n') and
Buffer.byteLength for string chunks was wasted work — the values never
left the function.

Only compute stats when event.chunk is an AnsiLine[] now.
2026-04-20 16:04:58 +08:00
ChiGao
70396d1276
feat: optimize compact mode UX — shortcuts, settings sync, and safety (#3100)
* feat: optimize compact mode UX — shortcuts, settings sync, and safety improvements

- Add Ctrl+O to keyboard shortcuts list (?) and /help command
- Sync compact mode toggle from Settings dialog with CompactModeContext
- Protect tool approval prompts from being hidden in compact mode
  (MainContent forces live rendering during WaitingForConfirmation)
- Remove snapshot freezing on toggle — treat as persistent preference,
  not temporary peek (differs from Claude Code's session-scoped model)
- Add compact mode tip to startup Tips rotation for non-intrusive discovery
- Remove compact mode indicator from footer to reduce UI clutter
- Add competitive analysis design doc (EN + ZH) comparing with Claude Code
- Update user docs (settings.md) and i18n translations (en/zh/ru/pt)

Relates to #3047, #2767, #2770

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor: remove frozenSnapshot dead code and Chinese design doc

- Remove frozenSnapshot state, useEffect, and all related logic from
  AppContainer, MainContent, CompactModeContext, and test files
- Simplify MainContent to always render live pendingHistoryItems
- Delete compact-mode-design-zh.md (redundant Chinese translation)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address PR review feedback for compact mode optimization

- Add refreshStatic() call after setCompactMode in SettingsDialog
  so already-rendered Static history updates immediately
- Fix outdated column split comment in KeyboardShortcuts (5+4+4)
- Update design doc: remove all frozenSnapshot references, renumber
  optimization recommendations, fix file reference descriptions
- Add missing i18n keys for de.js and ja.js locales
- Add test for SettingsDialog compact mode sync with CompactModeContext

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: prevent subagent confirmation from being hidden in compact mode

hasConfirmingTool only checks ToolCallStatus.Confirming, but subagent
approvals arrive via resultDisplay.pendingConfirmation while the tool
status remains Executing. Add hasSubagentPendingConfirmation to the
showCompact guard so tool groups with pending subagent confirmations
are always force-expanded.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: force show subagent confirmation result in compact mode

The previous fix (47ee03c) correctly force-expanded the tool group
wrapper when a subagent had pending confirmation, but each inner
ToolMessage still hid its resultDisplay due to compactMode check,
which hid the AgentExecutionDisplay containing the inline confirmation
UI.

Add isAgentWithPendingConfirmation to forceShowResult conditions so
the inner AgentExecutionDisplay is rendered even in compact mode.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(compact-mode): merge consecutive tool groups across hidden items

In compact mode, sequential tool calls across multiple LLM turns each
produced a separate bordered box, defeating the "compact" intent. The
model typically emits a `gemini_thought` between consecutive tool calls,
which is hidden in compact mode — so visually the boxes look adjacent,
but in `history` they are separated by hidden items.

This commit adds render-time merging of consecutive tool_group history
items, where "consecutive" allows hidden-in-compact items
(`gemini_thought`, `gemini_thought_content`) between them.

Key pieces:
- New `mergeCompactToolGroups` utility that merges adjacent mergeable
  tool_groups, skipping hidden items between them. Force-expand
  conditions (Confirming/Error tools, subagent pending confirmation,
  user-initiated, focused embedded shell) preserve group boundaries so
  authorization prompts, errors, and shell focus stay visible.
- `MainContent.tsx` applies the merger only when `compactMode === true`
  (verbose mode is unchanged) and calls `refreshStatic()` when a merge
  consolidates items, because Ink's `<Static>` is append-only and
  cannot replace already-committed terminal content.
- `CompactToolGroupDisplay.tsx` shows a `× N` count when a merged
  group contains more than one tool, matching the existing single-turn
  multi-tool display style.
- 19 unit tests covering empty/single/multiple groups, hidden-item
  skipping (the 8-tool real-world scenario), force-expand boundaries,
  mixed tool types, and complex sequences.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: 秦奇 <gary.gq@alibaba-inc.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 09:29:24 +08:00
chiga0
8d1866ca55 fix: address PR #2770 review feedback for verbose/compact mode toggle
- Fix default value: compact mode (verboseMode=false) is now the default,
  matching PR description and intended UX
- Extract shared ToolStatusIndicator component to eliminate duplicate
  status icon rendering between ToolMessage and CompactToolGroupDisplay
- Memoize VerboseModeProvider context value to prevent unnecessary
  re-renders of all consumer components
- Clear frozenSnapshot on WaitingForConfirmation state to ensure tool
  confirmation UI remains interactive during mid-stream toggle
- Replace magic string 'Shell' with SHELL_NAME constant in ToolMessage
- Remove unused i18n translation keys (verbose/compact mode messages)
- Update snapshots for Footer and ToolGroupMessage tests

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 15:07:59 +08:00
chiga0
6fd29b698b fix: address PR review feedback for verbose/compact mode toggle
- Change default verboseMode to true (preserving current UX behavior)
- Fix compact mode hiding active shell output (add forceShowResult + isUserInitiated)
- Fix asymmetric frozen snapshot (freeze on ANY toggle during streaming)
- Fix copyright header in VerboseModeContext.tsx (Google LLC → Qwen)
- Add proper translations for all 6 locales (de/ja/pt/ru/zh/en)
- Rewrite CompactToolGroupDisplay with bordered box, i18n hint, shell detection
- Fix Pending status color (theme.text.secondary instead of theme.status.success)
- Fix description casing: ctrl+o → Ctrl+O
- Add explanatory comment for useCallback settings dependency

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 20:43:06 +08:00