Skip to content

Commit 95120bb

Browse files
Garothhugelungcanvrnoellipsis-dev[bot]saoudrizwan
authored
Remote browser control using devtools protocol (RooCodeInc#2423)
* manual port * successfully open remote chrome * clean up auto-detect vs specified path * move the browser settings into regular settings * changeset & prettier * correct chrome path description, remove some old comments, and rename headless mode to local mode * rename incorrect headless mode to 'local mode' * Sub-PR of hugelung/remote_browser: clicking browser widget's gear opens basic settings & scrolls down with a highlight (RooCodeInc#2439) * first version of scrolling to browser settings * really nice generic scroll to settings & highlight * formatting & changeset --------- Co-authored-by: Andrei Edell <[email protected]> * added feature to detect and display chrome path as placeholder in browser settings (RooCodeInc#2442) Co-authored-by: Andrei Edell <[email protected]> * Features to relaunch browser in debug, test connection (RooCodeInc#2440) * Features to Relaunch browser in debug, test connection * Update src/services/browser/BrowserSession.ts Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> * Update webview-ui/src/components/browser/BrowserSettingsMenu.tsx Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> --------- Co-authored-by: Andrei Edell <[email protected]> Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> * fix a merge conflict resolution error * fix linter issue * clarify settings descriptions * Remove sketchy network scanning code * respect viewport size in remote host * headless browser fix (RooCodeInc#2451) * Disable notifications in browser * start of info panel popover (RooCodeInc#2453) * start of info panel popover * remove duplicated message & prettier fix * Revert "remove duplicated message & prettier fix" This reverts commit dcefef35aacfc9d9a8461d37805c2ffb64b558c4. * info styling, close browser tab, hide headless info --------- Co-authored-by: Andrei Edell <[email protected]> * remove headless checkbox * settings layout rework & more auto * new chrome flags experiments * make headless choice automatic & phrasing & visual cleanups * auto-recheck chrome connection every second - while we are looking at settings - while we have remote debugging enabled * continuous remote connection testing & ux cleanup * remove advanced settings from package.json * format fixes * dont display connection type after dc to smooth over ui of reloading tasks * seems we need package-lock now for ci * Revert "remove advanced settings from package.json" This reverts commit 5defe4a8cae7631bcf9c1fb9efa874e3238c5034. * relaunch correctly with default session * prevent about:blank opening on relaunch * Resolve merge conflicts with refactor * add browser tool telemetry * try launching chrome using node spawn_child to detach it * browser settings update * do async dispose for browsersession * remove duplicated message implementation * Remove remote browser settings from configuration, and enhance browser settings UI with an advanced settings button. * Remove updateBrowserSettings * Fix text with chrome path * fix arafat's pr note about multiple timers * fix saoud's note about require use * Remote browser logging (RooCodeInc#2682) * logging * reduce logging levels --------- Co-authored-by: Andrei Edell <[email protected]> * Make browser status popup adapt to viewport width * remove requires for exec/spawn * remove unneeded comments * error telemetry * remove headless mode / settings everywhere * migrate values list to simple endpoint string * fix log spam and clean up a comment * Fixes; copy * Remove local state since we're already using extension state * Remove unnecessary remoteBrowserHost and remoteBrowserEnabled states * Fix status wrapping --------- Co-authored-by: Andrei Edell <[email protected]> Co-authored-by: canvrno <[email protected]> Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> Co-authored-by: canvrno <[email protected]> Co-authored-by: Saoud Rizwan <[email protected]> Co-authored-by: frostbournesb <[email protected]> Co-authored-by: Dennis Bartlett <[email protected]>
1 parent dbe5f74 commit 95120bb

21 files changed

+1453
-323
lines changed

.changeset/hungry-buckets-serve.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"claude-dev": patch
3+
---
4+
5+
Feature to open basic settings & scroll a section into view with a highlight animation

.changeset/many-keys-pay.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"claude-dev": patch
3+
---
4+
5+
Disable notifications in browser

.changeset/nervous-ties-try.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"claude-dev": patch
3+
---
4+
5+
Features to Relaunch browser in debug, test connection

.changeset/polite-flowers-lick.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"claude-dev": patch
3+
---
4+
5+
Remote browser control via devtools protocol
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"claude-dev": patch
3+
---
4+
5+
Added feature to detect installed versions of chromium and display them as a placeholder if not already explicitly configured by the user

.changeset/swift-frogs-learn.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"claude-dev": patch
3+
---
4+
5+
Fix for headless browser mode

package-lock.json

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

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,7 @@
364364
"axios": "^1.8.2",
365365
"cheerio": "^1.0.0",
366366
"chokidar": "^4.0.1",
367+
"chrome-launcher": "^1.1.2",
367368
"clone-deep": "^4.0.1",
368369
"default-shell": "^2.2.0",
369370
"diff": "^5.2.0",

src/core/controller/index.ts

Lines changed: 152 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,9 @@ import {
4343
updateGlobalState,
4444
} from "../storage/state"
4545
import { WebviewProvider } from "../webview"
46+
import { BrowserSession } from "../../services/browser/BrowserSession"
4647
import { GlobalFileNames } from "../storage/disk"
48+
import { discoverChromeInstances } from "../../services/browser/BrowserDiscovery"
4749
import { searchWorkspaceFiles } from "../../services/search/file-search"
4850
import { getWorkspacePath } from "../../utils/path"
4951

@@ -279,6 +281,12 @@ export class Controller {
279281
break
280282
case "browserSettings":
281283
if (message.browserSettings) {
284+
// remoteBrowserEnabled now means "enable remote browser connection"
285+
// commenting out since this is being done in BrowserSettingsSection updateRemoteBrowserEnabled
286+
// if (!message.browserSettings.remoteBrowserEnabled) {
287+
// // If disabling remote browser connection, clear the remoteBrowserHost
288+
// message.browserSettings.remoteBrowserHost = undefined
289+
// }
282290
await updateGlobalState(this.context, "browserSettings", message.browserSettings)
283291
if (this.task) {
284292
this.task.browserSettings = message.browserSettings
@@ -287,6 +295,123 @@ export class Controller {
287295
await this.postStateToWebview()
288296
}
289297
break
298+
case "getBrowserConnectionInfo":
299+
try {
300+
// Get the current browser session from Cline if it exists
301+
if (this.task?.browserSession) {
302+
const connectionInfo = this.task.browserSession.getConnectionInfo()
303+
await this.postMessageToWebview({
304+
type: "browserConnectionInfo",
305+
isConnected: connectionInfo.isConnected,
306+
isRemote: connectionInfo.isRemote,
307+
host: connectionInfo.host,
308+
})
309+
} else {
310+
// If no active browser session, just return the settings
311+
const { browserSettings } = await getAllExtensionState(this.context)
312+
await this.postMessageToWebview({
313+
type: "browserConnectionInfo",
314+
isConnected: false,
315+
isRemote: !!browserSettings.remoteBrowserEnabled,
316+
host: browserSettings.remoteBrowserHost,
317+
})
318+
}
319+
} catch (error) {
320+
console.error("Error getting browser connection info:", error)
321+
await this.postMessageToWebview({
322+
type: "browserConnectionInfo",
323+
isConnected: false,
324+
isRemote: false,
325+
})
326+
}
327+
break
328+
case "testBrowserConnection":
329+
try {
330+
const { browserSettings } = await getAllExtensionState(this.context)
331+
const browserSession = new BrowserSession(this.context, browserSettings)
332+
// If no text is provided, try auto-discovery
333+
if (!message.text) {
334+
try {
335+
const discoveredHost = await discoverChromeInstances()
336+
if (discoveredHost) {
337+
// Test the connection to the discovered host
338+
const result = await browserSession.testConnection(discoveredHost)
339+
// Send the result back to the webview
340+
await this.postMessageToWebview({
341+
type: "browserConnectionResult",
342+
success: result.success,
343+
text: `Auto-discovered and tested connection to Chrome at ${discoveredHost}: ${result.message}`,
344+
endpoint: result.endpoint,
345+
})
346+
} else {
347+
await this.postMessageToWebview({
348+
type: "browserConnectionResult",
349+
success: false,
350+
text: "No Chrome instances found on the network. Make sure Chrome is running with remote debugging enabled (--remote-debugging-port=9222).",
351+
})
352+
}
353+
} catch (error) {
354+
await this.postMessageToWebview({
355+
type: "browserConnectionResult",
356+
success: false,
357+
text: `Error during auto-discovery: ${error instanceof Error ? error.message : String(error)}`,
358+
})
359+
}
360+
} else {
361+
// Test the provided URL
362+
const result = await browserSession.testConnection(message.text)
363+
364+
// Send the result back to the webview
365+
await this.postMessageToWebview({
366+
type: "browserConnectionResult",
367+
success: result.success,
368+
text: result.message,
369+
endpoint: result.endpoint,
370+
})
371+
}
372+
} catch (error) {
373+
await this.postMessageToWebview({
374+
type: "browserConnectionResult",
375+
success: false,
376+
text: `Error testing connection: ${error instanceof Error ? error.message : String(error)}`,
377+
})
378+
}
379+
break
380+
case "discoverBrowser":
381+
try {
382+
const discoveredHost = await discoverChromeInstances()
383+
384+
if (discoveredHost) {
385+
// Don't update the remoteBrowserHost state when auto-discovering
386+
// This way we don't override the user's preference
387+
388+
// Test the connection to get the endpoint
389+
const { browserSettings } = await getAllExtensionState(this.context)
390+
const browserSession = new BrowserSession(this.context, browserSettings)
391+
const result = await browserSession.testConnection(discoveredHost)
392+
393+
// Send the result back to the webview
394+
await this.postMessageToWebview({
395+
type: "browserConnectionResult",
396+
success: true,
397+
text: `Successfully discovered and connected to Chrome at ${discoveredHost}`,
398+
endpoint: result.endpoint,
399+
})
400+
} else {
401+
await this.postMessageToWebview({
402+
type: "browserConnectionResult",
403+
success: false,
404+
text: "No Chrome instances found on the network. Make sure Chrome is running with remote debugging enabled (--remote-debugging-port=9222).",
405+
})
406+
}
407+
} catch (error) {
408+
await this.postMessageToWebview({
409+
type: "browserConnectionResult",
410+
success: false,
411+
text: `Error discovering browser: ${error instanceof Error ? error.message : String(error)}`,
412+
})
413+
}
414+
break
290415
case "togglePlanActMode":
291416
if (message.chatSettings) {
292417
await this.togglePlanActModeWithChatSettings(message.chatSettings, message.chatContent)
@@ -299,11 +424,11 @@ export class Controller {
299424
text: message.text,
300425
})
301426
break
302-
// case "relaunchChromeDebugMode":
303-
// if (this.task) {
304-
// this.task.browserSession.relaunchChromeDebugMode()
305-
// }
306-
// break
427+
case "relaunchChromeDebugMode":
428+
const { browserSettings } = await getAllExtensionState(this.context)
429+
const browserSession = new BrowserSession(this.context, browserSettings)
430+
await browserSession.relaunchChromeDebugMode(this)
431+
break
307432
case "askResponse":
308433
this.task?.handleWebviewAskResponse(message.askResponse!, message.text, message.images)
309434
break
@@ -623,6 +748,13 @@ export class Controller {
623748
})
624749
break
625750
}
751+
case "scrollToSettings": {
752+
await this.postMessageToWebview({
753+
type: "scrollToSettings",
754+
text: message.text,
755+
})
756+
break
757+
}
626758
case "telemetrySetting": {
627759
if (message.telemetrySetting) {
628760
await this.updateTelemetrySetting(message.telemetrySetting)
@@ -663,6 +795,21 @@ export class Controller {
663795
this.postMessageToWebview({ type: "relinquishControl" })
664796
break
665797
}
798+
case "getDetectedChromePath": {
799+
try {
800+
const { browserSettings } = await getAllExtensionState(this.context)
801+
const browserSession = new BrowserSession(this.context, browserSettings)
802+
const { path, isBundled } = await browserSession.getDetectedChromePath()
803+
await this.postMessageToWebview({
804+
type: "detectedChromePath",
805+
text: path,
806+
isBundled,
807+
})
808+
} catch (error) {
809+
console.error("Error getting detected Chrome path:", error)
810+
}
811+
break
812+
}
666813
case "getRelativePaths": {
667814
if (message.uris && message.uris.length > 0) {
668815
const resolvedPaths = await Promise.all(
@@ -699,7 +846,6 @@ export class Controller {
699846
}
700847
break
701848
}
702-
703849
case "searchFiles": {
704850
const workspacePath = getWorkspacePath()
705851

src/core/storage/state.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
280280
customInstructions,
281281
taskHistory,
282282
autoApprovalSettings: autoApprovalSettings || DEFAULT_AUTO_APPROVAL_SETTINGS, // default value can be 0 or empty string
283-
browserSettings: browserSettings || DEFAULT_BROWSER_SETTINGS,
283+
browserSettings: { ...DEFAULT_BROWSER_SETTINGS, ...browserSettings }, // this will ensure that older versions of browserSettings (e.g. before remoteBrowserEnabled was added) are merged with the default values (false for remoteBrowserEnabled)
284284
chatSettings: chatSettings || DEFAULT_CHAT_SETTINGS,
285285
userInfo,
286286
previousModeApiProvider,

0 commit comments

Comments
 (0)