diff --git a/packages/cli/src/ui/components/messages/ToolGroupMessage.test.tsx b/packages/cli/src/ui/components/messages/ToolGroupMessage.test.tsx index 2a9be3f7b5..2bda2d5b4e 100644 --- a/packages/cli/src/ui/components/messages/ToolGroupMessage.test.tsx +++ b/packages/cli/src/ui/components/messages/ToolGroupMessage.test.tsx @@ -663,6 +663,31 @@ describe('', () => { expect(output).toMatchSnapshot(); unmount(); }); + + it('renders nothing when only tool is in-progress AskUser with borderBottom=false', () => { + // AskUser tools in progress are rendered by AskUserDialog, not ToolGroupMessage. + // When AskUser is the only tool and borderBottom=false (no border to close), + // the component should render nothing. + const toolCalls = [ + createToolCall({ + callId: 'ask-user-tool', + name: 'Ask User', + status: ToolCallStatus.Executing, + }), + ]; + + const { lastFrame, unmount } = renderWithProviders( + , + { config: baseMockConfig }, + ); + // AskUser tools in progress are rendered by AskUserDialog, so we expect nothing. + expect(lastFrame()).toMatchSnapshot(); + unmount(); + }); }); describe('Ask User Filtering', () => { diff --git a/packages/cli/src/ui/components/messages/ToolGroupMessage.tsx b/packages/cli/src/ui/components/messages/ToolGroupMessage.tsx index 80c74baa58..14272995d5 100644 --- a/packages/cli/src/ui/components/messages/ToolGroupMessage.tsx +++ b/packages/cli/src/ui/components/messages/ToolGroupMessage.tsx @@ -116,13 +116,11 @@ export const ToolGroupMessage: React.FC = ({ [toolCalls, isEventDriven], ); - // If all tools are hidden (e.g. group only contains confirming or pending tools), - // render nothing in the history log unless we have a border override. - if ( - visibleToolCalls.length === 0 && - borderTopOverride === undefined && - borderBottomOverride === undefined - ) { + // If all tools are filtered out (e.g., in-progress AskUser tools, confirming tools + // in event-driven mode), only render if we need to close a border from previous + // tool groups. borderBottomOverride=true means we must render the closing border; + // undefined or false means there's nothing to display. + if (visibleToolCalls.length === 0 && borderBottomOverride !== true) { return null; } 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 422c3de760..925568daa6 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 @@ -110,6 +110,8 @@ exports[` > Confirmation Handling > shows confirmation dialo exports[` > Event-Driven Scheduler > hides confirming tools when event-driven scheduler is enabled 1`] = `""`; +exports[` > Event-Driven Scheduler > renders nothing when only tool is in-progress AskUser with borderBottom=false 1`] = `""`; + exports[` > Event-Driven Scheduler > shows only successful tools when mixed with confirming tools 1`] = ` "╭──────────────────────────────────────────────────────────────────────────────╮ │ ✓ success-tool A tool for testing │ diff --git a/packages/cli/src/ui/hooks/useGeminiStream.ts b/packages/cli/src/ui/hooks/useGeminiStream.ts index 7ae0fdab6a..0d5260b82f 100644 --- a/packages/cli/src/ui/hooks/useGeminiStream.ts +++ b/packages/cli/src/ui/hooks/useGeminiStream.ts @@ -25,6 +25,7 @@ import { debugLogger, runInDevTraceSpan, EDIT_TOOL_NAMES, + ASK_USER_TOOL_NAME, processRestorableToolCalls, recordToolCallInteractions, ToolErrorType, @@ -367,6 +368,14 @@ export const useGeminiStream = ( const isEventDriven = config.isEventDrivenSchedulerEnabled(); const anyVisibleInHistory = pushedToolCallIds.size > 0; const anyVisibleInPending = remainingTools.some((tc) => { + // AskUser tools are rendered by AskUserDialog, not ToolGroupMessage + const isInProgress = + tc.status !== 'success' && + tc.status !== 'error' && + tc.status !== 'cancelled'; + if (tc.request.name === ASK_USER_TOOL_NAME && isInProgress) { + return false; + } if (!isEventDriven) return true; return ( tc.status !== 'scheduled' &&