BYPASS SHELL BY ./RAZORGANZ
Server: nginx/1.20.1
System: Linux iZdzfnv9mwfppeZ 5.10.134-19.2.al8.x86_64 #1 SMP Wed Oct 29 22:47:09 CST 2025 x86_64
User: apache (48)
PHP: 8.2.30
Disabled: NONE
Upload Files
File: //lib/node_modules/openclaw/dist/server-context-B5puT4XF.js
import { Kt as resolveGatewayPort, o as parseBooleanValue } from "./entry.js";
import { i as loadConfig, l as writeConfigFile, r as createConfigIO } from "./config-DWx3SXRc.js";
import { r as isLoopbackHost } from "./net-BEAjYacy.js";
import { i as getImageMetadata, n as buildImageResizeSideGrid, s as resizeToJpeg, t as IMAGE_REDUCE_QUALITY_STEPS } from "./image-ops-CdtmwUCR.js";
import { A as DEFAULT_OPENCLAW_BROWSER_ENABLED, C as stopChromeExtensionRelayServer, D as DEFAULT_BROWSER_DEFAULT_PROFILE_NAME, E as DEFAULT_AI_SNAPSHOT_MAX_CHARS, O as DEFAULT_BROWSER_EVALUATE_ENABLED, S as ensureChromeExtensionRelayServer, T as DEFAULT_AI_SNAPSHOT_EFFICIENT_MAX_CHARS, _ as appendCdpPath, a as resolveOpenClawUserDataDir, c as captureScreenshot, d as normalizeCdpWsUrl, f as snapshotAria, g as withBrowserNavigationPolicy, h as assertBrowserNavigationResultAllowed, i as launchOpenClawChrome, j as DEFAULT_OPENCLAW_BROWSER_PROFILE_NAME, k as DEFAULT_OPENCLAW_BROWSER_COLOR, l as createTargetViaCdp, m as assertBrowserNavigationAllowed, n as isChromeCdpReady, o as stopOpenClawChrome, p as InvalidBrowserNavigationUrlError, r as isChromeReachable, s as resolveBrowserExecutableForPlatform, v as fetchJson, w as DEFAULT_AI_SNAPSHOT_EFFICIENT_DEPTH, y as fetchOk } from "./chrome-Cw-tdii4.js";
import { a as resolveGatewayAuth } from "./auth-Dw00sIWu.js";
import { n as formatErrorMessage, t as extractErrorCode } from "./errors-DjnYuRJy.js";
import { a as resolveExistingPathsWithinRoot, i as DEFAULT_UPLOAD_DIR, n as DEFAULT_DOWNLOAD_DIR, o as resolvePathWithinRoot, r as DEFAULT_TRACE_DIR, t as movePathToTrash } from "./trash-CX7nB-3e.js";
import { t as SsrFBlockedError } from "./ssrf-BjDQh-0k.js";
import { r as saveMediaBuffer, t as ensureMediaDir } from "./store-CP7czNHW.js";
import path from "node:path";
import fs from "node:fs";
import fs$1 from "node:fs/promises";
import crypto from "node:crypto";

//#region src/gateway/startup-auth.ts
function mergeGatewayTailscaleConfig(base, override) {
	const merged = { ...base };
	if (!override) return merged;
	if (override.mode !== void 0) merged.mode = override.mode;
	if (override.resetOnExit !== void 0) merged.resetOnExit = override.resetOnExit;
	return merged;
}
function resolveGatewayAuthFromConfig(params) {
	const tailscaleConfig = mergeGatewayTailscaleConfig(params.cfg.gateway?.tailscale, params.tailscaleOverride);
	return resolveGatewayAuth({
		authConfig: params.cfg.gateway?.auth,
		authOverride: params.authOverride,
		env: params.env,
		tailscaleMode: tailscaleConfig.mode ?? "off"
	});
}
function shouldPersistGeneratedToken(params) {
	if (!params.persistRequested) return false;
	if (params.resolvedAuth.modeSource === "override") return false;
	return true;
}
async function ensureGatewayStartupAuth(params) {
	const env = params.env ?? process.env;
	const persistRequested = params.persist === true;
	const resolved = resolveGatewayAuthFromConfig({
		cfg: params.cfg,
		env,
		authOverride: params.authOverride,
		tailscaleOverride: params.tailscaleOverride
	});
	if (resolved.mode !== "token" || (resolved.token?.trim().length ?? 0) > 0) {
		assertHooksTokenSeparateFromGatewayAuth({
			cfg: params.cfg,
			auth: resolved
		});
		return {
			cfg: params.cfg,
			auth: resolved,
			persistedGeneratedToken: false
		};
	}
	const generatedToken = crypto.randomBytes(24).toString("hex");
	const nextCfg = {
		...params.cfg,
		gateway: {
			...params.cfg.gateway,
			auth: {
				...params.cfg.gateway?.auth,
				mode: "token",
				token: generatedToken
			}
		}
	};
	const persist = shouldPersistGeneratedToken({
		persistRequested,
		resolvedAuth: resolved
	});
	if (persist) await writeConfigFile(nextCfg);
	const nextAuth = resolveGatewayAuthFromConfig({
		cfg: nextCfg,
		env,
		authOverride: params.authOverride,
		tailscaleOverride: params.tailscaleOverride
	});
	assertHooksTokenSeparateFromGatewayAuth({
		cfg: nextCfg,
		auth: nextAuth
	});
	return {
		cfg: nextCfg,
		auth: nextAuth,
		generatedToken,
		persistedGeneratedToken: persist
	};
}
function assertHooksTokenSeparateFromGatewayAuth(params) {
	if (params.cfg.hooks?.enabled !== true) return;
	const hooksToken = typeof params.cfg.hooks.token === "string" ? params.cfg.hooks.token.trim() : "";
	if (!hooksToken) return;
	const gatewayToken = params.auth.mode === "token" && typeof params.auth.token === "string" ? params.auth.token.trim() : "";
	if (!gatewayToken) return;
	if (hooksToken !== gatewayToken) return;
	throw new Error("Invalid config: hooks.token must not match gateway auth token. Set a distinct hooks.token for hook ingress.");
}

//#endregion
//#region src/browser/control-auth.ts
function resolveBrowserControlAuth(cfg, env = process.env) {
	const auth = resolveGatewayAuth({
		authConfig: cfg?.gateway?.auth,
		env,
		tailscaleMode: cfg?.gateway?.tailscale?.mode
	});
	const token = typeof auth.token === "string" ? auth.token.trim() : "";
	const password = typeof auth.password === "string" ? auth.password.trim() : "";
	return {
		token: token || void 0,
		password: password || void 0
	};
}
function shouldAutoGenerateBrowserAuth(env) {
	if ((env.NODE_ENV ?? "").trim().toLowerCase() === "test") return false;
	const vitest = (env.VITEST ?? "").trim().toLowerCase();
	if (vitest && vitest !== "0" && vitest !== "false" && vitest !== "off") return false;
	return true;
}
async function ensureBrowserControlAuth(params) {
	const env = params.env ?? process.env;
	const auth = resolveBrowserControlAuth(params.cfg, env);
	if (auth.token || auth.password) return { auth };
	if (!shouldAutoGenerateBrowserAuth(env)) return { auth };
	if (params.cfg.gateway?.auth?.mode === "password") return { auth };
	if (params.cfg.gateway?.auth?.mode === "none") return { auth };
	if (params.cfg.gateway?.auth?.mode === "trusted-proxy") return { auth };
	const latestCfg = loadConfig();
	const latestAuth = resolveBrowserControlAuth(latestCfg, env);
	if (latestAuth.token || latestAuth.password) return { auth: latestAuth };
	if (latestCfg.gateway?.auth?.mode === "password") return { auth: latestAuth };
	if (latestCfg.gateway?.auth?.mode === "none") return { auth: latestAuth };
	if (latestCfg.gateway?.auth?.mode === "trusted-proxy") return { auth: latestAuth };
	const ensured = await ensureGatewayStartupAuth({
		cfg: latestCfg,
		env,
		persist: true
	});
	return {
		auth: resolveBrowserControlAuth(ensured.cfg, env),
		generatedToken: ensured.generatedToken
	};
}

//#endregion
//#region src/browser/routes/agent.act.shared.ts
const ACT_KINDS = [
	"click",
	"close",
	"drag",
	"evaluate",
	"fill",
	"hover",
	"scrollIntoView",
	"press",
	"resize",
	"select",
	"type",
	"wait"
];
function isActKind(value) {
	if (typeof value !== "string") return false;
	return ACT_KINDS.includes(value);
}
const ALLOWED_CLICK_MODIFIERS = new Set([
	"Alt",
	"Control",
	"ControlOrMeta",
	"Meta",
	"Shift"
]);
function parseClickButton(raw) {
	if (raw === "left" || raw === "right" || raw === "middle") return raw;
}
function parseClickModifiers(raw) {
	if (raw.filter((m) => !ALLOWED_CLICK_MODIFIERS.has(m)).length) return { error: "modifiers must be Alt|Control|ControlOrMeta|Meta|Shift" };
	return { modifiers: raw.length ? raw : void 0 };
}

//#endregion
//#region src/browser/pw-ai-module.ts
let pwAiModuleSoft = null;
let pwAiModuleStrict = null;
function isModuleNotFoundError(err) {
	if (extractErrorCode(err) === "ERR_MODULE_NOT_FOUND") return true;
	const msg = formatErrorMessage(err);
	return msg.includes("Cannot find module") || msg.includes("Cannot find package") || msg.includes("Failed to resolve import") || msg.includes("Failed to resolve entry for package") || msg.includes("Failed to load url");
}
async function loadPwAiModule(mode) {
	try {
		return await import("./pw-ai-CYmNs6zZ.js");
	} catch (err) {
		if (mode === "soft") return null;
		if (isModuleNotFoundError(err)) return null;
		throw err;
	}
}
async function getPwAiModule$1(opts) {
	if ((opts?.mode ?? "soft") === "soft") {
		if (!pwAiModuleSoft) pwAiModuleSoft = loadPwAiModule("soft");
		return await pwAiModuleSoft;
	}
	if (!pwAiModuleStrict) pwAiModuleStrict = loadPwAiModule("strict");
	return await pwAiModuleStrict;
}

//#endregion
//#region src/browser/routes/utils.ts
/**
* Extract profile name from query string or body and get profile context.
* Query string takes precedence over body for consistency with GET routes.
*/
function getProfileContext(req, ctx) {
	let profileName;
	if (typeof req.query.profile === "string") profileName = req.query.profile.trim() || void 0;
	if (!profileName && req.body && typeof req.body === "object") {
		const body = req.body;
		if (typeof body.profile === "string") profileName = body.profile.trim() || void 0;
	}
	try {
		return ctx.forProfile(profileName);
	} catch (err) {
		return {
			error: String(err),
			status: 404
		};
	}
}
function jsonError(res, status, message) {
	res.status(status).json({ error: message });
}
function toStringOrEmpty(value) {
	if (typeof value === "string") return value.trim();
	if (typeof value === "number" || typeof value === "boolean") return String(value).trim();
	return "";
}
function toNumber(value) {
	if (typeof value === "number" && Number.isFinite(value)) return value;
	if (typeof value === "string" && value.trim()) {
		const parsed = Number(value);
		return Number.isFinite(parsed) ? parsed : void 0;
	}
}
function toBoolean(value) {
	return parseBooleanValue(value, {
		truthy: [
			"true",
			"1",
			"yes"
		],
		falsy: [
			"false",
			"0",
			"no"
		]
	});
}
function toStringArray(value) {
	if (!Array.isArray(value)) return;
	const strings = value.map((v) => toStringOrEmpty(v)).filter(Boolean);
	return strings.length ? strings : void 0;
}

