UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

1,350 lines • 58.4 kB
import { l as normalizeOptionalString } from "./string-coerce-1VuiLaaR.mjs";
import { s as asFiniteNumber } from "./number-coercion-CkhMiRw8.mjs";
import { t as AsyncWorkScope } from "./async-work-scope-DDiH0P7W.mjs";
import { a as asOptionalRecord } from "./record-coerce-TIsquoVR.mjs";
import { n as filterStringEntries } from "./string-normalization-D6DKCpwE.mjs";
import { n as sliceUtf16Safe } from "./utf16-slice-D_ngcYKd.mjs";
import { i as resolveRuntimeWorkerUrl } from "./runtime-worker-url-CFqA9Dvi.mjs";
import { t as formatErrorMessage } from "./errors-BvOkmQM5.mjs";
import { d as readDatabasePathIdentitySync } from "./sqlite-worker-identity-BRNRpRyc.mjs";
import { s as throwSqliteLifecycleErrors } from "./sqlite-lifecycle-errors-DuOygjcn.mjs";
import { t as createSubsystemLogger } from "./subsystem-4NzR43uz.mjs";
import { n as runtimeProcessEntrypoints } from "./runtime-process-entrypoints-De68tE_W.mjs";
import { n as hydrateOpenClawStateWorkerError, r as retainOpenClawStateWorkerErrorPayload } from "./openclaw-state-worker-error-Bi-BnNrV.mjs";
import { r as normalizeAgentIdStrict } from "./agent-id-D1-Q6vR4.mjs";
import { r as isIncognitoSessionKey } from "./session-key-CmZ4o9-l.mjs";
import { c as normalizeOptionalAgentId, l as resolveAgentIdFromSessionKey } from "./session-key-DY3MOX4N.mjs";
import { h as parseAgentSessionKey, s as isSubagentSessionKey } from "./session-key-utils-CDyBC9Gn.mjs";
import { n as normalizeAccountId } from "./account-id-ifCOO7_9.mjs";
import { i as listAgentIds, n as listAgentEntries } from "./agent-roster-DH6r_8f4.mjs";
import { c as resolveSessionStorePathCore } from "./paths-Bonddxfk.mjs";
import { o as resolveAgentEntry } from "./agent-scope-config-BEk6-ThS.mjs";
import { l as resolveOpenClawAgentSqlitePath } from "./openclaw-agent-db.paths-BbmZlEP5.mjs";
import { u as isSqliteWorkerError } from "./sqlite-worker-contract-C51cJHzg.mjs";
import { i as sessionChanges } from "./session-row-changes-Dj2yVWo-.mjs";
import { r as cloneEnvWithPlatformSemantics } from "./config-env-vars-BzUE_dOh.mjs";
import { t as executeExistingOpenClawStateRead } from "./openclaw-state-db-readonly-ywwvuxdQ.mjs";
import { n as runOpenClawAgentWriteAdmission } from "./openclaw-agent-write-admission-CXJiE5VB.mjs";
import { l as deliveryContextFromSession } from "./store-entry-ClyG-tia.mjs";
import { r as hasDeliveryTargetFields } from "./delivery-context.shared-BYdFKp9k.mjs";
import { _ as resolveSessionAgentIds, h as resolveSessionAgentId } from "./agent-scope-bJOokm5r.mjs";
import { h as onAgentEventForRun } from "./agent-events-jWdQ_ESE.mjs";
import { d as sessionEntryCommitGuardOptions } from "./session-source-authority-h1UO3u0I.mjs";
import { r as getBoundLegacyPluginSdkResourceHost } from "./legacy-sdk-resource-host-Ck09VxpO.mjs";
import { r as getRuntimeConfig } from "./io.runtime-B134MOMz.mjs";
import "./config-VnHbY_QV.mjs";
import { n as captureOpenClawAgentDatabaseExecution } from "./openclaw-agent-execution-C2B2fucX.mjs";
import { n as openOpenClawAgentSqliteWorkerStore } from "./openclaw-agent-worker-store-BE9hhRoh.mjs";
import { s as areSessionEventWakesEnabled } from "./heartbeat-wake-C26T6nJF.mjs";
import { n as getGlobalHookRunner } from "./hook-runner-global-Cn3xHNvt.mjs";
import { n as isExecutionIdentityCollectionEnabled } from "./audit-config-BXFCjLO0.mjs";
import { r as resolveChannelAccountEntry } from "./account-lookup-BAaS-Jak.mjs";
import { a as inheritSessionGitContributorProfileIds, r as buildSessionCreationStamp } from "./session-entry-provenance-C8CX2hws.mjs";
import { a as normalizeAssistantPhase } from "./chat-message-content-C-d0FKNG.mjs";
import { _ as upsertSessionEntryCore, s as loadSessionEntryReadOnly } from "./session-accessor.sqlite-entry-DLa7_9ui.mjs";
import "./session-accessor-C3ljJ974.mjs";
import { i as readSessionEntryReadOnlyInWorker, l as withSessionEntryReadOnlyInWorker } from "./session-entry-read-runtime-BdnQjoL6.mjs";
import { p as recordSubagentSpawned } from "./session-state-events-tNQ7VRZz.mjs";
import { r as captureSessionEventTargetForHost, t as enqueueSessionEventForHost } from "./session-event-handoff-BkyE9Uzx.mjs";
import { t as acpSessionRowMatchesEntry } from "./session-meta-keys-C29RYca5.mjs";
import { n as listActiveAcpSessionsForOwner } from "./active-turns-Cmaiqrih.mjs";
import { t as getAcpRuntimeBackend } from "./registry-CwtcY73s.mjs";
import { d as captureAcpSessionReadContext } from "./session-meta-CSN8P5u2.mjs";
import { d as formatAcpInheritedToolAllowError, f as formatAcpInheritedToolDenyError, l as findAcpUnsupportedInheritedToolAllow, m as inheritedToolDenyPatch, p as inheritedToolAllowPatch, s as resolveSubagentCapabilityStore, t as isSubagentEnvelopeSession, u as findAcpUnsupportedInheritedToolDeny } from "./subagent-capabilities-DBYrBW0F.mjs";
import { p as isSessionBindingError } from "./current-conversation-bindings.kernel-DWqaaBL6.mjs";
import { a as listSessionBindingsBySessionAsync, k as resolveInboundConversationResolution, t as getSessionBindingService } from "./session-binding-service-wvWqRl2A.mjs";
import { D as resolveChannelStreamingProgressCommentary } from "./streaming-DF0Ecn4m.mjs";
import { n as truncateUtf16WithEllipsis } from "./text-truncate-DrDnz8rI.mjs";
import { m as prepareSubagentSessionListReadCache } from "./subagent-registry-state-6clmFwBl.mjs";
import { o as getSubagentSessionListRunByChildSessionKey } from "./subagent-registry-read-kqDz6PtN.mjs";
import { n as resolveEventSessionRoutingPolicy, t as resolveEventSessionKeyForPolicy } from "./event-session-routing-Dm8aw0lJ.mjs";
import { c as callSubagentGateway, f as buildSubagentExecutionSessionSpawnContext, p as withSubagentGatewayExecutionIdentity, u as readGatewayRunId } from "./subagent-spawn-cleanup-D0pAau7y.mjs";
import { r as resolveGatewaySessionStoreTargetInWorker } from "./session-utils-store-worker-9uML4l0-.mjs";
import { t as AGENT_LANE_SUBAGENT } from "./lanes-BJoXx6GG.mjs";
import { c as resolveSpawnAdmission, f as reserveChildAdmissionSlot, l as resolveSpawnChannelAccountId, m as summarizeSpawnError, n as resolveConfiguredSubagentRunTimeoutSeconds, o as mintSpawnSessionKey, p as runSpawnPipeline, s as prepareSpawnThreadBinding, u as resolveSpawnMode } from "./spawn-thread-binding-CxKbcyi_.mjs";
import { o as resolveInternalSessionKey, s as resolveMainSessionAlias } from "./sessions-resolution-DvbCWXwD.mjs";
import "./sessions-helpers-CA0CJ1ys.mjs";
import { n as formatConversationTarget, t as deliveryContextFromConversation } from "./route-projection-CySxH0Gu.mjs";
import { n as waitForSessionParticipantRecording, t as recordSessionParticipantBestEffort } from "./session-participant-recording-Cfoa8flN.mjs";
import { n as resolveAcpAgentPolicyError, t as isAcpEnabledByPolicy } from "./policy-B6crihax.mjs";
import { i as readParentExecutionIdentity, n as resolveSubagentSpawnOwnership, r as resolveRequesterOriginForChild, t as captureSpawnParentLineage } from "./spawn-parent-lineage-DmPvhKI2.mjs";
import { t as recordSessionCreated } from "./session-created-CYn2su72.mjs";
import { i as resolveSpawnedWorkspaceInheritance } from "./spawned-context-Dw3ZU-zS.mjs";
import { i as resolveHeartbeatConfig, o as resolveHeartbeatIntervalMs } from "./heartbeat-config-BsKBfvT-.mjs";
import { t as isHeartbeatEnabledForAgent } from "./heartbeat-summary-DanEvL-Q.mjs";
import { a as resolveAcpSenderSpawnError, i as resolveRuntimeCwdForAcpSpawn, n as initializeAcpSpawnRuntime, o as resolveAcpSpawnRuntimePolicyError, r as resolveAcpSpawnRuntimeOptions, s as cleanupFailedAcpSpawn, t as bindPreparedAcpThread } from "./acp-spawn-runtime-DcfazyEf.mjs";
import { n as resolveAcpProjectionSettings, t as isAcpTagVisible } from "./acp-stream-settings-Ca7bZwio.mjs";
import crypto from "node:crypto";
//#region src/acp/runtime/session-meta-resume.ts
/** Resolve only indexed resume candidates; canonical entries still own requester authorization. */
async function readAcpResumeSessionOwner(params) {
	const { agentId, backendId, resumeSessionId } = params;
	const runtimeAgentId = normalizeOptionalAgentId(params.runtimeAgentId);
	const { cfg, env, databasePath, assertCurrent } = await captureAcpSessionReadContext(params);
	if (!runtimeAgentId) return;
	const lookup = async (sessionKey) => {
		const result = await executeExistingOpenClawStateRead({
			env,
			path: databasePath
		}, {
			type: "acpSessions.resume",
			agentId,
			backendId,
			resumeSessionId,
			sessionKey
		}, { current: true });
		assertCurrent();
		if (!result) return [];
		if (!result.ok || result.type !== "acpSessions.resume") throw new Error("Unexpected ACP resume lookup result");
		return result.rows;
	};
	const rows = await lookup();
	const storePath = resolveSessionStorePathCore(cfg.session?.store, {
		agentId,
		env
	});
	for (const row of rows) {
		if (normalizeOptionalAgentId(row.agent) !== runtimeAgentId) continue;
		params.onCandidate?.(row.sessionKey);
		const owner = await withSessionEntryReadOnlyInWorker({
			agentId,
			storePath,
			sessionKey: row.sessionKey,
			env,
			clone: false
		}, assertCurrent, async (read) => {
			if (!read.ok) throw read.error;
			if (!read.value || !acpSessionRowMatchesEntry(row, read.value)) return;
			const [current] = await lookup(row.sessionKey);
			return current && acpSessionRowMatchesEntry(current, read.value) && normalizeOptionalAgentId(current.agent) === runtimeAgentId ? {
				sessionKey: row.sessionKey,
				entry: read.value
			} : void 0;
		});
		if (owner) return owner;
	}
}
//#endregion
//#region src/acp/runtime/session-meta-resume-authorization.ts
async function withAcpResumeSessionAuthorization(ownership, initialize) {
	if (!normalizeOptionalString(ownership.resumeSessionId)) return initialize();
	let current = true;
	const sources = /* @__PURE__ */ new Map();
	const unsubscribe = sessionChanges.subscribeFacts((change) => {
		if ("sessionKey" in change && !change.factsInvalidated && (change.facts?.kind === "unchanged" || change.facts?.kind === "participants" || change.facts?.kind === "category")) return;
		if ("all" in change || sources.has(change.sessionKey) && (!change.agentId || sources.get(change.sessionKey)?.has(change.agentId))) current = false;
	});
	const assertCurrent = () => {
		ownership.assertCurrent?.();
		if (!current) throw new Error("ACP resume source authority changed; retry from the current session.");
	};
	try {
		return await initialize(async () => {
			assertCurrent();
			const result = await validateAcpResumeSessionOwnership({
				...ownership,
				assertCurrent
			}, (agentId, sessionKey) => {
				const agents = sources.get(sessionKey) ?? /* @__PURE__ */ new Set();
				agents.add(agentId);
				sources.set(sessionKey, agents);
			});
			if (!result.ok) throw new Error(result.error);
			return assertCurrent;
		});
	} finally {
		current = false;
		unsubscribe();
	}
}
async function validateAcpResumeSessionOwnership(params, observeSource) {
	const resumeSessionId = normalizeOptionalString(params.resumeSessionId);
	if (!resumeSessionId) return { ok: true };
	const requesterSessionKey = normalizeOptionalString(params.requesterSessionKey);
	if (!requesterSessionKey) return {
		ok: false,
		error: "sessions_spawn resumeSessionId requires an active requester session context."
	};
	for (const agentId of /* @__PURE__ */ new Set([params.ownerAgentId, params.runtimeAgentId])) {
		const owner = await readAcpResumeSessionOwner({
			...params,
			agentId,
			resumeSessionId,
			onCandidate: (sessionKey) => observeSource?.(agentId, sessionKey)
		});
		params.assertCurrent?.();
		if (!owner) continue;
		if (owner.sessionKey === requesterSessionKey || normalizeOptionalString(owner.entry.spawnedBy) === requesterSessionKey || normalizeOptionalString(owner.entry.parentSessionKey) === requesterSessionKey) return { ok: true };
		break;
	}
	return {
		ok: false,
		error: "sessions_spawn resumeSessionId is only allowed for ACP sessions previously recorded for this requester. Omit resumeSessionId to start a fresh ACP session."
	};
}
//#endregion
//#region src/agents/subagents/spawn/acp-spawn-admission.ts
function countUntrackedActiveAcpRunsForOwner(ownerKey, pendingChildSessionKeys) {
	if (!ownerKey?.trim()) return 0;
	const sessions = listActiveAcpSessionsForOwner(ownerKey.trim());
	return new Set(sessions.filter((sessionKey) => {
		const run = getSubagentSessionListRunByChildSessionKey(sessionKey);
		return !pendingChildSessionKeys?.has(sessionKey) && !(run && typeof run.execution.endedAt !== "number");
	})).size;
}
//#endregion
//#region src/agents/subagents/spawn/acp-spawn-bootstrap-delivery.ts
function toGatewayImageAttachments(attachments) {
	if (!attachments || attachments.length === 0) return;
	return attachments.map((attachment) => ({
		type: "image",
		source: {
			type: "base64",
			media_type: attachment.mediaType,
			data: attachment.data
		}
	}));
}
function resolveAcpSpawnBootstrapDeliveryPlan(params) {
	const boundThreadIdRaw = params.binding?.conversation.conversationId;
	const boundThreadId = boundThreadIdRaw ? normalizeOptionalString(boundThreadIdRaw) : void 0;
	const fallbackThreadIdRaw = params.requester.origin?.threadId;
	const fallbackThreadId = fallbackThreadIdRaw != null ? normalizeOptionalString(String(fallbackThreadIdRaw)) : void 0;
	const deliveryThreadId = boundThreadId ?? fallbackThreadId;
	const requesterConversationRef = resolveInboundConversationResolution({
		cfg: params.cfg,
		channel: params.requester.origin?.channel,
		accountId: params.requester.origin?.accountId,
		threadId: fallbackThreadId,
		to: params.requester.origin?.to
	});
	const requesterAccountId = resolveSpawnChannelAccountId({
		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 && requesterConversationRef?.conversationId && params.binding?.conversation.conversationId === requesterConversationRef.conversationId && (params.binding?.conversation.parentConversationId ?? void 0) === (requesterConversationRef.parentConversationId ?? void 0));
	const boundDeliveryTarget = deliveryContextFromConversation(params.binding?.conversation);
	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 != null ? normalizeOptionalString(String(resolvedDeliveryThreadId)) : void 0
	};
}
//#endregion
//#region src/agents/subagents/spawn/acp-spawn-gateway.ts
async function launchAcpChildThroughGateway(params) {
	const promptedAt = Date.now();
	const response = await callSubagentGateway(withSubagentGatewayExecutionIdentity({
		method: "agent",
		assertDispatchCurrent: params.assertDispatchCurrent,
		params: {
			message: params.task,
			sessionKey: params.sessionKey,
			channel: params.deliveryPlan.channel,
			to: params.deliveryPlan.to,
			accountId: params.deliveryPlan.accountId,
			threadId: params.deliveryPlan.threadId,
			idempotencyKey: params.childIdem,
			deliver: params.deliveryPlan.useInlineDelivery,
			lane: AGENT_LANE_SUBAGENT,
			acpTurnSource: "manual_spawn",
			timeout: params.runTimeoutSeconds,
			label: params.label || void 0,
			...params.attachments ? { attachments: params.attachments } : {}
		},
		timeoutMs: 1e4
	}, {
		sessionSpawnContext: buildSubagentExecutionSessionSpawnContext(params.lineage),
		parentExecutionIdentityToken: params.parentExecutionIdentityToken
	}));
	recordSessionParticipantBestEffort({
		promptedAt,
		identity: {
			type: "agent",
			id: params.lineage.parentAgentId
		},
		agentId: params.lineage.targetAgentId,
		sessionKey: params.sessionKey,
		storePath: params.participantStorePath
	});
	return response;
}
//#endregion
//#region src/agents/subagents/spawn/acp-parent-stream-store.sqlite.ts
/** Captures the child and physical store before delayed relay flushes can yield. */
function createAcpParentStreamRecorder(input) {
	const options = {
		...input,
		env: cloneEnvWithPlatformSemantics(input.env ?? process.env)
	};
	const identity = readDatabasePathIdentitySync(resolveOpenClawAgentSqlitePath(options));
	if (!identity.key.startsWith("file:")) throw new Error("ACP parent-stream diagnostics require the existing child database");
	const execution = captureOpenClawAgentDatabaseExecution(options, { expectedIdentity: {
		kind: "file",
		physicalIdentity: identity.key.slice(5),
		nativeLocation: identity.canonicalPath,
		birthtime: identity.birthtime
	} });
	options.path = execution.path;
	const worker = openOpenClawAgentSqliteWorkerStore(options, { execution }, {
		moduleUrl: resolveRuntimeWorkerUrl(runtimeProcessEntrypoints.acpParentStreamStore),
		input: void 0
	});
	worker.catch(() => {});
	return {
		async record(events) {
			const prepared = events.flatMap((entry) => {
				try {
					const eventJson = JSON.stringify(entry.event);
					if (eventJson !== void 0) return [{
						eventJson,
						createdAt: entry.createdAt
					}];
				} catch {}
				return [];
			});
			if (prepared.length === 0) return {
				ok: true,
				value: void 0
			};
			const result = await runOpenClawAgentWriteAdmission(options, async () => (await worker).execute({
				type: "record",
				input: {
					sessionId: options.sessionId,
					runId: options.runId,
					events: prepared
				}
			}, () => execution.assertCurrent()), true);
			if (result.ok) return result;
			const error = /* @__PURE__ */ new Error("ACP parent-stream transaction rolled back");
			retainOpenClawStateWorkerErrorPayload(error, result.error);
			return {
				ok: false,
				error: hydrateOpenClawStateWorkerError(error, { includeOrdinary: true })
			};
		},
		async close() {
			const failures = [];
			for (const close of [async () => (await worker).close(), () => execution.release()]) try {
				await close();
			} catch (error) {
				failures.push(error);
			}
			throwSqliteLifecycleErrors(failures, "ACP parent-stream recorder cleanup failed");
		}
	};
}
//#endregion
//#region src/agents/subagents/spawn/acp-spawn-parent-stream.ts
const STREAM_FLUSH_MS = 2500;
const NO_OUTPUT_NOTICE_MS = 6e4;
const NO_OUTPUT_POLL_MS = 15e3;
const MAX_RELAY_LIFETIME_MS = 216e5;
const STREAM_BUFFER_MAX_CHARS = 4e3;
const STREAM_SNIPPET_MAX_CHARS = 220;
const STREAM_LOG_BATCH_SIZE = 100;
const STREAM_LOG_FLUSH_MS = 1e3;
const STREAM_LOG_MAX_PENDING_EVENTS = 256;
const STREAM_LOG_MAX_RETRY_MS = 3e4;
const log = createSubsystemLogger("agents/acp-parent-stream");
function mergeStreamingConfig(base, override) {
	const baseRecord = asOptionalRecord(base);
	const overrideRecord = asOptionalRecord(override);
	if (!baseRecord || !overrideRecord) return override ?? base;
	const merged = {
		...baseRecord,
		...overrideRecord
	};
	const baseProgress = asOptionalRecord(baseRecord.progress);
	const overrideProgress = asOptionalRecord(overrideRecord.progress);
	if (baseProgress && overrideProgress) merged.progress = {
		...baseProgress,
		...overrideProgress
	};
	else if (overrideProgress ?? baseProgress) merged.progress = overrideProgress ?? baseProgress;
	else delete merged.progress;
	return merged;
}
function resolveParentProgressStreamingEntry(params) {
	const channelId = normalizeOptionalString(params.deliveryContext?.channel);
	if (!params.cfg || !channelId) return;
	const channelCfg = params.cfg.channels?.[channelId];
	if (!channelCfg) return;
	const accountCfg = resolveChannelAccountEntry(channelCfg.accounts, normalizeAccountId(params.deliveryContext?.accountId), channelId, normalizeAccountId);
	return accountCfg ? {
		...channelCfg,
		...accountCfg,
		streaming: mergeStreamingConfig(channelCfg.streaming, accountCfg.streaming)
	} : channelCfg;
}
function startAcpSpawnParentStreamRelay(params) {
	const { runId, parentSessionKey, eventRouting } = params;
	const relayLabel = truncateUtf16WithEllipsis(params.agentId.replace(/\s+/g, " ").trim(), 40) || "ACP child";
	const contextPrefix = `acp-spawn:${runId}`;
	const childSessionId = normalizeOptionalString(params.childSessionId);
	const stateEnv = { ...params.env ?? process.env };
	const host = getBoundLegacyPluginSdkResourceHost();
	const closingSignal = host?.scheduler.signal;
	const acceptedWork = new AsyncWorkScope();
	let recorder;
	try {
		if (childSessionId) recorder = createAcpParentStreamRecorder({
			agentId: params.ownerAgentId ?? params.agentId,
			env: stateEnv,
			sessionId: childSessionId,
			runId
		});
	} catch (error) {
		log.warn("Failed to capture ACP parent stream diagnostic store", {
			runId,
			error: String(error)
		});
	}
	const pendingLogEvents = [];
	let logFlush;
	let disposal;
	let logFlushTimer;
	let logFailureWarned = false;
	let logBufferWarned = false;
	let consecutiveLogFailures = 0;
	let disposed = false;
	const capPendingLogEvents = () => {
		const overflow = pendingLogEvents.length - STREAM_LOG_MAX_PENDING_EVENTS;
		if (overflow <= 0) return;
		pendingLogEvents.splice(0, overflow);
		if (!logBufferWarned) {
			log.warn("Capped ACP parent stream diagnostic buffer", {
				runId,
				childSessionId,
				maxPendingEvents: STREAM_LOG_MAX_PENDING_EVENTS
			});
			logBufferWarned = true;
		}
	};
	const clearLogFlushTimer = () => {
		clearTimeout(logFlushTimer);
		logFlushTimer = void 0;
	};
	function flushLogEvents() {
		clearLogFlushTimer();
		if (logFlush || !recorder || pendingLogEvents.length === 0) return logFlush;
		const events = pendingLogEvents.splice(0);
		const writer = recorder;
		logFlush = acceptedWork.track(async () => {
			let retryable = false;
			try {
				const result = await writer.record(events);
				if (!result.ok) {
					retryable = true;
					throw result.error;
				}
				logFailureWarned = false;
				logBufferWarned = false;
				consecutiveLogFailures = 0;
			} catch (error) {
				retryable ||= isSqliteWorkerError(error, "overloaded") || isSqliteWorkerError(error, "unavailable");
				const retrying = retryable && !disposed;
				if (retrying) {
					pendingLogEvents.unshift(...events);
					capPendingLogEvents();
					consecutiveLogFailures += 1;
					clearLogFlushTimer();
					scheduleLogFlush(Math.min(STREAM_LOG_FLUSH_MS * 2 ** consecutiveLogFailures, STREAM_LOG_MAX_RETRY_MS));
				}
				if (!logFailureWarned || disposed) {
					log.warn("Failed to persist ACP parent stream diagnostics", {
						runId,
						childSessionId,
						retrying,
						error: String(error)
					});
					logFailureWarned = true;
				}
			}
		}).finally(() => {
			logFlush = void 0;
			scheduleLogFlush();
		});
		return logFlush;
	}
	function scheduleLogFlush(delayMs = STREAM_LOG_FLUSH_MS) {
		if (disposed || logFlushTimer || pendingLogEvents.length === 0) return;
		logFlushTimer = setTimeout(() => {
			flushLogEvents();
		}, delayMs);
		logFlushTimer.unref?.();
	}
	const logEvent = (kind, fields) => {
		if (disposed || !recorder) return;
		const createdAt = Date.now();
		pendingLogEvents.push({
			createdAt,
			event: {
				ts: new Date(createdAt).toISOString(),
				epochMs: createdAt,
				runId,
				parentSessionKey,
				childSessionKey: params.childSessionKey,
				agentId: params.agentId,
				kind,
				...fields
			}
		});
		capPendingLogEvents();
		if (consecutiveLogFailures === 0 && pendingLogEvents.length >= STREAM_LOG_BATCH_SIZE) {
			flushLogEvents();
			return;
		}
		scheduleLogFlush();
	};
	const shouldRelayProgressCommentary = resolveChannelStreamingProgressCommentary(resolveParentProgressStreamingEntry({
		cfg: params.cfg,
		deliveryContext: params.deliveryContext
	}), true);
	const acpProjectionSettings = resolveAcpProjectionSettings(params.cfg ?? {});
	const eventSessionKey = resolveEventSessionKeyForPolicy(parentSessionKey, eventRouting);
	const deliveryContext = structuredClone(params.deliveryContext);
	const emit = (text, contextKey) => {
		const cleaned = truncateUtf16WithEllipsis(text.trim(), STREAM_BUFFER_MAX_CHARS);
		if (disposed || !cleaned) return;
		logEvent("system_event", {
			contextKey,
			text: cleaned
		});
		try {
			enqueueSessionEventForHost(cleaned, {
				agentId: params.requesterAgentId,
				sessionKey: eventSessionKey,
				source: "task",
				contextKey,
				deliveryContext,
				expectedTarget: params.expectedTarget
			}).settled.then((outcome) => {
				if (outcome.status !== "completed") log.warn("ACP parent relay follow-up failed", {
					runId,
					contextKey,
					...outcome
				});
			});
		} catch (error) {
			log.warn("ACP parent relay follow-up rejected", {
				runId,
				contextKey,
				error: String(error)
			});
		}
	};
	const emitStartNotice = () => {
		emit(`Started ${relayLabel} session ${params.childSessionKey}. Streaming progress updates to parent session.`, `${contextPrefix}:start`);
	};
	let pendingText = "";
	let pendingProgressKind;
	let replaceableAssistantSnapshot;
	const itemProgressTextById = /* @__PURE__ */ new Map();
	let lastProgressAt = Date.now();
	let stallNotified = false;
	let promptSubmittedAt;
	let firstRuntimeEventAt;
	let firstVisibleOutputAt;
	let lastRuntimeEventType;
	let proxyEnvKeysAtPrompt = [];
	let flushTimer;
	const clearFlushTimer = () => {
		clearTimeout(flushTimer);
		flushTimer = void 0;
	};
	const flushPending = () => {
		clearFlushTimer();
		if (!pendingText) return;
		const snippet = truncateUtf16WithEllipsis(pendingText.replace(/\s+/g, " ").trim(), STREAM_SNIPPET_MAX_CHARS);
		pendingText = "";
		pendingProgressKind = void 0;
		if (!snippet) return;
		emit(`${relayLabel}: ${snippet}`, `${contextPrefix}:progress`);
	};
	const scheduleFlush = () => {
		if (disposed || flushTimer) return;
		flushTimer = setTimeout(flushPending, STREAM_FLUSH_MS);
		flushTimer.unref?.();
	};
	const appendVisibleProgress = (delta, kind) => {
		if (stallNotified) {
			stallNotified = false;
			emit(`${relayLabel} resumed output.`, `${contextPrefix}:resumed`);
		}
		lastProgressAt = Date.now();
		firstVisibleOutputAt ??= lastProgressAt;
		if (pendingText && pendingProgressKind && pendingProgressKind !== kind) flushPending();
		pendingProgressKind = kind;
		pendingText += delta;
		if (pendingText.length > STREAM_BUFFER_MAX_CHARS) pendingText = sliceUtf16Safe(pendingText, -4e3);
		if (pendingText.length >= STREAM_SNIPPET_MAX_CHARS || delta.includes("\n\n")) {
			flushPending();
			return;
		}
		scheduleFlush();
	};
	const flushReplaceableAssistantSnapshot = () => {
		const snapshot = replaceableAssistantSnapshot;
		replaceableAssistantSnapshot = void 0;
		if (!snapshot?.trim()) return;
		appendVisibleProgress(snapshot, "assistant:replaceable");
	};
	const appendItemProgressSnapshot = (snapshot) => {
		const previous = itemProgressTextById.get(snapshot.itemId) ?? "";
		if (snapshot.text === previous) return;
		const kind = `item:${snapshot.itemId}`;
		const isPrefixUpdate = Boolean(previous && snapshot.text.startsWith(previous));
		if (previous && !isPrefixUpdate && pendingProgressKind === kind && Boolean(pendingText)) pendingText = "";
		itemProgressTextById.set(snapshot.itemId, snapshot.text);
		const delta = isPrefixUpdate ? snapshot.text.slice(previous.length) : snapshot.text;
		appendVisibleProgress(delta, kind);
	};
	const buildNoOutputNotice = () => {
		const seconds = Math.round(NO_OUTPUT_NOTICE_MS / 1e3);
		if (!promptSubmittedAt) return `${relayLabel} session started but no prompt submission was observed for ${seconds}s.`;
		if (!firstRuntimeEventAt) {
			const proxySummary = `proxy env: ${proxyEnvKeysAtPrompt.join(", ") || "none"}`;
			return `${relayLabel} prompt was submitted but no ACP runtime event arrived for ${seconds}s (${proxySummary}). Check upstream connectivity, auth, or proxy/network access in the gateway child environment.`;
		}
		if (!firstVisibleOutputAt) {
			const lastEvent = lastRuntimeEventType ? ` Last ACP event: ${lastRuntimeEventType}.` : "";
			return `${relayLabel} has ACP runtime activity but no visible assistant output for ${seconds}s.${lastEvent} It may be working, blocked on a tool, or failing before visible output.`;
		}
		return `${relayLabel} has produced no visible output for ${seconds}s. It may be waiting for interactive input.`;
	};
	const noOutputWatcherTimer = setInterval(() => {
		if (disposed || stallNotified || Date.now() - lastProgressAt < NO_OUTPUT_NOTICE_MS) return;
		stallNotified = true;
		emit(buildNoOutputNotice(), `${contextPrefix}:stall`);
	}, NO_OUTPUT_POLL_MS);
	noOutputWatcherTimer.unref?.();
	const relayLifetimeTimer = setTimeout(() => {
		if (disposed) return;
		emit(`${relayLabel} stream relay timed out after ${Math.round(MAX_RELAY_LIFETIME_MS / 1e3)}s without completion.`, `${contextPrefix}:timeout`);
		dispose();
	}, MAX_RELAY_LIFETIME_MS);
	relayLifetimeTimer.unref?.();
	const unsubscribe = onAgentEventForRun(runId, (event) => {
		if (disposed || event.runId !== runId) return;
		if (event.stream === "assistant") {
			const data = event.data;
			const assistantPhase = normalizeAssistantPhase(data.phase);
			const textCandidate = data.text;
			const deltaCandidate = data.delta;
			const snapshot = typeof textCandidate === "string" ? textCandidate : typeof deltaCandidate === "string" ? deltaCandidate : void 0;
			if (data.replaceable === true) {
				if (snapshot?.trim()) {
					replaceableAssistantSnapshot = snapshot;
					lastProgressAt = Date.now();
					logEvent("assistant_replaceable_snapshot", {
						text: snapshot,
						...assistantPhase ? { phase: assistantPhase } : {}
					});
				}
				return;
			}
			const delta = typeof deltaCandidate === "string" ? deltaCandidate : snapshot;
			if (!delta || !delta.trim()) return;
			logEvent("assistant_delta", {
				delta,
				...assistantPhase ? { phase: assistantPhase } : {}
			});
			if (assistantPhase === "commentary" && !shouldRelayProgressCommentary) {
				lastProgressAt = Date.now();
				return;
			}
			replaceableAssistantSnapshot = void 0;
			appendVisibleProgress(delta, `assistant:${assistantPhase ?? "unknown"}`);
			return;
		}
		if (event.stream === "item") {
			const data = event.data;
			const itemId = normalizeOptionalString(data?.itemId);
			const kind = normalizeOptionalString(data?.kind);
			const progressText = normalizeOptionalString(data?.progressText);
			if (kind === "preamble" && progressText) {
				lastProgressAt = Date.now();
				if (shouldRelayProgressCommentary && itemId) appendItemProgressSnapshot({
					itemId,
					text: progressText
				});
			}
			return;
		}
		if (event.stream === "acp") {
			const data = event.data;
			const phase = normalizeOptionalString(data?.phase);
			logEvent("acp", {
				phase: phase ?? "unknown",
				data: event.data
			});
			if (phase === "prompt_submitted") {
				const at = asFiniteNumber(data?.at) ?? Date.now();
				promptSubmittedAt ??= at;
				proxyEnvKeysAtPrompt = filterStringEntries(data?.proxyEnvKeys).filter(Boolean);
				lastProgressAt = Date.now();
				return;
			}
			if (phase === "runtime_event") {
				const eventType = normalizeOptionalString(data?.eventType);
				const text = normalizeOptionalString(data?.text);
				const tag = normalizeOptionalString(data?.tag);
				firstRuntimeEventAt ??= Date.now();
				lastRuntimeEventType = eventType;
				if (shouldRelayProgressCommentary && eventType === "status" && text && isAcpTagVisible(acpProjectionSettings, tag)) {
					appendVisibleProgress(`${text}\n\n`, "acp:status");
					return;
				}
				lastProgressAt = Date.now();
				return;
			}
			return;
		}
		if (event.stream !== "lifecycle") return;
		const phase = normalizeOptionalString(event.data.phase);
		logEvent("lifecycle", {
			phase: phase ?? "unknown",
			data: event.data
		});
		if (phase !== "end" && phase !== "error") return;
		flushReplaceableAssistantSnapshot();
		flushPending();
		if (phase === "end") {
			const startedAt = asFiniteNumber(event.data.startedAt);
			const endedAt = asFiniteNumber(event.data.endedAt);
			const durationMs = startedAt != null && endedAt != null && endedAt >= startedAt ? endedAt - startedAt : void 0;
			emit(durationMs != null ? `${relayLabel} run completed in ${Math.max(1, Math.round(durationMs / 1e3))}s.` : `${relayLabel} run completed.`, `${contextPrefix}:done`);
		} else {
			const errorText = normalizeOptionalString(event.data.error);
			emit(errorText ? `${relayLabel} run failed: ${errorText}` : `${relayLabel} run failed.`, `${contextPrefix}:error`);
		}
		dispose();
	});
	const dispose = () => {
		if (disposal) return disposal;
		disposed = true;
		clearFlushTimer();
		clearLogFlushTimer();
		clearTimeout(relayLifetimeTimer);
		clearInterval(noOutputWatcherTimer);
		unsubscribe();
		closingSignal?.removeEventListener("abort", onClose);
		disposal = (async () => {
			await logFlush;
			await flushLogEvents();
			await acceptedWork.drain();
			await recorder?.close();
		})().catch((error) => {
			log.warn("Failed to close ACP parent stream diagnostics", {
				runId,
				error: String(error)
			});
		});
		const completion = disposal;
		host?.releaseClaim({ release: () => completion });
		return disposal;
	};
	const onClose = () => {
		dispose();
	};
	closingSignal?.addEventListener("abort", onClose, { once: true });
	return {
		dispose,
		notifyStarted: emitStartNotice
	};
}
//#endregion
//#region src/agents/subagents/spawn/acp-spawn-heartbeat.ts
function isHeartbeatEnabledForSessionAgent(params) {
	if (!areSessionEventWakesEnabled()) return false;
	if (!params.sessionKey?.trim()) return true;
	const requesterAgentId = resolveSessionAgentIds({
		config: params.cfg,
		agentId: params.requesterAgentId,
		sessionKey: params.sessionKey
	}).sessionAgentId;
	if (!isHeartbeatEnabledForAgent(params.cfg, requesterAgentId)) return false;
	return resolveHeartbeatIntervalMs(params.cfg, void 0, resolveHeartbeatConfig(params.cfg, requesterAgentId)) !== null;
}
function hasSessionLocalHeartbeatRelayRoute(params) {
	if ((params.cfg.session?.scope ?? "per-sender") === "global") return false;
	const heartbeat = resolveHeartbeatConfig(params.cfg, params.requesterAgentId);
	if (heartbeat?.target !== "last") return false;
	if (normalizeOptionalString(heartbeat.to) || normalizeOptionalString(heartbeat.accountId)) return false;
	const storePath = resolveSessionStorePathCore(params.cfg.session?.store, { agentId: params.requesterAgentId });
	const parentEntry = loadSessionEntryReadOnly({
		storePath,
		sessionKey: params.parentSessionKey,
		clone: false
	});
	const parentDeliveryContext = deliveryContextFromSession(parentEntry);
	return hasDeliveryTargetFields(parentDeliveryContext);
}
//#endregion
//#region src/agents/subagents/spawn/acp-spawn-requester.ts
async function readAcpSpawnParentDeliveryContext(params) {
	return await withSessionEntryReadOnlyInWorker({
		sessionKey: params.parentSessionKey,
		agentId: resolveAgentIdFromSessionKey(params.parentSessionKey, params.requesterAgentId),
		clone: false
	}, () => params.assertActive?.(), async (read) => {
		if (!read.ok) throw read.error;
		return deliveryContextFromSession(read.value);
	});
}
function resolveRequesterInternalSessionKey(params) {
	const { alias } = resolveMainSessionAlias(params.cfg);
	const requesterSessionKey = normalizeOptionalString(params.requesterSessionKey);
	return requesterSessionKey ? resolveInternalSessionKey({
		key: requesterSessionKey,
		alias
	}) : alias;
}
async function resolveAcpSpawnRequesterState(params) {
	const requesterParsedSession = parseAgentSessionKey(params.parentSessionKey);
	const isSubagentSession = Boolean(requesterParsedSession) && isSubagentSessionKey(params.parentSessionKey);
	return {
		isSubagentSession,
		hasActiveSubagentBinding: isSubagentSession && params.parentSessionKey ? (await listSessionBindingsBySessionAsync(params.parentSessionKey)).some((record) => record.targetKind === "subagent" && record.status !== "ended") : false,
		hasThreadContext: typeof params.ctx.agentThreadId === "string" ? Boolean(normalizeOptionalString(params.ctx.agentThreadId)) : params.ctx.agentThreadId != null,
		heartbeatEnabled: isHeartbeatEnabledForSessionAgent({
			cfg: params.cfg,
			requesterAgentId: params.requesterAgentId,
			sessionKey: params.parentSessionKey
		}),
		heartbeatRelayRouteUsable: params.parentSessionKey && params.requesterAgentId ? hasSessionLocalHeartbeatRelayRoute({
			cfg: params.cfg,
			parentSessionKey: params.parentSessionKey,
			requesterAgentId: params.requesterAgentId
		}) : false,
		origin: resolveRequesterOriginForChild({
			cfg: params.cfg,
			targetAgentId: params.ownerAgentId,
			requesterAgentId: params.requesterAgentId,
			requesterChannel: params.ctx.agentChannel,
			requesterAccountId: params.ctx.agentAccountId,
			requesterTo: params.ctx.agentTo,
			requesterThreadId: params.ctx.agentThreadId,
			requesterGroupSpace: params.ctx.agentGroupSpace,
			requesterMemberRoleIds: params.ctx.agentMemberRoleIds
		})
	};
}
function shouldStreamAcpSpawnToParent(params) {
	const implicitStreamToParent = params.spawnMode === "run" && !params.requestThreadBinding && params.requester.isSubagentSession && !params.requester.hasActiveSubagentBinding && !params.requester.hasThreadContext && params.requester.heartbeatEnabled && params.requester.heartbeatRelayRouteUsable;
	return params.streamToParentRequested || implicitStreamToParent;
}
//#endregion
//#region src/agents/subagents/spawn/acp-spawn-result.ts
function buildAcpSpawnError(errorCode, error, status = "error") {
	return {
		status,
		errorCode,
		error
	};
}
function buildAcpSpawnFailureResult(result, childSessionKey) {
	const { phase, error, runId } = result;
	const bindingError = phase === "initialize" && isSessionBindingError(error);
	return {
		status: "error",
		errorCode: bindingError ? "thread_binding_invalid" : phase === "dispatch" ? "dispatch_failed" : "spawn_failed",
		error: bindingError ? error.message : phase === "register" ? `Failed to register ACP run: ${summarizeSpawnError(error)}. Cleanup was attempted, but the already-started ACP run may still finish in the background.` : summarizeSpawnError(error),
		...phase !== "initialize" ? { childSessionKey } : {},
		...phase === "register" && runId ? { runId } : {}
	};
}
//#endregion
//#region src/agents/subagents/spawn/acp-spawn-target.ts
function resolveAcpAgentTarget(params) {
	const backendId = normalizeOptionalString(params.agentBackend) ?? normalizeOptionalString(params.cfg.acp?.backend) ?? getAcpRuntimeBackend()?.id;
	return {
		ok: true,
		agentId: params.agentId,
		...params.configAgentId ? { configAgentId: params.configAgentId } : {},
		...backendId ? { backendId } : {}
	};
}
function resolveTargetAcpAgentId(params) {
	const normalizedRequest = params.requestedAgentId === void 0 ? null : normalizeAgentIdStrict(params.requestedAgentId);
	if (normalizedRequest && !normalizedRequest.ok) return {
		ok: false,
		error: `agentId "${params.requestedAgentId}" was not found`
	};
	const requested = normalizedRequest?.value ?? normalizeOptionalAgentId(params.cfg.acp?.defaultAgent);
	if (requested) {
		const configuredAgent = resolveAgentEntry(params.cfg, requested);
		if (configuredAgent?.runtime?.type === "acp") return resolveAcpAgentTarget({
			cfg: params.cfg,
			agentId: normalizeOptionalAgentId(configuredAgent.runtime.acp?.agent) ?? requested,
			configAgentId: requested,
			agentBackend: configuredAgent.runtime.acp?.backend
		});
		if (configuredAgent && !isExplicitlyAllowedAcpAgent(params.cfg, requested)) return {
			ok: false,
			error: `agentId "${requested}" is an OpenClaw config agent, not an ACP harness. Use runtime="subagent" or omit runtime for OpenClaw config agents. Use runtime="acp" only with external ACP harness ids such as codex, claude, droid, gemini, or opencode, or configure agents.entries.*.runtime.type="acp" with runtime.acp.agent.`
		};
		return resolveAcpAgentTarget({
			cfg: params.cfg,
			agentId: requested,
			...configuredAgent ? { configAgentId: requested } : {}
		});
	}
	return {
		ok: false,
		error: "ACP target agent is not configured. Pass `agentId` in `sessions_spawn` or set `acp.defaultAgent` in config."
	};
}
function isExplicitlyAllowedAcpAgent(cfg, agentId) {
	return (cfg.acp?.allowedAgents ?? []).some((entry) => entry.trim() === "*" || normalizeOptionalAgentId(entry) === agentId);
}
function resolveConfiguredAcpSubagentTargetIds(cfg) {
	const ids = new Set(listAgentIds(cfg));
	const candidates = [
		...listAgentEntries(cfg).flatMap((agent) => agent.runtime?.type === "acp" ? [agent.runtime.acp?.agent] : []),
		cfg.acp?.defaultAgent,
		...(cfg.acp?.allowedAgents ?? []).filter((entry) => entry.trim() !== "*")
	];
	for (const entry of candidates) {
		const id = normalizeOptionalAgentId(entry);
		if (id) ids.add(id);
	}
	return Array.from(ids);
}
//#endregion
//#region src/agents/subagents/spawn/acp-spawn.ts
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.";
async function spawnAcpDirect(params, ctx) {
	const cfg = getRuntimeConfig();
	const runTimeoutSeconds = resolveConfiguredSubagentRunTimeoutSeconds({
		cfg,
		runTimeoutSeconds: params.runTimeoutSeconds
	});
	const requesterInternalKey = resolveRequesterInternalSessionKey({
		cfg,
		requesterSessionKey: ctx.agentSessionKey
	});
	if (!isAcpEnabledByPolicy(cfg)) return buildAcpSpawnError("acp_disabled", "ACP is disabled by policy (`acp.enabled=false`).", "forbidden");
	const streamToParentRequested = params.streamTo === "parent";
	const parentSessionKey = normalizeOptionalString(ctx.agentSessionKey);
	if (streamToParentRequested && !parentSessionKey) return buildAcpSpawnError("requester_session_required", "sessions_spawn streamTo=\"parent\" requires an active requester session context.");
	const requestThreadBinding = params.thread === true;
	const requesterAgentId = resolveSessionAgentId({
		config: cfg,
		sessionKey: requesterInternalKey,
		agentId: ctx.requesterAgentIdOverride
	});
	const runtimePolicyError = resolveAcpSpawnRuntimePolicyError({
		cfg,
		requesterAgentId,
		requesterSessionKey: ctx.agentSessionKey,
		requesterSandboxed: ctx.sandboxed,
		sandbox: params.sandbox
	});
	if (runtimePolicyError) return buildAcpSpawnError("runtime_policy", runtimePolicyError, "forbidden");
	const acpUnsupportedInheritedTool = findAcpUnsupportedInheritedToolDeny(ctx.inheritedToolDenylist);
	if (acpUnsupportedInheritedTool) return buildAcpSpawnError("runtime_policy", formatAcpInheritedToolDenyError(acpUnsupportedInheritedTool), "forbidden");
	const acpUnsupportedInheritedAllow = findAcpUnsupportedInheritedToolAllow(ctx.inheritedToolAllowlist);
	if (acpUnsupportedInheritedAllow) return buildAcpSpawnError("runtime_policy", formatAcpInheritedToolAllowError(acpUnsupportedInheritedAllow), "forbidden");
	const spawnMode = resolveSpawnMode({
		requestedMode: params.mode,
		threadRequested: requestThreadBinding
	});
	if (spawnMode === "session" && !requestThreadBinding) return buildAcpSpawnError("thread_required", "sessions_spawn(runtime=\"acp\", mode=\"session\") requires thread=true so the ACP session can stay bound to a channel thread. Retry with { mode: \"session\", thread: true } on a channel that exposes threads (e.g. Discord, Slack, Telegram topics), or use mode=\"run\" for one-shot work.");
	const targetAgentResult = resolveTargetAcpAgentId({
		requestedAgentId: params.agentId,
		cfg
	});
	if (!targetAgentResult.ok) return buildAcpSpawnError(params.agentId && normalizeOptionalAgentId(params.agentId) ? "runtime_agent_mismatch" : "target_agent_required", targetAgentResult.error);
	const { agentId: targetAgentId, configAgentId, backendId } = targetAgentResult;
	const ownerAgentId = configAgentId ?? requesterAgentId;
	const senderRestricted = ctx.inheritedToolPolicySource === "sender";
	const requesterRoot = ctx.sessionPermissionPolicy?.root ?? ctx.workspaceDir;
	const requesterPolicyError = resolveAcpSenderSpawnError({
		...ctx,
		requesterAgentId,
		targetAgentId,
		cwd: params.cwd
	});
	if (requesterPolicyError) return buildAcpSpawnError("runtime_policy", requesterPolicyError, "forbidden");
	const agentPolicyError = resolveAcpAgentPolicyError(cfg, targetAgentId);
	if (agentPolicyError) return buildAcpSpawnError("agent_forbidden", agentPolicyError.message, "forbidden");
	const subagentStore = resolveSubagentCapabilityStore(parentSessionKey, { cfg });
	const requesterState = await resolveAcpSpawnRequesterState({
		cfg,
		parentSessionKey,
		requesterAgentId,
		ownerAgentId,
		ctx
	});
	ctx.assertActive?.();
	const ownership = resolveSubagentSpawnOwnership({
		cfg,
		agentSessionKey: ctx.agentSessionKey,
		completionOwnerKey: ctx.completionOwnerKey
	});
	const requesterTarget = await resolveGatewaySessionStoreTargetInWorker({
		cfg,
		key: ownership.completionRequesterSessionKey,
		agentId: ctx.requesterAgentIdOverride,
		assertActive: ctx.assertActive
	});
	ctx.assertActive?.();
	const requesterEntry = requesterTarget.store[requesterTarget.canonicalKey];
	const completionRequesterSessionId = requesterEntry?.sessionId;
	const completionRequesterLifecycleRevision = requesterEntry?.lifecycleRevision;
	const hasSubagentEnvelope = isSubagentEnvelopeSession(requesterInternalKey, {
		cfg,
		store: subagentStore
	});
	await prepareSubagentSessionListReadCache();
	ctx.assertActive?.();
	const resolveAdmission = (pendingChildren = 0, pendingChildSessionKeys) => resolveSpawnAdmission({
		cfg,
		inheritedToolPolicySource: ctx.inheritedToolPolicySource,
		enabled: hasSubagentEnvelope || senderRestricted,
		requesterSessionKey: requesterInternalKey,
		requesterAgentId,
		targetAgentId,
		requestedAgentId: params.agentId,
		configuredAgentIds: resolveConfiguredAcpSubagentTargetIds(cfg),
		additionalActiveChildren: hasSubagentEnvelope ? countUntrackedActiveAcpRunsForOwner(requesterInternalKey, pendingChildSessionKeys) + pendingChildren : 0
	});
	const admission = resolveAdmission();
	if (!admission.ok) return buildAcpSpawnError("subagent_policy", admission.error, "forbidden");
	const resumeOwnership = {
		cfg,
		ownerAgentId,
		runtimeAgentId: targetAgentId,
		backendId,
		requesterSessionKey: requesterInternalKey,
		resumeSessionId: params.resumeSessionId,
		assertCurrent: ctx.assertActive
	};
	const resumeAuthorization = await validateAcpResumeSessionOwnership(resumeOwnership);
	ctx.assertActive?.();
	if (!resumeAuthorization.ok) return buildAcpSpawnError("resume_forbidden", resumeAuthorization.error, "forbidden");
	const runtimeOptionsResult = resolveAcpSpawnRuntimeOptions({
		cfg,
		targetAgentId,
		configAgentId: targetAgentResult.configAgentId,
		model: params.model,
		thinking: params.thinking,
		runTimeoutSeconds
	});
	if (!runtimeOptionsResult.ok) return buildAcpSpawnError("spawn_failed", runtimeOptionsResult.error);
	const effectiveStreamToParent = shouldStreamAcpSpawnToParent({
		spawnMode,
		requestThreadBinding,
		streamToParentRequested,
		requester: requesterState
	});
	const sessionKey = mintSpawnSessionKey({
		targetAgentId: ownerAgentId,
		backend: "acp"
	});
	const resolvedCwd = resolveSpawnedWorkspaceInheritance({
		config: cfg,
		targetAgentId: ownerAgentId,
		requesterSessionKey: ctx.agentSessionKey,
		explicitWorkspaceDir: senderRestricted ? requesterRoot : params.cwd
	});
	let runtimeCwd;
	try {
		runtimeCwd = await resolveRuntimeCwdForAcpSpawn({
			resolvedCwd,
			explicitCwd: senderRestricted ? requesterRoot : params.cwd
		});
	} catch (error) {
		return buildAcpSpawnError("cwd_resolution_failed", formatErrorMessage(error));
	}
	let preparedBinding = null;
	if (requestThreadBinding) {
		const prepared = await prepareSpawnThreadBinding({
			cfg,
			kind: "acp",
			mode: spawnMode,
			bindingService: {
				...getSessionBindingService(),
				listBySession: listSessionBindingsBySessionAsync
			},
			channel: requesterState.origin?.channel,
			accountId: requesterState.origin?.accountId,
			to: requesterState.origin?.to,
			threadId: requesterState.origin?.threadId,
			groupId: ctx.agentGroupId
		});
		ctx.assertActive?.();
		if (!prepared.ok) return buildAcpSpawnError("thread_binding_invalid", prepared.error);
		preparedBinding = prepared.binding;
	}
	let childCreationEntry;
	let closeRuntimeOnFailure;
	const childIdem = crypto.randomUUID();
	const parentDeliveryCtx = effectiveStreamToParent && parentSessionKey ? await readAcpSpawnParentDeliveryContext({
		parentSessionKey,
		requesterAgentId,
		assertActive: ctx.assertActive
	}) : void 0;
	ctx.assertActive?.();
	const parentRelayStateEnv = { ...process.env };
	const parentEventRouting = parentSessionKey ? resolveEventSessionRoutingPolicy({
		cfg,
		sessionKey: parentSessionKey
	}) : void 0;
	const parentEventTarget = effectiveStreamToParent && parentSessionKey && parentEventRouting ? await captureSessionEventTargetForHost(requesterAgentId, resolveEventSessionKeyForPolicy(parentSessionKey, parentEventRouting)) : void 0;
	ctx.assertActive?.();
	const gatewayAttachments = toGatewayImageAttachments(params.attachments);
	const requesterOrigin = requesterState.origin;
	const progressOrigin = {
		channel: requesterOrigin?.channel,
		accountId: requesterOrigin?.accountId,
		to: ctx.currentMessagingTarget ?? ctx.currentChannelId ?? requesterOrigin?.to,
		threadId: requesterOrigin?.threadId,
		channelId: ctx.currentChannelId,
		messageId: ctx.currentMessageId
	};
	const adapter = {
		async initialize() {
			const parentTarget = await resolveGatewaySessionStoreTargetInWorker({
				cfg,
				key: requesterInternalKey,
				agentId: requesterAgentId,
				assertActive: ctx.assertActive
			});
			const parentStorePath = parentTarget.readSource?.path ?? parentTarget.storePath;
			await waitForSessionParticipantRecording({
				agentId: requesterAgentId,
				sessionKey: parentTarget.canonicalKey,
				storePath: parentStorePath
			});
			ctx.assertActive?.();
			const readParentEntry = () => readSessionEntryReadOnlyInWorker({
				agentId: requesterAgentId,
				sessionKey: parentTarget.canonicalKey,
				storePath: parentStorePath
			}, () => ctx.assertActive?.());
			const parentEntry = isIncognitoSessionKey(requesterInternalKey) ? void 0 : await readParentEntry();
			const parentLineage = captureSpawnParentLineage({
				parentEntry,
				expectedParentSessionId: ctx.expectedParentSessionId,
				senderIsOwner: ctx.senderIsOwner,
				readParentEntry
			});
			const creationStamp = buildSessionCreationStamp({
				via: "spawn",
				actor: {
					type: "agent",
					id: requesterAgentId
				},
				inheritedGitContributorProfileIds: inheritSessionGitContributorProfileIds(parentEntry)
			});
			const storePath = resolveSessionStorePathCore(cfg.session?.store, { agentId: ownerAgentId });
			const childSessionPatch = admission.childSessionPatch ? {
				spawnDepth: admission.childSessionPatch.spawnDepth,
				...admission.childSessionPatch.subagentRole ? { subagentRole: admission.childSessionPatch.subagentRole } : {},
				subagentControlScope: admission.childSessionPatch.subagentControlScope
			} : {};
			await parentLineage.assertParentUnchanged();
			ctx.assertActive?.();
			childCreationEntry = await upsertSessionEntryCore({
				storePath,
				sessionKey,
				agentId: ownerAgentId
			}, {
				...creationStamp,
				spawnedBy: requesterInternalKey,
				completionOwnerSessionKey: ownership.completionRequesterSessionKey,
				parentSessionKey: requesterInternalKey,
				...childSessionPatch,
				inheritedToolPolicyVersion: 1,
				...ctx.inheritedToolPolicySource ? { inheritedToolPolicySource: ctx.inheritedToolPolicySource } : {},
				...inheritedToolAllowPatch(ctx.inheritedToolAllowlist),
				...inheritedToolDenyPatch(ctx.inheritedToolDenylist),
				...senderRestricted ? {
					spawnedWorkspaceDir: ctx.workspaceDir ?? requesterRoot,
					spawnedCwd: runtimeCwd,
					sessionRoot: requesterRoot,
					permissionMode: ctx.sessionPermissionPolicy?.mode
				} : {},
				...params.label ? { label: params.label } : {},
				...parentLineage.receipt
			}, sessionEntryCommitGuardOptions(ctx.assertActive)) ?? void 0;
			const initializedSession = await withAcpResumeSessionAuthorization(resumeOwnership, (revalidateResume) => initializeAcpSpawnRuntime({
				assertActive: ctx.assertActive,
				revalidateResume,
				cfg,
				sessionKey,
				ownerAgentId,
				runtimeAgentId: targetAgentId,
				runtimeMode: spawnMode === "session" ? "persistent" : "oneshot",
				backendId,
				resumeSessionId: params.resumeSessionId,
				runtimeOptions: runtimeOptionsResult.runtimeOptions,
				modelExplicit: runtimeOptionsResult.modelExplicit,
				thinkingExplicit: runtimeOptionsResult.thinkingExplicit,
				cwd: runtimeCwd
			}));
			closeRuntimeOnFailure = initializedSession.initialized.closeRuntimeOnFailure;
			ctx.assertActive?.();
			return {
				initializedSession,
				binding: preparedBinding ? await bindPreparedAcpThread({
					assertActive: ctx.assertActive,
					cfg,
					sessionKey,
					targetAgentId: ownerAgentId,
					label: params.label,
					preparedBinding,
					initializedRuntime: initializedSession
				}) : null
			};
		},
		async dispatchTurn(state) {
			state.deliveryPlan = resolveAcpSpawnBootstrapDeliveryPlan({
				cfg,
				spawnMode,
				effectiveStreamToParent,
				requester: requesterState,
				binding: state.binding
			});
			if (childCreationEntry) await recordSessionCreated(cfg, {
				sessionKey,
				agentId: ownerAgentId,
				entry: childCreationEntry
			});
			await recordSubagentSpawned({
				childSessionKey: sessionKey,
				childRunId: childIdem,
				requesterSessionKey: requesterInternalKey,
				agentId: ownerAgentId
			});
			const startParentRelay = (runId) => effectiveStreamToParent && parentSessionKey && parentEventRouting && parentEventTarget ? startAcpSpawnParentStreamRelay({
				runId,
				parentSessionKey,
				requesterAgentId,
				expectedTarget: parentEventTarget,
				childSessionKey: sessionKey,
				childSessionId: state.initializedSession.sessionId,
				agentId: targetAgentId,
				ownerAgentId,
				env: parentRelayStateEnv,
				eventRouting: parentEventRouting,
				deliveryContext: parentDeliveryCtx,
				cfg
			}) : void 0;
			state.parentRelay = startParentRelay(childIdem);
			const response = await launchAcpChildThroughGateway({
				assertDispatchCurrent: ctx.assertActive,
				task: params.task,
				sessionKey,
				deliveryPlan: state.deliveryPlan,
				childIdem,
				runTimeoutSeconds,
				label: params.label,
				attachments: gatewayAttachments,
				lineage: {
					enabled: isExecutionIdentityCollectionEnabled(cfg),
					backend: "acp",
					parentAgentId: requesterAgentId,
					requesterRef: requesterInternalKey,
					controllerRef: ownership.controllerSessionKey,
					depth: admission.childSessionPatch?.spawnDepth ?? 1,
					maxDepth: admission.maxSpawnDepth,
					targetAgentId: ownerAgentId,
					sandbox: params.sandbox === "require" ? "require" : "inherit",
					inheritedToolAllowlist: ctx.inheritedToolAllowlist,
					inheritedToolDenylist: ctx.inheritedToolDenylist
				},
				parentExecutionIdentityToken: readParentExecutionIdentity(ctx),
				participantStorePath: resolveSessionStorePathCore(cfg.session?.store, { agentId: ownerAgentId })
			});
			const runId = readGatewayRunId(response) ?? childIdem;
			if (state.parentRelay && runId !== childIdem) {
				state.parentRelay.dispose();
				state.parentRelay = startParentRelay(runId);
			}
			state.parentRelay?.notifyStarted();
			return { runId };
		},
		async cleanupOnFailure({ state }) {
			await state?.parentRelay?.dispose();
			await cleanupFailedAcpSpawn({
				cfg,
				sessionKey,
				agentId: ownerAgentId,
				sessionEntry: childCreationEntry,
				deleteTranscript: true,
				closeRuntimeOnFailure
			});
		}
	};
	const { controllerSessionKey } = ownership;
	ctx.assertActive?.();
	const admissionReservation = hasSubagentEnvelope ? reserveChildAdmissionSlot({
		controllerSessionKey,
		childSessionKey: sessionKey,
		resolveAdmission
	}) : void 0;
	if (admissionReservation && !admissionReservation.ok) return buildAcpSpawnError("subagent_policy", admissionReservation.error, "forbidden");
	ctx.onSpawnEffectsStart?.();
	let expectsCompletionMessage = false;
	const pipelineResult = await runSpawnPipeline({
		adapter,
		assertActive: ctx.assertActive,
		admissionReservation,
		hookRunner: getGlobalHookRunner(),
		progressOrigin,
		progressSessionKey: ownership.completionRequesterSessionKey,
		buildRegistration: (state, runId) => {
			expectsCompletionMessage = !(state.deliveryPlan?.useInlineDelivery === true) && params.expectsCompletionMessage !== false;
			return {
				runId,
				requesterTurnRunId: ctx.requesterTurnRunId,
				childSessionKey: sessionKey,
				controllerSessionKey,
				sessionEntry: state.initializedSession.sessionEntry,
				requesterSessionKey: ownership.completionRequesterSessionKey,
				completionRequesterSessionId,
				completionRequesterLifecycleRevision,
				requesterOrigin,
				progressOrigin,
				requesterDisplayKey: ownership.completionRequesterDisplayKey,
				task: params.task,
				taskName: params.taskName,
				agentId: ownerAgentId,
				requesterAgentId,
				cleanup: spawnMode === "session" ? "keep" : params.cleanup === "delete" ? "delete" : "keep",
				label: params.label,
				runTimeoutSeconds,
				expectsCompletionMessage,
				spawnMode
			};
		}
	});
	if (!pipelineResult.ok) return buildAcpSpawnFailureResult(pipelineResult, sessionKey);
	return {
		status: "accepted",
		childSessionKey: sessionKey,
		runId: pipelineResult.runId,
		mode: spawnMode,
		runTimeoutSeconds,
		expectsCompletionMessage,
		...pipelineResult.state.deliveryPlan?.useInlineDelivery ? { inlineDelivery: true } : {},
		note: spawnMode === "session" ? ACP_SPAWN_SESSION_ACCEPTED_NOTE : ACP_SPAWN_ACCEPTED_NOTE
	};
}
//#endregion
export { spawnAcpDirect };