feat(cli): combine elapsed + timeout in shell time indicator#3512
Conversation
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)
wenshao
left a comment
There was a problem hiding this comment.
Compact mode still drops the timeout budget for shell tools. This change removes timeout rendering from ShellStatsBar and wires timeoutMs only through the expanded ToolMessage path, but CompactToolGroupDisplay still renders ToolElapsedTime without timeoutMs. As a result, compact tool groups fall back to the old elapsed-only behavior and no longer show the configured timeout. Please thread timeoutMs through the compact path as well so both views implement the new behavior consistently.
— gpt-5.4 via Qwen Code /review
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)
|
Good catch — fixed in 5de1944. |
wenshao
left a comment
There was a problem hiding this comment.
No issues found. LGTM! ✅ — gpt-5.4 via Qwen Code /review
tanzhenxin
left a comment
There was a problem hiding this comment.
Review
Nice cleanup — inline (elapsed · timeout N) reads much better than the previous split across the right-aligned elapsed and the stats-bar timeout, and unifying both modes on formatDuration removes the minute/hour-range divergence. Tests look reasonable.
Verdict
APPROVE
Port upstream QwenLM/qwen-code improvements to HopCode: fix(core): scope StreamingToolCallParser per stream (QwenLM#3516) - Add ConverterStreamContext interface with per-stream StreamingToolCallParser - Add createStreamContext() factory on OpenAIContentConverter - convertOpenAIChunkToGemini() now takes ctx as explicit arg - Drop shared streamingToolCallParser instance field and resetStreamingToolCalls() - ContentGenerationPipeline creates one context per stream entry - Update all tests to use createStreamContext() API - Fixes concurrent subagent streams corrupting each other (NO_RESPONSE_TEXT) feat(cli): combine elapsed + timeout in shell time indicator (QwenLM#3512) - formatters: add FormatDurationOptions with hideTrailingZeros option - ToolElapsedTime: accept optional timeoutMs; render (elapsed · timeout N) - ToolMessage: extract timeoutMs from AnsiOutputDisplay, feed to ToolElapsedTime - CompactToolGroupDisplay: add getShellTimeoutMs() helper, thread timeoutMs - AnsiOutput: drop timeoutMs from ShellStatsBarProps (now inline in elapsed) fix(cli): stabilize resume callback deps (QwenLM#3533) - Destructure historyManager into clearItems/loadHistory stable refs - Use hasHistoryManager boolean guard in useCallback dep array chore: bump version 0.14.41 -> 0.15.1 across all packages Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…3512) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* 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)
…3512) * 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)
Summary
Render shell tools that have an explicit
timeoutas(elapsed · timeout N)inline with the Running… status from t=0, instead of splitting the information across the right-aligned elapsed indicator and theShellStatsBarrow.Before
(elapsed shown after a 3s quiet threshold, timeout shown on a separate stats row below)
After
(elapsed + timeout combined inline from t=0; sub-second precision preserved for non-integer timeouts)
What changed
formatters: addhideTrailingZerosoption so whole seconds render as5s(not5.0s) while5.5sstays intact.ToolElapsedTime: accept optionaltimeoutMs. When present, skip the 3s quiet threshold and render(elapsed · timeout N); when absent, keep the original quiet-then-elapsed behavior so fast non-shell tools stay visually calm.ToolMessage: extracttimeoutMsfromAnsiOutputDisplayand feed it toToolElapsedTime.ShellStatsBar: drop itstimeoutMsfield (now inline above); keeps+N linesand memory usage only.formatDurationso hour-range output is consistent (1h 2m 6sacross timeout and no-timeout paths).Test plan
packages/cli/src/ui/utils/formatters.test.ts— 5 new cases coveringhideTrailingZeros(whole seconds, fractional seconds, ms-range, multi-unit)packages/cli/src/ui/components/shared/ToolElapsedTime.test.tsx— 8 cases covering Pending/Executing/Success, the 3s quiet threshold, combined format from t=0, fractional timeout (5.5s), minute-range (1m 5s · timeout 5m), and the non-positive-timeout fallbackManual TTY verification (tmux)
Mounted
ToolElapsedTimeinside a 140×24 tmux pane via a small Ink harness that exercises Pending/Executing/Success and feeds varioustimeoutMscombinations, then captured frames at specific offsets.1. Initial frame (t=0) — combined format visible from the first tick; no-timeout row stays quiet:
2. Past the 3s threshold (t≈9s) — quiet row becomes visible; combined rows advance; mid-flight row has received the new
timeoutMsand switched into combined mode; defensive fallbacks behave like the no-timeout path:3. Hour range (t=1h 2m 6s) — both modes render consistently via
formatDuration:4. Status cycling — the row in
[5]flipsPending → Executing → Successon a 9s cycle and confirms that transitioning out ofExecutinghides the indicator, while re-enteringExecutingresets the elapsed counter (executionStartTimechange re-inits the effect):5. Defensive edges —
timeoutMs={0}andtimeoutMs={-500}fall back to the no-timeout path (rows[7]/[8]above), i.e. quiet for the first 3s and plain elapsed afterward. No crash or garbled label.