import { O as escapeRegExp, t as createSubsystemLogger } from "./subsystem-CBhYrL33.js";
import { n as loadConfig } from "./config-CCV5uD38.js";
import { i as formatCliCommand } from "./env-CNGYrGXP.js";
import { v as ensureChromeExtensionRelayServer } from "./chrome-B4N0bMCd.js";
import { d as getBridgeAuthForPort, i as resolveProfile, m as resolveBrowserControlAuth, n as registerBrowserRoutes, p as ensureBrowserControlAuth, r as resolveBrowserConfig, t as createBrowserRouteContext } from "./server-context-Az6ygVCq.js";

//#region src/browser/client-actions-url.ts
function buildProfileQuery$1(profile) {
	return profile ? `?profile=${encodeURIComponent(profile)}` : "";
}
function withBaseUrl$1(baseUrl, path) {
	const trimmed = baseUrl?.trim();
	if (!trimmed) return path;
	return `${trimmed.replace(/\/$/, "")}${path}`;
}

//#endregion
//#region src/browser/server-lifecycle.ts
async function ensureExtensionRelayForProfiles(params) {
	for (const name of Object.keys(params.resolved.profiles)) {
		const profile = resolveProfile(params.resolved, name);
		if (!profile || profile.driver !== "extension") continue;
		await ensureChromeExtensionRelayServer({
			cdpUrl: profile.cdpUrl,
			allowRemote: profile.allowRemote
		}).catch((err) => {
			params.onWarn(`Chrome extension relay init failed for profile "${name}": ${String(err)}`);
		});
	}
}

//#endregion
//#region src/browser/control-service.ts
let state = null;
const logService = createSubsystemLogger("browser").child("service");
function createBrowserControlContext() {
	return createBrowserRouteContext({
		getState: () => state,
		refreshConfigFromDisk: true
	});
}
async function startBrowserControlServiceFromConfig() {
	if (state) return state;
	const cfg = loadConfig();
	const resolved = resolveBrowserConfig(cfg.browser, cfg);
	if (!resolved.enabled) return null;
	try {
		if ((await ensureBrowserControlAuth({ cfg })).generatedToken) logService.info("No browser auth configured; generated gateway.auth.token automatically.");
	} catch (err) {
		logService.warn(`failed to auto-configure browser auth: ${String(err)}`);
	}
	state = {
		server: null,
		port: resolved.controlPort,
		resolved,
		profiles: /* @__PURE__ */ new Map()
	};
	await ensureExtensionRelayForProfiles({
		resolved,
		onWarn: (message) => logService.warn(message)
	});
	logService.info(`Browser control service ready (profiles=${Object.keys(resolved.profiles).length}, default=${resolved.defaultProfile})`);
	const defaultProfile = resolveProfile(resolved, resolved.defaultProfile);
	if (defaultProfile && defaultProfile.driver !== "extension" && defaultProfile.cdpIsLoopback) (async () => {
		try {
			await createBrowserRouteContext({
				getState: () => state,
				refreshConfigFromDisk: false
			}).forProfile(resolved.defaultProfile).ensureBrowserAvailable();
			const mod = await import("./pw-session-D2rY5pdL.js").catch(() => null);
			if (mod?.getPageForTargetId) await mod.getPageForTargetId({ cdpUrl: defaultProfile.cdpUrl }).catch(() => {});
			logService.info("Playwright warmup complete for default profile");
		} catch (err) {
			logService.debug?.(`Playwright warmup skipped: ${String(err)}`);
		}
	})();
	return state;
}

