UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

1,388 lines 62.8 kB
import { c as normalizeOptionalLowercaseString, l as normalizeOptionalString, o as normalizeLowercaseStringOrEmpty } from "./string-coerce-CIXf7egm.js";
import { r as truncateUtf16Safe } from "./utf16-slice-D_ngcYKd.js";
import { t as createLazyImportLoader } from "./lazy-promise-DGqyc4Y4.js";
import { m as resolveAgentWorkspaceDir, u as resolveAgentDir } from "./agent-scope-config-DcbEhP0R.js";
import { f as resolveAgentIdFromSessionKey } from "./session-key-BnWWjqNc.js";
import { t as formatErrorMessage } from "./errors-Db3Ymjlb.js";
import { f as isDiagnosticsEnabled } from "./diagnostic-events-Cwe92uV3.js";
import { a as generateSecureUuid } from "./secure-random-Ds4AFLgz.js";
import { d as toolPolicyRestrictsTools } from "./tool-policy-Wi0C45cX.js";
import { o as resolveSessionStorePathCore } from "./paths-CXdaYWF_.js";
import { _ as resolveSessionAgentId } from "./agent-scope-DbtJyKUL.js";
import { r as logVerbose } from "./globals-CTaGxEqj.js";
import { i as shouldCleanTtsDirectiveText, t as resolveConfiguredTtsMode } from "./tts-config-lx8wBLdc.js";
import { n as recordRuntimeActionDecision } from "./runtime-action-decision-C4JNkXkP.js";
import { d as isReplyPayloadStatusNotice, h as markReplyPayloadAsTtsSupplement, i as copyReplyPayloadMetadata, p as isReplyPayloadTtsSupplement } from "./reply-payload-DahKk-7n.js";
import { l as readChannelContextAdmissionEvidence } from "./admission-evidence-BW_2Woj4.js";
import { o as prepareChannelParticipantObservation } from "./ingress-drain-DcmwOHP5.js";
import { a as toAcpRuntimeError, n as AcpRuntimeError } from "./errors-CY1OFR4J.js";
import { t as formatAcpRuntimeErrorText } from "./error-text-BiCLPjnD.js";
import { o as isSessionIdentityPending, u as resolveSessionIdentityFromMeta } from "./session-identity-CY5KdnMP.js";
import { i as resolveAcpThreadSessionDetailLines } from "./session-identifiers-B5CDFQVW.js";
import "./errors-Bnilofa2.js";
import { n as classifySessionStateActor } from "./session-state-events-DNCKmH78.js";
import { i as buildAgentRunTerminalOutcomeFromLifecycleEvent } from "./agent-run-terminal-outcome-CigeY75d.js";
import { n as createVerifiedConversationContextStreamFilter } from "./user-facing-text-0KenW-TQ.js";
import { t as QuestionAnswerUnconfirmedError } from "./gateway-question-dispatch-BkYi3BfY.js";
import { f as markDiagnosticSessionProgress } from "./diagnostic-ryAZMr5N.js";
import { a as prepareAgentRunAdmission, i as getAdmittedRunDelegatedAuthority, r as createOperationalRunInstanceRef, t as closeAdmittedRunDelegatedAuthority } from "./admitted-run-context-CHY5SdVF.js";
import { r as normalizeReplyPayloadOutcome } from "./normalize-reply-C1iRjnEc.js";
import { o as prepareReplyPayloadForDispatcher, r as captureReplyDispatchDeliveryOutcome, s as waitForReplyDispatcherIdle, t as attachReplyDispatchUndeliveredFallback } from "./reply-dispatcher-BjdCLv_G.js";
import { a as hasOutboundReplyContent } from "./reply-payload-Bfsi665c.js";
import { a as resolveSendableOutboundReplyParts } from "./reply-payload-parts-BsFA4sDv.js";
import { a as resolveReplyDeliveryAccountId, o as resolveReplyToMode, t as createReplyDeliveryContext } from "./reply-threading-b8mBrrsZ.js";
import { a as isCaptionedFinalTextPayload, i as cleanDeferredFinalText, n as needsTtsFallback, o as mergeDeferredFinalText, r as buildCaptionedFinalTextFallback, s as shouldDeferFinalTtsText } from "./dispatch-from-config.finalize-DcagE2ge.js";
import { t as createTtsDirectiveTextStreamCleaner } from "./directives-EQrayC82.js";
import { t as resolveStatusTtsSnapshot } from "./status-config-wwgdsC-4.js";
import { m as createBlockReplyPipeline } from "./dispatch-from-config.payloads-D95ct5gu.js";
import { n as claimPendingAgentQuestionAnswer } from "./gateway-question-DoL5eagC.js";
import { n as formatToolSummary, o as resolveToolDisplay } from "./tool-display-CzOArp4x.js";
import { n as truncateUtf16WithEllipsis } from "./text-truncate-DL21A82B.js";
import { n as resolveRoutedDeliveryThreadId } from "./routed-delivery-thread-D8RIfcVU.js";
import { n as resolveAcpAgentPolicyError, r as resolveAcpDispatchPolicyError } from "./policy-Dc3ayPTK.js";
import { t as EmbeddedBlockChunker } from "./embedded-agent-block-chunker-C_UhVcqR.js";
import { r as createChannelReplyTransform } from "./reply-transform-C8Hh1eV1.js";
import { r as prefixSystemMessage } from "./system-message-Dltw0_t9.js";
import { r as hasInboundMediaForUnderstanding } from "./inbound-media-Bq3xonSo.js";
import { t as stripExtractedFileImageMetadata } from "./extracted-file-images-CdmNdoIK.js";
import { a as appendRecentHistoryImageContext, i as resolveInlineAgentImageAttachments, n as loadAgentTurnMediaRuntime, r as resolveAgentTurnAttachments, t as collectDescribedImageAttachmentIndexes } from "./agent-turn-attachments-DPqIEbM0.js";
import { t as recordAcceptedSessionParticipantInput } from "./session-participant-input-recording-5o8R5HOJ.js";
import { t as createLazyAcpElicitationHandler } from "./acp-elicitation-handler-lazy-CEkk3Bpp.js";
import { t as resolveAcpToolTerminalOutcome } from "./tool-status-CyYVXMyT.js";
import { n as resolveAcpProjectionSettings, r as resolveAcpStreamingConfig, t as isAcpTagVisible } from "./acp-stream-settings-PvJ2p8YE.js";
import { t as consumeChannelRunAdmission } from "./channel-run-admission-CUuWSD0O.js";
//#region src/auto-reply/reply/acp-projector.ts
const ACP_BLOCK_REPLY_TIMEOUT_MS = 15e3;
const ACP_LIVE_IDLE_FLUSH_FLOOR_MS = 750;
const ACP_LIVE_IDLE_MIN_CHARS = 80;
const ACP_LIVE_SOFT_FLUSH_CHARS = 220;
const ACP_LIVE_HARD_FLUSH_CHARS = 480;
const HIDDEN_BOUNDARY_TAGS = /* @__PURE__ */ new Set(["tool_call", "tool_call_update"]);
function hashText(text) {
	return text.trim();
}
function normalizeToolStatus(status) {
	return normalizeOptionalLowercaseString(status) || void 0;
}
function resolveHiddenBoundarySeparatorText(mode) {
	if (mode === "space") return " ";
	if (mode === "newline") return "\n";
	if (mode === "paragraph") return "\n\n";
	return "";
}
function shouldInsertSeparator(params) {
	if (!params.separator) return false;
	if (!params.nextText) return false;
	const firstChar = params.nextText[0];
	if (typeof firstChar === "string" && /\s/.test(firstChar)) return false;
	const tail = params.previousTail ?? "";
	if (!tail) return false;
	if (params.separator === " " && /\s$/.test(tail)) return false;
	if ((params.separator === "\n" || params.separator === "\n\n") && tail.endsWith("\n")) return false;
	return true;
}
function shouldFlushLiveBufferOnBoundary(text) {
	if (!text) return false;
	if (text.length >= ACP_LIVE_HARD_FLUSH_CHARS) return true;
	if (text.endsWith("\n\n")) return true;
	if (/[.!?][)"'`]*\s$/.test(text)) return true;
	if (text.length >= ACP_LIVE_SOFT_FLUSH_CHARS && /\s$/.test(text)) return true;
	return false;
}
function shouldFlushLiveBufferOnIdle(text) {
	if (!text) return false;
	if (text.length >= ACP_LIVE_IDLE_MIN_CHARS) return true;
	if (/[.!?][)"'`]*$/.test(text.trimEnd())) return true;
	if (text.includes("\n")) return true;
	return false;
}
function renderToolSummaryText(event, shouldSendFullToolDetails) {
	const detailParts = [];
	const commandBearing = normalizeOptionalLowercaseString(event.kind) === "execute";
	const title = shouldSendFullToolDetails || !commandBearing ? normalizeOptionalString(event.title) : void 0;
	if (title) detailParts.push(title);
	const status = normalizeOptionalString(event.status);
	if (status) detailParts.push(`status=${status}`);
	const fallback = shouldSendFullToolDetails || !commandBearing ? normalizeOptionalString(event.text) : void 0;
	if (detailParts.length === 0 && fallback) detailParts.push(fallback);
	const display = resolveToolDisplay({
		name: "tool_call",
		meta: detailParts.join(" · ") || "tool call"
	});
	return formatToolSummary(display);
}
function createAcpReplyProjector(params) {
	const settings = resolveAcpProjectionSettings(params.cfg);
	const streaming = resolveAcpStreamingConfig({
		cfg: params.cfg,
		provider: params.provider,
		accountId: params.accountId,
		deliveryMode: settings.deliveryMode
	});
	const blockReplyPipeline = createBlockReplyPipeline({
		onBlockReply: async (payload) => {
			await params.deliver("block", payload);
		},
		timeoutMs: ACP_BLOCK_REPLY_TIMEOUT_MS,
		coalescing: settings.deliveryMode === "live" ? void 0 : streaming.coalescing
	});
	const chunker = new EmbeddedBlockChunker(streaming.chunking);
	const filterConversationContext = createVerifiedConversationContextStreamFilter(params.getConversationContext);
	const liveIdleFlushMs = Math.max(streaming.coalescing.idleMs, ACP_LIVE_IDLE_FLUSH_FLOOR_MS);
	let emittedOutputChars = 0;
	let truncationNoticeEmitted = false;
	let lastStatusHash;
	let lastToolHash;
	let lastUsageTuple;
	let lastVisibleOutputTail;
	let pendingHiddenBoundary = false;
	let liveBufferText = "";
	let finalOnlyOutputText = "";
	let liveIdleTimer;
	const pendingToolDeliveries = [];
	const toolLifecycleById = /* @__PURE__ */ new Map();
	const shouldSendToolSummaries = () => params.shouldSendToolSummariesNow?.() ?? params.shouldSendToolSummaries;
	const clearLiveIdleTimer = () => {
		if (!liveIdleTimer) return;
		clearTimeout(liveIdleTimer);
		liveIdleTimer = void 0;
	};
	const drainChunker = (force) => {
		if (settings.deliveryMode === "final_only" && !force) return;
		chunker.drain({
			force,
			emit: (chunk) => {
				blockReplyPipeline.enqueue({ text: chunk });
			}
		});
	};
	const flushLiveBuffer = (opts) => {
		if (settings.deliveryMode !== "live") return;
		if (!liveBufferText) return;
		if (opts?.idle && !shouldFlushLiveBufferOnIdle(liveBufferText)) return;
		const text = liveBufferText;
		liveBufferText = "";
		chunker.append(text);
		drainChunker(opts?.force === true);
	};
	const scheduleLiveIdleFlush = () => {
		if (settings.deliveryMode !== "live") return;
		if (liveIdleFlushMs <= 0 || !liveBufferText) return;
		clearLiveIdleTimer();
		liveIdleTimer = setTimeout(() => {
			flushLiveBuffer({
				force: true,
				idle: true
			});
			if (liveBufferText) scheduleLiveIdleFlush();
		}, liveIdleFlushMs);
	};
	const flushBufferedToolDeliveries = async (force) => {
		if (!(settings.deliveryMode === "final_only" && force)) return;
		if (!shouldSendToolSummaries()) {
			pendingToolDeliveries.length = 0;
			return;
		}
		for (const entry of pendingToolDeliveries.splice(0)) await params.deliver("tool", entry.payload, entry.meta);
	};
	const flush = async (force = false) => {
		if (settings.deliveryMode === "live") {
			clearLiveIdleTimer();
			flushLiveBuffer({ force: true });
		}
		await flushBufferedToolDeliveries(force);
		if (settings.deliveryMode === "final_only") {
			if (force && finalOnlyOutputText.trim().length > 0) {
				const text = finalOnlyOutputText;
				finalOnlyOutputText = "";
				await params.deliver("final", { text });
			}
		} else drainChunker(force);
		await blockReplyPipeline.flush({ force });
	};
	const emitSystemStatus = async (text, meta, opts) => {
		if (!shouldSendToolSummaries()) return;
		const bounded = truncateUtf16WithEllipsis(text.trim(), settings.maxSessionUpdateChars);
		if (!bounded) return;
		const formatted = prefixSystemMessage(bounded);
		const hash = hashText(formatted);
		if (settings.repeatSuppression && opts?.dedupe !== false && lastStatusHash === hash) return;
		if (settings.deliveryMode === "final_only") pendingToolDeliveries.push({
			payload: { text: formatted },
			meta
		});
		else {
			await flush(true);
			await params.deliver("tool", { text: formatted }, meta);
		}
		lastStatusHash = hash;
	};
	const markHiddenToolBoundary = (event) => {
		if (!event.tag || !HIDDEN_BOUNDARY_TAGS.has(event.tag)) return;
		const status = normalizeToolStatus(event.status);
		const isTerminal = resolveAcpToolTerminalOutcome(status) !== void 0;
		pendingHiddenBoundary = pendingHiddenBoundary || event.tag === "tool_call" || isTerminal;
	};
	const emitToolSummary = async (event) => {
		if (!shouldSendToolSummaries()) {
			markHiddenToolBoundary(event);
			return;
		}
		if (!isAcpTagVisible(settings, event.tag)) return;
		const renderedToolSummary = renderToolSummaryText(event, params.shouldSendFullToolDetails);
		const toolSummary = truncateUtf16WithEllipsis(renderedToolSummary, settings.maxSessionUpdateChars);
		const hash = hashText(renderedToolSummary);
		const toolCallId = normalizeOptionalString(event.toolCallId);
		const status = normalizeToolStatus(event.status);
		const isTerminal = resolveAcpToolTerminalOutcome(status) !== void 0;
		const isStart = status === "in_progress" || event.tag === "tool_call";
		if (settings.repeatSuppression) {
			if (toolCallId) {
				const state = toolLifecycleById.get(toolCallId) ?? {
					started: false,
					terminal: false
				};
				if (isTerminal && state.terminal) return;
				if (isStart && state.started) return;
				if (state.lastRenderedHash === hash) return;
				if (isStart) state.started = true;
				if (isTerminal) state.terminal = true;
				state.lastRenderedHash = hash;
				toolLifecycleById.set(toolCallId, state);
			} else if (lastToolHash === hash) return;
		}
		const deliveryMeta = {
			...event.tag ? { tag: event.tag } : {},
			...toolCallId ? { toolCallId } : {},
			...status ? { toolStatus: status } : {},
			allowEdit: Boolean(toolCallId && event.tag === "tool_call_update")
		};
		if (settings.deliveryMode === "final_only") {
			pendingToolDeliveries.push({
				payload: { text: toolSummary },
				meta: deliveryMeta
			});
			markHiddenToolBoundary(event);
		} else {
			await flush(true);
			await params.deliver("tool", { text: toolSummary }, deliveryMeta);
		}
		lastToolHash = hash;
	};
	const emitTruncationNotice = async () => {
		if (truncationNoticeEmitted) return;
		truncationNoticeEmitted = true;
		await emitSystemStatus("output truncated", { tag: "session_info_update" }, { dedupe: false });
	};
	const onEvent = async (event) => {
		params.onProgress?.();
		if (event.type === "text_delta") {
			if (event.stream && event.stream !== "output") return;
			if (!isAcpTagVisible(settings, event.tag)) return;
			let text = event.text;
			if (!text) return;
			if (pendingHiddenBoundary && shouldInsertSeparator({
				separator: resolveHiddenBoundarySeparatorText(settings.hiddenBoundarySeparator),
				previousTail: lastVisibleOutputTail,
				nextText: text
			})) text = `${resolveHiddenBoundarySeparatorText(settings.hiddenBoundarySeparator)}${text}`;
			pendingHiddenBoundary = false;
			if (emittedOutputChars >= settings.maxOutputChars) {
				await emitTruncationNotice();
				return;
			}
			const remaining = settings.maxOutputChars - emittedOutputChars;
			const accepted = remaining < text.length ? truncateUtf16Safe(text, remaining) : text;
			if (accepted.length > 0) {
				emittedOutputChars += accepted.length;
				const safeText = filterConversationContext(accepted);
				lastVisibleOutputTail = safeText.slice(-1) || lastVisibleOutputTail;
				if (settings.deliveryMode === "live") {
					liveBufferText += safeText;
					if (shouldFlushLiveBufferOnBoundary(liveBufferText)) {
						clearLiveIdleTimer();
						flushLiveBuffer({ force: true });
					} else scheduleLiveIdleFlush();
				} else finalOnlyOutputText += safeText;
			}
			if (accepted.length < text.length) {
				emittedOutputChars = settings.maxOutputChars;
				await emitTruncationNotice();
			}
			return;
		}
		if (event.type === "status") {
			if (!isAcpTagVisible(settings, event.tag)) return;
			if (event.tag === "usage_update" && settings.repeatSuppression) {
				const usageTuple = typeof event.used === "number" && typeof event.size === "number" ? `${event.used}/${event.size}` : hashText(event.text);
				if (usageTuple === lastUsageTuple) return;
				lastUsageTuple = usageTuple;
			}
			await emitSystemStatus(event.text, event.tag ? { tag: event.tag } : void 0, { dedupe: true });
			return;
		}
		if (event.type === "tool_call") {
			if (!isAcpTagVisible(settings, event.tag)) {
				markHiddenToolBoundary(event);
				return;
			}
			await emitToolSummary(event);
		}
	};
	return {
		onEvent,
		flush
	};
}
//#endregion
//#region src/auto-reply/reply/dispatch-acp-payload.ts
function prepareAcpDeliveryPayload(params) {
	if (!params.routed) return prepareReplyPayloadForDispatcher(params.dispatcher, params.kind, params.payload);
	return normalizeReplyPayloadOutcome(params.payload, { transformReplyPayload: createChannelReplyTransform({
		messaging: params.messaging,
		cfg: params.cfg,
		accountId: params.accountId
	}) });
}
//#endregion
//#region src/auto-reply/reply/dispatch-acp-delivery.ts
const routeReplyRuntimeLoader = createLazyImportLoader(() => import("./route-reply.runtime.js"));
const dispatchAcpTtsRuntimeLoader$1 = createLazyImportLoader(() => import("./tts.runtime.js"));
const channelPluginRuntimeLoader = createLazyImportLoader(() => import("./plugins-D2edCor-.js"));
const messageActionRuntimeLoader = createLazyImportLoader(() => import("./message-action-runner-CAny-Fw5.js"));
function loadRouteReplyRuntime() {
	return routeReplyRuntimeLoader.load();
}
function loadDispatchAcpTtsRuntime$1() {
	return dispatchAcpTtsRuntimeLoader$1.load();
}
function loadChannelPluginRuntime() {
	return channelPluginRuntimeLoader.load();
}
function loadMessageActionRuntime() {
	return messageActionRuntimeLoader.load();
}
async function shouldTreatDeliveredTextAsVisible(params) {
	if (!normalizeOptionalString(params.text)) return false;
	if (params.kind === "final") return true;
	const channelId = normalizeOptionalLowercaseString(params.channel);
	if (!channelId) return false;
	const { getChannelPlugin } = await loadChannelPluginRuntime();
	const outbound = getChannelPlugin(channelId)?.outbound;
	const visibilityOverride = outbound?.shouldTreatDeliveredTextAsVisible ?? outbound?.shouldTreatRoutedTextAsVisible;
	if (visibilityOverride) return visibilityOverride({
		kind: params.kind,
		text: params.text
	});
	return false;
}
async function maybeApplyAcpTts(params) {
	if (params.skipTts) return params.payload;
	if (isReplyPayloadStatusNotice(params.payload)) return params.payload;
	const ttsStatus = resolveStatusTtsSnapshot({
		cfg: params.cfg,
		sessionAuto: params.ttsAuto,
		agentId: params.agentId,
		channelId: params.channel,
		accountId: params.accountId
	});
	if (!ttsStatus) return params.payload;
	if (ttsStatus.autoMode === "inbound" && !params.inboundAudio) return params.payload;
	if (params.kind !== "final" && resolveConfiguredTtsMode(params.cfg, {
		agentId: params.agentId,
		channelId: params.channel,
		accountId: params.accountId
	}) === "final") return params.payload;
	const { maybeApplyTtsToPayload } = await loadDispatchAcpTtsRuntime$1();
	const applied = await maybeApplyTtsToPayload({
		payload: params.payload,
		cfg: params.cfg,
		channel: params.channel,
		kind: params.kind,
		inboundAudio: params.inboundAudio,
		ttsAuto: params.ttsAuto,
		agentId: params.agentId,
		accountId: params.accountId
	});
	return copyReplyPayloadMetadata(params.payload, applied);
}
function createAcpDispatchDeliveryCoordinator(params) {
	const directChannel = normalizeOptionalLowercaseString(params.ctx.Provider ?? params.ctx.Surface);
	const routedChannel = normalizeOptionalLowercaseString(params.originatingChannel);
	const deliverySessionKey = normalizeOptionalString(params.sessionKey) ?? params.ctx.SessionKey;
	const explicitAccountId = normalizeOptionalString(params.originatingAccountId) ?? normalizeOptionalString(params.ctx.AccountId);
	const resolvedAccountId = resolveReplyDeliveryAccountId(params.cfg, routedChannel ?? directChannel, explicitAccountId);
	const routedReplyDelivery = params.originatingChannel ? createReplyDeliveryContext(resolveReplyToMode(params.cfg, params.originatingChannel, resolvedAccountId, params.originatingChatType ?? params.ctx.ChatType), params.originatingChatType ?? params.ctx.ChatType) : void 0;
	const state = {
		startedReplyLifecycle: false,
		accumulatedBlockText: "",
		accumulatedDeliveredBlockText: "",
		accumulatedVisibleBlockText: "",
		accumulatedBlockTtsText: "",
		accumulatedFinalText: "",
		accumulatedDeliveredFinalText: "",
		pendingTranscriptOutcomes: [],
		cleanBlockTtsDirectiveText: shouldCleanTtsDirectiveText({
			cfg: params.cfg,
			ttsAuto: params.sessionTtsAuto,
			agentId: params.agentId,
			channelId: params.ttsChannel,
			accountId: resolvedAccountId
		}) ? createTtsDirectiveTextStreamCleaner() : void 0,
		blockCount: 0,
		deliveredFinalReply: false,
		deliveredAnswerFinalToUser: false,
		deliveredFinalTtsMedia: false,
		deliveredVisibleText: false,
		failedVisibleTextDelivery: false,
		queuedDirectVisibleTextDeliveries: 0,
		settledDirectVisibleText: false,
		routedCounts: {
			tool: 0,
			block: 0,
			final: 0
		},
		suppressionReason: void 0,
		toolMessageByCallId: /* @__PURE__ */ new Map()
	};
	let hasPendingDirectBlockReplyDelivery = false;
	const appendDeliveredTranscriptText = (kind, blockText, finalText) => {
		if (kind === "block" && blockText) state.accumulatedDeliveredBlockText = state.accumulatedDeliveredBlockText ? `${state.accumulatedDeliveredBlockText}\n${blockText}` : blockText;
		if (kind === "final" && finalText) state.accumulatedDeliveredFinalText = state.accumulatedDeliveredFinalText ? `${state.accumulatedDeliveredFinalText}\n${finalText}` : finalText;
	};
	const waitForPendingDirectBlockReplyDelivery = async () => {
		if (!hasPendingDirectBlockReplyDelivery) return;
		hasPendingDirectBlockReplyDelivery = false;
		await waitForReplyDispatcherIdle(params.dispatcher, params.abortSignal);
	};
	const settleDirectVisibleText = async () => {
		if (state.settledDirectVisibleText || state.queuedDirectVisibleTextDeliveries === 0) return;
		state.settledDirectVisibleText = true;
		hasPendingDirectBlockReplyDelivery = false;
		const receipt = await params.dispatcher.waitForIdle();
		if (!receipt) return;
		const visibleCounts = [receipt.counts.block, receipt.counts.final];
		state.failedVisibleTextDelivery ||= visibleCounts.some((counts) => counts.failedBeforeSend + counts.failedAfterSend > 0);
		state.deliveredVisibleText ||= visibleCounts.some((counts) => counts.delivered + counts.failedAfterSend > 0);
	};
	const startReplyLifecycleOnce = async () => {
		if (state.startedReplyLifecycle) return;
		state.startedReplyLifecycle = true;
		if (params.suppressReplyLifecycle) return;
		Promise.resolve(params.onReplyStart?.()).catch((error) => {
			logVerbose(`dispatch-acp: reply lifecycle start failed: ${error instanceof Error ? error.message : String(error)}`);
		});
	};
	const tryEditToolMessage = async (payload, toolCallId) => {
		if (!params.shouldRouteToOriginating || !params.originatingChannel || !params.originatingTo) return false;
		const handle = state.toolMessageByCallId.get(toolCallId);
		if (!handle?.messageId) return false;
		const message = normalizeOptionalString(payload.text);
		if (!message) return false;
		try {
			const { runMessageAction } = await loadMessageActionRuntime();
			await runMessageAction({
				cfg: params.cfg,
				action: "edit",
				params: {
					channel: handle.channel,
					to: handle.to,
					threadId: handle.threadId,
					messageId: handle.messageId,
					message
				},
				defaultAccountId: handle.accountId,
				sessionKey: params.ctx.SessionKey,
				requesterAccountId: params.ctx.AccountId
			});
			state.routedCounts.tool += 1;
			return true;
		} catch (error) {
			logVerbose(`dispatch-acp: tool message edit failed for ${toolCallId}: ${formatErrorMessage(error)}`);
			return false;
		}
	};
	const deliver = async (kind, payload, meta) => {
		let visiblePayload = payload;
		if (!params.suppressUserDelivery) {
			const routed = params.shouldRouteToOriginating && routedChannel !== void 0;
			const messaging = routed ? (await loadChannelPluginRuntime()).getChannelPlugin(routedChannel)?.messaging : void 0;
			const prepared = prepareAcpDeliveryPayload({
				cfg: params.cfg,
				dispatcher: params.dispatcher,
				kind,
				payload,
				routed,
				...messaging ? { messaging } : {},
				accountId: resolvedAccountId
			});
			if (prepared.kind === "suppress") {
				if (prepared.reason === "channel_transform") state.suppressionReason = prepared.reason;
				return false;
			}
			visiblePayload = prepared.payload;
		}
		const isStatusNotice = isReplyPayloadStatusNotice(visiblePayload);
		const rawBlockPayloadText = kind === "block" ? normalizeOptionalString(visiblePayload.text) : void 0;
		const rawBlockText = isStatusNotice ? void 0 : rawBlockPayloadText;
		if (rawBlockPayloadText) {
			const joinsBufferedTtsDirective = state.cleanBlockTtsDirectiveText?.hasBufferedDirectiveText() === true;
			if (rawBlockText) {
				if (state.accumulatedBlockText.length > 0) state.accumulatedBlockText += "\n";
				state.accumulatedBlockText += rawBlockText;
				if (state.accumulatedBlockTtsText.length > 0 && !joinsBufferedTtsDirective) state.accumulatedBlockTtsText += "\n";
				state.accumulatedBlockTtsText += rawBlockText;
				state.blockCount += 1;
			}
			if (state.cleanBlockTtsDirectiveText && rawBlockText) {
				const text = state.cleanBlockTtsDirectiveText.push(rawBlockPayloadText);
				visiblePayload = copyReplyPayloadMetadata(visiblePayload, {
					...visiblePayload,
					text: text.trim() ? text : void 0
				});
			}
			if (visiblePayload.text) {
				if (state.accumulatedVisibleBlockText.length > 0) state.accumulatedVisibleBlockText += "\n";
				state.accumulatedVisibleBlockText += visiblePayload.text;
			}
		}
		const rawFinalText = kind === "final" && !isStatusNotice ? normalizeOptionalString(visiblePayload.text) : void 0;
		if (rawFinalText) {
			if (state.accumulatedFinalText.length > 0) state.accumulatedFinalText += "\n";
			state.accumulatedFinalText += rawFinalText;
		}
		if (hasOutboundReplyContent(visiblePayload, { trimText: true })) await startReplyLifecycleOnce();
		else return false;
		if (params.suppressUserDelivery) return false;
		if (kind === "block" && params.suppressBlockUserDelivery && !isStatusNotice && !visiblePayload.isReasoning && !visiblePayload.isCommentary) {
			if (!Boolean(visiblePayload.mediaUrl || visiblePayload.mediaUrls?.length || visiblePayload.presentation || visiblePayload.interactive || visiblePayload.channelData)) return false;
			visiblePayload = copyReplyPayloadMetadata(visiblePayload, {
				...visiblePayload,
				text: void 0
			});
		}
		const appliedTtsPayload = await maybeApplyAcpTts({
			payload: visiblePayload,
			cfg: params.cfg,
			agentId: params.agentId,
			channel: params.ttsChannel,
			accountId: resolvedAccountId,
			kind,
			inboundAudio: params.inboundAudio,
			ttsAuto: params.sessionTtsAuto,
			skipTts: meta?.skipTts
		});
		const finalVisibleTextSource = kind === "final" && params.suppressBlockUserDelivery && state.cleanBlockTtsDirectiveText ? meta?.skipTts || visiblePayload.isError || isReplyPayloadTtsSupplement(visiblePayload) ? visiblePayload.text : mergeDeferredFinalText(state.accumulatedBlockTtsText, visiblePayload.text) : void 0;
		const ttsPayload = finalVisibleTextSource !== void 0 ? copyReplyPayloadMetadata(appliedTtsPayload, {
			...appliedTtsPayload,
			text: cleanDeferredFinalText(finalVisibleTextSource) || void 0
		}) : appliedTtsPayload;
		const hasFinalTtsMedia = kind === "final" && resolveSendableOutboundReplyParts(ttsPayload).hasMedia && isReplyPayloadTtsSupplement(ttsPayload);
		const isAnswerBearingFinal = kind === "final" && isCaptionedFinalTextPayload(visiblePayload);
		if (params.shouldRouteToOriginating && params.originatingChannel && params.originatingTo) {
			const toolCallId = normalizeOptionalString(meta?.toolCallId);
			if (kind === "tool" && meta?.allowEdit === true && toolCallId) {
				if (await tryEditToolMessage(ttsPayload, toolCallId)) return true;
			}
			const tracksVisibleText = await shouldTreatDeliveredTextAsVisible({
				channel: routedChannel,
				kind,
				text: ttsPayload.text,
				routed: true
			});
			const { routeReply } = await loadRouteReplyRuntime();
			const threadId = params.originatingThreadId ?? resolveRoutedDeliveryThreadId({
				ctx: params.ctx,
				sessionKey: deliverySessionKey
			});
			const result = await routeReply({
				payload: ttsPayload,
				channel: params.originatingChannel,
				to: params.originatingTo,
				agentId: params.agentId,
				sessionKey: deliverySessionKey,
				...deliverySessionKey !== params.ctx.SessionKey ? { policySessionKey: params.ctx.SessionKey } : {},
				accountId: resolvedAccountId,
				requesterSenderId: params.ctx.SenderId,
				requesterSenderName: params.ctx.SenderName,
				requesterSenderUsername: params.ctx.SenderUsername,
				requesterSenderE164: params.ctx.SenderE164,
				threadId,
				replyDelivery: routedReplyDelivery,
				cfg: params.cfg,
				abortSignal: params.abortSignal,
				mirror: false,
				replyKind: kind,
				runId: params.runId
			});
			if (!result.delivered && hasFinalTtsMedia && ttsPayload.text?.trim()) {
				if (!result.suppressed) logVerbose(`dispatch-acp: route-reply (acp/${kind}) failed: ${result.error ?? "unknown error"}`);
				return await deliver("final", { text: ttsPayload.text }, { skipTts: true });
			}
			if (!result.delivered && !result.suppressed) {
				if (tracksVisibleText) state.failedVisibleTextDelivery = true;
				logVerbose(`dispatch-acp: route-reply (acp/${kind}) failed: ${result.error ?? "unknown error"}`);
				return false;
			}
			if (result.suppressed) {
				if (kind === "final") state.deliveredFinalReply = true;
				if (tracksVisibleText) state.deliveredVisibleText = true;
				return true;
			}
			if (!result.ok) logVerbose(`dispatch-acp: route-reply (acp/${kind}) partially failed after delivery: ${result.error ?? "unknown error"}`);
			if (kind === "tool" && meta?.toolCallId && result.messageId) state.toolMessageByCallId.set(meta.toolCallId, {
				channel: params.originatingChannel,
				accountId: resolvedAccountId,
				to: params.originatingTo,
				...threadId != null ? { threadId } : {},
				messageId: result.messageId
			});
			appendDeliveredTranscriptText(kind, rawBlockText, rawFinalText);
			if (kind === "final") {
				state.deliveredFinalReply = true;
				if (isAnswerBearingFinal) {
					state.deliveredAnswerFinalToUser = true;
					state.deliveredFinalTtsMedia = hasFinalTtsMedia;
				}
			}
			if (tracksVisibleText) state.deliveredVisibleText = true;
			state.routedCounts[kind] += 1;
			return true;
		}
		if (kind === "tool") await waitForPendingDirectBlockReplyDelivery();
		const tracksVisibleText = await shouldTreatDeliveredTextAsVisible({
			channel: directChannel,
			kind,
			text: ttsPayload.text,
			routed: false
		});
		const transcriptOutcome = rawBlockText || rawFinalText || hasFinalTtsMedia ? captureReplyDispatchDeliveryOutcome(ttsPayload) : void 0;
		if (hasFinalTtsMedia && ttsPayload.text?.trim()) attachReplyDispatchUndeliveredFallback(ttsPayload, buildCaptionedFinalTextFallback(ttsPayload));
		const delivered = kind === "tool" ? params.dispatcher.sendToolResult(ttsPayload) : kind === "block" ? params.dispatcher.sendBlockReply(ttsPayload) : params.dispatcher.sendFinalReply(ttsPayload);
		if (delivered && transcriptOutcome) {
			if (transcriptOutcome.isTracked()) state.pendingTranscriptOutcomes.push(transcriptOutcome.promise.then((outcome) => {
				if (outcome === "delivered") appendDeliveredTranscriptText(kind, rawBlockText, rawFinalText);
			}));
		}
		if (kind === "final" && delivered) {
			state.deliveredFinalReply = true;
			if (isAnswerBearingFinal) {
				state.deliveredAnswerFinalToUser = true;
				state.deliveredFinalTtsMedia = hasFinalTtsMedia;
			}
		}
		if (delivered && tracksVisibleText) {
			state.queuedDirectVisibleTextDeliveries += 1;
			state.settledDirectVisibleText = false;
		} else if (!delivered && tracksVisibleText) state.failedVisibleTextDelivery = true;
		if (kind === "block" && delivered) hasPendingDirectBlockReplyDelivery = true;
		return delivered;
	};
	return {
		startReplyLifecycle: startReplyLifecycleOnce,
		deliver,
		getBlockCount: () => state.blockCount,
		getAccumulatedBlockText: () => state.accumulatedBlockText,
		getAccumulatedVisibleBlockText: () => state.accumulatedVisibleBlockText,
		getAccumulatedBlockTtsText: () => state.accumulatedBlockTtsText,
		getAccumulatedFinalText: () => state.accumulatedFinalText,
		getAccumulatedTranscriptText: () => state.accumulatedFinalText || state.accumulatedBlockText,
		resolveAccumulatedDeliveredTranscriptText: async () => {
			await Promise.all(state.pendingTranscriptOutcomes.splice(0));
			return state.accumulatedDeliveredFinalText || state.accumulatedDeliveredBlockText;
		},
		settleVisibleText: settleDirectVisibleText,
		hasDeliveredFinalReply: () => state.deliveredFinalReply,
		hasDeliveredAnswerFinalToUser: () => state.deliveredAnswerFinalToUser,
		hasDeliveredFinalTtsMedia: () => state.deliveredFinalTtsMedia,
		hasDeliveredVisibleText: () => state.deliveredVisibleText,
		hasFailedVisibleTextDelivery: () => state.failedVisibleTextDelivery,
		getDeliverySuppressionReason: () => state.suppressionReason,
		getRoutedCounts: () => ({ ...state.routedCounts }),
		applyRoutedCounts: (counts) => {
			counts.tool += state.routedCounts.tool;
			counts.block += state.routedCounts.block;
			counts.final += state.routedCounts.final;
		}
	};
}
//#endregion
//#region src/auto-reply/reply/dispatch-acp.ts
const dispatchAcpManagerRuntimeLoader = createLazyImportLoader(() => import("./dispatch-acp-manager.runtime.js"));
const dispatchAcpAuditRuntimeLoader = createLazyImportLoader(() => import("./attempt-execution.runtime.js"));
function appendOrderedAcpAttachments(params) {
	for (const [index, attachment] of params.attachments.entries()) params.entries.push({
		attachment,
		sourceIndex: params.sourceIndexes?.[index],
		sequence: params.entries.length
	});
}
function resolveMergedAcpAttachments(entries) {
	return entries.toSorted((left, right) => {
		if (left.sourceIndex !== void 0 && right.sourceIndex !== void 0) return left.sourceIndex - right.sourceIndex || left.sequence - right.sequence;
		return left.sequence - right.sequence;
	}).map((entry) => entry.attachment);
}
const dispatchAcpTtsRuntimeLoader = createLazyImportLoader(() => import("./tts.runtime.js"));
const dispatchAcpTranscriptRuntimeLoader = createLazyImportLoader(() => import("./dispatch-acp-transcript.runtime.js"));
function loadDispatchAcpManagerRuntime() {
	return dispatchAcpManagerRuntimeLoader.load();
}
function loadDispatchAcpAuditRuntime() {
	return dispatchAcpAuditRuntimeLoader.load();
}
function loadDispatchAcpTtsRuntime() {
	return dispatchAcpTtsRuntimeLoader.load();
}
function loadDispatchAcpTranscriptRuntime() {
	return dispatchAcpTranscriptRuntimeLoader.load();
}
function resolveAcpPromptText(ctx) {
	return ctx.agentText.trim();
}
function resolveAcpRequestId(ctx) {
	const id = ctx.MessageSidFull ?? ctx.MessageSid ?? ctx.MessageSidFirst ?? ctx.MessageSidLast;
	if (typeof id === "string") {
		const normalizedId = normalizeOptionalString(id);
		if (normalizedId) return normalizedId;
	}
	if (typeof id === "number" || typeof id === "bigint") return String(id);
	return generateSecureUuid();
}
function resolveAcpTurnText(params) {
	if (params.sourceReplyDeliveryMode !== "message_tool_only") return params.promptText;
	const guidance = prefixSystemMessage([
		"Source channel delivery is private by default for this turn.",
		"Normal ACP final output will not be automatically posted to the source channel.",
		"To send visible output, use message(action=send). The target defaults to the current source channel."
	].join(" "));
	return params.promptText ? `${guidance}\n\n${params.promptText}` : guidance;
}
function isRestrictiveRuntimeToolsAllow(toolsAllow) {
	if (toolsAllow === void 0) return false;
	return !toolsAllow.some((entry) => normalizeLowercaseStringOrEmpty(entry) === "*");
}
async function hasBoundConversationForSession(params) {
	const channel = normalizeOptionalLowercaseString(params.channelRaw) ?? "";
	if (!channel) return false;
	const accountId = normalizeOptionalLowercaseString(params.accountIdRaw) ?? "";
	const configuredDefaultAccountId = params.cfg.channels?.[channel]?.defaultAccount;
	const normalizedAccountId = accountId || normalizeOptionalLowercaseString(configuredDefaultAccountId) || "default";
	const { getSessionBindingService } = await loadDispatchAcpManagerRuntime();
	return getSessionBindingService().listBySession(params.sessionKey).some((binding) => {
		const bindingChannel = normalizeOptionalLowercaseString(binding.conversation.channel) ?? "";
		const bindingAccountId = normalizeOptionalLowercaseString(binding.conversation.accountId) ?? "";
		const conversationId = normalizeOptionalString(binding.conversation.conversationId) ?? "";
		return bindingChannel === channel && (bindingAccountId || "default") === normalizedAccountId && conversationId.length > 0;
	});
}
function finishAcpDispatchAttempt(params) {
	const counts = params.dispatcher.getQueuedCounts();
	params.delivery.applyRoutedCounts(counts);
	const suppressionReason = counts.tool + counts.block + counts.final > 0 || params.queuedFinal ? void 0 : params.delivery.getDeliverySuppressionReason();
	const acpStats = params.getStats();
	if (params.outcome.kind === "ok") {
		logVerbose(`acp-dispatch: session=${params.sessionKey} outcome=ok latencyMs=${Date.now() - params.startedAt} queueDepth=${acpStats.turns.queueDepth} activeRuntimes=${acpStats.runtimeCache.activeSessions}`);
		params.recordProcessed("completed", { reason: suppressionReason ?? "acp_dispatch" });
	} else {
		logVerbose(`acp-dispatch: session=${params.sessionKey} outcome=error code=${params.outcome.error.code} latencyMs=${Date.now() - params.startedAt} queueDepth=${acpStats.turns.queueDepth} activeRuntimes=${acpStats.runtimeCache.activeSessions}`);
		params.recordProcessed("completed", { reason: `acp_error:${normalizeLowercaseStringOrEmpty(params.outcome.error.code)}` });
	}
	params.markIdle("message_completed");
	return {
		queuedFinal: params.queuedFinal,
		counts
	};
}
const ACP_STALE_BINDING_UNBIND_REASON = "acp-session-init-failed";
function isStaleSessionInitError(params) {
	if (params.code !== "ACP_SESSION_INIT_FAILED") return false;
	return /(ACP (session )?metadata is missing|missing ACP metadata|Session is not ACP-enabled|Resource not found)/i.test(params.message);
}
async function maybeUnbindStaleBoundConversations(params) {
	if (!isStaleSessionInitError(params.error)) return;
	try {
		const { getSessionBindingService } = await loadDispatchAcpManagerRuntime();
		const removed = await getSessionBindingService().unbind({
			targetSessionKey: params.targetSessionKey,
			reason: ACP_STALE_BINDING_UNBIND_REASON
		});
		if (removed.length > 0) logVerbose(`dispatch-acp: removed ${removed.length} stale bound conversation(s) for ${params.targetSessionKey} after ${params.error.code}: ${params.error.message}`);
	} catch (error) {
		logVerbose(`dispatch-acp: failed to unbind stale bound conversations for ${params.targetSessionKey}: ${formatErrorMessage(error)}`);
	}
}
async function finalizeAcpTurnOutput(params) {
	const ttsMode = resolveConfiguredTtsMode(params.cfg, {
		agentId: params.agentId,
		channelId: params.ttsChannel,
		accountId: params.ttsAccountId
	});
	const accumulatedBlockTtsText = params.delivery.getAccumulatedBlockTtsText();
	const hasAccumulatedBlockText = accumulatedBlockTtsText.trim().length > 0;
	const ttsStatus = resolveStatusTtsSnapshot({
		cfg: params.cfg,
		sessionAuto: params.sessionTtsAuto,
		agentId: params.agentId,
		channelId: params.ttsChannel,
		accountId: params.ttsAccountId
	});
	const canAttemptFinalTts = ttsStatus != null && !(ttsStatus.autoMode === "inbound" && !params.inboundAudio);
	const shouldDeferVisibleTextForTts = params.shouldDeferVisibleTextForTts && ttsMode === "final" && hasAccumulatedBlockText && canAttemptFinalTts;
	const accumulatedVisibleBlockText = shouldDeferVisibleTextForTts ? cleanDeferredFinalText(accumulatedBlockTtsText) : params.delivery.getAccumulatedVisibleBlockText();
	if (!shouldDeferVisibleTextForTts) await params.delivery.settleVisibleText();
	let queuedFinal = params.delivery.hasDeliveredVisibleText() && !params.delivery.hasFailedVisibleTextDelivery();
	let finalMediaDelivered = params.delivery.hasDeliveredFinalTtsMedia();
	if (ttsMode === "final" && hasAccumulatedBlockText && canAttemptFinalTts && !finalMediaDelivered) try {
		const { maybeApplyTtsToPayload } = await loadDispatchAcpTtsRuntime();
		const ttsSyntheticReply = await maybeApplyTtsToPayload({
			payload: { text: accumulatedBlockTtsText },
			cfg: params.cfg,
			channel: params.ttsChannel,
			kind: "final",
			inboundAudio: params.inboundAudio,
			ttsAuto: params.sessionTtsAuto,
			agentId: params.agentId,
			accountId: params.ttsAccountId
		});
		if (ttsSyntheticReply.mediaUrl) {
			const finalTtsPayload = markReplyPayloadAsTtsSupplement(shouldDeferVisibleTextForTts ? {
				...ttsSyntheticReply,
				text: accumulatedVisibleBlockText || void 0,
				trustedLocalMedia: true
			} : {
				...ttsSyntheticReply,
				text: void 0,
				trustedLocalMedia: true
			}, accumulatedBlockTtsText, shouldDeferVisibleTextForTts ? void 0 : { visibleTextAlreadyDelivered: true });
			const delivered = await params.delivery.deliver("final", finalTtsPayload);
			queuedFinal = queuedFinal || delivered;
			finalMediaDelivered = params.delivery.hasDeliveredFinalTtsMedia();
		} else if (shouldDeferVisibleTextForTts && ttsSyntheticReply.text?.trim()) {
			const delivered = await params.delivery.deliver("final", { text: ttsSyntheticReply.text }, { skipTts: true });
			queuedFinal = queuedFinal || delivered;
		} else if (needsTtsFallback(true, accumulatedVisibleBlockText, ttsSyntheticReply.text)) {
			const delivered = await params.delivery.deliver("final", { text: ttsSyntheticReply.text }, { skipTts: true });
			queuedFinal = queuedFinal || delivered;
		}
	} catch (err) {
		logVerbose(`dispatch-acp: accumulated ACP block TTS failed: ${formatErrorMessage(err)}`);
	}
	if (ttsMode !== "all" && accumulatedVisibleBlockText.trim().length > 0 && !finalMediaDelivered && (shouldDeferVisibleTextForTts ? !params.delivery.hasDeliveredAnswerFinalToUser() : !params.delivery.hasDeliveredFinalReply() && (!params.delivery.hasDeliveredVisibleText() || params.delivery.hasFailedVisibleTextDelivery()))) {
		const delivered = await params.delivery.deliver("final", { text: accumulatedVisibleBlockText }, { skipTts: true });
		queuedFinal = queuedFinal || delivered;
	}
	if (params.shouldEmitResolvedIdentityNotice) {
		const { readAcpSessionEntry } = await loadDispatchAcpManagerRuntime();
		const currentMeta = readAcpSessionEntry({
			cfg: params.cfg,
			sessionKey: params.sessionKey,
			agentId: params.agentId
		})?.acp;
		const identityAfterTurn = resolveSessionIdentityFromMeta(currentMeta);
		if (!isSessionIdentityPending(identityAfterTurn)) {
			const resolvedDetails = resolveAcpThreadSessionDetailLines({
				sessionKey: params.sessionKey,
				meta: currentMeta
			});
			if (resolvedDetails.length > 0) {
				const delivered = await params.delivery.deliver("final", { text: prefixSystemMessage(["Session ids resolved.", ...resolvedDetails].join("\n")) });
				queuedFinal = queuedFinal || delivered;
			}
		}
	}
	return queuedFinal;
}
async function tryDispatchAcpReplyCore(params) {
	const sessionKey = normalizeOptionalString(params.sessionKey);
	if (!sessionKey || params.bypassForCommand) return null;
	prepareChannelParticipantObservation(params.ctx);
	const { getAcpSessionManager } = await loadDispatchAcpManagerRuntime();
	const acpManager = getAcpSessionManager();
	const acpResolution = acpManager.resolveSession({
		cfg: params.cfg,
		sessionKey,
		agentId: resolveSessionAgentId({
			config: params.cfg,
			sessionKey,
			fallbackAgentId: params.ctx.AgentId
		})
	});
	if (acpResolution.kind === "none") return null;
	const canonicalSessionKey = acpResolution.sessionKey;
	const transcriptSessionId = acpResolution.kind === "ready" ? acpResolution.entry?.sessionId : void 0;
	const acpAgentId = acpResolution.agentId;
	const participantTarget = {
		agentId: acpAgentId,
		sessionKey: canonicalSessionKey,
		storePath: resolveSessionStorePathCore(params.cfg.session?.store, { agentId: acpAgentId }),
		onError: (error) => logVerbose(`dispatch-acp: participant persistence failed: ${formatErrorMessage(error)}`)
	};
	const progressSessionKeys = isDiagnosticsEnabled(params.cfg) ? Array.from(new Set([
		params.ctx.SessionKey,
		sessionKey,
		canonicalSessionKey
	].map((key) => normalizeOptionalString(key)).filter((key) => Boolean(key)))) : [];
	const markAcpProgress = progressSessionKeys.length > 0 ? () => {
		for (const key of progressSessionKeys) markDiagnosticSessionProgress({ sessionKey: key });
	} : void 0;
	const identityPendingBeforeTurn = isSessionIdentityPending(resolveSessionIdentityFromMeta(acpResolution.kind === "ready" ? acpResolution.meta : void 0));
	const shouldEmitResolvedIdentityNotice = !params.suppressUserDelivery && identityPendingBeforeTurn && (Boolean(params.ctx.MessageThreadId != null && (normalizeOptionalString(String(params.ctx.MessageThreadId)) ?? "")) || await hasBoundConversationForSession({
		cfg: params.cfg,
		sessionKey: canonicalSessionKey,
		channelRaw: params.ctx.OriginatingChannel ?? params.ctx.Surface ?? params.ctx.Provider,
		accountIdRaw: params.ctx.AccountId
	}));
	const resolvedAcpAgent = acpResolution.kind === "ready" ? normalizeOptionalString(acpResolution.meta.agent) ?? normalizeOptionalString(params.cfg.acp?.defaultAgent) ?? resolveAgentIdFromSessionKey(canonicalSessionKey) : resolveAgentIdFromSessionKey(canonicalSessionKey);
	const normalizedDispatchChannel = normalizeOptionalLowercaseString(params.ctx.OriginatingChannel ?? params.ctx.Surface ?? params.ctx.Provider);
	const explicitDispatchAccountId = normalizeOptionalString(params.ctx.AccountId);
	const dispatchChannels = params.cfg.channels;
	const defaultDispatchAccount = normalizedDispatchChannel == null ? void 0 : dispatchChannels?.[normalizedDispatchChannel]?.defaultAccount;
	const effectiveDispatchAccountId = explicitDispatchAccountId ?? normalizeOptionalString(defaultDispatchAccount);
	const shouldDeferVisibleTextForTts = shouldDeferFinalTtsText({
		cfg: params.cfg,
		ttsAuto: params.sessionTtsAuto,
		agentId: acpAgentId,
		channelId: params.ttsChannel,
		accountId: effectiveDispatchAccountId,
		inboundAudio: params.inboundAudio
	});
	let queuedFinal = false;
	const delivery = createAcpDispatchDeliveryCoordinator({
		cfg: params.cfg,
		agentId: acpAgentId,
		ctx: params.ctx,
		dispatcher: params.dispatcher,
		inboundAudio: params.inboundAudio,
		sessionKey: canonicalSessionKey,
		sessionTtsAuto: params.sessionTtsAuto,
		ttsChannel: params.ttsChannel,
		suppressUserDelivery: params.suppressUserDelivery,
		suppressBlockUserDelivery: shouldDeferVisibleTextForTts,
		suppressReplyLifecycle: params.suppressReplyLifecycle,
		shouldRouteToOriginating: params.shouldRouteToOriginating,
		originatingChannel: params.originatingChannel,
		originatingTo: params.originatingTo,
		originatingAccountId: params.originatingAccountId,
		originatingThreadId: params.originatingThreadId,
		originatingChatType: params.originatingChatType,
		onReplyStart: params.onReplyStart,
		abortSignal: params.abortSignal,
		runId: params.runId
	});
	const pendingAnswerText = resolveAcpPromptText(params.ctx);
	try {
		if (pendingAnswerText && !params.images?.length && !params.extractedFileImages?.length && !hasInboundMediaForUnderstanding(params.ctx) && await claimPendingAgentQuestionAnswer({
			sessionKey: acpResolution.sessionKey,
			text: pendingAnswerText
		})) {
			recordAcceptedSessionParticipantInput(params.ctx, participantTarget);
			const counts = params.dispatcher.getQueuedCounts();
			params.recordProcessed("completed", { reason: "acp_question_answer" });
			params.markIdle("message_completed");
			return {
				queuedFinal: false,
				counts
			};
		}
	} catch (error) {
		if (!(error instanceof QuestionAnswerUnconfirmedError)) throw error;
		params.recordProcessed("error", {
			reason: "acp_question_answer_unconfirmed",
			error: error.message
		});
		const queuedNotice = await delivery.deliver("final", {
			text: error.message,
			isError: true
		}).catch((deliveryError) => {
			logVerbose(`dispatch-acp: uncertain question notice delivery failed: ${formatErrorMessage(deliveryError)}`);
			return false;
		});
		params.markIdle("message_error");
		const counts = params.dispatcher.getQueuedCounts();
		delivery.applyRoutedCounts(counts);
		return {
			queuedFinal: queuedNotice,
			counts
		};
	}
	const deliverDeferredTextFallback = async () => {
		if (!shouldDeferVisibleTextForTts || delivery.hasDeliveredAnswerFinalToUser()) return false;
		const text = delivery.getAccumulatedVisibleBlockText();
		return text.trim() ? await delivery.deliver("final", { text }, { skipTts: true }) : false;
	};
	const projector = createAcpReplyProjector({
		cfg: params.cfg,
		shouldSendToolSummaries: params.shouldSendToolSummaries,
		shouldSendToolSummariesNow: params.shouldSendToolSummariesNow,
		shouldSendFullToolDetails: params.shouldSendFullToolDetails,
		deliver: delivery.deliver,
		getConversationContext: () => params.ctx.agentText,
		onProgress: markAcpProgress,
		provider: params.ctx.Surface ?? params.ctx.Provider,
		accountId: effectiveDispatchAccountId
	});
	const acpDispatchStartedAt = Date.now();
	const finishAttempt = (options) => finishAcpDispatchAttempt({
		...options,
		dispatcher: params.dispatcher,
		delivery,
		getStats: () => acpManager.getObservabilitySnapshot(),
		sessionKey,
		startedAt: acpDispatchStartedAt,
		recordProcessed: params.recordProcessed,
		markIdle: params.markIdle
	});
	const requestId = resolveAcpRequestId(params.ctx);
	const existingRunId = normalizeOptionalString(params.runId);
	const auditOnly = existingRunId === void 0;
	let completionSource;
	const auditRunId = existingRunId ?? generateSecureUuid();
	const auditRuntime = await loadDispatchAcpAuditRuntime();
	const auditToolTracker = auditRuntime.createAcpToolLifecycleTracker();
	let auditStarted = false;
	let auditFinished = false;
	let auditTerminalOutcome;
	let auditStopReason;
	let auditResultStatus;
	let runtimeTurnWasCancelled = false;
	let assistantTranscript;
	let terminalOutcome;
	let auditEndFields;
	const resolveAuditEndFields = () => auditEndFields ??= auditRuntime.resolveAcpLifecycleEndFields(params.abortSignal, auditStopReason, auditResultStatus);
	const emitAuditStart = () => {
		if (auditStarted) return;
		auditStarted = true;
		const completionOwner = params.onAgentRunStart?.(auditRunId, void 0, {
			completionSource: "reply-dispatch",
			getResult: () => ({
				assistantTranscript,
				terminalOutcome
			})
		});
		completionSource = completionOwner === "reply-dispatch" ? completionOwner : void 0;
		auditRuntime.emitAcpLifecycleStart({
			runId: auditRunId,
			sessionKey: canonicalSessionKey,
			agentId: acpAgentId,
			startedAt: Date.now(),
			auditOnly,
			completionSource
		});
	};
	const emitAuditEnd = () => {
		if (auditFinished) return;
		emitAuditStart();
		auditFinished = true;
		terminalOutcome = auditRuntime.emitAcpLifecycleEnd({
			runId: auditRunId,
			toolTracker: auditToolTracker,
			sessionKey: canonicalSessionKey,
			agentId: acpAgentId,
			endFields: resolveAuditEndFields(),
			auditOnly,
			completionSource
		});
	};
	const emitAuditError = (error) => {
		if (auditFinished) return;
		emitAuditStart();
		auditFinished = true;
		terminalOutcome = auditRuntime.emitAcpLifecycleError({
			runId: auditRunId,
			toolTracker: auditToolTracker,
			sessionKey: canonicalSessionKey,
			agentId: acpAgentId,
			...params.abortSignal ? { abortSignal: params.abortSignal } : {},
			...auditTerminalOutcome ? { terminalOutcome: auditTerminalOutcome } : {},
			auditOnly,
			completionSource,
			error
		});
	};
	let transcriptPromptText = "";
	let turnDispatched = false;
	let transcriptPersistenceAttempted = false;
	const persistTranscript = async (finalText) => {
		if (transcriptPersistenceAttempted) return;
		transcriptPersistenceAttempted = true;
		terminalOutcome ??= buildAgentRunTerminalOutcomeFromLifecycleEvent({
			phase: "end",
			data: resolveAuditEndFields()
		});
		const { persistAcpDispatchTranscript } = await loadDispatchAcpTranscriptRuntime();
		assistantTranscript = await persistAcpDispatchTranscript({
			cfg: params.cfg,
			sessionKey: canonicalSessionKey,
			agentId: acpAgentId,
			expectedSessionId: transcriptSessionId,
			promptText: transcriptPromptText,
			finalText,
			terminalOutcome,
			meta: acpResolution.kind === "ready" ? acpResolution.meta : void 0,
			threadId: params.ctx.MessageThreadId,
			userTurnTranscriptRecorder: params.userTurnTranscriptRecorder,
			prepareAssistantTranscriptMessage: params.prepareAssistantTranscriptMessage,
			assistantIdempotencyKey: existingRunId
		});
	};
	let admittedRunContext;
	let nativeActionEvidenceRecorded = false;
	const recordUnsupportedNativeActionEvidence = () => {
		if (nativeActionEvidenceRecorded) return;
		nativeActionEvidenceRecorded = true;
		recordRuntimeActionDecision({
			token: admittedRunContext?.executionIdentityToken,
			family: "native-runtime",
			operation: "action-evidence",
			outcome: "not-applicable",
			coverageState: "unsupported",
			reasonCode: "native_action_callback_unsupported",
			owner: "acp-runtime",
			decisionBoundary: "acp-runtime.prompt-submitted",
			summary: "ACP runtime action evidence is unsupported because the adapter exposes no authoritative native-action callback.",
			missingEvidence: ["native.action_callback"],
			remediation: [{
				code: "instrument_native_action_callback",
				text: "Instrument an authoritative native-action callback in the ACP adapter before claiming action evidence."
			}]
		});
	};
	try {
		const dispatchPolicyError = resolveAcpDispatchPolicyError(params.cfg);
		if (dispatchPolicyError) {
			auditTerminalOutcome = "blocked";
			throw dispatchPolicyError;
		}
		if (isRestrictiveRuntimeToolsAllow(params.toolsAllow) || toolPolicyRestrictsTools(params.ctx.ConversationToolPolicy)) {
			auditTerminalOutcome = "blocked";
			throw new AcpRuntimeError("ACP_DISPATCH_DISABLED", "This session's bound runtime cannot enforce its permission or tool policy; use an embedded runtime for this restricted conversation.");
		}
		if (acpResolution.kind === "stale") {
			emitAuditError(acpResolution.error);
			await maybeUnbindStaleBoundConversations({
				targetSessionKey: canonicalSessionKey,
				error: acpResolution.error
			});
			return finishAttempt({
				queuedFinal: await delivery.deliver("final", {
					text: formatAcpRuntimeErrorText(acpResolution.error),
					isError: true
				}),
				outcome: {
					kind: "error",
					error: acpResolution.error
				}
			});
		}
		const agentPolicyError = resolveAcpAgentPolicyError(params.cfg, resolvedAcpAgent);
		if (agentPolicyError) {
			auditTerminalOutcome = "blocked";
			throw agentPolicyError;
		}
		const resolvedTurnAttachments = await resolveAgentTurnAttachments({
			ctx: params.ctx,
			cfg: params.cfg,
			includeAttachmentIndexes: true
		});
		let extractedFileImages = params.extractedFileImages ?? [];
		if (hasInboundMediaForUnderstanding(params.ctx) && !params.ctx.MediaUnderstanding?.length) try {
			const { applyMediaUnderstanding } = await loadAgentTurnMediaRuntime();
			const mediaResult = await applyMediaUnderstanding({
				ctx: params.ctx,
				cfg: params.cfg,
				deliveredImageIndexes: new Set(resolvedTurnAttachments.attachmentIndexes ?? []),
				agentId: acpAgentId,
				agentDir: resolveAgentDir(params.cfg, acpAgentId),
				workspaceDir: resolveAgentWorkspaceDir(params.cfg, acpAgentId)
			});
			if (mediaResult.extractedFileImages.length > 0) extractedFileImages = [...extractedFileImages, ...mediaResult.extractedFileImages];
		} catch (err) {
			logVerbose(`dispatch-acp: media understanding failed, proceeding with raw content: ${formatErrorMessage(err)}`);
		}
		const promptText = resolveAcpPromptText(params.ctx);
		const describedImageIndexes = collectDescribedImageAttachmentIndexes(params.ctx);
		const recentHistoryStart = resolvedTurnAttachments.attachments.length - resolvedTurnAttachments.recentHistoryImages.length;
		const mediaAttachmentEntries = resolvedTurnAttachments.attachments.flatMap((attachment, index) => {
			const sourceIndex = resolvedTurnAttachments.attachmentIndexes?.[index];
			return sourceIndex !== void 0 && !describedImageIndexes.has(sourceIndex) && (describedImageIndexes.size === 0 || index < recentHistoryStart) ? [{
				attachment,
				sourceIndex
			}] : [];
		});
		const mediaAttachments = mediaAttachmentEntries.map((entry) => entry.attachment);
		const recentHistoryImages = describedImageIndexes.size === 0 ? resolvedTurnAttachments.recentHistoryImages : [];
		const inlineAttachments = resolveInlineAgentImageAttachments(params.images);
		const extractedAttachments = resolveInlineAgentImageAttachments(extractedFileImages.map(stripExtractedFileImageMetadata));
		const useMediaAttachments = mediaAttachments.length > 0 && !(mediaAttachments.length === recentHistoryImages.length && (inlineAttachments.length > 0 || extractedAttachments.length > 0));
		const attachmentEntries = [];
		if (useMediaAttachments) appendOrderedAcpAttachments({
			entries: attachmentEntries,
			attachments: mediaAttachments,
			sourceIndexes: mediaAttachmentEntries.map((entry) => entry.sourceIndex)
		});
		else appendOrderedAcpAttachments({
			entries: attachmentEntries,
			attachments: inlineAttachments
		});
		appendOrderedAcpAttachments({
			entries: attachmentEntries,
			attachments: extractedAttachments,
			sourceIndexes: extractedFileImages.map((image) => image.attachmentIndex)
		});
		const attachments = resolveMergedAcpAttachments(attachmentEntries);
		const turnPromptText = useMediaAttachments ? appendRecentHistoryImageContext({
			promptText,
			images: recentHistoryImages
		}) : promptText;
		transcriptPromptText = turnPromptText;
		if (!turnPromptText && attachments.length === 0) {
			const counts = params.dispatcher.getQueuedCounts();
			delivery.applyRoutedCounts(counts);
			params.recordProcessed("completed", { reason: "acp_empty_prompt" });
			params.markIdle("message_completed");
			return {
				queuedFinal: false,
				counts
			};
		}
		emitAuditStart();
		try {
			await delivery.startReplyLifecycle();
		} catch (error) {
			logVerbose(`dispatch-acp: start reply lifecycle failed: ${formatErrorMessage(error)}`);
		}
		turnDispatched = true;
		const channelAdmission = consumeChannelRunAdmission(readChannelContextAdmissionEvidence(params.ctx));
		admittedRunContext = await prepareAgentRunAdmission({
			cfg: params.cfg,
			operationalRunInstance: createOperationalRunInstanceRef(requestId),
			facts: {
				runId: requestId,
				agentId: acpAgentId,
				ingress: {
					kind: "acp",
					boundary: "auto-reply.acp",
					state: channelAdmission.ingressState
				},
				...channelAdmission.facts
			},
			onAdmitted: channelAdmission.onAdmitted
		}).admit("acp");
		recordAcceptedSessionParticipantInput(params.ctx, participantTarget);
		const turnAdmission = admittedRunContext;
		const onElicitation = createLazyAcpElicitationHandler({
			sourceSessionKey: sessionKey,
			targetSessionKey: canonicalSessionKey,
			outerRequestId: requestId,
			agentId: acpAgentId,
			runId: auditRunId,
			delivery,
			isActive: () => params.abortSignal?.aborted !== true && admittedRunContext === turnAdmission && getAdmittedRunDelegatedAuthority(turnAdmission) !== void 0
		});
		await acpManager.runTurn({
			admittedRunContext,
			cfg: params.cfg,
			sessionKey: canonicalSessionKey,
			agentId: acpAgentId,
			provenance: classifySessionStateActor({
				inputProvenance: params.ctx.InputProvenance,
				sessionEffects: params.ctx.InboundEventKind === "room_event" ? "internal" : "visible"
			}).actorType,
			text: resolveAcpTurnText({
				promptText: turnPromptText,
				sourceReplyDeliveryMode: params.sourceReplyDeliveryMode
			}),
			attachments: attachments.length > 0 ? attachments : void 0,
			mode: "prompt",
			requestId,
			...params.abortSignal ? { signal: params.abortSignal } : {},
			onElicitation,
			onLifecycle: recordUnsupportedNativeActionEvidence,
			onEvent: async (event) => {
				auditRuntime.emitAcpRuntimeEvent({
					runId: auditRunId,
					toolTracker: auditToolTracker,
					sessionKey: canonicalSessionKey,
					agentId: acpAgentId,
					...params.abortSignal ? { abortSignal: params.abortSignal } : {},
					auditOnly,
					event
				});
				if (event.type === "done") {
					auditStopReason = event.stopReason;
					auditResultStatus = event.status;
					runtimeTurnWasCancelled = event.status === "cancelled";
				}
				await projector.onEvent(event);
			}
		});
		await projector.flush(true);
		if (runtimeTurnWasCancelled || params.abortSignal?.aborted) {
			queuedFinal = await deliverDeferredTextFallback() || queuedFinal;
			await persistTranscript(await delivery.resolveAccumulatedDeliveredTranscriptText());
			queuedFinal = delivery.hasDeliveredFinalReply() || queuedFinal;
			const counts = params.dispatcher.getQueuedCounts();
			delivery.applyRoutedCounts(counts);
			params.recordProcessed("completed", { reason: "acp_aborted" });
			params.markIdle("message_aborted");
			emitAuditEnd();
			return {
				queuedFinal,
				counts
			};
		}
		queuedFinal = await finalizeAcpTurnOutput({
			cfg: params.cfg,
			sessionKey: canonicalSessionKey,
			agentId: acpAgentId,
			delivery,
			inboundAudio: params.inboundAudio,
			sessionTtsAuto: params.sessionTtsAuto,
			ttsChannel: params.ttsChannel,
			ttsAccountId: effectiveDispatchAccountId,
			shouldDeferVisibleTextForTts,
			shouldEmitResolvedIdentityNotice
		}) || queuedFinal;
		await persistTranscript(delivery.getAccumulatedTranscriptText());
		const result = finishAttempt({
			queuedFinal,
			outcome: { kind: "ok" }
		});
		emitAuditEnd();
		return result;
	} catch (err) {
		const acpError = toAcpRuntimeError({
			error: err,
			fallbackCode: "ACP_TURN_FAILED",
			fallbackMessage: "ACP turn failed before completion."
		});
		emitAuditError(acpError);
		await projector.flush(true);
		queuedFinal = await deliverDeferredTextFallback() || queuedFinal;
		await maybeUnbindStaleBoundConversations({
			targetSessionKey: canonicalSessionKey,
			error: acpError
		});
		const errorText = formatAcpRuntimeErrorText(acpError);
		const partialText = delivery.getAccumulatedTranscriptText();
		const delivered = await delivery.deliver("final", {
			text: errorText,
			isError: true
		});
		if (turnDispatched) await persistTranscript(partialText ? `${partialText}\n\n${errorText}` : errorText);
		queuedFinal = queuedFinal || delivered;
		return finishAttempt({
			queuedFinal,
			outcome: {
				kind: "error",
				error: acpError
			}
		});
	} finally {
		if (admittedRunContext) closeAdmittedRunDelegatedAuthority(admittedRunContext);
	}
}
//#endregion
export { tryDispatchAcpReplyCore };