import { d as resolveConfigDir } from "./utils-DLd4fFd2.js";
import { d as resolveGatewaySystemdServiceName, f as resolveGatewayWindowsTaskName, l as resolveGatewayLaunchAgentLabel, n as GATEWAY_SERVICE_KIND, r as GATEWAY_SERVICE_MARKER } from "./constants-CJLwmrLm.js";
import { o as resolveGatewayLogPaths, s as resolveLaunchAgentPlistPath } from "./service-BXV5WdCs.js";
import { o as resolveSystemdUserUnitPath } from "./systemd-B3oVOU-l.js";
import { a as isVersionManagedNodePath, i as isSystemNodePath, l as resolveSystemNodePath, r as getMinimalServicePathPartsFromEnv } from "./service-env-BwW73jih.js";
import path from "node:path";
import dotenv from "dotenv";
import { promisify } from "node:util";
import fs from "node:fs/promises";
import { execFile } from "node:child_process";

//#region ../../src/daemon/diagnostics.ts
const GATEWAY_LOG_ERROR_PATTERNS = [
	/refusing to bind gateway/i,
	/gateway auth mode/i,
	/gateway start blocked/i,
	/failed to bind gateway socket/i,
	/tailscale .* requires/i
];
async function readLastLogLine(filePath) {
	try {
		const lines = (await fs.readFile(filePath, "utf8")).split(/\r?\n/).map((line) => line.trim());
		for (let i = lines.length - 1; i >= 0; i -= 1) if (lines[i]) return lines[i];
		return null;
	} catch {
		return null;
	}
}
async function readLastGatewayErrorLine(env) {
	const { stdoutPath, stderrPath } = resolveGatewayLogPaths(env);
	const stderrRaw = await fs.readFile(stderrPath, "utf8").catch(() => "");
	const stdoutRaw = await fs.readFile(stdoutPath, "utf8").catch(() => "");
	const lines = [...stderrRaw.split(/\r?\n/), ...stdoutRaw.split(/\r?\n/)].map((line) => line.trim());
	for (let i = lines.length - 1; i >= 0; i -= 1) {
		const line = lines[i];
		if (!line) continue;
		if (GATEWAY_LOG_ERROR_PATTERNS.some((pattern) => pattern.test(line))) return line;
	}
	return await readLastLogLine(stderrPath) ?? await readLastLogLine(stdoutPath);
}

