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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.pnpm-store
dist
out
out-*
Expand Down
3 changes: 2 additions & 1 deletion scripts/generate-types.mts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import fs from "fs/promises"
import { zodToTs, createTypeAlias, printNode } from "zod-to-ts"
import { $ } from "execa"

import { typeDefinitions } from "../src/schemas"
import schemas from "../src/schemas"
const { typeDefinitions } = schemas

async function main() {
const types: string[] = [
Expand Down
7 changes: 4 additions & 3 deletions src/core/Cline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2466,7 +2466,8 @@ export class Cline extends EventEmitter<ClineEvents> {
}
break
} else {
let browserActionResult: BrowserActionResult
// Initialize with empty object to avoid "used before assigned" errors
let browserActionResult: BrowserActionResult = {}
if (action === "launch") {
if (!url) {
this.consecutiveMistakeCount++
Expand Down Expand Up @@ -2552,9 +2553,9 @@ export class Cline extends EventEmitter<ClineEvents> {
pushToolResult(
formatResponse.toolResult(
`The browser action has been executed. The console logs and screenshot have been captured for your analysis.\n\nConsole logs:\n${
browserActionResult.logs || "(No new logs)"
browserActionResult?.logs || "(No new logs)"
}\n\n(REMEMBER: if you need to proceed to using non-\`browser_action\` tools or launch a new browser, you MUST first close this browser. For example, if after analyzing the logs and screenshot you need to edit a file, you must first close the browser before you can use the write_to_file tool.)`,
browserActionResult.screenshot ? [browserActionResult.screenshot] : [],
browserActionResult?.screenshot ? [browserActionResult.screenshot] : [],
),
)
break
Expand Down
5 changes: 1 addition & 4 deletions src/core/mentions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,7 @@ export async function openMention(mention?: string, osInfo?: string): Promise<vo
return
}

if (
(osInfo !== "win32" && mention.startsWith("/")) ||
(osInfo === "win32" && mention.startsWith("\\"))
) {
if ((osInfo !== "win32" && mention.startsWith("/")) || (osInfo === "win32" && mention.startsWith("\\"))) {
const relPath = mention.slice(1)
let absPath = path.resolve(cwd, relPath)
if (absPath.includes(" ")) {
Expand Down
90 changes: 21 additions & 69 deletions src/core/webview/ClineProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ import { McpHub } from "../../services/mcp/McpHub"
import { McpServerManager } from "../../services/mcp/McpServerManager"
import { ShadowCheckpointService } from "../../services/checkpoints/ShadowCheckpointService"
import { BrowserSession } from "../../services/browser/BrowserSession"
import { discoverChromeInstances } from "../../services/browser/browserDiscovery"
import { discoverChromeHostUrl, tryChromeHostUrl } from "../../services/browser/browserDiscovery"
import { searchWorkspaceFiles } from "../../services/search/file-search"
import { fileExistsAtPath } from "../../utils/fs"
import { playSound, setSoundEnabled, setSoundVolume } from "../../utils/sound"
Expand Down Expand Up @@ -1420,74 +1420,17 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
await this.postStateToWebview()
break
case "testBrowserConnection":
try {
const browserSession = new BrowserSession(this.context)
// If no text is provided, try auto-discovery
if (!message.text) {
try {
const discoveredHost = await discoverChromeInstances()
if (discoveredHost) {
// Test the connection to the discovered host
const result = await browserSession.testConnection(discoveredHost)
// Send the result back to the webview
await this.postMessageToWebview({
type: "browserConnectionResult",
success: result.success,
text: `Auto-discovered and tested connection to Chrome at ${discoveredHost}: ${result.message}`,
values: { endpoint: result.endpoint },
})
} else {
await this.postMessageToWebview({
type: "browserConnectionResult",
success: false,
text: "No Chrome instances found on the network. Make sure Chrome is running with remote debugging enabled (--remote-debugging-port=9222).",
})
}
} catch (error) {
await this.postMessageToWebview({
type: "browserConnectionResult",
success: false,
text: `Error during auto-discovery: ${error instanceof Error ? error.message : String(error)}`,
})
}
} else {
// Test the provided URL
const result = await browserSession.testConnection(message.text)

// Send the result back to the webview
await this.postMessageToWebview({
type: "browserConnectionResult",
success: result.success,
text: result.message,
values: { endpoint: result.endpoint },
})
}
} catch (error) {
await this.postMessageToWebview({
type: "browserConnectionResult",
success: false,
text: `Error testing connection: ${error instanceof Error ? error.message : String(error)}`,
})
}
break
case "discoverBrowser":
try {
const discoveredHost = await discoverChromeInstances()

if (discoveredHost) {
// Don't update the remoteBrowserHost state when auto-discovering
// This way we don't override the user's preference

// Test the connection to get the endpoint
const browserSession = new BrowserSession(this.context)
const result = await browserSession.testConnection(discoveredHost)

// If no text is provided, try auto-discovery
if (!message.text) {
// Use testBrowserConnection for auto-discovery
const chromeHostUrl = await discoverChromeHostUrl()
if (chromeHostUrl) {
// Send the result back to the webview
await this.postMessageToWebview({
type: "browserConnectionResult",
success: true,
text: `Successfully discovered and connected to Chrome at ${discoveredHost}`,
values: { endpoint: result.endpoint },
success: !!chromeHostUrl,
text: `Auto-discovered and tested connection to Chrome: ${chromeHostUrl}`,
values: { endpoint: chromeHostUrl },
})
} else {
await this.postMessageToWebview({
Expand All @@ -1496,11 +1439,17 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
text: "No Chrome instances found on the network. Make sure Chrome is running with remote debugging enabled (--remote-debugging-port=9222).",
})
}
} catch (error) {
} else {
// Test the provided URL
const customHostUrl = message.text
const hostIsValid = await tryChromeHostUrl(message.text)
// Send the result back to the webview
await this.postMessageToWebview({
type: "browserConnectionResult",
success: false,
text: `Error discovering browser: ${error instanceof Error ? error.message : String(error)}`,
success: hostIsValid,
text: hostIsValid
? `Successfully connected to Chrome: ${customHostUrl}`
: "Failed to connect to Chrome",
})
}
break
Expand Down Expand Up @@ -2602,6 +2551,7 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
screenshotQuality,
remoteBrowserHost,
remoteBrowserEnabled,
cachedChromeHostUrl,
writeDelayMs,
terminalOutputLineLimit,
terminalShellIntegrationTimeout,
Expand Down Expand Up @@ -2670,6 +2620,7 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
screenshotQuality: screenshotQuality ?? 75,
remoteBrowserHost,
remoteBrowserEnabled: remoteBrowserEnabled ?? false,
cachedChromeHostUrl: cachedChromeHostUrl,
writeDelayMs: writeDelayMs ?? 1000,
terminalOutputLineLimit: terminalOutputLineLimit ?? 500,
terminalShellIntegrationTimeout: terminalShellIntegrationTimeout ?? TERMINAL_SHELL_INTEGRATION_TIMEOUT,
Expand Down Expand Up @@ -2755,6 +2706,7 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
screenshotQuality: stateValues.screenshotQuality ?? 75,
remoteBrowserHost: stateValues.remoteBrowserHost,
remoteBrowserEnabled: stateValues.remoteBrowserEnabled ?? false,
cachedChromeHostUrl: stateValues.cachedChromeHostUrl as string | undefined,
fuzzyMatchThreshold: stateValues.fuzzyMatchThreshold ?? 1.0,
writeDelayMs: stateValues.writeDelayMs ?? 1000,
terminalOutputLineLimit: stateValues.terminalOutputLineLimit ?? 500,
Expand Down
82 changes: 7 additions & 75 deletions src/core/webview/__tests__/ClineProvider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,12 @@ jest.mock("../../../services/browser/BrowserSession", () => ({

// Mock browserDiscovery
jest.mock("../../../services/browser/browserDiscovery", () => ({
discoverChromeInstances: jest.fn().mockImplementation(async () => {
discoverChromeHostUrl: jest.fn().mockImplementation(async () => {
return "http://localhost:9222"
}),
tryChromeHostUrl: jest.fn().mockImplementation(async (url) => {
return url === "http://localhost:9222"
}),
}))

jest.mock(
Expand Down Expand Up @@ -1916,9 +1919,9 @@ describe("ClineProvider", () => {
type: "testBrowserConnection",
})

// Verify discoverChromeInstances was called
const { discoverChromeInstances } = require("../../../services/browser/browserDiscovery")
expect(discoverChromeInstances).toHaveBeenCalled()
// Verify discoverChromeHostUrl was called
const { discoverChromeHostUrl } = require("../../../services/browser/browserDiscovery")
expect(discoverChromeHostUrl).toHaveBeenCalled()

// Verify postMessage was called with success result
expect(mockPostMessage).toHaveBeenCalledWith(
Expand All @@ -1929,77 +1932,6 @@ describe("ClineProvider", () => {
}),
)
})

test("handles discoverBrowser message", async () => {
// Get the message handler
const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as jest.Mock).mock.calls[0][0]

// Test browser discovery
await messageHandler({
type: "discoverBrowser",
})

// Verify discoverChromeInstances was called
const { discoverChromeInstances } = require("../../../services/browser/browserDiscovery")
expect(discoverChromeInstances).toHaveBeenCalled()

// Verify postMessage was called with success result
expect(mockPostMessage).toHaveBeenCalledWith(
expect.objectContaining({
type: "browserConnectionResult",
success: true,
text: expect.stringContaining("Successfully discovered and connected to Chrome"),
}),
)
})

test("handles errors during browser discovery", async () => {
// Mock discoverChromeInstances to throw an error
const { discoverChromeInstances } = require("../../../services/browser/browserDiscovery")
discoverChromeInstances.mockImplementationOnce(() => {
throw new Error("Discovery error")
})

// Get the message handler
const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as jest.Mock).mock.calls[0][0]

// Test browser discovery with error
await messageHandler({
type: "discoverBrowser",
})

// Verify postMessage was called with error result
expect(mockPostMessage).toHaveBeenCalledWith(
expect.objectContaining({
type: "browserConnectionResult",
success: false,
text: expect.stringContaining("Error discovering browser"),
}),
)
})

test("handles case when no browsers are discovered", async () => {
// Mock discoverChromeInstances to return null (no browsers found)
const { discoverChromeInstances } = require("../../../services/browser/browserDiscovery")
discoverChromeInstances.mockImplementationOnce(() => null)

// Get the message handler
const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as jest.Mock).mock.calls[0][0]

// Test browser discovery with no browsers found
await messageHandler({
type: "discoverBrowser",
})

// Verify postMessage was called with failure result
expect(mockPostMessage).toHaveBeenCalledWith(
expect.objectContaining({
type: "browserConnectionResult",
success: false,
text: expect.stringContaining("No Chrome instances found"),
}),
)
})
})
})

Expand Down
1 change: 1 addition & 0 deletions src/exports/roo-code.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@ type GlobalSettings = {
screenshotQuality?: number | undefined
remoteBrowserEnabled?: boolean | undefined
remoteBrowserHost?: string | undefined
cachedChromeHostUrl?: string | undefined
enableCheckpoints?: boolean | undefined
checkpointStorage?: ("task" | "workspace") | undefined
ttsEnabled?: boolean | undefined
Expand Down
1 change: 1 addition & 0 deletions src/exports/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ type GlobalSettings = {
screenshotQuality?: number | undefined
remoteBrowserEnabled?: boolean | undefined
remoteBrowserHost?: string | undefined
cachedChromeHostUrl?: string | undefined
enableCheckpoints?: boolean | undefined
checkpointStorage?: ("task" | "workspace") | undefined
ttsEnabled?: boolean | undefined
Expand Down
7 changes: 6 additions & 1 deletion src/schemas/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,7 @@ export const globalSettingsSchema = z.object({
screenshotQuality: z.number().optional(),
remoteBrowserEnabled: z.boolean().optional(),
remoteBrowserHost: z.string().optional(),
cachedChromeHostUrl: z.string().optional(),

enableCheckpoints: z.boolean().optional(),
checkpointStorage: checkpointStoragesSchema.optional(),
Expand Down Expand Up @@ -618,6 +619,7 @@ const globalSettingsRecord: GlobalSettingsRecord = {
customModePrompts: undefined,
customSupportPrompts: undefined,
enhancementApiConfigId: undefined,
cachedChromeHostUrl: undefined,
}

export const GLOBAL_SETTINGS_KEYS = Object.keys(globalSettingsRecord) as Keys<GlobalSettings>[]
Expand Down Expand Up @@ -791,7 +793,7 @@ export type TokenUsage = z.infer<typeof tokenUsageSchema>
* TypeDefinition
*/

type TypeDefinition = {
export type TypeDefinition = {
schema: z.ZodTypeAny
identifier: string
}
Expand All @@ -802,3 +804,6 @@ export const typeDefinitions: TypeDefinition[] = [
{ schema: clineMessageSchema, identifier: "ClineMessage" },
{ schema: tokenUsageSchema, identifier: "TokenUsage" },
]

// Also export as default for ESM compatibility
export default { typeDefinitions }
Loading
Loading