//#endregion
//#region src/browser/routes/agent.shared.ts
const SELECTOR_UNSUPPORTED_MESSAGE = [
	"Error: 'selector' is not supported. Use 'ref' from snapshot instead.",
	"",
	"Example workflow:",
	"1. snapshot action to get page state with refs",
	"2. act with ref: \"e123\" to interact with element",
	"",
	"This is more reliable for modern SPAs."
].join("\n");
function readBody(req) {
	const body = req.body;
	if (!body || typeof body !== "object" || Array.isArray(body)) return {};
	return body;
}
function resolveTargetIdFromBody(body) {
	return (typeof body.targetId === "string" ? body.targetId.trim() : "") || void 0;
}
function resolveTargetIdFromQuery(query) {
	return (typeof query.targetId === "string" ? query.targetId.trim() : "") || void 0;
}
function handleRouteError(ctx, res, err) {
	const mapped = ctx.mapTabError(err);
	if (mapped) return jsonError(res, mapped.status, mapped.message);
	jsonError(res, 500, String(err));
}
function resolveProfileContext(req, res, ctx) {
	const profileCtx = getProfileContext(req, ctx);
	if ("error" in profileCtx) {
		jsonError(res, profileCtx.status, profileCtx.error);
		return null;
	}
	return profileCtx;
}
async function getPwAiModule() {
	return await getPwAiModule$1({ mode: "soft" });
}
async function requirePwAi(res, feature) {
	const mod = await getPwAiModule();
	if (mod) return mod;
	jsonError(res, 501, [
		`Playwright is not available in this gateway build; '${feature}' is unsupported.`,
		"Install the full Playwright package (not playwright-core) and restart the gateway, or reinstall with browser support.",
		"Docs: /tools/browser#playwright-requirement"
	].join("\n"));
	return null;
}
async function withRouteTabContext(params) {
	const profileCtx = resolveProfileContext(params.req, params.res, params.ctx);
	if (!profileCtx) return;
	try {
		const tab = await profileCtx.ensureTabAvailable(params.targetId);
		return await params.run({
			profileCtx,
			tab,
			cdpUrl: profileCtx.profile.cdpUrl
		});
	} catch (err) {
		handleRouteError(params.ctx, params.res, err);
		return;
	}
}
async function withPlaywrightRouteContext(params) {
	return await withRouteTabContext({
		req: params.req,
		res: params.res,
		ctx: params.ctx,
		targetId: params.targetId,
		run: async ({ profileCtx, tab, cdpUrl }) => {
			const pw = await requirePwAi(params.res, params.feature);
			if (!pw) return;
			return await params.run({
				profileCtx,
				tab,
				cdpUrl,
				pw
			});
		}
	});
}

//#endregion
//#region src/browser/routes/agent.act.ts
function resolveDownloadPathOrRespond(res, requestedPath) {
	const downloadPathResult = resolvePathWithinRoot({
		rootDir: DEFAULT_DOWNLOAD_DIR,
		requestedPath,
		scopeLabel: "downloads directory"
	});
	if (!downloadPathResult.ok) {
		res.status(400).json({ error: downloadPathResult.error });
		return null;
	}
	return downloadPathResult.path;
}
function buildDownloadRequestBase(cdpUrl, targetId, timeoutMs) {
	return {
		cdpUrl,
		targetId,
		timeoutMs: timeoutMs ?? void 0
	};
}
function respondWithDownloadResult(res, targetId, result) {
	res.json({
		ok: true,
		targetId,
		download: result
	});
}
function registerBrowserAgentActRoutes(app, ctx) {
	app.post("/act", async (req, res) => {
		const body = readBody(req);
		const kindRaw = toStringOrEmpty(body.kind);
		if (!isActKind(kindRaw)) return jsonError(res, 400, "kind is required");
		const kind = kindRaw;
		const targetId = resolveTargetIdFromBody(body);
		if (Object.hasOwn(body, "selector") && kind !== "wait") return jsonError(res, 400, SELECTOR_UNSUPPORTED_MESSAGE);
		await withPlaywrightRouteContext({
			req,
			res,
			ctx,
			targetId,
			feature: `act:${kind}`,
			run: async ({ cdpUrl, tab, pw }) => {
				const evaluateEnabled = ctx.state().resolved.evaluateEnabled;
				switch (kind) {
					case "click": {
						const ref = toStringOrEmpty(body.ref);
						if (!ref) return jsonError(res, 400, "ref is required");
						const doubleClick = toBoolean(body.doubleClick) ?? false;
						const timeoutMs = toNumber(body.timeoutMs);
						const buttonRaw = toStringOrEmpty(body.button) || "";
						const button = buttonRaw ? parseClickButton(buttonRaw) : void 0;
						if (buttonRaw && !button) return jsonError(res, 400, "button must be left|right|middle");
						const parsedModifiers = parseClickModifiers(toStringArray(body.modifiers) ?? []);
						if (parsedModifiers.error) return jsonError(res, 400, parsedModifiers.error);
						const modifiers = parsedModifiers.modifiers;
						const clickRequest = {
							cdpUrl,
							targetId: tab.targetId,
							ref,
							doubleClick
						};
						if (button) clickRequest.button = button;
						if (modifiers) clickRequest.modifiers = modifiers;
						if (timeoutMs) clickRequest.timeoutMs = timeoutMs;
						await pw.clickViaPlaywright(clickRequest);
						return res.json({
							ok: true,
							targetId: tab.targetId,
							url: tab.url
						});
					}
					case "type": {
						const ref = toStringOrEmpty(body.ref);
						if (!ref) return jsonError(res, 400, "ref is required");
						if (typeof body.text !== "string") return jsonError(res, 400, "text is required");
						const text = body.text;
						const submit = toBoolean(body.submit) ?? false;
						const slowly = toBoolean(body.slowly) ?? false;
						const timeoutMs = toNumber(body.timeoutMs);
						const typeRequest = {
							cdpUrl,
							targetId: tab.targetId,
							ref,
							text,
							submit,
							slowly
						};
						if (timeoutMs) typeRequest.timeoutMs = timeoutMs;
						await pw.typeViaPlaywright(typeRequest);
						return res.json({
							ok: true,
							targetId: tab.targetId
						});
					}
					case "press": {
						const key = toStringOrEmpty(body.key);
						if (!key) return jsonError(res, 400, "key is required");
						const delayMs = toNumber(body.delayMs);
						await pw.pressKeyViaPlaywright({
							cdpUrl,
							targetId: tab.targetId,
							key,
							delayMs: delayMs ?? void 0
						});
						return res.json({
							ok: true,
							targetId: tab.targetId
						});
					}
					case "hover": {
						const ref = toStringOrEmpty(body.ref);
						if (!ref) return jsonError(res, 400, "ref is required");
						const timeoutMs = toNumber(body.timeoutMs);
						await pw.hoverViaPlaywright({
							cdpUrl,
							targetId: tab.targetId,
							ref,
							timeoutMs: timeoutMs ?? void 0
						});
						return res.json({
							ok: true,
							targetId: tab.targetId
						});
					}
					case "scrollIntoView": {
						const ref = toStringOrEmpty(body.ref);
						if (!ref) return jsonError(res, 400, "ref is required");
						const timeoutMs = toNumber(body.timeoutMs);
						const scrollRequest = {
							cdpUrl,
							targetId: tab.targetId,
							ref
						};
						if (timeoutMs) scrollRequest.timeoutMs = timeoutMs;
						await pw.scrollIntoViewViaPlaywright(scrollRequest);
						return res.json({
							ok: true,
							targetId: tab.targetId
						});
					}
					case "drag": {
						const startRef = toStringOrEmpty(body.startRef);
						const endRef = toStringOrEmpty(body.endRef);
						if (!startRef || !endRef) return jsonError(res, 400, "startRef and endRef are required");
						const timeoutMs = toNumber(body.timeoutMs);
						await pw.dragViaPlaywright({
							cdpUrl,
							targetId: tab.targetId,
							startRef,
							endRef,
							timeoutMs: timeoutMs ?? void 0
						});
						return res.json({
							ok: true,
							targetId: tab.targetId
						});
					}
					case "select": {
						const ref = toStringOrEmpty(body.ref);
						const values = toStringArray(body.values);
						if (!ref || !values?.length) return jsonError(res, 400, "ref and values are required");
						const timeoutMs = toNumber(body.timeoutMs);
						await pw.selectOptionViaPlaywright({
							cdpUrl,
							targetId: tab.targetId,
							ref,
							values,
							timeoutMs: timeoutMs ?? void 0
						});
						return res.json({
							ok: true,
							targetId: tab.targetId
						});
					}
					case "fill": {
						const fields = (Array.isArray(body.fields) ? body.fields : []).map((field) => {
							if (!field || typeof field !== "object") return null;
							const rec = field;
							const ref = toStringOrEmpty(rec.ref);
							const type = toStringOrEmpty(rec.type);
							if (!ref || !type) return null;
							const value = typeof rec.value === "string" || typeof rec.value === "number" || typeof rec.value === "boolean" ? rec.value : void 0;
							return value === void 0 ? {
								ref,
								type
							} : {
								ref,
								type,
								value
							};
						}).filter((field) => field !== null);
						if (!fields.length) return jsonError(res, 400, "fields are required");
						const timeoutMs = toNumber(body.timeoutMs);
						await pw.fillFormViaPlaywright({
							cdpUrl,
							targetId: tab.targetId,
							fields,
							timeoutMs: timeoutMs ?? void 0
						});
						return res.json({
							ok: true,
							targetId: tab.targetId
						});
					}
					case "resize": {
						const width = toNumber(body.width);
						const height = toNumber(body.height);
						if (!width || !height) return jsonError(res, 400, "width and height are required");
						await pw.resizeViewportViaPlaywright({
							cdpUrl,
							targetId: tab.targetId,
							width,
							height
						});
						return res.json({
							ok: true,
							targetId: tab.targetId,
							url: tab.url
						});
					}
					case "wait": {
						const timeMs = toNumber(body.timeMs);
						const text = toStringOrEmpty(body.text) || void 0;
						const textGone = toStringOrEmpty(body.textGone) || void 0;
						const selector = toStringOrEmpty(body.selector) || void 0;
						const url = toStringOrEmpty(body.url) || void 0;
						const loadStateRaw = toStringOrEmpty(body.loadState);
						const loadState = loadStateRaw === "load" || loadStateRaw === "domcontentloaded" || loadStateRaw === "networkidle" ? loadStateRaw : void 0;
						const fn = toStringOrEmpty(body.fn) || void 0;
						const timeoutMs = toNumber(body.timeoutMs) ?? void 0;
						if (fn && !evaluateEnabled) return jsonError(res, 403, ["wait --fn is disabled by config (browser.evaluateEnabled=false).", "Docs: /gateway/configuration#browser-openclaw-managed-browser"].join("\n"));
						if (timeMs === void 0 && !text && !textGone && !selector && !url && !loadState && !fn) return jsonError(res, 400, "wait requires at least one of: timeMs, text, textGone, selector, url, loadState, fn");
						await pw.waitForViaPlaywright({
							cdpUrl,
							targetId: tab.targetId,
							timeMs,
							text,
							textGone,
							selector,
							url,
							loadState,
							fn,
							timeoutMs
						});
						return res.json({
							ok: true,
							targetId: tab.targetId
						});
					}
					case "evaluate": {
						if (!evaluateEnabled) return jsonError(res, 403, ["act:evaluate is disabled by config (browser.evaluateEnabled=false).", "Docs: /gateway/configuration#browser-openclaw-managed-browser"].join("\n"));
						const fn = toStringOrEmpty(body.fn);
						if (!fn) return jsonError(res, 400, "fn is required");
						const ref = toStringOrEmpty(body.ref) || void 0;
						const evalTimeoutMs = toNumber(body.timeoutMs);
						const evalRequest = {
							cdpUrl,
							targetId: tab.targetId,
							fn,
							ref,
							signal: req.signal
						};
						if (evalTimeoutMs !== void 0) evalRequest.timeoutMs = evalTimeoutMs;
						const result = await pw.evaluateViaPlaywright(evalRequest);
						return res.json({
							ok: true,
							targetId: tab.targetId,
							url: tab.url,
							result
						});
					}
					case "close":
						await pw.closePageViaPlaywright({
							cdpUrl,
							targetId: tab.targetId
						});
						return res.json({
							ok: true,
							targetId: tab.targetId
						});
					default: return jsonError(res, 400, "unsupported kind");
				}
			}
		});
	});
	app.post("/hooks/file-chooser", async (req, res) => {
		const body = readBody(req);
		const targetId = resolveTargetIdFromBody(body);
		const ref = toStringOrEmpty(body.ref) || void 0;
		const inputRef = toStringOrEmpty(body.inputRef) || void 0;
		const element = toStringOrEmpty(body.element) || void 0;
		const paths = toStringArray(body.paths) ?? [];
		const timeoutMs = toNumber(body.timeoutMs);
		if (!paths.length) return jsonError(res, 400, "paths are required");
		await withPlaywrightRouteContext({
			req,
			res,
			ctx,
			targetId,
			feature: "file chooser hook",
			run: async ({ cdpUrl, tab, pw }) => {
				const uploadPathsResult = await resolveExistingPathsWithinRoot({
					rootDir: DEFAULT_UPLOAD_DIR,
					requestedPaths: paths,
					scopeLabel: `uploads directory (${DEFAULT_UPLOAD_DIR})`
				});
				if (!uploadPathsResult.ok) {
					res.status(400).json({ error: uploadPathsResult.error });
					return;
				}
				const resolvedPaths = uploadPathsResult.paths;
				if (inputRef || element) {
					if (ref) return jsonError(res, 400, "ref cannot be combined with inputRef/element");
					await pw.setInputFilesViaPlaywright({
						cdpUrl,
						targetId: tab.targetId,
						inputRef,
						element,
						paths: resolvedPaths
					});
				} else {
					await pw.armFileUploadViaPlaywright({
						cdpUrl,
						targetId: tab.targetId,
						paths: resolvedPaths,
						timeoutMs: timeoutMs ?? void 0
					});
					if (ref) await pw.clickViaPlaywright({
						cdpUrl,
						targetId: tab.targetId,
						ref
					});
				}
				res.json({ ok: true });
			}
		});
	});
	app.post("/hooks/dialog", async (req, res) => {
		const body = readBody(req);
		const targetId = resolveTargetIdFromBody(body);
		const accept = toBoolean(body.accept);
		const promptText = toStringOrEmpty(body.promptText) || void 0;
		const timeoutMs = toNumber(body.timeoutMs);
		if (accept === void 0) return jsonError(res, 400, "accept is required");
		await withPlaywrightRouteContext({
			req,
			res,
			ctx,
			targetId,
			feature: "dialog hook",
			run: async ({ cdpUrl, tab, pw }) => {
				await pw.armDialogViaPlaywright({
					cdpUrl,
					targetId: tab.targetId,
					accept,
					promptText,
					timeoutMs: timeoutMs ?? void 0
				});
				res.json({ ok: true });
			}
		});
	});
	app.post("/wait/download", async (req, res) => {
		const body = readBody(req);
		const targetId = resolveTargetIdFromBody(body);
		const out = toStringOrEmpty(body.path) || "";
		const timeoutMs = toNumber(body.timeoutMs);
		await withPlaywrightRouteContext({
			req,
			res,
			ctx,
			targetId,
			feature: "wait for download",
			run: async ({ cdpUrl, tab, pw }) => {
				let downloadPath;
				if (out.trim()) {
					const resolvedDownloadPath = resolveDownloadPathOrRespond(res, out);
					if (!resolvedDownloadPath) return;
					downloadPath = resolvedDownloadPath;
				}
				const requestBase = buildDownloadRequestBase(cdpUrl, tab.targetId, timeoutMs);
				const result = await pw.waitForDownloadViaPlaywright({
					...requestBase,
					path: downloadPath
				});
				respondWithDownloadResult(res, tab.targetId, result);
			}
		});
	});
	app.post("/download", async (req, res) => {
		const body = readBody(req);
		const targetId = resolveTargetIdFromBody(body);
		const ref = toStringOrEmpty(body.ref);
		const out = toStringOrEmpty(body.path);
		const timeoutMs = toNumber(body.timeoutMs);
		if (!ref) return jsonError(res, 400, "ref is required");
		if (!out) return jsonError(res, 400, "path is required");
		await withPlaywrightRouteContext({
			req,
			res,
			ctx,
			targetId,
			feature: "download",
			run: async ({ cdpUrl, tab, pw }) => {
				const downloadPath = resolveDownloadPathOrRespond(res, out);
				if (!downloadPath) return;
				const requestBase = buildDownloadRequestBase(cdpUrl, tab.targetId, timeoutMs);
				const result = await pw.downloadViaPlaywright({
					...requestBase,
					ref,
					path: downloadPath
				});
				respondWithDownloadResult(res, tab.targetId, result);
			}
		});
	});
	app.post("/response/body", async (req, res) => {
		const body = readBody(req);
		const targetId = resolveTargetIdFromBody(body);
		const url = toStringOrEmpty(body.url);
		const timeoutMs = toNumber(body.timeoutMs);
		const maxChars = toNumber(body.maxChars);
		if (!url) return jsonError(res, 400, "url is required");
		await withPlaywrightRouteContext({
			req,
			res,
			ctx,
			targetId,
			feature: "response body",
			run: async ({ cdpUrl, tab, pw }) => {
				const result = await pw.responseBodyViaPlaywright({
					cdpUrl,
					targetId: tab.targetId,
					url,
					timeoutMs: timeoutMs ?? void 0,
					maxChars: maxChars ?? void 0
				});
				res.json({
					ok: true,
					targetId: tab.targetId,
					response: result
				});
			}
		});
	});
	app.post("/highlight", async (req, res) => {
		const body = readBody(req);
		const targetId = resolveTargetIdFromBody(body);
		const ref = toStringOrEmpty(body.ref);
		if (!ref) return jsonError(res, 400, "ref is required");
		await withPlaywrightRouteContext({
			req,
			res,
			ctx,
			targetId,
			feature: "highlight",
			run: async ({ cdpUrl, tab, pw }) => {
				await pw.highlightViaPlaywright({
					cdpUrl,
					targetId: tab.targetId,
					ref
				});
				res.json({
					ok: true,
					targetId: tab.targetId
				});
			}
		});
	});
}

