openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
257 lines (256 loc) • 9.07 kB
JavaScript
import { t as collectErrorGraphCandidates } from "./errors-BXgSefBE.js";
import { d as registerUnhandledRejectionHandler } from "./unhandled-rejections-CifjyMHp.js";
import { c as resolveEffectiveEnableState, s as normalizePluginsConfig } from "./config-state-CikNNz7T.js";
import { a as isSubagentSessionKey, i as isCronSessionKey, n as isAcpSessionKey } from "./session-key-utils-Bx3apsJ3.js";
import { i as getRuntimeConfig } from "./io-Gi7-pyU-.js";
import "./error-runtime-C8vbtAJt.js";
import "./runtime-env-D_2q8-VK.js";
import "./routing-CQ63ICPX.js";
import { n as resolveBrowserConfig } from "./config-C2rsUqn7.js";
import "./config-DGptl9kI.js";
import { n as sweepTrackedBrowserTabs } from "./session-tab-registry-BG1AGKBy.js";
import { n as getPwAiModule } from "./target-id-CwSK7UJx.js";
import { n as listKnownProfileNames, t as createBrowserRouteContext } from "./server-context-Dm_OCxXF.js";
import { o as stopOpenClawChrome } from "./chrome-BWowRFBm.js";
import { t as isPwAiLoaded } from "./pw-ai-state-B4Mk01M0.js";
//#region extensions/browser/src/browser/server-lifecycle.ts
/**
* Browser server lifecycle helpers for relay setup and profile shutdown.
*/
/** Ensures extension relay compatibility hooks for configured profiles. */
async function ensureExtensionRelayForProfiles(_params) {}
/** Stops every known Browser profile during runtime shutdown. */
async function stopKnownBrowserProfiles(params) {
const current = params.getState();
if (!current) return;
const ctx = createBrowserRouteContext({
getState: params.getState,
refreshConfigFromDisk: true
});
try {
for (const name of listKnownProfileNames(current)) try {
const runtime = current.profiles.get(name);
if (runtime?.running) {
await stopOpenClawChrome(runtime.running);
runtime.running = null;
continue;
}
await ctx.forProfile(name).stopRunningBrowser();
} catch {}
} catch (err) {
params.onWarn(`openclaw browser stop failed: ${String(err)}`);
}
}
//#endregion
//#region extensions/browser/src/browser/session-tab-cleanup.ts
/**
* Periodic cleanup for browser tabs tracked to primary OpenClaw sessions.
*/
const MIN_SWEEP_INTERVAL_MS = 6e4;
function minutesToMs(minutes) {
return Math.max(0, Math.floor(minutes * 6e4));
}
/** Returns true for user-facing sessions whose tabs should be tracked for cleanup. */
function isPrimaryTrackedBrowserSessionKey(sessionKey) {
return !isSubagentSessionKey(sessionKey) && !isCronSessionKey(sessionKey) && !isAcpSessionKey(sessionKey);
}
function resolveBrowserTabCleanupRuntimeConfig() {
const cfg = getRuntimeConfig();
return resolveBrowserConfig(cfg.browser, cfg).tabCleanup;
}
/** Runs one Browser tab cleanup sweep from runtime config or injected test config. */
async function runTrackedBrowserTabCleanupOnce(params) {
const cleanup = params?.cleanup ?? resolveBrowserTabCleanupRuntimeConfig();
if (!cleanup.enabled) return 0;
return await sweepTrackedBrowserTabs({
now: params?.now,
idleMs: minutesToMs(cleanup.idleMinutes),
maxTabsPerSession: cleanup.maxTabsPerSession,
sessionFilter: isPrimaryTrackedBrowserSessionKey,
closeTab: params?.closeTab,
onWarn: params?.onWarn
});
}
/** Starts the recurring Browser tab cleanup timer and returns its disposer. */
function startTrackedBrowserTabCleanupTimer(params) {
let stopped = false;
let timer = null;
let running = null;
const schedule = () => {
if (stopped) return;
let sweepMinutes = 5;
try {
sweepMinutes = resolveBrowserTabCleanupRuntimeConfig().sweepMinutes;
} catch (err) {
params.onWarn(`failed to resolve browser tab cleanup config: ${String(err)}`);
}
timer = setTimeout(run, Math.max(MIN_SWEEP_INTERVAL_MS, minutesToMs(sweepMinutes)));
timer.unref?.();
};
const run = () => {
if (stopped) return;
if (!running) {
running = runTrackedBrowserTabCleanupOnce({ onWarn: params.onWarn }).finally(() => {
running = null;
schedule();
});
return;
}
schedule();
};
schedule();
return () => {
stopped = true;
if (timer) {
clearTimeout(timer);
timer = null;
}
};
}
//#endregion
//#region extensions/browser/src/browser/unhandled-rejections.ts
/**
* Browser-specific unhandled rejection filter for benign Playwright dialog
* races.
*/
const PLAYWRIGHT_DIALOG_METHODS = new Set(["Page.handleJavaScriptDialog", "Dialog.handleJavaScriptDialog"]);
const NO_DIALOG_MESSAGE = "no dialog is showing";
function readMessage(err) {
if (typeof err === "string") return err;
if (!err || typeof err !== "object") return "";
const message = err.message;
return typeof message === "string" ? message : "";
}
function readPlaywrightMethod(err) {
if (!err || typeof err !== "object") return;
const method = err.method;
return typeof method === "string" ? method : void 0;
}
/** Detects Playwright "no dialog is showing" races that can escape as rejections. */
function isPlaywrightDialogRaceUnhandledRejection(reason) {
for (const candidate of collectErrorGraphCandidates(reason, (current) => [
current.cause,
current.reason,
current.original,
current.error,
current.data,
...Array.isArray(current.errors) ? current.errors : []
])) {
const message = readMessage(candidate);
if (!message.toLowerCase().includes(NO_DIALOG_MESSAGE)) continue;
const method = readPlaywrightMethod(candidate);
if (method && PLAYWRIGHT_DIALOG_METHODS.has(method)) return true;
for (const playwrightMethod of PLAYWRIGHT_DIALOG_METHODS) if (message.includes(playwrightMethod)) return true;
}
return false;
}
/** Installs the Browser unhandled-rejection filter and returns its disposer. */
function registerBrowserUnhandledRejectionHandler() {
return registerUnhandledRejectionHandler(isPlaywrightDialogRaceUnhandledRejection);
}
//#endregion
//#region extensions/browser/src/browser/runtime-lifecycle.ts
/** Creates Browser server state and starts runtime-wide cleanup handlers. */
async function createBrowserRuntimeState(params) {
const state = {
server: params.server ?? null,
port: params.port,
resolved: params.resolved,
profiles: /* @__PURE__ */ new Map()
};
state.stopTrackedTabCleanup = startTrackedBrowserTabCleanupTimer({ onWarn: params.onWarn });
await ensureExtensionRelayForProfiles({
resolved: params.resolved,
onWarn: params.onWarn
});
state.stopUnhandledRejectionHandler = registerBrowserUnhandledRejectionHandler();
return state;
}
/** Stops Browser profiles, the optional HTTP server, and loaded Playwright state. */
async function stopBrowserRuntime(params) {
if (!params.current) return;
try {
params.current.stopTrackedTabCleanup?.();
await stopKnownBrowserProfiles({
getState: params.getState,
onWarn: params.onWarn
});
if (params.closeServer && params.current.server) await new Promise((resolve) => {
params.current?.server?.close(() => resolve());
});
params.clearState();
if (!isPwAiLoaded()) return;
try {
await (await getPwAiModule({ mode: "soft" }))?.closePlaywrightBrowserConnection();
} catch {}
} finally {
params.current.stopUnhandledRejectionHandler?.();
}
}
//#endregion
//#region extensions/browser/src/browser-control-state.ts
let state = null;
let owner = null;
function getBrowserControlState() {
return state;
}
/** Create a route context bound to the current shared browser runtime. */
function createBrowserControlContext() {
return createBrowserRouteContext({
getState: () => state,
refreshConfigFromDisk: true
});
}
/** Start or attach the shared browser runtime for either the server or service owner. */
async function ensureBrowserControlRuntime(params) {
if (state) {
if (params.server) {
state.server = params.server;
state.port = params.port;
state.resolved = {
...params.resolved,
controlPort: params.port
};
owner = "server";
}
return state;
}
state = await createBrowserRuntimeState({
server: params.server ?? null,
port: params.port,
resolved: params.resolved,
onWarn: params.onWarn
});
owner = params.owner;
return state;
}
/** Stop the shared browser runtime when the requesting owner is allowed to do so. */
async function stopBrowserControlRuntime(params) {
const current = state;
if (!current) return;
if (params.requestedBy === "service" && current.server && owner === "server") return;
await stopBrowserRuntime({
current,
getState: () => state,
clearState: () => {
state = null;
owner = null;
},
closeServer: params.closeServer,
onWarn: params.onWarn
});
}
//#endregion
//#region extensions/browser/src/plugin-enabled.ts
/** Returns whether the bundled Browser plugin is effectively enabled by config. */
function isDefaultBrowserPluginEnabled(cfg) {
return resolveEffectiveEnableState({
id: "browser",
origin: "bundled",
config: normalizePluginsConfig(cfg.plugins),
rootConfig: cfg,
enabledByDefault: true
}).enabled;
}
//#endregion
export { stopBrowserControlRuntime as a, getBrowserControlState as i, createBrowserControlContext as n, createBrowserRuntimeState as o, ensureBrowserControlRuntime as r, stopBrowserRuntime as s, isDefaultBrowserPluginEnabled as t };