UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

205 lines (204 loc) 12.4 kB
import { c as normalizeOptionalString } from "../../string-coerce-mnp54Vah.js"; import { n as asNullableRecord } from "../../record-coerce-DHZ4bFlT.js"; import { t as formatCliCommand } from "../../command-format-CKGmlpAQ.js"; import { p as resolveUserPath, t as CONFIG_DIR } from "../../utils-CCC-BEJH.js"; import "../../string-coerce-runtime-CEGJWkQ_.js"; import { t as note } from "../../note-BRSJp0UF.js"; import { q as DEFAULT_OPENCLAW_BROWSER_PROFILE_NAME } from "../../cdp.helpers-CTQ-1blZ.js"; import { n as resolveBrowserConfig } from "../../config-C2rsUqn7.js"; import { i as resolveGoogleChromeExecutableForPlatform, n as readBrowserVersion, r as resolveBrowserExecutableForPlatform, t as parseBrowserMajorVersion } from "../../chrome.executables-BGN3WFpZ.js"; import { t as movePathToTrash } from "../../trash-UCE9Bn4F.js"; import "../../record-shared-BdOR6CcJ.js"; import "../../sdk-setup-tools-Bm-WxrMN.js"; import fs from "node:fs"; import path from "node:path"; //#region extensions/browser/src/doctor-browser.ts /** * Browser doctor checks for Chrome MCP readiness and legacy managed-profile * residue cleanup. */ const CHROME_MCP_MIN_MAJOR = 144; const LEGACY_CLAWD_BROWSER_PROFILE_NAME = "clawd"; const REMOTE_DEBUGGING_PAGES = [ "chrome://inspect/#remote-debugging", "brave://inspect/#remote-debugging", "edge://inspect/#remote-debugging" ].join(", "); function collectChromeMcpProfiles(cfg) { const browser = asNullableRecord(cfg.browser); if (!browser) return []; const profiles = /* @__PURE__ */ new Map(); if ((normalizeOptionalString(browser.defaultProfile) ?? "") === "user") profiles.set("user", { name: "user" }); const configuredProfiles = asNullableRecord(browser.profiles); if (!configuredProfiles) return [...profiles.values()].toSorted((a, b) => a.name.localeCompare(b.name)); for (const [profileName, rawProfile] of Object.entries(configuredProfiles)) { const profile = asNullableRecord(rawProfile); if ((normalizeOptionalString(profile?.driver) ?? "") === "existing-session") profiles.set(profileName, { name: profileName, userDataDir: normalizeOptionalString(profile?.userDataDir) }); } return [...profiles.values()].toSorted((a, b) => a.name.localeCompare(b.name)); } function collectManagedProfiles(cfg) { const browser = asNullableRecord(cfg.browser); if (!browser) return []; const profiles = /* @__PURE__ */ new Map(); const defaultProfile = normalizeOptionalString(browser.defaultProfile) ?? ""; if (defaultProfile && defaultProfile !== "user") profiles.set(defaultProfile, { name: defaultProfile }); const configuredProfiles = asNullableRecord(browser.profiles); if (!configuredProfiles) return [...profiles.values()].toSorted((a, b) => a.name.localeCompare(b.name)); for (const [profileName, rawProfile] of Object.entries(configuredProfiles)) if ((normalizeOptionalString(asNullableRecord(rawProfile)?.driver) ?? "openclaw") !== "existing-session") profiles.set(profileName, { name: profileName }); return [...profiles.values()].toSorted((a, b) => a.name.localeCompare(b.name)); } function resolveManagedBrowserProfileDir(configDir, profileName) { return path.join(configDir, "browser", profileName); } function resolveManagedBrowserUserDataDir(configDir, profileName) { return path.join(resolveManagedBrowserProfileDir(configDir, profileName), "user-data"); } function normalizeComparablePath(targetPath) { return path.resolve(targetPath); } function isSameOrChildPath(candidatePath, parentPath) { const candidate = normalizeComparablePath(candidatePath); const parent = normalizeComparablePath(parentPath); return candidate === parent || candidate.startsWith(`${parent}${path.sep}`); } function isLegacyClawdProfileConfigured(cfg, legacyProfileDir) { const browser = asNullableRecord(cfg.browser); if (!browser) return false; if (normalizeOptionalString(browser.defaultProfile) === LEGACY_CLAWD_BROWSER_PROFILE_NAME) return true; const configuredProfiles = asNullableRecord(browser.profiles); if (!configuredProfiles) return false; if (Object.hasOwn(configuredProfiles, LEGACY_CLAWD_BROWSER_PROFILE_NAME)) return true; for (const rawProfile of Object.values(configuredProfiles)) { const userDataDir = normalizeOptionalString(asNullableRecord(rawProfile)?.userDataDir); if (userDataDir && isSameOrChildPath(resolveUserPath(userDataDir), legacyProfileDir)) return true; } return false; } /** Detects unmanaged legacy clawd browser profile residue on disk. */ function detectLegacyClawdBrowserProfileResidue(cfg, deps) { const configDir = deps?.configDir ?? CONFIG_DIR; const legacyProfileDir = resolveManagedBrowserProfileDir(configDir, LEGACY_CLAWD_BROWSER_PROFILE_NAME); const legacyUserDataDir = resolveManagedBrowserUserDataDir(configDir, LEGACY_CLAWD_BROWSER_PROFILE_NAME); const pathExists = deps?.pathExists ?? fs.existsSync; if (!pathExists(legacyProfileDir) && !pathExists(legacyUserDataDir)) return null; if (isLegacyClawdProfileConfigured(cfg, legacyProfileDir)) return null; const resolved = resolveBrowserConfig(cfg.browser, cfg); const defaultProfile = resolved.profiles[resolved.defaultProfile]; if (resolved.defaultProfile !== "openclaw" || defaultProfile?.driver === "existing-session") return null; return { legacyProfileDir, legacyUserDataDir, canonicalUserDataDir: resolveManagedBrowserUserDataDir(configDir, DEFAULT_OPENCLAW_BROWSER_PROFILE_NAME) }; } function formatLegacyClawdBrowserProfileResidueNote(residue) { return [ `- Legacy managed browser profile residue was found at ${residue.legacyProfileDir}.`, `- The canonical OpenClaw-managed browser profile is ${residue.canonicalUserDataDir}.`, `- If no browser is using the legacy profile, run ${formatCliCommand("openclaw doctor --fix")} to archive it safely instead of deleting it in place.` ].join("\n"); } /** Emits Browser doctor notes for Chrome MCP, managed Chrome, and legacy residue readiness. */ async function noteChromeMcpBrowserReadiness(cfg, deps) { const noteFn = deps?.noteFn ?? note; const platform = deps?.platform ?? process.platform; const env = deps?.env ?? process.env; const getUid = deps?.getUid ?? (() => process.getuid?.() ?? -1); const resolveManagedExecutable = deps?.resolveManagedExecutable ?? resolveBrowserExecutableForPlatform; const resolveChromeExecutable = deps?.resolveChromeExecutable ?? resolveGoogleChromeExecutableForPlatform; const readVersion = deps?.readVersion ?? readBrowserVersion; const managedProfiles = collectManagedProfiles(cfg); const managedProfileLabel = managedProfiles.map((profile) => profile.name).join(", "); const resolved = resolveBrowserConfig(cfg.browser, cfg); const legacyClawdResidue = detectLegacyClawdBrowserProfileResidue(cfg, { configDir: deps?.configDir, pathExists: deps?.pathExists }); if (legacyClawdResidue) noteFn(formatLegacyClawdBrowserProfileResidueNote(legacyClawdResidue), "Browser"); const browserExecutable = managedProfiles.length > 0 ? resolveManagedExecutable(resolved, platform) : null; const missingDisplay = platform === "linux" && managedProfiles.length > 0 && !resolved.headless && !normalizeOptionalString(env.DISPLAY) && !normalizeOptionalString(env.WAYLAND_DISPLAY); const shouldWarnRootNoSandbox = platform === "linux" && managedProfiles.length > 0 && !resolved.noSandbox && getUid() === 0; if (!browserExecutable && managedProfiles.length > 0) noteFn([ `- OpenClaw-managed browser profile(s) are configured: ${managedProfileLabel}.`, "- No Chromium-based browser executable was found on this host for OpenClaw-managed launch.", "- Install Chrome, Chromium, Brave, Edge, or set browser.executablePath explicitly." ].join("\n"), "Browser"); if (missingDisplay || shouldWarnRootNoSandbox) { const lines = [`- OpenClaw-managed browser profile(s) are configured: ${managedProfileLabel}.`]; if (missingDisplay) lines.push("- No DISPLAY or WAYLAND_DISPLAY is set, and browser.headless is false. Managed browser launch needs a desktop session, Xvfb, or browser.headless: true."); if (shouldWarnRootNoSandbox) lines.push("- The Gateway is running as root and browser.noSandbox is false. Chromium commonly requires browser.noSandbox: true in container/root runtimes."); noteFn(lines.join("\n"), "Browser"); } const profiles = collectChromeMcpProfiles(cfg); if (profiles.length === 0) return; const explicitProfiles = profiles.filter((profile) => profile.userDataDir); const autoConnectProfiles = profiles.filter((profile) => !profile.userDataDir); const profileLabel = profiles.map((profile) => profile.name).join(", "); if (autoConnectProfiles.length === 0) { noteFn([ `- Chrome MCP existing-session is configured for profile(s): ${profileLabel}.`, "- These profiles use an explicit Chromium user data directory instead of Chrome's default auto-connect path.", `- Verify the matching Chromium-based browser is version ${CHROME_MCP_MIN_MAJOR}+ on the same host as the Gateway or node.`, `- Enable remote debugging in that browser's inspect page (${REMOTE_DEBUGGING_PAGES}).`, "- Keep the browser running and accept the attach consent prompt the first time OpenClaw connects." ].join("\n"), "Browser"); return; } const chrome = resolveChromeExecutable(platform); const autoProfileLabel = autoConnectProfiles.map((profile) => profile.name).join(", "); if (!chrome) { const lines = [ `- Chrome MCP existing-session is configured for profile(s): ${profileLabel}.`, `- Google Chrome was not found on this host for auto-connect profile(s): ${autoProfileLabel}. OpenClaw does not bundle Chrome.`, `- Install Google Chrome ${CHROME_MCP_MIN_MAJOR}+ on the same host as the Gateway or node, or set browser.profiles.<name>.userDataDir for a different Chromium-based browser.`, `- Enable remote debugging in the browser inspect page (${REMOTE_DEBUGGING_PAGES}).`, "- Keep the browser running and accept the attach consent prompt the first time OpenClaw connects.", "- Docker, headless, and sandbox browser flows stay on raw CDP; this check only applies to host-local Chrome MCP attach." ]; if (explicitProfiles.length > 0) lines.push(`- Profiles with explicit userDataDir skip Chrome auto-detection: ${explicitProfiles.map((profile) => profile.name).join(", ")}.`); noteFn(lines.join("\n"), "Browser"); return; } const versionRaw = readVersion(chrome.path); const major = parseBrowserMajorVersion(versionRaw); const lines = [`- Chrome MCP existing-session is configured for profile(s): ${profileLabel}.`, `- Chrome path: ${chrome.path}`]; if (!versionRaw || major === null) lines.push(`- Could not determine the installed Chrome version. Chrome MCP requires Google Chrome ${CHROME_MCP_MIN_MAJOR}+ on this host.`); else if (major < CHROME_MCP_MIN_MAJOR) lines.push(`- Detected Chrome ${versionRaw}, which is too old for Chrome MCP existing-session attach. Upgrade to Chrome ${CHROME_MCP_MIN_MAJOR}+.`); else lines.push(`- Detected Chrome ${versionRaw}.`); lines.push(`- Enable remote debugging in the browser inspect page (${REMOTE_DEBUGGING_PAGES}).`); lines.push("- Keep the browser running and accept the attach consent prompt the first time OpenClaw connects."); if (explicitProfiles.length > 0) lines.push(`- Profiles with explicit userDataDir still need manual validation of the matching Chromium-based browser: ${explicitProfiles.map((profile) => profile.name).join(", ")}.`); noteFn(lines.join("\n"), "Browser"); } /** Archives legacy clawd browser profile residue when doctor --fix is requested. */ async function maybeArchiveLegacyClawdBrowserProfileResidue(cfg, deps) { const residue = detectLegacyClawdBrowserProfileResidue(cfg, deps); if (!residue) return { changes: [], warnings: [] }; const move = deps?.movePathToTrash ?? movePathToTrash; try { const archivedPath = await move(residue.legacyProfileDir); return { changes: [[ "Archived legacy clawd managed browser profile residue.", `- legacy profile: ${residue.legacyProfileDir}`, `- canonical profile: ${residue.canonicalUserDataDir}`, `- archived at: ${archivedPath}` ].join("\n")], warnings: [] }; } catch (error) { return { changes: [], warnings: [`Legacy clawd browser profile residue could not be archived: ${error instanceof Error ? error.message : String(error)}`] }; } } //#endregion export { detectLegacyClawdBrowserProfileResidue, maybeArchiveLegacyClawdBrowserProfileResidue, noteChromeMcpBrowserReadiness };