//#endregion
//#region src/browser/routes/dispatcher.ts
function compileRoute(path) {
	const paramNames = [];
	const parts = path.split("/").map((part) => {
		if (part.startsWith(":")) {
			const name = part.slice(1);
			paramNames.push(name);
			return "([^/]+)";
		}
		return escapeRegExp(part);
	});
	return {
		regex: new RegExp(`^${parts.join("/")}$`),
		paramNames
	};
}
function createRegistry() {
	const routes = [];
	const register = (method) => (path, handler) => {
		const { regex, paramNames } = compileRoute(path);
		routes.push({
			method,
			path,
			regex,
			paramNames,
			handler
		});
	};
	return {
		routes,
		router: {
			get: register("GET"),
			post: register("POST"),
			delete: register("DELETE")
		}
	};
}
function normalizePath(path) {
	if (!path) return "/";
	return path.startsWith("/") ? path : `/${path}`;
}
function createBrowserRouteDispatcher(ctx) {
	const registry = createRegistry();
	registerBrowserRoutes(registry.router, ctx);
	return { dispatch: async (req) => {
		const method = req.method;
		const path = normalizePath(req.path);
		const query = req.query ?? {};
		const body = req.body;
		const signal = req.signal;
		const match = registry.routes.find((route) => {
			if (route.method !== method) return false;
			return route.regex.test(path);
		});
		if (!match) return {
			status: 404,
			body: { error: "Not Found" }
		};
		const exec = match.regex.exec(path);
		const params = {};
		if (exec) for (const [idx, name] of match.paramNames.entries()) {
			const value = exec[idx + 1];
			if (typeof value === "string") params[name] = decodeURIComponent(value);
		}
		let status = 200;
		let payload = void 0;
		const res = {
			status(code) {
				status = code;
				return res;
			},
			json(bodyValue) {
				payload = bodyValue;
			}
		};
		try {
			await match.handler({
				params,
				query,
				body,
				signal
			}, res);
		} catch (err) {
			return {
				status: 500,
				body: { error: String(err) }
			};
		}
		return {
			status,
			body: payload
		};
	} };
}

