UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

1,359 lines • 65.7 kB
import { c as normalizeOptionalLowercaseString, l as normalizeOptionalString, o as normalizeLowercaseStringOrEmpty } from "./string-coerce-1VuiLaaR.mjs";
import { _ as normalizeUniqueTrimmedStringList } from "./string-normalization-D6DKCpwE.mjs";
import { r as truncateUtf16Safe } from "./utf16-slice-D_ngcYKd.mjs";
import { t as formatErrorMessage } from "./errors-BvOkmQM5.mjs";
import { p as isDiagnosticsEnabled } from "./diagnostic-events-DT4ejwoa.mjs";
import { n as createLazyPromise, t as createLazyImportLoader } from "./lazy-promise-DGqyc4Y4.mjs";
import { l as resolveAgentIdFromSessionKey } from "./session-key-DY3MOX4N.mjs";
import { c as resolveSessionStorePathCore } from "./paths-Bonddxfk.mjs";
import { a as resolveAgentDir, d as resolveAgentWorkspaceDir } from "./agent-scope-config-BEk6-ThS.mjs";
import { h as resolveSessionAgentId } from "./agent-scope-bJOokm5r.mjs";
import { d as toolPolicyRestrictsTools } from "./tool-policy-BrMCyUgN.mjs";
import { a as generateSecureUuid } from "./secure-random-B2AO8OrW.mjs";
import { r as logVerbose } from "./globals-yEiTitH8.mjs";
import { n as recordRuntimeActionDecision } from "./runtime-action-decision-B2u479yP.mjs";
import { S as markReplyPayloadAsTtsSupplement, _ as isReplyPayloadStatusNotice, b as isReplyPayloadTtsSupplement, o as copyReplyPayloadMetadata } from "./reply-payload-BEgsL2xJ.mjs";
import { n as resolveConfiguredTtsMode, s as shouldCleanTtsDirectiveText } from "./tts-config-CX34pOp-.mjs";
import { c as readChannelContextAdmissionEvidence } from "./admission-evidence-CcY0jAvs.mjs";
import { o as prepareChannelParticipantObservation } from "./host-context-builder-DqHC41dw.mjs";
import { a as closeAdmittedRunDelegatedAuthority, u as getAdmittedRunDelegatedAuthority } from "./admitted-run-context-D0yzk2io.mjs";
import { A as createVerifiedConversationContextStreamFilter } from "./user-facing-text-BCPDY-39.mjs";
import { n as classifySessionStateActor } from "./session-state-events-tNQ7VRZz.mjs";
import { a as toAcpRuntimeError, n as AcpRuntimeError } from "./errors-1GV1HisX.mjs";
import { t as formatAcpRuntimeErrorText } from "./error-text-CMbNX9RD.mjs";
import { o as isSessionIdentityPending, u as resolveSessionIdentityFromMeta } from "./session-identity-Y754M7yL.mjs";
import { i as resolveAcpThreadSessionDetailLines } from "./session-identifiers-2CkhpQUq.mjs";
import "./errors-CEQyfSPE.mjs";
import { n as QuestionAnswerUnconfirmedError, t as PreparedQuestionAnswerRefusedError } from "./gateway-question-dispatch-Fh2N-MFN.mjs";
import { r as hasInboundMediaForUnderstanding } from "./inbound-media-D9dVgt41.mjs";
import { d as markDiagnosticSessionProgress } from "./diagnostic-jtgHaFVw.mjs";
import { r as normalizeReplyPayloadOutcome } from "./normalize-reply-B3mNRWkY.mjs";
import { g as shouldRetryReplyDispatch, h as resolveRoutedReplyDeliveryOutcome, o as prepareReplyPayloadForDispatcher, r as captureReplyDispatchDeliveryOutcome, s as waitForReplyDispatcherIdle, t as attachReplyDispatchUndeliveredFallback } from "./reply-dispatcher-gTTFXVZE.mjs";
import { a as hasOutboundReplyContent } from "./reply-payload-eiK8mKuN.mjs";
import { n as resolveRoutedDeliveryThreadId } from "./routed-delivery-thread-Dew4dOMp.mjs";
import { n as assertPreparedConversationBindingRouteCurrent } from "./session-conversation-binding-Ct7uOkBw.mjs";
import { a as isCaptionedFinalTextPayload, i as cleanDeferredFinalText, n as needsTtsFallback, o as mergeDeferredFinalText, r as buildCaptionedFinalTextFallback, s as shouldDeferFinalTtsText } from "./dispatch-from-config.finalize-BkelvVWI.mjs";
import { t as createTtsDirectiveTextStreamCleaner } from "./directives-N8T4kLmk.mjs";
import { t as resolveStatusTtsSnapshot } from "./status-config-DRoj3BMT.mjs";
import { g as hasBlockReplyDeliveryCustody, m as createBlockReplySource, p as createBlockReplyPipeline, v as setBlockReplyDelivery } from "./dispatch-from-config.payloads-Bju_QM-B.mjs";
import { i as claimPreparedPendingAgentQuestionAnswer } from "./gateway-question-C8X_AymR.mjs";
import { n as formatToolSummary, o as resolveToolDisplay } from "./tool-display-D_9ViFCQ.mjs";
import { n as truncateUtf16WithEllipsis } from "./text-truncate-DrDnz8rI.mjs";
import { t as prepareTtsPreferences } from "./tts-preferences-O074cDlX.mjs";
import { i as buildAgentRunTerminalOutcomeFromLifecycleEvent } from "./agent-run-terminal-outcome-8xoMCk5L.mjs";
import { a as resolveReplyDeliveryAccountId, o as resolveReplyToMode, t as createReplyDeliveryContext } from "./reply-threading-ChfJS7JV.mjs";
import { r as createChannelReplyTransform } from "./reply-transform-D02pkEMa.mjs";
import { r as getGatewayLocalUserIngress } from "./local-user-ingress-PBPakyQk.mjs";
import { n as resolveAcpAgentPolicyError, r as resolveAcpDispatchPolicyError } from "./policy-B6crihax.mjs";
import { r as prefixSystemMessage } from "./system-message-C34aMvlW.mjs";
import { r as bindUserTurnInput } from "./user-turn-transcript-runtime-context-BtMCMUja.mjs";
import { t as EmbeddedBlockChunker } from "./embedded-agent-block-chunker-CxsctPo2.mjs";
import { n as resolveEffectiveBlockStreamingConfig } from "./block-streaming-DDmPsWMC.mjs";
import { t as stripExtractedFileImageMetadata } from "./extracted-file-images-CdmNdoIK.mjs";
import { a as appendRecentHistoryImageContext, i as resolveInlineAgentImageAttachments, n as loadAgentTurnMediaRuntime, r as resolveAgentTurnAttachments, t as collectDescribedImageAttachmentIndexes } from "./agent-turn-attachments-CLKa0Q-a.mjs";
import { t as recordAcceptedSessionParticipantInput } from "./session-participant-input-recording-BS-iewnb.mjs";
import { t as createLazyAcpElicitationHandler } from "./acp-elicitation-handler-lazy-U_vq302T.mjs";
import { t as resolveAcpToolTerminalOutcome } from "./tool-status-BfHSDdEj.mjs";
import { n as resolveAcpProjectionSettings, t as isAcpTagVisible } from "./acp-stream-settings-Ca7bZwio.mjs";
import { t as prepareChannelRunAdmission } from "./channel-run-admission-Bz64bSPr.mjs";
//#region src/auto-reply/reply/acp-projector.ts
const ACP_BLOCK_REPLY_TIMEOUT_MS = 15e3;
const ACP_MAX_OUTPUT_CHARS = 24e3;
const ACP_MAX_SESSION_UPDATE_CHARS = 320;
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 shouldInsertSeparator(params) {
	if (!params.previousTail || /^\s/.test(params.nextText)) return false;
	return params.separator === " " ? !/\s$/.test(params.previousTail) : !params.previousTail.endsWith("\n");
}
function shouldFlushLiveBufferOnBoundary(text) {
	return text.length >= ACP_LIVE_HARD_FLUSH_CHARS || text.endsWith("\n\n") || /[.!?][)"'`]*\s$/.test(text) || text.length >= ACP_LIVE_SOFT_FLUSH_CHARS && /\s$/.test(text);
}
function shouldFlushLiveBufferOnIdle(text) {
	return text.length >= ACP_LIVE_IDLE_MIN_CHARS || /[.!?][)"'`]*$/.test(text.trimEnd()) || text.includes("\n");
}
function renderToolSummaryText(event, shouldSendFullToolDetails) {
	const showDetails = shouldSendFullToolDetails || normalizeOptionalLowercaseString(event.kind) !== "execute";
	const title = showDetails ? normalizeOptionalString(event.title) : void 0;
	const status = normalizeOptionalString(event.status);
	const fallback = showDetails ? normalizeOptionalString(event.text) : void 0;
	const display = resolveToolDisplay({
		name: "tool_call",
		meta: [title, status && `status=${status}`].filter(Boolean).join(" · ") || fallback || "tool call"
	});
	return formatToolSummary(display);
}
function createAcpReplyProjector(params) {
	const settings = resolveAcpProjectionSettings(params.cfg);
	const isLive = settings.deliveryMode === "live";
	const hiddenBoundarySeparator = isLive ? " " : "\n\n";
	const streaming = resolveEffectiveBlockStreamingConfig({
		cfg: params.cfg,
		provider: params.provider,
		accountId: params.accountId,
		maxChunkChars: 1800,
		coalesceIdleMs: 350
	});
	const blockReplyPipeline = createBlockReplyPipeline({
		onBlockReply: async (payload) => {
			await params.deliver("block", payload);
		},
		timeoutMs: ACP_BLOCK_REPLY_TIMEOUT_MS,
		coalescing: isLive ? void 0 : streaming.coalescing
	});
	const chunker = new EmbeddedBlockChunker(isLive ? {
		...streaming.chunking,
		minChars: 1
	} : 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 bufferedText = "";
	let liveIdleTimer;
	const pendingToolDeliveries = [];
	const toolLifecycleById = /* @__PURE__ */ new Map();
	const clearLiveIdleTimer = () => {
		clearTimeout(liveIdleTimer);
		liveIdleTimer = void 0;
	};
	const drainChunker = () => {
		chunker.drain({
			force: true,
			emit: (chunk) => {
				blockReplyPipeline.enqueue({ text: chunk });
			}
		});
	};
	const flushLiveBuffer = (idle = false) => {
		if (!bufferedText) return;
		if (idle && !shouldFlushLiveBufferOnIdle(bufferedText)) return;
		const text = bufferedText;
		bufferedText = "";
		chunker.append(text);
		drainChunker();
	};
	const scheduleLiveIdleFlush = () => {
		if (!bufferedText) return;
		clearLiveIdleTimer();
		liveIdleTimer = setTimeout(() => {
			flushLiveBuffer(true);
			if (bufferedText) scheduleLiveIdleFlush();
		}, liveIdleFlushMs);
	};
	const flush = async () => {
		if (isLive) {
			clearLiveIdleTimer();
			flushLiveBuffer();
			drainChunker();
		} else {
			if (await params.shouldSendToolSummaries()) for (const entry of pendingToolDeliveries.splice(0)) await params.deliver("tool", entry.payload, entry.meta);
			else pendingToolDeliveries.length = 0;
			if (bufferedText.trim().length > 0) {
				const text = bufferedText;
				bufferedText = "";
				await params.deliver("final", { text });
			}
		}
		await blockReplyPipeline.flush({ force: true });
	};
	const deliverTool = async (text, meta) => {
		if (!isLive) pendingToolDeliveries.push({
			payload: { text },
			...meta ? { meta } : {}
		});
		else {
			await flush();
			await params.deliver("tool", { text }, meta);
		}
	};
	const emitSystemStatus = async (text, opts) => {
		if (!await params.shouldSendToolSummaries()) return;
		const bounded = truncateUtf16WithEllipsis(text.trim(), ACP_MAX_SESSION_UPDATE_CHARS);
		if (!bounded) return;
		const formatted = prefixSystemMessage(bounded);
		const hash = formatted.trim();
		if (settings.repeatSuppression && opts?.dedupe !== false && lastStatusHash === hash) return;
		await deliverTool(formatted);
		lastStatusHash = hash;
	};
	const markHiddenToolBoundary = (event) => {
		if (!event.tag || !HIDDEN_BOUNDARY_TAGS.has(event.tag)) return;
		const isTerminal = resolveAcpToolTerminalOutcome(event.status) !== void 0;
		pendingHiddenBoundary = pendingHiddenBoundary || event.tag === "tool_call" || isTerminal;
	};
	const emitToolSummary = async (event) => {
		if (!await params.shouldSendToolSummaries()) {
			markHiddenToolBoundary(event);
			return;
		}
		const renderedToolSummary = renderToolSummaryText(event, await params.shouldSendFullToolDetails());
		const toolSummary = truncateUtf16WithEllipsis(renderedToolSummary, ACP_MAX_SESSION_UPDATE_CHARS);
		const hash = renderedToolSummary.trim();
		const toolCallId = normalizeOptionalString(event.toolCallId);
		const status = normalizeOptionalLowercaseString(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 || isStart && state.started || state.lastRenderedHash === hash) return;
				state.started ||= isStart;
				state.terminal ||= isTerminal;
				state.lastRenderedHash = hash;
				toolLifecycleById.set(toolCallId, state);
			} else if (lastToolHash === hash) return;
		}
		const deliveryMeta = {
			...toolCallId ? { toolCallId } : {},
			allowEdit: Boolean(toolCallId && event.tag === "tool_call_update")
		};
		await deliverTool(toolSummary, deliveryMeta);
		if (!isLive) markHiddenToolBoundary(event);
		lastToolHash = hash;
	};
	const emitTruncationNotice = async () => {
		if (truncationNoticeEmitted) return;
		truncationNoticeEmitted = true;
		await emitSystemStatus("output truncated", { 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: hiddenBoundarySeparator,
				previousTail: lastVisibleOutputTail,
				nextText: text
			})) text = `${hiddenBoundarySeparator}${text}`;
			pendingHiddenBoundary = false;
			if (emittedOutputChars >= ACP_MAX_OUTPUT_CHARS) {
				await emitTruncationNotice();
				return;
			}
			const remaining = ACP_MAX_OUTPUT_CHARS - 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;
				bufferedText += safeText;
				if (isLive) {
					if (shouldFlushLiveBufferOnBoundary(bufferedText)) {
						clearLiveIdleTimer();
						flushLiveBuffer();
					} else scheduleLiveIdleFlush();
				}
			}
			if (accepted.length < text.length) {
				emittedOutputChars = ACP_MAX_OUTPUT_CHARS;
				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}` : event.text.trim();
				if (usageTuple === lastUsageTuple) return;
				lastUsageTuple = usageTuple;
			}
			await emitSystemStatus(event.text);
			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
const dispatchAcpTtsRuntimeLoader = createLazyImportLoader(() => import("./tts.runtime.js"));
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
	}) });
}
async function maybeApplyAcpTts(params) {
	if (params.skipTts || isReplyPayloadStatusNotice(params.payload)) return params.payload;
	const ttsStatus = resolveStatusTtsSnapshot({
		cfg: params.cfg,
		preparedTtsPreferences: params.preparedTtsPreferences,
		sessionAuto: params.ttsAuto,
		agentId: params.agentId,
		channelId: params.channel,
		accountId: params.accountId
	});
	if (!ttsStatus || 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 dispatchAcpTtsRuntimeLoader.load();
	const applied = await maybeApplyTtsToPayload(params);
	return copyReplyPayloadMetadata(params.payload, applied);
}
const channelPluginRuntimeLoader$1 = createLazyImportLoader(() => import("./plugins-B8IhIS-G.mjs"));
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 channelPluginRuntimeLoader$1.load();
	const outbound = getChannelPlugin(channelId)?.outbound;
	return (outbound?.shouldTreatDeliveredTextAsVisible ?? outbound?.shouldTreatRoutedTextAsVisible)?.({
		kind: params.kind,
		text: params.text
	}) ?? false;
}
function getAcpBlockTranscriptText(blocks, pendingBlockSource, confirmedOnly = false) {
	const deliveredSources = new Set(blocks.filter((block) => block.delivered).map((block) => block.source));
	const recoveredSources = new Set(blocks.filter((block) => block.delivered === "final").map((block) => block.source));
	recoveredSources.delete(pendingBlockSource);
	for (const block of blocks) if (block.payload.text && !block.delivered) recoveredSources.delete(block.source);
	return blocks.flatMap((block) => {
		const sourceConfirmed = block.source ? block.source.complete && deliveredSources.has(block.source) || recoveredSources.has(block.source) : block.delivered;
		const text = !confirmedOnly || sourceConfirmed ? block.transcriptText : block.delivered ? block.payload.text : void 0;
		return text ? [text] : [];
	}).join("\n");
}
function joinAcpBlockText(blocks) {
	let text = "";
	let previousSource;
	for (const block of blocks) if (block.payload.text) {
		if (text && (!block.source || block.source !== previousSource)) text += "\n";
		text += block.payload.text;
		previousSource = block.source;
	}
	return text;
}
async function recoverAcpBlockText(state, params) {
	if (state.deliveredAnswerFinalToUser || !params.shouldRouteToOriginating && state.untrackedVisibleText !== "none" && !params.suppressBlockUserDelivery && state.deliveredVisibleText && !state.failedVisibleTextDelivery) return false;
	const blocks = state.blockTexts.filter((block) => block.needsFinalDelivery && (!params.onlyUndelivered || !block.delivered));
	let queued = false;
	for (const block of blocks) {
		if (params.abortSignal?.aborted) break;
		if (block.source && !params.suppressBlockUserDelivery && await shouldTreatDeliveredTextAsVisible({
			channel: params.channel,
			kind: "block",
			text: block.payload.text
		}) && hasBlockReplyDeliveryCustody(await block.source.settle())) continue;
		if (params.abortSignal?.aborted) break;
		queued = await block.deliver("final", true) || queued;
		if (block.delivered !== "final") break;
	}
	return queued;
}
function buildAcpTextContinuation(payload, text) {
	return copyReplyPayloadMetadata(payload, {
		text,
		replyToId: payload.replyToId,
		replyToTag: payload.replyToTag,
		replyToCurrent: payload.replyToCurrent,
		isCommentary: payload.isCommentary,
		isReasoning: payload.isReasoning
	});
}
//#endregion
//#region src/auto-reply/reply/dispatch-acp-delivery.ts
const routeReplyRuntimeLoader = createLazyImportLoader(() => import("./route-reply-CtxXrG1T.mjs"));
const channelPluginRuntimeLoader = createLazyImportLoader(() => import("./plugins-B8IhIS-G.mjs"));
const messageActionRuntimeLoader = createLazyImportLoader(() => import("./message-action-runner-kLqi-ene.mjs"));
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,
		blockTexts: [],
		accumulatedBlockTtsText: "",
		accumulatedFinalText: "",
		accumulatedDeliveredFinalText: "",
		pendingTranscriptOutcomes: [],
		cleanBlockTtsDirectiveText: shouldCleanTtsDirectiveText({
			cfg: params.cfg,
			preparedTtsPreferences: params.preparedTtsPreferences,
			ttsAuto: params.sessionTtsAuto,
			agentId: params.agentId,
			channelId: params.ttsChannel,
			accountId: resolvedAccountId
		}) ? createTtsDirectiveTextStreamCleaner() : void 0,
		deliveredFinalReply: false,
		pendingAnswerDelivery: false,
		pendingFinalTtsMedia: false,
		deliveredAnswerFinalToUser: false,
		deliveredFinalTtsMedia: false,
		deliveredVisibleText: false,
		failedVisibleTextDelivery: false,
		untrackedVisibleText: "none",
		routedCounts: {
			tool: 0,
			block: 0,
			final: 0
		},
		suppressionReason: void 0,
		toolMessageByCallId: /* @__PURE__ */ new Map()
	};
	let hasPendingDirectBlockReplyDelivery = false;
	let pendingBlockSource;
	let drainBlockText;
	const settleDirectVisibleText = async () => {
		await waitForReplyDispatcherIdle({ waitForIdle: async () => {
			await Promise.all(state.pendingTranscriptOutcomes);
		} }, params.abortSignal);
		if (params.abortSignal?.aborted) return;
		hasPendingDirectBlockReplyDelivery = false;
		if (state.untrackedVisibleText !== "pending") return;
		state.untrackedVisibleText = "settled";
		const receipt = await waitForReplyDispatcherIdle(params.dispatcher, params.abortSignal);
		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: ${formatErrorMessage(error)}`);
		});
	};
	const tryEditToolMessage = async (payload, toolCallId) => {
		const handle = state.toolMessageByCallId.get(toolCallId);
		if (!handle?.messageId) return false;
		const message = normalizeOptionalString(payload.text);
		if (!message) return false;
		try {
			const { runMessageAction } = await messageActionRuntimeLoader.load();
			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) => {
		if (kind === "block") setBlockReplyDelivery(Promise.resolve({ outcome: "cancelled" }));
		const transcriptSource = meta?.transcriptSource;
		const coveredBlocks = kind === "final" ? state.blockTexts.filter((block) => transcriptSource?.kind === "blocks" || block.needsFinalDelivery) : [];
		const coverFinalBlockText = (source, blocks = coveredBlocks) => {
			if (!source.text?.trim() || source.isCommentary || source.isReasoning || isReplyPayloadStatusNotice(source)) return;
			for (const block of blocks) block.needsFinalDelivery = false;
		};
		let visiblePayload = payload;
		if (!params.suppressUserDelivery) {
			const routed = params.shouldRouteToOriginating && routedChannel !== void 0;
			const messaging = routed ? (await channelPluginRuntimeLoader.load()).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;
					coverFinalBlockText(payload);
					if (kind === "block" && payload.text?.trim()) setBlockReplyDelivery(Promise.resolve({ outcome: "channel-transform" }), payload);
				}
				return false;
			}
			visiblePayload = prepared.payload;
		}
		const isStatusNotice = isReplyPayloadStatusNotice(visiblePayload);
		const rawBlockPayloadText = kind === "block" ? normalizeOptionalString(visiblePayload.text) : void 0;
		const rawBlockText = isStatusNotice ? void 0 : rawBlockPayloadText;
		let blockSource;
		if (rawBlockPayloadText) {
			const joinsBufferedTtsDirective = state.cleanBlockTtsDirectiveText?.hasBufferedDirectiveText() === true;
			if (rawBlockText) {
				if (state.accumulatedBlockTtsText.length > 0 && !joinsBufferedTtsDirective) state.accumulatedBlockTtsText += "\n";
				state.accumulatedBlockTtsText += rawBlockText;
			}
			if (state.cleanBlockTtsDirectiveText && rawBlockText) {
				if (!visiblePayload.isCommentary && !visiblePayload.isReasoning) blockSource = pendingBlockSource ?? createBlockReplySource();
				const text = state.cleanBlockTtsDirectiveText.push(rawBlockPayloadText);
				if (blockSource) {
					const hasPendingText = state.cleanBlockTtsDirectiveText.hasBufferedDirectiveText();
					blockSource.setComplete(!hasPendingText);
					pendingBlockSource = hasPendingText ? blockSource : void 0;
				}
				visiblePayload = copyReplyPayloadMetadata(visiblePayload, {
					...visiblePayload,
					text: text.trim() ? text : void 0
				});
			}
		}
		const rawFinalText = kind === "final" && !isStatusNotice ? normalizeOptionalString(visiblePayload.text) : void 0;
		if (rawFinalText && !transcriptSource) {
			if (state.accumulatedFinalText.length > 0) state.accumulatedFinalText += "\n";
			state.accumulatedFinalText += rawFinalText;
		}
		const transcriptFinalText = !transcriptSource ? rawFinalText : transcriptSource.kind === "final" ? transcriptSource.text : void 0;
		const sendPrepared = async (preparedPayload, deliveredBlock, skipTts = meta?.skipTts, sendKind = kind) => {
			const recoveringBlock = sendKind === "final" && kind === "block";
			const finalBlocks = recoveringBlock && deliveredBlock ? [deliveredBlock] : coveredBlocks;
			let outgoingPayload = recoveringBlock ? deliveredBlock?.delivered || params.suppressBlockUserDelivery ? buildAcpTextContinuation(preparedPayload, preparedPayload.text) : copyReplyPayloadMetadata(preparedPayload, { ...preparedPayload }) : preparedPayload;
			if (!hasOutboundReplyContent(outgoingPayload, { trimText: true })) return false;
			await startReplyLifecycleOnce();
			if (params.suppressUserDelivery) return false;
			if (sendKind === "block" && params.suppressBlockUserDelivery && !isStatusNotice && !outgoingPayload.isReasoning && !outgoingPayload.isCommentary) {
				if (!Boolean(outgoingPayload.mediaUrl || outgoingPayload.mediaUrls?.length || outgoingPayload.presentation || outgoingPayload.interactive || outgoingPayload.channelData)) return false;
				outgoingPayload = copyReplyPayloadMetadata(outgoingPayload, {
					...outgoingPayload,
					text: void 0
				});
			}
			const appliedTtsPayload = await maybeApplyAcpTts({
				preparedTtsPreferences: params.preparedTtsPreferences,
				payload: outgoingPayload,
				cfg: params.cfg,
				agentId: params.agentId,
				channel: params.ttsChannel,
				accountId: resolvedAccountId,
				kind: sendKind,
				inboundAudio: params.inboundAudio,
				ttsAuto: params.sessionTtsAuto,
				skipTts
			});
			const finalVisibleTextSource = sendKind === "final" && params.suppressBlockUserDelivery && state.cleanBlockTtsDirectiveText ? skipTts || outgoingPayload.isError || isReplyPayloadTtsSupplement(outgoingPayload) ? outgoingPayload.text : mergeDeferredFinalText(state.accumulatedBlockTtsText, outgoingPayload.text) : void 0;
			const ttsPayload = finalVisibleTextSource !== void 0 ? copyReplyPayloadMetadata(appliedTtsPayload, {
				...appliedTtsPayload,
				text: cleanDeferredFinalText(finalVisibleTextSource) || void 0
			}) : appliedTtsPayload;
			const hasFinalTtsMedia = sendKind === "final" && isReplyPayloadTtsSupplement(ttsPayload);
			const resolveVisibleText = (channel) => shouldTreatDeliveredTextAsVisible({
				channel,
				kind: sendKind,
				text: ttsPayload.text
			});
			const isAnswerBearingFinal = sendKind === "final" && (isCaptionedFinalTextPayload(outgoingPayload) || hasFinalTtsMedia && Boolean(ttsPayload.text?.trim()));
			const recordVisibleBlock = (tracksVisibleText, confirmed) => {
				state.deliveredVisibleText ||= tracksVisibleText && confirmed;
				if (deliveredBlock && tracksVisibleText) deliveredBlock.needsFinalDelivery = false;
			};
			const recordPendingDelivery = (tracksVisibleText) => {
				recordVisibleBlock(tracksVisibleText, false);
				const pendingAnswer = tracksVisibleText && sendKind !== "tool" && !isStatusNotice && !ttsPayload.isCommentary && !ttsPayload.isReasoning;
				state.pendingAnswerDelivery ||= pendingAnswer;
				coverFinalBlockText(ttsPayload, finalBlocks);
				state.pendingFinalTtsMedia ||= hasFinalTtsMedia;
			};
			const recordFinalReply = () => {
				if (sendKind === "final") {
					state.deliveredFinalReply = true;
					state.deliveredAnswerFinalToUser ||= !recoveringBlock && isAnswerBearingFinal && (!transcriptSource || transcriptSource.kind === "final");
					state.deliveredFinalTtsMedia ||= hasFinalTtsMedia;
					coverFinalBlockText(ttsPayload, finalBlocks);
				}
			};
			const recordDeliveredReply = (tracksVisibleText) => {
				if (deliveredBlock) deliveredBlock.delivered = sendKind === "final" ? "final" : "block";
				if ((rawFinalText || hasFinalTtsMedia) && transcriptSource?.kind === "blocks") for (const block of finalBlocks) block.delivered = "final";
				else if (transcriptFinalText) state.accumulatedDeliveredFinalText = state.accumulatedDeliveredFinalText ? `${state.accumulatedDeliveredFinalText}\n${transcriptFinalText}` : transcriptFinalText;
				recordFinalReply();
				recordVisibleBlock(tracksVisibleText, true);
			};
			if (params.shouldRouteToOriginating && params.originatingChannel && params.originatingTo) {
				const toolCallId = normalizeOptionalString(meta?.toolCallId);
				if (sendKind === "tool" && meta?.allowEdit === true && toolCallId) {
					if (await tryEditToolMessage(ttsPayload, toolCallId)) return true;
				}
				const tracksVisibleText = await resolveVisibleText(routedChannel);
				const { routeReply } = await routeReplyRuntimeLoader.load();
				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: sendKind,
					runId: params.runId
				});
				const outcome = resolveRoutedReplyDeliveryOutcome(result);
				const pending = outcome === "recovery-owned" || outcome === "failed-deliver";
				if (sendKind === "block") setBlockReplyDelivery(Promise.resolve({
					outcome,
					pending
				}), ttsPayload);
				if (deliveredBlock && result.suppressed && (tracksVisibleText || outcome === "channel-transform" && ttsPayload.text?.trim())) deliveredBlock.needsFinalDelivery = false;
				if (pending) {
					recordPendingDelivery(tracksVisibleText);
					return true;
				}
				if (shouldRetryReplyDispatch(outcome) && hasFinalTtsMedia && ttsPayload.text?.trim()) {
					if (!result.suppressed) logVerbose(`dispatch-acp: route-reply (acp/${sendKind}) failed: ${result.error ?? "unknown error"}`);
					return await deliver("final", { text: ttsPayload.text }, {
						skipTts: true,
						transcriptSource: transcriptSource ?? {
							kind: "final",
							text: rawFinalText ?? ""
						}
					});
				}
				if (!result.delivered && !result.suppressed) {
					if (tracksVisibleText) state.failedVisibleTextDelivery = true;
					logVerbose(`dispatch-acp: route-reply (acp/${sendKind}) failed: ${result.error ?? "unknown error"}`);
					return false;
				}
				if (result.suppressed) {
					if (outcome === "channel-transform") coverFinalBlockText(ttsPayload, finalBlocks);
					if (sendKind === "final") state.deliveredFinalReply = true;
					if (tracksVisibleText) state.deliveredVisibleText = true;
					return true;
				}
				if (!result.ok) logVerbose(`dispatch-acp: route-reply (acp/${sendKind}) partially failed after delivery: ${result.error ?? "unknown error"}`);
				if (sendKind === "tool" && meta?.toolCallId && result.messageId) state.toolMessageByCallId.set(meta.toolCallId, {
					channel: params.originatingChannel,
					accountId: resolvedAccountId,
					to: params.originatingTo,
					...threadId != null ? { threadId } : {},
					messageId: result.messageId
				});
				recordDeliveredReply(tracksVisibleText);
				state.routedCounts[sendKind] += 1;
				return true;
			}
			if (sendKind === "tool" && hasPendingDirectBlockReplyDelivery) {
				hasPendingDirectBlockReplyDelivery = false;
				await waitForReplyDispatcherIdle(params.dispatcher, params.abortSignal);
			}
			const tracksVisibleText = await resolveVisibleText(directChannel);
			const transcriptOutcome = sendKind !== "tool" ? captureReplyDispatchDeliveryOutcome(ttsPayload) : void 0;
			if (hasFinalTtsMedia && ttsPayload.text?.trim()) attachReplyDispatchUndeliveredFallback(ttsPayload, buildCaptionedFinalTextFallback(ttsPayload));
			const delivered = sendKind === "tool" ? params.dispatcher.sendToolResult(ttsPayload) : sendKind === "block" ? params.dispatcher.sendBlockReply(ttsPayload) : params.dispatcher.sendFinalReply(ttsPayload);
			if (sendKind === "block") setBlockReplyDelivery(delivered && transcriptOutcome?.isTracked() ? transcriptOutcome.promise.then((outcome) => ({
				outcome,
				pending: transcriptOutcome.hasPendingDelivery()
			})) : Promise.resolve(delivered ? {
				outcome: "failed-deliver",
				pending: true
			} : { outcome: "cancelled" }), ttsPayload);
			if (delivered && transcriptOutcome?.isTracked()) {
				const settlement = transcriptOutcome.promise.then((outcome) => {
					if (transcriptOutcome.hasPendingDelivery()) recordPendingDelivery(tracksVisibleText);
					else if (outcome === "delivered") recordDeliveredReply(tracksVisibleText);
					else {
						if (!shouldRetryReplyDispatch(outcome)) {
							if (deliveredBlock && ttsPayload.text?.trim()) deliveredBlock.needsFinalDelivery = false;
							coverFinalBlockText(ttsPayload, finalBlocks);
						}
						if (tracksVisibleText) {
							state.failedVisibleTextDelivery ||= outcome === "failed-before-deliver" || outcome === "failed-deliver";
							state.deliveredVisibleText ||= outcome === "failed-deliver";
						}
					}
				});
				state.pendingTranscriptOutcomes.push(settlement);
				if (sendKind === "final") await settlement;
			} else if (delivered) {
				recordFinalReply();
				if (tracksVisibleText) state.untrackedVisibleText = "pending";
			} else if (tracksVisibleText) state.failedVisibleTextDelivery = true;
			if (sendKind === "block" && delivered) hasPendingDirectBlockReplyDelivery = true;
			return delivered;
		};
		const recordBlock = (preparedPayload, transcriptText) => {
			const block = {
				payload: preparedPayload,
				transcriptText,
				source: blockSource,
				needsFinalDelivery: Boolean(preparedPayload.text),
				deliver: (sendKind, skipTts) => sendPrepared(block.payload, block, skipTts, sendKind)
			};
			state.blockTexts.push(block);
			return block;
		};
		const blockText = rawBlockPayloadText ? recordBlock(visiblePayload, rawBlockText) : void 0;
		if (state.cleanBlockTtsDirectiveText && rawBlockText) drainBlockText = state.cleanBlockTtsDirectiveText.hasBufferedDirectiveText() ? async (text) => {
			const tailPayload = buildAcpTextContinuation(visiblePayload, text);
			const tailBlock = recordBlock(tailPayload);
			const sendTail = async () => {
				setBlockReplyDelivery(Promise.resolve({ outcome: "cancelled" }), tailPayload);
				return params.abortSignal?.aborted ? false : await tailBlock.deliver("block", true);
			};
			return blockSource ? await blockSource.run(sendTail) ?? false : await sendTail();
		} : void 0;
		const send = () => blockText ? blockText.deliver("block") : sendPrepared(visiblePayload, void 0);
		return blockSource ? await blockSource.run(send) ?? false : await send();
	};
	return {
		startReplyLifecycle: startReplyLifecycleOnce,
		deliver,
		flushBlockText: async () => {
			if (params.abortSignal?.aborted) return;
			const text = state.cleanBlockTtsDirectiveText?.flush();
			const drain = drainBlockText;
			drainBlockText = void 0;
			pendingBlockSource?.setComplete(true);
			pendingBlockSource = void 0;
			if (text?.trim() && drain) await drain(text);
		},
		getAccumulatedVisibleBlockText: () => joinAcpBlockText(state.blockTexts),
		recoverBlockText: async (options) => {
			if (!params.suppressBlockUserDelivery) await settleDirectVisibleText();
			return await recoverAcpBlockText(state, {
				...params,
				channel: routedChannel ?? directChannel,
				onlyUndelivered: options?.onlyUndelivered
			});
		},
		getAccumulatedBlockTtsText: () => state.accumulatedBlockTtsText,
		getAccumulatedTranscriptText: () => state.accumulatedFinalText || getAcpBlockTranscriptText(state.blockTexts, pendingBlockSource),
		resolveAccumulatedDeliveredTranscriptText: async () => {
			await Promise.all(state.pendingTranscriptOutcomes);
			await Promise.all(state.blockTexts.flatMap((block) => block.source ? [block.source.settle()] : []));
			return state.accumulatedDeliveredFinalText || getAcpBlockTranscriptText(state.blockTexts, pendingBlockSource, true);
		},
		settleVisibleText: settleDirectVisibleText,
		hasDeliveredFinalReply: () => state.deliveredFinalReply,
		hasPendingAnswerDelivery: () => state.pendingAnswerDelivery,
		hasPendingFinalTtsMedia: () => state.pendingFinalTtsMedia,
		hasDeliveredAnswerFinalToUser: () => state.deliveredAnswerFinalToUser,
		hasDeliveredFinalTtsMedia: () => state.deliveredFinalTtsMedia,
		hasDeliveredVisibleText: () => state.deliveredVisibleText,
		hasFailedVisibleTextDelivery: () => state.failedVisibleTextDelivery,
		getDeliverySuppressionReason: () => state.suppressionReason,
		applyRoutedCounts: (counts) => {
			counts.tool += state.routedCounts.tool;
			counts.block += state.routedCounts.block;
			counts.final += state.routedCounts.final;
			return counts;
		}
	};
}
//#endregion
//#region src/auto-reply/reply/dispatch-acp-finalize.ts
const loadDispatchAcpTtsRuntime = createLazyPromise(() => import("./tts.runtime.js"));
const loadDispatchAcpManagerRuntime$1 = createLazyPromise(() => import("./dispatch-acp-manager.runtime.js"));
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,
		preparedTtsPreferences: params.preparedTtsPreferences,
		sessionAuto: params.sessionTtsAuto,
		agentId: params.agentId,
		channelId: params.ttsChannel,
		accountId: params.ttsAccountId
	});
	const canAttemptFinalTts = ttsMode === "final" && hasAccumulatedBlockText && ttsStatus != null && !(ttsStatus.autoMode === "inbound" && !params.inboundAudio);
	const shouldDeferVisibleTextForTts = params.shouldDeferVisibleTextForTts && canAttemptFinalTts;
	const accumulatedVisibleBlockText = shouldDeferVisibleTextForTts ? cleanDeferredFinalText(accumulatedBlockTtsText) : params.delivery.getAccumulatedVisibleBlockText();
	if (!shouldDeferVisibleTextForTts) await params.delivery.settleVisibleText();
	if (params.abortSignal?.aborted) return false;
	let queuedFinal = params.delivery.hasPendingAnswerDelivery() || params.delivery.hasPendingFinalTtsMedia() || params.delivery.hasDeliveredVisibleText() && !params.delivery.hasFailedVisibleTextDelivery();
	if (canAttemptFinalTts && !params.delivery.hasPendingFinalTtsMedia() && !params.delivery.hasDeliveredFinalTtsMedia()) try {
		const { maybeApplyTtsToPayload } = await loadDispatchAcpTtsRuntime();
		if (params.abortSignal?.aborted) return queuedFinal;
		const ttsSyntheticReply = await maybeApplyTtsToPayload({
			preparedTtsPreferences: params.preparedTtsPreferences,
			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({
				...ttsSyntheticReply,
				text: shouldDeferVisibleTextForTts ? accumulatedVisibleBlockText || void 0 : void 0,
				trustedLocalMedia: true
			}, accumulatedBlockTtsText, shouldDeferVisibleTextForTts ? void 0 : { visibleTextAlreadyDelivered: true });
			const delivered = await params.delivery.deliver("final", finalTtsPayload, { transcriptSource: { kind: "blocks" } });
			queuedFinal = queuedFinal || delivered;
		} else if (shouldDeferVisibleTextForTts && ttsSyntheticReply.text?.trim() || needsTtsFallback(true, accumulatedVisibleBlockText, ttsSyntheticReply.text)) {
			const delivered = await params.delivery.deliver("final", { text: ttsSyntheticReply.text }, {
				skipTts: true,
				transcriptSource: { kind: "blocks" }
			});
			queuedFinal = queuedFinal || delivered;
		}
	} catch (err) {
		logVerbose(`dispatch-acp: accumulated ACP block TTS failed: ${formatErrorMessage(err)}`);
	}
	queuedFinal = await params.delivery.recoverBlockText({ onlyUndelivered: ttsMode === "all" }) || queuedFinal;
	if (params.shouldEmitResolvedIdentityNotice) {
		const { readAcpSessionEntryAsync } = await loadDispatchAcpManagerRuntime$1();
		const currentSession = await readAcpSessionEntryAsync({
			cfg: params.cfg,
			sessionKey: params.sessionKey,
			agentId: params.agentId
		});
		if (params.abortSignal?.aborted) return queuedFinal;
		const currentMeta = currentSession?.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")),
					isStatusNotice: true
				});
				queuedFinal = queuedFinal || delivered;
			}
		}
	}
	return queuedFinal;
}
//#endregion
//#region src/auto-reply/reply/dispatch-acp-prompt.ts
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;
}
//#endregion
//#region src/auto-reply/reply/dispatch-acp.ts
const loadDispatchAcpManagerRuntime = createLazyPromise(() => import("./dispatch-acp-manager.runtime.js"));
const loadDispatchAcpAuditRuntime = createLazyPromise(() => import("./acp-lifecycle-FaSouywh.mjs"));
const loadDispatchAcpTranscriptRuntime = createLazyPromise(() => import("./dispatch-acp-transcript.runtime.js"));
function resolveAcpRequestId(ctx) {
	const id = ctx.MessageSidFull ?? ctx.MessageSid ?? ctx.MessageSidFirst ?? ctx.MessageSidLast;
	return normalizeOptionalString(id) ?? (typeof id === "number" || typeof id === "bigint" ? String(id) : generateSecureUuid());
}
async function tryDispatchAcpReplyCore(params) {
	const sessionKey = normalizeOptionalString(params.sessionKey);
	if (!sessionKey || params.bypassForCommand) return null;
	prepareChannelParticipantObservation(params.ctx);
	const inputRecorder = params.userTurnTranscriptRecorder;
	const input = bindUserTurnInput(inputRecorder, () => params.abortSignal?.throwIfAborted());
	const { getAcpSessionManager, maybeUnbindStaleBoundConversations, prepareAcpDispatchStart } = await loadDispatchAcpManagerRuntime();
	const acpManager = getAcpSessionManager();
	const acpResolution = await acpManager.resolveSessionAsync({
		cfg: params.cfg,
		sessionKey,
		agentId: resolveSessionAgentId({
			config: params.cfg,
			sessionKey,
			fallbackAgentId: params.ctx.AgentId
		}),
		assertCurrent: input.assertLifetimeCurrent
	});
	await input.withCurrent(() => {});
	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) ? normalizeUniqueTrimmedStringList([
		params.ctx.SessionKey,
		sessionKey,
		canonicalSessionKey
	]) : [];
	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 hasBoundConversationForSession = async () => {
		const channel = normalizeOptionalLowercaseString(params.ctx.OriginatingChannel ?? params.ctx.Surface ?? params.ctx.Provider) ?? "";
		if (!channel) return false;
		const accountId = normalizeOptionalLowercaseString(params.ctx.AccountId) ?? "";
		const configuredDefaultAccountId = params.cfg.channels?.[channel]?.defaultAccount;
		const normalizedAccountId = accountId || normalizeOptionalLowercaseString(configuredDefaultAccountId) || "default";
		const { listSessionBindingsBySessionAsync } = await loadDispatchAcpManagerRuntime();
		return (await listSessionBindingsBySessionAsync(canonicalSessionKey)).some((binding) => normalizeOptionalLowercaseString(binding.conversation.channel) === channel && (normalizeOptionalLowercaseString(binding.conversation.accountId) || "default") === normalizedAccountId && Boolean(normalizeOptionalString(binding.conversation.conversationId)));
	};
	const shouldEmitResolvedIdentityNotice = !params.suppressUserDelivery && identityPendingBeforeTurn && (params.ctx.MessageThreadId != null && String(params.ctx.MessageThreadId).trim() !== "" || await hasBoundConversationForSession());
	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 preparedTtsPreferences = params.preparedTtsPreferences ?? await prepareTtsPreferences();
	await input.withCurrent(() => {});
	const shouldDeferVisibleTextForTts = shouldDeferFinalTtsText({
		preparedTtsPreferences,
		cfg: params.cfg,
		ttsAuto: params.sessionTtsAuto,
		agentId: acpAgentId,
		channelId: params.ttsChannel,
		accountId: effectiveDispatchAccountId,
		inboundAudio: params.inboundAudio
	});
	let queuedFinal = false;
	const delivery = createAcpDispatchDeliveryCoordinator({
		...params,
		preparedTtsPreferences,
		agentId: acpAgentId,
		sessionKey: canonicalSessionKey,
		suppressBlockUserDelivery: shouldDeferVisibleTextForTts
	});
	const pendingAnswerText = params.ctx.agentText.trim();
	const persistInput = inputRecorder ? async () => {
		await input.withCurrent(() => {});
		await inputRecorder.persistApproved();
		await input.withCurrent(() => {});
		if (!inputRecorder.hasPersisted()) throw new Error("ACP input must be durably committed before dispatch.");
	} : void 0;
	try {
		if (pendingAnswerText && !params.images?.length && !params.extractedFileImages?.length && !hasInboundMediaForUnderstanding(params.ctx) && await claimPreparedPendingAgentQuestionAnswer({
			sessionKey: acpResolution.sessionKey,
			text: pendingAnswerText,
			sourceRecorder: inputRecorder,
			authority: {
				kind: "run",
				assertCurrent: input.assertNativeCurrent
			}
		}, () => assertPreparedConversationBindingRouteCurrent(params.ctx))) {
			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) && !(error instanceof PreparedQuestionAnswerRefusedError)) throw error;
		params.recordProcessed("error", {
			reason: error instanceof QuestionAnswerUnconfirmedError ? "acp_question_answer_unconfirmed" : "acp_question_answer_refused",
			error: error.message
		});
		const queuedNotice = await delivery.deliver("final", {
			text: error.message,
			isError: true
		}).catch((deliveryError) => {
			logVerbose(`dispatch-acp: question notice delivery failed: ${formatErrorMessage(deliveryError)}`);
			return false;
		});
		params.markIdle("message_error");
		return {
			queuedFinal: queuedNotice,
			counts: delivery.applyRoutedCounts(params.dispatcher.getQueuedCounts())
		};
	}
	const deliverDeferredTextFallback = async () => shouldDeferVisibleTextForTts ? await delivery.recoverBlockText() : false;
	const projector = createAcpReplyProjector({
		cfg: params.cfg,
		shouldSendToolSummaries: params.shouldSendToolSummaries,
		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 = (finalQueued, error) => {
		const counts = delivery.applyRoutedCounts(params.dispatcher.getQueuedCounts());
		const suppressionReason = counts.tool + counts.block + counts.final > 0 || finalQueued ? void 0 : delivery.getDeliverySuppressionReason();
		const acpStats = acpManager.getObservabilitySnapshot();
		logVerbose(`acp-dispatch: session=${sessionKey} outcome=${error ? `error code=${error.code}` : "ok"} latencyMs=${Date.now() - acpDispatchStartedAt} queueDepth=${acpStats.turns.queueDepth} activeRuntimes=${acpStats.runtimeCache.activeSessions}`);
		params.recordProcessed("completed", { reason: error ? `acp_error:${normalizeLowercaseStringOrEmpty(error.code)}` : suppressionReason ?? "acp_dispatch" });
		params.markIdle("message_completed");
		return {
			queuedFinal: finalQueued,
			counts
		};
	};
	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();
	const auditContext = {
		runId: auditRunId,
		sessionKey: canonicalSessionKey,
		agentId: acpAgentId,
		auditOnly
	};
	let auditStarted = false;
	let auditFinished = false;
	let auditTerminalOutcome;
	let auditStopReason;
	let auditResultStatus;
	let assistantTranscript;
	let terminalOutcome;
	const notifyDispatchStart = await prepareAcpDispatchStart({
		scope: participantTarget,
		sessionId: transcriptSessionId,
		runId: auditRunId,
		onAgentRunStart: params.onAgentRunStart,
		getResult: () => ({
			assistantTranscript,
			terminalOutcome
		})
	});
	await input.withCurrent(() => {});
	let auditEndFields;
	const resolveAuditEndFields = () => auditEndFields ??= auditRuntime.resolveAcpLifecycleEndFields(params.abortSignal, auditStopReason, auditResultStatus);
	const emitAuditStart = () => {
		if (auditStarted) return;
		auditStarted = true;
		completionSource = notifyDispatchStart();
		auditRuntime.emitAcpLifecycleStart({
			...auditContext,
			startedAt: Date.now(),
			completionSource
		});
	};
	const emitAuditTerminal = (error) => {
		if (auditFinished) return;
		emitAuditStart();
		auditFinished = true;
		const lifecycle = {
			...auditContext,
			toolTracker: auditToolTracker,
			completionSource
		};
		terminalOutcome = error ? auditRuntime.emitAcpLifecycleError({
			...lifecycle,
			...params.abortSignal ? { abortSignal: params.abortSignal } : {},
			...auditTerminalOutcome ? { terminalOutcome: auditTerminalOutcome } : {},
			error
		}) : auditRuntime.emitAcpLifecycleEnd({
			...lifecycle,
			endFields: resolveAuditEndFields()
		});
	};
	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 (params.toolsAllow !== void 0 && !params.toolsAllow.some((entry) => normalizeLowercaseStringOrEmpty(entry) === "*") || 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") {
			emitAuditTerminal(acpResolution.error);
			await maybeUnbindStaleBoundConversations({
				targetSessionKey: canonicalSessionKey,
				error: acpResolution.error
			});
			return finishAttempt(await delivery.deliver("final", {
				text: formatAcpRuntimeErrorText(acpResolution.error),
				isError: true
			}), acpResolution.error);
		}
		const agentPolicyError = resolveAcpAgentPolicyError(params.cfg, resolvedAcpAgent);
		if (agentPolicyError) {
			auditTerminalOutcome = "blocked";
			throw agentPolicyError;
		}
		const resolvedTurnAttachments = await resolveAgentTurnAttachments({
			ctx: params.ctx,
			cfg: params.cfg
		});
		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 = params.ctx.agentText.trim();
		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 recentHistoryImages = describedImageIndexes.size === 0 ? resolvedTurnAttachments.recentHistoryImages : [];
		const inlineAttachments = resolveInlineAgentImageAttachments(params.images);
		const extractedAttachments = resolveInlineAgentImageAttachments(extractedFileImages.map(stripExtractedFileImageMetadata));
		const useMediaAttachments = mediaAttachmentEntries.length > 0 && !(mediaAttachmentEntries.length === recentHistoryImages.length && (inlineAttachments.length > 0 || extractedAttachments.length > 0));
		const attachments = [...useMediaAttachments ? mediaAttachmentEntries : inlineAttachments.map((attachment) => ({
			attachment,
			sourceIndex: void 0
		})), ...extractedAttachments.map((attachment, index) => ({
			attachment,
			sourceIndex: extractedFileImages[index]?.attachmentIndex
		}))].map(({ attachment, sourceIndex }, sequence) => ({
			attachment,
			sourceIndex,
			sequence
		})).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 turnPromptText = useMediaAttachments ? appendRecentHistoryImageContext({
			promptText,
			images: recentHistoryImages
		}) : promptText;
		transcriptPromptText = turnPromptText;
		if (!turnPromptText && attachments.length === 0) {
			const counts = delivery.applyRoutedCounts(params.dispatcher.getQueuedCounts());
			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)}`);
		}
		admittedRunContext = await prepareChannelRunAdmission({
			cfg: params.cfg,
			runId: requestId,
			agentId: acpAgentId,
			ingressKind: "acp",
			boundary: "auto-reply.acp",
			evidence: readChannelContextAdmissionEvidence(params.ctx),
			gatewayLocalUserIngress: getGatewayLocalUserIngress(params.ctx)
		}).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 persistInput?.();
		await assertPreparedConversationBindingRouteCurrent(params.ctx);
		const turnInput = {
			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({
					...auditContext,
					toolTracker: auditToolTracker,
					...params.abortSignal ? { abortSignal: params.abortSignal } : {},
					event
				});
				if (event.type === "done") {
					auditStopReason = event.stopReason;
					auditResultStatus = event.status;
				}
				await projector.onEvent(event);
			}
		};
		await input.withCurrent(() => {
			if (getAdmittedRunDelegatedAuthority(turnAdmission) === void 0) throw new Error("ACP turn admission ended before input dispatch.");
			turnDispatched = true;
			return acpManager.runTurn(turnInput);
		});
		await projector.flush();
		await delivery.flushBlockText();
		if (auditResultStatus !== "cancelled" && !params.abortSignal?.aborted) queuedFinal = await finalizeAcpTurnOutput({
			preparedTtsPreferences,
			cfg: params.cfg,
			sessionKey: canonicalSessionKey,
			agentId: acpAgentId,
			delivery,
			inboundAudio: params.inboundAudio,
			sessionTtsAuto: params.sessionTtsAuto,
			ttsChannel: params.ttsChannel,
			ttsAccountId: effectiveDispatchAccountId,
			shouldDeferVisibleTextForTts,
			shouldEmitResolvedIdentityNotice,
			abortSignal: params.abortSignal
		}) || queuedFinal;
		if (auditResultStatus === "cancelled" || params.abortSignal?.aborted) {
			queuedFinal = await deliverDeferredTextFallback() || queuedFinal;
			await persistTranscript(await delivery.resolveAccumulatedDeliveredTranscriptText());
			queuedFinal = delivery.hasPendingAnswerDelivery() || delivery.hasPendingFinalTtsMedia() || delivery.hasDeliveredFinalReply() || queuedFinal;
			const counts = delivery.applyRoutedCounts(params.dispatcher.getQueuedCounts());
			params.recordProcessed("completed", { reason: "acp_aborted" });
			params.markIdle("message_aborted");
			emitAuditTerminal();
			return {
				queuedFinal,
				counts
			};
		}
		await persistTranscript(delivery.getAccumulatedTranscriptText());
		const result = finishAttempt(queuedFinal);
		emitAuditTerminal();
		return result;
	} catch (err) {
		const acpError = toAcpRuntimeError({
			error: err,
			fallbackCode: "ACP_TURN_FAILED",
			fallbackMessage: "ACP turn failed before completion."
		});
		emitAuditTerminal(acpError);
		await projector.flush();
		await delivery.flushBlockText();
		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, acpError);
	} finally {
		if (admittedRunContext) closeAdmittedRunDelegatedAuthority(admittedRunContext);
	}
}
//#endregion
export { tryDispatchAcpReplyCore };