|
@@ -15,6 +15,7 @@ import type { ClineAsk, ClineMessage } from "@roo-code/types"
|
|
|
import { ClineSayBrowserAction, ClineSayTool, ExtensionMessage } from "@roo/ExtensionMessage"
|
|
import { ClineSayBrowserAction, ClineSayTool, ExtensionMessage } from "@roo/ExtensionMessage"
|
|
|
import { McpServer, McpTool } from "@roo/mcp"
|
|
import { McpServer, McpTool } from "@roo/mcp"
|
|
|
import { findLast } from "@roo/array"
|
|
import { findLast } from "@roo/array"
|
|
|
|
|
+import { FollowUpData, SuggestionItem } from "@roo-code/types"
|
|
|
import { combineApiRequests } from "@roo/combineApiRequests"
|
|
import { combineApiRequests } from "@roo/combineApiRequests"
|
|
|
import { combineCommandSequences } from "@roo/combineCommandSequences"
|
|
import { combineCommandSequences } from "@roo/combineCommandSequences"
|
|
|
import { getApiMetrics } from "@roo/getApiMetrics"
|
|
import { getApiMetrics } from "@roo/getApiMetrics"
|
|
@@ -88,11 +89,13 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|
|
alwaysAllowMcp,
|
|
alwaysAllowMcp,
|
|
|
allowedCommands,
|
|
allowedCommands,
|
|
|
writeDelayMs,
|
|
writeDelayMs,
|
|
|
|
|
+ followupAutoApproveTimeoutMs,
|
|
|
mode,
|
|
mode,
|
|
|
setMode,
|
|
setMode,
|
|
|
autoApprovalEnabled,
|
|
autoApprovalEnabled,
|
|
|
alwaysAllowModeSwitch,
|
|
alwaysAllowModeSwitch,
|
|
|
alwaysAllowSubtasks,
|
|
alwaysAllowSubtasks,
|
|
|
|
|
+ alwaysAllowFollowupQuestions,
|
|
|
customModes,
|
|
customModes,
|
|
|
telemetrySetting,
|
|
telemetrySetting,
|
|
|
hasSystemPromptOverride,
|
|
hasSystemPromptOverride,
|
|
@@ -879,6 +882,10 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|
|
return false
|
|
return false
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ if (message.ask === "followup") {
|
|
|
|
|
+ return alwaysAllowFollowupQuestions
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
if (message.ask === "browser_action_launch") {
|
|
if (message.ask === "browser_action_launch") {
|
|
|
return alwaysAllowBrowser
|
|
return alwaysAllowBrowser
|
|
|
}
|
|
}
|
|
@@ -957,6 +964,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|
|
alwaysAllowMcp,
|
|
alwaysAllowMcp,
|
|
|
isMcpToolAlwaysAllowed,
|
|
isMcpToolAlwaysAllowed,
|
|
|
alwaysAllowModeSwitch,
|
|
alwaysAllowModeSwitch,
|
|
|
|
|
+ alwaysAllowFollowupQuestions,
|
|
|
alwaysAllowSubtasks,
|
|
alwaysAllowSubtasks,
|
|
|
],
|
|
],
|
|
|
)
|
|
)
|
|
@@ -1189,18 +1197,43 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|
|
|
|
|
|
|
const placeholderText = task ? t("chat:typeMessage") : t("chat:typeTask")
|
|
const placeholderText = task ? t("chat:typeMessage") : t("chat:typeTask")
|
|
|
|
|
|
|
|
|
|
+ // Function to switch to a specific mode
|
|
|
|
|
+ const switchToMode = useCallback(
|
|
|
|
|
+ (modeSlug: string): void => {
|
|
|
|
|
+ // Update local state and notify extension to sync mode change
|
|
|
|
|
+ setMode(modeSlug)
|
|
|
|
|
+
|
|
|
|
|
+ // Send the mode switch message
|
|
|
|
|
+ vscode.postMessage({
|
|
|
|
|
+ type: "mode",
|
|
|
|
|
+ text: modeSlug,
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ [setMode],
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
const handleSuggestionClickInRow = useCallback(
|
|
const handleSuggestionClickInRow = useCallback(
|
|
|
- (answer: string, event?: React.MouseEvent) => {
|
|
|
|
|
|
|
+ (suggestion: SuggestionItem, event?: React.MouseEvent) => {
|
|
|
|
|
+ // Check if we need to switch modes
|
|
|
|
|
+ if (suggestion.mode) {
|
|
|
|
|
+ // Only switch modes if it's a manual click (event exists) or auto-approval is allowed
|
|
|
|
|
+ const isManualClick = !!event
|
|
|
|
|
+ if (isManualClick || alwaysAllowModeSwitch) {
|
|
|
|
|
+ // Switch mode without waiting
|
|
|
|
|
+ switchToMode(suggestion.mode)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
if (event?.shiftKey) {
|
|
if (event?.shiftKey) {
|
|
|
// Always append to existing text, don't overwrite
|
|
// Always append to existing text, don't overwrite
|
|
|
setInputValue((currentValue) => {
|
|
setInputValue((currentValue) => {
|
|
|
- return currentValue !== "" ? `${currentValue} \n${answer}` : answer
|
|
|
|
|
|
|
+ return currentValue !== "" ? `${currentValue} \n${suggestion.answer}` : suggestion.answer
|
|
|
})
|
|
})
|
|
|
} else {
|
|
} else {
|
|
|
- handleSendMessage(answer, [])
|
|
|
|
|
|
|
+ handleSendMessage(suggestion.answer, [])
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
- [handleSendMessage, setInputValue], // setInputValue is stable, handleSendMessage depends on clineAsk
|
|
|
|
|
|
|
+ [handleSendMessage, setInputValue, switchToMode, alwaysAllowModeSwitch],
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
const handleBatchFileResponse = useCallback((response: { [key: string]: boolean }) => {
|
|
const handleBatchFileResponse = useCallback((response: { [key: string]: boolean }) => {
|
|
@@ -1208,6 +1241,15 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|
|
vscode.postMessage({ type: "askResponse", askResponse: "objectResponse", text: JSON.stringify(response) })
|
|
vscode.postMessage({ type: "askResponse", askResponse: "objectResponse", text: JSON.stringify(response) })
|
|
|
}, [])
|
|
}, [])
|
|
|
|
|
|
|
|
|
|
+ // Handler for when FollowUpSuggest component unmounts
|
|
|
|
|
+ const handleFollowUpUnmount = useCallback(() => {
|
|
|
|
|
+ // Clear the auto-approve timeout to prevent race conditions
|
|
|
|
|
+ if (autoApproveTimeoutRef.current) {
|
|
|
|
|
+ clearTimeout(autoApproveTimeoutRef.current)
|
|
|
|
|
+ autoApproveTimeoutRef.current = null
|
|
|
|
|
+ }
|
|
|
|
|
+ }, [])
|
|
|
|
|
+
|
|
|
const itemContent = useCallback(
|
|
const itemContent = useCallback(
|
|
|
(index: number, messageOrGroup: ClineMessage | ClineMessage[]) => {
|
|
(index: number, messageOrGroup: ClineMessage | ClineMessage[]) => {
|
|
|
// browser session group
|
|
// browser session group
|
|
@@ -1243,6 +1285,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|
|
isStreaming={isStreaming}
|
|
isStreaming={isStreaming}
|
|
|
onSuggestionClick={handleSuggestionClickInRow} // This was already stabilized
|
|
onSuggestionClick={handleSuggestionClickInRow} // This was already stabilized
|
|
|
onBatchFileResponse={handleBatchFileResponse}
|
|
onBatchFileResponse={handleBatchFileResponse}
|
|
|
|
|
+ onFollowUpUnmount={handleFollowUpUnmount}
|
|
|
/>
|
|
/>
|
|
|
)
|
|
)
|
|
|
},
|
|
},
|
|
@@ -1255,6 +1298,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|
|
isStreaming,
|
|
isStreaming,
|
|
|
handleSuggestionClickInRow,
|
|
handleSuggestionClickInRow,
|
|
|
handleBatchFileResponse,
|
|
handleBatchFileResponse,
|
|
|
|
|
+ handleFollowUpUnmount,
|
|
|
],
|
|
],
|
|
|
)
|
|
)
|
|
|
|
|
|
|
@@ -1270,19 +1314,41 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|
|
|
|
|
|
|
const autoApprove = async () => {
|
|
const autoApprove = async () => {
|
|
|
if (lastMessage?.ask && isAutoApproved(lastMessage)) {
|
|
if (lastMessage?.ask && isAutoApproved(lastMessage)) {
|
|
|
- if (lastMessage.ask === "tool" && isWriteToolAction(lastMessage)) {
|
|
|
|
|
|
|
+ // Special handling for follow-up questions
|
|
|
|
|
+ if (lastMessage.ask === "followup") {
|
|
|
|
|
+ // Handle invalid JSON
|
|
|
|
|
+ let followUpData: FollowUpData = {}
|
|
|
|
|
+ try {
|
|
|
|
|
+ followUpData = JSON.parse(lastMessage.text || "{}") as FollowUpData
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error("Failed to parse follow-up data:", error)
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (followUpData && followUpData.suggest && followUpData.suggest.length > 0) {
|
|
|
|
|
+ // Wait for the configured timeout before auto-selecting the first suggestion
|
|
|
|
|
+ await new Promise<void>((resolve) => {
|
|
|
|
|
+ autoApproveTimeoutRef.current = setTimeout(resolve, followupAutoApproveTimeoutMs)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ // Get the first suggestion
|
|
|
|
|
+ const firstSuggestion = followUpData.suggest[0]
|
|
|
|
|
+
|
|
|
|
|
+ // Handle the suggestion click
|
|
|
|
|
+ handleSuggestionClickInRow(firstSuggestion)
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if (lastMessage.ask === "tool" && isWriteToolAction(lastMessage)) {
|
|
|
await new Promise<void>((resolve) => {
|
|
await new Promise<void>((resolve) => {
|
|
|
autoApproveTimeoutRef.current = setTimeout(resolve, writeDelayMs)
|
|
autoApproveTimeoutRef.current = setTimeout(resolve, writeDelayMs)
|
|
|
})
|
|
})
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if (autoApproveTimeoutRef.current === null || autoApproveTimeoutRef.current) {
|
|
|
|
|
- vscode.postMessage({ type: "askResponse", askResponse: "yesButtonClicked" })
|
|
|
|
|
|
|
+ vscode.postMessage({ type: "askResponse", askResponse: "yesButtonClicked" })
|
|
|
|
|
|
|
|
- setSendingDisabled(true)
|
|
|
|
|
- setClineAsk(undefined)
|
|
|
|
|
- setEnableButtons(false)
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ setSendingDisabled(true)
|
|
|
|
|
+ setClineAsk(undefined)
|
|
|
|
|
+ setEnableButtons(false)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
autoApprove()
|
|
autoApprove()
|
|
@@ -1303,6 +1369,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|
|
alwaysAllowWrite,
|
|
alwaysAllowWrite,
|
|
|
alwaysAllowWriteOutsideWorkspace,
|
|
alwaysAllowWriteOutsideWorkspace,
|
|
|
alwaysAllowExecute,
|
|
alwaysAllowExecute,
|
|
|
|
|
+ followupAutoApproveTimeoutMs,
|
|
|
alwaysAllowMcp,
|
|
alwaysAllowMcp,
|
|
|
messages,
|
|
messages,
|
|
|
allowedCommands,
|
|
allowedCommands,
|
|
@@ -1311,6 +1378,8 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|
|
lastMessage,
|
|
lastMessage,
|
|
|
writeDelayMs,
|
|
writeDelayMs,
|
|
|
isWriteToolAction,
|
|
isWriteToolAction,
|
|
|
|
|
+ alwaysAllowFollowupQuestions,
|
|
|
|
|
+ handleSuggestionClickInRow,
|
|
|
])
|
|
])
|
|
|
|
|
|
|
|
// Function to handle mode switching
|
|
// Function to handle mode switching
|
|
@@ -1319,12 +1388,8 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|
|
const currentModeIndex = allModes.findIndex((m) => m.slug === mode)
|
|
const currentModeIndex = allModes.findIndex((m) => m.slug === mode)
|
|
|
const nextModeIndex = (currentModeIndex + 1) % allModes.length
|
|
const nextModeIndex = (currentModeIndex + 1) % allModes.length
|
|
|
// Update local state and notify extension to sync mode change
|
|
// Update local state and notify extension to sync mode change
|
|
|
- setMode(allModes[nextModeIndex].slug)
|
|
|
|
|
- vscode.postMessage({
|
|
|
|
|
- type: "mode",
|
|
|
|
|
- text: allModes[nextModeIndex].slug,
|
|
|
|
|
- })
|
|
|
|
|
- }, [mode, setMode, customModes])
|
|
|
|
|
|
|
+ switchToMode(allModes[nextModeIndex].slug)
|
|
|
|
|
+ }, [mode, customModes, switchToMode])
|
|
|
|
|
|
|
|
// Add keyboard event handler
|
|
// Add keyboard event handler
|
|
|
const handleKeyDown = useCallback(
|
|
const handleKeyDown = useCallback(
|