//#endregion
//#region src/browser/client-fetch.ts
function isAbsoluteHttp(url) {
	return /^https?:\/\//i.test(url.trim());
}
function isLoopbackHttpUrl(url) {
	try {
		const host = new URL(url).hostname.trim().toLowerCase();
		const normalizedHost = host.startsWith("[") && host.endsWith("]") ? host.slice(1, -1) : host;
		return normalizedHost === "127.0.0.1" || normalizedHost === "localhost" || normalizedHost === "::1";
	} catch {
		return false;
	}
}
function withLoopbackBrowserAuthImpl(url, init, deps) {
	const headers = new Headers(init?.headers ?? {});
	if (headers.has("authorization") || headers.has("x-openclaw-password")) return {
		...init,
		headers
	};
	if (!isLoopbackHttpUrl(url)) return {
		...init,
		headers
	};
	try {
		const cfg = deps.loadConfig();
		const auth = deps.resolveBrowserControlAuth(cfg);
		if (auth.token) {
			headers.set("Authorization", `Bearer ${auth.token}`);
			return {
				...init,
				headers
			};
		}
		if (auth.password) {
			headers.set("x-openclaw-password", auth.password);
			return {
				...init,
				headers
			};
		}
	} catch {}
	try {
		const parsed = new URL(url);
		const port = parsed.port && Number.parseInt(parsed.port, 10) > 0 ? Number.parseInt(parsed.port, 10) : parsed.protocol === "https:" ? 443 : 80;
		const bridgeAuth = deps.getBridgeAuthForPort(port);
		if (bridgeAuth?.token) headers.set("Authorization", `Bearer ${bridgeAuth.token}`);
		else if (bridgeAuth?.password) headers.set("x-openclaw-password", bridgeAuth.password);
	} catch {}
	return {
		...init,
		headers
	};
}
function withLoopbackBrowserAuth(url, init) {
	return withLoopbackBrowserAuthImpl(url, init, {
		loadConfig,
		resolveBrowserControlAuth,
		getBridgeAuthForPort
	});
}
function enhanceBrowserFetchError(url, err, timeoutMs) {
	const operatorHint = !isAbsoluteHttp(url) ? `Restart the OpenClaw gateway (OpenClaw.app menubar, or \`${formatCliCommand("openclaw gateway")}\`).` : "If this is a sandboxed session, ensure the sandbox browser is running.";
	const modelHint = "Do NOT retry the browser tool — it will keep failing. Use an alternative approach or inform the user that the browser is currently unavailable.";
	const msg = String(err);
	const msgLower = msg.toLowerCase();
	if (msgLower.includes("timed out") || msgLower.includes("timeout") || msgLower.includes("aborted") || msgLower.includes("abort") || msgLower.includes("aborterror")) return /* @__PURE__ */ new Error(`Can't reach the OpenClaw browser control service (timed out after ${timeoutMs}ms). ${operatorHint} ${modelHint}`);
	return /* @__PURE__ */ new Error(`Can't reach the OpenClaw browser control service. ${operatorHint} ${modelHint} (${msg})`);
}
async function fetchHttpJson(url, init) {
	const timeoutMs = init.timeoutMs ?? 5e3;
	const ctrl = new AbortController();
	const upstreamSignal = init.signal;
	let upstreamAbortListener;
	if (upstreamSignal) if (upstreamSignal.aborted) ctrl.abort(upstreamSignal.reason);
	else {
		upstreamAbortListener = () => ctrl.abort(upstreamSignal.reason);
		upstreamSignal.addEventListener("abort", upstreamAbortListener, { once: true });
	}
	const t = setTimeout(() => ctrl.abort(/* @__PURE__ */ new Error("timed out")), timeoutMs);
	try {
		const res = await fetch(url, {
			...init,
			signal: ctrl.signal
		});
		if (!res.ok) {
			const text = await res.text().catch(() => "");
			throw new Error(text || `HTTP ${res.status}`);
		}
		return await res.json();
	} finally {
		clearTimeout(t);
		if (upstreamSignal && upstreamAbortListener) upstreamSignal.removeEventListener("abort", upstreamAbortListener);
	}
}
async function fetchBrowserJson(url, init) {
	const timeoutMs = init?.timeoutMs ?? 5e3;
	try {
		if (isAbsoluteHttp(url)) return await fetchHttpJson(url, {
			...withLoopbackBrowserAuth(url, init),
			timeoutMs
		});
		if (!await startBrowserControlServiceFromConfig()) throw new Error("browser control disabled");
		const dispatcher = createBrowserRouteDispatcher(createBrowserControlContext());
		const parsed = new URL(url, "http://localhost");
		const query = {};
		for (const [key, value] of parsed.searchParams.entries()) query[key] = value;
		let body = init?.body;
		if (typeof body === "string") try {
			body = JSON.parse(body);
		} catch {}
		const abortCtrl = new AbortController();
		const upstreamSignal = init?.signal;
		let upstreamAbortListener;
		if (upstreamSignal) if (upstreamSignal.aborted) abortCtrl.abort(upstreamSignal.reason);
		else {
			upstreamAbortListener = () => abortCtrl.abort(upstreamSignal.reason);
			upstreamSignal.addEventListener("abort", upstreamAbortListener, { once: true });
		}
		let abortListener;
		const abortPromise = abortCtrl.signal.aborted ? Promise.reject(abortCtrl.signal.reason ?? /* @__PURE__ */ new Error("aborted")) : new Promise((_, reject) => {
			abortListener = () => reject(abortCtrl.signal.reason ?? /* @__PURE__ */ new Error("aborted"));
			abortCtrl.signal.addEventListener("abort", abortListener, { once: true });
		});
		let timer;
		if (timeoutMs) timer = setTimeout(() => abortCtrl.abort(/* @__PURE__ */ new Error("timed out")), timeoutMs);
		const dispatchPromise = dispatcher.dispatch({
			method: init?.method?.toUpperCase() === "DELETE" ? "DELETE" : init?.method?.toUpperCase() === "POST" ? "POST" : "GET",
			path: parsed.pathname,
			query,
			body,
			signal: abortCtrl.signal
		});
		const result = await Promise.race([dispatchPromise, abortPromise]).finally(() => {
			if (timer) clearTimeout(timer);
			if (abortListener) abortCtrl.signal.removeEventListener("abort", abortListener);
			if (upstreamSignal && upstreamAbortListener) upstreamSignal.removeEventListener("abort", upstreamAbortListener);
		});
		if (result.status >= 400) {
			const message = result.body && typeof result.body === "object" && "error" in result.body ? String(result.body.error) : `HTTP ${result.status}`;
			if (result.status < 500) throw new Error(`${result.status}: ${message}`);
			throw new Error(message);
		}
		return result.body;
	} catch (err) {
		if (err instanceof Error && /^\d{3}: /.test(err.message)) throw err;
		throw enhanceBrowserFetchError(url, err, timeoutMs);
	}
}

