diff --git a/package-lock.json b/package-lock.json
index ce7f1e67e9..057953752c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "roo-cline",
- "version": "2.0.2",
+ "version": "2.0.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "roo-cline",
- "version": "2.0.2",
+ "version": "2.0.3",
"dependencies": {
"@anthropic-ai/bedrock-sdk": "^0.10.2",
"@anthropic-ai/sdk": "^0.26.0",
diff --git a/package.json b/package.json
index 8ba3d51f82..43e266b259 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
"name": "roo-cline",
"displayName": "Roo Cline",
"description": "Autonomous coding agent right in your IDE, capable of creating/editing files, running commands, using the browser, and more with your permission every step of the way.",
- "version": "2.0.2",
+ "version": "2.0.3",
"icon": "assets/icons/icon_Roo.png",
"galleryBanner": {
"color": "#617A91",
@@ -111,6 +111,15 @@
"when": "view == claude-dev.SidebarProvider"
}
]
+ },
+ "configuration": {
+ "properties": {
+ "cline.alwaysAllowBrowser": {
+ "type": "boolean",
+ "default": false,
+ "description": "Always allow browser actions without requiring confirmation"
+ }
+ }
}
},
"scripts": {
diff --git a/src/core/Cline.ts b/src/core/Cline.ts
index 5b407f0ebf..bc5f235075 100644
--- a/src/core/Cline.ts
+++ b/src/core/Cline.ts
@@ -77,6 +77,7 @@ export class Cline {
alwaysAllowReadOnly: boolean
alwaysAllowWrite: boolean
alwaysAllowExecute: boolean
+ alwaysAllowBrowser: boolean
apiConversationHistory: Anthropic.MessageParam[] = []
clineMessages: ClineMessage[] = []
@@ -109,6 +110,7 @@ export class Cline {
alwaysAllowReadOnly?: boolean,
alwaysAllowWrite?: boolean,
alwaysAllowExecute?: boolean,
+ alwaysAllowBrowser?: boolean,
task?: string,
images?: string[],
historyItem?: HistoryItem
@@ -123,6 +125,7 @@ export class Cline {
this.alwaysAllowReadOnly = alwaysAllowReadOnly ?? false
this.alwaysAllowWrite = alwaysAllowWrite ?? false
this.alwaysAllowExecute = alwaysAllowExecute ?? false
+ this.alwaysAllowBrowser = alwaysAllowBrowser ?? false
if (historyItem) {
this.taskId = historyItem.id
@@ -869,7 +872,7 @@ export class Cline {
// (have to do this for partial and complete since sending content in thinking tags to markdown renderer will automatically be removed)
// Remove end substrings of (with optional line break after) and (with optional line break before)
// - Needs to be separate since we dont want to remove the line break before the first tag
// - Needs to happen before the xml parsing below
@@ -1418,11 +1421,24 @@ export class Cline {
try {
if (block.partial) {
if (action === "launch") {
- await this.ask(
- "browser_action_launch",
- removeClosingTag("url", url),
- block.partial
- ).catch(() => {})
+ if (this.alwaysAllowBrowser) {
+ await this.say(
+ "browser_action",
+ JSON.stringify({
+ action: action as BrowserAction,
+ coordinate: undefined,
+ text: undefined
+ } satisfies ClineSayBrowserAction),
+ undefined,
+ block.partial
+ )
+ } else {
+ await this.ask(
+ "browser_action_launch",
+ removeClosingTag("url", url),
+ block.partial
+ ).catch(() => {})
+ }
} else {
await this.say(
"browser_action",
@@ -1448,7 +1464,7 @@ export class Cline {
break
}
this.consecutiveMistakeCount = 0
- const didApprove = await askApproval("browser_action_launch", url)
+ const didApprove = this.alwaysAllowBrowser || await askApproval("browser_action_launch", url)
if (!didApprove) {
break
}
diff --git a/src/core/__tests__/Cline.test.ts b/src/core/__tests__/Cline.test.ts
index e02be1c822..fd9f03626f 100644
--- a/src/core/__tests__/Cline.test.ts
+++ b/src/core/__tests__/Cline.test.ts
@@ -239,12 +239,14 @@ describe('Cline', () => {
undefined, // alwaysAllowReadOnly
undefined, // alwaysAllowWrite
undefined, // alwaysAllowExecute
+ undefined, // alwaysAllowBrowser
'test task'
);
expect(cline.alwaysAllowReadOnly).toBe(false);
expect(cline.alwaysAllowWrite).toBe(false);
expect(cline.alwaysAllowExecute).toBe(false);
+ expect(cline.alwaysAllowBrowser).toBe(false);
});
it('should respect provided settings', () => {
@@ -255,12 +257,14 @@ describe('Cline', () => {
true, // alwaysAllowReadOnly
true, // alwaysAllowWrite
true, // alwaysAllowExecute
+ true, // alwaysAllowBrowser
'test task'
);
expect(cline.alwaysAllowReadOnly).toBe(true);
expect(cline.alwaysAllowWrite).toBe(true);
expect(cline.alwaysAllowExecute).toBe(true);
+ expect(cline.alwaysAllowBrowser).toBe(true);
expect(cline.customInstructions).toBe('custom instructions');
});
@@ -285,6 +289,7 @@ describe('Cline', () => {
false,
false,
false,
+ false,
'test task'
);
});
@@ -297,6 +302,7 @@ describe('Cline', () => {
false,
true, // alwaysAllowWrite
false,
+ false,
'test task'
);
@@ -312,6 +318,7 @@ describe('Cline', () => {
false,
false, // alwaysAllowWrite
false,
+ false,
'test task'
);
@@ -350,6 +357,7 @@ describe('Cline', () => {
false, // alwaysAllowReadOnly
false, // alwaysAllowWrite
false, // alwaysAllowExecute
+ false, // alwaysAllowBrowser
'test task' // task
)
diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts
index 3df652f0cb..5781eacd6e 100644
--- a/src/core/webview/ClineProvider.ts
+++ b/src/core/webview/ClineProvider.ts
@@ -48,6 +48,7 @@ type GlobalStateKey =
| "alwaysAllowReadOnly"
| "alwaysAllowWrite"
| "alwaysAllowExecute"
+ | "alwaysAllowBrowser"
| "taskHistory"
| "openAiBaseUrl"
| "openAiModelId"
@@ -189,14 +190,40 @@ 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, alwaysAllowWrite, alwaysAllowExecute } = await this.getState()
- this.cline = new Cline(this, apiConfiguration, customInstructions, alwaysAllowReadOnly, alwaysAllowWrite, alwaysAllowExecute, task, images)
+ await this.clearTask()
+ const {
+ apiConfiguration,
+ customInstructions,
+ alwaysAllowReadOnly,
+ alwaysAllowWrite,
+ alwaysAllowExecute,
+ alwaysAllowBrowser
+ } = await this.getState()
+
+ this.cline = new Cline(
+ this,
+ apiConfiguration,
+ customInstructions,
+ alwaysAllowReadOnly,
+ alwaysAllowWrite,
+ alwaysAllowExecute,
+ alwaysAllowBrowser,
+ task,
+ images
+ )
}
async initClineWithHistoryItem(historyItem: HistoryItem) {
await this.clearTask()
- const { apiConfiguration, customInstructions, alwaysAllowReadOnly, alwaysAllowWrite, alwaysAllowExecute } = await this.getState()
+ const {
+ apiConfiguration,
+ customInstructions,
+ alwaysAllowReadOnly,
+ alwaysAllowWrite,
+ alwaysAllowExecute,
+ alwaysAllowBrowser
+ } = await this.getState()
+
this.cline = new Cline(
this,
apiConfiguration,
@@ -204,6 +231,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
alwaysAllowReadOnly,
alwaysAllowWrite,
alwaysAllowExecute,
+ alwaysAllowBrowser,
undefined,
undefined,
historyItem
@@ -499,6 +527,13 @@ export class ClineProvider implements vscode.WebviewViewProvider {
// await this.postStateToWebview() // new Cline instance will post state when it's ready. having this here sent an empty messages array to webview leading to virtuoso having to reload the entire list
}
+ break
+ case "alwaysAllowBrowser":
+ await this.updateGlobalState("alwaysAllowBrowser", message.bool ?? undefined)
+ if (this.cline) {
+ this.cline.alwaysAllowBrowser = message.bool ?? false
+ }
+ await this.postStateToWebview()
break
// Add more switch case statements here as more webview message commands
// are created within the webview context (i.e. inside media/main.js)
@@ -785,7 +820,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
async deleteTaskFromState(id: string) {
// Remove the task from history
- const taskHistory = ((await this.getGlobalState("taskHistory")) as HistoryItem[] | undefined) || []
+ const taskHistory = ((await this.getGlobalState("taskHistory")) as HistoryItem[]) || []
const updatedTaskHistory = taskHistory.filter((task) => task.id !== id)
await this.updateGlobalState("taskHistory", updatedTaskHistory)
@@ -799,8 +834,17 @@ export class ClineProvider implements vscode.WebviewViewProvider {
}
async getStateToPostToWebview() {
- const { apiConfiguration, lastShownAnnouncementId, customInstructions, alwaysAllowReadOnly, alwaysAllowWrite, alwaysAllowExecute, taskHistory } =
- await this.getState()
+ const {
+ apiConfiguration,
+ lastShownAnnouncementId,
+ customInstructions,
+ alwaysAllowReadOnly,
+ alwaysAllowWrite,
+ alwaysAllowExecute,
+ alwaysAllowBrowser,
+ taskHistory
+ } = await this.getState()
+
return {
version: this.context.extension?.packageJSON?.version ?? "",
apiConfiguration,
@@ -808,9 +852,12 @@ export class ClineProvider implements vscode.WebviewViewProvider {
alwaysAllowReadOnly: alwaysAllowReadOnly ?? false,
alwaysAllowWrite: alwaysAllowWrite ?? false,
alwaysAllowExecute: alwaysAllowExecute ?? false,
+ alwaysAllowBrowser: alwaysAllowBrowser ?? false,
uriScheme: vscode.env.uriScheme,
clineMessages: this.cline?.clineMessages || [],
- taskHistory: (taskHistory || []).filter((item) => item.ts && item.task).sort((a, b) => b.ts - a.ts),
+ taskHistory: (taskHistory || [])
+ .filter((item) => item.ts && item.task)
+ .sort((a, b) => b.ts - a.ts),
shouldShowAnnouncement: lastShownAnnouncementId !== this.latestAnnouncementId,
}
}
@@ -898,6 +945,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
alwaysAllowWrite,
alwaysAllowExecute,
taskHistory,
+ alwaysAllowBrowser,
] = await Promise.all([
this.getGlobalState("apiProvider") as Promise,
this.getGlobalState("apiModelId") as Promise,
@@ -929,6 +977,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
this.getGlobalState("alwaysAllowWrite") as Promise,
this.getGlobalState("alwaysAllowExecute") as Promise,
this.getGlobalState("taskHistory") as Promise,
+ this.getGlobalState("alwaysAllowBrowser") as Promise,
])
let apiProvider: ApiProvider
@@ -977,6 +1026,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
alwaysAllowReadOnly: alwaysAllowReadOnly ?? false,
alwaysAllowWrite: alwaysAllowWrite ?? false,
alwaysAllowExecute: alwaysAllowExecute ?? false,
+ alwaysAllowBrowser: alwaysAllowBrowser ?? false,
taskHistory,
}
}
diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts
index 6cd9414cf3..beae01a35d 100644
--- a/src/shared/ExtensionMessage.ts
+++ b/src/shared/ExtensionMessage.ts
@@ -35,6 +35,7 @@ export interface ExtensionState {
alwaysAllowReadOnly?: boolean
alwaysAllowWrite?: boolean
alwaysAllowExecute?: boolean
+ alwaysAllowBrowser?: boolean
uriScheme?: string
clineMessages: ClineMessage[]
taskHistory: HistoryItem[]
diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts
index b89ffec560..ba23355b56 100644
--- a/src/shared/WebviewMessage.ts
+++ b/src/shared/WebviewMessage.ts
@@ -25,6 +25,7 @@ export interface WebviewMessage {
| "openMention"
| "cancelTask"
| "refreshOpenRouterModels"
+ | "alwaysAllowBrowser"
text?: string
askResponse?: ClineAskResponse
apiConfiguration?: ApiConfiguration
diff --git a/webview-ui/src/components/settings/SettingsView.tsx b/webview-ui/src/components/settings/SettingsView.tsx
index 893a7bf0f9..525807cb7b 100644
--- a/webview-ui/src/components/settings/SettingsView.tsx
+++ b/webview-ui/src/components/settings/SettingsView.tsx
@@ -23,6 +23,8 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
setAlwaysAllowWrite,
alwaysAllowExecute,
setAlwaysAllowExecute,
+ alwaysAllowBrowser,
+ setAlwaysAllowBrowser,
openRouterModels,
} = useExtensionState()
const [apiErrorMessage, setApiErrorMessage] = useState(undefined)
@@ -39,6 +41,7 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
vscode.postMessage({ type: "alwaysAllowReadOnly", bool: alwaysAllowReadOnly })
vscode.postMessage({ type: "alwaysAllowWrite", bool: alwaysAllowWrite })
vscode.postMessage({ type: "alwaysAllowExecute", bool: alwaysAllowExecute })
+ vscode.postMessage({ type: "alwaysAllowBrowser", bool: alwaysAllowBrowser })
onDone()
}
}
@@ -170,6 +173,22 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
+
+
setAlwaysAllowBrowser(e.target.checked)}>
+ Always approve browser actions
+
+
+ When enabled, Cline will automatically perform browser actions without requiring
+ you to click the Approve button.
+
+
{IS_DEV && (
<>
diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx
index 9314cf1861..519cf07935 100644
--- a/webview-ui/src/context/ExtensionStateContext.tsx
+++ b/webview-ui/src/context/ExtensionStateContext.tsx
@@ -22,6 +22,7 @@ export interface ExtensionStateContextType extends ExtensionState {
setAlwaysAllowReadOnly: (value: boolean) => void
setAlwaysAllowWrite: (value: boolean) => void
setAlwaysAllowExecute: (value: boolean) => void
+ setAlwaysAllowBrowser: (value: boolean) => void
setShowAnnouncement: (value: boolean) => void
}
@@ -118,6 +119,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
setAlwaysAllowReadOnly: (value) => setState((prevState) => ({ ...prevState, alwaysAllowReadOnly: value })),
setAlwaysAllowWrite: (value) => setState((prevState) => ({ ...prevState, alwaysAllowWrite: value })),
setAlwaysAllowExecute: (value) => setState((prevState) => ({ ...prevState, alwaysAllowExecute: value })),
+ setAlwaysAllowBrowser: (value) => setState((prevState) => ({ ...prevState, alwaysAllowBrowser: value })),
setShowAnnouncement: (value) => setState((prevState) => ({ ...prevState, shouldShowAnnouncement: value })),
}