import { g as resolveStateDir } from "./paths-VdmjGGyc.js";
import { i as resolveKoiConfig } from "./koi-scope-BeICsBhH.js";
import { N as VERSION, o as loadConfig } from "./config-BOTdzMIB.js";
import { i as resolveBrowserConfig } from "./server-context-Cs8Yx5qf.js";
import { o as detectMime } from "./image-ops-B6MN1Uli.js";
import { h as GATEWAY_CLIENT_NAMES, m as GATEWAY_CLIENT_MODES } from "./message-channel-B-LM-U45.js";
import { n as getMachineDisplayName, t as createBrowserRouteDispatcher } from "./dispatcher-Da_eDTB8.js";
import { r as loadOrCreateDeviceIdentity } from "./device-identity-CBC9Fa-4.js";
import { n as GatewayClient } from "./client-CnWPlz1L.js";
import { i as readExecApprovalsSnapshot, o as resolveExecApprovalsSocketPath, r as normalizeExecApprovals, s as saveExecApprovals, t as ensureExecApprovals } from "./exec-approvals-io-CeAmdjpP.js";
import { c as analyzeArgvCommand, l as resolveSafeBins, n as evaluateExecAllowlist, o as recordAllowlistUse, r as evaluateShellAllowlist, s as requiresExecApproval, t as addAllowlistEntry, u as resolveExecApprovals } from "./exec-approvals-evaluate-F3Z7wvYv.js";
import { n as createBrowserControlContext, r as startBrowserControlServiceFromConfig } from "./control-service-CamrxDfq.js";
import { t as ensureSKYKOICliOnPath } from "./path-env-B9AK_97y.js";
import fs from "node:fs";
import path from "node:path";
import os from "node:os";
import fs$1 from "node:fs/promises";
import { spawn } from "node:child_process";
import crypto from "node:crypto";
import net from "node:net";

//#region src/infra/exec-host.ts
async function requestExecHostViaSocket(params) {
	const { socketPath, token, request } = params;
	if (!socketPath || !token) return null;
	const timeoutMs = params.timeoutMs ?? 2e4;
	return await new Promise((resolve) => {
		const client = new net.Socket();
		let settled = false;
		let buffer = "";
		const finish = (value) => {
			if (settled) return;
			settled = true;
			try {
				client.destroy();
			} catch {}
			resolve(value);
		};
		const requestJson = JSON.stringify(request);
		const nonce = crypto.randomBytes(16).toString("hex");
		const ts = Date.now();
		const hmac = crypto.createHmac("sha256", token).update(`${nonce}:${ts}:${requestJson}`).digest("hex");
		const payload = JSON.stringify({
			type: "exec",
			id: crypto.randomUUID(),
			nonce,
			ts,
			hmac,
			requestJson
		});
		const timer = setTimeout(() => finish(null), timeoutMs);
		client.on("error", () => finish(null));
		client.connect(socketPath, () => {
			client.write(`${payload}\n`);
		});
		client.on("data", (data) => {
			buffer += data.toString("utf8");
			let idx = buffer.indexOf("\n");
			while (idx !== -1) {
				const line = buffer.slice(0, idx).trim();
				buffer = buffer.slice(idx + 1);
				idx = buffer.indexOf("\n");
				if (!line) continue;
				try {
					const msg = JSON.parse(line);
					if (msg?.type === "exec-res") {
						clearTimeout(timer);
						if (msg.ok === true && msg.payload) {
							finish({
								ok: true,
								payload: msg.payload
							});
							return;
						}
						if (msg.ok === false && msg.error) {
							finish({
								ok: false,
								error: msg.error
							});
							return;
						}
						finish(null);
						return;
					}
				} catch {}
			}
		});
	});
}

//#endregion
//#region src/node-host/config.ts
/**
* @module node-host/config
*
* Persistence layer for the headless node host identity and gateway
* connection settings. Stores node ID, display name, and gateway
* coordinates in `~/.skykoi/node.json`.
*/
const NODE_HOST_FILE = "node.json";
function resolveNodeHostConfigPath() {
	return path.join(resolveStateDir(), NODE_HOST_FILE);
}
function normalizeConfig(config) {
	const base = {
		version: 1,
		nodeId: "",
		token: config?.token,
		displayName: config?.displayName,
		gateway: config?.gateway
	};
	if (config?.version === 1 && typeof config.nodeId === "string") base.nodeId = config.nodeId.trim();
	if (!base.nodeId) base.nodeId = crypto.randomUUID();
	return base;
}
async function loadNodeHostConfig() {
	const filePath = resolveNodeHostConfigPath();
	try {
		const raw = await fs$1.readFile(filePath, "utf8");
		return normalizeConfig(JSON.parse(raw));
	} catch {
		return null;
	}
}
async function saveNodeHostConfig(config) {
	const filePath = resolveNodeHostConfigPath();
	await fs$1.mkdir(path.dirname(filePath), { recursive: true });
	const payload = JSON.stringify(config, null, 2);
	await fs$1.writeFile(filePath, `${payload}\n`, { mode: 384 });
	try {
		await fs$1.chmod(filePath, 384);
	} catch {}
}
async function ensureNodeHostConfig() {
	const normalized = normalizeConfig(await loadNodeHostConfig());
	await saveNodeHostConfig(normalized);
	return normalized;
}

//#endregion
//#region src/node-host/runner.ts
/**
* @module node-host/runner
*
* Headless node host that connects to a Gateway (local or remote) as a
* paired device. Exposes system capabilities (exec, file I/O, screen capture,
* browser proxy, audio, clipboard) to the koi via the Gateway's node
* invocation protocol. Runs as a long-lived WebSocket client that auto-
* reconnects and handles command dispatch.
*/
/**
* @module node-host/runner
*
* Node host runtime that connects to a Gateway and exposes local machine
* capabilities: shell command execution, browser proxy, camera/screen capture,
* system diagnostics, file operations, and device invoke commands.
* Handles exec-approvals policy enforcement, command sandboxing, and
* real-time output streaming back to the gateway.
*/
/** Timeouts for system diagnostic spawn calls (ms). */
const SPAWN_TIMEOUT_SHORT_MS = 5e3;
const SPAWN_TIMEOUT_MEDIUM_MS = 1e4;
const SPAWN_TIMEOUT_LONG_MS = 15e3;
const SPAWN_TIMEOUT_INSTALL_MS = 3e4;
const SPAWN_TIMEOUT_INSTALL_LONG_MS = 6e4;
function resolveExecSecurity(value) {
	return value === "deny" || value === "allowlist" || value === "full" ? value : "allowlist";
}
function isCmdExeInvocation(argv) {
	const token = argv[0]?.trim();
	if (!token) return false;
	const base = path.win32.basename(token).toLowerCase();
	return base === "cmd.exe" || base === "cmd";
}
function resolveExecAsk(value) {
	return value === "off" || value === "on-miss" || value === "always" ? value : "on-miss";
}
const OUTPUT_CAP = 2e5;
const OUTPUT_EVENT_TAIL = 2e4;
const DEFAULT_NODE_PATH = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
const BROWSER_PROXY_MAX_FILE_BYTES = 10 * 1024 * 1024;
const execHostEnforced = process.env.SKYKOI_NODE_EXEC_HOST?.trim().toLowerCase() === "app";
const execHostFallbackAllowed = process.env.SKYKOI_NODE_EXEC_FALLBACK?.trim().toLowerCase() !== "0";
const blockedEnvKeys = new Set([
	"NODE_OPTIONS",
	"PYTHONHOME",
	"PYTHONPATH",
	"PERL5LIB",
	"PERL5OPT",
	"RUBYOPT"
]);
const blockedEnvPrefixes = ["DYLD_", "LD_"];
var SkillBinsCache = class {
	constructor(fetch) {
		this.bins = /* @__PURE__ */ new Set();
		this.lastRefresh = 0;
		this.ttlMs = 9e4;
		this.fetch = fetch;
	}
	async current(force = false) {
		if (force || Date.now() - this.lastRefresh > this.ttlMs) await this.refresh();
		return this.bins;
	}
	async refresh() {
		try {
			const bins = await this.fetch();
			this.bins = new Set(bins);
			this.lastRefresh = Date.now();
		} catch {
			if (!this.lastRefresh) this.bins = /* @__PURE__ */ new Set();
		}
	}
};
function sanitizeEnv(overrides) {
	if (!overrides) return;
	const merged = { ...process.env };
	const basePath = process.env.PATH ?? DEFAULT_NODE_PATH;
	for (const [rawKey, value] of Object.entries(overrides)) {
		const key = rawKey.trim();
		if (!key) continue;
		const upper = key.toUpperCase();
		if (upper === "PATH") {
			const trimmed = value.trim();
			if (!trimmed) continue;
			if (!basePath || trimmed === basePath) {
				merged[key] = trimmed;
				continue;
			}
			const suffix = `${path.delimiter}${basePath}`;
			if (trimmed.endsWith(suffix)) merged[key] = trimmed;
			continue;
		}
		if (blockedEnvKeys.has(upper)) continue;
		if (blockedEnvPrefixes.some((prefix) => upper.startsWith(prefix))) continue;
		merged[key] = value;
	}
	return merged;
}
function normalizeProfileAllowlist(raw) {
	return Array.isArray(raw) ? raw.map((entry) => entry.trim()).filter(Boolean) : [];
}
function resolveBrowserProxyConfig() {
	const proxy = loadConfig().nodeHost?.browserProxy;
	const allowProfiles = normalizeProfileAllowlist(proxy?.allowProfiles);
	return {
		enabled: proxy?.enabled !== false,
		allowProfiles
	};
}
let browserControlReady = null;
async function ensureBrowserControlService() {
	if (browserControlReady) return browserControlReady;
	browserControlReady = (async () => {
		const cfg = loadConfig();
		if (!resolveBrowserConfig(cfg.browser, cfg).enabled) throw new Error("browser control disabled");
		if (!await startBrowserControlServiceFromConfig()) throw new Error("browser control disabled");
	})();
	return browserControlReady;
}
async function withTimeout(promise, timeoutMs, label) {
	const resolved = typeof timeoutMs === "number" && Number.isFinite(timeoutMs) ? Math.max(1, Math.floor(timeoutMs)) : void 0;
	if (!resolved) return await promise;
	let timer;
	const timeoutPromise = new Promise((_, reject) => {
		timer = setTimeout(() => {
			reject(/* @__PURE__ */ new Error(`${label ?? "request"} timed out`));
		}, resolved);
	});
	try {
		return await Promise.race([promise, timeoutPromise]);
	} finally {
		if (timer) clearTimeout(timer);
	}
}
function isProfileAllowed(params) {
	const { allowProfiles, profile } = params;
	if (!allowProfiles.length) return true;
	if (!profile) return false;
	return allowProfiles.includes(profile.trim());
}
function collectBrowserProxyPaths(payload) {
	const paths = /* @__PURE__ */ new Set();
	const obj = typeof payload === "object" && payload !== null ? payload : null;
	if (!obj) return [];
	if (typeof obj.path === "string" && obj.path.trim()) paths.add(obj.path.trim());
	if (typeof obj.imagePath === "string" && obj.imagePath.trim()) paths.add(obj.imagePath.trim());
	const download = obj.download;
	if (download && typeof download === "object") {
		const dlPath = download.path;
		if (typeof dlPath === "string" && dlPath.trim()) paths.add(dlPath.trim());
	}
	return [...paths];
}
async function readBrowserProxyFile(filePath) {
	const stat = await fs$1.stat(filePath).catch(() => null);
	if (!stat || !stat.isFile()) return null;
	if (stat.size > BROWSER_PROXY_MAX_FILE_BYTES) throw new Error(`browser proxy file exceeds ${Math.round(BROWSER_PROXY_MAX_FILE_BYTES / (1024 * 1024))}MB`);
	const buffer = await fs$1.readFile(filePath);
	const mimeType = await detectMime({
		buffer,
		filePath
	});
	return {
		path: filePath,
		base64: buffer.toString("base64"),
		mimeType
	};
}
function formatCommand(argv) {
	return argv.map((arg) => {
		const trimmed = arg.trim();
		if (!trimmed) return "\"\"";
		if (!/\s|"/.test(trimmed)) return trimmed;
		return `"${trimmed.replace(/"/g, "\\\"")}"`;
	}).join(" ");
}
function truncateOutput(raw, maxChars) {
	if (raw.length <= maxChars) return {
		text: raw,
		truncated: false
	};
	return {
		text: `... (truncated) ${raw.slice(raw.length - maxChars)}`,
		truncated: true
	};
}
function redactExecApprovals(file) {
	const socketPath = file.socket?.path?.trim();
	return {
		...file,
		socket: socketPath ? { path: socketPath } : void 0
	};
}
function requireExecApprovalsBaseHash(params, snapshot) {
	if (!snapshot.exists) return;
	if (!snapshot.hash) throw new Error("INVALID_REQUEST: exec approvals base hash unavailable; reload and retry");
	const baseHash = typeof params.baseHash === "string" ? params.baseHash.trim() : "";
	if (!baseHash) throw new Error("INVALID_REQUEST: exec approvals base hash required; reload and retry");
	if (baseHash !== snapshot.hash) throw new Error("INVALID_REQUEST: exec approvals changed; reload and retry");
}
async function runCommand(argv, cwd, env, timeoutMs) {
	return await new Promise((resolve) => {
		let stdout = "";
		let stderr = "";
		let outputLen = 0;
		let truncated = false;
		let timedOut = false;
		let settled = false;
		const child = spawn(argv[0], argv.slice(1), {
			cwd,
			env,
			stdio: [
				"ignore",
				"pipe",
				"pipe"
			],
			windowsHide: true
		});
		const onChunk = (chunk, target) => {
			if (outputLen >= OUTPUT_CAP) {
				truncated = true;
				return;
			}
			const remaining = OUTPUT_CAP - outputLen;
			const slice = chunk.length > remaining ? chunk.subarray(0, remaining) : chunk;
			const str = slice.toString("utf8");
			outputLen += slice.length;
			if (target === "stdout") stdout += str;
			else stderr += str;
			if (chunk.length > remaining) truncated = true;
		};
		child.stdout?.on("data", (chunk) => onChunk(chunk, "stdout"));
		child.stderr?.on("data", (chunk) => onChunk(chunk, "stderr"));
		let timer;
		if (timeoutMs && timeoutMs > 0) timer = setTimeout(() => {
			timedOut = true;
			try {
				child.kill("SIGKILL");
			} catch {}
		}, timeoutMs);
		const finalize = (exitCode, error) => {
			if (settled) return;
			settled = true;
			if (timer) clearTimeout(timer);
			resolve({
				exitCode,
				timedOut,
				success: exitCode === 0 && !timedOut && !error,
				stdout,
				stderr,
				error: error ?? null,
				truncated
			});
		};
		child.on("error", (err) => {
			finalize(void 0, err.message);
		});
		child.on("exit", (code) => {
			finalize(code === null ? void 0 : code, null);
		});
	});
}
function resolveEnvPath(env) {
	return (env?.PATH ?? env?.Path ?? process.env.PATH ?? process.env.Path ?? DEFAULT_NODE_PATH).split(path.delimiter).filter(Boolean);
}
function ensureNodePathEnv() {
	ensureSKYKOICliOnPath({ pathEnv: process.env.PATH ?? "" });
	const entries = (process.env.PATH ?? "").split(path.delimiter).filter(Boolean);
	const seen = new Set(entries);
	for (const dir of DEFAULT_NODE_PATH.split(path.delimiter)) if (dir && !seen.has(dir)) {
		entries.push(dir);
		seen.add(dir);
	}
	const merged = entries.join(path.delimiter);
	process.env.PATH = merged;
	return merged;
}
function resolveExecutable(bin, env) {
	if (bin.includes("/") || bin.includes("\\")) return null;
	const extensions = process.platform === "win32" ? (process.env.PATHEXT ?? process.env.PathExt ?? ".EXE;.CMD;.BAT;.COM").split(";").map((ext) => ext.toLowerCase()) : [""];
	for (const dir of resolveEnvPath(env)) for (const ext of extensions) {
		const candidate = path.join(dir, bin + ext);
		if (fs.existsSync(candidate)) return candidate;
	}
	return null;
}
async function handleSystemWhich(params, env) {
	const bins = params.bins.map((bin) => bin.trim()).filter(Boolean);
	const found = {};
	for (const bin of bins) {
		const path = resolveExecutable(bin, env);
		if (path) found[bin] = path;
	}
	return { bins: found };
}
function buildExecEventPayload(payload) {
	if (!payload.output) return payload;
	const trimmed = payload.output.trim();
	if (!trimmed) return payload;
	const { text } = truncateOutput(trimmed, OUTPUT_EVENT_TAIL);
	return {
		...payload,
		output: text
	};
}
async function runViaMacAppExecHost(params) {
	const { approvals, request } = params;
	return await requestExecHostViaSocket({
		socketPath: approvals.socketPath,
		token: approvals.token,
		request
	});
}
async function runNodeHost(opts) {
	const config = await ensureNodeHostConfig();
	const nodeId = opts.nodeId?.trim() || config.nodeId;
	if (nodeId !== config.nodeId) config.nodeId = nodeId;
	const displayName = opts.displayName?.trim() || config.displayName || await getMachineDisplayName();
	config.displayName = displayName;
	let cfg;
	try {
		cfg = loadConfig();
	} catch {
		cfg = {};
	}
	const gateway = {
		host: opts.gatewayHost,
		port: opts.gatewayPort,
		tls: opts.gatewayTls ?? cfg.gateway?.tls?.enabled ?? false,
		tlsFingerprint: opts.gatewayTlsFingerprint
	};
	config.gateway = gateway;
	await saveNodeHostConfig(config);
	const browserProxy = resolveBrowserProxyConfig();
	const resolvedBrowser = resolveBrowserConfig(cfg.browser, cfg);
	const browserProxyEnabled = browserProxy.enabled && resolvedBrowser.enabled;
	const isRemoteMode = cfg.gateway?.mode === "remote";
	const liveTokenGetter = () => {
		let live;
		try {
			live = loadConfig();
		} catch {
			live = void 0;
		}
		return ((live?.gateway?.mode === "remote" ? live?.gateway?.remote?.token : live?.gateway?.auth?.token) || process.env.SKYKOI_GATEWAY_TOKEN?.trim())?.trim() || void 0;
	};
	const password = (isRemoteMode ? cfg.gateway?.remote?.password : cfg.gateway?.auth?.password) || process.env.SKYKOI_GATEWAY_PASSWORD?.trim();
	const host = gateway.host ?? "127.0.0.1";
	const port = gateway.port ?? 18789;
	const url = `${gateway.tls ? "wss" : "ws"}://${host}:${port}`;
	const pathEnv = ensureNodePathEnv();
	const client = new GatewayClient({
		url,
		token: opts.tokenGetter ?? liveTokenGetter,
		password: password?.trim() || void 0,
		instanceId: nodeId,
		clientName: GATEWAY_CLIENT_NAMES.NODE_HOST,
		clientDisplayName: displayName,
		clientVersion: VERSION,
		platform: process.platform,
		mode: GATEWAY_CLIENT_MODES.NODE,
		role: "node",
		scopes: [],
		caps: [
			"system",
			"screen",
			"input",
			"file",
			"audio",
			"app",
			...browserProxyEnabled ? ["browser"] : []
		],
		commands: [
			"system.run",
			"system.which",
			"system.info",
			"system.execApprovals.get",
			"system.execApprovals.set",
			"screen.snap",
			"clipboard.read",
			"clipboard.write",
			"process.list",
			"process.kill",
			"network.info",
			"display.list",
			"input.type",
			"input.key",
			"input.click",
			"app.launch",
			"app.list",
			"audio.play",
			"audio.record",
			"file.watch",
			"file.read",
			"file.write",
			"file.list",
			"file.stat",
			"file.copy",
			"file.delete",
			...browserProxyEnabled ? ["browser.proxy"] : []
		],
		pathEnv,
		permissions: void 0,
		deviceIdentity: loadOrCreateDeviceIdentity(),
		tlsFingerprint: gateway.tlsFingerprint,
		maxReconnectAttempts: opts.maxReconnectAttempts,
		onEvent: (evt) => {
			if (evt.event !== "node.invoke.request") return;
			const payload = coerceNodeInvokePayload(evt.payload);
			if (!payload) return;
			handleInvoke(payload, client, skillBins);
		},
		onConnectError: (err) => {
			console.error(`node host gateway connect failed: ${err.message}`);
		},
		onClose: (code, reason) => {
			console.error(`node host gateway closed (${code}): ${reason}`);
		}
	});
	const skillBins = new SkillBinsCache(async () => {
		const res = await client.request("skills.bins", {});
		return Array.isArray(res?.bins) ? res.bins.map((bin) => String(bin)) : [];
	});
	client.start();
	await new Promise(() => {});
}
async function handleInvoke(frame, client, skillBins) {
	const command = String(frame.command ?? "");
	if (command === "system.execApprovals.get") {
		try {
			ensureExecApprovals();
			const snapshot = readExecApprovalsSnapshot();
			const payload = {
				path: snapshot.path,
				exists: snapshot.exists,
				hash: snapshot.hash,
				file: redactExecApprovals(snapshot.file)
			};
			await sendInvokeResult(client, frame, {
				ok: true,
				payloadJSON: JSON.stringify(payload)
			});
		} catch (err) {
			const message = String(err);
			await sendInvokeResult(client, frame, {
				ok: false,
				error: {
					code: message.toLowerCase().includes("timed out") ? "TIMEOUT" : "INVALID_REQUEST",
					message
				}
			});
		}
		return;
	}
	if (command === "system.execApprovals.set") {
		try {
			const params = decodeParams(frame.paramsJSON);
			if (!params.file || typeof params.file !== "object") throw new Error("INVALID_REQUEST: exec approvals file required");
			ensureExecApprovals();
			const snapshot = readExecApprovalsSnapshot();
			requireExecApprovalsBaseHash(params, snapshot);
			const normalized = normalizeExecApprovals(params.file);
			const currentSocketPath = snapshot.file.socket?.path?.trim();
			const currentToken = snapshot.file.socket?.token?.trim();
			const socketPath = normalized.socket?.path?.trim() ?? currentSocketPath ?? resolveExecApprovalsSocketPath();
			const token = normalized.socket?.token?.trim() ?? currentToken ?? "";
			saveExecApprovals({
				...normalized,
				socket: {
					path: socketPath,
					token
				}
			});
			const nextSnapshot = readExecApprovalsSnapshot();
			const payload = {
				path: nextSnapshot.path,
				exists: nextSnapshot.exists,
				hash: nextSnapshot.hash,
				file: redactExecApprovals(nextSnapshot.file)
			};
			await sendInvokeResult(client, frame, {
				ok: true,
				payloadJSON: JSON.stringify(payload)
			});
		} catch (err) {
			await sendInvokeResult(client, frame, {
				ok: false,
				error: {
					code: "INVALID_REQUEST",
					message: String(err)
				}
			});
		}
		return;
	}
	if (command === "system.which") {
		try {
			const params = decodeParams(frame.paramsJSON);
			if (!Array.isArray(params.bins)) throw new Error("INVALID_REQUEST: bins required");
			const payload = await handleSystemWhich(params, sanitizeEnv(void 0));
			await sendInvokeResult(client, frame, {
				ok: true,
				payloadJSON: JSON.stringify(payload)
			});
		} catch (err) {
			await sendInvokeResult(client, frame, {
				ok: false,
				error: {
					code: "INVALID_REQUEST",
					message: String(err)
				}
			});
		}
		return;
	}
	if (command === "browser.proxy") {
		try {
			const params = decodeParams(frame.paramsJSON);
			const pathValue = typeof params.path === "string" ? params.path.trim() : "";
			if (!pathValue) throw new Error("INVALID_REQUEST: path required");
			const proxyConfig = resolveBrowserProxyConfig();
			if (!proxyConfig.enabled) throw new Error("UNAVAILABLE: node browser proxy disabled");
			await ensureBrowserControlService();
			const cfg = loadConfig();
			const resolved = resolveBrowserConfig(cfg.browser, cfg);
			const requestedProfile = typeof params.profile === "string" ? params.profile.trim() : "";
			const allowedProfiles = proxyConfig.allowProfiles;
			if (allowedProfiles.length > 0) {
				if (pathValue !== "/profiles") {
					if (!isProfileAllowed({
						allowProfiles: allowedProfiles,
						profile: requestedProfile || resolved.defaultProfile
					})) throw new Error("INVALID_REQUEST: browser profile not allowed");
				} else if (requestedProfile) {
					if (!isProfileAllowed({
						allowProfiles: allowedProfiles,
						profile: requestedProfile
					})) throw new Error("INVALID_REQUEST: browser profile not allowed");
				}
			}
			const method = typeof params.method === "string" ? params.method.toUpperCase() : "GET";
			const path = pathValue.startsWith("/") ? pathValue : `/${pathValue}`;
			const body = params.body;
			const query = {};
			if (requestedProfile) query.profile = requestedProfile;
			const rawQuery = params.query ?? {};
			for (const [key, value] of Object.entries(rawQuery)) {
				if (value === void 0 || value === null) continue;
				query[key] = typeof value === "string" ? value : String(value);
			}
			const response = await withTimeout(createBrowserRouteDispatcher(createBrowserControlContext()).dispatch({
				method: method === "DELETE" ? "DELETE" : method === "POST" ? "POST" : "GET",
				path,
				query,
				body
			}), params.timeoutMs, "browser proxy request");
			if (response.status >= 400) {
				const message = response.body && typeof response.body === "object" && "error" in response.body ? String(response.body.error) : `HTTP ${response.status}`;
				throw new Error(message);
			}
			const result = response.body;
			if (allowedProfiles.length > 0 && path === "/profiles") {
				const obj = typeof result === "object" && result !== null ? result : {};
				obj.profiles = (Array.isArray(obj.profiles) ? obj.profiles : []).filter((entry) => {
					if (!entry || typeof entry !== "object") return false;
					const name = entry.name;
					return typeof name === "string" && allowedProfiles.includes(name);
				});
			}
			let files;
			const paths = collectBrowserProxyPaths(result);
			if (paths.length > 0) {
				const loaded = await Promise.all(paths.map(async (p) => {
					try {
						const file = await readBrowserProxyFile(p);
						if (!file) throw new Error("file not found");
						return file;
					} catch (err) {
						throw new Error(`browser proxy file read failed for ${p}: ${String(err)}`, { cause: err });
					}
				}));
				if (loaded.length > 0) files = loaded;
			}
			const payload = files ? {
				result,
				files
			} : { result };
			await sendInvokeResult(client, frame, {
				ok: true,
				payloadJSON: JSON.stringify(payload)
			});
		} catch (err) {
			await sendInvokeResult(client, frame, {
				ok: false,
				error: {
					code: "INVALID_REQUEST",
					message: String(err)
				}
			});
		}
		return;
	}
	if (command === "screen.snap") {
		try {
			const params = decodeParams(frame.paramsJSON);
			const display = params.display || process.env.DISPLAY || ":0";
			const format = params.format === "jpg" || params.format === "jpeg" ? "jpg" : "png";
			const tmpFile = path.join(os.tmpdir(), `screen-snap-${crypto.randomUUID()}.${format}`);
			const platform = process.platform;
			let cmd;
			if (platform === "darwin") cmd = [
				"screencapture",
				"-x",
				"-t",
				format,
				tmpFile
			];
			else if (platform === "win32") cmd = [
				"powershell",
				"-NoProfile",
				"-Command",
				`
Add-Type -AssemblyName System.Windows.Forms,System.Drawing
$bounds = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds
$bmp = New-Object System.Drawing.Bitmap($bounds.Width, $bounds.Height)
$g = [System.Drawing.Graphics]::FromImage($bmp)
$g.CopyFromScreen($bounds.Location, [System.Drawing.Point]::Empty, $bounds.Size)
$bmp.Save('${tmpFile.replace(/\\/g, "\\\\")}')
$g.Dispose()
$bmp.Dispose()`
			];
			else cmd = [
				"bash",
				"-c",
				`DISPLAY=${display} import -window root ${tmpFile}`
			];
			await new Promise((resolve, reject) => {
				const proc = spawn(cmd[0], cmd.slice(1), { timeout: SPAWN_TIMEOUT_LONG_MS });
				proc.on("close", (code) => code === 0 ? resolve() : reject(/* @__PURE__ */ new Error(`screenshot failed (exit ${code})`)));
				proc.on("error", reject);
			});
			const buffer = await fs$1.readFile(tmpFile);
			await fs$1.rm(tmpFile, { force: true }).catch(() => {});
			await sendInvokeResult(client, frame, {
				ok: true,
				payloadJSON: JSON.stringify({
					format,
					size: buffer.length,
					encoding: "base64",
					data: buffer.toString("base64")
				})
			});
		} catch (err) {
			await sendInvokeResult(client, frame, {
				ok: false,
				error: {
					code: "INTERNAL",
					message: String(err)
				}
			});
		}
		return;
	}
	if (command === "clipboard.read") {
		try {
			const platform = process.platform;
			let cmd;
			if (platform === "darwin") cmd = ["pbpaste"];
			else if (platform === "win32") cmd = [
				"powershell",
				"-NoProfile",
				"-Command",
				"Get-Clipboard"
			];
			else {
				const display = process.env.DISPLAY || ":0";
				cmd = [
					"bash",
					"-c",
					`DISPLAY=${display} xclip -selection clipboard -o 2>/dev/null || DISPLAY=${display} xsel --clipboard --output 2>/dev/null || echo ""`
				];
			}
			const text = await new Promise((resolve, reject) => {
				const proc = spawn(cmd[0], cmd.slice(1), { timeout: SPAWN_TIMEOUT_SHORT_MS });
				let out = "";
				proc.stdout?.on("data", (d) => {
					out += d.toString();
				});
				proc.on("close", () => resolve(out));
				proc.on("error", reject);
			});
			await sendInvokeResult(client, frame, {
				ok: true,
				payloadJSON: JSON.stringify({ text })
			});
		} catch (err) {
			await sendInvokeResult(client, frame, {
				ok: false,
				error: {
					code: "INTERNAL",
					message: String(err)
				}
			});
		}
		return;
	}
	if (command === "clipboard.write") {
		try {
			const params = decodeParams(frame.paramsJSON);
			if (typeof params.text !== "string") throw new Error("INVALID_REQUEST: text required");
			const platform = process.platform;
			let cmd;
			if (platform === "darwin") cmd = ["pbcopy"];
			else if (platform === "win32") cmd = [
				"powershell",
				"-NoProfile",
				"-Command",
				`Set-Clipboard -Value $input`
			];
			else {
				const display = process.env.DISPLAY || ":0";
				cmd = [
					"bash",
					"-c",
					`DISPLAY=${display} xclip -selection clipboard 2>/dev/null || DISPLAY=${display} xsel --clipboard --input 2>/dev/null`
				];
			}
			await new Promise((resolve, reject) => {
				const proc = spawn(cmd[0], cmd.slice(1), { timeout: SPAWN_TIMEOUT_SHORT_MS });
				proc.stdin?.write(params.text);
				proc.stdin?.end();
				proc.on("close", () => resolve());
				proc.on("error", reject);
			});
			await sendInvokeResult(client, frame, {
				ok: true,
				payloadJSON: JSON.stringify({
					written: true,
					length: params.text.length
				})
			});
		} catch (err) {
			await sendInvokeResult(client, frame, {
				ok: false,
				error: {
					code: "INTERNAL",
					message: String(err)
				}
			});
		}
		return;
	}
	if (command === "process.list") {
		try {
			const params = decodeParams(frame.paramsJSON);
			const platform = process.platform;
			let cmd;
			if (platform === "win32") cmd = [
				"powershell",
				"-NoProfile",
				"-Command",
				"Get-Process | Select-Object Id,ProcessName,CPU,WorkingSet64 | ConvertTo-Json -Depth 2"
			];
			else cmd = [
				"ps",
				"aux",
				"--no-headers"
			];
			const output = await new Promise((resolve, reject) => {
				const proc = spawn(cmd[0], cmd.slice(1), { timeout: SPAWN_TIMEOUT_MEDIUM_MS });
				let out = "";
				proc.stdout?.on("data", (d) => {
					out += d.toString();
				});
				proc.on("close", () => resolve(out));
				proc.on("error", reject);
			});
			let processes;
			if (platform === "win32") try {
				const raw = JSON.parse(output);
				processes = (Array.isArray(raw) ? raw : [raw]).map((p) => ({
					pid: p.Id,
					name: p.ProcessName,
					cpu: String(p.CPU ?? 0),
					mem: String(Math.round((p.WorkingSet64 ?? 0) / 1024 / 1024)) + "MB"
				}));
			} catch {
				processes = [];
			}
			else processes = output.trim().split("\n").filter(Boolean).map((line) => {
				const parts = line.trim().split(/\s+/);
				return {
					pid: parseInt(parts[1], 10),
					name: parts[10] || parts[1],
					cpu: parts[2],
					mem: parts[3]
				};
			});
			const filter = params.filter?.toLowerCase();
			if (filter) processes = processes.filter((p) => p.name.toLowerCase().includes(filter));
			const limit = params.limit && params.limit > 0 ? params.limit : 100;
			processes = processes.slice(0, limit);
			await sendInvokeResult(client, frame, {
				ok: true,
				payloadJSON: JSON.stringify({
					count: processes.length,
					processes
				})
			});
		} catch (err) {
			await sendInvokeResult(client, frame, {
				ok: false,
				error: {
					code: "INTERNAL",
					message: String(err)
				}
			});
		}
		return;
	}
	if (command === "process.kill") {
		try {
			const params = decodeParams(frame.paramsJSON);
			if (!params.pid && !params.name) throw new Error("INVALID_REQUEST: pid or name required");
			const signal = params.signal || "SIGTERM";
			if (params.pid) {
				process.kill(params.pid, signal);
				await sendInvokeResult(client, frame, {
					ok: true,
					payloadJSON: JSON.stringify({
						killed: true,
						pid: params.pid,
						signal
					})
				});
			} else {
				const platform = process.platform;
				let cmd;
				if (platform === "win32") cmd = [
					"powershell",
					"-NoProfile",
					"-Command",
					`Stop-Process -Name "${params.name}" -Force`
				];
				else cmd = [
					"pkill",
					"-f",
					params.name
				];
				await new Promise((resolve, reject) => {
					const proc = spawn(cmd[0], cmd.slice(1), { timeout: SPAWN_TIMEOUT_SHORT_MS });
					proc.on("close", () => resolve());
					proc.on("error", reject);
				});
				await sendInvokeResult(client, frame, {
					ok: true,
					payloadJSON: JSON.stringify({
						killed: true,
						name: params.name,
						signal
					})
				});
			}
		} catch (err) {
			await sendInvokeResult(client, frame, {
				ok: false,
				error: {
					code: "INTERNAL",
					message: String(err)
				}
			});
		}
		return;
	}
	if (command === "system.info") {
		try {
			const cpus = os.cpus();
			const totalMem = os.totalmem();
			const freeMem = os.freemem();
			const uptimeSecs = os.uptime();
			let diskInfo = {};
			try {
				if (process.platform === "win32") {
					const out = await new Promise((resolve, reject) => {
						const proc = spawn("powershell", [
							"-NoProfile",
							"-Command",
							"Get-PSDrive C | Select-Object Used,Free | ConvertTo-Json"
						], { timeout: SPAWN_TIMEOUT_SHORT_MS });
						let data = "";
						proc.stdout?.on("data", (d) => {
							data += d.toString();
						});
						proc.on("close", () => resolve(data));
						proc.on("error", reject);
					});
					const d = JSON.parse(out);
					diskInfo = {
						total: String(Math.round((d.Used + d.Free) / 1024 / 1024 / 1024)) + "GB",
						free: String(Math.round(d.Free / 1024 / 1024 / 1024)) + "GB",
						used: String(Math.round(d.Used / 1024 / 1024 / 1024)) + "GB"
					};
				} else {
					const lines = (await new Promise((resolve, reject) => {
						const proc = spawn("df", ["-h", "/"], { timeout: SPAWN_TIMEOUT_SHORT_MS });
						let data = "";
						proc.stdout?.on("data", (d) => {
							data += d.toString();
						});
						proc.on("close", () => resolve(data));
						proc.on("error", reject);
					})).trim().split("\n");
					if (lines.length > 1) {
						const parts = lines[1].split(/\s+/);
						diskInfo = {
							total: parts[1],
							used: parts[2],
							free: parts[3]
						};
					}
				}
			} catch {}
			await sendInvokeResult(client, frame, {
				ok: true,
				payloadJSON: JSON.stringify({
					hostname: os.hostname(),
					platform: process.platform,
					arch: process.arch,
					os: `${os.type()} ${os.release()}`,
					cpuModel: cpus[0]?.model || "unknown",
					cpuCores: cpus.length,
					totalMemory: `${Math.round(totalMem / 1024 / 1024)}MB`,
					freeMemory: `${Math.round(freeMem / 1024 / 1024)}MB`,
					usedMemory: `${Math.round((totalMem - freeMem) / 1024 / 1024)}MB`,
					disk: diskInfo,
					uptime: `${Math.floor(uptimeSecs / 3600)}h ${Math.floor(uptimeSecs % 3600 / 60)}m`,
					nodeVersion: process.version,
					pid: process.pid
				})
			});
		} catch (err) {
			await sendInvokeResult(client, frame, {
				ok: false,
				error: {
					code: "INTERNAL",
					message: String(err)
				}
			});
		}
		return;
	}
	if (command === "network.info") {
		try {
			const interfaces = os.networkInterfaces();
			const nets = [];
			for (const [name, addrs] of Object.entries(interfaces)) {
				if (!addrs) continue;
				for (const addr of addrs) nets.push({
					name,
					address: addr.address,
					family: addr.family,
					mac: addr.mac,
					internal: addr.internal
				});
			}
			await sendInvokeResult(client, frame, {
				ok: true,
				payloadJSON: JSON.stringify({
					hostname: os.hostname(),
					interfaces: nets
				})
			});
		} catch (err) {
			await sendInvokeResult(client, frame, {
				ok: false,
				error: {
					code: "INTERNAL",
					message: String(err)
				}
			});
		}
		return;
	}
	if (command === "display.list") {
		try {
			const platform = process.platform;
			let displays = [];
			if (platform === "win32") {
				const out = await new Promise((resolve, reject) => {
					const proc = spawn("powershell", [
						"-NoProfile",
						"-Command",
						"[System.Windows.Forms.Screen]::AllScreens | ForEach-Object { @{ DeviceName=$_.DeviceName; Bounds=\"$($_.Bounds.Width)x$($_.Bounds.Height)\"; Primary=$_.Primary } } | ConvertTo-Json"
					], { timeout: SPAWN_TIMEOUT_SHORT_MS });
					let data = "";
					proc.stdout?.on("data", (d) => {
						data += d.toString();
					});
					proc.on("close", () => resolve(data));
					proc.on("error", reject);
				});
				try {
					const raw = JSON.parse(out);
					displays = (Array.isArray(raw) ? raw : [raw]).map((d) => ({
						name: d.DeviceName,
						resolution: d.Bounds,
						primary: d.Primary
					}));
				} catch {}
			} else if (platform === "darwin") {
				const out = await new Promise((resolve, reject) => {
					const proc = spawn("system_profiler", ["SPDisplaysDataType", "-json"], { timeout: SPAWN_TIMEOUT_MEDIUM_MS });
					let data = "";
					proc.stdout?.on("data", (d) => {
						data += d.toString();
					});
					proc.on("close", () => resolve(data));
					proc.on("error", reject);
				});
				try {
					const gpus = JSON.parse(out).SPDisplaysDataType || [];
					for (const gpu of gpus) for (const disp of gpu.spdisplays_ndrvs || []) displays.push({
						name: disp._name || "Display",
						resolution: disp._spdisplays_resolution || "unknown",
						primary: disp.spdisplays_main === "spdisplays_yes"
					});
				} catch {}
			} else {
				const display = process.env.DISPLAY || ":0";
				const lines = (await new Promise((resolve, reject) => {
					const proc = spawn("bash", ["-c", `DISPLAY=${display} xrandr --query 2>/dev/null || echo "no display"`], { timeout: SPAWN_TIMEOUT_SHORT_MS });
					let data = "";
					proc.stdout?.on("data", (d) => {
						data += d.toString();
					});
					proc.on("close", () => resolve(data));
					proc.on("error", reject);
				})).split("\n");
				for (const line of lines) {
					const match = line.match(/^(\S+)\s+connected\s+(primary\s+)?(\d+x\d+)/);
					if (match) displays.push({
						name: match[1],
						resolution: match[3],
						primary: !!match[2]
					});
				}
			}
			await sendInvokeResult(client, frame, {
				ok: true,
				payloadJSON: JSON.stringify({ displays })
			});
		} catch (err) {
			await sendInvokeResult(client, frame, {
				ok: false,
				error: {
					code: "INTERNAL",
					message: String(err)
				}
			});
		}
		return;
	}
	if (command === "input.type") {
		try {
			const params = decodeParams(frame.paramsJSON);
			if (typeof params.text !== "string") throw new Error("INVALID_REQUEST: text required");
			const platform = process.platform;
			let cmd;
			if (platform === "darwin") cmd = [
				"osascript",
				"-e",
				`tell application "System Events" to keystroke "${params.text.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`
			];
			else if (platform === "win32") cmd = [
				"powershell",
				"-NoProfile",
				"-Command",
				`Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('${params.text.replace(/'/g, "''")}')`
			];
			else cmd = [
				"bash",
				"-c",
				`DISPLAY=${process.env.DISPLAY || ":0"} xdotool type --clearmodifiers -- "${params.text.replace(/"/g, "\\\"")}"`
			];
			await new Promise((resolve, reject) => {
				const proc = spawn(cmd[0], cmd.slice(1), { timeout: SPAWN_TIMEOUT_MEDIUM_MS });
				proc.on("close", (code) => code === 0 ? resolve() : reject(/* @__PURE__ */ new Error(`input.type failed (exit ${code})`)));
				proc.on("error", reject);
			});
			await sendInvokeResult(client, frame, {
				ok: true,
				payloadJSON: JSON.stringify({
					typed: true,
					length: params.text.length
				})
			});
		} catch (err) {
			await sendInvokeResult(client, frame, {
				ok: false,
				error: {
					code: "INTERNAL",
					message: String(err)
				}
			});
		}
		return;
	}
	if (command === "input.key") {
		try {
			const params = decodeParams(frame.paramsJSON);
			if (typeof params.key !== "string") throw new Error("INVALID_REQUEST: key required");
			const platform = process.platform;
			const mods = Array.isArray(params.modifiers) ? params.modifiers : [];
			let cmd;
			if (platform === "darwin") {
				const modStr = mods.map((m) => {
					if (m === "ctrl" || m === "control") return "control down";
					if (m === "alt" || m === "option") return "option down";
					if (m === "shift") return "shift down";
					if (m === "cmd" || m === "command") return "command down";
					return `${m} down`;
				}).join(", ");
				const using = modStr ? ` using {${modStr}}` : "";
				cmd = [
					"osascript",
					"-e",
					`tell application "System Events" to key code ${params.key}${using}`
				];
			} else if (platform === "win32") {
				let keyStr = "";
				if (mods.includes("ctrl") || mods.includes("control")) keyStr += "^";
				if (mods.includes("alt")) keyStr += "%";
				if (mods.includes("shift")) keyStr += "+";
				keyStr += `{${params.key}}`;
				cmd = [
					"powershell",
					"-NoProfile",
					"-Command",
					`Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('${keyStr}')`
				];
			} else cmd = [
				"bash",
				"-c",
				`DISPLAY=${process.env.DISPLAY || ":0"} xdotool key --clearmodifiers ${mods.map((m) => `${m}+`).join("")}${params.key}`
			];
			await new Promise((resolve, reject) => {
				const proc = spawn(cmd[0], cmd.slice(1), { timeout: SPAWN_TIMEOUT_SHORT_MS });
				proc.on("close", (code) => code === 0 ? resolve() : reject(/* @__PURE__ */ new Error(`input.key failed (exit ${code})`)));
				proc.on("error", reject);
			});
			await sendInvokeResult(client, frame, {
				ok: true,
				payloadJSON: JSON.stringify({
					pressed: true,
					key: params.key,
					modifiers: mods
				})
			});
		} catch (err) {
			await sendInvokeResult(client, frame, {
				ok: false,
				error: {
					code: "INTERNAL",
					message: String(err)
				}
			});
		}
		return;
	}
	if (command === "input.click") {
		try {
			const params = decodeParams(frame.paramsJSON);
			if (typeof params.x !== "number" || typeof params.y !== "number") throw new Error("INVALID_REQUEST: x and y required");
			const button = params.button || "left";
			const double = params.doubleClick ? true : false;
			const platform = process.platform;
			let cmd;
			if (platform === "darwin") cmd = [
				"osascript",
				"-e",
				`tell application "System Events" to ${double ? "double click" : "click"} at {${params.x}, ${params.y}}`
			];
			else if (platform === "win32") cmd = [
				"powershell",
				"-NoProfile",
				"-Command",
				`
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Cursor]::Position = New-Object System.Drawing.Point(${params.x}, ${params.y})
Add-Type @"
using System;
using System.Runtime.InteropServices;
public class Mouse {
    [DllImport("user32.dll")] public static extern void mouse_event(int dwFlags, int dx, int dy, int dwData, int dwExtraInfo);
}
"@
[Mouse]::mouse_event(0x0002, 0, 0, 0, 0)
[Mouse]::mouse_event(0x0004, 0, 0, 0, 0)
${double ? "[Mouse]::mouse_event(0x0002, 0, 0, 0, 0)\n[Mouse]::mouse_event(0x0004, 0, 0, 0, 0)" : ""}`
			];
			else {
				const display = process.env.DISPLAY || ":0";
				const btnNum = button === "right" ? "3" : button === "middle" ? "2" : "1";
				cmd = [
					"bash",
					"-c",
					`DISPLAY=${display} ${double ? `xdotool mousemove ${params.x} ${params.y} click --repeat 2 ${btnNum}` : `xdotool mousemove ${params.x} ${params.y} click ${btnNum}`}`
				];
			}
			await new Promise((resolve, reject) => {
				const proc = spawn(cmd[0], cmd.slice(1), { timeout: SPAWN_TIMEOUT_SHORT_MS });
				proc.on("close", (code) => code === 0 ? resolve() : reject(/* @__PURE__ */ new Error(`input.click failed (exit ${code})`)));
				proc.on("error", reject);
			});
			await sendInvokeResult(client, frame, {
				ok: true,
				payloadJSON: JSON.stringify({
					clicked: true,
					x: params.x,
					y: params.y,
					button,
					doubleClick: double
				})
			});
		} catch (err) {
			await sendInvokeResult(client, frame, {
				ok: false,
				error: {
					code: "INTERNAL",
					message: String(err)
				}
			});
		}
		return;
	}
	if (command === "app.launch") {
		try {
			const params = decodeParams(frame.paramsJSON);
			if (!params.name) throw new Error("INVALID_REQUEST: name required");
			const platform = process.platform;
			let cmd;
			if (platform === "darwin") cmd = ["open", ...params.args?.length ? [
				"-a",
				params.name,
				"--args",
				...params.args
			] : ["-a", params.name]];
			else if (platform === "win32") cmd = [
				"powershell",
				"-NoProfile",
				"-Command",
				`Start-Process "${params.name}" ${params.args?.map((a) => `"${a}"`).join(" ") || ""}`
			];
			else {
				const display = process.env.DISPLAY || ":0";
				const argStr = params.args?.join(" ") || "";
				cmd = [
					"bash",
					"-c",
					`DISPLAY=${display} nohup ${params.name} ${argStr} > /dev/null 2>&1 &`
				];
			}
			await new Promise((resolve, reject) => {
				const proc = spawn(cmd[0], cmd.slice(1), {
					timeout: SPAWN_TIMEOUT_MEDIUM_MS,
					detached: platform !== "win32"
				});
				proc.unref?.();
				proc.on("close", () => resolve());
				proc.on("error", reject);
			});
			await sendInvokeResult(client, frame, {
				ok: true,
				payloadJSON: JSON.stringify({
					launched: true,
					name: params.name
				})
			});
		} catch (err) {
			await sendInvokeResult(client, frame, {
				ok: false,
				error: {
					code: "INTERNAL",
					message: String(err)
				}
			});
		}
		return;
	}
	if (command === "app.list") {
		try {
			const platform = process.platform;
			let apps = [];
			if (platform === "darwin") apps = (await new Promise((resolve, reject) => {
				const proc = spawn("bash", ["-c", "ls /Applications/*.app | sed 's|/Applications/||;s|.app||'"], { timeout: SPAWN_TIMEOUT_SHORT_MS });
				let data = "";
				proc.stdout?.on("data", (d) => {
					data += d.toString();
				});
				proc.on("close", () => resolve(data));
				proc.on("error", reject);
			})).trim().split("\n").filter(Boolean).map((name) => ({
				name,
				path: `/Applications/${name}.app`
			}));
			else if (platform === "win32") {
				const out = await new Promise((resolve, reject) => {
					const proc = spawn("powershell", [
						"-NoProfile",
						"-Command",
						"Get-StartApps | Select-Object Name,AppID | ConvertTo-Json -Depth 2"
					], { timeout: SPAWN_TIMEOUT_LONG_MS });
					let data = "";
					proc.stdout?.on("data", (d) => {
						data += d.toString();
					});
					proc.on("close", () => resolve(data));
					proc.on("error", reject);
				});
				try {
					const raw = JSON.parse(out);
					apps = (Array.isArray(raw) ? raw : [raw]).map((a) => ({
						name: a.Name,
						path: a.AppID
					}));
				} catch {}
			} else apps = (await new Promise((resolve, reject) => {
				const proc = spawn("bash", ["-c", "find /usr/share/applications /usr/local/share/applications ~/.local/share/applications -name '*.desktop' -exec grep -l '^Name=' {} \\; 2>/dev/null | head -100 | while read f; do grep '^Name=' \"$f\" | head -1 | sed 's/Name=//'; done"], { timeout: SPAWN_TIMEOUT_MEDIUM_MS });
				let data = "";
				proc.stdout?.on("data", (d) => {
					data += d.toString();
				});
				proc.on("close", () => resolve(data));
				proc.on("error", reject);
			})).trim().split("\n").filter(Boolean).map((name) => ({ name }));
			await sendInvokeResult(client, frame, {
				ok: true,
				payloadJSON: JSON.stringify({
					count: apps.length,
					apps
				})
			});
		} catch (err) {
			await sendInvokeResult(client, frame, {
				ok: false,
				error: {
					code: "INTERNAL",
					message: String(err)
				}
			});
		}
		return;
	}
	if (command === "audio.play") {
		try {
			const params = decodeParams(frame.paramsJSON);
			let audioFile;
			if (params.url) {
				audioFile = path.join(os.tmpdir(), `audio-play-${crypto.randomUUID()}`);
				await new Promise((resolve, reject) => {
					const cmd = process.platform === "win32" ? [
						"powershell",
						"-NoProfile",
						"-Command",
						`Invoke-WebRequest -Uri '${params.url}' -OutFile '${audioFile}'`
					] : [
						"curl",
						"-sL",
						"-o",
						audioFile,
						params.url
					];
					const proc = spawn(cmd[0], cmd.slice(1), { timeout: SPAWN_TIMEOUT_INSTALL_MS });
					proc.on("close", (code) => code === 0 ? resolve() : reject(/* @__PURE__ */ new Error(`download failed (exit ${code})`)));
					proc.on("error", reject);
				});
			} else if (params.data) {
				audioFile = path.join(os.tmpdir(), `audio-play-${crypto.randomUUID()}.wav`);
				const encoding = params.encoding === "utf8" ? "utf8" : "base64";
				await fs$1.writeFile(audioFile, Buffer.from(params.data, encoding));
			} else if (params.path) audioFile = path.resolve(params.path);
			else throw new Error("INVALID_REQUEST: path, data, or url required");
			const platform = process.platform;
			let cmd;
			if (platform === "darwin") cmd = ["afplay", audioFile];
			else if (platform === "win32") cmd = [
				"powershell",
				"-NoProfile",
				"-Command",
				`(New-Object System.Media.SoundPlayer '${audioFile}').PlaySync()`
			];
			else cmd = [
				"bash",
				"-c",
				`aplay "${audioFile}" 2>/dev/null || paplay "${audioFile}" 2>/dev/null || ffplay -nodisp -autoexit "${audioFile}" 2>/dev/null`
			];
			await new Promise((resolve, reject) => {
				const proc = spawn(cmd[0], cmd.slice(1), { timeout: SPAWN_TIMEOUT_INSTALL_LONG_MS });
				proc.on("close", () => resolve());
				proc.on("error", reject);
			});
			await sendInvokeResult(client, frame, {
				ok: true,
				payloadJSON: JSON.stringify({ played: true })
			});
		} catch (err) {
			await sendInvokeResult(client, frame, {
				ok: false,
				error: {
					code: "INTERNAL",
					message: String(err)
				}
			});
		}
		return;
	}
	if (command === "audio.record") {
		try {
			const params = decodeParams(frame.paramsJSON);
			const duration = params.durationMs && params.durationMs > 0 ? params.durationMs : 5e3;
			const durationSecs = Math.ceil(duration / 1e3);
			const format = params.format === "mp3" ? "mp3" : "wav";
			const tmpFile = path.join(os.tmpdir(), `audio-rec-${crypto.randomUUID()}.${format}`);
			const platform = process.platform;
			let cmd;
			if (platform === "darwin") cmd = [
				"bash",
				"-c",
				`rec -q "${tmpFile}" trim 0 ${durationSecs} 2>/dev/null || ffmpeg -f avfoundation -i ":0" -t ${durationSecs} "${tmpFile}" -y 2>/dev/null`
			];
			else if (platform === "win32") cmd = [
				"powershell",
				"-NoProfile",
				"-Command",
				`ffmpeg -f dshow -i audio="Microphone" -t ${durationSecs} "${tmpFile}" -y 2>$null`
			];
			else cmd = [
				"bash",
				"-c",
				`arecord -d ${durationSecs} -f cd "${tmpFile}" 2>/dev/null || ffmpeg -f pulse -i default -t ${durationSecs} "${tmpFile}" -y 2>/dev/null || ffmpeg -f alsa -i default -t ${durationSecs} "${tmpFile}" -y 2>/dev/null`
			];
			await new Promise((resolve, reject) => {
				const proc = spawn(cmd[0], cmd.slice(1), { timeout: (durationSecs + 5) * 1e3 });
				proc.on("close", (code) => code === 0 ? resolve() : reject(/* @__PURE__ */ new Error(`recording failed (exit ${code})`)));
				proc.on("error", reject);
			});
			const buffer = await fs$1.readFile(tmpFile);
			await fs$1.rm(tmpFile, { force: true }).catch(() => {});
			await sendInvokeResult(client, frame, {
				ok: true,
				payloadJSON: JSON.stringify({
					format,
					durationMs: duration,
					size: buffer.length,
					encoding: "base64",
					data: buffer.toString("base64")
				})
			});
		} catch (err) {
			await sendInvokeResult(client, frame, {
				ok: false,
				error: {
					code: "INTERNAL",
					message: String(err)
				}
			});
		}
		return;
	}
	if (command === "file.watch") {
		try {
			const params = decodeParams(frame.paramsJSON);
			if (!params.path) throw new Error("INVALID_REQUEST: path required");
			const watchPath = path.resolve(params.path);
			const duration = params.durationMs && params.durationMs > 0 ? params.durationMs : 1e4;
			const events = [];
			await new Promise((resolve) => {
				const watcher = fs.watch(watchPath, { recursive: false }, (eventType, filename) => {
					events.push({
						event: eventType,
						filename: filename ?? null,
						time: (/* @__PURE__ */ new Date()).toISOString()
					});
				});
				setTimeout(() => {
					watcher.close();
					resolve();
				}, Math.min(duration, 3e4));
			});
			await sendInvokeResult(client, frame, {
				ok: true,
				payloadJSON: JSON.stringify({
					path: watchPath,
					durationMs: duration,
					events
				})
			});
		} catch (err) {
			await sendInvokeResult(client, frame, {
				ok: false,
				error: {
					code: "INTERNAL",
					message: String(err)
				}
			});
		}
		return;
	}
	if (command === "file.read") {
		try {
			const params = decodeParams(frame.paramsJSON);
			if (!params.path || typeof params.path !== "string") throw new Error("INVALID_REQUEST: path required");
			const filePath = path.resolve(params.path);
			const stat = await fs$1.stat(filePath);
			if (!stat.isFile()) throw new Error("INVALID_REQUEST: path is not a file");
			const maxBytes = typeof params.maxBytes === "number" && params.maxBytes > 0 ? params.maxBytes : 50 * 1024 * 1024;
			if (stat.size > maxBytes) throw new Error(`INVALID_REQUEST: file size ${stat.size} exceeds limit ${maxBytes}`);
			const buffer = await fs$1.readFile(filePath);
			const encoding = params.encoding === "utf8" || params.encoding === "text" ? "utf8" : "base64";
			const data = encoding === "utf8" ? buffer.toString("utf8") : buffer.toString("base64");
			await sendInvokeResult(client, frame, {
				ok: true,
				payloadJSON: JSON.stringify({
					path: filePath,
					size: stat.size,
					encoding,
					data
				})
			});
		} catch (err) {
			const message = String(err);
			await sendInvokeResult(client, frame, {
				ok: false,
				error: {
					code: message.includes("ENOENT") ? "NOT_FOUND" : message.includes("EACCES") ? "PERMISSION_DENIED" : message.includes("INVALID_REQUEST") ? "INVALID_REQUEST" : "INTERNAL",
					message
				}
			});
		}
		return;
	}
	if (command === "file.write") {
		try {
			const params = decodeParams(frame.paramsJSON);
			if (!params.path || typeof params.path !== "string") throw new Error("INVALID_REQUEST: path required");
			if (typeof params.data !== "string") throw new Error("INVALID_REQUEST: data required");
			const filePath = path.resolve(params.path);
			if (params.mkdir) await fs$1.mkdir(path.dirname(filePath), { recursive: true });
			const buffer = (params.encoding === "utf8" || params.encoding === "text" ? "utf8" : "base64") === "utf8" ? Buffer.from(params.data, "utf8") : Buffer.from(params.data, "base64");
			await fs$1.writeFile(filePath, buffer);
			const stat = await fs$1.stat(filePath);
			await sendInvokeResult(client, frame, {
				ok: true,
				payloadJSON: JSON.stringify({
					path: filePath,
					size: stat.size,
					written: true
				})
			});
		} catch (err) {
			const message = String(err);
			await sendInvokeResult(client, frame, {
				ok: false,
				error: {
					code: message.includes("ENOENT") ? "NOT_FOUND" : message.includes("EACCES") ? "PERMISSION_DENIED" : message.includes("INVALID_REQUEST") ? "INVALID_REQUEST" : "INTERNAL",
					message
				}
			});
		}
		return;
	}
	if (command === "file.list") {
		try {
			const params = decodeParams(frame.paramsJSON);
			if (!params.path || typeof params.path !== "string") throw new Error("INVALID_REQUEST: path required");
			const dirPath = path.resolve(params.path);
			if (!(await fs$1.stat(dirPath)).isDirectory()) throw new Error("INVALID_REQUEST: path is not a directory");
			const items = (await fs$1.readdir(dirPath, { withFileTypes: true })).map((e) => ({
				name: e.name,
				type: e.isDirectory() ? "directory" : e.isFile() ? "file" : e.isSymbolicLink() ? "symlink" : "other"
			}));
			await sendInvokeResult(client, frame, {
				ok: true,
				payloadJSON: JSON.stringify({
					path: dirPath,
					entries: items
				})
			});
		} catch (err) {
			const message = String(err);
			await sendInvokeResult(client, frame, {
				ok: false,
				error: {
					code: message.includes("ENOENT") ? "NOT_FOUND" : message.includes("EACCES") ? "PERMISSION_DENIED" : message.includes("INVALID_REQUEST") ? "INVALID_REQUEST" : "INTERNAL",
					message
				}
			});
		}
		return;
	}
	if (command === "file.stat") {
		try {
			const params = decodeParams(frame.paramsJSON);
			if (!params.path || typeof params.path !== "string") throw new Error("INVALID_REQUEST: path required");
			const filePath = path.resolve(params.path);
			const stat = await fs$1.stat(filePath);
			await sendInvokeResult(client, frame, {
				ok: true,
				payloadJSON: JSON.stringify({
					path: filePath,
					size: stat.size,
					type: stat.isDirectory() ? "directory" : stat.isFile() ? "file" : "other",
					modified: stat.mtime.toISOString(),
					created: stat.birthtime.toISOString(),
					permissions: stat.mode.toString(8)
				})
			});
		} catch (err) {
			const message = String(err);
			await sendInvokeResult(client, frame, {
				ok: false,
				error: {
					code: message.includes("ENOENT") ? "NOT_FOUND" : "INTERNAL",
					message
				}
			});
		}
		return;
	}
	if (command === "file.copy") {
		try {
			const params = decodeParams(frame.paramsJSON);
			if (!params.source || !params.destination) throw new Error("INVALID_REQUEST: source and destination required");
			const src = path.resolve(params.source);
			const dst = path.resolve(params.destination);
			if (params.mkdir) await fs$1.mkdir(path.dirname(dst), { recursive: true });
			await fs$1.copyFile(src, dst);
			const stat = await fs$1.stat(dst);
			await sendInvokeResult(client, frame, {
				ok: true,
				payloadJSON: JSON.stringify({
					source: src,
					destination: dst,
					size: stat.size
				})
			});
		} catch (err) {
			const message = String(err);
			await sendInvokeResult(client, frame, {
				ok: false,
				error: {
					code: message.includes("ENOENT") ? "NOT_FOUND" : message.includes("EACCES") ? "PERMISSION_DENIED" : "INTERNAL",
					message
				}
			});
		}
		return;
	}
	if (command === "file.delete") {
		try {
			const params = decodeParams(frame.paramsJSON);
			if (!params.path || typeof params.path !== "string") throw new Error("INVALID_REQUEST: path required");
			const filePath = path.resolve(params.path);
			await fs$1.rm(filePath, { force: true });
			await sendInvokeResult(client, frame, {
				ok: true,
				payloadJSON: JSON.stringify({
					path: filePath,
					deleted: true
				})
			});
		} catch (err) {
			await sendInvokeResult(client, frame, {
				ok: false,
				error: {
					code: "INTERNAL",
					message: String(err)
				}
			});
		}
		return;
	}
	if (command !== "system.run") {
		await sendInvokeResult(client, frame, {
			ok: false,
			error: {
				code: "UNAVAILABLE",
				message: "command not supported"
			}
		});
		return;
	}
	let params;
	try {
		params = decodeParams(frame.paramsJSON);
	} catch (err) {
		await sendInvokeResult(client, frame, {
			ok: false,
			error: {
				code: "INVALID_REQUEST",
				message: String(err)
			}
		});
		return;
	}
	if (!Array.isArray(params.command) || params.command.length === 0) {
		await sendInvokeResult(client, frame, {
			ok: false,
			error: {
				code: "INVALID_REQUEST",
				message: "command required"
			}
		});
		return;
	}
	const argv = params.command.map((item) => String(item));
	const rawCommand = typeof params.rawCommand === "string" ? params.rawCommand.trim() : "";
	const cmdText = rawCommand || formatCommand(argv);
	const koiId = params.koiId?.trim() || void 0;
	const cfg = loadConfig();
	const koiExec = koiId ? resolveKoiConfig(cfg, koiId)?.tools?.exec : void 0;
	const approvals = resolveExecApprovals(koiId, {
		security: resolveExecSecurity(koiExec?.security ?? cfg.tools?.exec?.security),
		ask: resolveExecAsk(koiExec?.ask ?? cfg.tools?.exec?.ask)
	});
	const security = approvals.koi.security;
	const ask = approvals.koi.ask;
	const autoAllowSkills = approvals.koi.autoAllowSkills;
	const sessionKey = params.sessionKey?.trim() || "node";
	const runId = params.runId?.trim() || crypto.randomUUID();
	const env = sanitizeEnv(params.env ?? void 0);
	const safeBins = resolveSafeBins(koiExec?.safeBins ?? cfg.tools?.exec?.safeBins);
	const bins = autoAllowSkills ? await skillBins.current() : /* @__PURE__ */ new Set();
	let analysisOk = false;
	let allowlistMatches = [];
	let allowlistSatisfied = false;
	let segments = [];
	if (rawCommand) {
		const allowlistEval = evaluateShellAllowlist({
			command: rawCommand,
			allowlist: approvals.allowlist,
			safeBins,
			cwd: params.cwd ?? void 0,
			env,
			skillBins: bins,
			autoAllowSkills,
			platform: process.platform
		});
		analysisOk = allowlistEval.analysisOk;
		allowlistMatches = allowlistEval.allowlistMatches;
		allowlistSatisfied = security === "allowlist" && analysisOk ? allowlistEval.allowlistSatisfied : false;
		segments = allowlistEval.segments;
	} else {
		const analysis = analyzeArgvCommand({
			argv,
			cwd: params.cwd ?? void 0,
			env
		});
		const allowlistEval = evaluateExecAllowlist({
			analysis,
			allowlist: approvals.allowlist,
			safeBins,
			cwd: params.cwd ?? void 0,
			skillBins: bins,
			autoAllowSkills
		});
		analysisOk = analysis.ok;
		allowlistMatches = allowlistEval.allowlistMatches;
		allowlistSatisfied = security === "allowlist" && analysisOk ? allowlistEval.allowlistSatisfied : false;
		segments = analysis.segments;
	}
	const isWindows = process.platform === "win32";
	const cmdInvocation = rawCommand ? isCmdExeInvocation(segments[0]?.argv ?? []) : isCmdExeInvocation(argv);
	if (security === "allowlist" && isWindows && cmdInvocation) {
		analysisOk = false;
		allowlistSatisfied = false;
	}
	if (process.platform === "darwin") {
		const approvalDecision = params.approvalDecision === "allow-once" || params.approvalDecision === "allow-always" ? params.approvalDecision : null;
		const response = await runViaMacAppExecHost({
			approvals,
			request: {
				command: argv,
				rawCommand: rawCommand || null,
				cwd: params.cwd ?? null,
				env: params.env ?? null,
				timeoutMs: params.timeoutMs ?? null,
				needsScreenRecording: params.needsScreenRecording ?? null,
				koiId: koiId ?? null,
				sessionKey: sessionKey ?? null,
				approvalDecision
			}
		});
		if (!response) {
			if (execHostEnforced || !execHostFallbackAllowed) {
				await sendNodeEvent(client, "exec.denied", buildExecEventPayload({
					sessionKey,
					runId,
					host: "node",
					command: cmdText,
					reason: "companion-unavailable"
				}));
				await sendInvokeResult(client, frame, {
					ok: false,
					error: {
						code: "UNAVAILABLE",
						message: "COMPANION_APP_UNAVAILABLE: macOS app exec host unreachable"
					}
				});
				return;
			}
		} else if (!response.ok) {
			await sendNodeEvent(client, "exec.denied", buildExecEventPayload({
				sessionKey,
				runId,
				host: "node",
				command: cmdText,
				reason: response.error.reason ?? "approval-required"
			}));
			await sendInvokeResult(client, frame, {
				ok: false,
				error: {
					code: "UNAVAILABLE",
					message: response.error.message
				}
			});
			return;
		} else {
			const result = response.payload;
			const combined = [
				result.stdout,
				result.stderr,
				result.error
			].filter(Boolean).join("\n");
			await sendNodeEvent(client, "exec.finished", buildExecEventPayload({
				sessionKey,
				runId,
				host: "node",
				command: cmdText,
				exitCode: result.exitCode,
				timedOut: result.timedOut,
				success: result.success,
				output: combined
			}));
			await sendInvokeResult(client, frame, {
				ok: true,
				payloadJSON: JSON.stringify(result)
			});
			return;
		}
	}
	if (security === "deny") {
		await sendNodeEvent(client, "exec.denied", buildExecEventPayload({
			sessionKey,
			runId,
			host: "node",
			command: cmdText,
			reason: "security=deny"
		}));
		await sendInvokeResult(client, frame, {
			ok: false,
			error: {
				code: "UNAVAILABLE",
				message: "SYSTEM_RUN_DISABLED: security=deny"
			}
		});
		return;
	}
	const requiresAsk = requiresExecApproval({
		ask,
		security,
		analysisOk,
		allowlistSatisfied
	});
	const approvalDecision = params.approvalDecision === "allow-once" || params.approvalDecision === "allow-always" ? params.approvalDecision : null;
	const approvedByAsk = approvalDecision !== null || params.approved === true;
	if (requiresAsk && !approvedByAsk) {
		await sendNodeEvent(client, "exec.denied", buildExecEventPayload({
			sessionKey,
			runId,
			host: "node",
			command: cmdText,
			reason: "approval-required"
		}));
		await sendInvokeResult(client, frame, {
			ok: false,
			error: {
				code: "UNAVAILABLE",
				message: "SYSTEM_RUN_DENIED: approval required"
			}
		});
		return;
	}
	if (approvalDecision === "allow-always" && security === "allowlist") {
		if (analysisOk) for (const segment of segments) {
			const pattern = segment.resolution?.resolvedPath ?? "";
			if (pattern) addAllowlistEntry(approvals.file, koiId, pattern);
		}
	}
	if (security === "allowlist" && (!analysisOk || !allowlistSatisfied) && !approvedByAsk) {
		await sendNodeEvent(client, "exec.denied", buildExecEventPayload({
			sessionKey,
			runId,
			host: "node",
			command: cmdText,
			reason: "allowlist-miss"
		}));
		await sendInvokeResult(client, frame, {
			ok: false,
			error: {
				code: "UNAVAILABLE",
				message: "SYSTEM_RUN_DENIED: allowlist miss"
			}
		});
		return;
	}
	if (allowlistMatches.length > 0) {
		const seen = /* @__PURE__ */ new Set();
		for (const match of allowlistMatches) {
			if (!match?.pattern || seen.has(match.pattern)) continue;
			seen.add(match.pattern);
			recordAllowlistUse(approvals.file, koiId, match, cmdText, segments[0]?.resolution?.resolvedPath);
		}
	}
	if (params.needsScreenRecording === true) {
		await sendNodeEvent(client, "exec.denied", buildExecEventPayload({
			sessionKey,
			runId,
			host: "node",
			command: cmdText,
			reason: "permission:screenRecording"
		}));
		await sendInvokeResult(client, frame, {
			ok: false,
			error: {
				code: "UNAVAILABLE",
				message: "PERMISSION_MISSING: screenRecording"
			}
		});
		return;
	}
	let execArgv = argv;
	if (security === "allowlist" && isWindows && !approvedByAsk && rawCommand && analysisOk && allowlistSatisfied && segments.length === 1 && segments[0]?.argv.length > 0) execArgv = segments[0].argv;
	const result = await runCommand(execArgv, params.cwd?.trim() || void 0, env, params.timeoutMs ?? void 0);
	if (result.truncated) {
		const suffix = "... (truncated)";
		if (result.stderr.trim().length > 0) result.stderr = `${result.stderr}\n${suffix}`;
		else result.stdout = `${result.stdout}\n${suffix}`;
	}
	const combined = [
		result.stdout,
		result.stderr,
		result.error
	].filter(Boolean).join("\n");
	await sendNodeEvent(client, "exec.finished", buildExecEventPayload({
		sessionKey,
		runId,
		host: "node",
		command: cmdText,
		exitCode: result.exitCode,
		timedOut: result.timedOut,
		success: result.success,
		output: combined
	}));
	await sendInvokeResult(client, frame, {
		ok: true,
		payloadJSON: JSON.stringify({
			exitCode: result.exitCode,
			timedOut: result.timedOut,
			success: result.success,
			stdout: result.stdout,
			stderr: result.stderr,
			error: result.error ?? null
		})
	});
}
function decodeParams(raw) {
	if (!raw) throw new Error("INVALID_REQUEST: paramsJSON required");
	return JSON.parse(raw);
}
function coerceNodeInvokePayload(payload) {
	if (!payload || typeof payload !== "object") return null;
	const obj = payload;
	const id = typeof obj.id === "string" ? obj.id.trim() : "";
	const nodeId = typeof obj.nodeId === "string" ? obj.nodeId.trim() : "";
	const command = typeof obj.command === "string" ? obj.command.trim() : "";
	if (!id || !nodeId || !command) return null;
	return {
		id,
		nodeId,
		command,
		paramsJSON: typeof obj.paramsJSON === "string" ? obj.paramsJSON : obj.params !== void 0 ? JSON.stringify(obj.params) : null,
		timeoutMs: typeof obj.timeoutMs === "number" ? obj.timeoutMs : null,
		idempotencyKey: typeof obj.idempotencyKey === "string" ? obj.idempotencyKey : null
	};
}
async function sendInvokeResult(client, frame, result) {
	try {
		await client.request("node.invoke.result", buildNodeInvokeResultParams(frame, result));
	} catch {}
}
function buildNodeInvokeResultParams(frame, result) {
	const params = {
		id: frame.id,
		nodeId: frame.nodeId,
		ok: result.ok
	};
	if (result.payload !== void 0) params.payload = result.payload;
	if (typeof result.payloadJSON === "string") params.payloadJSON = result.payloadJSON;
	if (result.error) params.error = result.error;
	return params;
}
async function sendNodeEvent(client, event, payload) {
	try {
		await client.request("node.event", {
			event,
			payloadJSON: payload ? JSON.stringify(payload) : null
		});
	} catch {}
}

//#endregion
export { loadNodeHostConfig as n, runNodeHost as t };