//#endregion
//#region src/browser/routes/agent.debug.ts
function registerBrowserAgentDebugRoutes(app, ctx) {
	app.get("/console", async (req, res) => {
		const targetId = resolveTargetIdFromQuery(req.query);
		const level = typeof req.query.level === "string" ? req.query.level : "";
		await withPlaywrightRouteContext({
			req,
			res,
			ctx,
			targetId,
			feature: "console messages",
			run: async ({ cdpUrl, tab, pw }) => {
				const messages = await pw.getConsoleMessagesViaPlaywright({
					cdpUrl,
					targetId: tab.targetId,
					level: level.trim() || void 0
				});
				res.json({
					ok: true,
					messages,
					targetId: tab.targetId
				});
			}
		});
	});
	app.get("/errors", async (req, res) => {
		const targetId = resolveTargetIdFromQuery(req.query);
		const clear = toBoolean(req.query.clear) ?? false;
		await withPlaywrightRouteContext({
			req,
			res,
			ctx,
			targetId,
			feature: "page errors",
			run: async ({ cdpUrl, tab, pw }) => {
				const result = await pw.getPageErrorsViaPlaywright({
					cdpUrl,
					targetId: tab.targetId,
					clear
				});
				res.json({
					ok: true,
					targetId: tab.targetId,
					...result
				});
			}
		});
	});
	app.get("/requests", async (req, res) => {
		const targetId = resolveTargetIdFromQuery(req.query);
		const filter = typeof req.query.filter === "string" ? req.query.filter : "";
		const clear = toBoolean(req.query.clear) ?? false;
		await withPlaywrightRouteContext({
			req,
			res,
			ctx,
			targetId,
			feature: "network requests",
			run: async ({ cdpUrl, tab, pw }) => {
				const result = await pw.getNetworkRequestsViaPlaywright({
					cdpUrl,
					targetId: tab.targetId,
					filter: filter.trim() || void 0,
					clear
				});
				res.json({
					ok: true,
					targetId: tab.targetId,
					...result
				});
			}
		});
	});
	app.post("/trace/start", async (req, res) => {
		const body = readBody(req);
		const targetId = resolveTargetIdFromBody(body);
		const screenshots = toBoolean(body.screenshots) ?? void 0;
		const snapshots = toBoolean(body.snapshots) ?? void 0;
		const sources = toBoolean(body.sources) ?? void 0;
		await withPlaywrightRouteContext({
			req,
			res,
			ctx,
			targetId,
			feature: "trace start",
			run: async ({ cdpUrl, tab, pw }) => {
				await pw.traceStartViaPlaywright({
					cdpUrl,
					targetId: tab.targetId,
					screenshots,
					snapshots,
					sources
				});
				res.json({
					ok: true,
					targetId: tab.targetId
				});
			}
		});
	});
	app.post("/trace/stop", async (req, res) => {
		const body = readBody(req);
		const targetId = resolveTargetIdFromBody(body);
		const out = toStringOrEmpty(body.path) || "";
		await withPlaywrightRouteContext({
			req,
			res,
			ctx,
			targetId,
			feature: "trace stop",
			run: async ({ cdpUrl, tab, pw }) => {
				const id = crypto.randomUUID();
				const dir = DEFAULT_TRACE_DIR;
				await fs$1.mkdir(dir, { recursive: true });
				const tracePathResult = resolvePathWithinRoot({
					rootDir: dir,
					requestedPath: out,
					scopeLabel: "trace directory",
					defaultFileName: `browser-trace-${id}.zip`
				});
				if (!tracePathResult.ok) {
					res.status(400).json({ error: tracePathResult.error });
					return;
				}
				const tracePath = tracePathResult.path;
				await pw.traceStopViaPlaywright({
					cdpUrl,
					targetId: tab.targetId,
					path: tracePath
				});
				res.json({
					ok: true,
					targetId: tab.targetId,
					path: path.resolve(tracePath)
				});
			}
		});
	});
}

//#endregion
//#region src/browser/screenshot.ts
const DEFAULT_BROWSER_SCREENSHOT_MAX_SIDE = 2e3;
const DEFAULT_BROWSER_SCREENSHOT_MAX_BYTES = 5 * 1024 * 1024;
async function normalizeBrowserScreenshot(buffer, opts) {
	const maxSide = Math.max(1, Math.round(opts?.maxSide ?? DEFAULT_BROWSER_SCREENSHOT_MAX_SIDE));
	const maxBytes = Math.max(1, Math.round(opts?.maxBytes ?? DEFAULT_BROWSER_SCREENSHOT_MAX_BYTES));
	const meta = await getImageMetadata(buffer);
	const width = Number(meta?.width ?? 0);
	const height = Number(meta?.height ?? 0);
	const maxDim = Math.max(width, height);
	if (buffer.byteLength <= maxBytes && (maxDim === 0 || width <= maxSide && height <= maxSide)) return { buffer };
	const sideGrid = buildImageResizeSideGrid(maxSide, maxDim > 0 ? Math.min(maxSide, maxDim) : maxSide);
	let smallest = null;
	for (const side of sideGrid) for (const quality of IMAGE_REDUCE_QUALITY_STEPS) {
		const out = await resizeToJpeg({
			buffer,
			maxSide: side,
			quality,
			withoutEnlargement: true
		});
		if (!smallest || out.byteLength < smallest.size) smallest = {
			buffer: out,
			size: out.byteLength
		};
		if (out.byteLength <= maxBytes) return {
			buffer: out,
			contentType: "image/jpeg"
		};
	}
	const best = smallest?.buffer ?? buffer;
	throw new Error(`Browser screenshot could not be reduced below ${(maxBytes / (1024 * 1024)).toFixed(0)}MB (got ${(best.byteLength / (1024 * 1024)).toFixed(2)}MB)`);
}

