Browse Source

Add a checkbox to enable/disable the browser tool

Matt Rubens 10 months ago
parent
commit
6b0347e0bd

+ 5 - 0
.changeset/nervous-candles-refuse.md

@@ -0,0 +1,5 @@
+---
+"roo-cline": patch
+---
+
+Add a checkbox to disable the browser tool

+ 2 - 1
src/core/Cline.ts

@@ -946,6 +946,7 @@ export class Cline {
 			preferredLanguage,
 			experiments,
 			enableMcpServerCreation,
+			browserToolEnabled,
 		} = (await this.providerRef.deref()?.getState()) ?? {}
 		const { customModes } = (await this.providerRef.deref()?.getState()) ?? {}
 		const systemPrompt = await (async () => {
@@ -956,7 +957,7 @@ export class Cline {
 			return SYSTEM_PROMPT(
 				provider.context,
 				cwd,
-				this.api.getModel().info.supportsComputerUse ?? false,
+				(this.api.getModel().info.supportsComputerUse ?? false) && (browserToolEnabled ?? true),
 				mcpHub,
 				this.diffStrategy,
 				browserViewportSize,

+ 9 - 0
src/core/webview/ClineProvider.ts

@@ -1224,6 +1224,10 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 						await this.updateGlobalState("maxOpenTabsContext", tabCount)
 						await this.postStateToWebview()
 						break
+					case "browserToolEnabled":
+						await this.updateGlobalState("browserToolEnabled", message.bool ?? true)
+						await this.postStateToWebview()
+						break
 					case "enhancementApiConfigId":
 						await this.updateGlobalState("enhancementApiConfigId", message.text)
 						await this.postStateToWebview()
@@ -2022,6 +2026,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 			autoApprovalEnabled,
 			experiments,
 			maxOpenTabsContext,
+			browserToolEnabled,
 		} = await this.getState()
 
 		const allowedCommands = vscode.workspace.getConfiguration("roo-cline").get<string[]>("allowedCommands") || []
@@ -2075,6 +2080,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 			mcpServers: this.mcpHub?.getAllServers() ?? [],
 			maxOpenTabsContext: maxOpenTabsContext ?? 20,
 			cwd: cwd,
+			browserToolEnabled: browserToolEnabled ?? true,
 		}
 	}
 
@@ -2214,6 +2220,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 			modelMaxTokens,
 			modelMaxThinkingTokens,
 			maxOpenTabsContext,
+			browserToolEnabled,
 		] = await Promise.all([
 			this.getGlobalState("apiProvider") as Promise<ApiProvider | undefined>,
 			this.getGlobalState("apiModelId") as Promise<string | undefined>,
@@ -2298,6 +2305,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 			this.getGlobalState("modelMaxTokens") as Promise<number | undefined>,
 			this.getGlobalState("anthropicThinking") as Promise<number | undefined>,
 			this.getGlobalState("maxOpenTabsContext") as Promise<number | undefined>,
+			this.getGlobalState("browserToolEnabled") as Promise<boolean | undefined>,
 		])
 
 		let apiProvider: ApiProvider
@@ -2431,6 +2439,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 			customModes,
 			maxOpenTabsContext: maxOpenTabsContext ?? 20,
 			openRouterUseMiddleOutTransform: openRouterUseMiddleOutTransform ?? true,
+			browserToolEnabled: browserToolEnabled ?? true,
 		}
 	}
 

+ 16 - 0
src/core/webview/__tests__/ClineProvider.test.ts

