From 22f51304a26bcaca031f52270f0b2ceb68467fb2 Mon Sep 17 00:00:00 2001
From: Afshawn Lotfi
Date: Sun, 9 Mar 2025 20:11:52 +0000
Subject: [PATCH] Add support for remote browser connection and settings
---
src/core/webview/ClineProvider.ts | 7 +++
src/services/browser/BrowserSession.ts | 47 ++++++++++++++++++-
src/shared/ExtensionMessage.ts | 1 +
src/shared/WebviewMessage.ts | 1 +
src/shared/globalState.ts | 1 +
.../components/settings/BrowserSettings.tsx | 33 ++++++++++++-
.../src/components/settings/SettingsView.tsx | 3 ++
7 files changed, 90 insertions(+), 3 deletions(-)
diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts
index 657e4a9ab6..3e8ed51668 100644
--- a/src/core/webview/ClineProvider.ts
+++ b/src/core/webview/ClineProvider.ts
@@ -1262,6 +1262,10 @@ export class ClineProvider implements vscode.WebviewViewProvider {
await this.updateGlobalState("browserViewportSize", browserViewportSize)
await this.postStateToWebview()
break
+ case "remoteBrowserHost":
+ await this.updateGlobalState("remoteBrowserHost", message.text)
+ await this.postStateToWebview()
+ break
case "fuzzyMatchThreshold":
await this.updateGlobalState("fuzzyMatchThreshold", message.value)
await this.postStateToWebview()
@@ -2188,6 +2192,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
soundVolume,
browserViewportSize,
screenshotQuality,
+ remoteBrowserHost,
preferredLanguage,
writeDelayMs,
terminalOutputLimit,
@@ -2246,6 +2251,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
soundVolume: soundVolume ?? 0.5,
browserViewportSize: browserViewportSize ?? "900x600",
screenshotQuality: screenshotQuality ?? 75,
+ remoteBrowserHost,
preferredLanguage: preferredLanguage ?? "English",
writeDelayMs: writeDelayMs ?? 1000,
terminalOutputLimit: terminalOutputLimit ?? TERMINAL_OUTPUT_LIMIT,
@@ -2399,6 +2405,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
soundVolume: stateValues.soundVolume,
browserViewportSize: stateValues.browserViewportSize ?? "900x600",
screenshotQuality: stateValues.screenshotQuality ?? 75,
+ remoteBrowserHost: stateValues.remoteBrowserHost,
fuzzyMatchThreshold: stateValues.fuzzyMatchThreshold ?? 1.0,
writeDelayMs: stateValues.writeDelayMs ?? 1000,
terminalOutputLimit: stateValues.terminalOutputLimit ?? TERMINAL_OUTPUT_LIMIT,
diff --git a/src/services/browser/BrowserSession.ts b/src/services/browser/BrowserSession.ts
index bed0332244..6fe25ddf5b 100644
--- a/src/services/browser/BrowserSession.ts
+++ b/src/services/browser/BrowserSession.ts
@@ -1,11 +1,12 @@
import * as vscode from "vscode"
import * as fs from "fs/promises"
import * as path from "path"
-import { Browser, Page, ScreenshotOptions, TimeoutError, launch } from "puppeteer-core"
+import { Browser, Page, ScreenshotOptions, TimeoutError, launch, connect } from "puppeteer-core"
// @ts-ignore
import PCR from "puppeteer-chromium-resolver"
import pWaitFor from "p-wait-for"
import delay from "delay"
+import axios from "axios"
import { fileExistsAtPath } from "../../utils/fs"
import { BrowserActionResult } from "../../shared/ExtensionMessage"
@@ -52,6 +53,41 @@ export class BrowserSession {
await this.closeBrowser() // this may happen when the model launches a browser again after having used it already before
}
+ const remoteBrowserHost = this.context.globalState.get("remoteBrowserHost") as string | undefined
+
+ if (remoteBrowserHost) {
+ console.log(`Attempting to connect to remote browser at ${remoteBrowserHost}`)
+ try {
+ // Fetch the WebSocket endpoint from the Chrome DevTools Protocol
+ const versionUrl = `${remoteBrowserHost.replace(/\/$/, "")}/json/version`
+ console.log(`Fetching WebSocket endpoint from ${versionUrl}`)
+
+ const response = await axios.get(versionUrl)
+ const browserWSEndpoint = response.data.webSocketDebuggerUrl
+
+ if (!browserWSEndpoint) {
+ throw new Error("Could not find webSocketDebuggerUrl in the response")
+ }
+
+ console.log(`Found WebSocket endpoint: ${browserWSEndpoint}`)
+
+ this.browser = await connect({
+ browserWSEndpoint,
+ defaultViewport: (() => {
+ const size =
+ (this.context.globalState.get("browserViewportSize") as string | undefined) || "900x600"
+ const [width, height] = size.split("x").map(Number)
+ return { width, height }
+ })(),
+ })
+ this.page = await this.browser?.newPage()
+ return
+ } catch (error) {
+ console.error(`Failed to connect to remote browser: ${error}`)
+ // Fall back to local browser if remote connection fails
+ }
+ }
+
const stats = await this.ensureChromiumExists()
this.browser = await stats.puppeteer.launch({
args: [
@@ -72,7 +108,14 @@ export class BrowserSession {
async closeBrowser(): Promise {
if (this.browser || this.page) {
console.log("closing browser...")
- await this.browser?.close().catch(() => {})
+
+ const remoteBrowserHost = this.context.globalState.get("remoteBrowserHost") as string | undefined
+ if (remoteBrowserHost && this.browser) {
+ await this.browser.disconnect().catch(() => {})
+ } else {
+ await this.browser?.close().catch(() => {})
+ }
+
this.browser = undefined
this.page = undefined
this.currentMousePosition = undefined
diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts
index b7e3d850cf..5b638e6534 100644
--- a/src/shared/ExtensionMessage.ts
+++ b/src/shared/ExtensionMessage.ts
@@ -123,6 +123,7 @@ export interface ExtensionState {
checkpointStorage: CheckpointStorage
browserViewportSize?: string
screenshotQuality?: number
+ remoteBrowserHost?: string
fuzzyMatchThreshold?: number
preferredLanguage: string
writeDelayMs: number
diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts
index 216c7588d7..df5c7d29f9 100644
--- a/src/shared/WebviewMessage.ts
+++ b/src/shared/WebviewMessage.ts
@@ -57,6 +57,7 @@ export interface WebviewMessage {
| "checkpointStorage"
| "browserViewportSize"
| "screenshotQuality"
+ | "remoteBrowserHost"
| "openMcpSettings"
| "restartMcpServer"
| "toggleToolAlwaysAllow"
diff --git a/src/shared/globalState.ts b/src/shared/globalState.ts
index 35e53bbe9c..05f54bfb8b 100644
--- a/src/shared/globalState.ts
+++ b/src/shared/globalState.ts
@@ -66,6 +66,7 @@ export const GLOBAL_STATE_KEYS = [
"checkpointStorage",
"browserViewportSize",
"screenshotQuality",
+ "remoteBrowserHost",
"fuzzyMatchThreshold",
"preferredLanguage", // Language setting for Cline's communication
"writeDelayMs",
diff --git a/webview-ui/src/components/settings/BrowserSettings.tsx b/webview-ui/src/components/settings/BrowserSettings.tsx
index 1ff0a36b9c..ab4a88113a 100644
--- a/webview-ui/src/components/settings/BrowserSettings.tsx
+++ b/webview-ui/src/components/settings/BrowserSettings.tsx
@@ -12,13 +12,17 @@ type BrowserSettingsProps = HTMLAttributes & {
browserToolEnabled?: boolean
browserViewportSize?: string
screenshotQuality?: number
- setCachedStateField: SetCachedStateField<"browserToolEnabled" | "browserViewportSize" | "screenshotQuality">
+ remoteBrowserHost?: string
+ setCachedStateField: SetCachedStateField<
+ "browserToolEnabled" | "browserViewportSize" | "screenshotQuality" | "remoteBrowserHost"
+ >
}
export const BrowserSettings = ({
browserToolEnabled,
browserViewportSize,
screenshotQuality,
+ remoteBrowserHost,
setCachedStateField,
...props
}: BrowserSettingsProps) => {
@@ -96,6 +100,33 @@ export const BrowserSettings = ({
screenshots but increase token usage.
+
+
+ Remote Chrome DevTools Host (optional)
+
+
+ setCachedStateField("remoteBrowserHost", e.target.value || undefined)
+ }
+ />
+
+ Connect to a remote Chrome browser by providing the DevTools Protocol host address.
+ Roo will automatically fetch the WebSocket endpoint from this address. If provided,
+ Roo will use this browser instead of launching a local one. Leave empty to use the
+ built-in browser.
+
+
)}
diff --git a/webview-ui/src/components/settings/SettingsView.tsx b/webview-ui/src/components/settings/SettingsView.tsx
index 7cafbd6663..0b8cce9b87 100644
--- a/webview-ui/src/components/settings/SettingsView.tsx
+++ b/webview-ui/src/components/settings/SettingsView.tsx
@@ -77,6 +77,7 @@ const SettingsView = forwardRef(({ onDone },
mcpEnabled,
rateLimitSeconds,
requestDelaySeconds,
+ remoteBrowserHost,
screenshotQuality,
soundEnabled,
soundVolume,
@@ -172,6 +173,7 @@ const SettingsView = forwardRef(({ onDone },
vscode.postMessage({ type: "enableCheckpoints", bool: enableCheckpoints })
vscode.postMessage({ type: "checkpointStorage", text: checkpointStorage })
vscode.postMessage({ type: "browserViewportSize", text: browserViewportSize })
+ vscode.postMessage({ type: "remoteBrowserHost", text: remoteBrowserHost })
vscode.postMessage({ type: "fuzzyMatchThreshold", value: fuzzyMatchThreshold ?? 1.0 })
vscode.postMessage({ type: "writeDelayMs", value: writeDelayMs })
vscode.postMessage({ type: "screenshotQuality", value: screenshotQuality ?? 75 })
@@ -378,6 +380,7 @@ const SettingsView = forwardRef(({ onDone },
browserToolEnabled={browserToolEnabled}
browserViewportSize={browserViewportSize}
screenshotQuality={screenshotQuality}
+ remoteBrowserHost={remoteBrowserHost}
setCachedStateField={setCachedStateField}
/>