//#endregion
//#region src/browser/routes/agent.snapshot.ts
async function saveBrowserMediaResponse(params) {
	await ensureMediaDir();
	const saved = await saveMediaBuffer(params.buffer, params.contentType, "browser", params.maxBytes);
	params.res.json({
		ok: true,
		path: path.resolve(saved.path),
		targetId: params.targetId,
		url: params.url
	});
}
function registerBrowserAgentSnapshotRoutes(app, ctx) {
	app.post("/navigate", async (req, res) => {
		const body = readBody(req);
		const url = toStringOrEmpty(body.url);
		const targetId = toStringOrEmpty(body.targetId) || void 0;
		if (!url) return jsonError(res, 400, "url is required");
		await withPlaywrightRouteContext({
			req,
			res,
			ctx,
			targetId,
			feature: "navigate",
			run: async ({ cdpUrl, tab, pw }) => {
				const result = await pw.navigateViaPlaywright({
					cdpUrl,
					targetId: tab.targetId,
					url,
					...withBrowserNavigationPolicy(ctx.state().resolved.ssrfPolicy)
				});
				res.json({
					ok: true,
					targetId: tab.targetId,
					...result
				});
			}
		});
	});
	app.post("/pdf", async (req, res) => {
		await withPlaywrightRouteContext({
			req,
			res,
			ctx,
			targetId: toStringOrEmpty(readBody(req).targetId) || void 0,
			feature: "pdf",
			run: async ({ cdpUrl, tab, pw }) => {
				const pdf = await pw.pdfViaPlaywright({
					cdpUrl,
					targetId: tab.targetId
				});
				await saveBrowserMediaResponse({
					res,
					buffer: pdf.buffer,
					contentType: "application/pdf",
					maxBytes: pdf.buffer.byteLength,
					targetId: tab.targetId,
					url: tab.url
				});
			}
		});
	});
	app.post("/screenshot", async (req, res) => {
		const body = readBody(req);
		const targetId = toStringOrEmpty(body.targetId) || void 0;
		const fullPage = toBoolean(body.fullPage) ?? false;
		const ref = toStringOrEmpty(body.ref) || void 0;
		const element = toStringOrEmpty(body.element) || void 0;
		const type = body.type === "jpeg" ? "jpeg" : "png";
		if (fullPage && (ref || element)) return jsonError(res, 400, "fullPage is not supported for element screenshots");
		await withRouteTabContext({
			req,
			res,
			ctx,
			targetId,
			run: async ({ profileCtx, tab, cdpUrl }) => {
				let buffer;
				if (profileCtx.profile.driver === "extension" || !tab.wsUrl || Boolean(ref) || Boolean(element)) {
					const pw = await requirePwAi(res, "screenshot");
					if (!pw) return;
					buffer = (await pw.takeScreenshotViaPlaywright({
						cdpUrl,
						targetId: tab.targetId,
						ref,
						element,
						fullPage,
						type
					})).buffer;
				} else buffer = await captureScreenshot({
					wsUrl: tab.wsUrl ?? "",
					fullPage,
					format: type,
					quality: type === "jpeg" ? 85 : void 0
				});
				const normalized = await normalizeBrowserScreenshot(buffer, {
					maxSide: DEFAULT_BROWSER_SCREENSHOT_MAX_SIDE,
					maxBytes: DEFAULT_BROWSER_SCREENSHOT_MAX_BYTES
				});
				await saveBrowserMediaResponse({
					res,
					buffer: normalized.buffer,
					contentType: normalized.contentType ?? `image/${type}`,
					maxBytes: DEFAULT_BROWSER_SCREENSHOT_MAX_BYTES,
					targetId: tab.targetId,
					url: tab.url
				});
			}
		});
	});
	app.get("/snapshot", async (req, res) => {
		const profileCtx = resolveProfileContext(req, res, ctx);
		if (!profileCtx) return;
		const targetId = typeof req.query.targetId === "string" ? req.query.targetId.trim() : "";
		const mode = req.query.mode === "efficient" ? "efficient" : void 0;
		const labels = toBoolean(req.query.labels) ?? void 0;
		const format = (req.query.format === "aria" ? "aria" : req.query.format === "ai" ? "ai" : void 0) ?? (mode ? "ai" : await getPwAiModule() ? "ai" : "aria");
		const limitRaw = typeof req.query.limit === "string" ? Number(req.query.limit) : void 0;
		const hasMaxChars = Object.hasOwn(req.query, "maxChars");
		const maxCharsRaw = typeof req.query.maxChars === "string" ? Number(req.query.maxChars) : void 0;
		const limit = Number.isFinite(limitRaw) ? limitRaw : void 0;
		const resolvedMaxChars = format === "ai" ? hasMaxChars ? typeof maxCharsRaw === "number" && Number.isFinite(maxCharsRaw) && maxCharsRaw > 0 ? Math.floor(maxCharsRaw) : void 0 : mode === "efficient" ? DEFAULT_AI_SNAPSHOT_EFFICIENT_MAX_CHARS : DEFAULT_AI_SNAPSHOT_MAX_CHARS : void 0;
		const interactiveRaw = toBoolean(req.query.interactive);
		const compactRaw = toBoolean(req.query.compact);
		const depthRaw = toNumber(req.query.depth);
		const refsModeRaw = toStringOrEmpty(req.query.refs).trim();
		const refsMode = refsModeRaw === "aria" ? "aria" : refsModeRaw === "role" ? "role" : void 0;
		const interactive = interactiveRaw ?? (mode === "efficient" ? true : void 0);
		const compact = compactRaw ?? (mode === "efficient" ? true : void 0);
		const depth = depthRaw ?? (mode === "efficient" ? DEFAULT_AI_SNAPSHOT_EFFICIENT_DEPTH : void 0);
		const selector = toStringOrEmpty(req.query.selector);
		const frameSelector = toStringOrEmpty(req.query.frame);
		const selectorValue = selector.trim() || void 0;
		const frameSelectorValue = frameSelector.trim() || void 0;
		try {
			const tab = await profileCtx.ensureTabAvailable(targetId || void 0);
			if ((labels || mode === "efficient") && format === "aria") return jsonError(res, 400, "labels/mode=efficient require format=ai");
			if (format === "ai") {
				const pw = await requirePwAi(res, "ai snapshot");
				if (!pw) return;
				const wantsRoleSnapshot = labels === true || mode === "efficient" || interactive === true || compact === true || depth !== void 0 || Boolean(selectorValue) || Boolean(frameSelectorValue);
				const roleSnapshotArgs = {
					cdpUrl: profileCtx.profile.cdpUrl,
					targetId: tab.targetId,
					selector: selectorValue,
					frameSelector: frameSelectorValue,
					refsMode,
					options: {
						interactive: interactive ?? void 0,
						compact: compact ?? void 0,
						maxDepth: depth ?? void 0
					}
				};
				const snap = wantsRoleSnapshot ? await pw.snapshotRoleViaPlaywright(roleSnapshotArgs) : await pw.snapshotAiViaPlaywright({
					cdpUrl: profileCtx.profile.cdpUrl,
					targetId: tab.targetId,
					...typeof resolvedMaxChars === "number" ? { maxChars: resolvedMaxChars } : {}
				}).catch(async (err) => {
					if (String(err).toLowerCase().includes("_snapshotforai")) return await pw.snapshotRoleViaPlaywright(roleSnapshotArgs);
					throw err;
				});
				if (labels) {
					const labeled = await pw.screenshotWithLabelsViaPlaywright({
						cdpUrl: profileCtx.profile.cdpUrl,
						targetId: tab.targetId,
						refs: "refs" in snap ? snap.refs : {},
						type: "png"
					});
					const normalized = await normalizeBrowserScreenshot(labeled.buffer, {
						maxSide: DEFAULT_BROWSER_SCREENSHOT_MAX_SIDE,
						maxBytes: DEFAULT_BROWSER_SCREENSHOT_MAX_BYTES
					});
					await ensureMediaDir();
					const saved = await saveMediaBuffer(normalized.buffer, normalized.contentType ?? "image/png", "browser", DEFAULT_BROWSER_SCREENSHOT_MAX_BYTES);
					const imageType = normalized.contentType?.includes("jpeg") ? "jpeg" : "png";
					return res.json({
						ok: true,
						format,
						targetId: tab.targetId,
						url: tab.url,
						labels: true,
						labelsCount: labeled.labels,
						labelsSkipped: labeled.skipped,
						imagePath: path.resolve(saved.path),
						imageType,
						...snap
					});
				}
				return res.json({
					ok: true,
					format,
					targetId: tab.targetId,
					url: tab.url,
					...snap
				});
			}
			const snap = profileCtx.profile.driver === "extension" || !tab.wsUrl ? requirePwAi(res, "aria snapshot").then(async (pw) => {
				if (!pw) return null;
				return await pw.snapshotAriaViaPlaywright({
					cdpUrl: profileCtx.profile.cdpUrl,
					targetId: tab.targetId,
					limit
				});
			}) : snapshotAria({
				wsUrl: tab.wsUrl ?? "",
				limit
			});
			const resolved = await Promise.resolve(snap);
			if (!resolved) return;
			return res.json({
				ok: true,
				format,
				targetId: tab.targetId,
				url: tab.url,
				...resolved
			});
		} catch (err) {
			handleRouteError(ctx, res, err);
		}
	});
}

//#endregion
//#region src/browser/routes/agent.storage.ts
function parseStorageKind(raw) {
	if (raw === "local" || raw === "session") return raw;
	return null;
}
function parseStorageMutationRequest(kindParam, body) {
	return {
		kind: parseStorageKind(toStringOrEmpty(kindParam)),
		targetId: resolveTargetIdFromBody(body)
	};
}
function parseRequiredStorageMutationRequest(kindParam, body) {
	const parsed = parseStorageMutationRequest(kindParam, body);
	if (!parsed.kind) return null;
	return {
		kind: parsed.kind,
		targetId: parsed.targetId
	};
}
function parseStorageMutationOrRespond(res, kindParam, body) {
	const parsed = parseRequiredStorageMutationRequest(kindParam, body);
	if (!parsed) {
		jsonError(res, 400, "kind must be local|session");
		return null;
	}
	return parsed;
}
function parseStorageMutationFromRequest(req, res) {
	const body = readBody(req);
	const parsed = parseStorageMutationOrRespond(res, req.params.kind, body);
	if (!parsed) return null;
	return {
		body,
		parsed
	};
}
function registerBrowserAgentStorageRoutes(app, ctx) {
	app.get("/cookies", async (req, res) => {
		await withPlaywrightRouteContext({
			req,
			res,
			ctx,
			targetId: resolveTargetIdFromQuery(req.query),
			feature: "cookies",
			run: async ({ cdpUrl, tab, pw }) => {
				const result = await pw.cookiesGetViaPlaywright({
					cdpUrl,
					targetId: tab.targetId
				});
				res.json({
					ok: true,
					targetId: tab.targetId,
					...result
				});
			}
		});
	});
	app.post("/cookies/set", async (req, res) => {
		const body = readBody(req);
		const targetId = resolveTargetIdFromBody(body);
		const cookie = body.cookie && typeof body.cookie === "object" && !Array.isArray(body.cookie) ? body.cookie : null;
		if (!cookie) return jsonError(res, 400, "cookie is required");
		await withPlaywrightRouteContext({
			req,
			res,
			ctx,
			targetId,
			feature: "cookies set",
			run: async ({ cdpUrl, tab, pw }) => {
				await pw.cookiesSetViaPlaywright({
					cdpUrl,
					targetId: tab.targetId,
					cookie: {
						name: toStringOrEmpty(cookie.name),
						value: toStringOrEmpty(cookie.value),
						url: toStringOrEmpty(cookie.url) || void 0,
						domain: toStringOrEmpty(cookie.domain) || void 0,
						path: toStringOrEmpty(cookie.path) || void 0,
						expires: toNumber(cookie.expires) ?? void 0,
						httpOnly: toBoolean(cookie.httpOnly) ?? void 0,
						secure: toBoolean(cookie.secure) ?? void 0,
						sameSite: cookie.sameSite === "Lax" || cookie.sameSite === "None" || cookie.sameSite === "Strict" ? cookie.sameSite : void 0
					}
				});
				res.json({
					ok: true,
					targetId: tab.targetId
				});
			}
		});
	});
	app.post("/cookies/clear", async (req, res) => {
		await withPlaywrightRouteContext({
			req,
			res,
			ctx,
			targetId: resolveTargetIdFromBody(readBody(req)),
			feature: "cookies clear",
			run: async ({ cdpUrl, tab, pw }) => {
				await pw.cookiesClearViaPlaywright({
					cdpUrl,
					targetId: tab.targetId
				});
				res.json({
					ok: true,
					targetId: tab.targetId
				});
			}
		});
	});
	app.get("/storage/:kind", async (req, res) => {
		const kind = parseStorageKind(toStringOrEmpty(req.params.kind));
		if (!kind) return jsonError(res, 400, "kind must be local|session");
		const targetId = resolveTargetIdFromQuery(req.query);
		const key = toStringOrEmpty(req.query.key);
		await withPlaywrightRouteContext({
			req,
			res,
			ctx,
			targetId,
			feature: "storage get",
			run: async ({ cdpUrl, tab, pw }) => {
				const result = await pw.storageGetViaPlaywright({
					cdpUrl,
					targetId: tab.targetId,
					kind,
					key: key.trim() || void 0
				});
				res.json({
					ok: true,
					targetId: tab.targetId,
					...result
				});
			}
		});
	});
	app.post("/storage/:kind/set", async (req, res) => {
		const mutation = parseStorageMutationFromRequest(req, res);
		if (!mutation) return;
		const key = toStringOrEmpty(mutation.body.key);
		if (!key) return jsonError(res, 400, "key is required");
		const value = typeof mutation.body.value === "string" ? mutation.body.value : "";
		await withPlaywrightRouteContext({
			req,
			res,
			ctx,
			targetId: mutation.parsed.targetId,
			feature: "storage set",
			run: async ({ cdpUrl, tab, pw }) => {
				await pw.storageSetViaPlaywright({
					cdpUrl,
					targetId: tab.targetId,
					kind: mutation.parsed.kind,
					key,
					value
				});
				res.json({
					ok: true,
					targetId: tab.targetId
				});
			}
		});
	});
	app.post("/storage/:kind/clear", async (req, res) => {
		const mutation = parseStorageMutationFromRequest(req, res);
		if (!mutation) return;
		await withPlaywrightRouteContext({
			req,
			res,
			ctx,
			targetId: mutation.parsed.targetId,
			feature: "storage clear",
			run: async ({ cdpUrl, tab, pw }) => {
				await pw.storageClearViaPlaywright({
					cdpUrl,
					targetId: tab.targetId,
					kind: mutation.parsed.kind
				});
				res.json({
					ok: true,
					targetId: tab.targetId
				});
			}
		});
	});
	app.post("/set/offline", async (req, res) => {
		const body = readBody(req);
		const targetId = resolveTargetIdFromBody(body);
		const offline = toBoolean(body.offline);
		if (offline === void 0) return jsonError(res, 400, "offline is required");
		await withPlaywrightRouteContext({
			req,
			res,
			ctx,
			targetId,
			feature: "offline",
			run: async ({ cdpUrl, tab, pw }) => {
				await pw.setOfflineViaPlaywright({
					cdpUrl,
					targetId: tab.targetId,
					offline
				});
				res.json({
					ok: true,
					targetId: tab.targetId
				});
			}
		});
	});
	app.post("/set/headers", async (req, res) => {
		const body = readBody(req);
		const targetId = resolveTargetIdFromBody(body);
		const headers = body.headers && typeof body.headers === "object" && !Array.isArray(body.headers) ? body.headers : null;
		if (!headers) return jsonError(res, 400, "headers is required");
		const parsed = {};
		for (const [k, v] of Object.entries(headers)) if (typeof v === "string") parsed[k] = v;
		await withPlaywrightRouteContext({
			req,
			res,
			ctx,
			targetId,
			feature: "headers",
			run: async ({ cdpUrl, tab, pw }) => {
				await pw.setExtraHTTPHeadersViaPlaywright({
					cdpUrl,
					targetId: tab.targetId,
					headers: parsed
				});
				res.json({
					ok: true,
					targetId: tab.targetId
				});
			}
		});
	});
	app.post("/set/credentials", async (req, res) => {
		const body = readBody(req);
		const targetId = resolveTargetIdFromBody(body);
		const clear = toBoolean(body.clear) ?? false;
		const username = toStringOrEmpty(body.username) || void 0;
		const password = typeof body.password === "string" ? body.password : void 0;
		await withPlaywrightRouteContext({
			req,
			res,
			ctx,
			targetId,
			feature: "http credentials",
			run: async ({ cdpUrl, tab, pw }) => {
				await pw.setHttpCredentialsViaPlaywright({
					cdpUrl,
					targetId: tab.targetId,
					username,
					password,
					clear
				});
				res.json({
					ok: true,
					targetId: tab.targetId
				});
			}
		});
	});
	app.post("/set/geolocation", async (req, res) => {
		const body = readBody(req);
		const targetId = resolveTargetIdFromBody(body);
		const clear = toBoolean(body.clear) ?? false;
		const latitude = toNumber(body.latitude);
		const longitude = toNumber(body.longitude);
		const accuracy = toNumber(body.accuracy) ?? void 0;
		const origin = toStringOrEmpty(body.origin) || void 0;
		await withPlaywrightRouteContext({
			req,
			res,
			ctx,
			targetId,
			feature: "geolocation",
			run: async ({ cdpUrl, tab, pw }) => {
				await pw.setGeolocationViaPlaywright({
					cdpUrl,
					targetId: tab.targetId,
					latitude,
					longitude,
					accuracy,
					origin,
					clear
				});
				res.json({
					ok: true,
					targetId: tab.targetId
				});
			}
		});
	});
	app.post("/set/media", async (req, res) => {
		const body = readBody(req);
		const targetId = resolveTargetIdFromBody(body);
		const schemeRaw = toStringOrEmpty(body.colorScheme);
		const colorScheme = schemeRaw === "dark" || schemeRaw === "light" || schemeRaw === "no-preference" ? schemeRaw : schemeRaw === "none" ? null : void 0;
		if (colorScheme === void 0) return jsonError(res, 400, "colorScheme must be dark|light|no-preference|none");
		await withPlaywrightRouteContext({
			req,
			res,
			ctx,
			targetId,
			feature: "media emulation",
			run: async ({ cdpUrl, tab, pw }) => {
				await pw.emulateMediaViaPlaywright({
					cdpUrl,
					targetId: tab.targetId,
					colorScheme
				});
				res.json({
					ok: true,
					targetId: tab.targetId
				});
			}
		});
	});
	app.post("/set/timezone", async (req, res) => {
		const body = readBody(req);
		const targetId = resolveTargetIdFromBody(body);
		const timezoneId = toStringOrEmpty(body.timezoneId);
		if (!timezoneId) return jsonError(res, 400, "timezoneId is required");
		await withPlaywrightRouteContext({
			req,
			res,
			ctx,
			targetId,
			feature: "timezone",
			run: async ({ cdpUrl, tab, pw }) => {
				await pw.setTimezoneViaPlaywright({
					cdpUrl,
					targetId: tab.targetId,
					timezoneId
				});
				res.json({
					ok: true,
					targetId: tab.targetId
				});
			}
		});
	});
	app.post("/set/locale", async (req, res) => {
		const body = readBody(req);
		const targetId = resolveTargetIdFromBody(body);
		const locale = toStringOrEmpty(body.locale);
		if (!locale) return jsonError(res, 400, "locale is required");
		await withPlaywrightRouteContext({
			req,
			res,
			ctx,
			targetId,
			feature: "locale",
			run: async ({ cdpUrl, tab, pw }) => {
				await pw.setLocaleViaPlaywright({
					cdpUrl,
					targetId: tab.targetId,
					locale
				});
				res.json({
					ok: true,
					targetId: tab.targetId
				});
			}
		});
	});
	app.post("/set/device", async (req, res) => {
		const body = readBody(req);
		const targetId = resolveTargetIdFromBody(body);
		const name = toStringOrEmpty(body.name);
		if (!name) return jsonError(res, 400, "name is required");
		await withPlaywrightRouteContext({
			req,
			res,
			ctx,
			targetId,
			feature: "device emulation",
			run: async ({ cdpUrl, tab, pw }) => {
				await pw.setDeviceViaPlaywright({
					cdpUrl,
					targetId: tab.targetId,
					name
				});
				res.json({
					ok: true,
					targetId: tab.targetId
				});
			}
		});
	});
}

