Skip to content

Commit 81a5e80

Browse files
authored
Merge pull request RooCodeInc#638 from napter/approval-feedback
Allow the user to send context with approval or rejection
2 parents 652773d + fe82500 commit 81a5e80

File tree

3 files changed

+105
-74
lines changed

3 files changed

+105
-74
lines changed

src/core/Cline.ts

Lines changed: 9 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1093,35 +1093,23 @@ export class Cline {
10931093
const askApproval = async (type: ClineAsk, partialMessage?: string) => {
10941094
const { response, text, images } = await this.ask(type, partialMessage, false)
10951095
if (response !== "yesButtonClicked") {
1096-
if (response === "messageResponse") {
1096+
// Handle both messageResponse and noButtonClicked with text
1097+
if (text) {
10971098
await this.say("user_feedback", text, images)
10981099
pushToolResult(
10991100
formatResponse.toolResult(formatResponse.toolDeniedWithFeedback(text), images),
11001101
)
1101-
// this.userMessageContent.push({
1102-
// type: "text",
1103-
// text: `${toolDescription()}`,
1104-
// })
1105-
// this.toolResults.push({
1106-
// type: "tool_result",
1107-
// tool_use_id: toolUseId,
1108-
// content: this.formatToolResponseWithImages(
1109-
// await this.formatToolDeniedFeedback(text),
1110-
// images
1111-
// ),
1112-
// })
1113-
this.didRejectTool = true
1114-
return false
1102+
} else {
1103+
pushToolResult(formatResponse.toolDenied())
11151104
}
1116-
pushToolResult(formatResponse.toolDenied())
1117-
// this.toolResults.push({
1118-
// type: "tool_result",
1119-
// tool_use_id: toolUseId,
1120-
// content: await this.formatToolDenied(),
1121-
// })
11221105
this.didRejectTool = true
11231106
return false
11241107
}
1108+
// Handle yesButtonClicked with text
1109+
if (text) {
1110+
await this.say("user_feedback", text, images)
1111+
pushToolResult(formatResponse.toolResult(formatResponse.toolApprovedWithFeedback(text), images))
1112+
}
11251113
return true
11261114
}
11271115

src/core/prompts/responses.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ export const formatResponse = {
88
toolDeniedWithFeedback: (feedback?: string) =>
99
`The user denied this operation and provided the following feedback:\n<feedback>\n${feedback}\n</feedback>`,
1010

11+
toolApprovedWithFeedback: (feedback?: string) =>
12+
`The user approved this operation and provided the following context:\n<feedback>\n${feedback}\n</feedback>`,
13+
1114
toolError: (error?: string) => `The tool execution failed with the following error:\n<error>\n${error}\n</error>`,
1215

1316
noToolsUsed: () =>

webview-ui/src/components/chat/ChatView.tsx

Lines changed: 93 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -337,56 +337,96 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
337337
/*
338338
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.
339339
*/
340-
const handlePrimaryButtonClick = useCallback(() => {
341-
switch (clineAsk) {
342-
case "api_req_failed":
343-
case "command":
344-
case "command_output":
345-
case "tool":
346-
case "browser_action_launch":
347-
case "use_mcp_server":
348-
case "resume_task":
349-
case "mistake_limit_reached":
350-
vscode.postMessage({ type: "askResponse", askResponse: "yesButtonClicked" })
351-
break
352-
case "completion_result":
353-
case "resume_completed_task":
354-
// extension waiting for feedback. but we can just present a new task button
355-
startNewTask()
356-
break
357-
}
358-
setTextAreaDisabled(true)
359-
setClineAsk(undefined)
360-
setEnableButtons(false)
361-
disableAutoScrollRef.current = false
362-
}, [clineAsk, startNewTask])
363-
364-
const handleSecondaryButtonClick = useCallback(() => {
365-
if (isStreaming) {
366-
vscode.postMessage({ type: "cancelTask" })
367-
setDidClickCancel(true)
368-
return
369-
}
340+
const handlePrimaryButtonClick = useCallback(
341+
(text?: string, images?: string[]) => {
342+
const trimmedInput = text?.trim()
343+
switch (clineAsk) {
344+
case "api_req_failed":
345+
case "command":
346+
case "command_output":
347+
case "tool":
348+
case "browser_action_launch":
349+
case "use_mcp_server":
350+
case "resume_task":
351+
case "mistake_limit_reached":
352+
// Only send text/images if they exist
353+
if (trimmedInput || (images && images.length > 0)) {
354+
vscode.postMessage({
355+
type: "askResponse",
356+
askResponse: "yesButtonClicked",
357+
text: trimmedInput,
358+
images: images,
359+
})
360+
} else {
361+
vscode.postMessage({
362+
type: "askResponse",
363+
askResponse: "yesButtonClicked",
364+
})
365+
}
366+
// Clear input state after sending
367+
setInputValue("")
368+
setSelectedImages([])
369+
break
370+
case "completion_result":
371+
case "resume_completed_task":
372+
// extension waiting for feedback. but we can just present a new task button
373+
startNewTask()
374+
break
375+
}
376+
setTextAreaDisabled(true)
377+
setClineAsk(undefined)
378+
setEnableButtons(false)
379+
disableAutoScrollRef.current = false
380+
},
381+
[clineAsk, startNewTask],
382+
)
370383

371-
switch (clineAsk) {
372-
case "api_req_failed":
373-
case "mistake_limit_reached":
374-
case "resume_task":
375-
startNewTask()
376-
break
377-
case "command":
378-
case "tool":
379-
case "browser_action_launch":
380-
case "use_mcp_server":
381-
// responds to the API with a "This operation failed" and lets it try again
382-
vscode.postMessage({ type: "askResponse", askResponse: "noButtonClicked" })
383-
break
384-
}
385-
setTextAreaDisabled(true)
386-
setClineAsk(undefined)
387-
setEnableButtons(false)
388-
disableAutoScrollRef.current = false
389-
}, [clineAsk, startNewTask, isStreaming])
384+
const handleSecondaryButtonClick = useCallback(
385+
(text?: string, images?: string[]) => {
386+
const trimmedInput = text?.trim()
387+
if (isStreaming) {
388+
vscode.postMessage({ type: "cancelTask" })
389+
setDidClickCancel(true)
390+
return
391+
}
392+
393+
switch (clineAsk) {
394+
case "api_req_failed":
395+
case "mistake_limit_reached":
396+
case "resume_task":
397+
startNewTask()
398+
break
399+
case "command":
400+
case "tool":
401+
case "browser_action_launch":
402+
case "use_mcp_server":
403+
// Only send text/images if they exist
404+
if (trimmedInput || (images && images.length > 0)) {
405+
vscode.postMessage({
406+
type: "askResponse",
407+
askResponse: "noButtonClicked",
408+
text: trimmedInput,
409+
images: images,
410+
})
411+
} else {
412+
// responds to the API with a "This operation failed" and lets it try again
413+
vscode.postMessage({
414+
type: "askResponse",
415+
askResponse: "noButtonClicked",
416+
})
417+
}
418+
// Clear input state after sending
419+
setInputValue("")
420+
setSelectedImages([])
421+
break
422+
}
423+
setTextAreaDisabled(true)
424+
setClineAsk(undefined)
425+
setEnableButtons(false)
426+
disableAutoScrollRef.current = false
427+
},
428+
[clineAsk, startNewTask, isStreaming],
429+
)
390430

391431
const handleTaskCloseButtonClick = useCallback(() => {
392432
startNewTask()
@@ -430,10 +470,10 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
430470
handleSendMessage(message.text ?? "", message.images ?? [])
431471
break
432472
case "primaryButtonClick":
433-
handlePrimaryButtonClick()
473+
handlePrimaryButtonClick(message.text ?? "", message.images ?? [])
434474
break
435475
case "secondaryButtonClick":
436-
handleSecondaryButtonClick()
476+
handleSecondaryButtonClick(message.text ?? "", message.images ?? [])
437477
break
438478
}
439479
}
@@ -1038,7 +1078,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
10381078
flex: secondaryButtonText ? 1 : 2,
10391079
marginRight: secondaryButtonText ? "6px" : "0",
10401080
}}
1041-
onClick={handlePrimaryButtonClick}>
1081+
onClick={(e) => handlePrimaryButtonClick(inputValue, selectedImages)}>
10421082
{primaryButtonText}
10431083
</VSCodeButton>
10441084
)}
@@ -1050,7 +1090,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
10501090
flex: isStreaming ? 2 : 1,
10511091
marginLeft: isStreaming ? 0 : "6px",
10521092
}}
1053-
onClick={handleSecondaryButtonClick}>
1093+
onClick={(e) => handleSecondaryButtonClick(inputValue, selectedImages)}>
10541094
{isStreaming ? "Cancel" : secondaryButtonText}
10551095
</VSCodeButton>
10561096
)}

0 commit comments

Comments
 (0)