//#endregion
//#region ../../src/daemon/inspect.ts
const EXTRA_MARKERS = [
	"edwinpai",
	"clawdbot",
	"moltbot"
];
const execFileAsync = promisify(execFile);
function renderGatewayServiceCleanupHints(env = process.env) {
	const profile = env.EDWINPAI_PROFILE;
	switch (process.platform) {
		case "darwin": {
			const label = resolveGatewayLaunchAgentLabel(profile);
			return [`launchctl bootout gui/$UID/${label}`, `rm ~/Library/LaunchAgents/${label}.plist`];
		}
		case "linux": {
			const unit = resolveGatewaySystemdServiceName(profile);
			return [`systemctl --user disable --now ${unit}.service`, `rm ~/.config/systemd/user/${unit}.service`];
		}
		case "win32": return [`schtasks /Delete /TN "${resolveGatewayWindowsTaskName(profile)}" /F`];
		default: return [];
	}
}
function resolveHomeDir(env) {
	const home = env.HOME?.trim() || env.USERPROFILE?.trim();
	if (!home) throw new Error("Missing HOME");
	return home;
}
function detectMarker(content) {
	const lower = content.toLowerCase();
	for (const marker of EXTRA_MARKERS) if (lower.includes(marker)) return marker;
	return null;
}
function hasGatewayServiceMarker(content) {
	const lower = content.toLowerCase();
	const markerKeys = ["edwin_service_marker"];
	const kindKeys = ["edwin_service_kind"];
	const markerValues = [GATEWAY_SERVICE_MARKER.toLowerCase()];
	const hasMarkerKey = markerKeys.some((key) => lower.includes(key));
	const hasKindKey = kindKeys.some((key) => lower.includes(key));
	const hasMarkerValue = markerValues.some((value) => lower.includes(value));
	return hasMarkerKey && hasKindKey && hasMarkerValue && lower.includes(GATEWAY_SERVICE_KIND.toLowerCase());
}
function isEdwinPAIGatewayLaunchdService(label, contents) {
	if (hasGatewayServiceMarker(contents)) return true;
	if (!contents.toLowerCase().includes("gateway")) return false;
	return label.startsWith("ai.edwinpai.") || label.startsWith("ai.edwin.");
}
function isEdwinPAIGatewaySystemdService(name, contents) {
	if (hasGatewayServiceMarker(contents)) return true;
	if (!name.startsWith("edwinpai-gateway")) return false;
	return contents.toLowerCase().includes("gateway");
}
function isEdwinPAIGatewayTaskName(name) {
	const normalized = name.trim().toLowerCase();
	if (!normalized) return false;
	return normalized === resolveGatewayWindowsTaskName().toLowerCase() || normalized.startsWith("edwinpai gateway");
}
function tryExtractPlistLabel(contents) {
	const match = contents.match(/<key>Label<\/key>\s*<string>([\s\S]*?)<\/string>/i);
	if (!match) return null;
	return match[1]?.trim() || null;
}
function isIgnoredLaunchdLabel(label) {
	return label === resolveGatewayLaunchAgentLabel();
}
function isIgnoredSystemdName(name) {
	return name === resolveGatewaySystemdServiceName();
}
function isLegacyLabel(label) {
	const lower = label.toLowerCase();
	return lower.includes("clawdbot") || lower.includes("moltbot");
}
async function scanLaunchdDir(params) {
	const results = [];
	let entries = [];
	try {
		entries = await fs.readdir(params.dir);
	} catch {
		return results;
	}
	for (const entry of entries) {
		if (!entry.endsWith(".plist")) continue;
		const labelFromName = entry.replace(/\.plist$/, "");
		if (isIgnoredLaunchdLabel(labelFromName)) continue;
		const fullPath = path.join(params.dir, entry);
		let contents = "";
		try {
			contents = await fs.readFile(fullPath, "utf8");
		} catch {
			continue;
		}
		const marker = detectMarker(contents);
		const label = tryExtractPlistLabel(contents) ?? labelFromName;
		if (!marker) {
			if (!(isLegacyLabel(labelFromName) || isLegacyLabel(label))) continue;
			results.push({
				platform: "darwin",
				label,
				detail: `plist: ${fullPath}`,
				scope: params.scope,
				marker: isLegacyLabel(label) ? "clawdbot" : "moltbot",
				legacy: true
			});
			continue;
		}
		if (isIgnoredLaunchdLabel(label)) continue;
		if (marker === "edwinpai" && isEdwinPAIGatewayLaunchdService(label, contents)) continue;
		results.push({
			platform: "darwin",
			label,
			detail: `plist: ${fullPath}`,
			scope: params.scope,
			marker,
			legacy: marker !== "edwinpai" || isLegacyLabel(label)
		});
	}
	return results;
}
async function scanSystemdDir(params) {
	const results = [];
	let entries = [];
	try {
		entries = await fs.readdir(params.dir);
	} catch {
		return results;
	}
	for (const entry of entries) {
		if (!entry.endsWith(".service")) continue;
		const name = entry.replace(/\.service$/, "");
		if (isIgnoredSystemdName(name)) continue;
		const fullPath = path.join(params.dir, entry);
		let contents = "";
		try {
			contents = await fs.readFile(fullPath, "utf8");
		} catch {
			continue;
		}
		const marker = detectMarker(contents);
		if (!marker) continue;
		if (marker === "edwinpai" && isEdwinPAIGatewaySystemdService(name, contents)) continue;
		results.push({
			platform: "linux",
			label: entry,
			detail: `unit: ${fullPath}`,
			scope: params.scope,
			marker,
			legacy: marker !== "edwinpai"
		});
	}
	return results;
}
function parseSchtasksList(output) {
	const tasks = [];
	let current = null;
	for (const rawLine of output.split(/\r?\n/)) {
		const line = rawLine.trim();
		if (!line) {
			if (current) {
				tasks.push(current);
				current = null;
			}
			continue;
		}
		const idx = line.indexOf(":");
		if (idx <= 0) continue;
		const key = line.slice(0, idx).trim().toLowerCase();
		const value = line.slice(idx + 1).trim();
		if (!value) continue;
		if (key === "taskname") {
			if (current) tasks.push(current);
			current = { name: value };
			continue;
		}
		if (!current) continue;
		if (key === "task to run") current.taskToRun = value;
	}
	if (current) tasks.push(current);
	return tasks;
}
async function execSchtasks(args) {
	try {
		const { stdout, stderr } = await execFileAsync("schtasks", args, {
			encoding: "utf8",
			windowsHide: true
		});
		return {
			stdout: String(stdout ?? ""),
			stderr: String(stderr ?? ""),
			code: 0
		};
	} catch (error) {
		const e = error;
		return {
			stdout: typeof e.stdout === "string" ? e.stdout : "",
			stderr: typeof e.stderr === "string" ? e.stderr : typeof e.message === "string" ? e.message : "",
			code: typeof e.code === "number" ? e.code : 1
		};
	}
}
async function findExtraGatewayServices(env, opts = {}) {
	const results = [];
	const seen = /* @__PURE__ */ new Set();
	const push = (svc) => {
		const key = `${svc.platform}:${svc.label}:${svc.detail}:${svc.scope}`;
		if (seen.has(key)) return;
		seen.add(key);
		results.push(svc);
	};
	if (process.platform === "darwin") {
		try {
			const home = resolveHomeDir(env);
			const userDir = path.join(home, "Library", "LaunchAgents");
			for (const svc of await scanLaunchdDir({
				dir: userDir,
				scope: "user"
			})) push(svc);
			if (opts.deep) {
				for (const svc of await scanLaunchdDir({
					dir: path.join(path.sep, "Library", "LaunchAgents"),
					scope: "system"
				})) push(svc);
				for (const svc of await scanLaunchdDir({
					dir: path.join(path.sep, "Library", "LaunchDaemons"),
					scope: "system"
				})) push(svc);
			}
		} catch {
			return results;
		}
		return results;
	}
	if (process.platform === "linux") {
		try {
			const home = resolveHomeDir(env);
			const userDir = path.join(home, ".config", "systemd", "user");
			for (const svc of await scanSystemdDir({
				dir: userDir,
				scope: "user"
			})) push(svc);
			if (opts.deep) for (const dir of [
				"/etc/systemd/system",
				"/usr/lib/systemd/system",
				"/lib/systemd/system"
			]) for (const svc of await scanSystemdDir({
				dir,
				scope: "system"
			})) push(svc);
		} catch {
			return results;
		}
		return results;
	}
	if (process.platform === "win32") {
		if (!opts.deep) return results;
		const res = await execSchtasks([
			"/Query",
			"/FO",
			"LIST",
			"/V"
		]);
		if (res.code !== 0) return results;
		const tasks = parseSchtasksList(res.stdout);
		for (const task of tasks) {
			const name = task.name.trim();
			if (!name) continue;
			if (isEdwinPAIGatewayTaskName(name)) continue;
			const lowerName = name.toLowerCase();
			const lowerCommand = task.taskToRun?.toLowerCase() ?? "";
			let marker = null;
			for (const candidate of EXTRA_MARKERS) if (lowerName.includes(candidate) || lowerCommand.includes(candidate)) {
				marker = candidate;
				break;
			}
			if (!marker) continue;
			push({
				platform: "win32",
				label: name,
				detail: task.taskToRun ? `task: ${name}, run: ${task.taskToRun}` : name,
				scope: "system",
				marker,
				legacy: marker !== "edwinpai"
			});
		}
		return results;
	}
	return results;
}