//#endregion
//#region src/browser/routes/agent.ts
function registerBrowserAgentRoutes(app, ctx) {
	registerBrowserAgentSnapshotRoutes(app, ctx);
	registerBrowserAgentActRoutes(app, ctx);
	registerBrowserAgentDebugRoutes(app, ctx);
	registerBrowserAgentStorageRoutes(app, ctx);
}

//#endregion
//#region src/config/port-defaults.ts
function isValidPort(port) {
	return Number.isFinite(port) && port > 0 && port <= 65535;
}
function clampPort(port, fallback) {
	return isValidPort(port) ? port : fallback;
}
function derivePort(base, offset, fallback) {
	return clampPort(base + offset, fallback);
}
const DEFAULT_BROWSER_CONTROL_PORT = 18791;
const DEFAULT_BROWSER_CDP_PORT_RANGE_START = 18800;
const DEFAULT_BROWSER_CDP_PORT_RANGE_END = 18899;
function deriveDefaultBrowserControlPort(gatewayPort) {
	return derivePort(gatewayPort, 2, DEFAULT_BROWSER_CONTROL_PORT);
}
function deriveDefaultBrowserCdpPortRange(browserControlPort) {
	const start = derivePort(browserControlPort, 9, DEFAULT_BROWSER_CDP_PORT_RANGE_START);
	const end = clampPort(start + (DEFAULT_BROWSER_CDP_PORT_RANGE_END - DEFAULT_BROWSER_CDP_PORT_RANGE_START), DEFAULT_BROWSER_CDP_PORT_RANGE_END);
	if (end < start) return {
		start,
		end: start
	};
	return {
		start,
		end
	};
}

//#endregion
//#region src/browser/profiles.ts
/**
* CDP port allocation for browser profiles.
*
* Default port range: 18800-18899 (100 profiles max)
* Ports are allocated once at profile creation and persisted in config.
* Multi-instance: callers may pass an explicit range to avoid collisions.
*
* Reserved ports (do not use for CDP):
*   18789 - Gateway WebSocket
*   18790 - Bridge
*   18791 - Browser control server
*   18792-18799 - Reserved for future one-off services (canvas at 18793)
*/
const CDP_PORT_RANGE_START = 18800;
const CDP_PORT_RANGE_END = 18899;
const PROFILE_NAME_REGEX = /^[a-z0-9][a-z0-9-]*$/;
function isValidProfileName(name) {
	if (!name || name.length > 64) return false;
	return PROFILE_NAME_REGEX.test(name);
}
function allocateCdpPort(usedPorts, range) {
	const start = range?.start ?? CDP_PORT_RANGE_START;
	const end = range?.end ?? CDP_PORT_RANGE_END;
	if (!Number.isFinite(start) || !Number.isFinite(end) || start <= 0 || end <= 0) return null;
	if (start > end) return null;
	for (let port = start; port <= end; port++) if (!usedPorts.has(port)) return port;
	return null;
}
function getUsedPorts(profiles) {
	if (!profiles) return /* @__PURE__ */ new Set();
	const used = /* @__PURE__ */ new Set();
	for (const profile of Object.values(profiles)) {
		if (typeof profile.cdpPort === "number") {
			used.add(profile.cdpPort);
			continue;
		}
		const rawUrl = profile.cdpUrl?.trim();
		if (!rawUrl) continue;
		try {
			const parsed = new URL(rawUrl);
			const port = parsed.port && Number.parseInt(parsed.port, 10) > 0 ? Number.parseInt(parsed.port, 10) : parsed.protocol === "https:" ? 443 : 80;
			if (!Number.isNaN(port) && port > 0 && port <= 65535) used.add(port);
		} catch {}
	}
	return used;
}
const PROFILE_COLORS = [
	"#FF4500",
	"#0066CC",
	"#00AA00",
	"#9933FF",
	"#FF6699",
	"#00CCCC",
	"#FF9900",
	"#6666FF",
	"#CC3366",
	"#339966"
];
function allocateColor(usedColors) {
	for (const color of PROFILE_COLORS) if (!usedColors.has(color.toUpperCase())) return color;
	return PROFILE_COLORS[usedColors.size % PROFILE_COLORS.length] ?? PROFILE_COLORS[0];
}
function getUsedColors(profiles) {
	if (!profiles) return /* @__PURE__ */ new Set();
	return new Set(Object.values(profiles).map((p) => p.color.toUpperCase()));
}

