diff --git a/src/core/Cline.ts b/src/core/Cline.ts index cfe8eaaee7..2adc3c86d4 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -66,7 +66,6 @@ export class Cline { private browserSession: BrowserSession private didEditFile: boolean = false customInstructions?: string - alwaysAllowReadOnly: boolean autoApprovalSettings: AutoApprovalSettings apiConversationHistory: Anthropic.MessageParam[] = [] clineMessages: ClineMessage[] = [] @@ -97,7 +96,6 @@ export class Cline { apiConfiguration: ApiConfiguration, autoApprovalSettings: AutoApprovalSettings, customInstructions?: string, - alwaysAllowReadOnly?: boolean, task?: string, images?: string[], historyItem?: HistoryItem, @@ -109,7 +107,6 @@ export class Cline { this.browserSession = new BrowserSession(provider.context) this.diffViewProvider = new DiffViewProvider(cwd) this.customInstructions = customInstructions - this.alwaysAllowReadOnly = alwaysAllowReadOnly ?? false this.autoApprovalSettings = autoApprovalSettings if (historyItem) { this.taskId = historyItem.id @@ -754,6 +751,29 @@ export class Cline { } } + shouldAutoApproveTool(toolName: ToolUseName): boolean { + if (this.autoApprovalSettings.enabled) { + switch (toolName) { + case "read_file": + case "list_files": + case "list_code_definition_names": + case "search_files": + return this.autoApprovalSettings.actions.readFiles + case "write_to_file": + case "replace_in_file": + return this.autoApprovalSettings.actions.editFiles + case "execute_command": + return this.autoApprovalSettings.actions.executeCommands + case "browser_action": + return this.autoApprovalSettings.actions.useBrowser + case "access_mcp_resource": + case "use_mcp_tool": + return this.autoApprovalSettings.actions.useMcp + } + } + return false + } + async *attemptApiRequest(previousApiReqIndex: number): ApiStream { // Wait for MCP servers to be connected before generating system prompt await pWaitFor(() => this.providerRef.deref()?.mcpHub?.isConnecting !== true, { timeout: 10_000 }).catch(() => { @@ -1110,7 +1130,11 @@ export class Cline { if (block.partial) { // update gui message const partialMessage = JSON.stringify(sharedMessageProps) - await this.ask("tool", partialMessage, block.partial).catch(() => {}) + if (this.shouldAutoApproveTool(block.name)) { + await this.say("tool", partialMessage, undefined, block.partial) + } else { + await this.ask("tool", partialMessage, block.partial).catch(() => {}) + } // update editor if (!this.diffViewProvider.isEditing) { // open the editor and prepare to stream content in @@ -1165,11 +1189,17 @@ export class Cline { // ) // : undefined, } satisfies ClineSayTool) - const didApprove = await askApproval("tool", completeMessage) - if (!didApprove) { - await this.diffViewProvider.revertChanges() - break + + if (this.shouldAutoApproveTool(block.name)) { + await this.say("tool", completeMessage, undefined, false) + } else { + const didApprove = await askApproval("tool", completeMessage) + if (!didApprove) { + await this.diffViewProvider.revertChanges() + break + } } + const { newProblemsMessage, userEdits, finalContent } = await this.diffViewProvider.saveChanges() this.didEditFile = true // used to determine if we should wait for busy terminal to update before sending api request @@ -1224,7 +1254,7 @@ export class Cline { ...sharedMessageProps, content: undefined, } satisfies ClineSayTool) - if (this.alwaysAllowReadOnly) { + if (this.shouldAutoApproveTool(block.name)) { await this.say("tool", partialMessage, undefined, block.partial) } else { await this.ask("tool", partialMessage, block.partial).catch(() => {}) @@ -1242,7 +1272,7 @@ export class Cline { ...sharedMessageProps, content: absolutePath, } satisfies ClineSayTool) - if (this.alwaysAllowReadOnly) { + if (this.shouldAutoApproveTool(block.name)) { await this.say("tool", completeMessage, undefined, false) // need to be sending partialValue bool, since undefined has its own purpose in that the message is treated neither as a partial or completion of a partial, but as a single complete message } else { const didApprove = await askApproval("tool", completeMessage) @@ -1274,7 +1304,7 @@ export class Cline { ...sharedMessageProps, content: "", } satisfies ClineSayTool) - if (this.alwaysAllowReadOnly) { + if (this.shouldAutoApproveTool(block.name)) { await this.say("tool", partialMessage, undefined, block.partial) } else { await this.ask("tool", partialMessage, block.partial).catch(() => {}) @@ -1294,7 +1324,7 @@ export class Cline { ...sharedMessageProps, content: result, } satisfies ClineSayTool) - if (this.alwaysAllowReadOnly) { + if (this.shouldAutoApproveTool(block.name)) { await this.say("tool", completeMessage, undefined, false) } else { const didApprove = await askApproval("tool", completeMessage) @@ -1322,7 +1352,7 @@ export class Cline { ...sharedMessageProps, content: "", } satisfies ClineSayTool) - if (this.alwaysAllowReadOnly) { + if (this.shouldAutoApproveTool(block.name)) { await this.say("tool", partialMessage, undefined, block.partial) } else { await this.ask("tool", partialMessage, block.partial).catch(() => {}) @@ -1343,7 +1373,7 @@ export class Cline { ...sharedMessageProps, content: result, } satisfies ClineSayTool) - if (this.alwaysAllowReadOnly) { + if (this.shouldAutoApproveTool(block.name)) { await this.say("tool", completeMessage, undefined, false) } else { const didApprove = await askApproval("tool", completeMessage) @@ -1375,7 +1405,7 @@ export class Cline { ...sharedMessageProps, content: "", } satisfies ClineSayTool) - if (this.alwaysAllowReadOnly) { + if (this.shouldAutoApproveTool(block.name)) { await this.say("tool", partialMessage, undefined, block.partial) } else { await this.ask("tool", partialMessage, block.partial).catch(() => {}) @@ -1399,7 +1429,7 @@ export class Cline { ...sharedMessageProps, content: results, } satisfies ClineSayTool) - if (this.alwaysAllowReadOnly) { + if (this.shouldAutoApproveTool(block.name)) { await this.say("tool", completeMessage, undefined, false) } else { const didApprove = await askApproval("tool", completeMessage) @@ -1434,11 +1464,20 @@ export class Cline { try { if (block.partial) { if (action === "launch") { - await this.ask( - "browser_action_launch", - removeClosingTag("url", url), - block.partial, - ).catch(() => {}) + if (this.shouldAutoApproveTool(block.name)) { + await this.say( + "browser_action_launch", + removeClosingTag("url", url), + undefined, + block.partial, + ) + } else { + await this.ask( + "browser_action_launch", + removeClosingTag("url", url), + block.partial, + ).catch(() => {}) + } } else { await this.say( "browser_action", @@ -1464,9 +1503,14 @@ export class Cline { break } this.consecutiveMistakeCount = 0 - const didApprove = await askApproval("browser_action_launch", url) - if (!didApprove) { - break + + if (this.shouldAutoApproveTool(block.name)) { + await this.say("browser_action_launch", url, undefined, false) + } else { + const didApprove = await askApproval("browser_action_launch", url) + if (!didApprove) { + break + } } // NOTE: it's okay that we call this message since the partial inspect_site is finished streaming. The only scenario we have to avoid is sending messages WHILE a partial message exists at the end of the messages array. For example the api_req_finished message would interfere with the partial message, so we needed to remove that. @@ -1568,9 +1612,20 @@ export class Cline { try { if (block.partial) { - await this.ask("command", removeClosingTag("command", command), block.partial).catch( - () => {}, - ) + if (this.shouldAutoApproveTool(block.name)) { + await this.say( + "command", + removeClosingTag("command", command), + undefined, + block.partial, + ).catch(() => {}) + } else { + await this.ask( + "command", + removeClosingTag("command", command), + block.partial, + ).catch(() => {}) + } break } else { if (!command) { @@ -1591,13 +1646,20 @@ export class Cline { break } this.consecutiveMistakeCount = 0 - const didApprove = await askApproval( - "command", - command + `${requiresApproval ? COMMAND_REQ_APP_STRING : ""}`, // ugly hack until we refactor combineCommandSequences - ) - if (!didApprove) { - break + + if (!requiresApproval && this.shouldAutoApproveTool(block.name)) { + await this.say("command", command, undefined, false) + } else { + const didApprove = await askApproval( + "command", + command + + `${this.shouldAutoApproveTool(block.name) && requiresApproval ? COMMAND_REQ_APP_STRING : ""}`, // ugly hack until we refactor combineCommandSequences + ) + if (!didApprove) { + break + } } + const [userRejected, result] = await this.executeCommandTool(command) if (userRejected) { this.didRejectTool = true @@ -1622,7 +1684,13 @@ export class Cline { toolName: removeClosingTag("tool_name", tool_name), arguments: removeClosingTag("arguments", mcp_arguments), } satisfies ClineAskUseMcpServer) - await this.ask("use_mcp_server", partialMessage, block.partial).catch(() => {}) + + if (this.shouldAutoApproveTool(block.name)) { + await this.say("use_mcp_server", partialMessage, undefined, block.partial) + } else { + await this.ask("use_mcp_server", partialMessage, block.partial).catch(() => {}) + } + break } else { if (!server_name) { @@ -1670,10 +1738,16 @@ export class Cline { toolName: tool_name, arguments: mcp_arguments, } satisfies ClineAskUseMcpServer) - const didApprove = await askApproval("use_mcp_server", completeMessage) - if (!didApprove) { - break + + if (this.shouldAutoApproveTool(block.name)) { + await this.say("use_mcp_server", completeMessage, undefined, false) + } else { + const didApprove = await askApproval("use_mcp_server", completeMessage) + if (!didApprove) { + break + } } + // now execute the tool await this.say("mcp_server_request_started") // same as browser_action_result const toolResult = await this.providerRef @@ -1715,7 +1789,13 @@ export class Cline { serverName: removeClosingTag("server_name", server_name), uri: removeClosingTag("uri", uri), } satisfies ClineAskUseMcpServer) - await this.ask("use_mcp_server", partialMessage, block.partial).catch(() => {}) + + if (this.shouldAutoApproveTool(block.name)) { + await this.say("use_mcp_server", partialMessage, undefined, block.partial) + } else { + await this.ask("use_mcp_server", partialMessage, block.partial).catch(() => {}) + } + break } else { if (!server_name) { @@ -1738,10 +1818,16 @@ export class Cline { serverName: server_name, uri, } satisfies ClineAskUseMcpServer) - const didApprove = await askApproval("use_mcp_server", completeMessage) - if (!didApprove) { - break + + if (this.shouldAutoApproveTool(block.name)) { + await this.say("use_mcp_server", completeMessage, undefined, false) + } else { + const didApprove = await askApproval("use_mcp_server", completeMessage) + if (!didApprove) { + break + } } + // now execute the tool await this.say("mcp_server_request_started") const resourceResult = await this.providerRef @@ -1824,6 +1910,7 @@ export class Cline { // remove the previous partial attempt_completion ask, replace with say, post state to webview, then stream command // const secondLastMessage = this.clineMessages.at(-2) + // NOTE: we do not want to auto approve a command run as part of the attempt_completion tool if (lastMessage && lastMessage.ask === "command") { // update command await this.ask( diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index be8dad8cc2..9661615476 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -48,7 +48,6 @@ type GlobalStateKey = | "vertexRegion" | "lastShownAnnouncementId" | "customInstructions" - | "alwaysAllowReadOnly" | "taskHistory" | "openAiBaseUrl" | "openAiModelId" @@ -200,29 +199,18 @@ export class ClineProvider implements vscode.WebviewViewProvider { async initClineWithTask(task?: string, images?: string[]) { await this.clearTask() // ensures that an exising task doesn't exist before starting a new one, although this shouldn't be possible since user must clear task before starting a new one - const { apiConfiguration, customInstructions, alwaysAllowReadOnly, autoApprovalSettings } = - await this.getState() - this.cline = new Cline( - this, - apiConfiguration, - autoApprovalSettings, - customInstructions, - alwaysAllowReadOnly, - task, - images, - ) + const { apiConfiguration, customInstructions, autoApprovalSettings } = await this.getState() + this.cline = new Cline(this, apiConfiguration, autoApprovalSettings, customInstructions, task, images) } async initClineWithHistoryItem(historyItem: HistoryItem) { await this.clearTask() - const { apiConfiguration, customInstructions, alwaysAllowReadOnly, autoApprovalSettings } = - await this.getState() + const { apiConfiguration, customInstructions, autoApprovalSettings } = await this.getState() this.cline = new Cline( this, apiConfiguration, autoApprovalSettings, customInstructions, - alwaysAllowReadOnly, undefined, undefined, historyItem, @@ -426,13 +414,6 @@ export class ClineProvider implements vscode.WebviewViewProvider { case "customInstructions": await this.updateCustomInstructions(message.text) break - case "alwaysAllowReadOnly": - await this.updateGlobalState("alwaysAllowReadOnly", message.bool ?? undefined) - if (this.cline) { - this.cline.alwaysAllowReadOnly = message.bool ?? false - } - await this.postStateToWebview() - break case "autoApprovalSettings": if (message.autoApprovalSettings) { await this.updateGlobalState("autoApprovalSettings", message.autoApprovalSettings) @@ -846,19 +827,12 @@ export class ClineProvider implements vscode.WebviewViewProvider { } async getStateToPostToWebview() { - const { - apiConfiguration, - lastShownAnnouncementId, - customInstructions, - alwaysAllowReadOnly, - taskHistory, - autoApprovalSettings, - } = await this.getState() + const { apiConfiguration, lastShownAnnouncementId, customInstructions, taskHistory, autoApprovalSettings } = + await this.getState() return { version: this.context.extension?.packageJSON?.version ?? "", apiConfiguration, customInstructions, - alwaysAllowReadOnly, uriScheme: vscode.env.uriScheme, clineMessages: this.cline?.clineMessages || [], taskHistory: (taskHistory || []).filter((item) => item.ts && item.task).sort((a, b) => b.ts - a.ts), @@ -946,7 +920,6 @@ export class ClineProvider implements vscode.WebviewViewProvider { openRouterModelInfo, lastShownAnnouncementId, customInstructions, - alwaysAllowReadOnly, taskHistory, autoApprovalSettings, ] = await Promise.all([ @@ -976,7 +949,6 @@ export class ClineProvider implements vscode.WebviewViewProvider { this.getGlobalState("openRouterModelInfo") as Promise, this.getGlobalState("lastShownAnnouncementId") as Promise, this.getGlobalState("customInstructions") as Promise, - this.getGlobalState("alwaysAllowReadOnly") as Promise, this.getGlobalState("taskHistory") as Promise, this.getGlobalState("autoApprovalSettings") as Promise, ]) @@ -1024,7 +996,6 @@ export class ClineProvider implements vscode.WebviewViewProvider { }, lastShownAnnouncementId, customInstructions, - alwaysAllowReadOnly: alwaysAllowReadOnly ?? false, taskHistory, autoApprovalSettings: autoApprovalSettings || DEFAULT_AUTO_APPROVAL_SETTINGS, // default value can be 0 or empty string } diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index efa43e1dc3..3ec5e7f098 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -41,7 +41,6 @@ export interface ExtensionState { version: string apiConfiguration?: ApiConfiguration customInstructions?: string - alwaysAllowReadOnly?: boolean uriScheme?: string clineMessages: ClineMessage[] taskHistory: HistoryItem[] @@ -82,13 +81,16 @@ export type ClineSay = | "user_feedback" | "user_feedback_diff" | "api_req_retried" + | "command" | "command_output" | "tool" | "shell_integration_warning" + | "browser_action_launch" | "browser_action" | "browser_action_result" | "mcp_server_request_started" | "mcp_server_response" + | "use_mcp_server" export interface ClineSayTool { tool: diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index 0d0f727aa9..82ad22d95e 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -5,7 +5,6 @@ export interface WebviewMessage { type: | "apiConfiguration" | "customInstructions" - | "alwaysAllowReadOnly" | "webviewDidLaunch" | "newTask" | "askResponse" diff --git a/src/shared/combineCommandSequences.ts b/src/shared/combineCommandSequences.ts index d864dec6ff..3e41cd2df9 100644 --- a/src/shared/combineCommandSequences.ts +++ b/src/shared/combineCommandSequences.ts @@ -25,13 +25,13 @@ export function combineCommandSequences(messages: ClineMessage[]): ClineMessage[ // First pass: combine commands with their outputs for (let i = 0; i < messages.length; i++) { - if (messages[i].type === "ask" && messages[i].ask === "command") { + if (messages[i].type === "ask" && (messages[i].ask === "command" || messages[i].say === "command")) { let combinedText = messages[i].text || "" let didAddOutput = false let j = i + 1 while (j < messages.length) { - if (messages[j].type === "ask" && messages[j].ask === "command") { + if (messages[j].type === "ask" && (messages[j].ask === "command" || messages[j].say === "command")) { // Stop if we encounter the next command break } @@ -63,7 +63,7 @@ export function combineCommandSequences(messages: ClineMessage[]): ClineMessage[ return messages .filter((msg) => !(msg.ask === "command_output" || msg.say === "command_output")) .map((msg) => { - if (msg.type === "ask" && msg.ask === "command") { + if (msg.type === "ask" && (msg.ask === "command" || msg.say === "command")) { const combinedCommand = combinedCommands.find((cmd) => cmd.ts === msg.ts) return combinedCommand || msg } diff --git a/webview-ui/src/components/chat/BrowserSessionRow.tsx b/webview-ui/src/components/chat/BrowserSessionRow.tsx index dec573e25c..60320fd387 100644 --- a/webview-ui/src/components/chat/BrowserSessionRow.tsx +++ b/webview-ui/src/components/chat/BrowserSessionRow.tsx @@ -66,7 +66,7 @@ const BrowserSessionRow = memo((props: BrowserSessionRowProps) => { let nextActionMessages: ClineMessage[] = [] messages.forEach((message) => { - if (message.ask === "browser_action_launch") { + if (message.ask === "browser_action_launch" || message.say === "browser_action_launch") { // Start first page currentStateMessages = [message] } else if (message.say === "browser_action_result") { @@ -137,10 +137,19 @@ const BrowserSessionRow = memo((props: BrowserSessionRowProps) => { // Get initial URL from launch message const initialUrl = useMemo(() => { - const launchMessage = messages.find((m) => m.ask === "browser_action_launch") + const launchMessage = messages.find( + (m) => m.ask === "browser_action_launch" || m.say === "browser_action_launch", + ) return launchMessage?.text || "" }, [messages]) + const isAutoApproved = useMemo(() => { + const launchMessage = messages.find( + (m) => m.ask === "browser_action_launch" || m.say === "browser_action_launch", + ) + return launchMessage?.say === "browser_action_launch" + }, [messages]) + // Find the latest available URL and screenshot const latestState = useMemo(() => { for (let i = pages.length - 1; i >= 0; i--) { @@ -232,7 +241,7 @@ const BrowserSessionRow = memo((props: BrowserSessionRowProps) => { style={{ color: "var(--vscode-foreground)", marginBottom: "-1.5px" }}> )} - <>Cline wants to use the browser: + <>{isAutoApproved ? "Cline is using the browser:" : "Cline wants to use the browser:"}
+
+ Browser Session Started +
+
+ +
+ + ) + } + switch (message.type) { case "say": switch (message.say) { @@ -456,24 +484,6 @@ const BrowserSessionRowContent = ({ case "ask": switch (message.ask) { - case "browser_action_launch": - return ( - <> -
- Browser Session Started -
-
- -
- - ) - default: return null } diff --git a/webview-ui/src/components/chat/ChatRow.tsx b/webview-ui/src/components/chat/ChatRow.tsx index 6afd1afd8c..8f161152bf 100644 --- a/webview-ui/src/components/chat/ChatRow.tsx +++ b/webview-ui/src/components/chat/ChatRow.tsx @@ -90,7 +90,9 @@ export const ChatRowContent = ({ ? lastModifiedMessage?.text : undefined const isCommandExecuting = - isLast && lastModifiedMessage?.ask === "command" && lastModifiedMessage?.text?.includes(COMMAND_OUTPUT_STRING) + isLast && + (lastModifiedMessage?.ask === "command" || lastModifiedMessage?.say === "command") && + lastModifiedMessage?.text?.includes(COMMAND_OUTPUT_STRING) const isMcpServerResponding = isLast && lastModifiedMessage?.say === "mcp_server_request_started" @@ -127,7 +129,9 @@ export const ChatRowContent = ({ style={{ color: normalColor, marginBottom: "-1.5px" }}> ), - Cline wants to execute this command: + {message.type === "ask" + ? "Cline wants to execute this command:" + : "Cline executed this command:"} , ] case "use_mcp_server": @@ -141,8 +145,18 @@ export const ChatRowContent = ({ style={{ color: normalColor, marginBottom: "-1.5px" }}> ), - Cline wants to {mcpServerUse.type === "use_mcp_tool" ? "use a tool" : "access a resource"} on - the {mcpServerUse.serverName} MCP server: + {message.type === "ask" ? ( + <> + Cline wants to{" "} + {mcpServerUse.type === "use_mcp_tool" ? "use a tool" : "access a resource"} on the{" "} + {mcpServerUse.serverName} MCP server: + + ) : ( + <> + Cline {mcpServerUse.type === "use_mcp_tool" ? "used a tool" : "accessed a resource"} on + the {mcpServerUse.serverName} MCP server: + + )} , ] case "completion_result": @@ -217,6 +231,7 @@ export const ChatRowContent = ({ apiReqCancelReason, isMcpServerResponding, message.text, + message.type, ]) const headerStyle: React.CSSProperties = { @@ -253,7 +268,9 @@ export const ChatRowContent = ({ <>
{toolIcon("edit")} - Cline wants to edit this file: + + {message.type === "ask" ? "Cline wants to edit this file:" : "Cline edited this file:"} +
{toolIcon("new-file")} - Cline wants to create a new file: + + {message.type === "ask" + ? "Cline wants to create a new file:" + : "Cline created a new file:"} +
{ + const outputIndex = text.indexOf(COMMAND_OUTPUT_STRING) + if (outputIndex === -1) { + return { command: text, output: "" } + } + return { + command: text.slice(0, outputIndex).trim(), + output: text + .slice(outputIndex + COMMAND_OUTPUT_STRING.length) + .trim() + .split("") + .map((char) => { + switch (char) { + case "\t": + return "→ " + case "\b": + return "⌫" + case "\f": + return "⏏" + case "\v": + return "⇳" + default: + return char + } + }) + .join(""), + } + } + + const { command: rawCommand, output } = splitMessage(message.text || "") + + const requestsApproval = rawCommand.endsWith(COMMAND_REQ_APP_STRING) + const command = requestsApproval ? rawCommand.slice(0, -COMMAND_REQ_APP_STRING.length) : rawCommand + + return ( + <> +
+ {icon} + {title} +
+ {/* 0} + /> */} +
+ + {output.length > 0 && ( +
+
+ + Command Output +
+ {isExpanded && } +
+ )} +
+ {requestsApproval && ( +
+ + The model has determined this command requires explicit approval +
+ )} + + ) + } + + if (message.ask === "use_mcp_server" || message.say === "use_mcp_server") { + const useMcpServer = JSON.parse(message.text || "{}") as ClineAskUseMcpServer + const server = mcpServers.find((server) => server.name === useMcpServer.serverName) + return ( + <> +
+ {icon} + {title} +
+ +
+ {useMcpServer.type === "access_mcp_resource" && ( + + )} + + {useMcpServer.type === "use_mcp_tool" && ( + <> + tool.name === useMcpServer.toolName) + ?.description || "", + }} + /> + {useMcpServer.arguments && useMcpServer.arguments !== "{}" && ( +
+
+ Arguments +
+ +
+ )} + + )} +
+ + ) + } + switch (message.type) { case "say": switch (message.say) { @@ -702,166 +886,7 @@ export const ChatRowContent = ({

{message.text}

) - case "command": - const splitMessage = (text: string) => { - const outputIndex = text.indexOf(COMMAND_OUTPUT_STRING) - if (outputIndex === -1) { - return { command: text, output: "" } - } - return { - command: text.slice(0, outputIndex).trim(), - output: text - .slice(outputIndex + COMMAND_OUTPUT_STRING.length) - .trim() - .split("") - .map((char) => { - switch (char) { - case "\t": - return "→ " - case "\b": - return "⌫" - case "\f": - return "⏏" - case "\v": - return "⇳" - default: - return char - } - }) - .join(""), - } - } - const { command: rawCommand, output } = splitMessage(message.text || "") - - const requestsApproval = rawCommand.endsWith(COMMAND_REQ_APP_STRING) - const command = requestsApproval ? rawCommand.slice(0, -COMMAND_REQ_APP_STRING.length) : rawCommand - - return ( - <> -
- {icon} - {title} -
- {/* 0} - /> */} -
- - {output.length > 0 && ( -
-
- - Command Output -
- {isExpanded && } -
- )} -
- {requestsApproval && ( -
- - The model has determined this command requires explicit approval -
- )} - - ) - case "use_mcp_server": - const useMcpServer = JSON.parse(message.text || "{}") as ClineAskUseMcpServer - const server = mcpServers.find((server) => server.name === useMcpServer.serverName) - return ( - <> -
- {icon} - {title} -
- -
- {useMcpServer.type === "access_mcp_resource" && ( - - )} - - {useMcpServer.type === "use_mcp_tool" && ( - <> - tool.name === useMcpServer.toolName) - ?.description || "", - }} - /> - {useMcpServer.arguments && useMcpServer.arguments !== "{}" && ( -
-
- Arguments -
- -
- )} - - )} -
- - ) case "completion_result": if (message.text) { return ( diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index 2668a41701..a7e33df9f2 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -52,8 +52,8 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie // we need to hold on to the ask because useEffect > lastMessage will always let us know when an ask comes in and handle it, but by the time handleMessage is called, the last message might not be the ask anymore (it could be a say that followed) const [clineAsk, setClineAsk] = useState(undefined) const [enableButtons, setEnableButtons] = useState(false) - const [primaryButtonText, setPrimaryButtonText] = useState(undefined) - const [secondaryButtonText, setSecondaryButtonText] = useState(undefined) + const [primaryButtonText, setPrimaryButtonText] = useState("Approve") + const [secondaryButtonText, setSecondaryButtonText] = useState("Reject") const [didClickCancel, setDidClickCancel] = useState(false) const virtuosoRef = useRef(null) const [expandedRows, setExpandedRows] = useState>({}) @@ -186,6 +186,9 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie case "text": case "browser_action": case "browser_action_result": + case "browser_action_launch": + case "command": + case "use_mcp_server": case "command_output": case "mcp_server_request_started": case "mcp_server_response": @@ -210,8 +213,8 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie setTextAreaDisabled(false) setClineAsk(undefined) setEnableButtons(false) - setPrimaryButtonText(undefined) - setSecondaryButtonText(undefined) + setPrimaryButtonText("Approve") + setSecondaryButtonText("Reject") } }, [messages.length]) @@ -462,7 +465,13 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie return ["browser_action_launch"].includes(message.ask!) } if (message.type === "say") { - return ["api_req_started", "text", "browser_action", "browser_action_result"].includes(message.say!) + return [ + "browser_action_launch", + "api_req_started", + "text", + "browser_action", + "browser_action_result", + ].includes(message.say!) } return false } @@ -481,7 +490,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie } visibleMessages.forEach((message) => { - if (message.ask === "browser_action_launch") { + if (message.ask === "browser_action_launch" || message.say === "browser_action_launch") { // complete existing browser session if any endBrowserSession() // start new @@ -881,7 +890,7 @@ const ScrollToBottomButton = styled.div` justify-content: center; align-items: center; flex: 1; - height: 25px; + height: 24px; &:hover { background-color: color-mix(in srgb, var(--vscode-toolbar-hoverBackground) 90%, transparent); diff --git a/webview-ui/src/components/settings/SettingsView.tsx b/webview-ui/src/components/settings/SettingsView.tsx index ad7ff80639..bed3913a27 100644 --- a/webview-ui/src/components/settings/SettingsView.tsx +++ b/webview-ui/src/components/settings/SettingsView.tsx @@ -1,4 +1,4 @@ -import { VSCodeButton, VSCodeCheckbox, VSCodeLink, VSCodeTextArea } from "@vscode/webview-ui-toolkit/react" +import { VSCodeButton, VSCodeLink, VSCodeTextArea } from "@vscode/webview-ui-toolkit/react" import { memo, useEffect, useState } from "react" import { useExtensionState } from "../../context/ExtensionStateContext" import { validateApiConfiguration, validateModelId } from "../../utils/validate" @@ -12,15 +12,8 @@ type SettingsViewProps = { } const SettingsView = ({ onDone }: SettingsViewProps) => { - const { - apiConfiguration, - version, - customInstructions, - setCustomInstructions, - alwaysAllowReadOnly, - setAlwaysAllowReadOnly, - openRouterModels, - } = useExtensionState() + const { apiConfiguration, version, customInstructions, setCustomInstructions, openRouterModels } = + useExtensionState() const [apiErrorMessage, setApiErrorMessage] = useState(undefined) const [modelIdErrorMessage, setModelIdErrorMessage] = useState(undefined) const handleSubmit = () => { @@ -32,7 +25,6 @@ const SettingsView = ({ onDone }: SettingsViewProps) => { if (!apiValidationResult && !modelIdValidationResult) { vscode.postMessage({ type: "apiConfiguration", apiConfiguration }) vscode.postMessage({ type: "customInstructions", text: customInstructions }) - vscode.postMessage({ type: "alwaysAllowReadOnly", bool: alwaysAllowReadOnly }) onDone() } } @@ -113,23 +105,6 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {

-
- setAlwaysAllowReadOnly(e.target.checked)}> - Always approve read-only operations - -

- When enabled, Cline will automatically view directory contents and read files without requiring - you to click the Approve button. -

-
- {IS_DEV && ( <>
Debug
diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx index e816bd33f7..22e862e0fc 100644 --- a/webview-ui/src/context/ExtensionStateContext.tsx +++ b/webview-ui/src/context/ExtensionStateContext.tsx @@ -22,7 +22,6 @@ interface ExtensionStateContextType extends ExtensionState { filePaths: string[] setApiConfiguration: (config: ApiConfiguration) => void setCustomInstructions: (value?: string) => void - setAlwaysAllowReadOnly: (value: boolean) => void setShowAnnouncement: (value: boolean) => void } @@ -123,7 +122,6 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode filePaths, setApiConfiguration: (value) => setState((prevState) => ({ ...prevState, apiConfiguration: value })), setCustomInstructions: (value) => setState((prevState) => ({ ...prevState, customInstructions: value })), - setAlwaysAllowReadOnly: (value) => setState((prevState) => ({ ...prevState, alwaysAllowReadOnly: value })), setShowAnnouncement: (value) => setState((prevState) => ({ ...prevState, shouldShowAnnouncement: value })), }