diff --git a/src/ClaudeDev.ts b/src/ClaudeDev.ts index b4c9b5f..193cbc2 100644 --- a/src/ClaudeDev.ts +++ b/src/ClaudeDev.ts @@ -334,7 +334,7 @@ export class ClaudeDev { }) .join("") - const { response } = await this.ask( + const { response, text } = await this.ask( "tool", JSON.stringify({ tool: "editedExistingFile", @@ -343,18 +343,26 @@ export class ClaudeDev { } as ClaudeSayTool) ) if (response !== "yesButtonTapped") { - return "This operation was not approved by the user." + if (response === "textResponse" && text) { + await this.say("user_feedback", text) + return `The user denied this operation and provided the following feedback:\n\"${text}\"` + } + return "The user denied this operation." } await fs.writeFile(filePath, newContent) return `Changes applied to ${filePath}:\n${diffResult}` } else { - const { response } = await this.ask( + const { response, text } = await this.ask( "tool", JSON.stringify({ tool: "newFileCreated", path: filePath, content: newContent } as ClaudeSayTool) ) if (response !== "yesButtonTapped") { - return "This operation was not approved by the user." + if (response === "textResponse" && text) { + await this.say("user_feedback", text) + return `The user denied this operation and provided the following feedback:\n\"${text}\"` + } + return "The user denied this operation." } await fs.mkdir(path.dirname(filePath), { recursive: true }) await fs.writeFile(filePath, newContent) @@ -370,12 +378,16 @@ export class ClaudeDev { async readFile(filePath: string): Promise { try { const content = await fs.readFile(filePath, "utf-8") - const { response } = await this.ask( + const { response, text } = await this.ask( "tool", JSON.stringify({ tool: "readFile", path: filePath, content } as ClaudeSayTool) ) if (response !== "yesButtonTapped") { - return "This operation was not approved by the user." + if (response === "textResponse" && text) { + await this.say("user_feedback", text) + return `The user denied this operation and provided the following feedback:\n\"${text}\"` + } + return "The user denied this operation." } return content } catch (error) { @@ -391,12 +403,16 @@ export class ClaudeDev { const isRoot = absolutePath === root if (isRoot) { if (shouldLog) { - const { response } = await this.ask( + const { response, text } = await this.ask( "tool", JSON.stringify({ tool: "listFiles", path: dirPath, content: root } as ClaudeSayTool) ) if (response !== "yesButtonTapped") { - return "This operation was not approved by the user." + if (response === "textResponse" && text) { + await this.say("user_feedback", text) + return `The user denied this operation and provided the following feedback:\n\"${text}\"` + } + return "The user denied this operation." } } return root @@ -412,12 +428,16 @@ export class ClaudeDev { const entries = await glob("*", options) const result = entries.slice(0, 500).join("\n") // truncate to 500 entries if (shouldLog) { - const { response } = await this.ask( + const { response, text } = await this.ask( "tool", JSON.stringify({ tool: "listFiles", path: dirPath, content: result } as ClaudeSayTool) ) if (response !== "yesButtonTapped") { - return "This operation was not approved by the user." + if (response === "textResponse" && text) { + await this.say("user_feedback", text) + return `The user denied this operation and provided the following feedback:\n\"${text}\"` + } + return "The user denied this operation." } } return result @@ -434,9 +454,13 @@ export class ClaudeDev { } async executeCommand(command: string): Promise { - const { response } = await this.ask("command", command) + const { response, text } = await this.ask("command", command) if (response !== "yesButtonTapped") { - return "Command execution was not approved by the user." + if (response === "textResponse" && text) { + await this.say("user_feedback", text) + return `The user denied this operation and provided the following feedback:\n\"${text}\"` + } + return "The user denied this operation." } try { let result = "" diff --git a/webview-ui/src/components/ChatView.tsx b/webview-ui/src/components/ChatView.tsx index 1ba0865..630df97 100644 --- a/webview-ui/src/components/ChatView.tsx +++ b/webview-ui/src/components/ChatView.tsx @@ -99,18 +99,18 @@ const ChatView = ({ messages, isHidden, vscodeThemeName }: ChatViewProps) => { // setSecondaryButtonText(undefined) break case "tool": - setTextAreaDisabled(true) + setTextAreaDisabled(false) setClaudeAsk("tool") setEnableButtons(true) setPrimaryButtonText("Approve") - setSecondaryButtonText("Cancel") + setSecondaryButtonText("Reject") break case "command": - setTextAreaDisabled(true) + setTextAreaDisabled(false) setClaudeAsk("command") setEnableButtons(true) setPrimaryButtonText("Run Command") - setSecondaryButtonText("Cancel") + setSecondaryButtonText("Reject") break case "completion_result": // extension waiting for feedback. but we can just present a new task button @@ -170,6 +170,8 @@ const ChatView = ({ messages, isHidden, vscodeThemeName }: ChatViewProps) => { } else if (claudeAsk) { switch (claudeAsk) { case "followup": + case "tool": + case "command": // user can provide feedback to a tool or command use case "completion_result": // if this happens then the user has feedback for the completion result vscode.postMessage({ type: "askResponse", askResponse: "textResponse", text }) break @@ -210,10 +212,11 @@ const ChatView = ({ messages, isHidden, vscodeThemeName }: ChatViewProps) => { const handleSecondaryButtonClick = () => { switch (claudeAsk) { case "request_limit_reached": - case "tool": // TODO: for now when a user cancels, it starts a new task. But we could easily just respond to the API with a "This operation failed" and let it try again. startNewTask() break case "command": + case "tool": + // responds to the API with a "This operation failed" and lets it try again vscode.postMessage({ type: "askResponse", askResponse: "noButtonTapped" }) break } @@ -272,7 +275,7 @@ const ChatView = ({ messages, isHidden, vscodeThemeName }: ChatViewProps) => { }, [textAreaRef.current]) useEffect(() => { - if (!isHidden && !textAreaDisabled) { + if (!isHidden && !textAreaDisabled && !enableButtons) { textAreaRef.current?.focus() } // eslint-disable-next-line react-hooks/exhaustive-deps @@ -280,13 +283,14 @@ const ChatView = ({ messages, isHidden, vscodeThemeName }: ChatViewProps) => { useEffect(() => { const timer = setTimeout(() => { - if (!textAreaDisabled) { + if (!textAreaDisabled && !enableButtons) { textAreaRef.current?.focus() } }, 50) return () => { clearTimeout(timer) } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [textAreaDisabled]) return (