Compare commits

...

2 Commits

Author SHA1 Message Date
Jarrod Whelan 957f5639ba limit turning off message suppression to the current turn only 2026-04-07 13:51:20 -07:00
Jarrod Whelan 75adad8018 fix(cli): prevent suppression of history items during tool confirmation
Updates the history item suppression logic in MainContent to ensure that
intermediate turns and narration text remain visible while a tool
confirmation (like ask_user) is active. This prevents items from
unexpectedly disappearing from the UI during interactive tool execution.

- Modified MainContent.tsx to skip suppression when showConfirmationQueue is true.
- Updated MainContent.test.tsx with a default mock for useConfirmingTool.
2026-04-07 13:45:38 -07:00
2 changed files with 92 additions and 4 deletions
@@ -66,7 +66,7 @@ vi.mock('../hooks/useAlternateBuffer.js', () => ({
}));
vi.mock('../hooks/useConfirmingTool.js', () => ({
useConfirmingTool: vi.fn(),
useConfirmingTool: vi.fn(() => null),
}));
vi.mock('./AppHeader.js', () => ({
@@ -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(
<MainContent />,
{
uiState: uiState as Partial<UIState>,
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 () => {
+15 -3
View File
@@ -123,20 +123,32 @@ export const MainContent = () => {
// Rule 2: Suppress text in intermediate turns (turns containing non-topic
// tools) to hide mechanical narration.
if (turnIsIntermediate) {
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) {
if (hasTopicToolInTurn && !bypassSuppression) {
flags[i] = true;
}
}
}
}
return flags;
}, [uiState.history, pendingHistoryItems, topicUpdateNarrationEnabled]);
}, [
uiState.history,
pendingHistoryItems,
topicUpdateNarrationEnabled,
showConfirmationQueue,
lastUserPromptIndex,
]);
const augmentedHistory = useMemo(
() =>