//#endregion
//#region src/browser/client-actions-core.ts
async function postDownloadRequest(baseUrl, route, body, profile) {
	return await fetchBrowserJson(withBaseUrl$1(baseUrl, `${route}${buildProfileQuery$1(profile)}`), {
		method: "POST",
		headers: { "Content-Type": "application/json" },
		body: JSON.stringify(body),
		timeoutMs: 2e4
	});
}
async function browserNavigate(baseUrl, opts) {
	return await fetchBrowserJson(withBaseUrl$1(baseUrl, `/navigate${buildProfileQuery$1(opts.profile)}`), {
		method: "POST",
		headers: { "Content-Type": "application/json" },
		body: JSON.stringify({
			url: opts.url,
			targetId: opts.targetId
		}),
		timeoutMs: 2e4
	});
}
async function browserArmDialog(baseUrl, opts) {
	return await fetchBrowserJson(withBaseUrl$1(baseUrl, `/hooks/dialog${buildProfileQuery$1(opts.profile)}`), {
		method: "POST",
		headers: { "Content-Type": "application/json" },
		body: JSON.stringify({
			accept: opts.accept,
			promptText: opts.promptText,
			targetId: opts.targetId,
			timeoutMs: opts.timeoutMs
		}),
		timeoutMs: 2e4
	});
}
async function browserArmFileChooser(baseUrl, opts) {
	return await fetchBrowserJson(withBaseUrl$1(baseUrl, `/hooks/file-chooser${buildProfileQuery$1(opts.profile)}`), {
		method: "POST",
		headers: { "Content-Type": "application/json" },
		body: JSON.stringify({
			paths: opts.paths,
			ref: opts.ref,
			inputRef: opts.inputRef,
			element: opts.element,
			targetId: opts.targetId,
			timeoutMs: opts.timeoutMs
		}),
		timeoutMs: 2e4
	});
}
async function browserWaitForDownload(baseUrl, opts) {
	return await postDownloadRequest(baseUrl, "/wait/download", {
		targetId: opts.targetId,
		path: opts.path,
		timeoutMs: opts.timeoutMs
	}, opts.profile);
}
async function browserDownload(baseUrl, opts) {
	return await postDownloadRequest(baseUrl, "/download", {
		targetId: opts.targetId,
		ref: opts.ref,
		path: opts.path,
		timeoutMs: opts.timeoutMs
	}, opts.profile);
}
async function browserAct(baseUrl, req, opts) {
	return await fetchBrowserJson(withBaseUrl$1(baseUrl, `/act${buildProfileQuery$1(opts?.profile)}`), {
		method: "POST",
		headers: { "Content-Type": "application/json" },
		body: JSON.stringify(req),
		timeoutMs: 2e4
	});
}
async function browserScreenshotAction(baseUrl, opts) {
	return await fetchBrowserJson(withBaseUrl$1(baseUrl, `/screenshot${buildProfileQuery$1(opts.profile)}`), {
		method: "POST",
		headers: { "Content-Type": "application/json" },
		body: JSON.stringify({
			targetId: opts.targetId,
			fullPage: opts.fullPage,
			ref: opts.ref,
			element: opts.element,
			type: opts.type
		}),
		timeoutMs: 2e4
	});
}