//#endregion
//#region src/browser/config.ts
function normalizeHexColor(raw) {
	const value = (raw ?? "").trim();
	if (!value) return DEFAULT_OPENCLAW_BROWSER_COLOR;
	const normalized = value.startsWith("#") ? value : `#${value}`;
	if (!/^#[0-9a-fA-F]{6}$/.test(normalized)) return DEFAULT_OPENCLAW_BROWSER_COLOR;
	return normalized.toUpperCase();
}
function normalizeTimeoutMs(raw, fallback) {
	const value = typeof raw === "number" && Number.isFinite(raw) ? Math.floor(raw) : fallback;
	return value < 0 ? fallback : value;
}
function normalizeStringList(raw) {
	if (!Array.isArray(raw) || raw.length === 0) return;
	const values = raw.map((value) => value.trim()).filter((value) => value.length > 0);
	return values.length > 0 ? values : void 0;
}
function resolveBrowserSsrFPolicy(cfg) {
	const allowPrivateNetwork = cfg?.ssrfPolicy?.allowPrivateNetwork;
	const dangerouslyAllowPrivateNetwork = cfg?.ssrfPolicy?.dangerouslyAllowPrivateNetwork;
	const allowedHostnames = normalizeStringList(cfg?.ssrfPolicy?.allowedHostnames);
	const hostnameAllowlist = normalizeStringList(cfg?.ssrfPolicy?.hostnameAllowlist);
	const hasExplicitPrivateSetting = allowPrivateNetwork !== void 0 || dangerouslyAllowPrivateNetwork !== void 0;
	const resolvedAllowPrivateNetwork = dangerouslyAllowPrivateNetwork === true || allowPrivateNetwork === true || !hasExplicitPrivateSetting;
	if (!resolvedAllowPrivateNetwork && !hasExplicitPrivateSetting && !allowedHostnames && !hostnameAllowlist) return;
	return {
		...resolvedAllowPrivateNetwork ? { dangerouslyAllowPrivateNetwork: true } : {},
		...allowedHostnames ? { allowedHostnames } : {},
		...hostnameAllowlist ? { hostnameAllowlist } : {}
	};
}
function parseHttpUrl(raw, label) {
	const trimmed = raw.trim();
	const parsed = new URL(trimmed);
	if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new Error(`${label} must be http(s), got: ${parsed.protocol.replace(":", "")}`);
	const port = parsed.port && Number.parseInt(parsed.port, 10) > 0 ? Number.parseInt(parsed.port, 10) : parsed.protocol === "https:" ? 443 : 80;
	if (Number.isNaN(port) || port <= 0 || port > 65535) throw new Error(`${label} has invalid port: ${parsed.port}`);
	return {
		parsed,
		port,
		normalized: parsed.toString().replace(/\/$/, "")
	};
}
/**
* Ensure the default "openclaw" profile exists in the profiles map.
* Auto-creates it with the legacy CDP port (from browser.cdpUrl) or first port if missing.
*/
function ensureDefaultProfile(profiles, defaultColor, legacyCdpPort, derivedDefaultCdpPort) {
	const result = { ...profiles };
	if (!result[DEFAULT_OPENCLAW_BROWSER_PROFILE_NAME]) result[DEFAULT_OPENCLAW_BROWSER_PROFILE_NAME] = {
		cdpPort: legacyCdpPort ?? derivedDefaultCdpPort ?? CDP_PORT_RANGE_START,
		color: defaultColor
	};
	return result;
}
/**
* Ensure a built-in "chrome" profile exists for the Chrome extension relay.
*
* Note: this is an OpenClaw browser profile (routing config), not a Chrome user profile.
* It points at the local relay CDP endpoint (controlPort + 1).
*/
function ensureDefaultChromeExtensionProfile(profiles, controlPort) {
	const result = { ...profiles };
	if (result.chrome) return result;
	const relayPort = controlPort + 1;
	if (!Number.isFinite(relayPort) || relayPort <= 0 || relayPort > 65535) return result;
	if (getUsedPorts(result).has(relayPort)) return result;
	result.chrome = {
		driver: "extension",
		cdpUrl: `http://127.0.0.1:${relayPort}`,
		color: "#00AA00"
	};
	return result;
}
function resolveBrowserConfig(cfg, rootConfig) {
	const enabled = cfg?.enabled ?? DEFAULT_OPENCLAW_BROWSER_ENABLED;
	const evaluateEnabled = cfg?.evaluateEnabled ?? DEFAULT_BROWSER_EVALUATE_ENABLED;
	const controlPort = deriveDefaultBrowserControlPort(resolveGatewayPort(rootConfig) ?? DEFAULT_BROWSER_CONTROL_PORT);
	const defaultColor = normalizeHexColor(cfg?.color);
	const remoteCdpTimeoutMs = normalizeTimeoutMs(cfg?.remoteCdpTimeoutMs, 1500);
	const remoteCdpHandshakeTimeoutMs = normalizeTimeoutMs(cfg?.remoteCdpHandshakeTimeoutMs, Math.max(2e3, remoteCdpTimeoutMs * 2));
	const derivedCdpRange = deriveDefaultBrowserCdpPortRange(controlPort);
	const rawCdpUrl = (cfg?.cdpUrl ?? "").trim();
	let cdpInfo;
	if (rawCdpUrl) cdpInfo = parseHttpUrl(rawCdpUrl, "browser.cdpUrl");
	else {
		const derivedPort = controlPort + 1;
		if (derivedPort > 65535) throw new Error(`Derived CDP port (${derivedPort}) is too high; check gateway port configuration.`);
		const derived = new URL(`http://127.0.0.1:${derivedPort}`);
		cdpInfo = {
			parsed: derived,
			port: derivedPort,
			normalized: derived.toString().replace(/\/$/, "")
		};
	}
	const headless = cfg?.headless === true;
	const noSandbox = cfg?.noSandbox === true;
	const attachOnly = cfg?.attachOnly === true;
	const executablePath = cfg?.executablePath?.trim() || void 0;
	const defaultProfileFromConfig = cfg?.defaultProfile?.trim() || void 0;
	const legacyCdpPort = rawCdpUrl ? cdpInfo.port : void 0;
	const profiles = ensureDefaultChromeExtensionProfile(ensureDefaultProfile(cfg?.profiles, defaultColor, legacyCdpPort, derivedCdpRange.start), controlPort);
	const cdpProtocol = cdpInfo.parsed.protocol === "https:" ? "https" : "http";
	const defaultProfile = defaultProfileFromConfig ?? (profiles[DEFAULT_BROWSER_DEFAULT_PROFILE_NAME] ? DEFAULT_BROWSER_DEFAULT_PROFILE_NAME : DEFAULT_OPENCLAW_BROWSER_PROFILE_NAME);
	const extraArgs = Array.isArray(cfg?.extraArgs) ? cfg.extraArgs.filter((a) => typeof a === "string" && a.trim().length > 0) : [];
	const ssrfPolicy = resolveBrowserSsrFPolicy(cfg);
	return {
		enabled,
		evaluateEnabled,
		controlPort,
		cdpProtocol,
		cdpHost: cdpInfo.parsed.hostname,
		cdpIsLoopback: isLoopbackHost(cdpInfo.parsed.hostname),
		remoteCdpTimeoutMs,
		remoteCdpHandshakeTimeoutMs,
		color: defaultColor,
		executablePath,
		headless,
		noSandbox,
		attachOnly,
		defaultProfile,
		profiles,
		ssrfPolicy,
		extraArgs
	};
}
/**
* Resolve a profile by name from the config.
* Returns null if the profile doesn't exist.
*/
function resolveProfile(resolved, profileName) {
	const profile = resolved.profiles[profileName];
	if (!profile) return null;
	const rawProfileUrl = profile.cdpUrl?.trim() ?? "";
	let cdpHost = resolved.cdpHost;
	let cdpPort = profile.cdpPort ?? 0;
	let cdpUrl = "";
	const driver = profile.driver === "extension" ? "extension" : "openclaw";
	if (rawProfileUrl) {
		const parsed = parseHttpUrl(rawProfileUrl, `browser.profiles.${profileName}.cdpUrl`);
		cdpHost = parsed.parsed.hostname;
		cdpPort = parsed.port;
		cdpUrl = parsed.normalized;
	} else if (cdpPort) cdpUrl = `${resolved.cdpProtocol}://${resolved.cdpHost}:${cdpPort}`;
	else throw new Error(`Profile "${profileName}" must define cdpPort or cdpUrl.`);
	return {
		name: profileName,
		cdpPort,
		cdpUrl,
		cdpHost,
		cdpIsLoopback: isLoopbackHost(cdpHost),
		color: profile.color,
		driver
	};
}

//#endregion
//#region src/browser/profiles-service.ts
const HEX_COLOR_RE = /^#[0-9A-Fa-f]{6}$/;
function createBrowserProfilesService(ctx) {
	const listProfiles = async () => {
		return await ctx.listProfiles();
	};
	const createProfile = async (params) => {
		const name = params.name.trim();
		const rawCdpUrl = params.cdpUrl?.trim() || void 0;
		const driver = params.driver === "extension" ? "extension" : void 0;
		if (!isValidProfileName(name)) throw new Error("invalid profile name: use lowercase letters, numbers, and hyphens only");
		const state = ctx.state();
		const resolvedProfiles = state.resolved.profiles;
		if (name in resolvedProfiles) throw new Error(`profile "${name}" already exists`);
		const cfg = loadConfig();
		const rawProfiles = cfg.browser?.profiles ?? {};
		if (name in rawProfiles) throw new Error(`profile "${name}" already exists`);
		const usedColors = getUsedColors(resolvedProfiles);
		const profileColor = params.color && HEX_COLOR_RE.test(params.color) ? params.color : allocateColor(usedColors);
		let profileConfig;
		if (rawCdpUrl) profileConfig = {
			cdpUrl: parseHttpUrl(rawCdpUrl, "browser.profiles.cdpUrl").normalized,
			...driver ? { driver } : {},
			color: profileColor
		};
		else {
			const cdpPort = allocateCdpPort(getUsedPorts(resolvedProfiles), deriveDefaultBrowserCdpPortRange(state.resolved.controlPort));
			if (cdpPort === null) throw new Error("no available CDP ports in range");
			profileConfig = {
				cdpPort,
				...driver ? { driver } : {},
				color: profileColor
			};
		}
		await writeConfigFile({
			...cfg,
			browser: {
				...cfg.browser,
				profiles: {
					...rawProfiles,
					[name]: profileConfig
				}
			}
		});
		state.resolved.profiles[name] = profileConfig;
		const resolved = resolveProfile(state.resolved, name);
		if (!resolved) throw new Error(`profile "${name}" not found after creation`);
		return {
			ok: true,
			profile: name,
			cdpPort: resolved.cdpPort,
			cdpUrl: resolved.cdpUrl,
			color: resolved.color,
			isRemote: !resolved.cdpIsLoopback
		};
	};
	const deleteProfile = async (nameRaw) => {
		const name = nameRaw.trim();
		if (!name) throw new Error("profile name is required");
		if (!isValidProfileName(name)) throw new Error("invalid profile name");
		const cfg = loadConfig();
		const profiles = cfg.browser?.profiles ?? {};
		if (!(name in profiles)) throw new Error(`profile "${name}" not found`);
		if (name === (cfg.browser?.defaultProfile ?? DEFAULT_BROWSER_DEFAULT_PROFILE_NAME)) throw new Error(`cannot delete the default profile "${name}"; change browser.defaultProfile first`);
		let deleted = false;
		const state = ctx.state();
		if (resolveProfile(state.resolved, name)?.cdpIsLoopback) {
			try {
				await ctx.forProfile(name).stopRunningBrowser();
			} catch {}
			const userDataDir = resolveOpenClawUserDataDir(name);
			const profileDir = path.dirname(userDataDir);
			if (fs.existsSync(profileDir)) {
				await movePathToTrash(profileDir);
				deleted = true;
			}
		}
		const { [name]: _removed, ...remainingProfiles } = profiles;
		await writeConfigFile({
			...cfg,
			browser: {
				...cfg.browser,
				profiles: remainingProfiles
			}
		});
		delete state.resolved.profiles[name];
		state.profiles.delete(name);
		return {
			ok: true,
			profile: name,
			deleted
		};
	};
	return {
		listProfiles,
		createProfile,
		deleteProfile
	};
}

//#endregion
//#region src/browser/routes/basic.ts
async function withBasicProfileRoute(params) {
	const profileCtx = resolveProfileContext(params.req, params.res, params.ctx);
	if (!profileCtx) return;
	try {
		await params.run(profileCtx);
	} catch (err) {
		jsonError(params.res, 500, String(err));
	}
}
function registerBrowserBasicRoutes(app, ctx) {
	app.get("/profiles", async (_req, res) => {
		try {
			const profiles = await createBrowserProfilesService(ctx).listProfiles();
			res.json({ profiles });
		} catch (err) {
			jsonError(res, 500, String(err));
		}
	});
	app.get("/", async (req, res) => {
		let current;
		try {
			current = ctx.state();
		} catch {
			return jsonError(res, 503, "browser server not started");
		}
		const profileCtx = getProfileContext(req, ctx);
		if ("error" in profileCtx) return jsonError(res, profileCtx.status, profileCtx.error);
		const [cdpHttp, cdpReady] = await Promise.all([profileCtx.isHttpReachable(300), profileCtx.isReachable(600)]);
		const profileState = current.profiles.get(profileCtx.profile.name);
		let detectedBrowser = null;
		let detectedExecutablePath = null;
		let detectError = null;
		try {
			const detected = resolveBrowserExecutableForPlatform(current.resolved, process.platform);
			if (detected) {
				detectedBrowser = detected.kind;
				detectedExecutablePath = detected.path;
			}
		} catch (err) {
			detectError = String(err);
		}
		res.json({
			enabled: current.resolved.enabled,
			profile: profileCtx.profile.name,
			running: cdpReady,
			cdpReady,
			cdpHttp,
			pid: profileState?.running?.pid ?? null,
			cdpPort: profileCtx.profile.cdpPort,
			cdpUrl: profileCtx.profile.cdpUrl,
			chosenBrowser: profileState?.running?.exe.kind ?? null,
			detectedBrowser,
			detectedExecutablePath,
			detectError,
			userDataDir: profileState?.running?.userDataDir ?? null,
			color: profileCtx.profile.color,
			headless: current.resolved.headless,
			noSandbox: current.resolved.noSandbox,
			executablePath: current.resolved.executablePath ?? null,
			attachOnly: current.resolved.attachOnly
		});
	});
	app.post("/start", async (req, res) => {
		await withBasicProfileRoute({
			req,
			res,
			ctx,
			run: async (profileCtx) => {
				await profileCtx.ensureBrowserAvailable();
				res.json({
					ok: true,
					profile: profileCtx.profile.name
				});
			}
		});
	});
	app.post("/stop", async (req, res) => {
		await withBasicProfileRoute({
			req,
			res,
			ctx,
			run: async (profileCtx) => {
				const result = await profileCtx.stopRunningBrowser();
				res.json({
					ok: true,
					stopped: result.stopped,
					profile: profileCtx.profile.name
				});
			}
		});
	});
	app.post("/reset-profile", async (req, res) => {
		await withBasicProfileRoute({
			req,
			res,
			ctx,
			run: async (profileCtx) => {
				const result = await profileCtx.resetProfile();
				res.json({
					ok: true,
					profile: profileCtx.profile.name,
					...result
				});
			}
		});
	});
	app.post("/profiles/create", async (req, res) => {
		const name = toStringOrEmpty(req.body?.name);
		const color = toStringOrEmpty(req.body?.color);
		const cdpUrl = toStringOrEmpty(req.body?.cdpUrl);
		const driver = toStringOrEmpty(req.body?.driver);
		if (!name) return jsonError(res, 400, "name is required");
		try {
			const result = await createBrowserProfilesService(ctx).createProfile({
				name,
				color: color || void 0,
				cdpUrl: cdpUrl || void 0,
				driver: driver === "extension" ? "extension" : void 0
			});
			res.json(result);
		} catch (err) {
			const msg = String(err);
			if (msg.includes("already exists")) return jsonError(res, 409, msg);
			if (msg.includes("invalid profile name")) return jsonError(res, 400, msg);
			if (msg.includes("no available CDP ports")) return jsonError(res, 507, msg);
			if (msg.includes("cdpUrl")) return jsonError(res, 400, msg);
			jsonError(res, 500, msg);
		}
	});
	app.delete("/profiles/:name", async (req, res) => {
		const name = toStringOrEmpty(req.params.name);
		if (!name) return jsonError(res, 400, "profile name is required");
		try {
			const result = await createBrowserProfilesService(ctx).deleteProfile(name);
			res.json(result);
		} catch (err) {
			const msg = String(err);
			if (msg.includes("invalid profile name")) return jsonError(res, 400, msg);
			if (msg.includes("default profile")) return jsonError(res, 400, msg);
			if (msg.includes("not found")) return jsonError(res, 404, msg);
			jsonError(res, 500, msg);
		}
	});
}

//#endregion
//#region src/browser/routes/tabs.ts
function resolveTabsProfileContext(req, res, ctx) {
	const profileCtx = getProfileContext(req, ctx);
	if ("error" in profileCtx) {
		jsonError(res, profileCtx.status, profileCtx.error);
		return null;
	}
	return profileCtx;
}
function handleTabsRouteError(ctx, res, err, opts) {
	if (opts?.mapTabError) {
		const mapped = ctx.mapTabError(err);
		if (mapped) return jsonError(res, mapped.status, mapped.message);
	}
	return jsonError(res, 500, String(err));
}
async function withTabsProfileRoute(params) {
	const profileCtx = resolveTabsProfileContext(params.req, params.res, params.ctx);
	if (!profileCtx) return;
	try {
		await params.run(profileCtx);
	} catch (err) {
		handleTabsRouteError(params.ctx, params.res, err, { mapTabError: params.mapTabError });
	}
}
async function ensureBrowserRunning(profileCtx, res) {
	if (!await profileCtx.isReachable(300)) {
		jsonError(res, 409, "browser not running");
		return false;
	}
	return true;
}
function resolveIndexedTab(tabs, index) {
	return typeof index === "number" ? tabs[index] : tabs.at(0);
}
function parseRequiredTargetId(res, rawTargetId) {
	const targetId = toStringOrEmpty(rawTargetId);
	if (!targetId) {
		jsonError(res, 400, "targetId is required");
		return null;
	}
	return targetId;
}
async function runTabTargetMutation(params) {
	await withTabsProfileRoute({
		req: params.req,
		res: params.res,
		ctx: params.ctx,
		mapTabError: true,
		run: async (profileCtx) => {
			if (!await ensureBrowserRunning(profileCtx, params.res)) return;
			await params.mutate(profileCtx, params.targetId);
			params.res.json({ ok: true });
		}
	});
}
function registerBrowserTabRoutes(app, ctx) {
	app.get("/tabs", async (req, res) => {
		await withTabsProfileRoute({
			req,
			res,
			ctx,
			run: async (profileCtx) => {
				if (!await profileCtx.isReachable(300)) return res.json({
					running: false,
					tabs: []
				});
				const tabs = await profileCtx.listTabs();
				res.json({
					running: true,
					tabs
				});
			}
		});
	});
	app.post("/tabs/open", async (req, res) => {
		const url = toStringOrEmpty(req.body?.url);
		if (!url) return jsonError(res, 400, "url is required");
		await withTabsProfileRoute({
			req,
			res,
			ctx,
			mapTabError: true,
			run: async (profileCtx) => {
				await profileCtx.ensureBrowserAvailable();
				const tab = await profileCtx.openTab(url);
				res.json(tab);
			}
		});
	});
	app.post("/tabs/focus", async (req, res) => {
		const targetId = parseRequiredTargetId(res, req.body?.targetId);
		if (!targetId) return;
		await runTabTargetMutation({
			req,
			res,
			ctx,
			targetId,
			mutate: async (profileCtx, id) => {
				await profileCtx.focusTab(id);
			}
		});
	});
	app.delete("/tabs/:targetId", async (req, res) => {
		const targetId = parseRequiredTargetId(res, req.params.targetId);
		if (!targetId) return;
		await runTabTargetMutation({
			req,
			res,
			ctx,
			targetId,
			mutate: async (profileCtx, id) => {
				await profileCtx.closeTab(id);
			}
		});
	});
	app.post("/tabs/action", async (req, res) => {
		const action = toStringOrEmpty(req.body?.action);
		const index = toNumber(req.body?.index);
		await withTabsProfileRoute({
			req,
			res,
			ctx,
			mapTabError: true,
			run: async (profileCtx) => {
				if (action === "list") {
					if (!await profileCtx.isReachable(300)) return res.json({
						ok: true,
						tabs: []
					});
					const tabs = await profileCtx.listTabs();
					return res.json({
						ok: true,
						tabs
					});
				}
				if (action === "new") {
					await profileCtx.ensureBrowserAvailable();
					const tab = await profileCtx.openTab("about:blank");
					return res.json({
						ok: true,
						tab
					});
				}
				if (action === "close") {
					const target = resolveIndexedTab(await profileCtx.listTabs(), index);
					if (!target) return jsonError(res, 404, "tab not found");
					await profileCtx.closeTab(target.targetId);
					return res.json({
						ok: true,
						targetId: target.targetId
					});
				}
				if (action === "select") {
					if (typeof index !== "number") return jsonError(res, 400, "index is required");
					const target = (await profileCtx.listTabs())[index];
					if (!target) return jsonError(res, 404, "tab not found");
					await profileCtx.focusTab(target.targetId);
					return res.json({
						ok: true,
						targetId: target.targetId
					});
				}
				return jsonError(res, 400, "unknown tab action");
			}
		});
	});
}

//#endregion
//#region src/browser/routes/index.ts
function registerBrowserRoutes(app, ctx) {
	registerBrowserBasicRoutes(app, ctx);
	registerBrowserTabRoutes(app, ctx);
	registerBrowserAgentRoutes(app, ctx);
}

//#endregion
//#region src/browser/resolved-config-refresh.ts
function applyResolvedConfig(current, freshResolved) {
	current.resolved = freshResolved;
	for (const [name, runtime] of current.profiles) {
		const nextProfile = resolveProfile(freshResolved, name);
		if (nextProfile) {
			runtime.profile = nextProfile;
			continue;
		}
		if (!runtime.running) current.profiles.delete(name);
	}
}
function refreshResolvedBrowserConfigFromDisk(params) {
	if (!params.refreshConfigFromDisk) return;
	const cfg = params.mode === "fresh" ? createConfigIO().loadConfig() : loadConfig();
	const freshResolved = resolveBrowserConfig(cfg.browser, cfg);
	applyResolvedConfig(params.current, freshResolved);
}
function resolveBrowserProfileWithHotReload(params) {
	refreshResolvedBrowserConfigFromDisk({
		current: params.current,
		refreshConfigFromDisk: params.refreshConfigFromDisk,
		mode: "cached"
	});
	let profile = resolveProfile(params.current.resolved, params.name);
	if (profile) return profile;
	refreshResolvedBrowserConfigFromDisk({
		current: params.current,
		refreshConfigFromDisk: params.refreshConfigFromDisk,
		mode: "fresh"
	});
	profile = resolveProfile(params.current.resolved, params.name);
	return profile;
}

//#endregion
//#region src/browser/target-id.ts
function resolveTargetIdFromTabs(input, tabs) {
	const needle = input.trim();
	if (!needle) return {
		ok: false,
		reason: "not_found"
	};
	const exact = tabs.find((t) => t.targetId === needle);
	if (exact) return {
		ok: true,
		targetId: exact.targetId
	};
	const lower = needle.toLowerCase();
	const matches = tabs.map((t) => t.targetId).filter((id) => id.toLowerCase().startsWith(lower));
	const only = matches.length === 1 ? matches[0] : void 0;
	if (only) return {
		ok: true,
		targetId: only
	};
	if (matches.length === 0) return {
		ok: false,
		reason: "not_found"
	};
	return {
		ok: false,
		reason: "ambiguous",
		matches
	};
}

//#endregion
//#region src/browser/server-context.ts
function listKnownProfileNames(state) {
	const names = new Set(Object.keys(state.resolved.profiles));
	for (const name of state.profiles.keys()) names.add(name);
	return [...names];
}
/**
* Normalize a CDP WebSocket URL to use the correct base URL.
*/
function normalizeWsUrl(raw, cdpBaseUrl) {
	if (!raw) return;
	try {
		return normalizeCdpWsUrl(raw, cdpBaseUrl);
	} catch {
		return raw;
	}
}
/**
* Create a profile-scoped context for browser operations.
*/
function createProfileContext(opts, profile) {
	const state = () => {
		const current = opts.getState();
		if (!current) throw new Error("Browser server not started");
		return current;
	};
	const getProfileState = () => {
		const current = state();
		let profileState = current.profiles.get(profile.name);
		if (!profileState) {
			profileState = {
				profile,
				running: null,
				lastTargetId: null
			};
			current.profiles.set(profile.name, profileState);
		}
		return profileState;
	};
	const setProfileRunning = (running) => {
		const profileState = getProfileState();
		profileState.running = running;
	};
	const listTabs = async () => {
		if (!profile.cdpIsLoopback) {
			const listPagesViaPlaywright = (await getPwAiModule$1({ mode: "strict" }))?.listPagesViaPlaywright;
			if (typeof listPagesViaPlaywright === "function") return (await listPagesViaPlaywright({ cdpUrl: profile.cdpUrl })).map((p) => ({
				targetId: p.targetId,
				title: p.title,
				url: p.url,
				type: p.type
			}));
		}
		return (await fetchJson(appendCdpPath(profile.cdpUrl, "/json/list"))).map((t) => ({
			targetId: t.id ?? "",
			title: t.title ?? "",
			url: t.url ?? "",
			wsUrl: normalizeWsUrl(t.webSocketDebuggerUrl, profile.cdpUrl),
			type: t.type
		})).filter((t) => Boolean(t.targetId));
	};
	const openTab = async (url) => {
		const ssrfPolicyOpts = withBrowserNavigationPolicy(state().resolved.ssrfPolicy);
		if (!profile.cdpIsLoopback) {
			const createPageViaPlaywright = (await getPwAiModule$1({ mode: "strict" }))?.createPageViaPlaywright;
			if (typeof createPageViaPlaywright === "function") {
				const page = await createPageViaPlaywright({
					cdpUrl: profile.cdpUrl,
					url,
					...ssrfPolicyOpts
				});
				const profileState = getProfileState();
				profileState.lastTargetId = page.targetId;
				return {
					targetId: page.targetId,
					title: page.title,
					url: page.url,
					type: page.type
				};
			}
		}
		const createdViaCdp = await createTargetViaCdp({
			cdpUrl: profile.cdpUrl,
			url,
			...ssrfPolicyOpts
		}).then((r) => r.targetId).catch(() => null);
		if (createdViaCdp) {
			const profileState = getProfileState();
			profileState.lastTargetId = createdViaCdp;
			const deadline = Date.now() + 2e3;
			while (Date.now() < deadline) {
				const found = (await listTabs().catch(() => [])).find((t) => t.targetId === createdViaCdp);
				if (found) {
					await assertBrowserNavigationResultAllowed({
						url: found.url,
						...ssrfPolicyOpts
					});
					return found;
				}
				await new Promise((r) => setTimeout(r, 100));
			}
			return {
				targetId: createdViaCdp,
				title: "",
				url,
				type: "page"
			};
		}
		const encoded = encodeURIComponent(url);
		const endpointUrl = new URL(appendCdpPath(profile.cdpUrl, "/json/new"));
		await assertBrowserNavigationAllowed({
			url,
			...ssrfPolicyOpts
		});
		const endpoint = endpointUrl.search ? (() => {
			endpointUrl.searchParams.set("url", url);
			return endpointUrl.toString();
		})() : `${endpointUrl.toString()}?${encoded}`;
		const created = await fetchJson(endpoint, 1500, { method: "PUT" }).catch(async (err) => {
			if (String(err).includes("HTTP 405")) return await fetchJson(endpoint, 1500);
			throw err;
		});
		if (!created.id) throw new Error("Failed to open tab (missing id)");
		const profileState = getProfileState();
		profileState.lastTargetId = created.id;
		const resolvedUrl = created.url ?? url;
		await assertBrowserNavigationResultAllowed({
			url: resolvedUrl,
			...ssrfPolicyOpts
		});
		return {
			targetId: created.id,
			title: created.title ?? "",
			url: resolvedUrl,
			wsUrl: normalizeWsUrl(created.webSocketDebuggerUrl, profile.cdpUrl),
			type: created.type
		};
	};
	const resolveRemoteHttpTimeout = (timeoutMs) => {
		if (profile.cdpIsLoopback) return timeoutMs ?? 300;
		const resolved = state().resolved;
		if (typeof timeoutMs === "number" && Number.isFinite(timeoutMs)) return Math.max(Math.floor(timeoutMs), resolved.remoteCdpTimeoutMs);
		return resolved.remoteCdpTimeoutMs;
	};
	const resolveRemoteWsTimeout = (timeoutMs) => {
		if (profile.cdpIsLoopback) {
			const base = timeoutMs ?? 300;
			return Math.max(200, Math.min(2e3, base * 2));
		}
		const resolved = state().resolved;
		if (typeof timeoutMs === "number" && Number.isFinite(timeoutMs)) return Math.max(Math.floor(timeoutMs) * 2, resolved.remoteCdpHandshakeTimeoutMs);
		return resolved.remoteCdpHandshakeTimeoutMs;
	};
	const isReachable = async (timeoutMs) => {
		const httpTimeout = resolveRemoteHttpTimeout(timeoutMs);
		const wsTimeout = resolveRemoteWsTimeout(timeoutMs);
		return await isChromeCdpReady(profile.cdpUrl, httpTimeout, wsTimeout);
	};
	const isHttpReachable = async (timeoutMs) => {
		const httpTimeout = resolveRemoteHttpTimeout(timeoutMs);
		return await isChromeReachable(profile.cdpUrl, httpTimeout);
	};
	const attachRunning = (running) => {
		setProfileRunning(running);
		running.proc.on("exit", () => {
			if (!opts.getState()) return;
			if (getProfileState().running?.pid === running.pid) setProfileRunning(null);
		});
	};
	const ensureBrowserAvailable = async () => {
		const current = state();
		const remoteCdp = !profile.cdpIsLoopback;
		const isExtension = profile.driver === "extension";
		const profileState = getProfileState();
		const httpReachable = await isHttpReachable();
		if (isExtension && remoteCdp) throw new Error(`Profile "${profile.name}" uses driver=extension but cdpUrl is not loopback (${profile.cdpUrl}).`);
		if (isExtension) {
			if (!httpReachable) {
				await ensureChromeExtensionRelayServer({ cdpUrl: profile.cdpUrl });
				if (await isHttpReachable(1200)) {} else throw new Error(`Chrome extension relay for profile "${profile.name}" is not reachable at ${profile.cdpUrl}.`);
			}
			if (await isReachable(600)) return;
			throw new Error(`Chrome extension relay is running, but no tab is connected. Click the OpenClaw Chrome extension icon on a tab to attach it (profile "${profile.name}").`);
		}
		if (!httpReachable) {
			if ((current.resolved.attachOnly || remoteCdp) && opts.onEnsureAttachTarget) {
				await opts.onEnsureAttachTarget(profile);
				if (await isHttpReachable(1200)) return;
			}
			if (current.resolved.attachOnly || remoteCdp) throw new Error(remoteCdp ? `Remote CDP for profile "${profile.name}" is not reachable at ${profile.cdpUrl}.` : `Browser attachOnly is enabled and profile "${profile.name}" is not running.`);
			attachRunning(await launchOpenClawChrome(current.resolved, profile));
			return;
		}
		if (await isReachable()) return;
		if (!profileState.running) throw new Error(`Port ${profile.cdpPort} is in use for profile "${profile.name}" but not by openclaw. Run action=reset-profile profile=${profile.name} to kill the process.`);
		if (current.resolved.attachOnly || remoteCdp) {
			if (opts.onEnsureAttachTarget) {
				await opts.onEnsureAttachTarget(profile);
				if (await isReachable(1200)) return;
			}
			throw new Error(remoteCdp ? `Remote CDP websocket for profile "${profile.name}" is not reachable.` : `Browser attachOnly is enabled and CDP websocket for profile "${profile.name}" is not reachable.`);
		}
		await stopOpenClawChrome(profileState.running);
		setProfileRunning(null);
		attachRunning(await launchOpenClawChrome(current.resolved, profile));
		if (!await isReachable(600)) throw new Error(`Chrome CDP websocket for profile "${profile.name}" is not reachable after restart.`);
	};
	const ensureTabAvailable = async (targetId) => {
		await ensureBrowserAvailable();
		const profileState = getProfileState();
		if ((await listTabs()).length === 0) {
			if (profile.driver === "extension") throw new Error(`tab not found (no attached Chrome tabs for profile "${profile.name}"). Click the OpenClaw Browser Relay toolbar icon on the tab you want to control (badge ON).`);
			await openTab("about:blank");
		}
		const tabs = await listTabs();
		const candidates = profile.driver === "extension" || !profile.cdpIsLoopback ? tabs : tabs.filter((t) => Boolean(t.wsUrl));
		const resolveById = (raw) => {
			const resolved = resolveTargetIdFromTabs(raw, candidates);
			if (!resolved.ok) {
				if (resolved.reason === "ambiguous") return "AMBIGUOUS";
				return null;
			}
			return candidates.find((t) => t.targetId === resolved.targetId) ?? null;
		};
		const pickDefault = () => {
			const last = profileState.lastTargetId?.trim() || "";
			const lastResolved = last ? resolveById(last) : null;
			if (lastResolved && lastResolved !== "AMBIGUOUS") return lastResolved;
			return candidates.find((t) => (t.type ?? "page") === "page") ?? candidates.at(0) ?? null;
		};
		let chosen = targetId ? resolveById(targetId) : pickDefault();
		if (!chosen && (profile.driver === "extension" || !profile.cdpIsLoopback) && candidates.length === 1) chosen = candidates[0] ?? null;
		if (chosen === "AMBIGUOUS") throw new Error("ambiguous target id prefix");
		if (!chosen) throw new Error("tab not found");
		profileState.lastTargetId = chosen.targetId;
		return chosen;
	};
	const resolveTargetIdOrThrow = async (targetId) => {
		const resolved = resolveTargetIdFromTabs(targetId, await listTabs());
		if (!resolved.ok) {
			if (resolved.reason === "ambiguous") throw new Error("ambiguous target id prefix");
			throw new Error("tab not found");
		}
		return resolved.targetId;
	};
	const focusTab = async (targetId) => {
		const resolvedTargetId = await resolveTargetIdOrThrow(targetId);
		if (!profile.cdpIsLoopback) {
			const focusPageByTargetIdViaPlaywright = (await getPwAiModule$1({ mode: "strict" }))?.focusPageByTargetIdViaPlaywright;
			if (typeof focusPageByTargetIdViaPlaywright === "function") {
				await focusPageByTargetIdViaPlaywright({
					cdpUrl: profile.cdpUrl,
					targetId: resolvedTargetId
				});
				const profileState = getProfileState();
				profileState.lastTargetId = resolvedTargetId;
				return;
			}
		}
		await fetchOk(appendCdpPath(profile.cdpUrl, `/json/activate/${resolvedTargetId}`));
		const profileState = getProfileState();
		profileState.lastTargetId = resolvedTargetId;
	};
	const closeTab = async (targetId) => {
		const resolvedTargetId = await resolveTargetIdOrThrow(targetId);
		if (!profile.cdpIsLoopback) {
			const closePageByTargetIdViaPlaywright = (await getPwAiModule$1({ mode: "strict" }))?.closePageByTargetIdViaPlaywright;
			if (typeof closePageByTargetIdViaPlaywright === "function") {
				await closePageByTargetIdViaPlaywright({
					cdpUrl: profile.cdpUrl,
					targetId: resolvedTargetId
				});
				return;
			}
		}
		await fetchOk(appendCdpPath(profile.cdpUrl, `/json/close/${resolvedTargetId}`));
	};
	const stopRunningBrowser = async () => {
		if (profile.driver === "extension") return { stopped: await stopChromeExtensionRelayServer({ cdpUrl: profile.cdpUrl }) };
		const profileState = getProfileState();
		if (!profileState.running) return { stopped: false };
		await stopOpenClawChrome(profileState.running);
		setProfileRunning(null);
		return { stopped: true };
	};
	const resetProfile = async () => {
		if (profile.driver === "extension") {
			await stopChromeExtensionRelayServer({ cdpUrl: profile.cdpUrl }).catch(() => {});
			return {
				moved: false,
				from: profile.cdpUrl
			};
		}
		if (!profile.cdpIsLoopback) throw new Error(`reset-profile is only supported for local profiles (profile "${profile.name}" is remote).`);
		const userDataDir = resolveOpenClawUserDataDir(profile.name);
		const profileState = getProfileState();
		if (await isHttpReachable(300) && !profileState.running) try {
			await (await import("./pw-ai-CYmNs6zZ.js")).closePlaywrightBrowserConnection();
		} catch {}
		if (profileState.running) await stopRunningBrowser();
		try {
			await (await import("./pw-ai-CYmNs6zZ.js")).closePlaywrightBrowserConnection();
		} catch {}
		if (!fs.existsSync(userDataDir)) return {
			moved: false,
			from: userDataDir
		};
		return {
			moved: true,
			from: userDataDir,
			to: await movePathToTrash(userDataDir)
		};
	};
	return {
		profile,
		ensureBrowserAvailable,
		ensureTabAvailable,
		isHttpReachable,
		isReachable,
		listTabs,
		openTab,
		focusTab,
		closeTab,
		stopRunningBrowser,
		resetProfile
	};
}
function createBrowserRouteContext(opts) {
	const refreshConfigFromDisk = opts.refreshConfigFromDisk === true;
	const state = () => {
		const current = opts.getState();
		if (!current) throw new Error("Browser server not started");
		return current;
	};
	const forProfile = (profileName) => {
		const current = state();
		const name = profileName ?? current.resolved.defaultProfile;
		const profile = resolveBrowserProfileWithHotReload({
			current,
			refreshConfigFromDisk,
			name
		});
		if (!profile) {
			const available = Object.keys(current.resolved.profiles).join(", ");
			throw new Error(`Profile "${name}" not found. Available profiles: ${available || "(none)"}`);
		}
		return createProfileContext(opts, profile);
	};
	const listProfiles = async () => {
		const current = state();
		refreshResolvedBrowserConfigFromDisk({
			current,
			refreshConfigFromDisk,
			mode: "cached"
		});
		const result = [];
		for (const name of Object.keys(current.resolved.profiles)) {
			const profileState = current.profiles.get(name);
			const profile = resolveProfile(current.resolved, name);
			if (!profile) continue;
			let tabCount = 0;
			let running = false;
			if (profileState?.running) {
				running = true;
				try {
					tabCount = (await createProfileContext(opts, profile).listTabs()).filter((t) => t.type === "page").length;
				} catch {}
			} else try {
				if (await isChromeReachable(profile.cdpUrl, 200)) {
					running = true;
					tabCount = (await createProfileContext(opts, profile).listTabs().catch(() => [])).filter((t) => t.type === "page").length;
				}
			} catch {}
			result.push({
				name,
				cdpPort: profile.cdpPort,
				cdpUrl: profile.cdpUrl,
				color: profile.color,
				running,
				tabCount,
				isDefault: name === current.resolved.defaultProfile,
				isRemote: !profile.cdpIsLoopback
			});
		}
		return result;
	};
	const getDefaultContext = () => forProfile();
	const mapTabError = (err) => {
		if (err instanceof SsrFBlockedError) return {
			status: 400,
			message: err.message
		};
		if (err instanceof InvalidBrowserNavigationUrlError) return {
			status: 400,
			message: err.message
		};
		const msg = String(err);
		if (msg.includes("ambiguous target id prefix")) return {
			status: 409,
			message: "ambiguous target id prefix"
		};
		if (msg.includes("tab not found")) return {
			status: 404,
			message: msg
		};
		if (msg.includes("not found")) return {
			status: 404,
			message: msg
		};
		return null;
	};
	return {
		state,
		forProfile,
		listProfiles,
		ensureBrowserAvailable: () => getDefaultContext().ensureBrowserAvailable(),
		ensureTabAvailable: (targetId) => getDefaultContext().ensureTabAvailable(targetId),
		isHttpReachable: (timeoutMs) => getDefaultContext().isHttpReachable(timeoutMs),
		isReachable: (timeoutMs) => getDefaultContext().isReachable(timeoutMs),
		listTabs: () => getDefaultContext().listTabs(),
		openTab: (url) => getDefaultContext().openTab(url),
		focusTab: (targetId) => getDefaultContext().focusTab(targetId),
		closeTab: (targetId) => getDefaultContext().closeTab(targetId),
		stopRunningBrowser: () => getDefaultContext().stopRunningBrowser(),
		resetProfile: () => getDefaultContext().resetProfile(),
		mapTabError
	};
}

//#endregion
export { resolveProfile as a, ensureGatewayStartupAuth as c, resolveBrowserConfig as i, mergeGatewayTailscaleConfig as l, listKnownProfileNames as n, ensureBrowserControlAuth as o, registerBrowserRoutes as r, resolveBrowserControlAuth as s, createBrowserRouteContext as t };