diff --git a/packages/cli/src/ui/components/MainContent.test.tsx b/packages/cli/src/ui/components/MainContent.test.tsx index 2be3a0ac8b..3fda3d5793 100644 --- a/packages/cli/src/ui/components/MainContent.test.tsx +++ b/packages/cli/src/ui/components/MainContent.test.tsx @@ -882,6 +882,82 @@ describe('MainContent', () => { expect(output).toContain('Here is your answer.'); unmount(); }); + + it('bypasses suppression for the current turn during confirmation, but keeps old turns suppressed', async () => { + const { useConfirmingTool } = await import( + '../hooks/useConfirmingTool.js' + ); + vi.mocked(useConfirmingTool).mockReturnValue({ + tool: { + callId: 'call-1', + name: 'some_tool', + status: CoreToolCallStatus.AwaitingApproval, + confirmationDetails: { + type: 'confirm_shell_command' as const, + command: 'echo "hello"', + }, + }, + index: 0, + total: 1, + } as unknown as ConfirmingToolState); + + mockUseSettings.mockReturnValue(settingsWithNarration); + const uiState = { + ...defaultMockUiState, + history: [ + // Previous turn: should remain suppressed + { id: 10, type: 'user' as const, text: 'Old Turn' }, + { id: 11, type: 'gemini' as const, text: 'Old narration' }, + { + id: 12, + type: 'tool_group' as const, + tools: [ + { + callId: 'old', + name: 'ls', + status: CoreToolCallStatus.Success, + description: '', + resultDisplay: '', + confirmationDetails: undefined, + } as IndividualToolCallDisplay, + ], + }, + // Current turn: should be bypassed (shown) + { id: 20, type: 'user' as const, text: 'Current Turn' }, + { id: 21, type: 'gemini' as const, text: 'Current narration' }, + ], + pendingHistoryItems: [ + { + type: 'tool_group' as const, + tools: [ + { + callId: 'call-1', + name: 'some_tool', + status: CoreToolCallStatus.AwaitingApproval, + description: '', + resultDisplay: '', + confirmationDetails: undefined, + } as IndividualToolCallDisplay, + ], + }, + ], + }; + + const { lastFrame, unmount } = await renderWithProviders( + , + { + uiState: uiState as Partial, + settings: settingsWithNarration, + }, + ); + + const output = lastFrame(); + // Old narration should STILL be suppressed + expect(output).not.toContain('Old narration'); + // Current narration should be VISIBLE because of the bypass + expect(output).toContain('Current narration'); + unmount(); + }); }); it('renders multiple thinking messages sequentially correctly', async () => { diff --git a/packages/cli/src/ui/components/MainContent.tsx b/packages/cli/src/ui/components/MainContent.tsx index d0b7e1284e..8ad4a1dda0 100644 --- a/packages/cli/src/ui/components/MainContent.tsx +++ b/packages/cli/src/ui/components/MainContent.tsx @@ -123,13 +123,19 @@ export const MainContent = () => { // Rule 2: Suppress text in intermediate turns (turns containing non-topic // tools) to hide mechanical narration. - if (turnIsIntermediate && !showConfirmationQueue) { + + const isCurrentTurn = i > lastUserPromptIndex; + // Scoping the bypass to the current turn prevents old narration from reappearing. + // This logic affects the rendered height of the history list by conditionally hiding turns. + const bypassSuppression = isCurrentTurn && showConfirmationQueue; + + if (turnIsIntermediate && !bypassSuppression) { flags[i] = true; } // Rule 3: Suppress text that precedes a topic tool in the same turn, // as the topic tool "replaces" it. - if (hasTopicToolInTurn && !showConfirmationQueue) { + if (hasTopicToolInTurn && !bypassSuppression) { flags[i] = true; } } @@ -141,6 +147,7 @@ export const MainContent = () => { pendingHistoryItems, topicUpdateNarrationEnabled, showConfirmationQueue, + lastUserPromptIndex, ]); const augmentedHistory = useMemo(