diff --git a/packages/cli/src/ui/AppContainer.test.tsx b/packages/cli/src/ui/AppContainer.test.tsx index d37f3961ce7..9e409e04b02 100644 --- a/packages/cli/src/ui/AppContainer.test.tsx +++ b/packages/cli/src/ui/AppContainer.test.tsx @@ -1811,6 +1811,17 @@ describe('AppContainer State Management', () => { }); it('restores the prompt when onCancelSubmit is called with shouldRestorePrompt=true (or undefined)', async () => { + // Mock history containing the message we want to restore + const mockHistory = [{ type: 'user', text: 'previous message' }]; + mockedUseHistory.mockReturnValue({ + history: mockHistory, + addItem: vi.fn(), + updateItem: vi.fn(), + clearItems: vi.fn(), + loadHistory: vi.fn(), + }); + + // Mock logger to return the same message so userMessages is populated mockedUseLogger.mockReturnValue({ getPreviousUserMessages: vi .fn() @@ -1818,6 +1829,8 @@ describe('AppContainer State Management', () => { }); const { unmount } = renderAppContainer(); + + // Wait for userMessages to be populated await waitFor(() => expect(capturedUIState.userMessages).toContain('previous message'), ); @@ -1826,11 +1839,15 @@ describe('AppContainer State Management', () => { mockedUseGeminiStream.mock.lastCall!, ); - await act(async () => { + // Trigger cancel/restore + act(() => { onCancelSubmit(true); }); - expect(mockSetText).toHaveBeenCalledWith('previous message'); + // Wait for the effect to run and restore the text + await waitFor(() => { + expect(mockSetText).toHaveBeenCalledWith('previous message'); + }); unmount(); }); diff --git a/packages/cli/src/ui/AppContainer.tsx b/packages/cli/src/ui/AppContainer.tsx index ee2c18d1a27..ac41577a5dd 100644 --- a/packages/cli/src/ui/AppContainer.tsx +++ b/packages/cli/src/ui/AppContainer.tsx @@ -187,6 +187,8 @@ export const AppContainer = (props: AppContainerProps) => { const [warningBannerText, setWarningBannerText] = useState(''); const [bannerVisible, setBannerVisible] = useState(true); + const [pendingPromptRestore, setPendingPromptRestore] = useState(false); + const extensionManager = config.getExtensionLoader() as ExtensionManager; // We are in the interactive CLI, update how we request consent and settings. extensionManager.setRequestConsent((description) => @@ -725,8 +727,15 @@ Logging in with Google... Please restart Gemini CLI to continue. return; } - const lastUserMessage = userMessages.at(-1); - let textToSet = shouldRestorePrompt ? lastUserMessage || '' : ''; + let textToSet = ''; + if (shouldRestorePrompt) { + // Defer restoration to useEffect to ensure we get the latest user message + setPendingPromptRestore(true); + return; + } + + // If we are not restoring, ensure we cancel any pending restore + setPendingPromptRestore(false); const queuedText = getQueuedMessagesText(); if (queuedText) { @@ -740,7 +749,6 @@ Logging in with Google... Please restart Gemini CLI to continue. }, [ buffer, - userMessages, getQueuedMessagesText, clearQueue, pendingSlashCommandHistoryItems, @@ -748,6 +756,31 @@ Logging in with Google... Please restart Gemini CLI to continue. ], ); + useEffect(() => { + if (pendingPromptRestore) { + // Check if userMessages is up to date with historyManager + const lastHistoryItem = historyManager.history.findLast( + (item) => item.type === 'user' && item.text, + ); + const lastUserMessage = userMessages.at(-1); + + if ( + lastHistoryItem && + lastHistoryItem.text === lastUserMessage && + lastUserMessage + ) { + buffer.setText(lastUserMessage); + setPendingPromptRestore(false); + } + } + }, [ + pendingPromptRestore, + userMessages, + historyManager.history, + buffer, + setPendingPromptRestore, + ]); + const handleFinalSubmit = useCallback( (submittedValue: string) => { addMessage(submittedValue);