From 7edeb0d1e59a698a4b109f8a6f55d6eadf2c646f Mon Sep 17 00:00:00 2001 From: Roo Code Date: Fri, 22 Aug 2025 11:29:19 +0000 Subject: [PATCH] fix: preserve user messages at choice prompts in history (fixes #7316) - Added local message history buffer in usePromptHistory hook to store messages immediately upon sending - Modified ChatTextArea to save messages to local history before clearing input field - Ensured messages typed at follow-up question prompts are preserved and accessible via up-arrow navigation - Added deduplication logic to prevent duplicate entries when messages are confirmed by backend This fix ensures that user messages are never lost, even if typed at choice prompts or if backend processing fails. --- .../src/components/chat/ChatTextArea.tsx | 21 ++++++--- .../components/chat/hooks/usePromptHistory.ts | 46 +++++++++++++++++-- 2 files changed, 57 insertions(+), 10 deletions(-) diff --git a/webview-ui/src/components/chat/ChatTextArea.tsx b/webview-ui/src/components/chat/ChatTextArea.tsx index 5135eca2f2..905d0a1d88 100644 --- a/webview-ui/src/components/chat/ChatTextArea.tsx +++ b/webview-ui/src/components/chat/ChatTextArea.tsx @@ -215,13 +215,14 @@ const ChatTextArea = forwardRef( const [isFocused, setIsFocused] = useState(false) // Use custom hook for prompt history navigation - const { handleHistoryNavigation, resetHistoryNavigation, resetOnInputChange } = usePromptHistory({ - clineMessages, - taskHistory, - cwd, - inputValue, - setInputValue, - }) + const { handleHistoryNavigation, resetHistoryNavigation, resetOnInputChange, addToLocalHistory } = + usePromptHistory({ + clineMessages, + taskHistory, + cwd, + inputValue, + setInputValue, + }) // Fetch git commits when Git is selected or when typing a hash. useEffect(() => { @@ -465,6 +466,11 @@ const ChatTextArea = forwardRef( if (event.key === "Enter" && !event.shiftKey && !isComposing) { event.preventDefault() + // Add to local history before sending + if (inputValue.trim()) { + addToLocalHistory(inputValue.trim()) + } + // Always call onSend - let ChatView handle queueing when disabled resetHistoryNavigation() onSend() @@ -531,6 +537,7 @@ const ChatTextArea = forwardRef( handleHistoryNavigation, resetHistoryNavigation, commands, + addToLocalHistory, ], ) diff --git a/webview-ui/src/components/chat/hooks/usePromptHistory.ts b/webview-ui/src/components/chat/hooks/usePromptHistory.ts index 402538182a..172717e1ad 100644 --- a/webview-ui/src/components/chat/hooks/usePromptHistory.ts +++ b/webview-ui/src/components/chat/hooks/usePromptHistory.ts @@ -22,6 +22,7 @@ export interface UsePromptHistoryReturn { ) => boolean resetHistoryNavigation: () => void resetOnInputChange: () => void + addToLocalHistory: (message: string) => void } export const usePromptHistory = ({ @@ -38,6 +39,8 @@ export const usePromptHistory = ({ const [historyIndex, setHistoryIndex] = useState(-1) const [tempInput, setTempInput] = useState("") const [promptHistory, setPromptHistory] = useState([]) + // Local sent messages that haven't been confirmed by backend yet + const [localSentMessages, setLocalSentMessages] = useState([]) // Initialize prompt history with hybrid approach: conversation messages if in task, otherwise task history const filteredPromptHistory = useMemo(() => { @@ -46,9 +49,30 @@ export const usePromptHistory = ({ ?.filter((message) => message.type === "say" && message.say === "user_feedback" && message.text?.trim()) .map((message) => message.text!) - // If we have conversation messages, use those (newest first when navigating up) + // Combine conversation prompts with local sent messages (deduplicated) + const allPrompts: string[] = [] + const seen = new Set() + + // Add conversation prompts first if (conversationPrompts?.length) { - return conversationPrompts.slice(-MAX_PROMPT_HISTORY_SIZE).reverse() + conversationPrompts.forEach((prompt) => { + if (!seen.has(prompt)) { + seen.add(prompt) + allPrompts.push(prompt) + } + }) + } + + // Add local sent messages that aren't in conversation yet + localSentMessages.forEach((msg) => { + if (!seen.has(msg)) { + allPrompts.push(msg) + } + }) + + // If we have any prompts, use those (newest first when navigating up) + if (allPrompts.length) { + return allPrompts.slice(-MAX_PROMPT_HISTORY_SIZE).reverse() } // If we have clineMessages array (meaning we're in an active task), don't fall back to task history @@ -67,7 +91,7 @@ export const usePromptHistory = ({ .filter((item) => item.task?.trim() && (!item.workspace || item.workspace === cwd)) .map((item) => item.task) .slice(0, MAX_PROMPT_HISTORY_SIZE) - }, [clineMessages, taskHistory, cwd]) + }, [clineMessages, taskHistory, cwd, localSentMessages]) // Update prompt history when filtered history changes and reset navigation useEffect(() => { @@ -175,6 +199,21 @@ export const usePromptHistory = ({ setTempInput("") }, []) + // Add a message to local sent history + const addToLocalHistory = useCallback((message: string) => { + if (message?.trim()) { + setLocalSentMessages((prev) => [...prev, message].slice(-MAX_PROMPT_HISTORY_SIZE)) + } + }, []) + + // Clear local sent messages when task changes + useEffect(() => { + // When clineMessages changes significantly (new task), clear local messages + if (!clineMessages?.length) { + setLocalSentMessages([]) + } + }, [clineMessages?.length]) + return { historyIndex, setHistoryIndex, @@ -184,5 +223,6 @@ export const usePromptHistory = ({ handleHistoryNavigation, resetHistoryNavigation, resetOnInputChange, + addToLocalHistory, } }