import { loadConfig } from "../config/config.js"; import { createSubsystemLogger } from "../logging/subsystem.js"; import { resolveBrowserConfig, resolveProfile } from "./config.js"; import { ensureChromeExtensionRelayServer } from "./extension-relay.js"; import { type BrowserServerState, createBrowserRouteContext } from "./server-context.js"; let state: BrowserServerState | null = null; const log = createSubsystemLogger("browser"); const logService = log.child("service"); export function getBrowserControlState(): BrowserServerState | null { return state; } export function createBrowserControlContext() { return createBrowserRouteContext({ getState: () => state, }); } export async function startBrowserControlServiceFromConfig(): Promise { if (state) { return state; } const cfg = loadConfig(); const resolved = resolveBrowserConfig(cfg.browser, cfg); if (!resolved.enabled) { return null; } state = { server: null, port: resolved.controlPort, resolved, profiles: new Map(), }; // If any profile uses the Chrome extension relay, start the local relay server eagerly // so the extension can connect before the first browser action. for (const name of Object.keys(resolved.profiles)) { const profile = resolveProfile(resolved, name); if (!profile || profile.driver !== "extension") { continue; } await ensureChromeExtensionRelayServer({ cdpUrl: profile.cdpUrl }).catch((err) => { logService.warn(`Chrome extension relay init failed for profile "${name}": ${String(err)}`); }); } logService.info( `Browser control service ready (profiles=${Object.keys(resolved.profiles).length})`, ); return state; } export async function stopBrowserControlService(): Promise { const current = state; if (!current) { return; } const ctx = createBrowserRouteContext({ getState: () => state, }); try { for (const name of Object.keys(current.resolved.profiles)) { try { await ctx.forProfile(name).stopRunningBrowser(); } catch { // ignore } } } catch (err) { logService.warn(`openclaw browser stop failed: ${String(err)}`); } state = null; // Optional: Playwright is not always available (e.g. embedded gateway builds). try { const mod = await import("./pw-ai.js"); await mod.closePlaywrightBrowserConnection(); } catch { // ignore } }