import { o as normalizeOptionalLowercaseString, s as normalizeOptionalString } from "./string-coerce-CIT1-v_a.js";
import { t as createSubsystemLogger } from "./subsystem-qDUB67UD.js";
import { S as parseAgentSessionKey, c as normalizeAgentId, p as scopedHeartbeatWakeOptions, x as isSubagentSessionKey } from "./session-key-NGoeHrFI.js";
import { i as resolveAgentConfig, m as resolveDefaultAgentId } from "./agent-scope-BA9YeMqS.js";
import { r as logVerbose } from "./globals-D7B2Z7Cq.js";
import { a as loadConfig } from "./io-B_JNqEVl.js";
import { i as normalizeChannelId, t as getChannelPlugin } from "./registry-DAH3dnPp.js";
import { t as parseDurationMs } from "./parse-duration-D2MTLQU7.js";
import "./config-BR67jvKt.js";
import "./plugins-g2B6w-qz.js";
import "./store-EVWz5v6d.js";
import { i as resolveSessionFilePathOptions, l as resolveStorePath, r as resolveSessionFilePath } from "./paths-DRORteAV.js";
import { a as normalizeDeliveryContext, r as formatConversationTarget, s as resolveConversationDeliveryTarget, t as deliveryContextFromSession } from "./delivery-context-lh9J1L55.js";
import { t as loadSessionStore } from "./store-load-Cz0tEUuX.js";
import { r as resolveSessionTranscriptFile } from "./transcript-CSZqp3ZV.js";
import "./heartbeat-FoYjVBnE.js";
import { n as resolveSandboxRuntimeStatus } from "./runtime-status-FA2zZ8KQ.js";
import { n as requestHeartbeatNow, t as areHeartbeatsEnabled } from "./heartbeat-wake-CwF_Pnqd.js";
import { i as enqueueSystemEvent } from "./system-events-CAYHEKX1.js";
import { r as callGateway } from "./call-BbnBxegf.js";
import { b as resolveMainSessionAlias, y as resolveInternalSessionKey } from "./subagents-format-BUdGMHht.js";
import { l as onAgentEvent } from "./agent-events-Ca1q9xo_.js";
import { i as createRunningTaskRun, s as recordTaskRunProgressByRunId } from "./task-executor-C2fDldyX.js";
import { i as isSessionBindingError, r as getSessionBindingService } from "./session-binding-service-BO-m5w-7.js";
import { t as resolveConversationIdFromTargets } from "./conversation-id-C3ACT8t1.js";
import { n as getAcpSessionManager } from "./manager-C2wmIxL8.js";
import { n as readAcpSessionEntry } from "./session-meta-BtGvKgGv.js";
import { n as resolveAcpAgentPolicyError, t as isAcpEnabledByPolicy } from "./policy-B38iTarv.js";
import { r as resolveAcpThreadSessionDetailLines, t as resolveAcpSessionCwd } from "./session-identifiers-B_HXvAbr.js";
import { i as resolveThreadBindingThreadName, r as resolveThreadBindingIntroText } from "./thread-bindings-messages-DPC8Pb7u.js";
import { d as resolveThreadBindingSpawnPolicy, l as resolveThreadBindingMaxAgeMsForChannel, n as formatThreadBindingSpawnDisabledError, o as resolveThreadBindingIdleTimeoutMsForChannel, t as formatThreadBindingDisabledError } from "./thread-bindings-policy-qnDmsQ2M.js";
import { i as resolveSpawnedWorkspaceInheritance } from "./spawned-context-CQRBxY8z.js";
import path from "node:path";
import fs, { appendFile, mkdir } from "node:fs/promises";
import crypto from "node:crypto";
//#region src/acp/control-plane/spawn.ts
async function cleanupFailedAcpSpawn(params) {
	if (params.runtimeCloseHandle) await params.runtimeCloseHandle.runtime.close({
		handle: params.runtimeCloseHandle.handle,
		reason: "spawn-failed"
	}).catch((err) => {
		logVerbose(`acp-spawn: runtime cleanup close failed for ${params.sessionKey}: ${String(err)}`);
	});
	await getAcpSessionManager().closeSession({
		cfg: params.cfg,
		sessionKey: params.sessionKey,
		reason: "spawn-failed",
		allowBackendUnavailable: true,
		requireAcpSession: false
	}).catch((err) => {
		logVerbose(`acp-spawn: manager cleanup close failed for ${params.sessionKey}: ${String(err)}`);
	});
	await getSessionBindingService().unbind({
		targetSessionKey: params.sessionKey,
		reason: "spawn-failed"
	}).catch((err) => {
		logVerbose(`acp-spawn: binding cleanup unbind failed for ${params.sessionKey}: ${String(err)}`);
	});
	if (!params.shouldDeleteSession) return;
	await callGateway({
		method: "sessions.delete",
		params: {
			key: params.sessionKey,
			deleteTranscript: params.deleteTranscript,
			emitLifecycleHooks: false
		},
		timeoutMs: 1e4
	}).catch(() => {});
}
//#endregion
//#region src/agents/acp-spawn-parent-stream.ts
const DEFAULT_STREAM_FLUSH_MS = 2500;
const DEFAULT_NO_OUTPUT_NOTICE_MS = 6e4;
const DEFAULT_NO_OUTPUT_POLL_MS = 15e3;
const DEFAULT_MAX_RELAY_LIFETIME_MS = 360 * 60 * 1e3;
const STREAM_BUFFER_MAX_CHARS = 4e3;
const STREAM_SNIPPET_MAX_CHARS = 220;
function compactWhitespace(value) {
	return value.replace(/\s+/g, " ").trim();
}
function truncate(value, maxChars) {
	if (value.length <= maxChars) return value;
	if (maxChars <= 1) return value.slice(0, maxChars);
	return `${value.slice(0, maxChars - 1)}…`;
}
function toFiniteNumber(value) {
	return typeof value === "number" && Number.isFinite(value) ? value : void 0;
}
function resolveAcpStreamLogPathFromSessionFile(sessionFile, sessionId) {
	const baseDir = path.dirname(path.resolve(sessionFile));
	return path.join(baseDir, `${sessionId}.acp-stream.jsonl`);
}
function resolveAcpSpawnStreamLogPath(params) {
	const childSessionKey = normalizeOptionalString(params.childSessionKey);
	if (!childSessionKey) return;
	const storeEntry = readAcpSessionEntry({ sessionKey: childSessionKey });
	const sessionId = normalizeOptionalString(storeEntry?.entry?.sessionId);
	if (!storeEntry || !sessionId) return;
	try {
		return resolveAcpStreamLogPathFromSessionFile(resolveSessionFilePath(sessionId, storeEntry.entry, resolveSessionFilePathOptions({ storePath: storeEntry.storePath })), sessionId);
	} catch {
		return;
	}
}
function startAcpSpawnParentStreamRelay(params) {
	const runId = normalizeOptionalString(params.runId) ?? "";
	const parentSessionKey = normalizeOptionalString(params.parentSessionKey) ?? "";
	if (!runId || !parentSessionKey) return {
		dispose: () => {},
		notifyStarted: () => {}
	};
	const streamFlushMs = typeof params.streamFlushMs === "number" && Number.isFinite(params.streamFlushMs) ? Math.max(0, Math.floor(params.streamFlushMs)) : DEFAULT_STREAM_FLUSH_MS;
	const noOutputNoticeMs = typeof params.noOutputNoticeMs === "number" && Number.isFinite(params.noOutputNoticeMs) ? Math.max(0, Math.floor(params.noOutputNoticeMs)) : DEFAULT_NO_OUTPUT_NOTICE_MS;
	const noOutputPollMs = typeof params.noOutputPollMs === "number" && Number.isFinite(params.noOutputPollMs) ? Math.max(250, Math.floor(params.noOutputPollMs)) : DEFAULT_NO_OUTPUT_POLL_MS;
	const maxRelayLifetimeMs = typeof params.maxRelayLifetimeMs === "number" && Number.isFinite(params.maxRelayLifetimeMs) ? Math.max(1e3, Math.floor(params.maxRelayLifetimeMs)) : DEFAULT_MAX_RELAY_LIFETIME_MS;
	const relayLabel = truncate(compactWhitespace(params.agentId), 40) || "ACP child";
	const contextPrefix = `acp-spawn:${runId}`;
	const logPath = normalizeOptionalString(params.logPath);
	let logDirReady = false;
	let pendingLogLines = "";
	let logFlushScheduled = false;
	let logWriteChain = Promise.resolve();
	const flushLogBuffer = () => {
		if (!logPath || !pendingLogLines) return;
		const chunk = pendingLogLines;
		pendingLogLines = "";
		logWriteChain = logWriteChain.then(async () => {
			if (!logDirReady) {
				await mkdir(path.dirname(logPath), { recursive: true });
				logDirReady = true;
			}
			await appendFile(logPath, chunk, {
				encoding: "utf-8",
				mode: 384
			});
		}).catch(() => {});
	};
	const scheduleLogFlush = () => {
		if (!logPath || logFlushScheduled) return;
		logFlushScheduled = true;
		queueMicrotask(() => {
			logFlushScheduled = false;
			flushLogBuffer();
		});
	};
	const writeLogLine = (entry) => {
		if (!logPath) return;
		try {
			pendingLogLines += `${JSON.stringify(entry)}\n`;
			if (pendingLogLines.length >= 16384) {
				flushLogBuffer();
				return;
			}
			scheduleLogFlush();
		} catch {}
	};
	const logEvent = (kind, fields) => {
		writeLogLine({
			ts: (/* @__PURE__ */ new Date()).toISOString(),
			epochMs: Date.now(),
			runId,
			parentSessionKey,
			childSessionKey: params.childSessionKey,
			agentId: params.agentId,
			kind,
			...fields
		});
	};
	const shouldSurfaceUpdates = params.surfaceUpdates !== false;
	const wake = () => {
		if (!shouldSurfaceUpdates) return;
		requestHeartbeatNow(scopedHeartbeatWakeOptions(parentSessionKey, { reason: "acp:spawn:stream" }));
	};
	const emit = (text, contextKey) => {
		const cleaned = text.trim();
		if (!cleaned) return;
		logEvent("system_event", {
			contextKey,
			text: cleaned
		});
		if (!shouldSurfaceUpdates) return;
		enqueueSystemEvent(cleaned, {
			sessionKey: parentSessionKey,
			contextKey,
			trusted: false
		});
		wake();
	};
	const emitStartNotice = () => {
		recordTaskRunProgressByRunId({
			runId,
			runtime: "acp",
			sessionKey: params.childSessionKey,
			lastEventAt: Date.now(),
			eventSummary: "Started."
		});
		emit(`Started ${relayLabel} session ${params.childSessionKey}. Streaming progress updates to parent session.`, `${contextPrefix}:start`);
	};
	let disposed = false;
	let pendingText = "";
	let lastProgressAt = Date.now();
	let stallNotified = false;
	let flushTimer;
	let relayLifetimeTimer;
	const clearFlushTimer = () => {
		if (!flushTimer) return;
		clearTimeout(flushTimer);
		flushTimer = void 0;
	};
	const clearRelayLifetimeTimer = () => {
		if (!relayLifetimeTimer) return;
		clearTimeout(relayLifetimeTimer);
		relayLifetimeTimer = void 0;
	};
	const flushPending = () => {
		clearFlushTimer();
		if (!pendingText) return;
		const snippet = truncate(compactWhitespace(pendingText), STREAM_SNIPPET_MAX_CHARS);
		pendingText = "";
		if (!snippet) return;
		emit(`${relayLabel}: ${snippet}`, `${contextPrefix}:progress`);
	};
	const scheduleFlush = () => {
		if (disposed || flushTimer || streamFlushMs <= 0) return;
		flushTimer = setTimeout(() => {
			flushPending();
		}, streamFlushMs);
		flushTimer.unref?.();
	};
	const noOutputWatcherTimer = setInterval(() => {
		if (disposed || noOutputNoticeMs <= 0) return;
		if (stallNotified) return;
		if (Date.now() - lastProgressAt < noOutputNoticeMs) return;
		stallNotified = true;
		recordTaskRunProgressByRunId({
			runId,
			runtime: "acp",
			sessionKey: params.childSessionKey,
			lastEventAt: Date.now(),
			eventSummary: `No output for ${Math.round(noOutputNoticeMs / 1e3)}s. It may be waiting for input.`
		});
		emit(`${relayLabel} has produced no output for ${Math.round(noOutputNoticeMs / 1e3)}s. It may be waiting for interactive input.`, `${contextPrefix}:stall`);
	}, noOutputPollMs);
	noOutputWatcherTimer.unref?.();
	relayLifetimeTimer = setTimeout(() => {
		if (disposed) return;
		emit(`${relayLabel} stream relay timed out after ${Math.max(1, Math.round(maxRelayLifetimeMs / 1e3))}s without completion.`, `${contextPrefix}:timeout`);
		dispose();
	}, maxRelayLifetimeMs);
	relayLifetimeTimer.unref?.();
	if (params.emitStartNotice !== false) emitStartNotice();
	const unsubscribe = onAgentEvent((event) => {
		if (disposed || event.runId !== runId) return;
		if (event.stream === "assistant") {
			const data = event.data;
			const deltaCandidate = data?.delta ?? data?.text;
			const delta = typeof deltaCandidate === "string" ? deltaCandidate : void 0;
			if (!delta || !delta.trim()) return;
			logEvent("assistant_delta", { delta });
			if (stallNotified) {
				stallNotified = false;
				recordTaskRunProgressByRunId({
					runId,
					runtime: "acp",
					sessionKey: params.childSessionKey,
					lastEventAt: Date.now(),
					eventSummary: "Resumed output."
				});
				emit(`${relayLabel} resumed output.`, `${contextPrefix}:resumed`);
			}
			lastProgressAt = Date.now();
			pendingText += delta;
			if (pendingText.length > STREAM_BUFFER_MAX_CHARS) pendingText = pendingText.slice(-STREAM_BUFFER_MAX_CHARS);
			if (pendingText.length >= STREAM_SNIPPET_MAX_CHARS || delta.includes("\n\n")) {
				flushPending();
				return;
			}
			scheduleFlush();
			return;
		}
		if (event.stream !== "lifecycle") return;
		const phase = normalizeOptionalString(event.data?.phase);
		logEvent("lifecycle", {
			phase: phase ?? "unknown",
			data: event.data
		});
		if (phase === "end") {
			flushPending();
			const startedAt = toFiniteNumber(event.data?.startedAt);
			const endedAt = toFiniteNumber(event.data?.endedAt);
			const durationMs = startedAt != null && endedAt != null && endedAt >= startedAt ? endedAt - startedAt : void 0;
			if (durationMs != null) emit(`${relayLabel} run completed in ${Math.max(1, Math.round(durationMs / 1e3))}s.`, `${contextPrefix}:done`);
			else emit(`${relayLabel} run completed.`, `${contextPrefix}:done`);
			dispose();
			return;
		}
		if (phase === "error") {
			flushPending();
			const errorText = normalizeOptionalString(event.data?.error);
			if (errorText) emit(`${relayLabel} run failed: ${errorText}`, `${contextPrefix}:error`);
			else emit(`${relayLabel} run failed.`, `${contextPrefix}:error`);
			dispose();
		}
	});
	const dispose = () => {
		if (disposed) return;
		disposed = true;
		clearFlushTimer();
		clearRelayLifetimeTimer();
		flushLogBuffer();
		clearInterval(noOutputWatcherTimer);
		unsubscribe();
	};
	return {
		dispose,
		notifyStarted: emitStartNotice
	};
}
//#endregion
//#region src/agents/acp-spawn.ts
const log = createSubsystemLogger("agents/acp-spawn");
function isSpawnAcpAcceptedResult(result) {
	return result.status === "accepted";
}
const ACP_SPAWN_ACCEPTED_NOTE = "initial ACP task queued in isolated session; follow-ups continue in the bound thread.";
const ACP_SPAWN_SESSION_ACCEPTED_NOTE = "thread-bound ACP session stays active after this task; continue in-thread for follow-ups.";
function resolveAcpSpawnRuntimePolicyError(params) {
	const sandboxMode = params.sandbox === "require" ? "require" : "inherit";
	const requesterRuntime = resolveSandboxRuntimeStatus({
		cfg: params.cfg,
		sessionKey: params.requesterSessionKey
	});
	if (params.requesterSandboxed === true || requesterRuntime.sandboxed) return "Sandboxed sessions cannot spawn ACP sessions because runtime=\"acp\" runs on the host. Use runtime=\"subagent\" from sandboxed sessions.";
	if (sandboxMode === "require") return "sessions_spawn sandbox=\"require\" is unsupported for runtime=\"acp\" because ACP sessions run outside the sandbox. Use runtime=\"subagent\" or sandbox=\"inherit\".";
}
function resolvePlacementWithoutChannelPlugin(params) {
	switch (params.channel) {
		case "discord":
		case "matrix": return params.capabilities.placements.includes("child") ? "child" : "current";
		case "line":
		case "telegram": return "current";
	}
	return params.capabilities.placements.includes("child") ? "child" : "current";
}
function normalizeLineConversationIdFallback(value) {
	const trimmed = normalizeOptionalString(value) ?? "";
	if (!trimmed) return;
	const normalized = trimmed.match(/^line:(?:(?:user|group|room):)?(.+)$/i)?.[1]?.trim() ?? trimmed;
	return normalized ? normalized : void 0;
}
function normalizeTelegramConversationIdFallback(params) {
	const explicitGroupId = normalizeOptionalString(params.groupId);
	const explicitThreadId = params.threadId != null ? normalizeOptionalString(String(params.threadId)) : void 0;
	if (explicitGroupId && explicitThreadId && /^-?\d+$/.test(explicitGroupId) && /^\d+$/.test(explicitThreadId)) return `${explicitGroupId}:topic:${explicitThreadId}`;
	const trimmed = normalizeOptionalString(params.to) ?? "";
	if (!trimmed) return;
	const normalized = trimmed.replace(/^telegram:(?:group:|channel:|direct:)?/i, "");
	const topicMatch = /^(-?\d+):topic:(\d+)$/i.exec(normalized);
	if (topicMatch?.[1] && topicMatch[2]) return `${topicMatch[1]}:topic:${topicMatch[2]}`;
	return /^-?\d+$/.test(normalized) ? normalized : void 0;
}
function resolveSpawnMode(params) {
	if (params.requestedMode === "run" || params.requestedMode === "session") return params.requestedMode;
	return params.threadRequested ? "session" : "run";
}
function resolveAcpSessionMode(mode) {
	return mode === "session" ? "persistent" : "oneshot";
}
function isHeartbeatEnabledForSessionAgent(params) {
	if (!areHeartbeatsEnabled()) return false;
	const requesterAgentId = parseAgentSessionKey(params.sessionKey)?.agentId;
	if (!requesterAgentId) return true;
	const agentEntries = params.cfg.agents?.list ?? [];
	if (!(agentEntries.some((entry) => Boolean(entry?.heartbeat)) ? agentEntries.some((entry) => Boolean(entry?.heartbeat) && normalizeAgentId(entry?.id) === requesterAgentId) : requesterAgentId === resolveDefaultAgentId(params.cfg))) return false;
	const trimmedEvery = normalizeOptionalString(resolveAgentConfig(params.cfg, requesterAgentId)?.heartbeat?.every ?? params.cfg.agents?.defaults?.heartbeat?.every ?? "30m") ?? "";
	if (!trimmedEvery) return false;
	try {
		return parseDurationMs(trimmedEvery, { defaultUnit: "m" }) > 0;
	} catch {
		return false;
	}
}
function resolveHeartbeatConfigForAgent(params) {
	const defaults = params.cfg.agents?.defaults?.heartbeat;
	const overrides = resolveAgentConfig(params.cfg, params.agentId)?.heartbeat;
	if (!defaults && !overrides) return;
	return {
		...defaults,
		...overrides
	};
}
function hasSessionLocalHeartbeatRelayRoute(params) {
	if ((params.cfg.session?.scope ?? "per-sender") === "global") return false;
	const heartbeat = resolveHeartbeatConfigForAgent({
		cfg: params.cfg,
		agentId: params.requesterAgentId
	});
	if ((heartbeat?.target ?? "none") !== "last") return false;
	if (normalizeOptionalString(heartbeat?.to)) return false;
	if (normalizeOptionalString(heartbeat?.accountId)) return false;
	const parentEntry = loadSessionStore(resolveStorePath(params.cfg.session?.store, { agentId: params.requesterAgentId }))[params.parentSessionKey];
	const parentDeliveryContext = deliveryContextFromSession(parentEntry);
	return Boolean(parentDeliveryContext?.channel && parentDeliveryContext.to);
}
function resolveTargetAcpAgentId(params) {
	const requested = normalizeOptionalAgentId(params.requestedAgentId);
	if (requested) return {
		ok: true,
		agentId: requested
	};
	const configuredDefault = normalizeOptionalAgentId(params.cfg.acp?.defaultAgent);
	if (configuredDefault) return {
		ok: true,
		agentId: configuredDefault
	};
	return {
		ok: false,
		error: "ACP target agent is not configured. Pass `agentId` in `sessions_spawn` or set `acp.defaultAgent` in config."
	};
}
function normalizeOptionalAgentId(value) {
	const trimmed = normalizeOptionalString(value) ?? "";
	if (!trimmed) return;
	return normalizeAgentId(trimmed);
}
function summarizeError(err) {
	if (err instanceof Error) return err.message;
	if (typeof err === "string") return err;
	return "error";
}
function createAcpSpawnFailure(params) {
	return {
		status: params.status,
		errorCode: params.errorCode,
		error: params.error,
		...params.childSessionKey ? { childSessionKey: params.childSessionKey } : {}
	};
}
function isMissingPathError(error) {
	const code = error instanceof Error ? error.code : void 0;
	return code === "ENOENT" || code === "ENOTDIR";
}
async function resolveRuntimeCwdForAcpSpawn(params) {
	if (!params.resolvedCwd) return;
	if (normalizeOptionalString(params.explicitCwd)) return params.resolvedCwd;
	try {
		await fs.access(params.resolvedCwd);
		return params.resolvedCwd;
	} catch (error) {
		if (isMissingPathError(error)) return;
		throw error;
	}
}
function resolveRequesterInternalSessionKey(params) {
	const { mainKey, alias } = resolveMainSessionAlias(params.cfg);
	const requesterSessionKey = normalizeOptionalString(params.requesterSessionKey);
	return requesterSessionKey ? resolveInternalSessionKey({
		key: requesterSessionKey,
		alias,
		mainKey
	}) : alias;
}
async function persistAcpSpawnSessionFileBestEffort(params) {
	try {
		return (await resolveSessionTranscriptFile({
			sessionId: params.sessionId,
			sessionKey: params.sessionKey,
			sessionEntry: params.sessionEntry,
			sessionStore: params.sessionStore,
			storePath: params.storePath,
			agentId: params.agentId,
			threadId: params.threadId
		})).sessionEntry;
	} catch (error) {
		log.warn(`ACP session-file persistence failed during ${params.stage} for ${params.sessionKey}: ${summarizeError(error)}`);
		return params.sessionEntry;
	}
}
function resolveConversationIdForThreadBinding(params) {
	const channel = normalizeOptionalLowercaseString(params.channel);
	const normalizedChannelId = channel ? normalizeChannelId(channel) : null;
	const channelKey = normalizedChannelId ?? channel ?? null;
	const pluginResolvedConversationId = normalizedChannelId ? getChannelPlugin(normalizedChannelId)?.messaging?.resolveInboundConversation?.({
		to: params.groupId ?? params.to,
		conversationId: params.groupId ?? params.to,
		threadId: params.threadId,
		isGroup: true
	})?.conversationId : null;
	if (normalizeOptionalString(pluginResolvedConversationId)) return normalizeOptionalString(pluginResolvedConversationId);
	if (channelKey === "line") {
		const lineConversationId = normalizeLineConversationIdFallback(params.groupId ?? params.to);
		if (lineConversationId) return lineConversationId;
	}
	if (channelKey === "telegram") {
		const telegramConversationId = normalizeTelegramConversationIdFallback(params);
		if (telegramConversationId) return telegramConversationId;
	}
	const genericConversationId = resolveConversationIdFromTargets({
		threadId: params.threadId,
		targets: [params.to]
	});
	if (genericConversationId) return genericConversationId;
}
function resolveAcpSpawnChannelAccountId(params) {
	const channel = normalizeOptionalLowercaseString(params.channel);
	const explicitAccountId = normalizeOptionalString(params.accountId);
	if (explicitAccountId) return explicitAccountId;
	if (!channel) return;
	const configuredDefaultAccountId = params.cfg.channels?.[channel]?.defaultAccount;
	return normalizeOptionalString(configuredDefaultAccountId) ?? "default";
}
function prepareAcpThreadBinding(params) {
	const channel = normalizeOptionalLowercaseString(params.channel);
	if (!channel) return {
		ok: false,
		error: "thread=true for ACP sessions requires a channel context."
	};
	const accountId = resolveAcpSpawnChannelAccountId({
		cfg: params.cfg,
		channel,
		accountId: params.accountId
	});
	const policy = resolveThreadBindingSpawnPolicy({
		cfg: params.cfg,
		channel,
		accountId,
		kind: "acp"
	});
	if (!policy.enabled) return {
		ok: false,
		error: formatThreadBindingDisabledError({
			channel: policy.channel,
			accountId: policy.accountId,
			kind: "acp"
		})
	};
	if (!policy.spawnEnabled) return {
		ok: false,
		error: formatThreadBindingSpawnDisabledError({
			channel: policy.channel,
			accountId: policy.accountId,
			kind: "acp"
		})
	};
	const capabilities = getSessionBindingService().getCapabilities({
		channel: policy.channel,
		accountId: policy.accountId
	});
	if (!capabilities.adapterAvailable) return {
		ok: false,
		error: `Thread bindings are unavailable for ${policy.channel}.`
	};
	const placementToUse = getChannelPlugin(policy.channel)?.conversationBindings?.defaultTopLevelPlacement ?? resolvePlacementWithoutChannelPlugin({
		channel: policy.channel,
		capabilities
	});
	if (!capabilities.bindSupported || !capabilities.placements.includes(placementToUse)) return {
		ok: false,
		error: `Thread bindings do not support ${placementToUse} placement for ${policy.channel}.`
	};
	const conversationIdRaw = resolveConversationIdForThreadBinding({
		channel: policy.channel,
		to: params.to,
		threadId: params.threadId,
		groupId: params.groupId
	});
	if (!conversationIdRaw) return {
		ok: false,
		error: `Could not resolve a ${policy.channel} conversation for ACP thread spawn.`
	};
	return {
		ok: true,
		binding: {
			channel: policy.channel,
			accountId: policy.accountId,
			placement: placementToUse,
			conversationId: conversationIdRaw
		}
	};
}
function resolveAcpSpawnRequesterState(params) {
	const bindingService = getSessionBindingService();
	const requesterParsedSession = parseAgentSessionKey(params.parentSessionKey);
	const isSubagentSession = Boolean(requesterParsedSession) && isSubagentSessionKey(params.parentSessionKey);
	const hasActiveSubagentBinding = isSubagentSession && params.parentSessionKey ? bindingService.listBySession(params.parentSessionKey).some((record) => record.targetKind === "subagent" && record.status !== "ended") : false;
	const hasThreadContext = typeof params.ctx.agentThreadId === "string" ? Boolean(normalizeOptionalString(params.ctx.agentThreadId)) : params.ctx.agentThreadId != null;
	const requesterAgentId = requesterParsedSession?.agentId;
	return {
		parentSessionKey: params.parentSessionKey,
		isSubagentSession,
		hasActiveSubagentBinding,
		hasThreadContext,
		heartbeatEnabled: isHeartbeatEnabledForSessionAgent({
			cfg: params.cfg,
			sessionKey: params.parentSessionKey
		}),
		heartbeatRelayRouteUsable: params.parentSessionKey && requesterAgentId ? hasSessionLocalHeartbeatRelayRoute({
			cfg: params.cfg,
			parentSessionKey: params.parentSessionKey,
			requesterAgentId
		}) : false,
		origin: normalizeDeliveryContext({
			channel: params.ctx.agentChannel,
			accountId: params.ctx.agentAccountId,
			to: params.ctx.agentTo,
			threadId: params.ctx.agentThreadId
		})
	};
}
function resolveAcpSpawnStreamPlan(params) {
	const implicitStreamToParent = !params.streamToParentRequested && params.spawnMode === "run" && !params.requestThreadBinding && params.requester.isSubagentSession && !params.requester.hasActiveSubagentBinding && !params.requester.hasThreadContext && params.requester.heartbeatEnabled && params.requester.heartbeatRelayRouteUsable;
	return {
		implicitStreamToParent,
		effectiveStreamToParent: params.streamToParentRequested || implicitStreamToParent
	};
}
async function initializeAcpSpawnRuntime(params) {
	const storePath = resolveStorePath(params.cfg.session?.store, { agentId: params.targetAgentId });
	const sessionStore = loadSessionStore(storePath);
	let sessionEntry = sessionStore[params.sessionKey];
	const sessionId = sessionEntry?.sessionId;
	if (sessionId) sessionEntry = await persistAcpSpawnSessionFileBestEffort({
		sessionId,
		sessionKey: params.sessionKey,
		sessionStore,
		storePath,
		sessionEntry,
		agentId: params.targetAgentId,
		stage: "spawn"
	});
	const initialized = await getAcpSessionManager().initializeSession({
		cfg: params.cfg,
		sessionKey: params.sessionKey,
		agent: params.targetAgentId,
		mode: params.runtimeMode,
		resumeSessionId: params.resumeSessionId,
		cwd: params.cwd,
		backendId: params.cfg.acp?.backend
	});
	return {
		initialized,
		runtimeCloseHandle: {
			runtime: initialized.runtime,
			handle: initialized.handle
		},
		sessionId,
		sessionEntry,
		sessionStore,
		storePath
	};
}
async function bindPreparedAcpThread(params) {
	const binding = await getSessionBindingService().bind({
		targetSessionKey: params.sessionKey,
		targetKind: "session",
		conversation: {
			channel: params.preparedBinding.channel,
			accountId: params.preparedBinding.accountId,
			conversationId: params.preparedBinding.conversationId
		},
		placement: params.preparedBinding.placement,
		metadata: {
			threadName: resolveThreadBindingThreadName({
				agentId: params.targetAgentId,
				label: params.label || params.targetAgentId
			}),
			agentId: params.targetAgentId,
			label: params.label || void 0,
			boundBy: "system",
			introText: resolveThreadBindingIntroText({
				agentId: params.targetAgentId,
				label: params.label || void 0,
				idleTimeoutMs: resolveThreadBindingIdleTimeoutMsForChannel({
					cfg: params.cfg,
					channel: params.preparedBinding.channel,
					accountId: params.preparedBinding.accountId
				}),
				maxAgeMs: resolveThreadBindingMaxAgeMsForChannel({
					cfg: params.cfg,
					channel: params.preparedBinding.channel,
					accountId: params.preparedBinding.accountId
				}),
				sessionCwd: resolveAcpSessionCwd(params.initializedRuntime.initialized.meta),
				sessionDetails: resolveAcpThreadSessionDetailLines({
					sessionKey: params.sessionKey,
					meta: params.initializedRuntime.initialized.meta
				})
			})
		}
	});
	if (!binding.conversation.conversationId) throw new Error(params.preparedBinding.placement === "child" ? `Failed to create and bind a ${params.preparedBinding.channel} thread for this ACP session.` : `Failed to bind the current ${params.preparedBinding.channel} conversation for this ACP session.`);
	let sessionEntry = params.initializedRuntime.sessionEntry;
	if (params.initializedRuntime.sessionId && params.preparedBinding.placement === "child") {
		const boundThreadId = normalizeOptionalString(String(binding.conversation.conversationId));
		if (boundThreadId) sessionEntry = await persistAcpSpawnSessionFileBestEffort({
			sessionId: params.initializedRuntime.sessionId,
			sessionKey: params.sessionKey,
			sessionStore: params.initializedRuntime.sessionStore,
			storePath: params.initializedRuntime.storePath,
			sessionEntry,
			agentId: params.targetAgentId,
			threadId: boundThreadId,
			stage: "thread-bind"
		});
	}
	return {
		binding,
		sessionEntry
	};
}
function resolveAcpSpawnBootstrapDeliveryPlan(params) {
	const boundThreadIdRaw = params.binding?.conversation.conversationId;
	const boundThreadId = boundThreadIdRaw ? normalizeOptionalString(String(boundThreadIdRaw)) : void 0;
	const fallbackThreadIdRaw = params.requester.origin?.threadId;
	const fallbackThreadId = fallbackThreadIdRaw != null ? normalizeOptionalString(String(fallbackThreadIdRaw)) : void 0;
	const deliveryThreadId = boundThreadId ?? fallbackThreadId;
	const requesterConversationId = resolveConversationIdForThreadBinding({
		channel: params.requester.origin?.channel,
		threadId: fallbackThreadId,
		to: params.requester.origin?.to
	});
	const requesterAccountId = resolveAcpSpawnChannelAccountId({
		cfg: params.cfg,
		channel: params.requester.origin?.channel,
		accountId: params.requester.origin?.accountId
	});
	const bindingMatchesRequesterConversation = Boolean(params.requester.origin?.channel && params.binding?.conversation.channel === params.requester.origin.channel && params.binding?.conversation.accountId === requesterAccountId && requesterConversationId && params.binding?.conversation.conversationId === requesterConversationId);
	const boundDeliveryTarget = resolveConversationDeliveryTarget({
		channel: params.requester.origin?.channel ?? params.binding?.conversation.channel,
		conversationId: params.binding?.conversation.conversationId,
		parentConversationId: params.binding?.conversation.parentConversationId
	});
	const inferredDeliveryTo = (bindingMatchesRequesterConversation ? normalizeOptionalString(params.requester.origin?.to) : void 0) ?? boundDeliveryTarget.to ?? normalizeOptionalString(params.requester.origin?.to) ?? formatConversationTarget({
		channel: params.requester.origin?.channel,
		conversationId: deliveryThreadId
	});
	const resolvedDeliveryThreadId = bindingMatchesRequesterConversation ? fallbackThreadId : boundDeliveryTarget.threadId ?? deliveryThreadId;
	const useInlineDelivery = Boolean(params.requester.origin?.channel && inferredDeliveryTo) && !params.effectiveStreamToParent && params.spawnMode === "session";
	return {
		useInlineDelivery,
		channel: useInlineDelivery ? params.requester.origin?.channel : void 0,
		accountId: useInlineDelivery ? requesterAccountId : void 0,
		to: useInlineDelivery ? inferredDeliveryTo : void 0,
		threadId: useInlineDelivery ? resolvedDeliveryThreadId : void 0
	};
}
async function spawnAcpDirect(params, ctx) {
	const cfg = loadConfig();
	const requesterInternalKey = resolveRequesterInternalSessionKey({
		cfg,
		requesterSessionKey: ctx.agentSessionKey
	});
	if (!isAcpEnabledByPolicy(cfg)) return createAcpSpawnFailure({
		status: "forbidden",
		errorCode: "acp_disabled",
		error: "ACP is disabled by policy (`acp.enabled=false`)."
	});
	const streamToParentRequested = params.streamTo === "parent";
	const parentSessionKey = normalizeOptionalString(ctx.agentSessionKey);
	if (streamToParentRequested && !parentSessionKey) return createAcpSpawnFailure({
		status: "error",
		errorCode: "requester_session_required",
		error: "sessions_spawn streamTo=\"parent\" requires an active requester session context."
	});
	let requestThreadBinding = params.thread === true;
	const runtimePolicyError = resolveAcpSpawnRuntimePolicyError({
		cfg,
		requesterSessionKey: ctx.agentSessionKey,
		requesterSandboxed: ctx.sandboxed,
		sandbox: params.sandbox
	});
	if (runtimePolicyError) return createAcpSpawnFailure({
		status: "forbidden",
		errorCode: "runtime_policy",
		error: runtimePolicyError
	});
	const spawnMode = resolveSpawnMode({
		requestedMode: params.mode,
		threadRequested: requestThreadBinding
	});
	if (spawnMode === "session" && !requestThreadBinding) return createAcpSpawnFailure({
		status: "error",
		errorCode: "thread_required",
		error: "mode=\"session\" requires thread=true so the ACP session can stay bound to a thread."
	});
	const requesterState = resolveAcpSpawnRequesterState({
		cfg,
		parentSessionKey,
		ctx
	});
	const { effectiveStreamToParent } = resolveAcpSpawnStreamPlan({
		spawnMode,
		requestThreadBinding,
		streamToParentRequested,
		requester: requesterState
	});
	const targetAgentResult = resolveTargetAcpAgentId({
		requestedAgentId: params.agentId,
		cfg
	});
	if (!targetAgentResult.ok) return createAcpSpawnFailure({
		status: "error",
		errorCode: "target_agent_required",
		error: targetAgentResult.error
	});
	const targetAgentId = targetAgentResult.agentId;
	const agentPolicyError = resolveAcpAgentPolicyError(cfg, targetAgentId);
	if (agentPolicyError) return createAcpSpawnFailure({
		status: "forbidden",
		errorCode: "agent_forbidden",
		error: agentPolicyError.message
	});
	const sessionKey = `agent:${targetAgentId}:acp:${crypto.randomUUID()}`;
	const runtimeMode = resolveAcpSessionMode(spawnMode);
	const resolvedCwd = resolveSpawnedWorkspaceInheritance({
		config: cfg,
		targetAgentId,
		requesterSessionKey: ctx.agentSessionKey,
		explicitWorkspaceDir: params.cwd
	});
	let runtimeCwd;
	try {
		runtimeCwd = await resolveRuntimeCwdForAcpSpawn({
			resolvedCwd,
			explicitCwd: params.cwd
		});
	} catch (error) {
		return createAcpSpawnFailure({
			status: "error",
			errorCode: "cwd_resolution_failed",
			error: summarizeError(error)
		});
	}
	let preparedBinding = null;
	if (requestThreadBinding) {
		const prepared = prepareAcpThreadBinding({
			cfg,
			channel: ctx.agentChannel,
			accountId: ctx.agentAccountId,
			to: ctx.agentTo,
			threadId: ctx.agentThreadId,
			groupId: ctx.agentGroupId
		});
		if (!prepared.ok) return createAcpSpawnFailure({
			status: "error",
			errorCode: "thread_binding_invalid",
			error: prepared.error
		});
		preparedBinding = prepared.binding;
	}
	let binding = null;
	let sessionCreated = false;
	let initializedRuntime;
	try {
		await callGateway({
			method: "sessions.patch",
			params: {
				key: sessionKey,
				spawnedBy: requesterInternalKey,
				...params.label ? { label: params.label } : {}
			},
			timeoutMs: 1e4
		});
		sessionCreated = true;
		const initializedSession = await initializeAcpSpawnRuntime({
			cfg,
			sessionKey,
			targetAgentId,
			runtimeMode,
			resumeSessionId: params.resumeSessionId,
			cwd: runtimeCwd
		});
		initializedRuntime = initializedSession.runtimeCloseHandle;
		if (preparedBinding) ({binding} = await bindPreparedAcpThread({
			cfg,
			sessionKey,
			targetAgentId,
			label: params.label,
			preparedBinding,
			initializedRuntime: initializedSession
		}));
	} catch (err) {
		await cleanupFailedAcpSpawn({
			cfg,
			sessionKey,
			shouldDeleteSession: sessionCreated,
			deleteTranscript: true,
			runtimeCloseHandle: initializedRuntime
		});
		return createAcpSpawnFailure({
			status: "error",
			errorCode: isSessionBindingError(err) ? "thread_binding_invalid" : "spawn_failed",
			error: isSessionBindingError(err) ? err.message : summarizeError(err)
		});
	}
	const deliveryPlan = resolveAcpSpawnBootstrapDeliveryPlan({
		cfg,
		spawnMode,
		requestThreadBinding,
		effectiveStreamToParent,
		requester: requesterState,
		binding
	});
	const childIdem = crypto.randomUUID();
	let childRunId = childIdem;
	const streamLogPath = effectiveStreamToParent && parentSessionKey ? resolveAcpSpawnStreamLogPath({ childSessionKey: sessionKey }) : void 0;
	let parentRelay;
	if (effectiveStreamToParent && parentSessionKey) parentRelay = startAcpSpawnParentStreamRelay({
		runId: childIdem,
		parentSessionKey,
		childSessionKey: sessionKey,
		agentId: targetAgentId,
		logPath: streamLogPath,
		emitStartNotice: false
	});
	try {
		const responseRunId = normalizeOptionalString((await callGateway({
			method: "agent",
			params: {
				message: params.task,
				sessionKey,
				channel: deliveryPlan.channel,
				to: deliveryPlan.to,
				accountId: deliveryPlan.accountId,
				threadId: deliveryPlan.threadId,
				idempotencyKey: childIdem,
				deliver: deliveryPlan.useInlineDelivery,
				label: params.label || void 0
			},
			timeoutMs: 1e4
		}))?.runId);
		if (responseRunId) childRunId = responseRunId;
	} catch (err) {
		parentRelay?.dispose();
		await cleanupFailedAcpSpawn({
			cfg,
			sessionKey,
			shouldDeleteSession: true,
			deleteTranscript: true
		});
		return createAcpSpawnFailure({
			status: "error",
			errorCode: "dispatch_failed",
			error: summarizeError(err),
			childSessionKey: sessionKey
		});
	}
	if (effectiveStreamToParent && parentSessionKey) {
		if (parentRelay && childRunId !== childIdem) {
			parentRelay.dispose();
			parentRelay = startAcpSpawnParentStreamRelay({
				runId: childRunId,
				parentSessionKey,
				childSessionKey: sessionKey,
				agentId: targetAgentId,
				logPath: streamLogPath,
				emitStartNotice: false
			});
		}
		parentRelay?.notifyStarted();
		try {
			createRunningTaskRun({
				runtime: "acp",
				sourceId: childRunId,
				ownerKey: requesterInternalKey,
				scopeKind: "session",
				requesterOrigin: requesterState.origin,
				childSessionKey: sessionKey,
				runId: childRunId,
				label: params.label,
				task: params.task,
				preferMetadata: true,
				deliveryStatus: requesterInternalKey ? "pending" : "parent_missing",
				startedAt: Date.now()
			});
		} catch (error) {
			log.warn("Failed to create background task for ACP spawn", {
				sessionKey,
				runId: childRunId,
				error
			});
		}
		return {
			status: "accepted",
			childSessionKey: sessionKey,
			runId: childRunId,
			mode: spawnMode,
			...streamLogPath ? { streamLogPath } : {},
			note: spawnMode === "session" ? ACP_SPAWN_SESSION_ACCEPTED_NOTE : ACP_SPAWN_ACCEPTED_NOTE
		};
	}
	try {
		createRunningTaskRun({
			runtime: "acp",
			sourceId: childRunId,
			ownerKey: requesterInternalKey,
			scopeKind: "session",
			requesterOrigin: requesterState.origin,
			childSessionKey: sessionKey,
			runId: childRunId,
			label: params.label,
			task: params.task,
			preferMetadata: true,
			deliveryStatus: requesterInternalKey ? "pending" : "parent_missing",
			startedAt: Date.now()
		});
	} catch (error) {
		log.warn("Failed to create background task for ACP spawn", {
			sessionKey,
			runId: childRunId,
			error
		});
	}
	return {
		status: "accepted",
		childSessionKey: sessionKey,
		runId: childRunId,
		mode: spawnMode,
		note: spawnMode === "session" ? ACP_SPAWN_SESSION_ACCEPTED_NOTE : ACP_SPAWN_ACCEPTED_NOTE
	};
}
//#endregion
export { cleanupFailedAcpSpawn as i, resolveAcpSpawnRuntimePolicyError as n, spawnAcpDirect as r, isSpawnAcpAcceptedResult as t };