//#endregion
//#region src/browser/client.ts
function buildProfileQuery(profile) {
	return profile ? `?profile=${encodeURIComponent(profile)}` : "";
}
function withBaseUrl(baseUrl, path) {
	const trimmed = baseUrl?.trim();
	if (!trimmed) return path;
	return `${trimmed.replace(/\/$/, "")}${path}`;
}
async function browserStatus(baseUrl, opts) {
	return await fetchBrowserJson(withBaseUrl(baseUrl, `/${buildProfileQuery(opts?.profile)}`), { timeoutMs: 1500 });
}
async function browserProfiles(baseUrl) {
	return (await fetchBrowserJson(withBaseUrl(baseUrl, `/profiles`), { timeoutMs: 3e3 })).profiles ?? [];
}
async function browserStart(baseUrl, opts) {
	await fetchBrowserJson(withBaseUrl(baseUrl, `/start${buildProfileQuery(opts?.profile)}`), {
		method: "POST",
		timeoutMs: 15e3
	});
}
async function browserStop(baseUrl, opts) {
	await fetchBrowserJson(withBaseUrl(baseUrl, `/stop${buildProfileQuery(opts?.profile)}`), {
		method: "POST",
		timeoutMs: 15e3
	});
}
async function browserTabs(baseUrl, opts) {
	return (await fetchBrowserJson(withBaseUrl(baseUrl, `/tabs${buildProfileQuery(opts?.profile)}`), { timeoutMs: 3e3 })).tabs ?? [];
}
async function browserOpenTab(baseUrl, url, opts) {
	return await fetchBrowserJson(withBaseUrl(baseUrl, `/tabs/open${buildProfileQuery(opts?.profile)}`), {
		method: "POST",
		headers: { "Content-Type": "application/json" },
		body: JSON.stringify({ url }),
		timeoutMs: 15e3
	});
}
async function browserFocusTab(baseUrl, targetId, opts) {
	await fetchBrowserJson(withBaseUrl(baseUrl, `/tabs/focus${buildProfileQuery(opts?.profile)}`), {
		method: "POST",
		headers: { "Content-Type": "application/json" },
		body: JSON.stringify({ targetId }),
		timeoutMs: 5e3
	});
}
async function browserCloseTab(baseUrl, targetId, opts) {
	const q = buildProfileQuery(opts?.profile);
	await fetchBrowserJson(withBaseUrl(baseUrl, `/tabs/${encodeURIComponent(targetId)}${q}`), {
		method: "DELETE",
		timeoutMs: 5e3
	});
}
async function browserSnapshot(baseUrl, opts) {
	const q = new URLSearchParams();
	q.set("format", opts.format);
	if (opts.targetId) q.set("targetId", opts.targetId);
	if (typeof opts.limit === "number") q.set("limit", String(opts.limit));
	if (typeof opts.maxChars === "number" && Number.isFinite(opts.maxChars)) q.set("maxChars", String(opts.maxChars));
	if (opts.refs === "aria" || opts.refs === "role") q.set("refs", opts.refs);
	if (typeof opts.interactive === "boolean") q.set("interactive", String(opts.interactive));
	if (typeof opts.compact === "boolean") q.set("compact", String(opts.compact));
	if (typeof opts.depth === "number" && Number.isFinite(opts.depth)) q.set("depth", String(opts.depth));
	if (opts.selector?.trim()) q.set("selector", opts.selector.trim());
	if (opts.frame?.trim()) q.set("frame", opts.frame.trim());
	if (opts.labels === true) q.set("labels", "1");
	if (opts.mode) q.set("mode", opts.mode);
	if (opts.profile) q.set("profile", opts.profile);
	return await fetchBrowserJson(withBaseUrl(baseUrl, `/snapshot?${q.toString()}`), { timeoutMs: 2e4 });
}

//#endregion
export { fetchBrowserJson as _, browserSnapshot as a, browserStop as c, browserArmDialog as d, browserArmFileChooser as f, browserWaitForDownload as g, browserScreenshotAction as h, browserProfiles as i, browserTabs as l, browserNavigate as m, browserFocusTab as n, browserStart as o, browserDownload as p, browserOpenTab as r, browserStatus as s, browserCloseTab as t, browserAct as u, buildProfileQuery$1 as v, withBaseUrl$1 as y };