//#endregion
//#region ../../src/daemon/service-audit.ts
const SERVICE_AUDIT_CODES = {
	gatewayCommandMissing: "gateway-command-missing",
	gatewayEmbeddingServiceSafeMissing: "gateway-embedding-service-safe-missing",
	gatewayEntrypointMismatch: "gateway-entrypoint-mismatch",
	gatewayPathMissing: "gateway-path-missing",
	gatewayPathMissingDirs: "gateway-path-missing-dirs",
	gatewayPathNonMinimal: "gateway-path-nonminimal",
	gatewayWrapperSourceCheckout: "gateway-wrapper-source-checkout",
	gatewayRuntimeBun: "gateway-runtime-bun",
	gatewayRuntimeNodeVersionManager: "gateway-runtime-node-version-manager",
	gatewayRuntimeNodeSystemMissing: "gateway-runtime-node-system-missing",
	launchdKeepAlive: "launchd-keep-alive",
	launchdRunAtLoad: "launchd-run-at-load",
	systemdAfterNetworkOnline: "systemd-after-network-online",
	systemdRestartSec: "systemd-restart-sec",
	systemdWantsNetworkOnline: "systemd-wants-network-online"
};
function needsNodeRuntimeMigration(issues) {
	return issues.some((issue) => issue.code === SERVICE_AUDIT_CODES.gatewayRuntimeBun || issue.code === SERVICE_AUDIT_CODES.gatewayRuntimeNodeVersionManager);
}
function hasGatewaySubcommand(programArguments) {
	return Boolean(programArguments?.some((arg) => arg === "gateway"));
}
function parseSystemdUnit(content) {
	const after = /* @__PURE__ */ new Set();
	const wants = /* @__PURE__ */ new Set();
	let restartSec;
	for (const rawLine of content.split(/\r?\n/)) {
		const line = rawLine.trim();
		if (!line) continue;
		if (line.startsWith("#") || line.startsWith(";")) continue;
		if (line.startsWith("[")) continue;
		const idx = line.indexOf("=");
		if (idx <= 0) continue;
		const key = line.slice(0, idx).trim();
		const value = line.slice(idx + 1).trim();
		if (!value) continue;
		if (key === "After") {
			for (const entry of value.split(/\s+/)) if (entry) after.add(entry);
		} else if (key === "Wants") {
			for (const entry of value.split(/\s+/)) if (entry) wants.add(entry);
		} else if (key === "RestartSec") restartSec = value;
	}
	return {
		after,
		wants,
		restartSec
	};
}
function isRestartSecPreferred(value) {
	if (!value) return false;
	const parsed = Number.parseFloat(value);
	if (!Number.isFinite(parsed)) return false;
	return Math.abs(parsed - 5) < .01;
}
async function auditSystemdUnit(env, issues) {
	const unitPath = resolveSystemdUserUnitPath(env);
	let content = "";
	try {
		content = await fs.readFile(unitPath, "utf8");
	} catch {
		return;
	}
	const parsed = parseSystemdUnit(content);
	if (!parsed.after.has("network-online.target")) issues.push({
		code: SERVICE_AUDIT_CODES.systemdAfterNetworkOnline,
		message: "Missing systemd After=network-online.target",
		detail: unitPath,
		level: "recommended"
	});
	if (!parsed.wants.has("network-online.target")) issues.push({
		code: SERVICE_AUDIT_CODES.systemdWantsNetworkOnline,
		message: "Missing systemd Wants=network-online.target",
		detail: unitPath,
		level: "recommended"
	});
	if (!isRestartSecPreferred(parsed.restartSec)) issues.push({
		code: SERVICE_AUDIT_CODES.systemdRestartSec,
		message: "RestartSec does not match the recommended 5s",
		detail: unitPath,
		level: "recommended"
	});
}
async function auditLaunchdPlist(env, issues) {
	const plistPath = resolveLaunchAgentPlistPath(env);
	let content = "";
	try {
		content = await fs.readFile(plistPath, "utf8");
	} catch {
		return;
	}
	const hasRunAtLoad = /<key>RunAtLoad<\/key>\s*<true\s*\/>/i.test(content);
	const hasKeepAlive = /<key>KeepAlive<\/key>\s*<true\s*\/>/i.test(content);
	if (!hasRunAtLoad) issues.push({
		code: SERVICE_AUDIT_CODES.launchdRunAtLoad,
		message: "LaunchAgent is missing RunAtLoad=true",
		detail: plistPath,
		level: "recommended"
	});
	if (!hasKeepAlive) issues.push({
		code: SERVICE_AUDIT_CODES.launchdKeepAlive,
		message: "LaunchAgent is missing KeepAlive=true",
		detail: plistPath,
		level: "recommended"
	});
}
async function readServiceWrapper(programArguments) {
	const wrapperPath = programArguments?.[0];
	if (!wrapperPath || !path.isAbsolute(wrapperPath)) return null;
	try {
		const stat = await fs.stat(wrapperPath);
		if (!stat.isFile() || stat.size > 64e3) return null;
		return await fs.readFile(wrapperPath, "utf8");
	} catch {
		return null;
	}
}
function wrapperLaunchesEdwinpaiGateway(content) {
	return /edwinpai(?:["']|\s).*\bgateway\b/i.test(content);
}
function wrapperLaunchesSourceCheckout(content) {
	return /[/\]dist[/\][^\s"']*index\.(?:js|mjs|cjs)\b/i.test(content);
}
async function auditGatewayCommand(programArguments, issues) {
	if (!programArguments || programArguments.length === 0) return;
	if (hasGatewaySubcommand(programArguments)) return;
	const wrapper = await readServiceWrapper(programArguments);
	if (wrapper) {
		if (wrapperLaunchesSourceCheckout(wrapper)) {
			issues.push({
				code: SERVICE_AUDIT_CODES.gatewayWrapperSourceCheckout,
				message: "Service wrapper launches a source checkout/dist path instead of the installed edwinpai CLI",
				detail: programArguments[0],
				level: "aggressive"
			});
			return;
		}
		if (wrapperLaunchesEdwinpaiGateway(wrapper)) return;
	}
	issues.push({
		code: SERVICE_AUDIT_CODES.gatewayCommandMissing,
		message: "Service command does not include the gateway subcommand",
		level: "aggressive"
	});
}
function isNodeRuntime(execPath) {
	const base = path.basename(execPath).toLowerCase();
	return base === "node" || base === "node.exe";
}
function isBunRuntime(execPath) {
	const base = path.basename(execPath).toLowerCase();
	return base === "bun" || base === "bun.exe";
}
function getPathModule(platform) {
	return platform === "win32" ? path.win32 : path.posix;
}
function normalizePathEntry(entry, platform) {
	const normalized = getPathModule(platform).normalize(entry).replaceAll("\\", "/");
	if (platform === "win32") return normalized.toLowerCase();
	return normalized;
}
function auditGatewayServicePath(command, issues, env, platform) {
	if (platform === "win32") return;
	const servicePath = command?.environment?.PATH;
	if (!servicePath) {
		issues.push({
			code: SERVICE_AUDIT_CODES.gatewayPathMissing,
			message: "Gateway service PATH is not set; the daemon should use a minimal PATH.",
			level: "recommended"
		});
		return;
	}
	const expected = getMinimalServicePathPartsFromEnv({
		platform,
		env
	});
	const parts = servicePath.split(getPathModule(platform).delimiter).map((entry) => entry.trim()).filter(Boolean);
	const normalizedParts = new Set(parts.map((entry) => normalizePathEntry(entry, platform)));
	const normalizedExpected = new Set(expected.map((entry) => normalizePathEntry(entry, platform)));
	const missing = expected.filter((entry) => {
		const normalized = normalizePathEntry(entry, platform);
		return !normalizedParts.has(normalized);
	});
	if (missing.length > 0) issues.push({
		code: SERVICE_AUDIT_CODES.gatewayPathMissingDirs,
		message: `Gateway service PATH missing required dirs: ${missing.join(", ")}`,
		level: "recommended"
	});
	const nonMinimal = parts.filter((entry) => {
		const normalized = normalizePathEntry(entry, platform);
		if (normalizedExpected.has(normalized)) return false;
		return normalized.includes("/.nvm/") || normalized.includes("/.fnm/") || normalized.includes("/.volta/") || normalized.includes("/.asdf/") || normalized.includes("/.n/") || normalized.includes("/.nodenv/") || normalized.includes("/.nodebrew/") || normalized.includes("/nvs/") || normalized.includes("/.local/share/pnpm/") || normalized.includes("/pnpm/") || normalized.endsWith("/pnpm");
	});
	if (nonMinimal.length > 0) issues.push({
		code: SERVICE_AUDIT_CODES.gatewayPathNonMinimal,
		message: "Gateway service PATH includes version managers or package managers; recommend a minimal PATH.",
		detail: nonMinimal.join(", "),
		level: "recommended"
	});
}
async function auditGatewayRuntime(env, command, issues, platform) {
	const execPath = command?.programArguments?.[0];
	if (!execPath) return;
	if (isBunRuntime(execPath)) {
		issues.push({
			code: SERVICE_AUDIT_CODES.gatewayRuntimeBun,
			message: "Gateway service uses Bun; Bun is incompatible with WhatsApp + Telegram channels.",
			detail: execPath,
			level: "recommended"
		});
		return;
	}
	if (!isNodeRuntime(execPath)) return;
	if (isVersionManagedNodePath(execPath, platform)) {
		issues.push({
			code: SERVICE_AUDIT_CODES.gatewayRuntimeNodeVersionManager,
			message: "Gateway service uses Node from a version manager; it can break after upgrades.",
			detail: execPath,
			level: "recommended"
		});
		if (!isSystemNodePath(execPath, env, platform)) {
			if (!await resolveSystemNodePath(env, platform)) issues.push({
				code: SERVICE_AUDIT_CODES.gatewayRuntimeNodeSystemMissing,
				message: "System Node 22+ not found; install it before migrating away from version managers.",
				level: "recommended"
			});
		}
	}
}
function hasNonEmptyString(value) {
	return typeof value === "string" && value.trim().length > 0;
}
function resolveEffectiveServiceEnv(params) {
	return {
		HOME: params.command?.environment?.HOME ?? params.env.HOME,
		EDWINPAI_STATE_DIR: params.command?.environment?.EDWINPAI_STATE_DIR ?? params.env.EDWINPAI_STATE_DIR,
		EDWINPAI_CONFIG_PATH: params.command?.environment?.EDWINPAI_CONFIG_PATH ?? params.env.EDWINPAI_CONFIG_PATH,
		PATH: params.command?.environment?.PATH ?? params.env.PATH,
		...params.command?.environment ?? void 0
	};
}
function hasConfigManagedEmbeddingKey(cfg) {
	return Boolean(hasNonEmptyString(cfg?.memory?.qmd?.embeddingApiKey) || hasNonEmptyString(cfg?.env?.OPENAI_API_KEY) || hasNonEmptyString(cfg?.env?.vars?.OPENAI_API_KEY));
}
async function hasSharedEnvEmbeddingKey(env) {
	const envPath = path.join(resolveConfigDir(env), ".env");
	try {
		const raw = await fs.readFile(envPath, "utf8");
		return {
			present: hasNonEmptyString(dotenv.parse(raw).OPENAI_API_KEY),
			envPath
		};
	} catch {
		return {
			present: false,
			envPath
		};
	}
}
async function auditGatewayEmbeddingServiceEnv(params) {
	if (!params.env.OPENAI_API_KEY?.trim()) return;
	if ((params.config?.memory?.backend ?? "qmd") !== "qmd") return;
	const serviceEnv = resolveEffectiveServiceEnv(params);
	if (hasNonEmptyString(serviceEnv.OPENAI_API_KEY)) return;
	if (hasConfigManagedEmbeddingKey(params.config)) return;
	const sharedEnv = await hasSharedEnvEmbeddingKey(serviceEnv);
	if (sharedEnv.present) return;
	params.issues.push({
		code: SERVICE_AUDIT_CODES.gatewayEmbeddingServiceSafeMissing,
		message: "OPENAI_API_KEY is only visible in the current shell; qmd embeddings may fail under the gateway service.",
		detail: `Save OPENAI_API_KEY to ${sharedEnv.envPath} or set memory.qmd.embeddingApiKey / env.vars.OPENAI_API_KEY in config.`,
		level: "recommended"
	});
}
async function auditGatewayServiceConfig(params) {
	const issues = [];
	const platform = params.platform ?? process.platform;
	await auditGatewayCommand(params.command?.programArguments, issues);
	auditGatewayServicePath(params.command, issues, params.env, platform);
	await auditGatewayRuntime(params.env, params.command, issues, platform);
	await auditGatewayEmbeddingServiceEnv({
		env: params.env,
		command: params.command,
		config: params.config,
		issues
	});
	if (platform === "linux") await auditSystemdUnit(params.env, issues);
	else if (platform === "darwin") await auditLaunchdPlist(params.env, issues);
	return {
		ok: issues.length === 0,
		issues
	};
}

//#endregion
export { renderGatewayServiceCleanupHints as a, findExtraGatewayServices as i, auditGatewayServiceConfig as n, readLastGatewayErrorLine as o, needsNodeRuntimeMigration as r, SERVICE_AUDIT_CODES as t };