Last active
January 5, 2026 08:34
-
-
Save hugefiver/902e78db001e0bec0ffa07e5f2260ce5 to your computer and use it in GitHub Desktop.
antigravity-browser-launcher
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| Notes: | |
| - This is a *reference rewrite* for comprehension and further refactoring. | |
| - The original runtime depends on VS Code extension APIs (vscode) and a custom | |
| namespace: vscode.antigravityUnifiedStateSync.BrowserPreferences. | |
| */ | |
| import type * as vscode from "vscode"; | |
| import * as childProcess from "child_process"; | |
| import * as fs from "fs"; | |
| import * as os from "os"; | |
| import * as path from "path"; | |
| /** Parsed server address like: user@host:port */ | |
| export class ServerAddress { | |
| constructor( | |
| public hostname: string, | |
| public user?: string, | |
| public port?: number, | |
| ) {} | |
| static parse(address: string): ServerAddress { | |
| let user: string | undefined; | |
| let port: number | undefined; | |
| const atIndex = address.lastIndexOf("@"); | |
| if (atIndex !== -1) { | |
| user = address.substring(0, atIndex); | |
| } | |
| const colonIndex = address.lastIndexOf(":"); | |
| if (colonIndex !== -1) { | |
| const parsed = parseInt(address.substring(colonIndex + 1), 10); | |
| port = Number.isFinite(parsed) ? parsed : undefined; | |
| } | |
| const hostStart = atIndex !== -1 ? atIndex + 1 : 0; | |
| const hostEnd = colonIndex !== -1 ? colonIndex : address.length; | |
| const hostname = address.substring(hostStart, hostEnd); | |
| return new ServerAddress(hostname, user, port); | |
| } | |
| toString(): string { | |
| let value = this.hostname; | |
| if (this.user) value = `${this.user}@${value}`; | |
| if (this.port) value += `:${this.port}`; | |
| return value; | |
| } | |
| /** | |
| * Accepts either: | |
| * 1) hex-encoded JSON: { hostName, user, port } | |
| * 2) legacy encoding with "\\xNN" escapes | |
| */ | |
| static parseEncoded(encoded: string): ServerAddress { | |
| try { | |
| const decodedJson = Buffer.from(encoded, "hex").toString(); | |
| const parsed = JSON.parse(decodedJson) as { | |
| hostName?: string; | |
| user?: string; | |
| port?: number; | |
| }; | |
| if (parsed?.hostName) { | |
| return new ServerAddress(parsed.hostName, parsed.user, parsed.port); | |
| } | |
| } catch { | |
| // fall back to legacy string encoding | |
| } | |
| const legacyDecoded = encoded.replace(/\\x([0-9a-f]{2})/g, (_m, hex) => | |
| String.fromCharCode(parseInt(hex, 16)), | |
| ); | |
| return ServerAddress.parse(legacyDecoded); | |
| } | |
| /** Encodes by escaping capital letters into "\\xNN". */ | |
| toEncodedString(): string { | |
| return this.toString().replace(/[A-Z]/g, (char) => | |
| `\\x${char.charCodeAt(0).toString(16).toLowerCase()}`, | |
| ); | |
| } | |
| } | |
| const DEV_EXTENSION_ID = "Codeium.codeium-dev"; | |
| function hasDevExtension(vscodeApi: typeof vscode): boolean { | |
| return vscodeApi.extensions.getExtension(DEV_EXTENSION_ID) !== undefined; | |
| } | |
| /** OutputChannel wrapper used by the original module. */ | |
| class BrowserLauncherOutput { | |
| private static instance: vscode.OutputChannel | undefined; | |
| static get(vscodeApi: typeof vscode): vscode.OutputChannel { | |
| if (!BrowserLauncherOutput.instance) { | |
| BrowserLauncherOutput.instance = vscodeApi.window.createOutputChannel( | |
| "Browser Launcher", | |
| { log: true }, | |
| ); | |
| } | |
| return BrowserLauncherOutput.instance; | |
| } | |
| } | |
| class ChromeInstallationError extends Error { | |
| constructor(message: string) { | |
| super(message); | |
| this.name = "ChromeInstallationError"; | |
| } | |
| } | |
| const CHROME_DOWNLOAD_URL = "https://www.google.com/chrome/"; | |
| // Cache values (loaded from Antigravity unified state sync). | |
| let configuredChromePath: string | undefined; | |
| let configuredProfilePath: string | undefined; | |
| let configuredCdpPort: number | undefined; | |
| let lastLaunchCommand: string | undefined; | |
| let sshReverseProxyProcess: childProcess.ChildProcess | null = null; | |
| const DEFAULT_CHROME_PATHS: string[] = [ | |
| "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", | |
| "/usr/bin/google-chrome", | |
| "/usr/bin/chromium-browser", | |
| "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe", | |
| ]; | |
| const FALLBACK_CHROME_PATHS: string[] = [ | |
| "/Applications/Chromium.app/Contents/MacOS/Chromium", | |
| "/usr/bin/google-chrome-stable", | |
| "/usr/bin/chromium", | |
| "/snap/bin/chromium", | |
| "/usr/bin/google-chrome-beta", | |
| "/opt/google/chrome/chrome", | |
| "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe", | |
| "C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe", | |
| process.env.LOCALAPPDATA | |
| ? `${process.env.LOCALAPPDATA}\\Google\\Chrome\\Application\\chrome.exe` | |
| : "", | |
| process.env.PROGRAMFILES | |
| ? `${process.env.PROGRAMFILES}\\Google\\Chrome\\Application\\chrome.exe` | |
| : "", | |
| "C:\\Program Files\\Chromium\\Application\\chrome.exe", | |
| "C:\\Program Files (x86)\\Chromium\\Application\\chrome.exe", | |
| ].filter(Boolean); | |
| function getChromeExecutablePath(): string | undefined { | |
| if (configuredChromePath?.trim()) return configuredChromePath.trim(); | |
| for (const candidate of [...DEFAULT_CHROME_PATHS, ...FALLBACK_CHROME_PATHS]) { | |
| if (fs.existsSync(candidate)) return candidate; | |
| } | |
| return undefined; | |
| } | |
| function getProfilePath(): string { | |
| if (configuredProfilePath?.trim()) return configuredProfilePath.trim(); | |
| return path.join(os.homedir(), ".gemini", "antigravity-browser-profile"); | |
| } | |
| function doesProfileExist(): boolean { | |
| return fs.existsSync(getProfilePath()); | |
| } | |
| function getCdpPort(): number { | |
| return configuredCdpPort && configuredCdpPort > 0 ? configuredCdpPort : 9222; | |
| } | |
| function wait(ms: number): Promise<void> { | |
| return new Promise((resolve) => setTimeout(resolve, ms)); | |
| } | |
| /** | |
| * Checks if CDP endpoint responds on 127.0.0.1 by calling curl. | |
| * | |
| * NOTE: On Windows this assumes curl is available in PATH. | |
| */ | |
| function isCdpEndpointUp(port: number): boolean { | |
| try { | |
| childProcess.execSync(`curl -s http://127.0.0.1:${port}/json/version`, { | |
| stdio: "ignore", | |
| timeout: 500, | |
| }); | |
| return true; | |
| } catch { | |
| return false; | |
| } | |
| } | |
| async function waitForBrowserReady( | |
| vscodeApi: typeof vscode, | |
| port: number, | |
| startTimeMs: number, | |
| timeoutMs = 30_000, | |
| ): Promise<void> { | |
| const output = BrowserLauncherOutput.get(vscodeApi); | |
| while (Date.now() - startTimeMs < timeoutMs) { | |
| if (isCdpEndpointUp(port)) { | |
| output.appendLine( | |
| `Browser ready on port ${port} after ${Date.now() - startTimeMs}ms`, | |
| ); | |
| return; | |
| } | |
| await wait(100); | |
| } | |
| output.appendLine(`Browser startup timed out after ${timeoutMs}ms on port ${port}`); | |
| } | |
| function spawnBrowser( | |
| vscodeApi: typeof vscode, | |
| chromePath: string, | |
| args: string[], | |
| shouldFocus: boolean, | |
| ): childProcess.ChildProcess { | |
| const output = BrowserLauncherOutput.get(vscodeApi); | |
| // macOS uses `open -a <app> --args ...` | |
| if (os.platform() === "darwin") { | |
| const openArgs: string[] = ["--new"]; | |
| if (!shouldFocus) openArgs.push("--background"); | |
| openArgs.push("-a", chromePath, "--args", ...args); | |
| lastLaunchCommand = `open ${openArgs.join(" ")}`; | |
| output.appendLine(`Launching browser with command: ${lastLaunchCommand}`); | |
| return childProcess.spawn("open", openArgs, { | |
| detached: true, | |
| stdio: "ignore", | |
| shell: false, | |
| }); | |
| } | |
| lastLaunchCommand = `"${chromePath}" ${args.join(" ")}`; | |
| output.appendLine(`Launching browser with command: ${lastLaunchCommand}`); | |
| return childProcess.spawn(chromePath, args, { | |
| detached: true, | |
| stdio: "ignore", | |
| shell: false, | |
| }); | |
| } | |
| function isChromeRunningWithCdp(vscodeApi: typeof vscode): boolean { | |
| const output = BrowserLauncherOutput.get(vscodeApi); | |
| const port = getCdpPort(); | |
| if (isCdpEndpointUp(port)) { | |
| output.appendLine(`Chrome with CDP is already running on port ${port}.`); | |
| return true; | |
| } | |
| output.appendLine(`No Chrome with CDP found on port ${port}. Safe to launch Chrome.`); | |
| return false; | |
| } | |
| function startSshReverseProxyIfRemoteSsh(vscodeApi: typeof vscode): void { | |
| const output = BrowserLauncherOutput.get(vscodeApi); | |
| output.appendLine("Starting SSH reverse proxy..."); | |
| const remoteAuthority = vscodeApi.env.remoteAuthority; | |
| if (!remoteAuthority?.startsWith("ssh-remote+")) { | |
| output.appendLine("Not in remote SSH session. Skipping reverse proxy initialization."); | |
| return; | |
| } | |
| // If we already have a proxy process, restart it. | |
| if (sshReverseProxyProcess) { | |
| sshReverseProxyProcess.kill(); | |
| sshReverseProxyProcess = null; | |
| } | |
| const parts = remoteAuthority.split("+"); | |
| if (parts.length < 2) { | |
| output.appendLine("Warning: Invalid remote authority format. Expected ssh-remote+<hostData>"); | |
| return; | |
| } | |
| const encodedHostData = parts[1]; | |
| const host = ServerAddress.parseEncoded(encodedHostData); | |
| const sshTarget = host.user ? `${host.user}@${host.hostname}` : host.hostname; | |
| const port = getCdpPort(); | |
| const sshArgs = [ | |
| "-R", | |
| `${port}:127.0.0.1:${port}`, | |
| sshTarget, | |
| "-N", | |
| "-o", | |
| "ExitOnForwardFailure=yes", | |
| "-o", | |
| "LogLevel=ERROR", | |
| "-o", | |
| "StreamLocalBindUnlink=yes", | |
| ]; | |
| sshReverseProxyProcess = childProcess.spawn("ssh", sshArgs); | |
| } | |
| function getBrowserLaunchFlags(vscodeApi: typeof vscode): string[] { | |
| const customFlags = vscodeApi.workspace | |
| .getConfiguration("codeiumDev") | |
| .get<string[]>("browserLaunchFlags"); | |
| if (customFlags?.length) return customFlags; | |
| return [ | |
| `--remote-debugging-port=${getCdpPort()}`, | |
| `--user-data-dir=${getProfilePath()}`, | |
| "--disable-fre", | |
| "--no-default-browser-check", | |
| "--no-first-run", | |
| "--auto-accept-browser-signin-for-tests", | |
| "--ash-no-nudges", | |
| "--disable-features=OfferMigrationToDiceUsers,OptGuideOnDeviceModel", | |
| ]; | |
| } | |
| async function loadBrowserPreferences(vscodeApi: typeof vscode): Promise<void> { | |
| // This is a custom API provided by the original extension host. | |
| // Keep it as loose `any` so this file can be typechecked without that dependency. | |
| try { | |
| const unifiedState = (vscodeApi as any).antigravityUnifiedStateSync; | |
| if (!unifiedState?.BrowserPreferences) return; | |
| const [chromePath, profilePath, cdpPort] = await Promise.all([ | |
| unifiedState.BrowserPreferences.getBrowserChromePath(), | |
| unifiedState.BrowserPreferences.getBrowserUserProfilePath(), | |
| unifiedState.BrowserPreferences.getBrowserCdpPort(), | |
| ]); | |
| configuredChromePath = chromePath; | |
| configuredProfilePath = profilePath; | |
| configuredCdpPort = cdpPort; | |
| } catch (err) { | |
| // The original code logs to console and continues. | |
| // eslint-disable-next-line no-console | |
| console.error("[Browser Launcher] Failed to load browser settings:", err); | |
| } | |
| } | |
| type LaunchDiagnostics = Record<string, string>; | |
| async function collectLaunchFailureDiagnostics( | |
| vscodeApi: typeof vscode, | |
| err: Error, | |
| ): Promise<LaunchDiagnostics> { | |
| const diagnostics: LaunchDiagnostics = {}; | |
| const output = BrowserLauncherOutput.get(vscodeApi); | |
| const port = getCdpPort(); | |
| const platform = os.platform(); | |
| diagnostics._error_message = err.message; | |
| if (err.stack) diagnostics._error_stack = err.stack.substring(0, 500); | |
| // Port scan | |
| try { | |
| let portInfo = ""; | |
| if (platform === "win32") { | |
| portInfo = childProcess | |
| .execSync(`netstat -ano | findstr :${port}`, { | |
| encoding: "utf-8", | |
| timeout: 1000, | |
| }) | |
| .trim(); | |
| } else { | |
| portInfo = childProcess | |
| .execSync(`lsof -ti:${port}`, { | |
| encoding: "utf-8", | |
| timeout: 1000, | |
| }) | |
| .trim(); | |
| } | |
| diagnostics.port_in_use = portInfo ? "true" : "false"; | |
| if (portInfo) diagnostics.port_scan_output = portInfo; | |
| } catch { | |
| diagnostics.port_in_use = "false"; | |
| } | |
| diagnostics.platform = platform; | |
| // Process scan | |
| try { | |
| let procCommand: string; | |
| if (platform === "darwin" || platform === "linux") { | |
| procCommand = 'pgrep -fl "chrome|chromium"'; | |
| } else if (platform === "win32") { | |
| procCommand = 'tasklist /FI "IMAGENAME eq chrome.exe" /V'; | |
| } else { | |
| throw new Error("Unsupported platform"); | |
| } | |
| const processList = childProcess | |
| .execSync(procCommand, { | |
| encoding: "utf-8", | |
| timeout: 2000, | |
| }) | |
| .trim(); | |
| if (processList) { | |
| diagnostics.chrome_processes_found = "true"; | |
| diagnostics._chrome_processes = processList.substring(0, 1000); | |
| const debugPorts = processList.match(/--remote-debugging-port=(\d+)/g); | |
| if (debugPorts) diagnostics._running_cdp_ports = debugPorts.join(", "); | |
| const userDataDirs = processList.match(/--user-data-dir=([^\s]+)/g); | |
| if (userDataDirs) { | |
| diagnostics._running_user_data_dirs = userDataDirs | |
| .map((m) => m.substring(0, 100)) | |
| .join(", ") | |
| .substring(0, 300); | |
| } | |
| } else { | |
| diagnostics.chrome_processes_found = "false"; | |
| } | |
| } catch (scanErr) { | |
| diagnostics.chrome_processes_found = "error"; | |
| output.appendLine( | |
| `Failed to get Chrome processes: ${scanErr instanceof Error ? scanErr.message : String(scanErr)}`, | |
| ); | |
| } | |
| if (lastLaunchCommand) { | |
| diagnostics._launch_command = lastLaunchCommand.substring(0, 500); | |
| } | |
| return diagnostics; | |
| } | |
| export interface LaunchOptions { | |
| /** The original code passes a boolean into the launch routine; used as focus hint. */ | |
| runInForeground?: boolean; | |
| } | |
| async function performLaunch(vscodeApi: typeof vscode, options?: LaunchOptions): Promise<string | null> { | |
| const output = BrowserLauncherOutput.get(vscodeApi); | |
| try { | |
| output.appendLine("Launching browser..."); | |
| const launchStartTime = Date.now(); | |
| await loadBrowserPreferences(vscodeApi); | |
| // If profile doesn't exist, show onboarding (non-blocking). | |
| if (!doesProfileExist()) { | |
| vscodeApi.commands.executeCommand( | |
| "antigravity.showBrowserOnboardingFullScreenView", | |
| ); | |
| } | |
| // If a browser is already running with CDP, we do not spawn a new one. | |
| const usingExistingBrowser = isChromeRunningWithCdp(vscodeApi); | |
| if (!usingExistingBrowser) { | |
| const chromePath = getChromeExecutablePath(); | |
| if (!chromePath) { | |
| const message = | |
| `Chrome installation not found. Please install Google Chrome at ${CHROME_DOWNLOAD_URL} ` + | |
| "or set a custom Chrome binary path in the browser section of the Antigravity user settings."; | |
| output.appendLine(message); | |
| void vscodeApi.window | |
| .showErrorMessage( | |
| "Chrome installation not detected and is required for browser agent features.", | |
| { modal: true }, | |
| { title: "Install Chrome" }, | |
| { title: "Set custom Chrome binary path" }, | |
| ) | |
| .then((choice) => { | |
| if (choice?.title === "Install Chrome") { | |
| vscodeApi.env.openExternal(vscodeApi.Uri.parse(CHROME_DOWNLOAD_URL)); | |
| } else if (choice?.title === "Set custom Chrome binary path") { | |
| vscodeApi.commands.executeCommand( | |
| "workbench.action.openAntigravitySettingsWithId", | |
| "browserChromePath", | |
| ); | |
| } | |
| }); | |
| throw new ChromeInstallationError(message); | |
| } | |
| const flags = getBrowserLaunchFlags(vscodeApi); | |
| const shouldFocus = options?.runInForeground ?? false; | |
| spawnBrowser(vscodeApi, chromePath, flags, shouldFocus).unref(); | |
| } | |
| // Setup reverse proxy if in Remote-SSH. | |
| startSshReverseProxyIfRemoteSsh(vscodeApi); | |
| // If we launched a new browser, wait a bit for it to become responsive. | |
| if (!usingExistingBrowser) { | |
| const port = getCdpPort(); | |
| await vscodeApi.window.withProgress( | |
| { | |
| location: vscodeApi.ProgressLocation.Notification, | |
| title: "Launching browser...", | |
| cancellable: true, | |
| }, | |
| async (_progress, token) => { | |
| const waitStart = Date.now(); | |
| while (!isCdpEndpointUp(port)) { | |
| if (Date.now() - waitStart > 20_000) { | |
| output.appendLine( | |
| "Timed out waiting for chrome with remote debugging port to be ready.", | |
| ); | |
| break; | |
| } | |
| if (token.isCancellationRequested) break; | |
| await wait(500); | |
| } | |
| }, | |
| ); | |
| void waitForBrowserReady(vscodeApi, port, launchStartTime, 5000); | |
| } | |
| const browserUrl = `http://127.0.0.1:${getCdpPort()}`; | |
| if (usingExistingBrowser) { | |
| if (hasDevExtension(vscodeApi)) { | |
| vscodeApi.window.showInformationMessage("Dev: Connected to existing browser."); | |
| } | |
| } else { | |
| vscodeApi.window.showInformationMessage("Successfully launched browser."); | |
| } | |
| return browserUrl; | |
| } catch (err) { | |
| const error = err instanceof Error ? err : new Error(String(err)); | |
| output.appendLine(`Failed to launch browser: ${error.message}`); | |
| const diagnostics = await collectLaunchFailureDiagnostics(vscodeApi, error); | |
| try { | |
| await vscodeApi.commands.executeCommand( | |
| "antigravity.logProductEvent", | |
| "BROWSER_LAUNCH_FAILURE_DIAGNOSTICS", | |
| 0n, | |
| false, | |
| diagnostics, | |
| ); | |
| } catch (uploadErr) { | |
| output.appendLine( | |
| `Failed to upload diagnostics: ${uploadErr instanceof Error ? uploadErr.message : String(uploadErr)}`, | |
| ); | |
| } | |
| if (error instanceof ChromeInstallationError) throw error; | |
| vscodeApi.window.showErrorMessage(`Failed to launch browser: ${error.message}`); | |
| return null; | |
| } | |
| } | |
| class BrowserLauncher { | |
| static instance: BrowserLauncher | null = null; | |
| private runningWarmup: Promise<void> | null = null; | |
| private runningLaunch: Promise<string | null> | null = null; | |
| constructor(private readonly vscodeApi: typeof vscode) {} | |
| launch(options?: LaunchOptions): Promise<string | null> { | |
| if (!this.runningLaunch) { | |
| this.runningLaunch = (async () => { | |
| if (this.runningWarmup) await this.runningWarmup; | |
| return performLaunch(this.vscodeApi, options); | |
| })(); | |
| this.runningLaunch.finally(() => { | |
| this.runningLaunch = null; | |
| }); | |
| } | |
| return this.runningLaunch; | |
| } | |
| warmUp(): Promise<void> { | |
| // Keep original behavior: warmup is only performed when not launching. | |
| if (this.runningLaunch) return Promise.resolve(); | |
| this.runningWarmup ||= (async () => { | |
| const output = BrowserLauncherOutput.get(this.vscodeApi); | |
| output.appendLine("Starting browser warmup..."); | |
| try { | |
| const chromePath = getChromeExecutablePath(); | |
| if (!chromePath) { | |
| output.appendLine("Chrome not found, skipping warmup."); | |
| return; | |
| } | |
| const profilePath = getProfilePath(); | |
| const warmupPort = 9223; | |
| const warmupArgs = [ | |
| `--user-data-dir=${profilePath}`, | |
| `--remote-debugging-port=${warmupPort}`, | |
| "--headless=new", | |
| "--disable-gpu", | |
| "--no-sandbox", | |
| "--disable-dev-shm-usage", | |
| "about:blank", | |
| ]; | |
| const start = Date.now(); | |
| const proc = spawnBrowser(this.vscodeApi, chromePath, warmupArgs, false); | |
| await waitForBrowserReady(this.vscodeApi, warmupPort, start, 5000); | |
| // macOS `open` returns a wrapper; original code kills by port. | |
| if (os.platform() === "darwin") { | |
| try { | |
| const pidStr = childProcess | |
| .execSync(`lsof -ti:${warmupPort}`, { | |
| encoding: "utf-8", | |
| stdio: ["pipe", "pipe", "ignore"], | |
| }) | |
| .trim(); | |
| if (pidStr) { | |
| const pid = parseInt(pidStr, 10); | |
| output.appendLine(`Killing process ${pid} on port ${warmupPort}`); | |
| process.kill(pid, "SIGTERM"); | |
| } | |
| } catch (killErr) { | |
| output.appendLine( | |
| `No process found on port ${warmupPort} or failed to kill: ${killErr instanceof Error ? killErr.message : String(killErr)}`, | |
| ); | |
| } | |
| } else { | |
| proc.kill(); | |
| } | |
| } catch (warmErr) { | |
| const error = warmErr instanceof Error ? warmErr : new Error(String(warmErr)); | |
| BrowserLauncherOutput.get(this.vscodeApi).appendLine(`Browser warmup failed: ${error.message}`); | |
| } | |
| })(); | |
| return this.runningWarmup; | |
| } | |
| } | |
| /** VS Code extension entry. */ | |
| export async function activate(context: vscode.ExtensionContext): Promise<void> { | |
| const vscodeApi = await import("vscode"); | |
| context.subscriptions.push( | |
| vscodeApi.commands.registerCommand("browserLauncher.warmUpBrowser", async () => { | |
| BrowserLauncher.instance ||= new BrowserLauncher(vscodeApi); | |
| return BrowserLauncher.instance.warmUp(); | |
| }), | |
| ); | |
| context.subscriptions.push( | |
| vscodeApi.commands.registerCommand("browserLauncher.launchBrowser", async (options?: LaunchOptions) => { | |
| BrowserLauncher.instance ||= new BrowserLauncher(vscodeApi); | |
| return BrowserLauncher.instance.launch(options); | |
| }), | |
| ); | |
| context.subscriptions.push( | |
| vscodeApi.commands.registerCommand("browserLauncher.resetBrowserOnboarding", async () => { | |
| const output = BrowserLauncherOutput.get(vscodeApi); | |
| const profilePath = getProfilePath(); | |
| try { | |
| if (fs.existsSync(profilePath)) { | |
| output.appendLine(`Deleting Chrome profile at: ${profilePath}`); | |
| await fs.promises.rm(profilePath, { recursive: true, force: true }); | |
| output.appendLine("Successfully deleted Chrome profile."); | |
| vscodeApi.window.showInformationMessage("Successfully deleted Chrome profile."); | |
| } else { | |
| output.appendLine(`Chrome profile not found at: ${profilePath}`); | |
| vscodeApi.window.showInformationMessage("Chrome profile not found."); | |
| } | |
| } catch (err) { | |
| const error = err instanceof Error ? err : new Error(String(err)); | |
| output.appendLine(`Failed to delete Chrome profile: ${error.message}`); | |
| vscodeApi.window.showErrorMessage(`Failed to delete Chrome profile: ${error.message}`); | |
| } | |
| try { | |
| await vscodeApi.commands.executeCommand("antigravity.resetOnboardingBackend"); | |
| vscodeApi.window.showInformationMessage("Successfully reset onboarding."); | |
| } catch (err) { | |
| const error = err instanceof Error ? err : new Error(String(err)); | |
| output.appendLine(`Failed to reset onboarding: ${error.message}`); | |
| vscodeApi.window.showErrorMessage(`Failed to reset onboarding: ${error.message}`); | |
| } | |
| setTimeout(() => { | |
| vscodeApi.commands.executeCommand("workbench.action.reloadWindow"); | |
| }, 1000); | |
| }), | |
| ); | |
| } | |
| export function deactivate(): void { | |
| // no-op (matches original) | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| * Human-readable extraction from Antigravity webview bundle: | |
| * resources/app/extensions/antigravity/out/media/chat.js | |
| * | |
| * Goal | |
| * - Document the “tool/step/interaction/config” vocabulary the chat UI uses, | |
| * especially browser automation via CDP + language server. | |
| * | |
| * Notes | |
| * - This file is not a generated protobuf binding. It is a readable mirror of | |
| * names/fields/enums observed in the bundle. | |
| * - Some nested message types are left as `unknown` where the bundle does not | |
| * show a stable/public shape. | |
| */ | |
| export const SOURCE = { | |
| bundlePath: | |
| 'resources/app/extensions/antigravity/out/media/chat.js' as const, | |
| } as const; | |
| /** Webview <-> extension message/event names related to browser preferences + allowlist. */ | |
| export const WEBVIEW_EVENTS = { | |
| REQUEST_BROWSER_PREFERENCES: 'request-browser-preferences', | |
| PROVIDE_BROWSER_PREFERENCES: 'provide-browser-preferences', | |
| REQUEST_OPEN_BROWSER_ALLOWLIST: 'request-open-browser-allowlist-function', | |
| } as const; | |
| export type WebviewEventName = | |
| (typeof WEBVIEW_EVENTS)[keyof typeof WEBVIEW_EVENTS]; | |
| /** | |
| * Browser JavaScript execution policy. | |
| * Source: exa.codeium_common_pb.BrowserJsExecutionPolicy | |
| */ | |
| export enum BrowserJsExecutionPolicy { | |
| UNSPECIFIED = 'UNSPECIFIED', | |
| DISABLED = 'DISABLED', | |
| ALWAYS_ASK = 'ALWAYS_ASK', | |
| MODEL_DECIDES = 'MODEL_DECIDES', | |
| TURBO = 'TURBO', | |
| } | |
| /** | |
| * Browser JS auto-run policy. | |
| * Source: exa.codeium_common_pb.BrowserJsAutoRunPolicy | |
| */ | |
| export enum BrowserJsAutoRunPolicy { | |
| UNSPECIFIED = 'UNSPECIFIED', | |
| DISABLED = 'DISABLED', | |
| MODEL_DECIDES = 'MODEL_DECIDES', | |
| ENABLED = 'ENABLED', | |
| } | |
| /** | |
| * Whether/when to use the browser subagent. | |
| * Source: exa.cortex_pb.BrowserSubagentMode | |
| */ | |
| export enum BrowserSubagentMode { | |
| UNSPECIFIED = 'UNSPECIFIED', | |
| MAIN_AGENT_ONLY = 'MAIN_AGENT_ONLY', | |
| SUBAGENT_PRIMARILY = 'SUBAGENT_PRIMARILY', | |
| BOTH_AGENTS = 'BOTH_AGENTS', | |
| SUBAGENT_ONLY = 'SUBAGENT_ONLY', | |
| } | |
| /** | |
| * Which browser tool set is enabled. | |
| * Source: exa.cortex_pb.BrowserToolSetMode | |
| */ | |
| export enum BrowserToolSetMode { | |
| UNSPECIFIED = 'UNSPECIFIED', | |
| ALL_TOOLS = 'ALL_TOOLS', | |
| PIXEL_ONLY = 'PIXEL_ONLY', | |
| ALL_INPUT_PIXEL_OUTPUT = 'ALL_INPUT_PIXEL_OUTPUT', | |
| } | |
| /** | |
| * Click feedback overlay type. | |
| * Source: exa.cortex_pb.ClickFeedbackConfig.FeedbackType | |
| */ | |
| export enum ClickFeedbackType { | |
| UNSPECIFIED = 'UNSPECIFIED', | |
| DOT = 'DOT', | |
| MOUSE_POINTER = 'MOUSE_POINTER', | |
| } | |
| /** Source: exa.cortex_pb.DOMExtractionConfig */ | |
| export interface DOMExtractionConfig { | |
| /** include_coordinates */ | |
| includeCoordinates?: boolean; | |
| } | |
| /** Source: exa.cortex_pb.BrowserWindowSize */ | |
| export interface BrowserWindowSize { | |
| /** width_px */ | |
| widthPx: number; | |
| /** height_px */ | |
| heightPx: number; | |
| } | |
| /** Source: exa.cortex_pb.CaptureBrowserScreenshotToolConfig */ | |
| export interface CaptureBrowserScreenshotToolConfig { | |
| /** enable_saving */ | |
| enableSaving?: boolean; | |
| } | |
| /** Source: exa.cortex_pb.ClickFeedbackConfig */ | |
| export interface ClickFeedbackConfig { | |
| enabled?: boolean; | |
| red?: number; | |
| green?: number; | |
| blue?: number; | |
| alpha?: number; | |
| /** display_color */ | |
| displayColor?: string; | |
| radius?: number; | |
| /** feedback_type */ | |
| feedbackType?: ClickFeedbackType; | |
| } | |
| /** Source: exa.cortex_pb.ClickBrowserPixelToolConfig */ | |
| export interface ClickBrowserPixelToolConfig { | |
| /** click_feedback */ | |
| clickFeedback?: ClickFeedbackConfig; | |
| } | |
| /** Source: exa.cortex_pb.BrowserStateDiffingConfig */ | |
| export interface BrowserStateDiffingConfig { | |
| /** capture_agent_action_diffs */ | |
| captureAgentActionDiffs?: boolean; | |
| /** include_dom_tree_in_diffs */ | |
| includeDomTreeInDiffs?: boolean; | |
| } | |
| /** Source: exa.cortex_pb.BrowserSubagentContextConfig.ContextType */ | |
| export enum BrowserSubagentContextType { | |
| UNSPECIFIED = 'UNSPECIFIED', | |
| WITH_MARKDOWN_TRAJECTORY_SUMMARY = 'WITH_MARKDOWN_TRAJECTORY_SUMMARY', | |
| TASK_ONLY = 'TASK_ONLY', | |
| } | |
| /** Source: exa.cortex_pb.BrowserSubagentContextConfig */ | |
| export interface BrowserSubagentContextConfig { | |
| type?: BrowserSubagentContextType; | |
| /** max_chars */ | |
| maxChars?: number; | |
| } | |
| /** | |
| * Source: exa.cortex_pb.SubagentReminderMode | |
| * Bundle shows it as a `oneof`; keep it loose. | |
| */ | |
| export type SubagentReminderMode = | |
| | { case: string; value: unknown } | |
| | { case?: undefined; value?: undefined }; | |
| /** Source: exa.cortex_pb.BrowserSubagentToolConfig */ | |
| export interface BrowserSubagentToolConfig { | |
| mode?: BrowserSubagentMode; | |
| /** browser_subagent_model (shares enum with other model selectors; left as unknown) */ | |
| browserSubagentModel?: unknown; | |
| /** use_detailed_converter */ | |
| useDetailedConverter?: boolean; | |
| /** suggested_max_tool_calls */ | |
| suggestedMaxToolCalls?: number; | |
| /** disable_onboarding */ | |
| disableOnboarding?: boolean; | |
| /** subagent_reminder_mode */ | |
| subagentReminderMode?: SubagentReminderMode; | |
| /** max_context_tokens */ | |
| maxContextTokens?: number; | |
| /** context_config */ | |
| contextConfig?: BrowserSubagentContextConfig; | |
| /** dom_extraction_config */ | |
| domExtractionConfig?: DOMExtractionConfig; | |
| } | |
| /** Source: exa.cortex_pb.AntigravityBrowserToolConfig */ | |
| export interface AntigravityBrowserToolConfig { | |
| enabled?: boolean; | |
| /** auto_run_decision (enum appears elsewhere as mJ; kept as unknown) */ | |
| autoRunDecision?: unknown; | |
| captureBrowserScreenshot?: CaptureBrowserScreenshotToolConfig; | |
| browserSubagent?: BrowserSubagentToolConfig; | |
| clickBrowserPixel?: ClickBrowserPixelToolConfig; | |
| browserStateDiffingConfig?: BrowserStateDiffingConfig; | |
| toolSetMode?: BrowserToolSetMode; | |
| disableOpenUrl?: boolean; | |
| isEvalMode?: boolean; | |
| browserJsExecutionPolicy?: BrowserJsExecutionPolicy; | |
| disableActuationOverlay?: boolean; | |
| /** variable_wait_tool */ | |
| variableWaitTool?: boolean; | |
| browserJsAutoRunPolicy?: BrowserJsAutoRunPolicy; | |
| initialBrowserWindowSize?: BrowserWindowSize; | |
| domExtractionConfig?: DOMExtractionConfig; | |
| } | |
| /** | |
| * MCP (Model Context Protocol) pieces observed in the bundle. | |
| * | |
| * Notes | |
| * - MCP “tools” are executed via the language server; the chat UI renders a step case `mcpTool`. | |
| * - Some MCP configuration is present in `CascadeToolConfig.mcp`. | |
| */ | |
| /** Source: exa.cortex_pb.McpServerStatus */ | |
| export enum McpServerStatus { | |
| UNSPECIFIED = 'UNSPECIFIED', | |
| PENDING = 'PENDING', | |
| READY = 'READY', | |
| ERROR = 'ERROR', | |
| } | |
| /** Source: exa.cortex_pb.McpToolConfig */ | |
| export interface McpToolConfig { | |
| /** force_disable */ | |
| forceDisable?: boolean; | |
| /** max_output_bytes */ | |
| maxOutputBytes: number; | |
| } | |
| /** Source: exa.cortex_pb.McpServerSpec */ | |
| export interface McpServerSpec { | |
| /** server_name */ | |
| serverName: string; | |
| /** command */ | |
| command: string; | |
| /** args */ | |
| args: string[]; | |
| /** env */ | |
| env: Record<string, string>; | |
| /** server_url */ | |
| serverUrl: string; | |
| /** disabled */ | |
| disabled: boolean; | |
| /** disabled_tools */ | |
| disabledTools: string[]; | |
| /** headers */ | |
| headers: Record<string, string>; | |
| /** server_index */ | |
| serverIndex: number; | |
| } | |
| /** Source: exa.cortex_pb.McpServerInfo */ | |
| export interface McpServerInfo { | |
| name: string; | |
| version: string; | |
| } | |
| /** Source: exa.cortex_pb.McpServerState */ | |
| export interface McpServerState { | |
| spec?: McpServerSpec; | |
| status?: McpServerStatus; | |
| error?: string; | |
| /** tools (shape not stable in UI bundle; left unknown) */ | |
| tools?: unknown[]; | |
| /** tool_errors */ | |
| toolErrors?: string[]; | |
| serverInfo?: McpServerInfo; | |
| instructions?: string; | |
| } | |
| /** Source: exa.codeium_common_pb.McpResourceItem */ | |
| export interface McpResourceItem { | |
| uri: string; | |
| name: string; | |
| description?: string; | |
| mimeType?: string; | |
| serverName: string; | |
| } | |
| /** Source: exa.cortex_pb.McpResource */ | |
| export interface McpResource { | |
| uri: string; | |
| name: string; | |
| description: string; | |
| mimeType: string; | |
| } | |
| /** Source: exa.cortex_pb.McpResourceContent */ | |
| export type McpResourceContent = | |
| | { uri: string; text: { text: string } } | |
| | { uri: string; image: unknown } | |
| | { uri: string; mediaContent: unknown } | |
| | { uri: string; data?: { case?: undefined; value?: undefined } }; | |
| /** | |
| * Language server RPCs related to MCP (visible in the webview bundle). | |
| * These names are helpful when tracing UI -> language server call chains. | |
| */ | |
| export const MCP_LANGUAGE_SERVER_RPCS = { | |
| refreshMcpServers: 'RefreshMcpServers', | |
| getMcpServerStates: 'GetMcpServerStates', | |
| listMcpResources: 'ListMcpResources', | |
| } as const; | |
| /** | |
| * Language Server (LS) overview (as seen from the chat webview) | |
| * | |
| * In this Antigravity build, the chat webview talks to a “language server client” via RPC. | |
| * The bundle shows direct calls like: | |
| * - `t.openUrl(new OpenUrlRequest({ url }))` | |
| * - `t.listPages(new ListPagesRequest({}))` | |
| * - `t.captureScreenshot(new CaptureScreenshotRequest({ pageId }))` | |
| * - `t.captureConsoleLogs(new CaptureConsoleLogsRequest({ pageId }))` | |
| * and throws `Error("Language server client not available")` when the client is missing. | |
| * | |
| * Practical meaning: | |
| * - Browser actions in the UI are NOT implemented purely in the webview; they are executed | |
| * by the backend (language server) through RPC. | |
| * - The language server likely owns the CDP connection + automation plumbing; the UI only | |
| * requests operations and renders results/steps. | |
| */ | |
| /** Browser-related LS RPC names observed in the service definition inside chat.js. */ | |
| export const LANGUAGE_SERVER_BROWSER_RPCS = { | |
| listPages: 'ListPages', | |
| openUrl: 'OpenUrl', | |
| focusUserPage: 'FocusUserPage', | |
| captureScreenshot: 'CaptureScreenshot', | |
| captureConsoleLogs: 'CaptureConsoleLogs', | |
| /** allowlist / whitelist management */ | |
| addToBrowserWhitelist: 'AddToBrowserWhitelist', | |
| getBrowserWhitelistFilePath: 'GetBrowserWhitelistFilePath', | |
| getAllBrowserWhitelistedUrls: 'GetAllBrowserWhitelistedUrls', | |
| /** onboarding / orchestration */ | |
| smartOpenBrowser: 'SmartOpenBrowser', | |
| browserValidateCascadeOrCancelOverlay: 'BrowserValidateCascadeOrCancelOverlay', | |
| setBrowserOpenConversation: 'SetBrowserOpenConversation', | |
| getBrowserOpenConversation: 'GetBrowserOpenConversation', | |
| /** generic webview <- LS signaling */ | |
| sendActionToChatPanel: 'SendActionToChatPanel', | |
| } as const; | |
| export type LanguageServerBrowserRpcName = | |
| (typeof LANGUAGE_SERVER_BROWSER_RPCS)[keyof typeof LANGUAGE_SERVER_BROWSER_RPCS]; | |
| /** | |
| * A combined “RPC name” union useful for tracing. | |
| * Note: this is not exhaustive for all LS methods, only the ones relevant to MCP + browser. | |
| */ | |
| export type LanguageServerRpcName = | |
| | LanguageServerBrowserRpcName | |
| | (typeof MCP_LANGUAGE_SERVER_RPCS)[keyof typeof MCP_LANGUAGE_SERVER_RPCS]; | |
| /** exa.language_server_pb.ListPagesRequest */ | |
| export interface ListPagesRequest {} | |
| /** exa.language_server_pb.ListPagesResponse (page payload type is not expanded here) */ | |
| export interface ListPagesResponse { | |
| pages: unknown[]; | |
| } | |
| /** exa.language_server_pb.OpenUrlRequest */ | |
| export interface OpenUrlRequest { | |
| url: string; | |
| } | |
| /** exa.language_server_pb.OpenUrlResponse */ | |
| export interface OpenUrlResponse {} | |
| /** exa.language_server_pb.FocusUserPageRequest */ | |
| export interface FocusUserPageRequest { | |
| pageId: string; | |
| } | |
| /** exa.language_server_pb.FocusUserPageResponse */ | |
| export interface FocusUserPageResponse {} | |
| /** exa.language_server_pb.CaptureScreenshotRequest */ | |
| export interface CaptureScreenshotRequest { | |
| pageId: string; | |
| } | |
| /** exa.language_server_pb.CaptureScreenshotResponse */ | |
| export interface CaptureScreenshotResponse { | |
| screenshot?: unknown; | |
| mediaScreenshot?: unknown; | |
| } | |
| /** exa.language_server_pb.CaptureConsoleLogsRequest */ | |
| export interface CaptureConsoleLogsRequest { | |
| pageId: string; | |
| } | |
| /** exa.language_server_pb.CaptureConsoleLogsResponse */ | |
| export interface CaptureConsoleLogsResponse { | |
| consoleLogs?: unknown; | |
| } | |
| /** exa.language_server_pb.AddToBrowserWhitelistRequest */ | |
| export interface AddToBrowserWhitelistRequest { | |
| hostname?: string; | |
| hostnames?: string[]; | |
| } | |
| /** exa.language_server_pb.AddToBrowserWhitelistResponse */ | |
| export interface AddToBrowserWhitelistResponse {} | |
| /** exa.language_server_pb.GetBrowserWhitelistFilePathRequest */ | |
| export interface GetBrowserWhitelistFilePathRequest {} | |
| /** exa.language_server_pb.GetBrowserWhitelistFilePathResponse */ | |
| export interface GetBrowserWhitelistFilePathResponse { | |
| path: string; | |
| } | |
| /** exa.language_server_pb.GetAllBrowserWhitelistedUrlsRequest */ | |
| export interface GetAllBrowserWhitelistedUrlsRequest {} | |
| /** exa.language_server_pb.GetAllBrowserWhitelistedUrlsResponse */ | |
| export interface GetAllBrowserWhitelistedUrlsResponse { | |
| whitelistedUrls: string[]; | |
| } | |
| /** exa.language_server_pb.SmartOpenBrowserRequest */ | |
| export interface SmartOpenBrowserRequest { | |
| url: string; | |
| isOnboarded: boolean; | |
| } | |
| /** exa.language_server_pb.SmartOpenBrowserResponse */ | |
| export interface SmartOpenBrowserResponse {} | |
| /** exa.language_server_pb.BrowserValidateCascadeOrCancelOverlayRequest */ | |
| export interface BrowserValidateCascadeOrCancelOverlayRequest { | |
| cascadeId: string; | |
| } | |
| /** exa.language_server_pb.BrowserValidateCascadeOrCancelOverlayResponse */ | |
| export interface BrowserValidateCascadeOrCancelOverlayResponse {} | |
| /** exa.language_server_pb.SetBrowserOpenConversationRequest */ | |
| export interface SetBrowserOpenConversationRequest { | |
| cascadeId: string; | |
| /** timestamp-like proto message; left unknown */ | |
| expiresAt?: unknown; | |
| } | |
| /** exa.language_server_pb.SetBrowserOpenConversationResponse */ | |
| export interface SetBrowserOpenConversationResponse {} | |
| /** exa.language_server_pb.SendActionToChatPanelRequest */ | |
| export interface SendActionToChatPanelRequest { | |
| actionType: string; | |
| /** payload is bytes[] in proto; in JS it appears as Uint8Array-like */ | |
| payload: unknown[]; | |
| } | |
| /** exa.language_server_pb.SendActionToChatPanelResponse */ | |
| export interface SendActionToChatPanelResponse {} | |
| /** | |
| * RequestedInteraction (oneof) cases the UI handles. | |
| * Source: exa.cortex_pb.RequestedInteraction (subset) | |
| */ | |
| export type RequestedInteractionCase = | |
| | 'openBrowserUrl' | |
| | 'executeBrowserJavascript' | |
| | 'captureBrowserScreenshot' | |
| | 'clickBrowserPixel' | |
| | 'browserAction' | |
| | 'openBrowserSetup' | |
| | 'confirmBrowserSetup' | |
| | 'runCommand' | |
| | 'runExtensionCode' | |
| | 'sendCommandInput' | |
| | 'readUrlContent' | |
| | 'mcp' | |
| | (string & {}); | |
| /** | |
| * CortexStep UI “case” strings found in the webview renderer registry. | |
| * These are the discriminants used by `step.step.case` in the UI. | |
| */ | |
| export type StepCase = | |
| // Browser tools | |
| | 'openBrowserUrl' | |
| | 'executeBrowserJavascript' | |
| | 'listBrowserPages' | |
| | 'captureBrowserScreenshot' | |
| | 'captureBrowserConsoleLogs' | |
| | 'clickBrowserPixel' | |
| | 'readBrowserPage' | |
| | 'browserGetDom' | |
| | 'browserMoveMouse' | |
| | 'browserInput' | |
| | 'browserSelectOption' | |
| | 'browserScroll' | |
| | 'browserScrollUp' | |
| | 'browserScrollDown' | |
| | 'browserResizeWindow' | |
| | 'browserDragPixelToPixel' | |
| | 'browserMouseWheel' | |
| | 'browserPressKey' | |
| | 'browserClickElement' | |
| // Browser subagent | |
| | 'browserSubagent' | |
| // MCP tools/resources | |
| | 'mcpTool' | |
| | 'listResources' | |
| | 'readResource' | |
| // Other non-browser steps (partial; UI has many) | |
| | 'runCommand' | |
| | 'runExtensionCode' | |
| | 'searchWeb' | |
| | 'readUrlContent' | |
| | 'plannerResponse' | |
| | 'notifyUser' | |
| | 'ephemeralMessage' | |
| | 'errorMessage' | |
| | 'userInput' | |
| | 'finish' | |
| | (string & {}); | |
| /** Source: exa.cortex_pb.CortexStepOpenBrowserUrl */ | |
| export interface CortexStepOpenBrowserUrl { | |
| url: string; | |
| pageIdToReplace?: string; | |
| autoRunDecision?: unknown; | |
| userRejected?: boolean; | |
| pageId?: string; | |
| webDocument?: unknown; | |
| pageMetadata?: unknown; | |
| screenshot?: unknown; | |
| mediaScreenshot?: unknown; | |
| browserStateDiff?: string; | |
| } | |
| /** Source: exa.cortex_pb.CortexStepExecuteBrowserJavaScript */ | |
| export interface CortexStepExecuteBrowserJavaScript { | |
| title?: string; | |
| pageId: string; | |
| javascriptSource: string; | |
| javascriptDescription?: string; | |
| shouldAutoRun?: boolean; | |
| userRejected?: boolean; | |
| screenshotEnd?: unknown; | |
| mediaScreenshotEnd?: unknown; | |
| pageMetadata?: unknown; | |
| executionDurationMs?: unknown; | |
| javascriptResult?: string; | |
| browserStateDiff?: string; | |
| } | |
| /** Source: exa.cortex_pb.CortexStepCaptureBrowserScreenshot */ | |
| export interface CortexStepCaptureBrowserScreenshot { | |
| pageId: string; | |
| saveScreenshot?: boolean; | |
| screenshotName?: string; | |
| captureByElementIndex?: boolean; | |
| elementIndex?: number; | |
| userRejected?: boolean; | |
| screenshot?: unknown; | |
| mediaScreenshot?: unknown; | |
| pageMetadata?: unknown; | |
| autoRunDecision?: unknown; | |
| } | |
| /** Source: exa.cortex_pb.CortexStepClickBrowserPixel */ | |
| export interface CortexStepClickBrowserPixel { | |
| pageId: string; | |
| x: number; | |
| y: number; | |
| clickType?: unknown; | |
| userRejected?: boolean; | |
| pageMetadata?: unknown; | |
| screenshotWithClickFeedback?: unknown; | |
| browserStateDiff?: string; | |
| } | |
| /** Source: exa.cortex_pb.CortexStepReadBrowserPage */ | |
| export interface CortexStepReadBrowserPage { | |
| pageId: string; | |
| webDocument?: unknown; | |
| pageMetadata?: unknown; | |
| } | |
| /** Source: exa.cortex_pb.CortexStepBrowserGetDom */ | |
| export interface CortexStepBrowserGetDom { | |
| pageId: string; | |
| domTree?: unknown; | |
| serializedDomTree?: string; | |
| pageMetadata?: unknown; | |
| } | |
| /** Source: exa.cortex_pb.CortexStepListBrowserPages */ | |
| export interface CortexStepListBrowserPages { | |
| pages: unknown[]; | |
| } | |
| /** | |
| * Union of browser-related step payloads. | |
| * This mirrors `step.step.case` / `step.step.value` shape used in the UI. | |
| */ | |
| export type BrowserStep = | |
| | { case: 'openBrowserUrl'; value: CortexStepOpenBrowserUrl } | |
| | { case: 'executeBrowserJavascript'; value: CortexStepExecuteBrowserJavaScript } | |
| | { case: 'captureBrowserScreenshot'; value: CortexStepCaptureBrowserScreenshot } | |
| | { case: 'clickBrowserPixel'; value: CortexStepClickBrowserPixel } | |
| | { case: 'readBrowserPage'; value: CortexStepReadBrowserPage } | |
| | { case: 'browserGetDom'; value: CortexStepBrowserGetDom } | |
| | { case: 'listBrowserPages'; value: CortexStepListBrowserPages } | |
| | { case: 'captureBrowserConsoleLogs'; value: { pageId: string } } | |
| | { case: 'browserAction'; value: unknown } | |
| | { case: 'browserSubagent'; value: unknown }; | |
| export type StepCategory = | |
| | 'browser' | |
| | 'browser-input' | |
| | 'browser-output' | |
| | 'browser-subagent' | |
| | 'mcp' | |
| | 'code' | |
| | 'terminal' | |
| | 'web' | |
| | 'system' | |
| | 'other'; | |
| /** Source: exa.cortex_pb.CortexStepMcpTool */ | |
| export interface CortexStepMcpTool { | |
| serverName: string; | |
| /** tool_call (contains name + argumentsJson; exact shape varies; kept loose) */ | |
| toolCall?: { | |
| name?: string; | |
| argumentsJson?: string; | |
| [k: string]: unknown; | |
| }; | |
| serverInfo?: McpServerInfo; | |
| resultString?: string; | |
| images: unknown[]; | |
| media: unknown[]; | |
| userRejected?: boolean; | |
| } | |
| /** Source: exa.cortex_pb.CortexStepListResources */ | |
| export interface CortexStepListResources { | |
| serverName?: string; | |
| resources: McpResourceItem[]; | |
| } | |
| /** Source: exa.cortex_pb.CortexStepReadResource */ | |
| export interface CortexStepReadResource { | |
| uri: string; | |
| content?: McpResourceContent; | |
| } | |
| export interface StepUiSpec { | |
| /** UI discriminant (step.step.case) */ | |
| stepCase: StepCase; | |
| /** Rough grouping for humans */ | |
| category: StepCategory; | |
| /** Human-friendly title */ | |
| title: string; | |
| /** Whether the UI marks it as a tool (isTool: true in renderer registry) */ | |
| isTool?: boolean; | |
| /** If present, which RequestedInteraction.case is used for accept/reject UI */ | |
| requestedInteractionCase?: RequestedInteractionCase; | |
| /** Security sensitive (e.g., allowlist / JS execution) */ | |
| sensitive?: boolean; | |
| /** Short description of typical args */ | |
| argsHint?: string; | |
| } | |
| /** | |
| * High-value subset: browser-related steps and the UI behavior around them. | |
| * (Not an exhaustive list of all CortexStepType values.) | |
| */ | |
| export const STEP_UI: Record<string, StepUiSpec> = { | |
| // MCP | |
| mcpTool: { | |
| stepCase: 'mcpTool', | |
| category: 'mcp', | |
| title: '运行 MCP 工具调用', | |
| isTool: true, | |
| requestedInteractionCase: 'mcp', | |
| sensitive: true, | |
| argsHint: '{ serverName: string, toolCall: { name: string, argumentsJson?: string } }', | |
| }, | |
| listResources: { | |
| stepCase: 'listResources', | |
| category: 'mcp', | |
| title: '列出 MCP 资源', | |
| isTool: true, | |
| sensitive: false, | |
| argsHint: '{ serverName?: string, query?: string }', | |
| }, | |
| readResource: { | |
| stepCase: 'readResource', | |
| category: 'mcp', | |
| title: '读取 MCP 资源内容', | |
| isTool: true, | |
| sensitive: false, | |
| argsHint: '{ uri: string }', | |
| }, | |
| openBrowserUrl: { | |
| stepCase: 'openBrowserUrl', | |
| category: 'browser', | |
| title: '在内置浏览器打开 URL', | |
| isTool: true, | |
| requestedInteractionCase: 'openBrowserUrl', | |
| sensitive: true, | |
| argsHint: '{ url: string, pageIdToReplace?: string }', | |
| }, | |
| executeBrowserJavascript: { | |
| stepCase: 'executeBrowserJavascript', | |
| category: 'browser', | |
| title: '在内置浏览器执行 JavaScript', | |
| isTool: true, | |
| requestedInteractionCase: 'executeBrowserJavascript', | |
| sensitive: true, | |
| argsHint: '{ pageId: string, javascriptSource: string, javascriptDescription?: string }', | |
| }, | |
| captureBrowserScreenshot: { | |
| stepCase: 'captureBrowserScreenshot', | |
| category: 'browser-output', | |
| title: '捕获浏览器截图', | |
| isTool: true, | |
| requestedInteractionCase: 'captureBrowserScreenshot', | |
| sensitive: false, | |
| argsHint: '{ pageId: string, saveScreenshot?: boolean, screenshotName?: string }', | |
| }, | |
| clickBrowserPixel: { | |
| stepCase: 'clickBrowserPixel', | |
| category: 'browser-input', | |
| title: '按坐标点击(像素级)', | |
| isTool: true, | |
| requestedInteractionCase: 'clickBrowserPixel', | |
| sensitive: true, | |
| argsHint: '{ pageId: string, x: number, y: number, clickType?: enum }', | |
| }, | |
| listBrowserPages: { | |
| stepCase: 'listBrowserPages', | |
| category: 'browser', | |
| title: '列出当前浏览器页面', | |
| isTool: true, | |
| argsHint: '{ }', | |
| }, | |
| readBrowserPage: { | |
| stepCase: 'readBrowserPage', | |
| category: 'browser', | |
| title: '读取浏览器页面内容(抽取为文档)', | |
| isTool: true, | |
| argsHint: '{ pageId: string }', | |
| }, | |
| browserGetDom: { | |
| stepCase: 'browserGetDom', | |
| category: 'browser', | |
| title: '提取页面 DOM(结构化)', | |
| isTool: true, | |
| argsHint: '{ pageId: string }', | |
| }, | |
| captureBrowserConsoleLogs: { | |
| stepCase: 'captureBrowserConsoleLogs', | |
| category: 'browser-output', | |
| title: '抓取浏览器 Console Logs', | |
| isTool: true, | |
| argsHint: '{ pageId: string }', | |
| }, | |
| // The following are rendered as tools in the UI registry (pixel/input style) | |
| browserMoveMouse: { | |
| stepCase: 'browserMoveMouse', | |
| category: 'browser-input', | |
| title: '移动鼠标', | |
| isTool: true, | |
| sensitive: true, | |
| }, | |
| browserInput: { | |
| stepCase: 'browserInput', | |
| category: 'browser-input', | |
| title: '输入文本', | |
| isTool: true, | |
| sensitive: true, | |
| }, | |
| browserSelectOption: { | |
| stepCase: 'browserSelectOption', | |
| category: 'browser-input', | |
| title: '选择下拉选项', | |
| isTool: true, | |
| sensitive: true, | |
| }, | |
| browserScroll: { | |
| stepCase: 'browserScroll', | |
| category: 'browser-input', | |
| title: '滚动(通用)', | |
| isTool: true, | |
| sensitive: true, | |
| }, | |
| browserScrollUp: { | |
| stepCase: 'browserScrollUp', | |
| category: 'browser-input', | |
| title: '向上滚动', | |
| isTool: true, | |
| sensitive: true, | |
| }, | |
| browserScrollDown: { | |
| stepCase: 'browserScrollDown', | |
| category: 'browser-input', | |
| title: '向下滚动', | |
| isTool: true, | |
| sensitive: true, | |
| }, | |
| browserResizeWindow: { | |
| stepCase: 'browserResizeWindow', | |
| category: 'browser-input', | |
| title: '调整浏览器窗口大小', | |
| isTool: true, | |
| sensitive: true, | |
| }, | |
| browserDragPixelToPixel: { | |
| stepCase: 'browserDragPixelToPixel', | |
| category: 'browser-input', | |
| title: '拖拽(像素到像素)', | |
| isTool: true, | |
| sensitive: true, | |
| }, | |
| browserMouseWheel: { | |
| stepCase: 'browserMouseWheel', | |
| category: 'browser-input', | |
| title: '鼠标滚轮', | |
| isTool: true, | |
| sensitive: true, | |
| }, | |
| browserSubagent: { | |
| stepCase: 'browserSubagent', | |
| category: 'browser-subagent', | |
| title: '浏览器子代理(多步浏览器操作)', | |
| isTool: false, | |
| sensitive: true, | |
| }, | |
| } as const; | |
| export function formatMcpServerStatus(status?: McpServerStatus): string { | |
| switch (status) { | |
| case McpServerStatus.PENDING: | |
| return 'pending'; | |
| case McpServerStatus.READY: | |
| return 'ready'; | |
| case McpServerStatus.ERROR: | |
| return 'error'; | |
| default: | |
| return 'unspecified'; | |
| } | |
| } | |
| export function formatBrowserToolSetMode(mode?: BrowserToolSetMode): string { | |
| switch (mode) { | |
| case BrowserToolSetMode.ALL_TOOLS: | |
| return 'all_tools'; | |
| case BrowserToolSetMode.PIXEL_ONLY: | |
| return 'pixel_only'; | |
| case BrowserToolSetMode.ALL_INPUT_PIXEL_OUTPUT: | |
| return 'all_input_pixel_output'; | |
| default: | |
| return 'default'; | |
| } | |
| } | |
| export function formatBrowserSubagentMode(mode?: BrowserSubagentMode): string { | |
| switch (mode) { | |
| case BrowserSubagentMode.SUBAGENT_ONLY: | |
| return 'subagent_only'; | |
| case BrowserSubagentMode.MAIN_AGENT_ONLY: | |
| return 'main_only'; | |
| case BrowserSubagentMode.BOTH_AGENTS: | |
| return 'both'; | |
| case BrowserSubagentMode.SUBAGENT_PRIMARILY: | |
| return 'subagent_primarily'; | |
| default: | |
| return 'default'; | |
| } | |
| } | |
| export function formatBrowserSubagentContextType( | |
| type?: BrowserSubagentContextType, | |
| ): string { | |
| switch (type) { | |
| case BrowserSubagentContextType.WITH_MARKDOWN_TRAJECTORY_SUMMARY: | |
| return 'with_markdown_trajectory_summary'; | |
| case BrowserSubagentContextType.TASK_ONLY: | |
| return 'task_only'; | |
| default: | |
| return 'default'; | |
| } | |
| } | |
| export function formatBrowserJsExecutionPolicy( | |
| policy?: BrowserJsExecutionPolicy, | |
| ): string { | |
| switch (policy) { | |
| case BrowserJsExecutionPolicy.DISABLED: | |
| return 'disabled'; | |
| case BrowserJsExecutionPolicy.ALWAYS_ASK: | |
| return 'always_ask'; | |
| case BrowserJsExecutionPolicy.MODEL_DECIDES: | |
| return 'model_decides'; | |
| case BrowserJsExecutionPolicy.TURBO: | |
| return 'turbo'; | |
| default: | |
| return 'unspecified'; | |
| } | |
| } | |
| export function formatBrowserJsAutoRunPolicy(policy?: BrowserJsAutoRunPolicy): string { | |
| switch (policy) { | |
| case BrowserJsAutoRunPolicy.DISABLED: | |
| return 'disabled'; | |
| case BrowserJsAutoRunPolicy.MODEL_DECIDES: | |
| return 'model_decides'; | |
| case BrowserJsAutoRunPolicy.ENABLED: | |
| return 'enabled'; | |
| default: | |
| return 'unspecified'; | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment