import { D as CHANNEL_IDS, s as defaultRuntime } from "./subsystem-hATE68va.js";
import { r as STATE_DIR } from "./paths-VdmjGGyc.js";
import { d as resolveKoiIdFromSessionKey, l as normalizeKoiId } from "./session-key-B2OXLVi_.js";
import { b as sleep, h as resolveUserPath } from "./utils-D019Z2NU.js";
import { S as ensureKoiWorkspace, b as DEFAULT_TOOLS_FILENAME, f as DEFAULT_BOOTSTRAP_FILENAME, g as DEFAULT_KOI_WORKSPACE_DIR, h as DEFAULT_KOIS_FILENAME, i as resolveKoiConfig, m as DEFAULT_IDENTITY_FILENAME, p as DEFAULT_HEARTBEAT_FILENAME, u as resolveSessionKoiId, x as DEFAULT_USER_FILENAME, y as DEFAULT_SOUL_FILENAME } from "./koi-scope-BeICsBhH.js";
import { t as formatCliCommand } from "./command-format-DOYW_19_.js";
import { o as loadConfig } from "./config-BOTdzMIB.js";
import { A as resolveKoiMainSessionKey, O as canonicalizeMainSessionAlias } from "./sessions-DyqGkYWU.js";
import { a as expandToolGroups } from "./tool-policy-itfBRjY_.js";
import { M as DEFAULT_SKYKOI_BROWSER_COLOR, P as DEFAULT_SKYKOI_BROWSER_PROFILE_NAME, j as DEFAULT_BROWSER_EVALUATE_ENABLED } from "./chrome-CTicuLaA.js";
import { o as syncSkillsToWorkspace } from "./skills-DHpPesfZ.js";
import { t as registerBrowserRoutes } from "./routes-C952rvWJ.js";
import { o as resolveProfile, t as createBrowserRouteContext } from "./server-context-Cs8Yx5qf.js";
import path from "node:path";
import os from "node:os";
import fs from "node:fs/promises";
import { spawn } from "node:child_process";
import crypto from "node:crypto";
import express from "express";

//#region src/koi/sandbox/constants.ts
const DEFAULT_SANDBOX_WORKSPACE_ROOT = path.join(os.homedir(), ".skykoi", "sandboxes");
const DEFAULT_SANDBOX_IMAGE = "SKYKOI-sandbox:bookworm-slim";
const DEFAULT_SANDBOX_CONTAINER_PREFIX = "SKYKOI-sbx-";
const DEFAULT_SANDBOX_WORKDIR = "/workspace";
const DEFAULT_SANDBOX_IDLE_HOURS = 24;
const DEFAULT_SANDBOX_MAX_AGE_DAYS = 7;
const DEFAULT_TOOL_ALLOW = [
	"exec",
	"process",
	"read",
	"write",
	"edit",
	"apply_patch",
	"image",
	"sessions_list",
	"sessions_history",
	"sessions_send",
	"sessions_spawn",
	"session_status"
];
const DEFAULT_TOOL_DENY = [
	"browser",
	"canvas",
	"nodes",
	"cron",
	"gateway",
	...CHANNEL_IDS
];
const DEFAULT_SANDBOX_BROWSER_IMAGE = "SKYKOI-sandbox-browser:bookworm-slim";
const DEFAULT_SANDBOX_COMMON_IMAGE = "SKYKOI-sandbox-common:bookworm-slim";
const DEFAULT_SANDBOX_BROWSER_PREFIX = "SKYKOI-sbx-browser-";
const DEFAULT_SANDBOX_BROWSER_CDP_PORT = 9222;
const DEFAULT_SANDBOX_BROWSER_VNC_PORT = 5900;
const DEFAULT_SANDBOX_BROWSER_NOVNC_PORT = 6080;
const DEFAULT_SANDBOX_BROWSER_AUTOSTART_TIMEOUT_MS = 12e3;
const SANDBOX_KOI_WORKSPACE_MOUNT = "/koi";
const resolvedSandboxStateDir = STATE_DIR ?? path.join(os.homedir(), ".skykoi");
const SANDBOX_STATE_DIR = path.join(resolvedSandboxStateDir, "sandbox");
const SANDBOX_REGISTRY_PATH = path.join(SANDBOX_STATE_DIR, "containers.json");
const SANDBOX_BROWSER_REGISTRY_PATH = path.join(SANDBOX_STATE_DIR, "browsers.json");

//#endregion
//#region src/koi/sandbox/tool-policy.ts
function compilePattern(pattern) {
	const normalized = pattern.trim().toLowerCase();
	if (!normalized) return {
		kind: "exact",
		value: ""
	};
	if (normalized === "*") return { kind: "all" };
	if (!normalized.includes("*")) return {
		kind: "exact",
		value: normalized
	};
	const escaped = normalized.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
	return {
		kind: "regex",
		value: new RegExp(`^${escaped.replaceAll("\\*", ".*")}$`)
	};
}
function compilePatterns(patterns) {
	if (!Array.isArray(patterns)) return [];
	return expandToolGroups(patterns).map(compilePattern).filter((pattern) => pattern.kind !== "exact" || pattern.value);
}
function matchesAny(name, patterns) {
	for (const pattern of patterns) {
		if (pattern.kind === "all") return true;
		if (pattern.kind === "exact" && name === pattern.value) return true;
		if (pattern.kind === "regex" && pattern.value.test(name)) return true;
	}
	return false;
}
function isToolAllowed(policy, name) {
	const normalized = name.trim().toLowerCase();
	if (matchesAny(normalized, compilePatterns(policy.deny))) return false;
	const allow = compilePatterns(policy.allow);
	if (allow.length === 0) return true;
	return matchesAny(normalized, allow);
}
function resolveSandboxToolPolicyForKoi(cfg, koiId) {
	const koiConfig = cfg && koiId ? resolveKoiConfig(cfg, koiId) : void 0;
	const koiAllow = koiConfig?.tools?.sandbox?.tools?.allow;
	const koiDeny = koiConfig?.tools?.sandbox?.tools?.deny;
	const globalAllow = cfg?.tools?.sandbox?.tools?.allow;
	const globalDeny = cfg?.tools?.sandbox?.tools?.deny;
	const allowSource = Array.isArray(koiAllow) ? {
		source: "koi",
		key: "kois.list[].tools.sandbox.tools.allow"
	} : Array.isArray(globalAllow) ? {
		source: "global",
		key: "tools.sandbox.tools.allow"
	} : {
		source: "default",
		key: "tools.sandbox.tools.allow"
	};
	const denySource = Array.isArray(koiDeny) ? {
		source: "koi",
		key: "kois.list[].tools.sandbox.tools.deny"
	} : Array.isArray(globalDeny) ? {
		source: "global",
		key: "tools.sandbox.tools.deny"
	} : {
		source: "default",
		key: "tools.sandbox.tools.deny"
	};
	const deny = Array.isArray(koiDeny) ? koiDeny : Array.isArray(globalDeny) ? globalDeny : [...DEFAULT_TOOL_DENY];
	const allow = Array.isArray(koiAllow) ? koiAllow : Array.isArray(globalAllow) ? globalAllow : [...DEFAULT_TOOL_ALLOW];
	const expandedDeny = expandToolGroups(deny);
	let expandedAllow = expandToolGroups(allow);
	if (!expandedDeny.map((v) => v.toLowerCase()).includes("image") && !expandedAllow.map((v) => v.toLowerCase()).includes("image")) expandedAllow = [...expandedAllow, "image"];
	return {
		allow: expandedAllow,
		deny: expandedDeny,
		sources: {
			allow: allowSource,
			deny: denySource
		}
	};
}

//#endregion
//#region src/koi/sandbox/config.ts
function resolveSandboxScope(params) {
	if (params.scope) return params.scope;
	if (typeof params.perSession === "boolean") return params.perSession ? "session" : "shared";
	return "koi";
}
function resolveSandboxDockerConfig(params) {
	const koiDocker = params.scope === "shared" ? void 0 : params.koiDocker;
	const globalDocker = params.globalDocker;
	const env = koiDocker?.env ? {
		...globalDocker?.env ?? { LANG: "C.UTF-8" },
		...koiDocker.env
	} : globalDocker?.env ?? { LANG: "C.UTF-8" };
	const ulimits = koiDocker?.ulimits ? {
		...globalDocker?.ulimits,
		...koiDocker.ulimits
	} : globalDocker?.ulimits;
	const binds = [...globalDocker?.binds ?? [], ...koiDocker?.binds ?? []];
	return {
		image: koiDocker?.image ?? globalDocker?.image ?? DEFAULT_SANDBOX_IMAGE,
		containerPrefix: koiDocker?.containerPrefix ?? globalDocker?.containerPrefix ?? DEFAULT_SANDBOX_CONTAINER_PREFIX,
		workdir: koiDocker?.workdir ?? globalDocker?.workdir ?? DEFAULT_SANDBOX_WORKDIR,
		readOnlyRoot: koiDocker?.readOnlyRoot ?? globalDocker?.readOnlyRoot ?? true,
		tmpfs: koiDocker?.tmpfs ?? globalDocker?.tmpfs ?? [
			"/tmp",
			"/var/tmp",
			"/run"
		],
		network: koiDocker?.network ?? globalDocker?.network ?? "none",
		user: koiDocker?.user ?? globalDocker?.user,
		capDrop: koiDocker?.capDrop ?? globalDocker?.capDrop ?? ["ALL"],
		env,
		setupCommand: koiDocker?.setupCommand ?? globalDocker?.setupCommand,
		pidsLimit: koiDocker?.pidsLimit ?? globalDocker?.pidsLimit,
		memory: koiDocker?.memory ?? globalDocker?.memory,
		memorySwap: koiDocker?.memorySwap ?? globalDocker?.memorySwap,
		cpus: koiDocker?.cpus ?? globalDocker?.cpus,
		ulimits,
		seccompProfile: koiDocker?.seccompProfile ?? globalDocker?.seccompProfile,
		apparmorProfile: koiDocker?.apparmorProfile ?? globalDocker?.apparmorProfile,
		dns: koiDocker?.dns ?? globalDocker?.dns,
		extraHosts: koiDocker?.extraHosts ?? globalDocker?.extraHosts,
		binds: binds.length ? binds : void 0
	};
}
function resolveSandboxBrowserConfig(params) {
	const koiBrowser = params.scope === "shared" ? void 0 : params.koiBrowser;
	const globalBrowser = params.globalBrowser;
	return {
		enabled: koiBrowser?.enabled ?? globalBrowser?.enabled ?? false,
		image: koiBrowser?.image ?? globalBrowser?.image ?? DEFAULT_SANDBOX_BROWSER_IMAGE,
		containerPrefix: koiBrowser?.containerPrefix ?? globalBrowser?.containerPrefix ?? DEFAULT_SANDBOX_BROWSER_PREFIX,
		cdpPort: koiBrowser?.cdpPort ?? globalBrowser?.cdpPort ?? DEFAULT_SANDBOX_BROWSER_CDP_PORT,
		vncPort: koiBrowser?.vncPort ?? globalBrowser?.vncPort ?? DEFAULT_SANDBOX_BROWSER_VNC_PORT,
		noVncPort: koiBrowser?.noVncPort ?? globalBrowser?.noVncPort ?? DEFAULT_SANDBOX_BROWSER_NOVNC_PORT,
		headless: koiBrowser?.headless ?? globalBrowser?.headless ?? false,
		enableNoVnc: koiBrowser?.enableNoVnc ?? globalBrowser?.enableNoVnc ?? true,
		allowHostControl: koiBrowser?.allowHostControl ?? globalBrowser?.allowHostControl ?? false,
		autoStart: koiBrowser?.autoStart ?? globalBrowser?.autoStart ?? true,
		autoStartTimeoutMs: koiBrowser?.autoStartTimeoutMs ?? globalBrowser?.autoStartTimeoutMs ?? DEFAULT_SANDBOX_BROWSER_AUTOSTART_TIMEOUT_MS
	};
}
function resolveSandboxPruneConfig(params) {
	const koiPrune = params.scope === "shared" ? void 0 : params.koiPrune;
	const globalPrune = params.globalPrune;
	return {
		idleHours: koiPrune?.idleHours ?? globalPrune?.idleHours ?? DEFAULT_SANDBOX_IDLE_HOURS,
		maxAgeDays: koiPrune?.maxAgeDays ?? globalPrune?.maxAgeDays ?? DEFAULT_SANDBOX_MAX_AGE_DAYS
	};
}
function resolveSandboxConfigForKoi(cfg, koiId) {
	const koi = cfg?.kois?.defaults?.sandbox;
	let koiSandbox;
	const koiConfig = cfg && koiId ? resolveKoiConfig(cfg, koiId) : void 0;
	if (koiConfig?.sandbox) koiSandbox = koiConfig.sandbox;
	const scope = resolveSandboxScope({
		scope: koiSandbox?.scope ?? koi?.scope,
		perSession: koiSandbox?.perSession ?? koi?.perSession
	});
	const toolPolicy = resolveSandboxToolPolicyForKoi(cfg, koiId);
	return {
		mode: koiSandbox?.mode ?? koi?.mode ?? "off",
		scope,
		workspaceAccess: koiSandbox?.workspaceAccess ?? koi?.workspaceAccess ?? "none",
		workspaceRoot: koiSandbox?.workspaceRoot ?? koi?.workspaceRoot ?? DEFAULT_SANDBOX_WORKSPACE_ROOT,
		docker: resolveSandboxDockerConfig({
			scope,
			globalDocker: koi?.docker,
			koiDocker: koiSandbox?.docker
		}),
		browser: resolveSandboxBrowserConfig({
			scope,
			globalBrowser: koi?.browser,
			koiBrowser: koiSandbox?.browser
		}),
		tools: {
			allow: toolPolicy.allow,
			deny: toolPolicy.deny
		},
		prune: resolveSandboxPruneConfig({
			scope,
			globalPrune: koi?.prune,
			koiPrune: koiSandbox?.prune
		})
	};
}

//#endregion
//#region src/browser/bridge-server.ts
async function startBrowserBridgeServer(params) {
	const host = params.host ?? "127.0.0.1";
	const port = params.port ?? 0;
	const app = express();
	app.use(express.json({ limit: "1mb" }));
	const authToken = params.authToken?.trim();
	if (authToken) app.use((req, res, next) => {
		if (String(req.headers.authorization ?? "").trim() === `Bearer ${authToken}`) return next();
		res.status(401).send("Unauthorized");
	});
	const state = {
		server: null,
		port,
		resolved: params.resolved,
		profiles: /* @__PURE__ */ new Map()
	};
	registerBrowserRoutes(app, createBrowserRouteContext({
		getState: () => state,
		onEnsureAttachTarget: params.onEnsureAttachTarget
	}));
	const server = await new Promise((resolve, reject) => {
		const s = app.listen(port, host, () => resolve(s));
		s.once("error", reject);
	});
	const resolvedPort = server.address()?.port ?? port;
	state.server = server;
	state.port = resolvedPort;
	state.resolved.controlPort = resolvedPort;
	return {
		server,
		port: resolvedPort,
		baseUrl: `http://${host}:${resolvedPort}`,
		state
	};
}
async function stopBrowserBridgeServer(server) {
	await new Promise((resolve) => {
		server.close(() => resolve());
	});
}

//#endregion
//#region src/koi/sandbox/browser-bridges.ts
const BROWSER_BRIDGES = /* @__PURE__ */ new Map();

//#endregion
//#region src/koi/sandbox/config-hash.ts
function isPrimitive(value) {
	return value === null || typeof value !== "object" && typeof value !== "function";
}
function normalizeForHash(value) {
	if (value === void 0) return;
	if (Array.isArray(value)) {
		const normalized = value.map(normalizeForHash).filter((item) => item !== void 0);
		const primitives = normalized.filter(isPrimitive);
		if (primitives.length === normalized.length) return [...primitives].toSorted((a, b) => primitiveToString(a).localeCompare(primitiveToString(b)));
		return normalized;
	}
	if (value && typeof value === "object") {
		const entries = Object.entries(value).toSorted(([a], [b]) => a.localeCompare(b));
		const normalized = {};
		for (const [key, entryValue] of entries) {
			const next = normalizeForHash(entryValue);
			if (next !== void 0) normalized[key] = next;
		}
		return normalized;
	}
	return value;
}
function primitiveToString(value) {
	if (value === null) return "null";
	if (typeof value === "string") return value;
	if (typeof value === "number") return String(value);
	if (typeof value === "boolean") return value ? "true" : "false";
	return JSON.stringify(value);
}
function computeSandboxConfigHash(input) {
	const payload = normalizeForHash(input);
	const raw = JSON.stringify(payload);
	return crypto.createHash("sha1").update(raw).digest("hex");
}

//#endregion
//#region src/koi/sandbox/registry.ts
async function readRegistry() {
	try {
		const raw = await fs.readFile(SANDBOX_REGISTRY_PATH, "utf-8");
		const parsed = JSON.parse(raw);
		if (parsed && Array.isArray(parsed.entries)) return parsed;
	} catch {}
	return { entries: [] };
}
async function writeRegistry(registry) {
	await fs.mkdir(SANDBOX_STATE_DIR, { recursive: true });
	await fs.writeFile(SANDBOX_REGISTRY_PATH, `${JSON.stringify(registry, null, 2)}\n`, "utf-8");
}
async function updateRegistry(entry) {
	const registry = await readRegistry();
	const existing = registry.entries.find((item) => item.containerName === entry.containerName);
	const next = registry.entries.filter((item) => item.containerName !== entry.containerName);
	next.push({
		...entry,
		createdAtMs: existing?.createdAtMs ?? entry.createdAtMs,
		image: existing?.image ?? entry.image,
		configHash: entry.configHash ?? existing?.configHash
	});
	await writeRegistry({ entries: next });
}
async function removeRegistryEntry(containerName) {
	const registry = await readRegistry();
	const next = registry.entries.filter((item) => item.containerName !== containerName);
	if (next.length === registry.entries.length) return;
	await writeRegistry({ entries: next });
}
async function readBrowserRegistry() {
	try {
		const raw = await fs.readFile(SANDBOX_BROWSER_REGISTRY_PATH, "utf-8");
		const parsed = JSON.parse(raw);
		if (parsed && Array.isArray(parsed.entries)) return parsed;
	} catch {}
	return { entries: [] };
}
async function writeBrowserRegistry(registry) {
	await fs.mkdir(SANDBOX_STATE_DIR, { recursive: true });
	await fs.writeFile(SANDBOX_BROWSER_REGISTRY_PATH, `${JSON.stringify(registry, null, 2)}\n`, "utf-8");
}
async function updateBrowserRegistry(entry) {
	const registry = await readBrowserRegistry();
	const existing = registry.entries.find((item) => item.containerName === entry.containerName);
	const next = registry.entries.filter((item) => item.containerName !== entry.containerName);
	next.push({
		...entry,
		createdAtMs: existing?.createdAtMs ?? entry.createdAtMs,
		image: existing?.image ?? entry.image
	});
	await writeBrowserRegistry({ entries: next });
}
async function removeBrowserRegistryEntry(containerName) {
	const registry = await readBrowserRegistry();
	const next = registry.entries.filter((item) => item.containerName !== containerName);
	if (next.length === registry.entries.length) return;
	await writeBrowserRegistry({ entries: next });
}

//#endregion
//#region src/koi/sandbox/shared.ts
function slugifySessionKey(value) {
	const trimmed = value.trim() || "session";
	const hash = crypto.createHash("sha1").update(trimmed).digest("hex").slice(0, 8);
	return `${trimmed.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 32) || "session"}-${hash}`;
}
function resolveSandboxWorkspaceDir(root, sessionKey) {
	const resolvedRoot = resolveUserPath(root);
	const slug = slugifySessionKey(sessionKey);
	return path.join(resolvedRoot, slug);
}
function resolveSandboxScopeKey(scope, sessionKey) {
	const trimmed = sessionKey.trim() || "main";
	if (scope === "shared") return "shared";
	if (scope === "session") return trimmed;
	return `koi:${resolveKoiIdFromSessionKey(trimmed)}`;
}
function resolveSandboxKoiId(scopeKey) {
	const trimmed = scopeKey.trim();
	if (!trimmed || trimmed === "shared") return;
	const parts = trimmed.split(":").filter(Boolean);
	if (parts[0] === "koi" && parts[1]) return normalizeKoiId(parts[1]);
	return resolveKoiIdFromSessionKey(trimmed);
}

//#endregion
//#region src/koi/sandbox/docker.ts
const HOT_CONTAINER_WINDOW_MS = 300 * 1e3;
function execDocker(args, opts) {
	return new Promise((resolve, reject) => {
		const child = spawn("docker", args, { stdio: [
			"ignore",
			"pipe",
			"pipe"
		] });
		let stdout = "";
		let stderr = "";
		child.stdout?.on("data", (chunk) => {
			stdout += chunk.toString();
		});
		child.stderr?.on("data", (chunk) => {
			stderr += chunk.toString();
		});
		child.on("close", (code) => {
			const exitCode = code ?? 0;
			if (exitCode !== 0 && !opts?.allowFailure) {
				reject(new Error(stderr.trim() || `docker ${args.join(" ")} failed`));
				return;
			}
			resolve({
				stdout,
				stderr,
				code: exitCode
			});
		});
	});
}
async function readDockerPort(containerName, port) {
	const result = await execDocker([
		"port",
		containerName,
		`${port}/tcp`
	], { allowFailure: true });
	if (result.code !== 0) return null;
	const match = (result.stdout.trim().split(/\r?\n/)[0] ?? "").match(/:(\d+)\s*$/);
	if (!match) return null;
	const mapped = Number.parseInt(match[1] ?? "", 10);
	return Number.isFinite(mapped) ? mapped : null;
}
async function dockerImageExists(image) {
	const result = await execDocker([
		"image",
		"inspect",
		image
	], { allowFailure: true });
	if (result.code === 0) return true;
	const stderr = result.stderr.trim();
	if (stderr.includes("No such image")) return false;
	throw new Error(`Failed to inspect sandbox image: ${stderr}`);
}
async function ensureDockerImage(image) {
	if (await dockerImageExists(image)) return;
	if (image === DEFAULT_SANDBOX_IMAGE) {
		await execDocker(["pull", "debian:bookworm-slim"]);
		await execDocker([
			"tag",
			"debian:bookworm-slim",
			DEFAULT_SANDBOX_IMAGE
		]);
		return;
	}
	throw new Error(`Sandbox image not found: ${image}. Build or pull it first.`);
}
async function dockerContainerState(name) {
	const result = await execDocker([
		"inspect",
		"-f",
		"{{.State.Running}}",
		name
	], { allowFailure: true });
	if (result.code !== 0) return {
		exists: false,
		running: false
	};
	return {
		exists: true,
		running: result.stdout.trim() === "true"
	};
}
function normalizeDockerLimit(value) {
	if (value === void 0 || value === null) return;
	if (typeof value === "number") return Number.isFinite(value) ? String(value) : void 0;
	const trimmed = value.trim();
	return trimmed ? trimmed : void 0;
}
function formatUlimitValue(name, value) {
	if (!name.trim()) return null;
	if (typeof value === "number" || typeof value === "string") {
		const raw = String(value).trim();
		return raw ? `${name}=${raw}` : null;
	}
	const soft = typeof value.soft === "number" ? Math.max(0, value.soft) : void 0;
	const hard = typeof value.hard === "number" ? Math.max(0, value.hard) : void 0;
	if (soft === void 0 && hard === void 0) return null;
	if (soft === void 0) return `${name}=${hard}`;
	if (hard === void 0) return `${name}=${soft}`;
	return `${name}=${soft}:${hard}`;
}
function buildSandboxCreateArgs(params) {
	const createdAtMs = params.createdAtMs ?? Date.now();
	const args = [
		"create",
		"--name",
		params.name
	];
	args.push("--label", "skykoi.sandbox=1");
	args.push("--label", `SKYKOI.sessionKey=${params.scopeKey}`);
	args.push("--label", `SKYKOI.createdAtMs=${createdAtMs}`);
	if (params.configHash) args.push("--label", `SKYKOI.configHash=${params.configHash}`);
	for (const [key, value] of Object.entries(params.labels ?? {})) if (key && value) args.push("--label", `${key}=${value}`);
	if (params.cfg.readOnlyRoot) args.push("--read-only");
	for (const entry of params.cfg.tmpfs) args.push("--tmpfs", entry);
	if (params.cfg.network) args.push("--network", params.cfg.network);
	if (params.cfg.user) args.push("--user", params.cfg.user);
	for (const cap of params.cfg.capDrop) args.push("--cap-drop", cap);
	args.push("--security-opt", "no-new-privileges");
	if (params.cfg.seccompProfile) args.push("--security-opt", `seccomp=${params.cfg.seccompProfile}`);
	if (params.cfg.apparmorProfile) args.push("--security-opt", `apparmor=${params.cfg.apparmorProfile}`);
	for (const entry of params.cfg.dns ?? []) if (entry.trim()) args.push("--dns", entry);
	for (const entry of params.cfg.extraHosts ?? []) if (entry.trim()) args.push("--add-host", entry);
	if (typeof params.cfg.pidsLimit === "number" && params.cfg.pidsLimit > 0) args.push("--pids-limit", String(params.cfg.pidsLimit));
	const memory = normalizeDockerLimit(params.cfg.memory);
	if (memory) args.push("--memory", memory);
	const memorySwap = normalizeDockerLimit(params.cfg.memorySwap);
	if (memorySwap) args.push("--memory-swap", memorySwap);
	if (typeof params.cfg.cpus === "number" && params.cfg.cpus > 0) args.push("--cpus", String(params.cfg.cpus));
	for (const [name, value] of Object.entries(params.cfg.ulimits ?? {})) {
		const formatted = formatUlimitValue(name, value);
		if (formatted) args.push("--ulimit", formatted);
	}
	if (params.cfg.binds?.length) for (const bind of params.cfg.binds) args.push("-v", bind);
	return args;
}
async function createSandboxContainer(params) {
	const { name, cfg, workspaceDir, scopeKey } = params;
	await ensureDockerImage(cfg.image);
	const args = buildSandboxCreateArgs({
		name,
		cfg,
		scopeKey,
		configHash: params.configHash
	});
	args.push("--workdir", cfg.workdir);
	const mainMountSuffix = params.workspaceAccess === "ro" && workspaceDir === params.koiWorkspaceDir ? ":ro" : "";
	args.push("-v", `${workspaceDir}:${cfg.workdir}${mainMountSuffix}`);
	if (params.workspaceAccess !== "none" && workspaceDir !== params.koiWorkspaceDir) {
		const koiMountSuffix = params.workspaceAccess === "ro" ? ":ro" : "";
		args.push("-v", `${params.koiWorkspaceDir}:${SANDBOX_KOI_WORKSPACE_MOUNT}${koiMountSuffix}`);
	}
	args.push(cfg.image, "sleep", "infinity");
	await execDocker(args);
	await execDocker(["start", name]);
	if (cfg.setupCommand?.trim()) await execDocker([
		"exec",
		"-i",
		name,
		"sh",
		"-lc",
		cfg.setupCommand
	]);
}
async function readContainerConfigHash(containerName) {
	const readLabel = async (label) => {
		const result = await execDocker([
			"inspect",
			"-f",
			`{{ index .Config.Labels "${label}" }}`,
			containerName
		], { allowFailure: true });
		if (result.code !== 0) return null;
		const raw = result.stdout.trim();
		if (!raw || raw === "<no value>") return null;
		return raw;
	};
	return await readLabel("skykoi.configHash");
}
function formatSandboxRecreateHint(params) {
	if (params.scope === "session") return formatCliCommand(`SKYKOI sandbox recreate --session ${params.sessionKey}`);
	if (params.scope === "koi") return formatCliCommand(`SKYKOI sandbox recreate --koi ${resolveSandboxKoiId(params.sessionKey) ?? "main"}`);
	return formatCliCommand("skykoi sandbox recreate --all");
}
async function ensureSandboxContainer(params) {
	const scopeKey = resolveSandboxScopeKey(params.cfg.scope, params.sessionKey);
	const slug = params.cfg.scope === "shared" ? "shared" : slugifySessionKey(scopeKey);
	const containerName = `${params.cfg.docker.containerPrefix}${slug}`.slice(0, 63);
	const expectedHash = computeSandboxConfigHash({
		docker: params.cfg.docker,
		workspaceAccess: params.cfg.workspaceAccess,
		workspaceDir: params.workspaceDir,
		koiWorkspaceDir: params.koiWorkspaceDir
	});
	const now = Date.now();
	const state = await dockerContainerState(containerName);
	let hasContainer = state.exists;
	let running = state.running;
	let currentHash = null;
	let hashMismatch = false;
	let registryEntry;
	if (hasContainer) {
		registryEntry = (await readRegistry()).entries.find((entry) => entry.containerName === containerName);
		currentHash = await readContainerConfigHash(containerName);
		if (!currentHash) currentHash = registryEntry?.configHash ?? null;
		hashMismatch = !currentHash || currentHash !== expectedHash;
		if (hashMismatch) {
			const lastUsedAtMs = registryEntry?.lastUsedAtMs;
			if (running && (typeof lastUsedAtMs !== "number" || now - lastUsedAtMs < HOT_CONTAINER_WINDOW_MS)) {
				const hint = formatSandboxRecreateHint({
					scope: params.cfg.scope,
					sessionKey: scopeKey
				});
				defaultRuntime.log(`Sandbox config changed for ${containerName} (recently used). Recreate to apply: ${hint}`);
			} else {
				await execDocker([
					"rm",
					"-f",
					containerName
				], { allowFailure: true });
				hasContainer = false;
				running = false;
			}
		}
	}
	if (!hasContainer) await createSandboxContainer({
		name: containerName,
		cfg: params.cfg.docker,
		workspaceDir: params.workspaceDir,
		workspaceAccess: params.cfg.workspaceAccess,
		koiWorkspaceDir: params.koiWorkspaceDir,
		scopeKey,
		configHash: expectedHash
	});
	else if (!running) await execDocker(["start", containerName]);
	await updateRegistry({
		containerName,
		sessionKey: scopeKey,
		createdAtMs: now,
		lastUsedAtMs: now,
		image: params.cfg.docker.image,
		configHash: hashMismatch && running ? currentHash ?? void 0 : expectedHash
	});
	return containerName;
}

//#endregion
//#region src/koi/sandbox/browser.ts
async function waitForSandboxCdp(params) {
	const deadline = Date.now() + Math.max(0, params.timeoutMs);
	const url = `http://127.0.0.1:${params.cdpPort}/json/version`;
	while (Date.now() < deadline) {
		try {
			const ctrl = new AbortController();
			const t = setTimeout(() => ctrl.abort(), 1e3);
			try {
				if ((await fetch(url, { signal: ctrl.signal })).ok) return true;
			} finally {
				clearTimeout(t);
			}
		} catch {}
		await sleep(150);
	}
	return false;
}
function buildSandboxBrowserResolvedConfig(params) {
	return {
		enabled: true,
		evaluateEnabled: params.evaluateEnabled,
		controlPort: params.controlPort,
		cdpProtocol: "http",
		cdpHost: "127.0.0.1",
		cdpIsLoopback: true,
		remoteCdpTimeoutMs: 1500,
		remoteCdpHandshakeTimeoutMs: 3e3,
		color: DEFAULT_SKYKOI_BROWSER_COLOR,
		executablePath: void 0,
		headless: params.headless,
		noSandbox: false,
		attachOnly: true,
		defaultProfile: DEFAULT_SKYKOI_BROWSER_PROFILE_NAME,
		profiles: { [DEFAULT_SKYKOI_BROWSER_PROFILE_NAME]: {
			cdpPort: params.cdpPort,
			color: DEFAULT_SKYKOI_BROWSER_COLOR
		} }
	};
}
async function ensureSandboxBrowserImage(image) {
	if ((await execDocker([
		"image",
		"inspect",
		image
	], { allowFailure: true })).code === 0) return;
	throw new Error(`Sandbox browser image not found: ${image}. Build it with scripts/sandbox-browser-setup.sh.`);
}
async function ensureSandboxBrowser(params) {
	if (!params.cfg.browser.enabled) return null;
	if (!isToolAllowed(params.cfg.tools, "browser")) return null;
	const slug = params.cfg.scope === "shared" ? "shared" : slugifySessionKey(params.scopeKey);
	const containerName = `${params.cfg.browser.containerPrefix}${slug}`.slice(0, 63);
	const state = await dockerContainerState(containerName);
	if (!state.exists) {
		await ensureSandboxBrowserImage(params.cfg.browser.image ?? DEFAULT_SANDBOX_BROWSER_IMAGE);
		const args = buildSandboxCreateArgs({
			name: containerName,
			cfg: params.cfg.docker,
			scopeKey: params.scopeKey,
			labels: { "skykoi.sandboxBrowser": "1" }
		});
		const mainMountSuffix = params.cfg.workspaceAccess === "ro" && params.workspaceDir === params.koiWorkspaceDir ? ":ro" : "";
		args.push("-v", `${params.workspaceDir}:${params.cfg.docker.workdir}${mainMountSuffix}`);
		if (params.cfg.workspaceAccess !== "none" && params.workspaceDir !== params.koiWorkspaceDir) {
			const koiMountSuffix = params.cfg.workspaceAccess === "ro" ? ":ro" : "";
			args.push("-v", `${params.koiWorkspaceDir}:${SANDBOX_KOI_WORKSPACE_MOUNT}${koiMountSuffix}`);
		}
		args.push("-p", `127.0.0.1::${params.cfg.browser.cdpPort}`);
		if (params.cfg.browser.enableNoVnc && !params.cfg.browser.headless) args.push("-p", `127.0.0.1::${params.cfg.browser.noVncPort}`);
		args.push("-e", `SKYKOI_BROWSER_HEADLESS=${params.cfg.browser.headless ? "1" : "0"}`);
		args.push("-e", `SKYKOI_BROWSER_ENABLE_NOVNC=${params.cfg.browser.enableNoVnc ? "1" : "0"}`);
		args.push("-e", `SKYKOI_BROWSER_CDP_PORT=${params.cfg.browser.cdpPort}`);
		args.push("-e", `SKYKOI_BROWSER_VNC_PORT=${params.cfg.browser.vncPort}`);
		args.push("-e", `SKYKOI_BROWSER_NOVNC_PORT=${params.cfg.browser.noVncPort}`);
		args.push(params.cfg.browser.image);
		await execDocker(args);
		await execDocker(["start", containerName]);
	} else if (!state.running) await execDocker(["start", containerName]);
	const mappedCdp = await readDockerPort(containerName, params.cfg.browser.cdpPort);
	if (!mappedCdp) throw new Error(`Failed to resolve CDP port mapping for ${containerName}.`);
	const mappedNoVnc = params.cfg.browser.enableNoVnc && !params.cfg.browser.headless ? await readDockerPort(containerName, params.cfg.browser.noVncPort) : null;
	const existing = BROWSER_BRIDGES.get(params.scopeKey);
	const existingProfile = existing ? resolveProfile(existing.bridge.state.resolved, DEFAULT_SKYKOI_BROWSER_PROFILE_NAME) : null;
	const shouldReuse = existing && existing.containerName === containerName && existingProfile?.cdpPort === mappedCdp;
	if (existing && !shouldReuse) {
		await stopBrowserBridgeServer(existing.bridge.server).catch(() => void 0);
		BROWSER_BRIDGES.delete(params.scopeKey);
	}
	const bridge = (() => {
		if (shouldReuse && existing) return existing.bridge;
		return null;
	})();
	const ensureBridge = async () => {
		if (bridge) return bridge;
		const onEnsureAttachTarget = params.cfg.browser.autoStart ? async () => {
			const state = await dockerContainerState(containerName);
			if (state.exists && !state.running) await execDocker(["start", containerName]);
			if (!await waitForSandboxCdp({
				cdpPort: mappedCdp,
				timeoutMs: params.cfg.browser.autoStartTimeoutMs
			})) throw new Error(`Sandbox browser CDP did not become reachable on 127.0.0.1:${mappedCdp} within ${params.cfg.browser.autoStartTimeoutMs}ms.`);
		} : void 0;
		return await startBrowserBridgeServer({
			resolved: buildSandboxBrowserResolvedConfig({
				controlPort: 0,
				cdpPort: mappedCdp,
				headless: params.cfg.browser.headless,
				evaluateEnabled: params.evaluateEnabled ?? DEFAULT_BROWSER_EVALUATE_ENABLED
			}),
			onEnsureAttachTarget
		});
	};
	const resolvedBridge = await ensureBridge();
	if (!shouldReuse) BROWSER_BRIDGES.set(params.scopeKey, {
		bridge: resolvedBridge,
		containerName
	});
	const now = Date.now();
	await updateBrowserRegistry({
		containerName,
		sessionKey: params.scopeKey,
		createdAtMs: now,
		lastUsedAtMs: now,
		image: params.cfg.browser.image,
		cdpPort: mappedCdp,
		noVncPort: mappedNoVnc ?? void 0
	});
	const noVncUrl = mappedNoVnc && params.cfg.browser.enableNoVnc && !params.cfg.browser.headless ? `http://127.0.0.1:${mappedNoVnc}/vnc.html?autoconnect=1&resize=remote` : void 0;
	return {
		bridgeUrl: resolvedBridge.baseUrl,
		noVncUrl,
		containerName
	};
}

//#endregion
//#region src/koi/sandbox/prune.ts
let lastPruneAtMs = 0;
async function pruneSandboxContainers(cfg) {
	const now = Date.now();
	const idleHours = cfg.prune.idleHours;
	const maxAgeDays = cfg.prune.maxAgeDays;
	if (idleHours === 0 && maxAgeDays === 0) return;
	const registry = await readRegistry();
	for (const entry of registry.entries) {
		const idleMs = now - entry.lastUsedAtMs;
		const ageMs = now - entry.createdAtMs;
		if (idleHours > 0 && idleMs > idleHours * 60 * 60 * 1e3 || maxAgeDays > 0 && ageMs > maxAgeDays * 24 * 60 * 60 * 1e3) try {
			await execDocker([
				"rm",
				"-f",
				entry.containerName
			], { allowFailure: true });
		} catch {} finally {
			await removeRegistryEntry(entry.containerName);
		}
	}
}
async function pruneSandboxBrowsers(cfg) {
	const now = Date.now();
	const idleHours = cfg.prune.idleHours;
	const maxAgeDays = cfg.prune.maxAgeDays;
	if (idleHours === 0 && maxAgeDays === 0) return;
	const registry = await readBrowserRegistry();
	for (const entry of registry.entries) {
		const idleMs = now - entry.lastUsedAtMs;
		const ageMs = now - entry.createdAtMs;
		if (idleHours > 0 && idleMs > idleHours * 60 * 60 * 1e3 || maxAgeDays > 0 && ageMs > maxAgeDays * 24 * 60 * 60 * 1e3) try {
			await execDocker([
				"rm",
				"-f",
				entry.containerName
			], { allowFailure: true });
		} catch {} finally {
			await removeBrowserRegistryEntry(entry.containerName);
			const bridge = BROWSER_BRIDGES.get(entry.sessionKey);
			if (bridge?.containerName === entry.containerName) {
				await stopBrowserBridgeServer(bridge.bridge.server).catch(() => void 0);
				BROWSER_BRIDGES.delete(entry.sessionKey);
			}
		}
	}
}
async function maybePruneSandboxes(cfg) {
	const now = Date.now();
	if (now - lastPruneAtMs < 300 * 1e3) return;
	lastPruneAtMs = now;
	try {
		await pruneSandboxContainers(cfg);
		await pruneSandboxBrowsers(cfg);
	} catch (error) {
		const message = error instanceof Error ? error.message : typeof error === "string" ? error : JSON.stringify(error);
		defaultRuntime.error?.(`Sandbox prune failed: ${message ?? "unknown error"}`);
	}
}

//#endregion
//#region src/koi/sandbox/runtime-status.ts
function shouldSandboxSession(cfg, sessionKey, mainSessionKey) {
	if (cfg.mode === "off") return false;
	if (cfg.mode === "all") return true;
	return sessionKey.trim() !== mainSessionKey.trim();
}
function resolveMainSessionKeyForSandbox(params) {
	if (params.cfg?.session?.scope === "global") return "global";
	return resolveKoiMainSessionKey({
		cfg: params.cfg,
		koiId: params.koiId
	});
}
function resolveComparableSessionKeyForSandbox(params) {
	return canonicalizeMainSessionAlias({
		cfg: params.cfg,
		koiId: params.koiId,
		sessionKey: params.sessionKey
	});
}
function resolveSandboxRuntimeStatus(params) {
	const sessionKey = params.sessionKey?.trim() ?? "";
	const koiId = resolveSessionKoiId({
		sessionKey,
		config: params.cfg
	});
	const cfg = params.cfg;
	const sandboxCfg = resolveSandboxConfigForKoi(cfg, koiId);
	const mainSessionKey = resolveMainSessionKeyForSandbox({
		cfg,
		koiId
	});
	const sandboxed = sessionKey ? shouldSandboxSession(sandboxCfg, resolveComparableSessionKeyForSandbox({
		cfg,
		koiId,
		sessionKey
	}), mainSessionKey) : false;
	return {
		koiId,
		sessionKey,
		mainSessionKey,
		mode: sandboxCfg.mode,
		sandboxed,
		toolPolicy: resolveSandboxToolPolicyForKoi(cfg, koiId)
	};
}
function formatSandboxToolPolicyBlockedMessage(params) {
	const tool = params.toolName.trim().toLowerCase();
	if (!tool) return;
	const runtime = resolveSandboxRuntimeStatus({
		cfg: params.cfg,
		sessionKey: params.sessionKey
	});
	if (!runtime.sandboxed) return;
	const deny = new Set(expandToolGroups(runtime.toolPolicy.deny));
	const allow = expandToolGroups(runtime.toolPolicy.allow);
	const allowSet = allow.length > 0 ? new Set(allow) : null;
	const blockedByDeny = deny.has(tool);
	const blockedByAllow = allowSet ? !allowSet.has(tool) : false;
	if (!blockedByDeny && !blockedByAllow) return;
	const reasons = [];
	const fixes = [];
	if (blockedByDeny) {
		reasons.push("deny list");
		fixes.push(`Remove "${tool}" from ${runtime.toolPolicy.sources.deny.key}.`);
	}
	if (blockedByAllow) {
		reasons.push("allow list");
		fixes.push(`Add "${tool}" to ${runtime.toolPolicy.sources.allow.key} (or set it to [] to allow all).`);
	}
	const lines = [];
	lines.push(`Tool "${tool}" blocked by sandbox tool policy (mode=${runtime.mode}).`);
	lines.push(`Session: ${runtime.sessionKey || "(unknown)"}`);
	lines.push(`Reason: ${reasons.join(" + ")}`);
	lines.push("Fix:");
	lines.push(`- kois.defaults.sandbox.mode=off (disable sandbox)`);
	for (const fix of fixes) lines.push(`- ${fix}`);
	if (runtime.mode === "non-main") lines.push(`- Use main session key (direct): ${runtime.mainSessionKey}`);
	lines.push(`- See: ${formatCliCommand(`SKYKOI sandbox explain --session ${runtime.sessionKey}`)}`);
	return lines.join("\n");
}

//#endregion
//#region src/koi/sandbox/workspace.ts
async function ensureSandboxWorkspace(workspaceDir, seedFrom, skipBootstrap) {
	await fs.mkdir(workspaceDir, { recursive: true });
	if (seedFrom) {
		const seed = resolveUserPath(seedFrom);
		const files = [
			DEFAULT_KOIS_FILENAME,
			DEFAULT_SOUL_FILENAME,
			DEFAULT_TOOLS_FILENAME,
			DEFAULT_IDENTITY_FILENAME,
			DEFAULT_USER_FILENAME,
			DEFAULT_BOOTSTRAP_FILENAME,
			DEFAULT_HEARTBEAT_FILENAME
		];
		for (const name of files) {
			const src = path.join(seed, name);
			const dest = path.join(workspaceDir, name);
			try {
				await fs.access(dest);
			} catch {
				try {
					const content = await fs.readFile(src, "utf-8");
					await fs.writeFile(dest, content, {
						encoding: "utf-8",
						flag: "wx"
					});
				} catch {}
			}
		}
	}
	await ensureKoiWorkspace({
		dir: workspaceDir,
		ensureBootstrapFiles: !skipBootstrap
	});
}

//#endregion
//#region src/koi/sandbox/context.ts
async function resolveSandboxContext(params) {
	const rawSessionKey = params.sessionKey?.trim();
	if (!rawSessionKey) return null;
	const runtime = resolveSandboxRuntimeStatus({
		cfg: params.config,
		sessionKey: rawSessionKey
	});
	if (!runtime.sandboxed) return null;
	const cfg = resolveSandboxConfigForKoi(params.config, runtime.koiId);
	await maybePruneSandboxes(cfg);
	const koiWorkspaceDir = resolveUserPath(params.workspaceDir?.trim() || DEFAULT_KOI_WORKSPACE_DIR);
	const workspaceRoot = resolveUserPath(cfg.workspaceRoot);
	const scopeKey = resolveSandboxScopeKey(cfg.scope, rawSessionKey);
	const sandboxWorkspaceDir = cfg.scope === "shared" ? workspaceRoot : resolveSandboxWorkspaceDir(workspaceRoot, scopeKey);
	const workspaceDir = cfg.workspaceAccess === "rw" ? koiWorkspaceDir : sandboxWorkspaceDir;
	if (workspaceDir === sandboxWorkspaceDir) {
		await ensureSandboxWorkspace(sandboxWorkspaceDir, koiWorkspaceDir, params.config?.kois?.defaults?.skipBootstrap);
		if (cfg.workspaceAccess !== "rw") try {
			await syncSkillsToWorkspace({
				sourceWorkspaceDir: koiWorkspaceDir,
				targetWorkspaceDir: sandboxWorkspaceDir,
				config: params.config
			});
		} catch (error) {
			const message = error instanceof Error ? error.message : JSON.stringify(error);
			defaultRuntime.error?.(`Sandbox skill sync failed: ${message}`);
		}
	} else await fs.mkdir(workspaceDir, { recursive: true });
	const containerName = await ensureSandboxContainer({
		sessionKey: rawSessionKey,
		workspaceDir,
		koiWorkspaceDir,
		cfg
	});
	const browser = await ensureSandboxBrowser({
		scopeKey,
		workspaceDir,
		koiWorkspaceDir,
		cfg,
		evaluateEnabled: params.config?.browser?.evaluateEnabled ?? DEFAULT_BROWSER_EVALUATE_ENABLED
	});
	return {
		enabled: true,
		sessionKey: rawSessionKey,
		workspaceDir,
		koiWorkspaceDir,
		workspaceAccess: cfg.workspaceAccess,
		containerName,
		containerWorkdir: cfg.docker.workdir,
		docker: cfg.docker,
		tools: cfg.tools,
		browserAllowHostControl: cfg.browser.allowHostControl,
		browser: browser ?? void 0
	};
}
async function ensureSandboxWorkspaceForSession(params) {
	const rawSessionKey = params.sessionKey?.trim();
	if (!rawSessionKey) return null;
	const runtime = resolveSandboxRuntimeStatus({
		cfg: params.config,
		sessionKey: rawSessionKey
	});
	if (!runtime.sandboxed) return null;
	const cfg = resolveSandboxConfigForKoi(params.config, runtime.koiId);
	const koiWorkspaceDir = resolveUserPath(params.workspaceDir?.trim() || DEFAULT_KOI_WORKSPACE_DIR);
	const workspaceRoot = resolveUserPath(cfg.workspaceRoot);
	const scopeKey = resolveSandboxScopeKey(cfg.scope, rawSessionKey);
	const sandboxWorkspaceDir = cfg.scope === "shared" ? workspaceRoot : resolveSandboxWorkspaceDir(workspaceRoot, scopeKey);
	const workspaceDir = cfg.workspaceAccess === "rw" ? koiWorkspaceDir : sandboxWorkspaceDir;
	if (workspaceDir === sandboxWorkspaceDir) {
		await ensureSandboxWorkspace(sandboxWorkspaceDir, koiWorkspaceDir, params.config?.kois?.defaults?.skipBootstrap);
		if (cfg.workspaceAccess !== "rw") try {
			await syncSkillsToWorkspace({
				sourceWorkspaceDir: koiWorkspaceDir,
				targetWorkspaceDir: sandboxWorkspaceDir,
				config: params.config
			});
		} catch (error) {
			const message = error instanceof Error ? error.message : JSON.stringify(error);
			defaultRuntime.error?.(`Sandbox skill sync failed: ${message}`);
		}
	} else await fs.mkdir(workspaceDir, { recursive: true });
	return {
		workspaceDir,
		containerWorkdir: cfg.docker.workdir
	};
}

//#endregion
//#region src/koi/sandbox/manage.ts
async function listSandboxContainers() {
	const config = loadConfig();
	const registry = await readRegistry();
	const results = [];
	for (const entry of registry.entries) {
		const state = await dockerContainerState(entry.containerName);
		let actualImage = entry.image;
		if (state.exists) try {
			const result = await execDocker([
				"inspect",
				"-f",
				"{{.Config.Image}}",
				entry.containerName
			], { allowFailure: true });
			if (result.code === 0) actualImage = result.stdout.trim();
		} catch {}
		const configuredImage = resolveSandboxConfigForKoi(config, resolveSandboxKoiId(entry.sessionKey)).docker.image;
		results.push({
			...entry,
			image: actualImage,
			running: state.running,
			imageMatch: actualImage === configuredImage
		});
	}
	return results;
}
async function listSandboxBrowsers() {
	const config = loadConfig();
	const registry = await readBrowserRegistry();
	const results = [];
	for (const entry of registry.entries) {
		const state = await dockerContainerState(entry.containerName);
		let actualImage = entry.image;
		if (state.exists) try {
			const result = await execDocker([
				"inspect",
				"-f",
				"{{.Config.Image}}",
				entry.containerName
			], { allowFailure: true });
			if (result.code === 0) actualImage = result.stdout.trim();
		} catch {}
		const configuredImage = resolveSandboxConfigForKoi(config, resolveSandboxKoiId(entry.sessionKey)).browser.image;
		results.push({
			...entry,
			image: actualImage,
			running: state.running,
			imageMatch: actualImage === configuredImage
		});
	}
	return results;
}
async function removeSandboxContainer(containerName) {
	try {
		await execDocker([
			"rm",
			"-f",
			containerName
		], { allowFailure: true });
	} catch {}
	await removeRegistryEntry(containerName);
}
async function removeSandboxBrowserContainer(containerName) {
	try {
		await execDocker([
			"rm",
			"-f",
			containerName
		], { allowFailure: true });
	} catch {}
	await removeBrowserRegistryEntry(containerName);
	for (const [sessionKey, bridge] of BROWSER_BRIDGES.entries()) if (bridge.containerName === containerName) {
		await stopBrowserBridgeServer(bridge.bridge.server).catch(() => void 0);
		BROWSER_BRIDGES.delete(sessionKey);
	}
}

//#endregion
export { ensureSandboxWorkspaceForSession as a, resolveSandboxRuntimeStatus as c, resolveSandboxToolPolicyForKoi as d, DEFAULT_SANDBOX_BROWSER_IMAGE as f, removeSandboxContainer as i, resolveSandboxConfigForKoi as l, DEFAULT_SANDBOX_IMAGE as m, listSandboxContainers as n, resolveSandboxContext as o, DEFAULT_SANDBOX_COMMON_IMAGE as p, removeSandboxBrowserContainer as r, formatSandboxToolPolicyBlockedMessage as s, listSandboxBrowsers as t, resolveSandboxScope as u };