diff --git a/src/core/Cline.ts b/src/core/Cline.ts index e2911976d..a31c02a2e 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -1085,35 +1085,23 @@ export class Cline { const askApproval = async (type: ClineAsk, partialMessage?: string) => { const { response, text, images } = await this.ask(type, partialMessage, false) if (response !== "yesButtonClicked") { - if (response === "messageResponse") { + // Handle both messageResponse and noButtonClicked with text + if (text) { await this.say("user_feedback", text, images) pushToolResult( formatResponse.toolResult(formatResponse.toolDeniedWithFeedback(text), images), ) - // this.userMessageContent.push({ - // type: "text", - // text: `${toolDescription()}`, - // }) - // this.toolResults.push({ - // type: "tool_result", - // tool_use_id: toolUseId, - // content: this.formatToolResponseWithImages( - // await this.formatToolDeniedFeedback(text), - // images - // ), - // }) - this.didRejectTool = true - return false + } else { + pushToolResult(formatResponse.toolDenied()) } - pushToolResult(formatResponse.toolDenied()) - // this.toolResults.push({ - // type: "tool_result", - // tool_use_id: toolUseId, - // content: await this.formatToolDenied(), - // }) this.didRejectTool = true return false } + // Handle yesButtonClicked with text + if (text) { + await this.say("user_feedback", text, images) + pushToolResult(formatResponse.toolResult(formatResponse.toolApprovedWithFeedback(text), images)) + } return true } diff --git a/src/core/prompts/responses.ts b/src/core/prompts/responses.ts index 05f33ba71..f06dff3d8 100644 --- a/src/core/prompts/responses.ts +++ b/src/core/prompts/responses.ts @@ -8,6 +8,9 @@ export const formatResponse = { toolDeniedWithFeedback: (feedback?: string) => `The user denied this operation and provided the following feedback:\n\n${feedback}\n`, + toolApprovedWithFeedback: (feedback?: string) => + `The user approved this operation and provided the following context:\n\n${feedback}\n`, + toolError: (error?: string) => `The tool execution failed with the following error:\n\n${error}\n`, noToolsUsed: () => diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index 0a32eef77..7f89c434e 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -337,56 +337,96 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie /* This logic depends on the useEffect[messages] above to set clineAsk, after which buttons are shown and we then send an askResponse to the extension. */ - const handlePrimaryButtonClick = useCallback(() => { - switch (clineAsk) { - case "api_req_failed": - case "command": - case "command_output": - case "tool": - case "browser_action_launch": - case "use_mcp_server": - case "resume_task": - case "mistake_limit_reached": - vscode.postMessage({ type: "askResponse", askResponse: "yesButtonClicked" }) - break - case "completion_result": - case "resume_completed_task": - // extension waiting for feedback. but we can just present a new task button - startNewTask() - break - } - setTextAreaDisabled(true) - setClineAsk(undefined) - setEnableButtons(false) - disableAutoScrollRef.current = false - }, [clineAsk, startNewTask]) - - const handleSecondaryButtonClick = useCallback(() => { - if (isStreaming) { - vscode.postMessage({ type: "cancelTask" }) - setDidClickCancel(true) - return - } + const handlePrimaryButtonClick = useCallback( + (text?: string, images?: string[]) => { + const trimmedInput = text?.trim() + switch (clineAsk) { + case "api_req_failed": + case "command": + case "command_output": + case "tool": + case "browser_action_launch": + case "use_mcp_server": + case "resume_task": + case "mistake_limit_reached": + // Only send text/images if they exist + if (trimmedInput || (images && images.length > 0)) { + vscode.postMessage({ + type: "askResponse", + askResponse: "yesButtonClicked", + text: trimmedInput, + images: images, + }) + } else { + vscode.postMessage({ + type: "askResponse", + askResponse: "yesButtonClicked", + }) + } + // Clear input state after sending + setInputValue("") + setSelectedImages([]) + break + case "completion_result": + case "resume_completed_task": + // extension waiting for feedback. but we can just present a new task button + startNewTask() + break + } + setTextAreaDisabled(true) + setClineAsk(undefined) + setEnableButtons(false) + disableAutoScrollRef.current = false + }, + [clineAsk, startNewTask], + ) - switch (clineAsk) { - case "api_req_failed": - case "mistake_limit_reached": - case "resume_task": - startNewTask() - break - case "command": - case "tool": - case "browser_action_launch": - case "use_mcp_server": - // responds to the API with a "This operation failed" and lets it try again - vscode.postMessage({ type: "askResponse", askResponse: "noButtonClicked" }) - break - } - setTextAreaDisabled(true) - setClineAsk(undefined) - setEnableButtons(false) - disableAutoScrollRef.current = false - }, [clineAsk, startNewTask, isStreaming]) + const handleSecondaryButtonClick = useCallback( + (text?: string, images?: string[]) => { + const trimmedInput = text?.trim() + if (isStreaming) { + vscode.postMessage({ type: "cancelTask" }) + setDidClickCancel(true) + return + } + + switch (clineAsk) { + case "api_req_failed": + case "mistake_limit_reached": + case "resume_task": + startNewTask() + break + case "command": + case "tool": + case "browser_action_launch": + case "use_mcp_server": + // Only send text/images if they exist + if (trimmedInput || (images && images.length > 0)) { + vscode.postMessage({ + type: "askResponse", + askResponse: "noButtonClicked", + text: trimmedInput, + images: images, + }) + } else { + // responds to the API with a "This operation failed" and lets it try again + vscode.postMessage({ + type: "askResponse", + askResponse: "noButtonClicked", + }) + } + // Clear input state after sending + setInputValue("") + setSelectedImages([]) + break + } + setTextAreaDisabled(true) + setClineAsk(undefined) + setEnableButtons(false) + disableAutoScrollRef.current = false + }, + [clineAsk, startNewTask, isStreaming], + ) const handleTaskCloseButtonClick = useCallback(() => { startNewTask() @@ -430,10 +470,10 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie handleSendMessage(message.text ?? "", message.images ?? []) break case "primaryButtonClick": - handlePrimaryButtonClick() + handlePrimaryButtonClick(message.text ?? "", message.images ?? []) break case "secondaryButtonClick": - handleSecondaryButtonClick() + handleSecondaryButtonClick(message.text ?? "", message.images ?? []) break } } @@ -1038,7 +1078,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie flex: secondaryButtonText ? 1 : 2, marginRight: secondaryButtonText ? "6px" : "0", }} - onClick={handlePrimaryButtonClick}> + onClick={(e) => handlePrimaryButtonClick(inputValue, selectedImages)}> {primaryButtonText} )} @@ -1050,7 +1090,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie flex: isStreaming ? 2 : 1, marginLeft: isStreaming ? 0 : "6px", }} - onClick={handleSecondaryButtonClick}> + onClick={(e) => handleSecondaryButtonClick(inputValue, selectedImages)}> {isStreaming ? "Cancel" : secondaryButtonText} )}