@@ -381,6 +381,7 @@ describe("ClineProvider", () => {
 			customModes: [],
 			experiments: experimentDefault,
 			maxOpenTabsContext: 20,
+			browserToolEnabled: true,
 		}
 
 		const message: ExtensionMessage = {
@@ -591,6 +592,21 @@ describe("ClineProvider", () => {
 		expect(provider.configManager.setModeConfig).toHaveBeenCalledWith("architect", "new-id")
 	})
 
+	test("handles browserToolEnabled setting", async () => {
+		await provider.resolveWebviewView(mockWebviewView)
+		const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as jest.Mock).mock.calls[0][0]
+
+		// Test browserToolEnabled
+		await messageHandler({ type: "browserToolEnabled", bool: true })
+		expect(mockContext.globalState.update).toHaveBeenCalledWith("browserToolEnabled", true)
+		expect(mockPostMessage).toHaveBeenCalled()
+
+		// Verify state includes browserToolEnabled
+		const state = await provider.getState()
+		expect(state).toHaveProperty("browserToolEnabled")
+		expect(state.browserToolEnabled).toBe(true) // Default value should be true
+	})
+
 	test("handles request delay settings messages", async () => {
 		await provider.resolveWebviewView(mockWebviewView)
 		const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as jest.Mock).mock.calls[0][0]

+ 2 - 0
src/shared/ExtensionMessage.ts

@@ -45,6 +45,7 @@ export interface ExtensionMessage {
 		| "updateCustomMode"
 		| "deleteCustomMode"
 		| "currentCheckpointUpdated"
+		| "browserToolEnabled"
 	text?: string
 	action?:
 		| "chatButtonClicked"
@@ -103,6 +104,7 @@ export interface ExtensionState {
 	alwaysAllowMcp?: boolean
 	alwaysApproveResubmit?: boolean
 	alwaysAllowModeSwitch?: boolean
+	browserToolEnabled?: boolean
 	requestDelaySeconds: number
 	rateLimitSeconds: number // Minimum time between successive requests (0 = disabled)
 	uriScheme?: string

+ 1 - 0
src/shared/WebviewMessage.ts

@@ -94,6 +94,7 @@ export interface WebviewMessage {
 		| "checkpointRestore"
 		| "deleteMcpServer"
 		| "maxOpenTabsContext"
+		| "browserToolEnabled"
 	text?: string
 	disabled?: boolean
 	askResponse?: ClineAskResponse

+ 1 - 0
src/shared/globalState.ts

@@ -84,3 +84,4 @@ export type GlobalStateKey =
 	| "anthropicThinking" // TODO: Rename to `modelMaxThinkingTokens`.
 	| "mistralCodestralUrl"
 	| "maxOpenTabsContext"
+	| "browserToolEnabled" // Setting to enable/disable the browser tool

+ 74 - 48
webview-ui/src/components/settings/SettingsView.tsx

@@ -51,6 +51,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone },
 		alwaysAllowModeSwitch,
 		alwaysAllowWrite,
 		alwaysApproveResubmit,
+		browserToolEnabled,
 		browserViewportSize,
 		enableCheckpoints,
 		diffEnabled,
@@ -140,6 +141,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone },
 			vscode.postMessage({ type: "alwaysAllowBrowser", bool: alwaysAllowBrowser })
 			vscode.postMessage({ type: "alwaysAllowMcp", bool: alwaysAllowMcp })
 			vscode.postMessage({ type: "allowedCommands", commands: allowedCommands ?? [] })
+			vscode.postMessage({ type: "browserToolEnabled", bool: browserToolEnabled })
 			vscode.postMessage({ type: "soundEnabled", bool: soundEnabled })
 			vscode.postMessage({ type: "soundVolume", value: soundVolume })
 			vscode.postMessage({ type: "diffEnabled", bool: diffEnabled })
@@ -537,59 +539,83 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone },
 				<div style={{ marginBottom: 40 }}>
 					<h3 style={{ color: "var(--vscode-foreground)", margin: "0 0 15px 0" }}>Browser Settings</h3>
 					<div style={{ marginBottom: 15 }}>
-						<label style={{ fontWeight: "500", display: "block", marginBottom: 5 }}>Viewport size</label>
-						<div className="dropdown-container">
-							<Dropdown
-								value={browserViewportSize}
-								onChange={(value: unknown) => {
-									setCachedStateField("browserViewportSize", (value as DropdownOption).value)
-								}}
-								style={{ width: "100%" }}
-								options={[
-									{ value: "1280x800", label: "Large Desktop (1280x800)" },
-									{ value: "900x600", label: "Small Desktop (900x600)" },
-									{ value: "768x1024", label: "Tablet (768x1024)" },
-									{ value: "360x640", label: "Mobile (360x640)" },
-								]}
-							/>
-						</div>
-						<p
-							style={{
-								fontSize: "12px",
-								marginTop: "5px",
-								color: "var(--vscode-descriptionForeground)",
-							}}>
-							Select the viewport size for browser interactions. This affects how websites are displayed
-							and interacted with.
+						<VSCodeCheckbox
+							checked={browserToolEnabled}
+							onChange={(e: any) => setCachedStateField("browserToolEnabled", e.target.checked)}>
+							<span style={{ fontWeight: "500" }}>Enable browser tool</span>
+						</VSCodeCheckbox>
+						<p style={{ fontSize: "12px", marginTop: "5px", color: "var(--vscode-descriptionForeground)" }}>
+							When enabled, Roo can use a browser to interact with websites when using models that support
+							computer use.
 						</p>
 					</div>
+					{browserToolEnabled && (
+						<div
+							style={{
+								marginLeft: 0,
+								paddingLeft: 10,
+								borderLeft: "2px solid var(--vscode-button-background)",
+							}}>
+							<div style={{ marginBottom: 15 }}>
+								<label style={{ fontWeight: "500", display: "block", marginBottom: 5 }}>
+									Viewport size
+								</label>
+								<div className="dropdown-container">
+									<Dropdown
+										value={browserViewportSize}
+										onChange={(value: unknown) => {
+											setCachedStateField("browserViewportSize", (value as DropdownOption).value)
+										}}
+										style={{ width: "100%" }}
+										options={[
+											{ value: "1280x800", label: "Large Desktop (1280x800)" },
+											{ value: "900x600", label: "Small Desktop (900x600)" },
+											{ value: "768x1024", label: "Tablet (768x1024)" },
+											{ value: "360x640", label: "Mobile (360x640)" },
+										]}
+									/>
+								</div>
+								<p
+									style={{
+										fontSize: "12px",
+										marginTop: "5px",
+										color: "var(--vscode-descriptionForeground)",
+									}}>
+									Select the viewport size for browser interactions. This affects how websites are
+									displayed and interacted with.
+								</p>
+							</div>
 
-					<div style={{ marginBottom: 15 }}>
-						<div style={{ display: "flex", flexDirection: "column", gap: "5px" }}>
-							<span style={{ fontWeight: "500" }}>Screenshot quality</span>
-							<div style={{ display: "flex", alignItems: "center", gap: "5px" }}>
-								<input
-									type="range"
-									min="1"
-									max="100"
-									step="1"
-									value={screenshotQuality ?? 75}
-									className="h-2 focus:outline-0 w-4/5 accent-vscode-button-background"
-									onChange={(e) => setCachedStateField("screenshotQuality", parseInt(e.target.value))}
-								/>
-								<span style={{ ...sliderLabelStyle }}>{screenshotQuality ?? 75}%</span>
+							<div style={{ marginBottom: 15 }}>
+								<div style={{ display: "flex", flexDirection: "column", gap: "5px" }}>
+									<span style={{ fontWeight: "500" }}>Screenshot quality</span>
+									<div style={{ display: "flex", alignItems: "center", gap: "5px" }}>
+										<input
+											type="range"
+											min="1"
+											max="100"
+											step="1"
+											value={screenshotQuality ?? 75}
+											className="h-2 focus:outline-0 w-4/5 accent-vscode-button-background"
+											onChange={(e) =>
+												setCachedStateField("screenshotQuality", parseInt(e.target.value))
+											}
+										/>
+										<span style={{ ...sliderLabelStyle }}>{screenshotQuality ?? 75}%</span>
+									</div>
+								</div>
+								<p
+									style={{
+										fontSize: "12px",
+										marginTop: "5px",
+										color: "var(--vscode-descriptionForeground)",
+									}}>
+									Adjust the WebP quality of browser screenshots. Higher values provide clearer
+									screenshots but increase token usage.
+								</p>
 							</div>
 						</div>
-						<p
-							style={{
-								fontSize: "12px",
-								marginTop: "5px",
-								color: "var(--vscode-descriptionForeground)",
-							}}>
-							Adjust the WebP quality of browser screenshots. Higher values provide clearer screenshots
-							but increase token usage.
-						</p>
-					</div>
+					)}
 				</div>
 
 				<div style={{ marginBottom: 40 }}>

+ 3 - 0
webview-ui/src/context/ExtensionStateContext.tsx

@@ -27,6 +27,7 @@ export interface ExtensionStateContextType extends ExtensionState {
 	setAlwaysAllowBrowser: (value: boolean) => void
 	setAlwaysAllowMcp: (value: boolean) => void
 	setAlwaysAllowModeSwitch: (value: boolean) => void
+	setBrowserToolEnabled: (value: boolean) => void
 	setShowAnnouncement: (value: boolean) => void
 	setAllowedCommands: (value: string[]) => void
 	setSoundEnabled: (value: boolean) => void
@@ -128,6 +129,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
 		customModes: [],
 		maxOpenTabsContext: 20,
 		cwd: "",
+		browserToolEnabled: true,
 	})
 
 	const [didHydrateState, setDidHydrateState] = useState(false)
@@ -265,6 +267,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
 		setAutoApprovalEnabled: (value) => setState((prevState) => ({ ...prevState, autoApprovalEnabled: value })),
 		setCustomModes: (value) => setState((prevState) => ({ ...prevState, customModes: value })),
 		setMaxOpenTabsContext: (value) => setState((prevState) => ({ ...prevState, maxOpenTabsContext: value })),
+		setBrowserToolEnabled: (value) => setState((prevState) => ({ ...prevState, browserToolEnabled: value })),
 	}
 
 	return <ExtensionStateContext.Provider value={contextValue}>{children}</ExtensionStateContext.Provider>