diff --git a/packages/cli/src/ui/components/messages/CompactToolGroupDisplay.tsx b/packages/cli/src/ui/components/messages/CompactToolGroupDisplay.tsx index 852a7df927..3744f526b0 100644 --- a/packages/cli/src/ui/components/messages/CompactToolGroupDisplay.tsx +++ b/packages/cli/src/ui/components/messages/CompactToolGroupDisplay.tsx @@ -9,7 +9,6 @@ import { Box, Text } from 'ink'; import type { IndividualToolCallDisplay } from '../../types.js'; import { ToolCallStatus } from '../../types.js'; import type { AnsiOutputDisplay } from '@qwen-code/qwen-code-core'; -import { SHELL_COMMAND_NAME, SHELL_NAME } from '../../constants.js'; import { theme } from '../../semantic-colors.js'; import { localizeToolDisplayName, t } from '../../../i18n/index.js'; import { ToolStatusIndicator } from '../shared/ToolStatusIndicator.js'; @@ -129,19 +128,6 @@ export const CompactToolGroupDisplay: React.FC< const overallStatus = getOverallStatus(toolCalls); const activeTool = getActiveTool(toolCalls); - const isShellCommand = toolCalls.some( - (t) => t.name === SHELL_COMMAND_NAME || t.name === SHELL_NAME, - ); - const hasPending = !toolCalls.every( - (t) => t.status === ToolCallStatus.Success, - ); - - const borderColor = isShellCommand - ? theme.ui.symbol - : hasPending - ? theme.status.warning - : theme.border.default; - // Take only the first line of description to prevent multi-line shell scripts // from expanding the compact view (wrap="truncate-end" only handles width overflow, // not literal \n characters in the content) @@ -150,14 +136,7 @@ export const CompactToolGroupDisplay: React.FC< : ''; return ( - + {/* Status line: icon + (summary | tool name + description) + count + elapsed */} diff --git a/packages/cli/src/ui/components/messages/InlineParallelAgentsDisplay.tsx b/packages/cli/src/ui/components/messages/InlineParallelAgentsDisplay.tsx index c2a00987f9..8c8603f651 100644 --- a/packages/cli/src/ui/components/messages/InlineParallelAgentsDisplay.tsx +++ b/packages/cli/src/ui/components/messages/InlineParallelAgentsDisplay.tsx @@ -264,13 +264,7 @@ export const InlineParallelAgentsDisplay: React.FC< const headerLabel = `Parallel agents · ${total} · ${doneCount}/${total} done`; return ( - + {headerLabel} diff --git a/packages/cli/src/ui/components/messages/ToolGroupMessage.test.tsx b/packages/cli/src/ui/components/messages/ToolGroupMessage.test.tsx index c99669bd3c..b667345648 100644 --- a/packages/cli/src/ui/components/messages/ToolGroupMessage.test.tsx +++ b/packages/cli/src/ui/components/messages/ToolGroupMessage.test.tsx @@ -196,7 +196,7 @@ describe('', () => { expect(lastFrame()).toMatchSnapshot(); }); - it('renders shell command with yellow border', () => { + it('renders shell command', () => { const toolCalls = [ createToolCall({ callId: 'shell-1', @@ -503,45 +503,6 @@ describe('', () => { }); }); - describe('Border Color Logic', () => { - it('uses yellow border when tools are pending', () => { - const toolCalls = [createToolCall({ status: ToolCallStatus.Pending })]; - const { lastFrame } = renderWithProviders( - , - ); - // The snapshot will capture the visual appearance including border color - expect(lastFrame()).toMatchSnapshot(); - }); - - it('uses yellow border for shell commands even when successful', () => { - const toolCalls = [ - createToolCall({ - name: 'run_shell_command', - status: ToolCallStatus.Success, - }), - ]; - const { lastFrame } = renderWithProviders( - , - ); - expect(lastFrame()).toMatchSnapshot(); - }); - - it('uses gray border when all tools are successful and no shell commands', () => { - const toolCalls = [ - createToolCall({ status: ToolCallStatus.Success }), - createToolCall({ - callId: 'tool-2', - name: 'another-tool', - status: ToolCallStatus.Success, - }), - ]; - const { lastFrame } = renderWithProviders( - , - ); - expect(lastFrame()).toMatchSnapshot(); - }); - }); - describe('Height Calculation', () => { it('calculates available height correctly with multiple tools with results', () => { const toolCalls = [ diff --git a/packages/cli/src/ui/components/messages/ToolGroupMessage.tsx b/packages/cli/src/ui/components/messages/ToolGroupMessage.tsx index 74ca87ea5f..e911dbb7a6 100644 --- a/packages/cli/src/ui/components/messages/ToolGroupMessage.tsx +++ b/packages/cli/src/ui/components/messages/ToolGroupMessage.tsx @@ -13,8 +13,6 @@ import { ToolMessage } from './ToolMessage.js'; import { ToolConfirmationMessage } from './ToolConfirmationMessage.js'; import { CompactToolGroupDisplay } from './CompactToolGroupDisplay.js'; import { InlineParallelAgentsDisplay } from './InlineParallelAgentsDisplay.js'; -import { theme } from '../../semantic-colors.js'; -import { SHELL_COMMAND_NAME, SHELL_NAME } from '../../constants.js'; import { useConfig } from '../../contexts/ConfigContext.js'; import { useCompactMode } from '../../contexts/CompactModeContext.js'; import type { AgentResultDisplay } from '@qwen-code/qwen-code-core'; @@ -156,7 +154,7 @@ interface ToolGroupMessageProps { compactLabel?: string; } -// Main component renders the border and maps the tools using ToolMessage +// Main component maps the tools using ToolMessage export const ToolGroupMessage: React.FC = ({ toolCalls, availableTerminalHeight, @@ -292,15 +290,15 @@ export const ToolGroupMessage: React.FC = ({ // Hide the entire group when the live-phase filter leaves nothing // inline to render — i.e. a pure-running-subagent batch with no // pending approval. LiveAgentPanel below the composer is the - // single source of truth for those rows; an empty bordered - // container floating above the panel would just be a duplicate - // chrome line. Terminal subagents (completed / failed / cancelled) + // single source of truth for those rows; an empty + // container floating above the panel would just be noise. + // Terminal subagents (completed / failed / cancelled) // pass through `inlineToolCalls` because `unregisterForeground`'s // post-delete emit already dropped them from the panel snapshot, // and the inline path must render `SubagentScrollbackSummary` // immediately so the user keeps a record of the run. // (Gate on `isPending` so a degenerate empty `toolCalls=[]` in the - // committed phase still falls through to the legacy empty-border + // committed phase still falls through to the legacy empty-container // snapshot — the suppression is specifically about live-phase // panel ownership, not about hiding empty inputs in general.) if (isPending && inlineToolCalls.length === 0) { @@ -345,22 +343,8 @@ export const ToolGroupMessage: React.FC = ({ } // Full expanded view - const hasPending = !inlineToolCalls.every( - (t) => t.status === ToolCallStatus.Success, - ); - const isShellCommand = inlineToolCalls.some( - (t) => t.name === SHELL_COMMAND_NAME || t.name === SHELL_NAME, - ); - const borderColor = - isShellCommand || isEmbeddedShellFocused - ? theme.ui.symbol - : hasPending - ? theme.status.warning - : theme.border.default; - - const staticHeight = /* border */ 2 + /* marginBottom */ 1; - // account for border (2 chars) and padding (2 chars) - const innerWidth = contentWidth - 4; + const staticHeight = /* marginBottom */ 1; + const innerWidth = contentWidth - 2; let countToolCallsWithResults = 0; for (const tool of inlineToolCalls) { @@ -385,12 +369,7 @@ export const ToolGroupMessage: React.FC = ({ const readCount = memoryReadCount ?? 0; const writeCount = memoryWriteCount ?? 0; return ( - + {readCount > 0 && ( @@ -412,22 +391,7 @@ export const ToolGroupMessage: React.FC = ({ } return ( - + {/* Memory badge for mixed groups (some memory ops + other ops) */} {!isMemoryOnlyGroup && ((memoryWriteCount ?? 0) > 0 || (memoryReadCount ?? 0) > 0) && diff --git a/packages/cli/src/ui/components/messages/ToolMessage.test.tsx b/packages/cli/src/ui/components/messages/ToolMessage.test.tsx index 8056ac85e1..68e8f1d22b 100644 --- a/packages/cli/src/ui/components/messages/ToolMessage.test.tsx +++ b/packages/cli/src/ui/components/messages/ToolMessage.test.tsx @@ -190,6 +190,42 @@ describe('', () => { expect(output).not.toContain('MockMarkdown:Test result'); // result hidden }); + it('shows result for Error status in compact mode', () => { + const { lastFrame } = renderWithContext( + , + StreamingState.Idle, + true, + ); + expect(lastFrame()).toContain('MockMarkdown:Test result'); + }); + + it('shows result for Executing status in compact mode', () => { + const { lastFrame } = renderWithContext( + , + StreamingState.Idle, + true, + ); + expect(lastFrame()).toContain('MockMarkdown:Test result'); + }); + + it('shows result for Pending status in compact mode', () => { + const { lastFrame } = renderWithContext( + , + StreamingState.Idle, + true, + ); + expect(lastFrame()).toContain('MockMarkdown:Test result'); + }); + + it('shows result when forceShowResult overrides compact collapse', () => { + const { lastFrame } = renderWithContext( + , + StreamingState.Idle, + true, + ); + expect(lastFrame()).toContain('MockMarkdown:Test result'); + }); + describe('ToolStatusIndicator rendering', () => { it('shows ✓ for Success status', () => { const { lastFrame } = renderWithContext( diff --git a/packages/cli/src/ui/components/messages/ToolMessage.tsx b/packages/cli/src/ui/components/messages/ToolMessage.tsx index 4d8ba15319..1306e9dae2 100644 --- a/packages/cli/src/ui/components/messages/ToolMessage.tsx +++ b/packages/cli/src/ui/components/messages/ToolMessage.tsx @@ -670,10 +670,12 @@ export const ToolMessage: React.FC = ({ // Use the custom hook to determine the display type const displayRenderer = useResultDisplayRenderer(resultDisplay); const { compactMode } = useCompactMode(); - const effectiveDisplayRenderer = - !compactMode || forceShowResult - ? displayRenderer - : { type: 'none' as const }; + + const isCompleted = status === ToolCallStatus.Success; + const shouldCollapse = compactMode && isCompleted && !forceShowResult; + const effectiveDisplayRenderer = shouldCollapse + ? { type: 'none' as const } + : displayRenderer; return ( @@ -780,6 +782,7 @@ const ToolInfo: React.FC = ({ status, emphasis, }) => { + const { compactMode } = useCompactMode(); const nameColor = React.useMemo(() => { switch (emphasis) { case 'high': @@ -794,13 +797,15 @@ const ToolInfo: React.FC = ({ } } }, [emphasis]); + const isDim = compactMode && status === ToolCallStatus.Success; return ( - + {localizeToolDisplayName(name)} {' '} {description} diff --git a/packages/cli/src/ui/components/messages/__snapshots__/ToolGroupMessage.test.tsx.snap b/packages/cli/src/ui/components/messages/__snapshots__/ToolGroupMessage.test.tsx.snap index 28a00dabde..a28231587c 100644 --- a/packages/cli/src/ui/components/messages/__snapshots__/ToolGroupMessage.test.tsx.snap +++ b/packages/cli/src/ui/components/messages/__snapshots__/ToolGroupMessage.test.tsx.snap @@ -1,99 +1,51 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[` > Border Color Logic > uses gray border when all tools are successful and no shell commands 1`] = ` -"╭──────────────────────────────────────────────────────────────────────────────╮ -│MockTool[tool-123]: ✓ test-tool - A tool for testing (medium) │ -│MockTool[tool-2]: ✓ another-tool - A tool for testing (medium) │ -╰──────────────────────────────────────────────────────────────────────────────╯" -`; - -exports[` > Border Color Logic > uses yellow border for shell commands even when successful 1`] = ` -"╭──────────────────────────────────────────────────────────────────────────────╮ -│MockTool[tool-123]: ✓ run_shell_command - A tool for testing (medium) │ -╰──────────────────────────────────────────────────────────────────────────────╯" -`; - -exports[` > Border Color Logic > uses yellow border when tools are pending 1`] = ` -"╭──────────────────────────────────────────────────────────────────────────────╮ -│MockTool[tool-123]: o test-tool - A tool for testing (medium) │ -╰──────────────────────────────────────────────────────────────────────────────╯" -`; - exports[` > Confirmation Handling > shows confirmation dialog for first confirming tool only 1`] = ` -"╭──────────────────────────────────────────────────────────────────────────────╮ -│MockTool[tool-1]: ? first-confirm - A tool for testing (high) │ -│MockConfirmation: Confirm first tool │ -│MockTool[tool-2]: ? second-confirm - A tool for testing (low) │ -╰──────────────────────────────────────────────────────────────────────────────╯" +"MockTool[tool-1]: ? first-confirm - A tool for testing (high) +MockConfirmation: Confirm first tool +MockTool[tool-2]: ? second-confirm - A tool for testing (low)" `; -exports[` > Golden Snapshots > renders empty tool calls array 1`] = ` -"╭──────────────────────────────────────────────────────────────────────────────╮ -╰──────────────────────────────────────────────────────────────────────────────╯" -`; +exports[` > Golden Snapshots > renders empty tool calls array 1`] = `""`; exports[` > Golden Snapshots > renders mixed tool calls including shell command 1`] = ` -"╭──────────────────────────────────────────────────────────────────────────────╮ -│MockTool[tool-1]: ✓ read_file - Read a file (medium) │ -│MockTool[tool-2]: ⊷ run_shell_command - Run command (medium) │ -│MockTool[tool-3]: o write_file - Write to file (medium) │ -╰──────────────────────────────────────────────────────────────────────────────╯" +"MockTool[tool-1]: ✓ read_file - Read a file (medium) +MockTool[tool-2]: ⊷ run_shell_command - Run command (medium) +MockTool[tool-3]: o write_file - Write to file (medium)" `; exports[` > Golden Snapshots > renders multiple tool calls with different statuses 1`] = ` -"╭──────────────────────────────────────────────────────────────────────────────╮ -│MockTool[tool-1]: ✓ successful-tool - This tool succeeded (medium) │ -│MockTool[tool-2]: o pending-tool - This tool is pending (medium) │ -│MockTool[tool-3]: x error-tool - This tool failed (medium) │ -╰──────────────────────────────────────────────────────────────────────────────╯" +"MockTool[tool-1]: ✓ successful-tool - This tool succeeded (medium) +MockTool[tool-2]: o pending-tool - This tool is pending (medium) +MockTool[tool-3]: x error-tool - This tool failed (medium)" `; -exports[` > Golden Snapshots > renders shell command with yellow border 1`] = ` -"╭──────────────────────────────────────────────────────────────────────────────╮ -│MockTool[shell-1]: ✓ run_shell_command - Execute shell command (medium) │ -╰──────────────────────────────────────────────────────────────────────────────╯" -`; +exports[` > Golden Snapshots > renders shell command 1`] = `"MockTool[shell-1]: ✓ run_shell_command - Execute shell command (medium)"`; -exports[` > Golden Snapshots > renders single successful tool call 1`] = ` -"╭──────────────────────────────────────────────────────────────────────────────╮ -│MockTool[tool-123]: ✓ test-tool - A tool for testing (medium) │ -╰──────────────────────────────────────────────────────────────────────────────╯" -`; +exports[` > Golden Snapshots > renders single successful tool call 1`] = `"MockTool[tool-123]: ✓ test-tool - A tool for testing (medium)"`; exports[` > Golden Snapshots > renders tool call awaiting confirmation 1`] = ` -"╭──────────────────────────────────────────────────────────────────────────────╮ -│MockTool[tool-confirm]: ? confirmation-tool - This tool needs confirmation │ -│(high) │ -│MockConfirmation: Are you sure you want to proceed? │ -╰──────────────────────────────────────────────────────────────────────────────╯" +"MockTool[tool-confirm]: ? confirmation-tool - This tool needs confirmation +(high) +MockConfirmation: Are you sure you want to proceed?" `; -exports[` > Golden Snapshots > renders when not focused 1`] = ` -"╭──────────────────────────────────────────────────────────────────────────────╮ -│MockTool[tool-123]: ✓ test-tool - A tool for testing (medium) │ -╰──────────────────────────────────────────────────────────────────────────────╯" -`; +exports[` > Golden Snapshots > renders when not focused 1`] = `"MockTool[tool-123]: ✓ test-tool - A tool for testing (medium)"`; exports[` > Golden Snapshots > renders with limited terminal height 1`] = ` -"╭──────────────────────────────────────────────────────────────────────────────╮ -│MockTool[tool-1]: ✓ tool-with-result - Tool with output (medium) │ -│MockTool[tool-2]: ✓ another-tool - Another tool (medium) │ -╰──────────────────────────────────────────────────────────────────────────────╯" +"MockTool[tool-1]: ✓ tool-with-result - Tool with output (medium) +MockTool[tool-2]: ✓ another-tool - Another tool (medium)" `; exports[` > Golden Snapshots > renders with narrow terminal width 1`] = ` -"╭──────────────────────────────────────╮ -│MockTool[tool-123]: ✓ │ -│very-long-tool-name-that-might-wrap - │ -│This is a very long description that │ -│might cause wrapping issues (medium) │ -╰──────────────────────────────────────╯" +"MockTool[tool-123]: ✓ +very-long-tool-name-that-might-wrap - +This is a very long description that +might cause wrapping issues (medium)" `; exports[` > Height Calculation > calculates available height correctly with multiple tools with results 1`] = ` -"╭──────────────────────────────────────────────────────────────────────────────╮ -│MockTool[tool-1]: ✓ test-tool - A tool for testing (medium) │ -│MockTool[tool-2]: ✓ test-tool - A tool for testing (medium) │ -│MockTool[tool-3]: ✓ test-tool - A tool for testing (medium) │ -╰──────────────────────────────────────────────────────────────────────────────╯" +"MockTool[tool-1]: ✓ test-tool - A tool for testing (medium) +MockTool[tool-2]: ✓ test-tool - A tool for testing (medium) +MockTool[tool-3]: ✓ test-tool - A tool for testing (medium)" `;