Skip to content

Commit f08972a

Browse files
authored
Merge pull request #14 from RooVetGit/always_allow_browser
Add a setAlwaysAllowBrowser checkbox to settings
2 parents e55696e + 0346fde commit f08972a

File tree

9 files changed

+124
-18
lines changed

9 files changed

+124
-18
lines changed

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "roo-cline",
33
"displayName": "Roo Cline",
44
"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.",
5-
"version": "2.0.2",
5+
"version": "2.0.3",
66
"icon": "assets/icons/icon_Roo.png",
77
"galleryBanner": {
88
"color": "#617A91",
@@ -111,6 +111,15 @@
111111
"when": "view == claude-dev.SidebarProvider"
112112
}
113113
]
114+
},
115+
"configuration": {
116+
"properties": {
117+
"cline.alwaysAllowBrowser": {
118+
"type": "boolean",
119+
"default": false,
120+
"description": "Always allow browser actions without requiring confirmation"
121+
}
122+
}
114123
}
115124
},
116125
"scripts": {

src/core/Cline.ts

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ export class Cline {
7777
alwaysAllowReadOnly: boolean
7878
alwaysAllowWrite: boolean
7979
alwaysAllowExecute: boolean
80+
alwaysAllowBrowser: boolean
8081

8182
apiConversationHistory: Anthropic.MessageParam[] = []
8283
clineMessages: ClineMessage[] = []
@@ -109,6 +110,7 @@ export class Cline {
109110
alwaysAllowReadOnly?: boolean,
110111
alwaysAllowWrite?: boolean,
111112
alwaysAllowExecute?: boolean,
113+
alwaysAllowBrowser?: boolean,
112114
task?: string,
113115
images?: string[],
114116
historyItem?: HistoryItem
@@ -123,6 +125,7 @@ export class Cline {
123125
this.alwaysAllowReadOnly = alwaysAllowReadOnly ?? false
124126
this.alwaysAllowWrite = alwaysAllowWrite ?? false
125127
this.alwaysAllowExecute = alwaysAllowExecute ?? false
128+
this.alwaysAllowBrowser = alwaysAllowBrowser ?? false
126129

127130
if (historyItem) {
128131
this.taskId = historyItem.id
@@ -869,7 +872,7 @@ export class Cline {
869872
// (have to do this for partial and complete since sending content in thinking tags to markdown renderer will automatically be removed)
870873
// Remove end substrings of <thinking or </thinking (below xml parsing is only for opening tags)
871874
// (this is done with the xml parsing below now, but keeping here for reference)
872-
// content = content.replace(/<\/?t(?:h(?:i(?:n(?:k(?:i(?:n(?:g)?)?)?)?)?$/, "")
875+
// content = content.replace(/<\/?t(?:h(?:i(?:n(?:k(?:i(?:n(?:g)?)?)?)?$/, "")
873876
// Remove all instances of <thinking> (with optional line break after) and </thinking> (with optional line break before)
874877
// - Needs to be separate since we dont want to remove the line break before the first tag
875878
// - Needs to happen before the xml parsing below
@@ -1418,11 +1421,24 @@ export class Cline {
14181421
try {
14191422
if (block.partial) {
14201423
if (action === "launch") {
1421-
await this.ask(
1422-
"browser_action_launch",
1423-
removeClosingTag("url", url),
1424-
block.partial
1425-
).catch(() => {})
1424+
if (this.alwaysAllowBrowser) {
1425+
await this.say(
1426+
"browser_action",
1427+
JSON.stringify({
1428+
action: action as BrowserAction,
1429+
coordinate: undefined,
1430+
text: undefined
1431+
} satisfies ClineSayBrowserAction),
1432+
undefined,
1433+
block.partial
1434+
)
1435+
} else {
1436+
await this.ask(
1437+
"browser_action_launch",
1438+
removeClosingTag("url", url),
1439+
block.partial
1440+
).catch(() => {})
1441+
}
14261442
} else {
14271443
await this.say(
14281444
"browser_action",
@@ -1448,7 +1464,7 @@ export class Cline {
14481464
break
14491465
}
14501466
this.consecutiveMistakeCount = 0
1451-
const didApprove = await askApproval("browser_action_launch", url)
1467+
const didApprove = this.alwaysAllowBrowser || await askApproval("browser_action_launch", url)
14521468
if (!didApprove) {
14531469
break
14541470
}

src/core/__tests__/Cline.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,12 +239,14 @@ describe('Cline', () => {
239239
undefined, // alwaysAllowReadOnly
240240
undefined, // alwaysAllowWrite
241241
undefined, // alwaysAllowExecute
242+
undefined, // alwaysAllowBrowser
242243
'test task'
243244
);
244245

245246
expect(cline.alwaysAllowReadOnly).toBe(false);
246247
expect(cline.alwaysAllowWrite).toBe(false);
247248
expect(cline.alwaysAllowExecute).toBe(false);
249+
expect(cline.alwaysAllowBrowser).toBe(false);
248250
});
249251

250252
it('should respect provided settings', () => {
@@ -255,12 +257,14 @@ describe('Cline', () => {
255257
true, // alwaysAllowReadOnly
256258
true, // alwaysAllowWrite
257259
true, // alwaysAllowExecute
260+
true, // alwaysAllowBrowser
258261
'test task'
259262
);
260263

261264
expect(cline.alwaysAllowReadOnly).toBe(true);
262265
expect(cline.alwaysAllowWrite).toBe(true);
263266
expect(cline.alwaysAllowExecute).toBe(true);
267+
expect(cline.alwaysAllowBrowser).toBe(true);
264268
expect(cline.customInstructions).toBe('custom instructions');
265269
});
266270

@@ -285,6 +289,7 @@ describe('Cline', () => {
285289
false,
286290
false,
287291
false,
292+
false,
288293
'test task'
289294
);
290295
});
@@ -297,6 +302,7 @@ describe('Cline', () => {
297302
false,
298303
true, // alwaysAllowWrite
299304
false,
305+
false,
300306
'test task'
301307
);
302308

@@ -312,6 +318,7 @@ describe('Cline', () => {
312318
false,
313319
false, // alwaysAllowWrite
314320
false,
321+
false,
315322
'test task'
316323
);
317324

@@ -350,6 +357,7 @@ describe('Cline', () => {
350357
false, // alwaysAllowReadOnly
351358
false, // alwaysAllowWrite
352359
false, // alwaysAllowExecute
360+
false, // alwaysAllowBrowser
353361
'test task' // task
354362
)
355363

src/core/webview/ClineProvider.ts

Lines changed: 58 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ type GlobalStateKey =
4848
| "alwaysAllowReadOnly"
4949
| "alwaysAllowWrite"
5050
| "alwaysAllowExecute"
51+
| "alwaysAllowBrowser"
5152
| "taskHistory"
5253
| "openAiBaseUrl"
5354
| "openAiModelId"
@@ -189,21 +190,48 @@ export class ClineProvider implements vscode.WebviewViewProvider {
189190
}
190191

191192
async initClineWithTask(task?: string, images?: string[]) {
192-
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
193-
const { apiConfiguration, customInstructions, alwaysAllowReadOnly, alwaysAllowWrite, alwaysAllowExecute } = await this.getState()
194-
this.cline = new Cline(this, apiConfiguration, customInstructions, alwaysAllowReadOnly, alwaysAllowWrite, alwaysAllowExecute, task, images)
193+
await this.clearTask()
194+
const {
195+
apiConfiguration,
196+
customInstructions,
197+
alwaysAllowReadOnly,
198+
alwaysAllowWrite,
199+
alwaysAllowExecute,
200+
alwaysAllowBrowser
201+
} = await this.getState()
202+
203+
this.cline = new Cline(
204+
this,
205+
apiConfiguration,
206+
customInstructions,
207+
alwaysAllowReadOnly,
208+
alwaysAllowWrite,
209+
alwaysAllowExecute,
210+
alwaysAllowBrowser,
211+
task,
212+
images
213+
)
195214
}
196215

197216
async initClineWithHistoryItem(historyItem: HistoryItem) {
198217
await this.clearTask()
199-
const { apiConfiguration, customInstructions, alwaysAllowReadOnly, alwaysAllowWrite, alwaysAllowExecute } = await this.getState()
218+
const {
219+
apiConfiguration,
220+
customInstructions,
221+
alwaysAllowReadOnly,
222+
alwaysAllowWrite,
223+
alwaysAllowExecute,
224+
alwaysAllowBrowser
225+
} = await this.getState()
226+
200227
this.cline = new Cline(
201228
this,
202229
apiConfiguration,
203230
customInstructions,
204231
alwaysAllowReadOnly,
205232
alwaysAllowWrite,
206233
alwaysAllowExecute,
234+
alwaysAllowBrowser,
207235
undefined,
208236
undefined,
209237
historyItem
@@ -499,6 +527,13 @@ export class ClineProvider implements vscode.WebviewViewProvider {
499527
// 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
500528
}
501529

530+
break
531+
case "alwaysAllowBrowser":
532+
await this.updateGlobalState("alwaysAllowBrowser", message.bool ?? undefined)
533+
if (this.cline) {
534+
this.cline.alwaysAllowBrowser = message.bool ?? false
535+
}
536+
await this.postStateToWebview()
502537
break
503538
// Add more switch case statements here as more webview message commands
504539
// are created within the webview context (i.e. inside media/main.js)
@@ -785,7 +820,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
785820

786821
async deleteTaskFromState(id: string) {
787822
// Remove the task from history
788-
const taskHistory = ((await this.getGlobalState("taskHistory")) as HistoryItem[] | undefined) || []
823+
const taskHistory = ((await this.getGlobalState("taskHistory")) as HistoryItem[]) || []
789824
const updatedTaskHistory = taskHistory.filter((task) => task.id !== id)
790825
await this.updateGlobalState("taskHistory", updatedTaskHistory)
791826

@@ -799,18 +834,30 @@ export class ClineProvider implements vscode.WebviewViewProvider {
799834
}
800835

801836
async getStateToPostToWebview() {
802-
const { apiConfiguration, lastShownAnnouncementId, customInstructions, alwaysAllowReadOnly, alwaysAllowWrite, alwaysAllowExecute, taskHistory } =
803-
await this.getState()
837+
const {
838+
apiConfiguration,
839+
lastShownAnnouncementId,
840+
customInstructions,
841+
alwaysAllowReadOnly,
842+
alwaysAllowWrite,
843+
alwaysAllowExecute,
844+
alwaysAllowBrowser,
845+
taskHistory
846+
} = await this.getState()
847+
804848
return {
805849
version: this.context.extension?.packageJSON?.version ?? "",
806850
apiConfiguration,
807851
customInstructions,
808852
alwaysAllowReadOnly: alwaysAllowReadOnly ?? false,
809853
alwaysAllowWrite: alwaysAllowWrite ?? false,
810854
alwaysAllowExecute: alwaysAllowExecute ?? false,
855+
alwaysAllowBrowser: alwaysAllowBrowser ?? false,
811856
uriScheme: vscode.env.uriScheme,
812857
clineMessages: this.cline?.clineMessages || [],
813-
taskHistory: (taskHistory || []).filter((item) => item.ts && item.task).sort((a, b) => b.ts - a.ts),
858+
taskHistory: (taskHistory || [])
859+
.filter((item) => item.ts && item.task)
860+
.sort((a, b) => b.ts - a.ts),
814861
shouldShowAnnouncement: lastShownAnnouncementId !== this.latestAnnouncementId,
815862
}
816863
}
@@ -898,6 +945,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
898945
alwaysAllowWrite,
899946
alwaysAllowExecute,
900947
taskHistory,
948+
alwaysAllowBrowser,
901949
] = await Promise.all([
902950
this.getGlobalState("apiProvider") as Promise<ApiProvider | undefined>,
903951
this.getGlobalState("apiModelId") as Promise<string | undefined>,
@@ -929,6 +977,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
929977
this.getGlobalState("alwaysAllowWrite") as Promise<boolean | undefined>,
930978
this.getGlobalState("alwaysAllowExecute") as Promise<boolean | undefined>,
931979
this.getGlobalState("taskHistory") as Promise<HistoryItem[] | undefined>,
980+
this.getGlobalState("alwaysAllowBrowser") as Promise<boolean | undefined>,
932981
])
933982

934983
let apiProvider: ApiProvider
@@ -977,6 +1026,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
9771026
alwaysAllowReadOnly: alwaysAllowReadOnly ?? false,
9781027
alwaysAllowWrite: alwaysAllowWrite ?? false,
9791028
alwaysAllowExecute: alwaysAllowExecute ?? false,
1029+
alwaysAllowBrowser: alwaysAllowBrowser ?? false,
9801030
taskHistory,
9811031
}
9821032
}

src/shared/ExtensionMessage.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export interface ExtensionState {
3535
alwaysAllowReadOnly?: boolean
3636
alwaysAllowWrite?: boolean
3737
alwaysAllowExecute?: boolean
38+
alwaysAllowBrowser?: boolean
3839
uriScheme?: string
3940
clineMessages: ClineMessage[]
4041
taskHistory: HistoryItem[]

src/shared/WebviewMessage.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export interface WebviewMessage {
2525
| "openMention"
2626
| "cancelTask"
2727
| "refreshOpenRouterModels"
28+
| "alwaysAllowBrowser"
2829
text?: string
2930
askResponse?: ClineAskResponse
3031
apiConfiguration?: ApiConfiguration

webview-ui/src/components/settings/SettingsView.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
2323
setAlwaysAllowWrite,
2424
alwaysAllowExecute,
2525
setAlwaysAllowExecute,
26+
alwaysAllowBrowser,
27+
setAlwaysAllowBrowser,
2628
openRouterModels,
2729
} = useExtensionState()
2830
const [apiErrorMessage, setApiErrorMessage] = useState<string | undefined>(undefined)
@@ -39,6 +41,7 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
3941
vscode.postMessage({ type: "alwaysAllowReadOnly", bool: alwaysAllowReadOnly })
4042
vscode.postMessage({ type: "alwaysAllowWrite", bool: alwaysAllowWrite })
4143
vscode.postMessage({ type: "alwaysAllowExecute", bool: alwaysAllowExecute })
44+
vscode.postMessage({ type: "alwaysAllowBrowser", bool: alwaysAllowBrowser })
4245
onDone()
4346
}
4447
}
@@ -170,6 +173,22 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
170173
</p>
171174
</div>
172175

176+
<div style={{ marginBottom: 5 }}>
177+
<VSCodeCheckbox
178+
checked={alwaysAllowBrowser}
179+
onChange={(e: any) => setAlwaysAllowBrowser(e.target.checked)}>
180+
<span style={{ fontWeight: "500" }}>Always approve browser actions</span>
181+
</VSCodeCheckbox>
182+
<p
183+
style={{
184+
fontSize: "12px",
185+
marginTop: "5px",
186+
color: "var(--vscode-descriptionForeground)",
187+
}}>
188+
When enabled, Cline will automatically perform browser actions without requiring
189+
you to click the Approve button.
190+
</p>
191+
</div>
173192

174193
{IS_DEV && (
175194
<>

webview-ui/src/context/ExtensionStateContext.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export interface ExtensionStateContextType extends ExtensionState {
2222
setAlwaysAllowReadOnly: (value: boolean) => void
2323
setAlwaysAllowWrite: (value: boolean) => void
2424
setAlwaysAllowExecute: (value: boolean) => void
25+
setAlwaysAllowBrowser: (value: boolean) => void
2526
setShowAnnouncement: (value: boolean) => void
2627
}
2728

@@ -118,6 +119,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
118119
setAlwaysAllowReadOnly: (value) => setState((prevState) => ({ ...prevState, alwaysAllowReadOnly: value })),
119120
setAlwaysAllowWrite: (value) => setState((prevState) => ({ ...prevState, alwaysAllowWrite: value })),
120121
setAlwaysAllowExecute: (value) => setState((prevState) => ({ ...prevState, alwaysAllowExecute: value })),
122+
setAlwaysAllowBrowser: (value) => setState((prevState) => ({ ...prevState, alwaysAllowBrowser: value })),
121123
setShowAnnouncement: (value) => setState((prevState) => ({ ...prevState, shouldShowAnnouncement: value })),
122124
}
123125

0 commit comments

Comments
 (0)