UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

1,524 lines • 73.1 kB
import { l as normalizeOptionalString } from "./string-coerce-1VuiLaaR.mjs";
import { a as asOptionalRecord } from "./record-coerce-TIsquoVR.mjs";
import { r as truncateUtf16Safe } from "./utf16-slice-D_ngcYKd.mjs";
import "./utils-DgZn2dLu.mjs";
import { t as escapeRegExp } from "./regexp-BZyMFTlj.mjs";
import { t as formatErrorMessage } from "./errors-O33D01XW.mjs";
import { n as normalizeAgentId } from "./agent-id-D1-Q6vR4.mjs";
import { r as resolveAgentConfig } from "./agent-scope-config-CDYpzA0r.mjs";
import { M as parseAgentSessionKey, l as resolveAgentIdFromSessionKey, x as isCronRunSessionKey } from "./session-key-C09ow4h0.mjs";
import "./agent-scope-CG17uXFn.mjs";
import { l as getAgentEventLifecycleGeneration } from "./agent-events-GvNTkbe3.mjs";
import { s as getActivePluginChannelRegistry } from "./runtime-NsVZTERq.mjs";
import { v as resolveModelRefFromString } from "./model-selection-shared-DcM6hdAC.mjs";
import { r as getRuntimeConfig } from "./io.runtime-BsUXSsD4.mjs";
import "./config-DLycWFvB.mjs";
import { t as normalizeChatType } from "./chat-type-D-T98NfH.mjs";
import { a as isSilentReplyPayloadText, n as HEARTBEAT_TOKEN, r as SILENT_REPLY_TOKEN } from "./tokens-D4ghQG_g.mjs";
import { E as setReplyPayloadMetadata, S as markReplyPayloadForSourceSuppressionDelivery, c as getReplyPayloadMetadata, o as copyReplyPayloadMetadata } from "./reply-payload-NzglYOOT.mjs";
import { t as normalizeMediaReferenceForComparison } from "./media-reference-comparison-D61D3Ct7.mjs";
import { i as captureOwnedTranscriptWriteAssertion } from "./transcript-write-context-m7NE2aEm.mjs";
import { c as deferSessionEventWakePoll, f as markSessionEventWakeWorkStarted, i as HEARTBEAT_SKIP_REQUESTS_IN_FLIGHT, l as getSessionEventWakeAbortSignal, n as HEARTBEAT_SKIP_CRON_IN_PROGRESS, o as SESSION_EVENT_IDLE_RETRY_MS, p as requestSessionEventWake, r as HEARTBEAT_SKIP_NO_PENDING_EVENT, s as areSessionEventWakesEnabled, t as HEARTBEAT_SKIP_CHANNEL_NOT_READY } from "./heartbeat-wake-ba6LAy2M.mjs";
import { c as resolveSystemEventQueueKey, l as withSystemEventOwner, p as resolveIndicatorType, u as emitHeartbeatEvent } from "./system-event-ownership-BkQcgSZJ.mjs";
import { d as resolveSystemEventDeliveryContext, i as enqueueSystemEvent, l as peekSystemEventEntries, t as consumeSelectedSystemEventEntries } from "./system-events-BD0JkU8V.mjs";
import "./model-selection-CC48fyGE.mjs";
import { s as resolveEffectiveAgentRuntime } from "./thinking-runtime--sshEpUp.mjs";
import { t as isInternalSessionEffectsKey } from "./internal-session-key-CPayZwJR.mjs";
import { n as loadExactSessionEntry, o as loadExactSessionEntryReadOnly } from "./session-accessor.sqlite-exact-read-JW6IveXD.mjs";
import { h as listCronHeartbeatWaitOwners, l as hasActiveCronJobs, u as hasActiveCronJobsExceptMarkers } from "./active-jobs-BS2UT6i2.mjs";
import { c as resolveHeartbeatPromptCore, l as resolveHeartbeatPromptForResponseTool, o as isHeartbeatAcknowledgementText, r as HEARTBEAT_RESPONSE_TOOL_INSTRUCTIONS, s as isHeartbeatContentEffectivelyEmpty, u as stripHeartbeatToken } from "./heartbeat-BjLIgeQc.mjs";
import { t as getChannelPlugin } from "./registry-bbtfCBEf.mjs";
import "./plugins-9R-VsYQh.mjs";
import { o as loadSessionEntryReadOnly, s as patchSessionEntryCore } from "./session-accessor.sqlite-entry-Dn_MsDKD.mjs";
import { C as replaceGenericExternalRunFailureText } from "./user-facing-text-DC1sCo4O.mjs";
import "./transcript-only-openclaw-assistant-CzjHCvH2.mjs";
import { p as publishTranscriptUpdate } from "./session-history-eviction-DbJ8ofqr.mjs";
import { _ as readTranscriptEventMessage, g as readTranscriptEventId } from "./session-accessor.sqlite-read-DhXli1yO.mjs";
import { r as mergeSessionEntry } from "./types-BZlUzQZy.mjs";
import { o as persistSessionTranscriptTurn } from "./session-accessor-BDmTPdtx.mjs";
import { g as applySessionEntryLifecycleMutation } from "./session-accessor.reset-CwTHm9f4.mjs";
import { l as makeZeroUsageSnapshot } from "./usage-XmAH1cfG.mjs";
import { n as readActiveTranscriptEntryAnchor } from "./session-accessor.sqlite-transcript-anchor-eRtZH9vP.mjs";
import { n as readCommittedTranscriptMessageSequence } from "./session-accessor.sqlite-transcript-sequences-DRmuEgiP.mjs";
import { t as findTranscriptEvent } from "./session-transcript-match-DfPBIsFk.mjs";
import { r as readAssistantDisplayContent } from "./assistant-display-content-mzartsGP.mjs";
import { a as getHeartbeatToolNotificationText, s as selectHeartbeatToolResponse } from "./heartbeat-tool-response-BAjLTEaX.mjs";
import { a as isCronSystemEvent, c as isRelayableExecCompletionEvent, i as buildExecEventPrompt, l as isRestartContinuationEvent, o as isExecCompletionEvent, r as buildCronEventPrompt, s as isHeartbeatDeliveryAwarenessEvent, t as HEARTBEAT_DELIVERY_CONTEXT_KEY_PREFIX } from "./heartbeat-events-filter-DMpc12GN.mjs";
import { a as resolveMirroredTranscriptText } from "./transcript-DCh7fycW.mjs";
import "./session-state-events.kernel-CXzdFJ4D.mjs";
import { g as replyRunRegistry, m as listActiveReplyRunSessionKeys } from "./reply-run-registry.registry-5e3UVXZS.mjs";
import "./reply-run-registry-CxzSLEiU.mjs";
import { n as resolveCronJobsStorePathFromConfig } from "./paths-C7ju4So0.mjs";
import { i as resolveActiveEmbeddedRunSessionId, r as listActiveEmbeddedRunSessionKeys } from "./active-run-projections-xWOafyEQ.mjs";
import "./lanes-D7a4ntDu.mjs";
import { l as isCommandLaneTaskMarkerCurrent, s as getQueueSize } from "./command-queue-CFE60Avj.mjs";
import { o as resolveSessionWorkStartError } from "./lifecycle-BU4MFl1q.mjs";
import { t as parseReplyDirectives } from "./reply-directives-Ciui4dZv.mjs";
import { l as resolveOutboundPayloadMirrorText } from "./payloads-RF8yW6z7.mjs";
import { a as resolveSendableOutboundReplyParts } from "./reply-payload-parts-BtASEhL4.mjs";
import { a as resolveResponsePrefixTemplate } from "./normalize-reply-C8IYvn0x.mjs";
import { a as hasOutboundReplyContent } from "./reply-payload-DEDOYf-L.mjs";
import { c as resolveReplyOperationAbortReason, i as resolveReplyOperationAgentTurn, t as REPLY_OPERATION_RUN_STATE } from "./reply-operation-run-state-BdpfYSED.mjs";
import { r as clearBootstrapSnapshotOnSessionRollover } from "./bootstrap-cache-Dnyzgktr.mjs";
import { c as transitionMainSessionRecovery } from "./main-session-recovery-state-CPK7ott5.mjs";
import { n as withReplySystemEventContext } from "./system-event-session-key-d4mk05_u.mjs";
import { n as suppressPendingFinalDelivery } from "./dispatch-from-config.pending-final-DTi8lXmh.mjs";
import { t as buildOutboundSessionContext } from "./session-context-Kov7ff_A.mjs";
import { n as sendDurableMessageBatchCore } from "./send-CqAjSzNt.mjs";
import "./runtime-BZREclVS.mjs";
import { r as resolveSourceReplyDeliveryMode } from "./source-reply-delivery-mode-Do8TJ7GF.mjs";
import { o as resolveUserTimezone } from "./date-time-DNFKWNdt.mjs";
import { i as resolveHeartbeatSenderContext, r as resolveHeartbeatDeliveryTargetWithSessionRoute } from "./targets-BUerMF9S.mjs";
import { n as resolveSessionLane } from "./lanes-DQO4OUmF.mjs";
import { n as persistHeartbeatOutcome } from "./heartbeat-outcome-store-CRJePlvi.mjs";
import { a as resolveHeartbeatForWake, c as tryResolveAmbientHeartbeatAgentId, o as resolveHeartbeatIntervalMs, s as resolveHeartbeatTimeoutOverrideSeconds } from "./heartbeat-config--CDRLwW6.mjs";
import { t as isHeartbeatEnabledForAgent } from "./heartbeat-summary-DGkbnKda.mjs";
import { t as resolveRawAssistantAnswerText } from "./assistant-answer-text-BDCP6yWU.mjs";
import { t as appendCronStyleCurrentTimeLine } from "./current-time-CZi1S4GF.mjs";
import { i as resolveMessagingToolPayloadDedupe } from "./reply-payloads-dedupe-2wwcabmM.mjs";
import "./store-ChPMJI5O.mjs";
import { n as resolveHeartbeatTerminalToolFailure, t as resolveHeartbeatReplyPayload } from "./heartbeat-reply-payload-D-RdM_zK.mjs";
import { t as resolveDefaultModel } from "./directive-handling.defaults-mC8m3Xql.mjs";
import { t as prepareReplyConversation } from "./prompt-session-context-BxRPJusl.mjs";
import { t as createReplyPrefixContext } from "./reply-prefix-Bvtljso0.mjs";
import { t as createTypingCallbacks } from "./typing-C8KuE7vc.mjs";
import { t as resolveAgentOutboundIdentity } from "./identity-BLDSV5e3.mjs";
import { t as readCronScratchSnapshot } from "./scratch-read-D5KvLJ0l.mjs";
import { o as writeCronJobScratch } from "./scratch-store-CQLK4z6q.mjs";
import { a as restoreHeartbeatUpdatedAt, i as resolveStaleHeartbeatIsolatedSessionKey, r as resolveHeartbeatSessionSelection, t as resolveHeartbeatSession } from "./heartbeat-runner-session-T-RxWu6E.mjs";
import { r as resolveCronSession } from "./session-BPb5keTa.mjs";
import { t as resolveHeartbeatVisibility } from "./heartbeat-visibility-BK4csk9g.mjs";
import { a as heartbeatLog, i as resolveHeartbeatWakePayloadFlags, n as isConfiguredHeartbeatAgent, r as isTargetedUnscheduledWake, t as inferHeartbeatWakeSourceFromReason } from "./heartbeat-wake-policy-C3iBUwep.mjs";
import { createHash } from "node:crypto";
import { STREAM_ERROR_FALLBACK_TEXT } from "@openclaw/ai/internal/shared";
//#region src/infra/heartbeat-runner-config.ts
function resolveHeartbeatChannelPlugin(channel) {
	return getActivePluginChannelRegistry()?.channels.find((entry) => entry.plugin.id === channel)?.plugin ?? getChannelPlugin(channel);
}
function resolveHeartbeatPromptRaw(cfg, heartbeat) {
	return heartbeat?.prompt ?? cfg.agents?.defaults?.heartbeat?.prompt;
}
function resolveConfiguredHeartbeatPrompt(cfg, heartbeat) {
	return resolveHeartbeatPromptCore(resolveHeartbeatPromptRaw(cfg, heartbeat));
}
function resolveHeartbeatResponseToolPrompt(cfg, heartbeat) {
	return resolveHeartbeatPromptForResponseTool(resolveHeartbeatPromptRaw(cfg, heartbeat));
}
function resolveHeartbeatModelRef(params) {
	const { defaultProvider, defaultModel, aliasIndex } = resolveDefaultModel({
		cfg: params.cfg,
		agentId: params.agentId
	});
	const heartbeatRaw = normalizeOptionalString(params.heartbeat?.model) ?? normalizeOptionalString(params.cfg.agents?.defaults?.heartbeat?.model) ?? "";
	const heartbeatRef = heartbeatRaw ? resolveModelRefFromString({
		raw: heartbeatRaw,
		defaultProvider,
		aliasIndex
	})?.ref : void 0;
	if (heartbeatRef) return heartbeatRef;
	return {
		provider: normalizeOptionalString(params.entry?.providerOverride) ?? normalizeOptionalString(params.entry?.modelProvider) ?? defaultProvider,
		model: normalizeOptionalString(params.entry?.modelOverride) ?? normalizeOptionalString(params.entry?.model) ?? defaultModel
	};
}
function shouldUseHeartbeatResponseToolPrompt(params) {
	const chatType = normalizeChatType(params.chatType);
	const visibleReplies = chatType === "group" || chatType === "channel" ? params.cfg.messages?.groupChat?.visibleReplies ?? params.cfg.messages?.visibleReplies : params.cfg.messages?.visibleReplies;
	if (visibleReplies === "message_tool") return true;
	if (visibleReplies === "automatic") return false;
	const modelRef = resolveHeartbeatModelRef(params);
	return resolveEffectiveAgentRuntime({
		cfg: params.cfg,
		provider: modelRef.provider,
		modelId: modelRef.model,
		agentId: params.agentId,
		sessionKey: params.sessionKey,
		sessionEntry: params.entry
	}) === "codex";
}
function isHeartbeatTypingEnabled(params) {
	if (!params.hasChatDelivery) return false;
	return (resolveAgentConfig(params.cfg, params.agentId)?.typingMode ?? params.cfg.agents?.defaults?.typingMode) !== "never";
}
function resolveHeartbeatTypingIntervalSeconds(cfg) {
	const configured = cfg.agents?.defaults?.typingIntervalSeconds;
	return typeof configured === "number" && configured > 0 ? configured : void 0;
}
//#endregion
//#region src/infra/heartbeat-runner-prompt.ts
function truncateHeartbeatPreview(value) {
	return value ? truncateUtf16Safe(value, 200) : void 0;
}
/**
* Terminal no-op preflight (empty scratch, consumed exec events) must resolve
* before retryable busy guards; wakes carrying heartbeat tasks keep deferral.
*/
function shouldPreflightWakeBeforeBusy(source, scheduledEveryMs, scheduledTaskCount) {
	return scheduledTaskCount === 0 && (source === "interval" || source === "exec-event" && !(typeof scheduledEveryMs === "number" && Number.isSafeInteger(scheduledEveryMs) && scheduledEveryMs > 0));
}
async function resolveHeartbeatPreflight(params) {
	let monitorScratch;
	try {
		monitorScratch = await readCronScratchSnapshot(resolveCronJobsStorePathFromConfig(params.cfg), {
			kind: "heartbeat",
			agentId: params.agentId
		});
	} catch (error) {
		heartbeatLog.warn(`heartbeat: scratch read failed: ${formatErrorMessage(error)}`);
	}
	const wakeFlags = resolveHeartbeatWakePayloadFlags({
		source: params.source,
		reason: params.reason
	});
	const session = resolveHeartbeatSessionSelection(params.cfg, params.agentId, params.heartbeat, params.sessionKey);
	const pendingEventEntries = peekSystemEventEntries(resolveSystemEventQueueKey(session.sessionKey, params.agentId)).filter((event) => !isHeartbeatDeliveryAwarenessEvent(event));
	const turnSourceDeliveryContext = resolveSystemEventDeliveryContext(pendingEventEntries);
	const hasTaggedCronEvents = pendingEventEntries.some((event) => event.contextKey?.startsWith("cron:"));
	const shouldInspectWakePendingEvents = wakeFlags.isWakePayload && session.inspectsRunQueue;
	const shouldInspectPendingEvents = wakeFlags.isExecEventWake || wakeFlags.isCronWake || shouldInspectWakePendingEvents || hasTaggedCronEvents;
	const shouldBypassScratchGates = wakeFlags.isExecEventWake || wakeFlags.isCronWake || wakeFlags.isWakePayload || hasTaggedCronEvents;
	const heartbeatScratchContent = monitorScratch?.state.scratch?.content;
	const basePreflight = {
		...wakeFlags,
		session,
		pendingEventEntries,
		turnSourceDeliveryContext,
		shouldInspectPendingEvents,
		authoritativeScheduledTick: typeof params.scheduledEveryMs === "number" && Number.isSafeInteger(params.scheduledEveryMs) && params.scheduledEveryMs > 0,
		...monitorScratch?.jobId ? {
			scratchJobId: monitorScratch.jobId,
			scratchRevision: monitorScratch.state.currentRevision
		} : {},
		...!shouldBypassScratchGates && heartbeatScratchContent !== void 0 ? { heartbeatScratchContent } : {}
	};
	if (wakeFlags.isExecEventWake && !basePreflight.authoritativeScheduledTick && !params.scheduledTasks?.length && !hasTaggedCronEvents && !pendingEventEntries.some((event) => isExecCompletionEvent(event.text))) return {
		...basePreflight,
		skipReason: HEARTBEAT_SKIP_NO_PENDING_EVENT
	};
	if (shouldBypassScratchGates || params.scheduledTasks?.length || heartbeatScratchContent === void 0) return basePreflight;
	if (isHeartbeatContentEffectivelyEmpty(heartbeatScratchContent)) return {
		...basePreflight,
		skipReason: "empty-heartbeat-file"
	};
	return basePreflight;
}
function appendHeartbeatScratch(prompt, heartbeatScratchContent) {
	if (!heartbeatScratchContent) return prompt;
	const directives = heartbeatScratchContent.trim();
	if (!directives || prompt.includes(directives)) return prompt;
	return `${prompt}\n\nHeartbeat monitor scratch:\n${directives}`;
}
function resolveHeartbeatRunPrompt(params) {
	const pendingEventEntries = params.preflight.pendingEventEntries;
	const genericEvents = [];
	const cronEvents = [];
	const execEvents = [];
	const cronNoise = [];
	for (const event of pendingEventEntries) if (event.contextKey?.startsWith("session-created:")) genericEvents.push(event);
	else if (isExecCompletionEvent(event.text)) {
		if (params.preflight.shouldInspectPendingEvents) execEvents.push(event);
	} else if (params.preflight.isCronWake || event.contextKey?.startsWith("cron:")) (isCronSystemEvent(event.text) ? cronEvents : cronNoise).push(event);
	else genericEvents.push(event);
	const hasExecCompletion = execEvents.length > 0;
	const hasRelayableExecCompletion = params.canRelayToUser && execEvents.some((event) => isRelayableExecCompletionEvent(event.text));
	const hasCronEvents = cronEvents.length > 0;
	const hasBackgroundTaskEvent = params.preflight.session.inspectsRunQueue && genericEvents.some((event) => event.contextKey?.startsWith("task:"));
	if (params.scheduledTasks.length > 0) return {
		prompt: appendHeartbeatScratch(`Run the following periodic tasks (only those due based on their intervals):

${params.scheduledTasks.map((task) => `- ${task.name}: ${task.prompt}`).join("\n")}

${params.useHeartbeatResponseTool ? `After completing all due tasks:\n${HEARTBEAT_RESPONSE_TOOL_INSTRUCTIONS}` : `After completing all due tasks, reply ${SILENT_REPLY_TOKEN}.`}`, params.heartbeatScratchContent),
		hasTaskContinuation: hasBackgroundTaskEvent,
		hasExecCompletion: false,
		hasRelayableExecCompletion: false,
		hasCronEvents: false,
		usesHeartbeatResponseTool: params.useHeartbeatResponseTool,
		genericEvents,
		inspectedSystemEventsToConsume: cronNoise
	};
	return {
		prompt: appendHeartbeatScratch(hasExecCompletion || hasCronEvents ? (hasExecCompletion ? buildExecEventPrompt : buildCronEventPrompt)((hasExecCompletion ? execEvents : cronEvents).map((event) => event.text), {
			deliverToUser: params.canRelayToUser,
			useHeartbeatResponseTool: params.useHeartbeatResponseTool
		}) : params.useHeartbeatResponseTool ? resolveHeartbeatResponseToolPrompt(params.cfg, params.heartbeat) : resolveConfiguredHeartbeatPrompt(params.cfg, params.heartbeat), params.heartbeatScratchContent),
		hasTaskContinuation: hasExecCompletion || hasBackgroundTaskEvent || cronEvents.some((event) => event.contextKey?.startsWith("task:")),
		hasExecCompletion,
		hasRelayableExecCompletion,
		hasCronEvents,
		usesHeartbeatResponseTool: params.useHeartbeatResponseTool,
		genericEvents,
		inspectedSystemEventsToConsume: [...cronNoise, ...hasExecCompletion ? execEvents : cronEvents]
	};
}
//#endregion
//#region src/infra/heartbeat-delivery-normalization.ts
function stripLeadingHeartbeatResponsePrefix(text, responsePrefix) {
	const normalizedPrefix = responsePrefix?.trim();
	if (!normalizedPrefix) return text;
	const prefixPattern = new RegExp(`^${escapeRegExp(normalizedPrefix)}(?=$|\\s|[\\p{P}\\p{S}])\\s*`, "iu");
	return text.replace(prefixPattern, "");
}
function isStreamErrorFallbackPlaceholderOnly(text) {
	let remaining = text.trim();
	if (!remaining) return false;
	while (remaining.startsWith(STREAM_ERROR_FALLBACK_TEXT)) remaining = remaining.slice(STREAM_ERROR_FALLBACK_TEXT.length).trimStart();
	return remaining.length === 0;
}
const TRAILING_HEARTBEAT_NOTIFY_FALSE_RE = /(?:^|[\r\n])[ \t]*notify=false[ \t]*(?:\r?\n[ \t]*)*$/i;
function stripTrailingHeartbeatNotifyFalse(text) {
	const match = TRAILING_HEARTBEAT_NOTIFY_FALSE_RE.exec(text);
	return match ? {
		text: text.slice(0, match.index).trimEnd(),
		silent: true
	} : {
		text,
		silent: false
	};
}
function normalizeHeartbeatReply(payload, responsePrefix, ackMaxChars, mode = "heartbeat") {
	const textForStrip = stripLeadingHeartbeatResponsePrefix(typeof payload.text === "string" ? payload.text : "", responsePrefix);
	const isSilentReply = isSilentReplyPayloadText(textForStrip);
	const stripped = stripHeartbeatToken(isSilentReply ? "" : textForStrip, {
		mode,
		maxAckChars: ackMaxChars
	});
	const hasMedia = resolveSendableOutboundReplyParts(payload).hasMedia;
	const notifyFalse = stripTrailingHeartbeatNotifyFalse(stripped.text);
	notifyFalse.silent ||= isSilentReply;
	const isInternalPlaceholderOnly = isStreamErrorFallbackPlaceholderOnly(notifyFalse.text);
	let finalText = stripped.shouldSkip && !hasMedia || isInternalPlaceholderOnly ? "" : notifyFalse.text;
	if (responsePrefix && finalText && !finalText.startsWith(responsePrefix)) finalText = `${responsePrefix} ${finalText}`;
	return {
		shouldSkip: !hasMedia && finalText.trim().length === 0,
		text: finalText,
		hasMedia,
		isInternalPlaceholderOnly,
		...notifyFalse.silent ? { silent: true } : {}
	};
}
function normalizeHeartbeatToolNotification(response, responsePrefix) {
	let finalText = getHeartbeatToolNotificationText(response);
	if (responsePrefix && finalText && !finalText.startsWith(responsePrefix)) finalText = `${responsePrefix} ${finalText}`;
	return {
		shouldSkip: finalText.trim().length === 0,
		text: finalText,
		hasMedia: false,
		isInternalPlaceholderOnly: false,
		...response.notify ? {} : { silent: true }
	};
}
function classifyHeartbeatAgentOutcome(params) {
	const { agentRunFailed, heartbeatToolResponse, heartbeatTerminalToolFailure, replyPayload } = params.agentRun;
	const replyMetadata = replyPayload ? getReplyPayloadMetadata(replyPayload) : void 0;
	const hasExplicitFailure = Boolean(heartbeatTerminalToolFailure || agentRunFailed);
	const shouldSuppressSourceReply = params.suppressUnmarkedSourceReplies && !params.hasRelayableExecCompletion && replyPayload && replyPayload.isError !== true && replyMetadata?.deliverDespiteSourceReplySuppression !== true && (!hasExplicitFailure && !heartbeatToolResponse || agentRunFailed && !heartbeatTerminalToolFailure);
	if (heartbeatToolResponse && !heartbeatToolResponse.notify && !hasExplicitFailure) return {
		kind: "ack",
		eventStatus: "ok-token",
		preview: truncateHeartbeatPreview(heartbeatToolResponse.summary),
		response: heartbeatToolResponse
	};
	if (shouldSuppressSourceReply && !hasExplicitFailure) return {
		kind: "ack",
		eventStatus: "ok-token",
		silent: true
	};
	if (!heartbeatToolResponse && !hasExplicitFailure && (!replyPayload || !hasOutboundReplyContent(replyPayload))) return {
		kind: "ack",
		eventStatus: "ok-empty"
	};
	const mode = params.hasRelayableExecCompletion ? "message" : "heartbeat";
	const normalized = heartbeatToolResponse && !shouldSuppressSourceReply && !(hasExplicitFailure && replyPayload) ? normalizeHeartbeatToolNotification(heartbeatToolResponse, params.responsePrefix) : normalizeHeartbeatReply(shouldSuppressSourceReply ? {} : replyPayload ?? {}, params.responsePrefix, params.ackMaxChars, mode);
	if (agentRunFailed && params.useHeartbeatFailureCopy) {
		const replacement = replaceGenericExternalRunFailureText(normalized.text);
		if (replacement.replaced) {
			normalized.text = replacement.text;
			normalized.shouldSkip = false;
		}
	}
	const hasStructuredReplyContent = !shouldSuppressSourceReply && (!heartbeatToolResponse || agentRunFailed) && replyPayload !== void 0 && hasOutboundReplyContent({
		...replyPayload,
		text: void 0,
		mediaUrl: void 0,
		mediaUrls: void 0
	});
	const shouldSkipMain = !agentRunFailed && heartbeatToolResponse?.notify === false || normalized.shouldSkip && !normalized.hasMedia && (!hasStructuredReplyContent || normalized.isInternalPlaceholderOnly);
	if (hasExplicitFailure) return {
		kind: "failure",
		reason: heartbeatTerminalToolFailure ? "agent-tool-failure" : "agent-runner-failure",
		...heartbeatTerminalToolFailure ? { previewText: heartbeatToolResponse?.summary || heartbeatTerminalToolFailure.toolName } : {},
		replyPayload: shouldSuppressSourceReply ? void 0 : replyPayload,
		normalized,
		shouldSkipMain
	};
	if (shouldSkipMain) return {
		kind: "ack",
		eventStatus: "ok-token",
		silent: normalized.silent && !(mode === "heartbeat" && isSilentReplyPayloadText(replyPayload?.text))
	};
	return {
		kind: "delivery",
		response: heartbeatToolResponse,
		normalized,
		hasStructuredReplyContent,
		replyPayload: heartbeatToolResponse ? void 0 : replyPayload,
		mediaUrls: heartbeatToolResponse || !replyPayload ? [] : resolveSendableOutboundReplyParts(replyPayload).mediaUrls
	};
}
//#endregion
//#region src/infra/heartbeat-session-publication.ts
/** Publishes an admitted heartbeat final before its completion occurrences can settle. */
async function publishHeartbeatSessionReply(params) {
	let acceptedPublication;
	try {
		const { text, mediaUrls } = resolveSendableOutboundReplyParts(params.payload);
		const occurrences = [...new Set(params.occurrenceIds)].toSorted();
		if (occurrences.length === 0 || occurrences.some((id) => !id.trim())) return {
			ok: false,
			reason: "heartbeat session publication has no completion identity"
		};
		const scope = {
			agentId: params.agentId,
			storePath: params.storePath,
			sessionKey: params.sessionKey,
			sessionId: params.expectedGeneration.sessionId
		};
		const assertOwnedWrite = captureOwnedTranscriptWriteAssertion(scope);
		const metadata = getReplyPayloadMetadata(params.payload);
		const authority = metadata?.sessionWriterDeliveryAuthority;
		const initial = loadSessionEntryReadOnly({
			...scope,
			readConsistency: "latest"
		});
		const writerRunId = authority?.expectedWriterRunId ?? initial?.activeWriterRunId;
		const expected = {
			expectedSessionId: scope.sessionId,
			expectedLifecycleRevision: params.expectedGeneration.lifecycleRevision ?? null,
			...writerRunId ? { expectedWriterRunId: writerRunId } : {}
		};
		const assertCurrent = () => {
			params.signal?.throwIfAborted();
			assertOwnedWrite();
			if (authority && (authority.sessionKey !== scope.sessionKey || authority.agentId !== void 0 && authority.agentId !== scope.agentId || authority.expectedSessionId !== scope.sessionId || authority.expectedLifecycleRevision !== void 0 && authority.expectedLifecycleRevision !== params.expectedGeneration.lifecycleRevision)) throw new Error("heartbeat publication no longer owns the active transcript");
		};
		const assertTargetCurrent = (messageId) => {
			assertCurrent();
			const current = loadSessionEntryReadOnly({
				...scope,
				readConsistency: "latest"
			});
			if (!scope.sessionId || current?.sessionId !== scope.sessionId || current.lifecycleRevision !== params.expectedGeneration.lifecycleRevision || current.activeWriterRunId !== writerRunId || messageId && !readActiveTranscriptEntryAnchor({
				...scope,
				entryId: messageId
			})) throw new Error("heartbeat publication no longer owns the active transcript");
			const unavailable = resolveSessionWorkStartError(scope.sessionKey, current, {
				...expected,
				purpose: "accepted-result-settlement"
			});
			if (unavailable) throw new Error(unavailable);
		};
		assertTargetCurrent();
		const mirror = metadata?.sourceReplyTranscriptMirror?.transcriptOwner ? metadata.sourceReplyTranscriptMirror : void 0;
		if (mirror && (!mirror.idempotencyKey || mirror.sessionKey !== scope.sessionKey || mirror.expectedSessionId && mirror.expectedSessionId !== scope.sessionId || mirror.agentId && mirror.agentId !== scope.agentId)) return {
			ok: false,
			reason: "heartbeat source receipt belongs to another target"
		};
		const owned = metadata?.assistantTranscriptOwned === true || metadata?.assistantMessageIndex !== void 0 || mirror !== void 0;
		if (!owned && (!text.trim() || mediaUrls.length > 0)) return {
			ok: false,
			reason: "heartbeat session publication requires a text-only reply"
		};
		const key = `heartbeat-completion:${createHash("sha256").update(JSON.stringify([
			scope.sessionId,
			expected.expectedLifecycleRevision,
			occurrences
		])).digest("hex")}`;
		const ownedKey = mirror?.idempotencyKey ?? metadata?.assistantTranscriptIdempotencyKey;
		const lookupKey = owned ? ownedKey : key;
		let prior = lookupKey && (!owned || mirror || writerRunId) ? await findTranscriptEvent(scope, {
			kind: "idempotency",
			key: lookupKey,
			assistant: true,
			...owned && mirror ? { deliveryMirror: true } : {},
			...owned && !mirror ? { runId: writerRunId } : {}
		}) : void 0;
		if (owned && !ownedKey && writerRunId) prior = await findTranscriptEvent(scope, {
			kind: "active-assistant",
			runId: writerRunId
		});
		const priorMessage = prior && readTranscriptEventMessage(prior.event);
		const priorId = prior && readTranscriptEventId(prior.event);
		const answer = parseReplyDirectives(resolveRawAssistantAnswerText(priorMessage));
		const originalMedia = mirror?.mediaUrls ?? metadata?.assistantTranscriptMediaUrls ?? mediaUrls;
		const storedMedia = asOptionalRecord(priorMessage?.openclawDelivery)?.mediaUrls;
		const displayMedia = readAssistantDisplayContent(priorMessage).filter((block) => [
			"image",
			"audio",
			"video",
			"attachment"
		].includes(String(block.type)));
		const receiptMedia = displayMedia.length > 0 && Array.isArray(storedMedia) && storedMedia.every((source) => typeof source === "string") ? storedMedia : answer.mediaUrls ?? [];
		const matchesMedia = (displayMedia.length === 0 || displayMedia.length === originalMedia.length) && receiptMedia.length === originalMedia.length && originalMedia.length === mediaUrls.length && receiptMedia.every((source, index) => normalizeMediaReferenceForComparison(source) === normalizeMediaReferenceForComparison(originalMedia[index]));
		if (prior && (!priorMessage || !priorId) || owned && (!priorId || !priorMessage || !text.trim() && mediaUrls.length === 0 || answer.text.trim() !== (mirror ? mirror.text ?? "" : params.sourceText ?? text).trim() || !matchesMedia)) return {
			ok: false,
			reason: "heartbeat runtime final has no matching committed receipt"
		};
		assertTargetCurrent(priorId);
		const content = [{
			type: "text",
			text: text.trim()
		}];
		const message = owned && priorMessage ? priorMessage : {
			...priorMessage ?? {
				role: "assistant",
				content,
				api: "openclaw-transcript",
				provider: "openclaw",
				model: "automation-result",
				usage: makeZeroUsageSnapshot(),
				stopReason: "stop",
				timestamp: Date.now(),
				idempotencyKey: key
			},
			content
		};
		const attachMedia = displayMedia.length > 0 ? (await import("./managed-image-attachments-DHn7r_a7.mjs")).attachManagedOutgoingMediaToMessage : void 0;
		assertTargetCurrent(priorId);
		const committed = await persistSessionTranscriptTurn(scope, {
			...expected,
			config: params.cfg,
			assertCurrent,
			acceptedResultGuard: {
				expectedWriterRunId: writerRunId ?? null,
				errorMessage: "heartbeat publication no longer owns the active transcript"
			},
			messages: [{
				message,
				...priorId ? {
					eventId: priorId,
					predicate: {
						kind: "active-entry",
						entryId: priorId,
						errorMessage: "heartbeat publication no longer owns the active transcript"
					}
				} : {},
				idempotencyLookup: "scan"
			}],
			touchSessionEntry: !owned,
			updateMode: "none",
			onMessageCommitted: (receipt, acceptCompletion) => {
				assertTargetCurrent(receipt.messageId);
				const publish = () => {
					const messageSeq = readCommittedTranscriptMessageSequence(receipt);
					assertTargetCurrent(receipt.messageId);
					return publishTranscriptUpdate(scope, receipt.appended ? {
						lifecycleRevision: expected.expectedLifecycleRevision ?? void 0,
						message: receipt.message,
						messageId: receipt.messageId,
						...messageSeq !== void 0 ? { messageSeq } : {}
					} : {}).then(() => ({
						ok: true,
						messageId: receipt.messageId
					}), (error) => ({
						ok: false,
						reason: formatErrorMessage(error)
					}));
				};
				if (!attachMedia) {
					acceptedPublication = publish();
					return;
				}
				const completion = attachMedia({
					messageId: receipt.messageId,
					blocks: readAssistantDisplayContent(receipt.message)
				}).then((attached) => {
					if (!attached) throw new Error("heartbeat source receipt media custody is unavailable");
					return publish();
				});
				acceptedPublication = completion.catch((error) => ({
					ok: false,
					reason: formatErrorMessage(error)
				}));
				acceptCompletion(async () => {
					await completion;
				});
			}
		});
		return acceptedPublication ?? {
			ok: false,
			reason: committed.rejectedReason ?? "heartbeat publication rejected"
		};
	} catch (error) {
		if (acceptedPublication) {
			const receipt = await acceptedPublication;
			if (receipt.ok) heartbeatLog.warn("heartbeat: publication accepted before owned-write cleanup failed", { error: formatErrorMessage(error) });
			return receipt;
		}
		return {
			ok: false,
			reason: formatErrorMessage(error)
		};
	}
}
//#endregion
//#region src/infra/heartbeat-dispatch.ts
function createHeartbeatDispatch(opts, wake, prepared) {
	const policy = {
		opts,
		wake,
		prepared,
		prepareReply: (result, state) => prepareHeartbeatDispatchReply(policy, result, state)
	};
	return policy;
}
const FIRST_HEARTBEAT_ALERT_PREAMBLE = "First heartbeat alert: your bot runs periodic background checks and messages you only when something needs attention. Run `openclaw config set agents.defaults.heartbeat.target \"none\"` to keep these internal.";
const MAX_HEARTBEAT_TARGET_AWARENESS_CHARS = 1e3;
function prepareHeartbeatTargetAwareness(params) {
	const sessionKey = params.targetSessionKey?.trim();
	if (!sessionKey || sessionKey === params.runSessionKey) return;
	try {
		if (resolveAgentIdFromSessionKey(sessionKey, params.agentId) !== params.agentId) return;
		const scope = {
			agentId: params.agentId,
			storePath: params.storePath,
			sessionKey
		};
		const entry = loadExactSessionEntryReadOnly(scope)?.entry;
		if (!entry?.sessionId) return;
		const expectedSessionId = entry.sessionId;
		const expectedLifecycleRevision = entry.lifecycleRevision;
		const idempotencyKey = `${HEARTBEAT_DELIVERY_CONTEXT_KEY_PREFIX}${params.startedAt}:${params.runSessionKey}`;
		return (payload) => {
			try {
				const latest = loadExactSessionEntryReadOnly(scope)?.entry;
				if (latest?.sessionId !== expectedSessionId || latest.lifecycleRevision !== expectedLifecycleRevision) return;
				const deliveredText = resolveMirroredTranscriptText({
					text: payload.hookContent ?? resolveOutboundPayloadMirrorText(payload),
					mediaUrls: payload.mediaUrls
				});
				if (!deliveredText) return;
				const text = truncateUtf16Safe(deliveredText, MAX_HEARTBEAT_TARGET_AWARENESS_CHARS);
				const suffix = text.length < deliveredText.length ? "\n[truncated]" : "";
				enqueueSystemEvent(`A heartbeat delivered this message to this channel:\n${text}${suffix}`, withSystemEventOwner({
					sessionKey,
					contextKey: idempotencyKey
				}, params.agentId));
			} catch (error) {
				heartbeatLog.warn("heartbeat: failed to queue target session awareness", { error: formatErrorMessage(error) });
			}
		};
	} catch (error) {
		heartbeatLog.warn("heartbeat: failed to resolve existing target session projection", { error: formatErrorMessage(error) });
		return;
	}
}
/** Monitoring decides which final is public before ordinary dispatch can send it. */
async function prepareHeartbeatDispatchReply(policy, replyResult, runState) {
	const { opts, wake, prepared } = policy;
	const { cfg, agentId, startedAt, preflight, scheduledTasks, wakeSource } = wake;
	const { delivery, visibility, sessionKey, storePath, runSessionKey, previousUpdatedAt } = prepared;
	const replies = replyResult ? Array.isArray(replyResult) ? replyResult : [replyResult] : [];
	const selected = resolveHeartbeatReplyPayload(replyResult);
	const execution = resolveReplyOperationAgentTurn(runState);
	const heartbeatResponse = selectHeartbeatToolResponse(replyResult);
	const response = heartbeatResponse?.response;
	const admissionBusy = runState.admission?.status === "skipped" && runState.admission.reason === "active-run" && !response && (!selected || !hasOutboundReplyContent(selected));
	if (execution === "cancelled" || execution === "superseded" || admissionBusy) {
		const reason = execution === "superseded" ? "preempted" : execution === "cancelled" ? "agent-runner-cancelled" : HEARTBEAT_SKIP_REQUESTS_IN_FLIGHT;
		policy.result = {
			status: "skipped",
			reason
		};
		emitHeartbeatEvent({
			status: "skipped",
			reason,
			durationMs: Date.now() - startedAt
		});
		return {};
	}
	const channel = delivery.channel !== "none" ? delivery.channel : void 0;
	const committed = resolveMessagingToolPayloadDedupe({
		config: cfg,
		messageProvider: channel,
		originatingTo: delivery.to,
		originatingThreadId: delivery.threadId,
		accountId: delivery.accountId,
		messagingToolSentTargets: runState.messagingToolSentTargets
	});
	const failure = resolveHeartbeatTerminalToolFailure(replyResult);
	const responsePrefix = resolveResponsePrefixTemplate(prepared.replyPrefix.responsePrefix, prepared.replyPrefix.responsePrefixContextProvider());
	const outcome = classifyHeartbeatAgentOutcome({
		agentRun: {
			agentRunFailed: execution === "failed",
			heartbeatToolResponse: response,
			heartbeatTerminalToolFailure: failure,
			replyPayload: selected
		},
		useHeartbeatFailureCopy: prepared.useHeartbeatFailureCopy,
		hasRelayableExecCompletion: prepared.hasRelayableExecCompletion,
		suppressUnmarkedSourceReplies: resolveSourceReplyDeliveryMode({
			cfg,
			ctx: {
				ChatType: delivery.chatType,
				Provider: delivery.channel
			}
		}) === "message_tool_only",
		responsePrefix,
		ackMaxChars: 300
	});
	const scratch = outcome.kind === "failure" || !heartbeatResponse ? void 0 : getReplyPayloadMetadata(heartbeatResponse.payload)?.heartbeatScratchProposal;
	if (scratch !== void 0 && response) {
		if (!preflight.scratchJobId) heartbeatLog.warn("heartbeat: scratch update ignored because no monitor job exists");
		else try {
			const owner = runState.agentTurnOwner;
			if (!(await writeCronJobScratch({
				storePath: resolveCronJobsStorePathFromConfig(cfg),
				jobId: preflight.scratchJobId,
				content: scratch,
				expectedRevision: preflight.scratchRevision ?? 0
			}, { assertCurrent() {
				if (runState.agentTurnOwner !== owner || resolveReplyOperationAbortReason(owner)) throw new Error("Heartbeat scratch writer is no longer current");
			} })).ok) heartbeatLog.warn("heartbeat: scratch update lost a concurrent revision race");
		} catch (error) {
			heartbeatLog.warn(`heartbeat: scratch update failed: ${formatErrorMessage(error)}`);
		}
	}
	for (const reply of replies) if (execution !== "failed" && response?.notify === false || reply !== selected && outcome.kind !== "failure") await suppressPendingFinalDelivery(reply, { preserveActivity: true });
	const finish = (event, consume = true) => {
		emitHeartbeatEvent({
			...event,
			...committed.matchingRoute && event.silent === true ? { silent: false } : {},
			durationMs: Date.now() - startedAt,
			accountId: delivery.accountId
		});
		if (consume && preflight.shouldInspectPendingEvents) {
			consumeSelectedSystemEventEntries(resolveSystemEventQueueKey(sessionKey, agentId), [...prepared.inspectedSystemEventsToConsume, ...prepared.deferredGenericEvents]);
			if (prepared.hasExecCompletion && prepared.hasCronEvents) requestSessionEventWake({
				source: "cron",
				intent: "immediate",
				reason: "cron:pending",
				agentId,
				sessionKey,
				heartbeat: wake.heartbeat && {
					...wake.heartbeat.target !== void 0 ? { target: wake.heartbeat.target } : {},
					...wake.heartbeat.to !== void 0 ? { to: wake.heartbeat.to } : {},
					...wake.heartbeat.accountId !== void 0 ? { accountId: wake.heartbeat.accountId } : {}
				}
			});
		}
		policy.result = outcome.kind === "failure" ? {
			status: "failed",
			reason: outcome.reason
		} : {
			status: "ran",
			durationMs: Date.now() - startedAt
		};
	};
	const stateKey = prepared.outboundPolicySessionKey ?? sessionKey;
	const record = (value) => persistHeartbeatOutcome({
		agentId,
		sessionKey: stateKey,
		storePath,
		runSessionKey,
		response: value,
		taskNames: scheduledTasks.map((task) => task.name),
		wakeSource,
		wakeReason: opts.reason,
		occurredAt: startedAt
	});
	const unconfirmed = async (reason) => {
		if (outcome.kind !== "delivery" || !outcome.response) return;
		const value = outcome.response;
		await record({
			...value,
			outcome: "blocked",
			notify: false,
			summary: `Alert delivery was not confirmed for this attempt.\n${value.notificationText ?? value.summary}${value.notificationText ? `\nModel summary: ${value.summary}` : ""}`,
			reason: `notify:true; delivery=${reason}; model outcome=${value.outcome}; ${value.reason ?? value.summary}`
		});
	};
	const restoreActivity = () => restoreHeartbeatUpdatedAt({
		agentId,
		storePath,
		sessionKey,
		updatedAt: previousUpdatedAt
	});
	const suppressSelected = () => suppressPendingFinalDelivery(selected, { preserveActivity: true });
	if (outcome.kind === "ack") {
		if ("response" in outcome && outcome.response) await record(outcome.response);
		await restoreActivity();
		await suppressSelected();
		const aborted = resolveReplyOperationAbortReason(runState.agentTurnOwner);
		if (aborted) {
			const reason = aborted === "superseded" ? "preempted" : "agent-runner-cancelled";
			policy.result = {
				status: "skipped",
				reason
			};
			emitHeartbeatEvent({
				status: "skipped",
				reason,
				durationMs: Date.now() - startedAt
			});
			return {};
		}
		if (committed.matchingRoute) {
			finish({
				status: "sent",
				to: delivery.to,
				preview: truncateHeartbeatPreview(committed.routeSentTexts.join("\n")),
				hasMedia: committed.routeSentMediaUrls.length > 0,
				channel,
				indicatorType: visibility.useIndicator ? resolveIndicatorType("sent") : void 0,
				silent: false
			});
			return {};
		}
		if (runState.backgroundWorkStarted) {
			finish({
				status: "skipped",
				reason: "background-work",
				message: "Heartbeat started background work; completion is tracked separately.",
				channel,
				silent: true
			});
			return {};
		}
		const event = {
			status: outcome.eventStatus,
			reason: opts.reason,
			..."preview" in outcome ? { preview: outcome.preview } : {},
			channel,
			indicatorType: visibility.useIndicator ? resolveIndicatorType(outcome.eventStatus) : void 0
		};
		if (!("silent" in outcome && outcome.silent) && visibility.showOk && channel && delivery.to) {
			const readiness = await resolveHeartbeatChannelPlugin(channel)?.heartbeat?.checkReady?.({
				cfg,
				accountId: delivery.accountId,
				deps: opts.deps
			}).catch((error) => {
				heartbeatLog.warn(`heartbeat: HEARTBEAT_OK delivery failed: ${formatErrorMessage(error)}`);
				return { ok: false };
			});
			if (!readiness || readiness.ok) return {
				reply: setReplyPayloadMetadata({ text: responsePrefix ? `${responsePrefix} ${HEARTBEAT_TOKEN}` : HEARTBEAT_TOKEN }, {
					heartbeatReply: true,
					deliverDespiteSourceReplySuppression: true
				}),
				settle: async (result) => {
					if (policy.deliveryError) heartbeatLog.warn(`heartbeat: HEARTBEAT_OK delivery failed: ${policy.deliveryError}`);
					finish({
						...event,
						silent: result !== "delivered"
					});
				}
			};
		}
		finish({
			...event,
			silent: true
		});
		return {};
	}
	const stateEntry = prepared.policySessionEntry;
	const failed = outcome.kind === "failure";
	const normalized = outcome.normalized;
	const text = normalized.text;
	const preview = truncateHeartbeatPreview(failed ? text || outcome.previewText : text);
	const event = {
		status: failed ? "failed" : "sent",
		...failed ? { reason: outcome.reason } : {},
		preview,
		channel,
		indicatorType: failed && visibility.useIndicator ? resolveIndicatorType("failed") : void 0
	};
	if (failed) await restoreActivity();
	else {
		const previousAt = stateEntry?.lastHeartbeatSentAt;
		if (!prepared.internalProjection && !outcome.mediaUrls.length && !outcome.hasStructuredReplyContent && stateEntry?.lastHeartbeatText?.trim() && text.trim() === stateEntry.lastHeartbeatText.trim() && typeof previousAt === "number" && previousAt <= startedAt && startedAt - previousAt < 864e5) {
			await restoreActivity();
			await suppressSelected();
			finish({
				status: "skipped",
				reason: "duplicate",
				preview,
				hasMedia: false,
				channel
			});
			return {};
		}
	}
	const noChannelTarget = !prepared.internalProjection && (!channel || !delivery.to);
	if (noChannelTarget || !visibility.showAlerts || failed && outcome.shouldSkipMain) {
		if (!failed) {
			await unconfirmed(noChannelTarget ? delivery.reason ?? "no-target" : "alerts-disabled");
			if (!visibility.showAlerts) await restoreActivity();
			await suppressSelected();
		}
		finish(failed ? {
			...event,
			silent: true
		} : {
			...event,
			status: "skipped",
			reason: noChannelTarget ? delivery.reason ?? "no-target" : "alerts-disabled",
			hasMedia: outcome.mediaUrls.length > 0,
			indicatorType: !noChannelTarget && visibility.useIndicator ? resolveIndicatorType("sent") : void 0
		}, !failed);
		return {};
	}
	const readiness = channel ? await resolveHeartbeatChannelPlugin(channel)?.heartbeat?.checkReady?.({
		cfg,
		accountId: delivery.accountId,
		deps: opts.deps
	}).catch((error) => ({
		ok: false,
		reason: formatErrorMessage(error)
	})) : void 0;
	if (readiness && !readiness.ok) {
		await unconfirmed(readiness.reason ?? "channel-not-ready");
		await restoreActivity();
		finish({
			...event,
			status: failed ? "failed" : "skipped",
			reason: failed ? outcome.reason : readiness.reason,
			...failed ? { silent: true } : {}
		}, false);
		if (!failed) policy.result = {
			status: "skipped",
			reason: HEARTBEAT_SKIP_CHANNEL_NOT_READY,
			retryAtMs: Date.now() + SESSION_EVENT_IDLE_RETRY_MS
		};
		return {};
	}
	policy.deliverySilent = normalized.silent;
	policy.projectTarget = !failed;
	policy.publicationSourceText = outcome.replyPayload?.text;
	const deliveryText = !failed && delivery.implicitDefaultRoute && stateEntry?.lastHeartbeatSentAt === void 0 ? `${FIRST_HEARTBEAT_ALERT_PREAMBLE}\n${text}` : text;
	const payload = copyReplyPayloadMetadata(selected ?? {}, {
		...outcome.replyPayload,
		text: deliveryText || void 0,
		...!failed ? { mediaUrls: outcome.mediaUrls } : {}
	});
	return {
		reply: setReplyPayloadMetadata(markReplyPayloadForSourceSuppressionDelivery(payload), { heartbeatReply: true }),
		settle: async (result) => {
			const sent = result === "delivered";
			if (!sent) await unconfirmed(policy.deliveryError ?? policy.deliveryReason ?? result);
			if (sent && !failed && deliveryText.trim()) await patchSessionEntryCore({
				agentId,
				storePath,
				sessionKey: stateKey
			}, (current, context) => (context.existingEntry ? current.sessionId === stateEntry?.sessionId && current.lifecycleRevision === stateEntry?.lifecycleRevision : stateEntry === void 0) ? {
				lastHeartbeatText: text,
				lastHeartbeatSentAt: startedAt
			} : null, {
				fallbackEntry: mergeSessionEntry(void 0, { updatedAt: startedAt }),
				preserveActivity: true
			});
			finish(failed ? {
				...event,
				silent: !sent || normalized.silent === true
			} : {
				...event,
				status: sent ? "sent" : policy.deliveryError ? "failed" : "skipped",
				indicatorType: visibility.useIndicator ? resolveIndicatorType(sent ? "sent" : policy.deliveryError ? "failed" : "skipped") : void 0,
				...!sent ? { reason: policy.deliveryError ?? policy.deliveryReason ?? result } : {},
				to: delivery.to,
				preview: truncateHeartbeatPreview(deliveryText),
				hasMedia: outcome.mediaUrls.length > 0,
				...normalized.silent === true ? { silent: true } : {}
			}, sent && !failed);
			if (policy.deliveryError && !failed) policy.result = {
				status: "failed",
				reason: policy.deliveryError
			};
		}
	};
}
/** The core dispatcher owns custody; monitoring supplies its existing transport policy. */
async function deliverHeartbeatDispatch(policy, payload, signal) {
	const { cfg, agentId, startedAt } = policy.wake;
	const { delivery, runSessionKey, storePath, outboundPolicySessionKey, internalProjection } = policy.prepared;
	const onDeliveredPayload = policy.projectTarget ? prepareHeartbeatTargetAwareness({
		agentId,
		storePath,
		runSessionKey,
		targetSessionKey: delivery.targetSessionKey,
		startedAt
	}) : void 0;
	try {
		if (delivery.channel === "none" || !delivery.to) {
			if (!internalProjection || policy.projectTarget === false) return { visibleReplySent: false };
			const occurrenceIds = [...policy.prepared.inspectedSystemEventsToConsume, ...policy.prepared.deferredGenericEvents].map((event) => event.id);
			if (!occurrenceIds.every((id) => typeof id === "string" && id.length > 0)) {
				policy.deliveryReason = "exec completion occurrence identity unavailable";
				return { visibleReplySent: false };
			}
			const committed = await publishHeartbeatSessionReply({
				cfg,
				agentId,
				storePath,
				sessionKey: internalProjection.sessionKey,
				expectedGeneration: internalProjection,
				occurrenceIds,
				payload,
				sourceText: policy.publicationSourceText,
				signal
			});
			if (!committed.ok) policy.deliveryReason = committed.reason;
			return { visibleReplySent: committed.ok };
		}
		const send = await sendDurableMessageBatchCore({
			cfg,
			channel: delivery.channel,
			to: delivery.to,
			accountId: delivery.accountId,
			threadId: delivery.threadId,
			payloads: [payload],
			session: buildOutboundSessionContext({
				cfg,
				agentId,
				sessionKey: runSessionKey,
				policySessionKey: outboundPolicySessionKey
			}),
			identity: resolveAgentOutboundIdentity(cfg, agentId),
			deps: policy.opts.deps,
			signal,
			silent: policy.deliverySilent,
			onDeliveredPayload
		});
		if (send.status === "failed" || send.status === "partial_failed") throw send.error;
		if (send.status === "suppressed") policy.deliveryReason = send.reason;
		return {
			visibleReplySent: send.status === "sent",
			...send.status === "suppressed" && send.reason === "adapter_returned_no_identity" ? { ambiguous: true } : {}
		};
	} catch (error) {
		policy.deliveryError = formatErrorMessage(error);
		throw error;
	}
}
//#endregion
//#region src/infra/heartbeat-active-hours.ts
const ACTIVE_HOURS_TIME_PATTERN = /^(?:([01]\d|2[0-3]):([0-5]\d)|24:00)$/;
/** Resolve the formatter used to evaluate heartbeat active hours. */
function resolveActiveHoursFormatter(cfg, raw) {
	let timeZone = raw?.trim();
	const isExplicit = timeZone && timeZone !== "user" && timeZone !== "local";
	if (!timeZone || timeZone === "user") timeZone = resolveUserTimezone(cfg.agents?.defaults?.userTimezone);
	else if (timeZone === "local") timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone?.trim() || "UTC";
	try {
		return new Intl.DateTimeFormat("en-US", {
			timeZone,
			hour: "2-digit",
			minute: "2-digit",
			hourCycle: "h23"
		});
	} catch {
		return isExplicit ? resolveActiveHoursFormatter(cfg) : null;
	}
}
function parseActiveHoursTime(allow24, raw) {
	if (!raw || !ACTIVE_HOURS_TIME_PATTERN.test(raw)) return null;
	const [hourStr, minuteStr] = raw.split(":");
	const hour = Number(hourStr);
	const minute = Number(minuteStr);
	return hour === 24 && !allow24 ? null : hour * 60 + minute;
}
function resolveMinutesInTimeZone(nowMs, formatter) {
	try {
		const parts = formatter.formatToParts(new Date(nowMs));
		const map = {};
		for (const part of parts) if (part.type !== "literal") map[part.type] = part.value;
		const hour = Number(map.hour);
		const minute = Number(map.minute);
		if (!Number.isFinite(hour) || !Number.isFinite(minute)) return null;
		return hour * 60 + minute;
	} catch {
		return null;
	}
}
/** Return true when the current time is inside the configured heartbeat window. */
function isWithinActiveHours(cfg, heartbeat, nowMs) {
	const active = heartbeat?.activeHours;
	if (!active) return true;
	const startMin = parseActiveHoursTime(false, active.start);
	const endMin = parseActiveHoursTime(true, active.end);
	if (startMin === null || endMin === null) return true;
	if (startMin === endMin) return false;
	const formatter = resolveActiveHoursFormatter(cfg, active.timezone);
	if (!formatter) return true;
	const currentMin = resolveMinutesInTimeZone(nowMs ?? Date.now(), formatter);
	if (currentMin === null) return true;
	return endMin > startMin ? currentMin >= startMin && currentMin < endMin : currentMin >= startMin || currentMin < endMin;
}
//#endregion
//#region src/infra/heartbeat-runner-execution.ts
const CRON_COMMAND_LANE = "cron";
function hasActiveRunForAgent(agentId, listSessionKeys) {
	const normalizedAgentId = normalizeAgentId(agentId);
	return listSessionKeys().some((sessionKey) => {
		const parsed = parseAgentSessionKey(sessionKey);
		return parsed ? normalizeAgentId(parsed.agentId) === normalizedAgentId : false;
	});
}
function hasActiveRunForSession(sessionKey, listSessionKeys) {
	const normalizedSessionKey = sessionKey.trim();
	return Boolean(normalizedSessionKey) && listSessionKeys().includes(normalizedSessionKey);
}
function skippedHeartbeatStage(reason, startedAt) {
	emitHeartbeatEvent({
		status: "skipped",
		reason,
		durationMs: Date.now() - startedAt
	});
	return {
		kind: "skipped",
		reason
	};
}
async function resolveHeartbeatWakeStage(opts) {
	const cfg = opts.cfg ?? getRuntimeConfig();
	const explicitAgentId = typeof opts.agentId === "string" ? opts.agentId.trim() : "";
	const forcedSessionAgentId = explicitAgentId.length > 0 ? void 0 : parseAgentSessionKey(opts.sessionKey)?.agentId;
	const resolvedAgentId = explicitAgentId || forcedSessionAgentId || tryResolveAmbientHeartbeatAgentId(cfg);
	if (!resolvedAgentId) return {
		kind: "skipped",
		reason: "disabled"
	};
	const agentId = normalizeAgentId(resolvedAgentId);
	const wakeSource = opts.source ?? inferHeartbeatWakeSourceFromReason(opts.reason);
	const heartbeat = resolveHeartbeatForWake({
		cfg,
		agentId,
		requestedHeartbeat: opts.heartbeat,
		source: wakeSource
	});
	const scheduledTasks = [...opts.tasks ?? []].toSorted((left, right) => left.jobId.localeCompare(right.jobId));
	const allowsUnscheduledTarget = isTargetedUnscheduledWake(opts) && isConfiguredHeartbeatAgent(cfg, agentId);
	if (!areSessionEventWakesEnabled()) return {
		kind: "skipped",
		reason: "disabled"
	};
	if (!allowsUnscheduledTarget && !isHeartbeatEnabledForAgent(cfg, agentId)) return {
		kind: "skipped",
		reason: "disabled"
	};
	if (!allowsUnscheduledTarget && !resolveHeartbeatIntervalMs(cfg, void 0, heartbeat)) return {
		kind: "skipped",
		reason: "disabled"
	};
	const startedAt = opts.deps?.nowMs?.() ?? Date.now();
	if (!allowsUnscheduledTarget && wakeSource !== "cron" && !isWithinActiveHours(cfg, heartbeat, startedAt)) return skippedHeartbeatStage("quiet-hours", startedAt);
	const shouldPreflightBeforeBusy = shouldPreflightWakeBeforeBusy(wakeSource, opts.scheduledEveryMs, scheduledTasks.length);
	const resolvePreflight = () => resolveHeartbeatPreflight({
		...opts,
		cfg,
		agentId,
		heartbeat,
		source: wakeSource,
		scheduledTasks
	});
	let preflight = shouldPreflightBeforeBusy ? await resolvePreflight() : void 0;
	if (preflight?.skipReason) return skippedHeartbeatStage(preflight.skipReason, startedAt);
	const skippedBusyStage = (reason) => {
		if (preflight?.pendingEventEntries.length === 0 && scheduledTasks.length === 0) deferSessionEventWakePoll();
		return skippedHeartbeatStage(reason, startedAt);
	};
	const isSessionExecCompletion = normalizeOptionalString(opts.sessionKey) !== void 0 && preflight?.isExecEventWake === true && !preflight.authoritativeScheduledTick && scheduledTasks.length === 0 && preflight.pendingEventEntries.some((event) => isExecCompletionEvent(event.text));
	const getSize = opts.deps?.getQueueSize ?? getQueueSize;
	if (!isSessionExecCompletion && getSize("main") > 0) return skippedBusyStage(HEARTBEAT_SKIP_REQUESTS_IN_FLIGHT);
	const heartbeatWaitOwners = listCronHeartbeatWaitOwners();
	const cronBusy = heartbeatWaitOwners.activeJobMarkers.length > 0 ? hasActiveCronJobsExceptMarkers(heartbeatWaitOwners.activeJobMarkers) : hasActiveCronJobs();
	const owningCronLaneTaskIds = new Set(heartbeatWaitOwners.owningCronLaneTaskMarkers.filter((marker) => marker.lane === CRON_COMMAND_LANE && isCommandLaneTaskMarkerCurrent(marker)).map((marker) => marker.taskId));
	const cronLaneBusy = getSize("cron") > owningCronLaneTaskIds.size || getSize("cron-nested") > 0 || getSize("hook-dispatch") > 0;
	if (!isSessionExecCompletion && (cronBusy || cronLaneBusy)) return skippedBusyStage(HEARTBEAT_SKIP_CRON_IN_PROGRESS);
	const shouldHonorActiveReplyRuns = !isSessionExecCompletion && opts.intent !== "immediate" && opts.intent !== "manual";
	const listActiveReplyRuns = opts.deps?.listActiveReplyRunSessionKeys ?? listActiveReplyRunSessionKeys;
	const listActiveEmbeddedRuns = opts.deps?.listActiveEmbeddedRunSessionKeys ?? listActiveEmbeddedRunSessionKeys;
	if (shouldHonorActiveReplyRuns && (hasActiveRunForAgent(agentId, listActiveReplyRuns) || hasActiveRunForAgent(agentId, listActiveEmbeddedRuns))) return skippedBusyStage(HEARTBEAT_SKIP_REQUESTS_IN_FLIGHT);
	const { sessionKey: recentSessionKey, entry: recentSessionEntry } = resolveHeartbeatSession(cfg, agentId, heartbeat, opts.sessionKey);
	const lifecycleGeneration = getAgentEventLifecycleGeneration();
	const mainSessionRecovery = opts.intent !== "manual" && recentSessionEntry ? transitionMainSessionRecovery(recentSessionEntry, {
		kind: "inspect",
		lifecycleGeneration,
		sessionKey: recentSessionKey
	}) : void 0;
	const activeRestartRecoveryRunId = normalizeOptionalString(recentSessionEntry?.restartRecoveryDeliveryRunId);
	const hasCurrentRestartRecoveryDelivery = opts.intent !== "manual" && activeRestartRecoveryRunId !== void 0 && recentSessionEntry?.restartRecoveryRuns?.some((run) => run.runId === activeRestartRecoveryRunId && run.lifecycleGeneration === lifecycleGeneration) === true;
	if (mainSessionRecovery?.kind === "observed" && (mainSessionRecovery.view.status === "blocked" || mainSessionRecovery.view.status === "recoverable") || hasCurrentRestartRecoveryDelivery) return skippedBusyStage(HEARTBEAT_SKIP_REQUESTS_IN_FLIGHT);
	const HEARTBEAT_DEFER_WINDOW_MS = 3e4;
	const pendingFinalDeliveryText = recentSessionEntry?.pendingFinalDelivery?.kind === "replayable" ? recentSessionEntry.pendingFinalDelivery.text : void 0;
	const pendingFinalDeliveryIsHeartbeatAck = typeof pendingFinalDeliveryText === "string" && isHeartbeatAcknowledgementText(pendingFinalDeliveryText);
	if (recentSessionEntry?.pendingFinalDelivery !== void 0 && !pendingFinalDeliveryIsHeartbeatAck && recentSessionEntry?.updatedAt && startedAt - recentSessionEntry.updatedAt < HEARTBEAT_DEFER_WINDOW_MS) return skippedBusyStage(HEARTBEAT_SKIP_REQUESTS_IN_FLIGHT);
	if (!preflight) preflight = await resolvePreflight();
	if (preflight.skipReason) return skippedHeartbeatStage(preflight.skipReason, startedAt);
	const { sessionKey } = preflight.session;
	const isReplyRunActive = opts.deps?.isReplyRunActive ?? ((key) => replyRunRegistry.isActive(key));
	const isEmbeddedRunActive = opts.deps?.listActiveEmbeddedRunSessionKeys ? (key) => hasActiveRunForSession(key, listActiveEmbeddedRuns) : (key) => resolveActiveEmbeddedRunSessionId(key) !== void 0;
	if (isReplyRunActive(sessionKey) || isEmbeddedRunActive(sessionKey)) return skippedBusyStage(HEARTBEAT_SKIP_REQUESTS_IN_FLIGHT);
	if (getSize(resolveSessionLane(sessionKey)) > 0) return skippedBusyStage(HEARTBEAT_SKIP_REQUESTS_IN_FLIGHT);
	return {
		kind: "ready",
		cfg,
		agentId,
		wakeSource,
		heartbeat,
		scheduledTasks,
		startedAt,
		isEmbeddedRunActive,
		isReplyRunActive,
		preflight
	};
}
async function prepareHeartbeatRunStage(wake) {
	const { cfg, agentId, heartbeat, preflight } = wake;
	const { scheduledTasks, startedAt } = wake;
	const { isEmbeddedRunActive, isReplyRunActive } = wake;
	const { entry, sessionKey, run, conversationEntry } = preflight.session;
	const previousUpdatedAt = entry?.updatedAt;
	const projectionSessionKey = run.kind === "isolated" ? run.baseSessionKey : sessionKey;
	const isSessionOwnedEvent = (event) => isExecCompletionEvent(event.text) || wake.wakeSource === "restart-sentinel" && isRestartContinuationEvent(event);
	const projectionCandidate = scheduledTasks.length === 0 && preflight.shouldInspectPendingEvents && preflight.pendingEventEntries.some(isSessionOwnedEvent) && !preflight.session.suppressOriginatingContext && !isInternalSessionEffectsKey(projectionSessionKey) && conversationEntry?.delivery?.kind === "internal" && conversationEntry.createdVia !== "internal" && (conversationEntry.createdVia === "operator" || conversationEntry.lastReadAt !== void 0 || conversationEntry.createdVia === "spawn" && parseAgentSessionKey(projectionSessionKey)?.rest.startsWith("dashboard:")) ? {
		sessionKey: projectionSessionKey,
		sessionId: conversationEntry.sessionId,
		lifecycleRevision: conversationEntry.lifecycleRevision
	} : void 0;
	const resolvedDelivery = await resolveHeartbeatDeliveryTargetWithSessionRoute({
		cfg,
		agentId,
		entry: conversationEntry,
		heartbeat,
		currentSessionKey: sessionKey,
		turnSource: preflight.session.inspectsRunQueue ? preflight.turnSourceDeliveryContext : void 0
	});
	const internalProjection = resolvedDelivery.reason === "target-none" ? void 0 : projectionCandidate;
	const delivery = internalProjection !== void 0 && preflight.pendingEventEntries.length > 0 && preflight.pendingEventEntries.every(isSessionOwnedEvent) ? {
		...resolvedDelivery,
		channel: "none",
		to: void 0,
		reason: "session-owned-completion"
	} : resolvedDelivery;
	if (delivery.channel === "none" && delivery.reason === "no-route" && (wake.wakeSource === void 0 || wake.wakeSource === "interval") && preflight.pendingEventEntries.length === 0 && scheduledTasks.length === 0) return skippedHeartbeatStage("no-route", startedAt);
	const heartbeatAccountId = heartbeat?.accountId?.trim();
	if (delivery.reason === "unknown-account") heartbeatLog.warn("heartbeat: unknown accountId", {
		accountId: delivery.accountId ?? heartbeatAccountId ?? null,
		target: heartbeat?.target ?? "owner"
	});
	else if (heartbeatAccountId) heartbeatLog.info("heartbeat: using explicit accountId", {
		accountId: delivery.accountId ?? heartbeatAccountId,
		target: heartbeat?.target ?? "owner",
		channel: delivery.channel
	});
	const visibility = delivery.channel !== "none" ? resolveHeartbeatVisibility({
		cfg,
		channel: delivery.channel,
		accountId: delivery.accountId
	}) : {
		showOk: false,
		showAlerts: true,
		useIndicator: true
	};
	const { sender } = resolveHeartbeatSenderContext({
		cfg,
		entry,
		delivery
	});
	const replyPrefix = createReplyPrefixContext({
		cfg,
		agentId,
		channel: delivery.channel !== "none" ? delivery.channel : void 0,
		accountId: delivery.accountId
	});
	const canRelayToUser = visibility.showAlerts && (delivery.channel !== "none" && Boolean(delivery.to) || internalProjection !== void 0);
	const useHeartbeatResponseToolPrompt = shouldUseHeartbeatResponseToolPrompt({
		cfg,
		agentId,
		heartbeat,
		entry,
		sessionKey,
		chatType: delivery.chatType
	});
	const resolveRunPrompt = (useHeartbeatResponseTool) => resolveHeartbeatRunPrompt({
		cfg,
		heartbeat,
		preflight,
		canRelayToUser,
		scheduledTasks,
		heartbeatScratchContent: preflight.heartbeatScratchContent,
		useHeartbeatResponseTool
	});
	let heartbeatRunPrompt = resolveRunPrompt(useHeartbeatResponseToolPrompt);
	const runSessionKey = run.sessionKey;
	let runSessionEntry = entry;
	let outboundPolicySessionKey;
	if (run.kind === "isolated") {
		const { sessionKey: isolatedSessionKey, baseSessionKey: isolatedBaseSessionKey } = run;
		const isolatedStorePath = preflight.session.storePath;
		const staleIsolatedSessionKey = heartbeatRunPrompt.hasExecCompletion && heartbeatRunPrompt.hasCronEvents ? void 0 : resolveStaleHeartbeatIsolatedSessionKey({
			sessionKey,
			isolatedSessionKey,
			isolatedBaseSessionKey
		});
		if (isReplyRunActive(isolatedSessionKey) || isEmbeddedRunActive(isolatedSessionKey)) return skippedHeartbeatStage(HEARTBEAT_SKIP_REQUESTS_IN_FLIGHT, startedAt);
		const staleIsolatedEntry = staleIsolatedSessionKey ? loadExactSessionEntry({
			agentId,
			storePath: isolatedStorePath,
			sessionKey: staleIsolatedSessionKey
		})?.entry : void 0;
		const removals = staleIsolatedSessionKey ? [{
			sessionKey: staleIsolatedSessionKey,
			...staleIsolatedEntry ? { expectedEntry: staleIsolatedEntry } : {},
			...staleIsolatedEntry?.sessionId ? { expectedSessionId: staleIsolatedEntry.sessionId } : {},
			archiveRemovedTranscript: true
		}] : [];
		const lifecycleResult = await applySessionEntryLifecycleMutation({
			agentId,
			activeSessionKey: isolatedSessionKey,
			storePath: isolatedStorePath,
			removals,
			upserts: [{
				sessionKey: isolatedBaseSessionKey,
				buildEntry: ({ currentEntry, sessionKey: baseSessionKey }) => currentEntry || isCronRunSessionKey(baseSessionKey) ? void 0 : mergeSessionEntry(void 0, { updatedAt: startedAt })
			}, {
				sessionKey: isolatedSessionKey,
				buildEntry: ({ currentEntry }) => {
					const cronSession = resolveCronSession({
						cfg,
						sessionKey: isolatedSessionKey,
						agentId,
						nowMs: startedAt,
						forceNew: true,
						lifecycleTimestamps: {},
						store: currentEntry ? { [isolatedSessionKey]: currentEntry } : {}
					});
					clearBootstrapSnapshotOnSessionRollover({
						sessionKey: isolatedSessionKey,
						previousSessionId: cronSession.previousSessionId
					});
					const nextEntry = {
						...cronSession.sessionEntry,
						heartbeatIsolatedBaseSessionKey: isolatedBaseSessionKey
					};
					runSessionEntry = nextEntry;
					return nextEntry;
				}
			}],
			captureArtifactCleanupError: true
		});
		if (lifecycleResult.artifactCleanupError) heartbeatLog.warn("heartbeat: failed to archive stale isolated session transcript", {
			err: formatErrorMessage(lifecycleResult.artifactCleanupError),
			sessionKey: staleIsolatedSessionKey
		});
		outboundPolicySessionKey = isolatedBaseSessionKey;
		const actualUseHeartbeatResponseToolPrompt = shouldUseHeartbeatResponseToolPrompt({
			cfg,
			agentId,
			heartbeat,
			entry: runSessionEntry,
			sessionKey: runSessionKey,
			chatType: delivery.chatType
		});
		if (actualUseHeartbeatResponseToolPrompt !== useHeartbeatResponseToolPrompt) heartbeatRunPrompt = resolveRunPrompt(actualUseHeartbeatResponseToolPrompt);
	}
	return {
		kind: "ready",
		...preflight.session,
		previousUpdatedAt,
		policySessionEntry: outboundPolicySessionKey && (outboundPolicySessionKey !== sessionKey || !entry) ? loadExactSessionEntry({
			agentId,
			storePath: preflight.session.storePath,
			sessionKey: outboundPolicySessionKey
		})?.entry : entry,
		delivery,
		visibility,
		sender,
		replyPrefix,
		runSessionKey,
		outboundPolicySessionKey,
		internalProjection,
		...heartbeatRunPrompt,
		useHeartbeatFailureCopy: scheduledTasks.length > 0 || !heartbeatRunPrompt.hasTaskContinuation && !heartbeatRunPrompt.hasCronEvents && (wake.wakeSource === void 0 || wake.wakeSource === "interval" || wake.wakeSource === "manual"),
		deferredGenericEvents: internalProjection && delivery.channel === "none" ? heartbeatRunPrompt.genericEvents.filter(isRestartContinuationEvent) : []
	};
}
//#endregion
//#region src/infra/heartbeat-typing.ts
const DEFAULT_HEARTBEAT_TYPING_INTERVAL_SECONDS = 6;
/** Create typing start/stop/keepalive callbacks for a heartbeat delivery target. */
function createHeartbeatTypingCallbacks(params) {
	const sendTyping = params.plugin?.heartbeat?.sendTyping;
	const to = params.target.to?.trim();
	if (!sendTyping || !to) return;
	const clearTyping = params.plugin?.heartbeat?.clearTyping;
	const keepaliveIntervalMs = typeof params.typingIntervalSeconds === "number" && params.typingIntervalSeconds > 0 ? params.typingIntervalSeconds * 1e3 : DEFAULT_HEARTBEAT_TYPING_INTERVAL_SECONDS * 1e3;
	const target = {
		cfg: params.cfg,
		to,
		...params.target.accountId !== void 0 ? { accountId: params.target.accountId } : {},
		...params.target.threadId !== void 0 ? { threadId: params.target.threadId } : {},
		...params.deps ? { deps: params.deps } : {}
	};
	return createTypingCallbacks({
		start: async () => sendTyping(target),
		...clearTyping ? { stop: async () => clearTyping(target) } : {},
		keepaliveIntervalMs,
		onStartError: (err) => {
			params.log?.debug?.(`heartbeat typing failed for ${params.target.channel}`, {
				error: String(err),
				channel: params.target.channel,
				accountId: params.target.accountId
			});
		}
	});
}
//#endregion
//#region src/infra/heartbeat-runner-run.ts
async function runHeartbeatOnce(opts) {
	const wake = await resolveHeartbeatWakeStage(opts);
	if (wake.kind === "skipped") return {
		status: "skipped",
		reason: wake.reason
	};
	markSessionEventWakeWorkStarted();
	const prepared = await prepareHeartbeatRunStage(wake);
	if (prepared.kind === "skipped") return {
		status: "skipped",
		reason: prepared.reason
	};
	const { cfg, agentId, heartbeat, startedAt } = wake;
	const { delivery, visibility, sender, runSessionKey, suppressOriginatingContext } = prepared;
	if (!visibility.showAlerts && !visibility.showOk && !visibility.useIndicator) {
		emitHeartbeatEvent({
			status: "skipped",
			reason: "alerts-disabled",
			durationMs: Date.now() - startedAt,
			channel: delivery.channel !== "none" ? delivery.channel : void 0,
			accountId: delivery.accountId
		});
		return {
			status: "skipped",
			reason: "alerts-disabled"
		};
	}
	const policy = createHeartbeatDispatch(opts, wake, prepared);
	const state = { heartbeat: policy };
	const signal = getSessionEventWakeAbortSignal();
	const channel = delivery.channel !== "none" ? delivery.channel : void 0;
	const typing = channel && isHeartbeatTypingEnabled({
		cfg,
		agentId,
		hasChatDelivery: Boolean(delivery.to && (visibility.showAlerts || visibility.showOk))
	}) ? createHeartbeatTypingCallbacks({
		cfg,
		target: {
			...delivery,
			channel
		},
		plugin: resolveHeartbeatChannelPlugin(channel),
		deps: opts.deps,
		typingIntervalSeconds: resolveHeartbeatTypingIntervalSeconds(cfg),
		log: heartbeatLog
	}) : void 0;
	try {
		const { dispatchInboundMessageWithRoutedChannelDispatcher } = await import("./dispatch-BIx3OLpt.mjs");
		await typing?.onReplyStart();
		const heartbeatContext = {
			Body: appendCronStyleCurrentTimeLine(prepared.prompt, cfg, startedAt),
			From: sender,
			To: !suppressOriginatingContext ? delivery.to : void 0,
			OriginatingChannel: !suppressOriginatingContext ? channel : void 0,
			OriginatingTo: !suppressOriginatingContext ? delivery.to : void 0,
			AccountId: delivery.accountId,
			ChatType: delivery.chatType,
			MessageThreadId: delivery.threadId,
			InternalTurnSource: prepared.hasExecCompletion ? "exec" : prepared.hasCronEvents ? "cron" : "heartbeat",
			InputProvenance: {
				kind: "internal_system",
				sourceTool: prepared.hasExecCompletion ? "exec" : prepared.hasCronEvents ? "cron" : opts.intent === "scheduled" || !wake.wakeSource || wake.wakeSource === "interval" || wake.wakeSource === "manual" ? "heartbeat" : wake.wakeSource
			},
			SessionKey: runSessionKey,
			AgentId: agentId
		};
		await dispatchInboundMessageWithRoutedChannelDispatcher({
			cfg,
			ctx: heartbeatContext,
			replyResolver: opts.deps?.getReplyFromConfig,
			suppressOutboundHooks: true,
			replyOptions: withReplySystemEventContext({
				isHeartbeat: true,
				useHeartbeatFailureCopy: prepared.useHeartbeatFailureCopy,
				...prepared.run.kind === "isolated" ? { cleanupBundleMcpOnRunEnd: true } : {},
				replyConversation: prepareReplyConversation({
					ctx: heartbeatContext,
					sessionEntry: suppressOriginatingContext ? void 0 : prepared.conversationEntry,
					isHeartbeat: true
				}),
				[REPLY_OPERATION_RUN_STATE]: state,
				heartbeatModelOverride: heartbeat?.model?.trim(),
				...prepared.usesHeartbeatResponseTool ? {
					enableHeartbeatTool: true,
					forceHeartbeatTool: true,
					sourceReplyDeliveryMode: "message_tool_only"
				} : {},
				abortSignal: signal,
				timeoutOverrideSeconds: prepared.hasTaskContinuation ? void 0 : resolveHeartbeatTimeoutOverrideSeconds(cfg, heartbeat),
				bootstrapContextMode: heartbeat?.lightContext === true ? "lightweight" : void 0,
				disableBlockStreaming: true,
				suppressToolProgressMessages: true,
				suppressDefaultToolProgressMessages: true,
				onModelSelected: prepared.replyPrefix.onModelSelected,
				onSessionPrepared: (binding) => {
					if (!policy.prepared.policySessionEntry && !prepared.outboundPolicySessionKey && binding.sessionKey === prepared.sessionKey && binding.storePath === prepared.storePath && binding.lifecycleRevision !== void 0) policy.prepared = {
						...prepared,
						policySessionEntry: {
							sessionId: binding.sessionId,
							lifecycleRevision: binding.lifecycleRevision,
							updatedAt: startedAt
						}
					};
				}
			}, {
				sessionKey: prepared.inspectsRunQueue ? prepared.sessionKey : runSessionKey,
				events: prepared.inspectsRunQueue ? prepared.genericEvents : [],
				deferredEventIds: prepared.deferredGenericEvents.map((event) => event.id).filter((id) => typeof id === "string")
			}),
			dispatcherOptions: { deliver: (payload) => deliverHeartbeatDispatch(policy, payload, state.agentTurnOwner?.abortSignal ?? signal) }
		});
		if (policy.result) return policy.result;
		const execution = resolveReplyOperationAgentTurn(state);
		const reason = execution === "superseded" ? "preempted" : execution === "cancelled" ? "agent-runner-cancelled" : "requests-in-flight";
		emitHeartbeatEvent({
			status: "skipped",
			reason,
			durationMs: Date.now() - startedAt
		});
		return {
			status: "skipped",
			reason
		};
	} catch (error) {
		if (policy.result) return policy.result;
		const reason = formatErrorMessage(error);
		emitHeartbeatEvent({
			status: "failed",
			reason,
			durationMs: Date.now() - startedAt,
			channel,
			accountId: delivery.accountId,
			indicatorType: visibility.useIndicator ? resolveIndicatorType("failed") : void 0
		});
		heartbeatLog.error(`heartbeat failed: ${reason}`, { error: reason });
		return {
			status: "failed",
			reason
		};
	} finally {
		typing?.onCleanup?.();
	}
}
//#endregion
export { resolveConfiguredHeartbeatPrompt as n, runHeartbeatOnce as t };