Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 10 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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": {
Expand Down
30 changes: 23 additions & 7 deletions src/core/Cline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ export class Cline {
alwaysAllowReadOnly: boolean
alwaysAllowWrite: boolean
alwaysAllowExecute: boolean
alwaysAllowBrowser: boolean

apiConversationHistory: Anthropic.MessageParam[] = []
clineMessages: ClineMessage[] = []
Expand Down Expand Up @@ -109,6 +110,7 @@ export class Cline {
alwaysAllowReadOnly?: boolean,
alwaysAllowWrite?: boolean,
alwaysAllowExecute?: boolean,
alwaysAllowBrowser?: boolean,
task?: string,
images?: string[],
historyItem?: HistoryItem
Expand All @@ -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
Expand Down Expand Up @@ -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 <thinking or </thinking (below xml parsing is only for opening tags)
// (this is done with the xml parsing below now, but keeping here for reference)
// content = content.replace(/<\/?t(?:h(?:i(?:n(?:k(?:i(?:n(?:g)?)?)?)?)?$/, "")
// content = content.replace(/<\/?t(?:h(?:i(?:n(?:k(?:i(?:n(?:g)?)?)?)?$/, "")
// Remove all instances of <thinking> (with optional line break after) and </thinking> (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
Expand Down Expand Up @@ -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",
Expand All @@ -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
}
Expand Down
8 changes: 8 additions & 0 deletions src/core/__tests__/Cline.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand All @@ -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');
});

Expand All @@ -285,6 +289,7 @@ describe('Cline', () => {
false,
false,
false,
false,
'test task'
);
});
Expand All @@ -297,6 +302,7 @@ describe('Cline', () => {
false,
true, // alwaysAllowWrite
false,
false,
'test task'
);

Expand All @@ -312,6 +318,7 @@ describe('Cline', () => {
false,
false, // alwaysAllowWrite
false,
false,
'test task'
);

Expand Down Expand Up @@ -350,6 +357,7 @@ describe('Cline', () => {
false, // alwaysAllowReadOnly
false, // alwaysAllowWrite
false, // alwaysAllowExecute
false, // alwaysAllowBrowser
'test task' // task
)

Expand Down
66 changes: 58 additions & 8 deletions src/core/webview/ClineProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ type GlobalStateKey =
| "alwaysAllowReadOnly"
| "alwaysAllowWrite"
| "alwaysAllowExecute"
| "alwaysAllowBrowser"
| "taskHistory"
| "openAiBaseUrl"
| "openAiModelId"
Expand Down Expand Up @@ -189,21 +190,48 @@ 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,
customInstructions,
alwaysAllowReadOnly,
alwaysAllowWrite,
alwaysAllowExecute,
alwaysAllowBrowser,
undefined,
undefined,
historyItem
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)

Expand All @@ -799,18 +834,30 @@ 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,
customInstructions,
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,
}
}
Expand Down Expand Up @@ -898,6 +945,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
alwaysAllowWrite,
alwaysAllowExecute,
taskHistory,
alwaysAllowBrowser,
] = await Promise.all([
this.getGlobalState("apiProvider") as Promise<ApiProvider | undefined>,
this.getGlobalState("apiModelId") as Promise<string | undefined>,
Expand Down Expand Up @@ -929,6 +977,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
this.getGlobalState("alwaysAllowWrite") as Promise<boolean | undefined>,
this.getGlobalState("alwaysAllowExecute") as Promise<boolean | undefined>,
this.getGlobalState("taskHistory") as Promise<HistoryItem[] | undefined>,
this.getGlobalState("alwaysAllowBrowser") as Promise<boolean | undefined>,
])

let apiProvider: ApiProvider
Expand Down Expand Up @@ -977,6 +1026,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
alwaysAllowReadOnly: alwaysAllowReadOnly ?? false,
alwaysAllowWrite: alwaysAllowWrite ?? false,
alwaysAllowExecute: alwaysAllowExecute ?? false,
alwaysAllowBrowser: alwaysAllowBrowser ?? false,
taskHistory,
}
}
Expand Down
1 change: 1 addition & 0 deletions src/shared/ExtensionMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export interface ExtensionState {
alwaysAllowReadOnly?: boolean
alwaysAllowWrite?: boolean
alwaysAllowExecute?: boolean
alwaysAllowBrowser?: boolean
uriScheme?: string
clineMessages: ClineMessage[]
taskHistory: HistoryItem[]
Expand Down
1 change: 1 addition & 0 deletions src/shared/WebviewMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export interface WebviewMessage {
| "openMention"
| "cancelTask"
| "refreshOpenRouterModels"
| "alwaysAllowBrowser"
text?: string
askResponse?: ClineAskResponse
apiConfiguration?: ApiConfiguration
Expand Down
19 changes: 19 additions & 0 deletions webview-ui/src/components/settings/SettingsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
setAlwaysAllowWrite,
alwaysAllowExecute,
setAlwaysAllowExecute,
alwaysAllowBrowser,
setAlwaysAllowBrowser,
openRouterModels,
} = useExtensionState()
const [apiErrorMessage, setApiErrorMessage] = useState<string | undefined>(undefined)
Expand All @@ -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()
}
}
Expand Down Expand Up @@ -170,6 +173,22 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
</p>
</div>

<div style={{ marginBottom: 5 }}>
<VSCodeCheckbox
checked={alwaysAllowBrowser}
onChange={(e: any) => setAlwaysAllowBrowser(e.target.checked)}>
<span style={{ fontWeight: "500" }}>Always approve browser actions</span>
</VSCodeCheckbox>
<p
style={{
fontSize: "12px",
marginTop: "5px",
color: "var(--vscode-descriptionForeground)",
}}>
When enabled, Cline will automatically perform browser actions without requiring
you to click the Approve button.
</p>
</div>

{IS_DEV && (
<>
Expand Down
2 changes: 2 additions & 0 deletions webview-ui/src/context/ExtensionStateContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -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 })),
}

Expand Down