openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
756 lines (755 loc) • 39.5 kB
JavaScript
import { l as toErrorObject } from "./error-coercion-D_-xJ90S.js";
import { n as getRuntimeConfig } from "./io.runtime-B9iJRs3w.js";
import { w as resolveStateDir } from "./paths-D2sRr1a_.js";
import { t as formatErrorMessage } from "./errors-Db3Ymjlb.js";
import { t as createSubsystemLogger } from "./subsystem-Dy2tqXOS.js";
import { l as resolveRuntimeServiceVersion, s as resolveRuntimeServiceBuildId } from "./version-v1kuAkGj.js";
import "./io-bdCzpGWJ.js";
import { u as getActivePluginRegistry } from "./runtime-BL4wZfTq.js";
import { b as runWithGatewayIndependentRootWorkAdmission } from "./gateway-work-admission-R1IpuDim.js";
import { t as INTERNAL_MESSAGE_CHANNEL } from "./message-channel-constants-2zSoJXQC.js";
import "./message-channel-BQrhwUEA.js";
import { i as getUpdateRun, l as recordUpdateRunVerification, o as recordUpdateRunPhase, r as finishUpdateRun } from "./update-run-ledger-Dse_-g6v.js";
import { s as getMediaDir } from "./store-Ph57PZ9P.js";
import { c as normalizeDeliveryContext, o as mergeDeliveryContext } from "./delivery-context.shared-CXmRgetN.js";
import { l as requestHeartbeat } from "./heartbeat-wake-dlWl8dXS.js";
import { a as enqueueSystemEvent, h as withSystemEventOwner } from "./system-events-C03jsM0j.js";
import { c as resolveSystemMainSessionTarget } from "./main-session-Br0F9dzh.js";
import { a as hasRestartRecoveryTerminalRun, n as getRestartRecoveryTerminalDeliveryEvidence } from "./restart-recovery-state-DDdTIiD6.js";
import { L as resolveDurableCompletionDeliveryMode } from "./task-registry-BP9SCk1B.js";
import { i as finalizeUpdateRestartSentinelRunningVersion, l as readRestartSentinel, o as formatRestartSentinelMessage, p as summarizeRestartSentinel, r as clearRestartSentinelIfRevision } from "./restart-sentinel-FmKS9LVR.js";
import { a as isPendingControlPlaneUpdateRestartSentinel } from "./update-control-plane-sentinel-BN5TeCL_.js";
import "./sessions-9nxpeTwt.js";
import { t as appendAssistantMessageToSessionTranscript } from "./transcript-Dv8-hmIo.js";
import { t as finalizeInboundContext } from "./inbound-context-BJXKd4x8.js";
import { t as normalizeMediaReferenceForComparison } from "./media-reference-comparison-DpA8NCy-.js";
import { l as drainPendingSessionDelivery, u as recoverPendingSessionDeliveries } from "./subagent-completion-admission.store-ieYRlFt3.js";
import { r as loadGatewaySessionEntry } from "./session-utils-store-CInT2loy.js";
import "./session-utils-Cai0_C6U.js";
import { r as hasExplicitlyVisibleAgentPayload } from "./message-visibility-DVJxwfNj.js";
import { _ as markSessionDeliverySettlement, a as SessionDeliveryDeferredError, c as advanceSessionDeliveryAgentRun, f as enqueueSessionDelivery, g as markSessionDeliveryAttemptStarted, i as SessionDeliveryDeadLetteredError, o as SessionDeliveryRetryChargedError, p as failSessionDelivery, s as SessionDeliverySafeRetryError, u as deferSessionDelivery, v as mergeSessionDeliveryPreparedMediaBlocks } from "./session-delivery-queue-storage-ClcsFC9e.js";
import { a as getAgentCommandDeliveryFailure, d as hasCompleteAutomaticMediaDeliveryOutcomeEvidence, l as hasCommittedOutboundDeliveryEvidence, n as collectAutomaticDeliveredMediaUrls, r as collectDeliveredMediaUrls, s as getGatewayAgentResult, t as collectAmbiguousAutomaticMediaUrls } from "./delivery-evidence-5HyXxfSi.js";
import { f as formatGeneratedMediaDeliveryRetryForPrompt } from "./subagent-announce-delivery-CB6b5kft.js";
import { t as removeCronRunContinuationSessionIfIdle } from "./cron-run-continuation-cleanup-D93ByBcx.js";
import { a as settleCorrelatedSubagentDelivery, r as resolveCorrelatedSubagentDelivery } from "./subagent-completion-delivery-BDvv1caT.js";
import { t as dispatchGatewayLifecycleMethod } from "./server-recovery-runtime-context-Cx7vLPdb.js";
import { t as dispatchReplyWithBufferedBlockDispatcherCore } from "./provider-dispatcher-DyFXY5kS.js";
import { t as recordInboundSession } from "./session-CGo-oQbb.js";
import { n as dispatchAssembledChannelTurn } from "./lifecycle-DQ-vfJ_s.js";
import "./get-reply-run-queue-Bmv2zHLu.js";
import { t as runStartupTasks } from "./startup-tasks-BkYwMCKQ.js";
import { i as attachManagedOutgoingMediaToMessage, s as createManagedOutgoingMediaBlocks } from "./managed-image-attachments-CwIi4Xb-.js";
import { i as updateRunReportInputFromSentinel, n as renderUpdateRunReport } from "./update-run-report-CEiRumC4.js";
import { n as deliverRestartSentinelNotice, r as enqueueRestartSentinelNotice, t as resolveUpdateRunNoticeTarget } from "./update-run-notice-target-FgJmCpEJ.js";
//#region src/gateway/server-restart-sentinel-agent-delivery.ts
const log$1 = createSubsystemLogger("gateway/restart-sentinel");
const AGENT_DELIVERY_OWNERSHIP_RETRY_MS = 1e3;
function sessionDeliveryStateDirArgs$1(stateDir) {
return stateDir === void 0 ? [] : [stateDir];
}
async function deadLetterSessionDelivery(entry, reason, stateDir) {
await markSessionDeliverySettlement(entry, "moved-to-failed", ...sessionDeliveryStateDirArgs$1(stateDir));
log$1.warn("queued session delivery requires durable dead-letter settlement", { queueId: entry.id });
throw new SessionDeliveryDeadLetteredError(reason);
}
function hasQueuedVisibleAgentPayload(result) {
return Array.isArray(result.payloads) && result.payloads.some(hasExplicitlyVisibleAgentPayload);
}
function hasUnexpectedRecoverySideEffects(result) {
return result.restartUnsafeSideEffectsDetected === true || result.messagingToolAggregateEvidenceUnaccounted === true || result.messagingToolSentTargetsTruncated === true || result.didSendDeterministicApprovalPrompt === true || hasCommittedOutboundDeliveryEvidence(result);
}
function resolveQueuedAgentRunId(entry) {
const base = entry.idempotencyKey ?? entry.messageId;
return entry.agentRunAttempt ? `${base}:attempt:${entry.agentRunAttempt}` : base;
}
function collectVisiblePayloadMediaUrls(result) {
const urls = /* @__PURE__ */ new Set();
const payloads = Array.isArray(result.payloads) ? result.payloads : [];
for (const payload of payloads) {
if (!hasExplicitlyVisibleAgentPayload(payload)) continue;
for (const url of collectDeliveredMediaUrls({ payloads: [payload] })) urls.add(url);
}
return Array.from(urls);
}
function collectQueuedDeliveredMediaUrls(params) {
if (params.route.channel === "webchat") return collectVisiblePayloadMediaUrls(params.result);
return collectAutomaticDeliveredMediaUrls(params.result);
}
function hasAutomaticVisibleSendEvidence(result) {
if (result.deliveryStatus?.status === "sent" || result.deliveryStatus?.status === "suppressed") return hasQueuedVisibleAgentPayload(result);
const payloads = Array.isArray(result.payloads) ? result.payloads : [];
return (Array.isArray(result.deliveryStatus?.payloadOutcomes) ? result.deliveryStatus.payloadOutcomes : []).some((outcome) => {
if (!outcome || typeof outcome !== "object" || Array.isArray(outcome)) return false;
const record = outcome;
if (record.status !== "sent" && record.status !== "suppressed" && record.sentBeforeError !== true) return false;
const index = typeof record.index === "number" && Number.isInteger(record.index) ? record.index : void 0;
return index !== void 0 && hasExplicitlyVisibleAgentPayload(payloads[index]);
});
}
function hasQueuedVisibleReplyEvidence(params) {
if (params.route.channel === "webchat") return hasQueuedVisibleAgentPayload(params.result);
return hasAutomaticVisibleSendEvidence(params.result);
}
async function evaluateQueuedGeneratedMediaAgentResult(params) {
if (hasUnexpectedRecoverySideEffects(params.result)) {
log$1.warn("queued generated-media recovery reported an unexpected committed side effect", { queueId: params.entry.id });
await deadLetterSessionDelivery(params.entry, "queued generated-media delivery dead-lettered after an unexpected committed side effect", params.stateDir);
}
const expectedMediaUrls = params.entry.expectedMediaUrls ?? [];
const deliveredMediaUrls = new Set(collectQueuedDeliveredMediaUrls(params).map(normalizeMediaReferenceForComparison));
const isDelivered = (url) => deliveredMediaUrls.has(normalizeMediaReferenceForComparison(url));
const missingMediaUrls = expectedMediaUrls.filter((url) => !isDelivered(url));
const provenExpectedMediaUrls = expectedMediaUrls.filter(isDelivered);
const ambiguousMediaUrls = new Set(collectAmbiguousAutomaticMediaUrls(params.result).map(normalizeMediaReferenceForComparison));
const deliveryFailure = getAgentCommandDeliveryFailure(params.result);
const replySatisfied = expectedMediaUrls.length > 0 ? missingMediaUrls.length === 0 : hasQueuedVisibleReplyEvidence(params);
if (params.result.payloadsTruncated === true && !replySatisfied) {
log$1.warn("queued generated-media delivery has truncated delivery evidence", { queueId: params.entry.id });
await deadLetterSessionDelivery(params.entry, "queued generated-media delivery dead-lettered after truncated evidence", params.stateDir);
}
if (expectedMediaUrls.length > 0 && missingMediaUrls.length === 0) {
await params.persistInternalMedia?.(provenExpectedMediaUrls);
return;
}
const rearmAgentRun = async (reason, updates) => {
const currentAgentRunAttempt = params.entry.agentRunAttempt ?? 0;
if (!(params.entry.lastChargedAgentRunAttempt === currentAgentRunAttempt)) await failSessionDelivery(params.entry.id, reason, ...sessionDeliveryStateDirArgs$1(params.stateDir));
try {
if (updates) await advanceSessionDeliveryAgentRun(params.entry.id, updates, ...sessionDeliveryStateDirArgs$1(params.stateDir));
else if (params.stateDir !== void 0) await advanceSessionDeliveryAgentRun(params.entry.id, void 0, params.stateDir);
else await advanceSessionDeliveryAgentRun(params.entry.id);
await deferSessionDelivery(params.entry.id, AGENT_DELIVERY_OWNERSHIP_RETRY_MS, ...sessionDeliveryStateDirArgs$1(params.stateDir));
} catch (error) {
log$1.warn("queued generated-media terminal attempt state transition remains pending", {
queueId: params.entry.id,
error: String(error)
});
throw new SessionDeliveryRetryChargedError(`${reason}; queue state transition failed after retry charge`);
}
throw new SessionDeliveryDeferredError(reason);
};
if (deliveryFailure && expectedMediaUrls.length > 0) {
if (params.result.deliveryStatus?.status === "partial_failed" && !hasCompleteAutomaticMediaDeliveryOutcomeEvidence(params.result, missingMediaUrls) || missingMediaUrls.some((url) => ambiguousMediaUrls.has(normalizeMediaReferenceForComparison(url)))) {
log$1.warn("queued generated-media delivery has ambiguous attachment side effects", {
queueId: params.entry.id,
error: deliveryFailure
});
await deadLetterSessionDelivery(params.entry, "queued generated-media delivery dead-lettered after ambiguous side effects", params.stateDir);
}
} else if (deliveryFailure) {
if (hasQueuedVisibleReplyEvidence(params)) {
log$1.warn("queued generated-media notice may already be visible; refusing duplicate replay", {
queueId: params.entry.id,
error: deliveryFailure
});
await deadLetterSessionDelivery(params.entry, "queued generated-media notice dead-lettered after a visible partial delivery", params.stateDir);
}
await rearmAgentRun(deliveryFailure);
}
if (provenExpectedMediaUrls.length > 0) await params.persistInternalMedia?.(provenExpectedMediaUrls);
if (missingMediaUrls.length > 0) {
const retryMessage = formatGeneratedMediaDeliveryRetryForPrompt(missingMediaUrls);
await rearmAgentRun(`queued generated-media agent turn ${missingMediaUrls.length < expectedMediaUrls.length ? "partially missed" : "missed"} expected media: ${missingMediaUrls.join(", ")}`, {
expectedMediaUrls: missingMediaUrls,
...missingMediaUrls.length < expectedMediaUrls.length || hasQueuedVisibleReplyEvidence(params) || params.result.deliveryStatus?.status === "partial_failed" ? { suppressTextDelivery: true } : {},
...retryMessage ? { message: retryMessage } : {}
});
}
if (expectedMediaUrls.length === 0 && !hasQueuedVisibleReplyEvidence(params)) await rearmAgentRun("queued generated-media agent turn completed without a visible reply");
}
/** Runs durable generated-media handoffs through the normal owning-session agent loop. */
async function deliverQueuedGeneratedMediaAgentTurn(params) {
if (params.entry.kind !== "agentTurn") return false;
const entry = params.entry;
const route = entry.route;
if (!route || entry.inputProvenance?.kind !== "inter_session" || !entry.sourceReplyDeliveryMode) return false;
const queuedRunId = resolveQueuedAgentRunId(entry);
if (resolveDurableCompletionDeliveryMode(entry.sourceReplyDeliveryMode) === "host_owned" && route.channel === "webchat") return await deadLetterSessionDelivery(entry, "queued host-owned generated-media delivery requires an external route", params.stateDir);
const persistInternalMedia = route.channel === "webchat" && (entry.expectedMediaUrls?.length ?? 0) > 0 ? async (mediaUrls) => {
const sessionId = params.sessionEntry?.sessionId?.trim();
if (!sessionId) throw new Error("queued internal generated-media delivery has no owning session");
const stateDir = params.stateDir ?? resolveStateDir();
const preparedMediaBlocks = { ...entry.preparedMediaBlocks };
const content = [];
for (const mediaUrl of mediaUrls) {
let blocks = preparedMediaBlocks[mediaUrl];
if (!blocks) {
const attachment = entry.expectedMediaAttachments?.[mediaUrl];
blocks = await createManagedOutgoingMediaBlocks({
sessionKey: params.canonicalKey,
agentId: params.agentId,
items: [{
url: mediaUrl,
...attachment?.name ? { filename: attachment.name } : {},
...attachment?.mimeType ? { mimeType: attachment.mimeType } : {},
trustedLocal: true,
...attachment?.durationMs !== void 0 ? { durationMs: attachment.durationMs } : {},
...attachment?.width !== void 0 ? { width: attachment.width } : {},
...attachment?.height !== void 0 ? { height: attachment.height } : {}
}],
stateDir,
localRoots: [getMediaDir()]
});
if (!blocks.some((block) => block.type === "image" || block.type === "audio" || block.type === "video" || block.type === "attachment")) throw new Error("queued internal generated media could not be prepared");
blocks = await mergeSessionDeliveryPreparedMediaBlocks(entry.id, mediaUrl, blocks, stateDir);
preparedMediaBlocks[mediaUrl] = blocks;
}
content.push(...blocks);
}
const appended = await appendAssistantMessageToSessionTranscript({
agentId: params.agentId,
sessionKey: params.canonicalKey,
storePath: params.storePath,
expectedSessionId: sessionId,
...params.sessionEntry?.cronRunContinuation?.lifecycleRevision ? { expectedLifecycleRevision: params.sessionEntry.cronRunContinuation.lifecycleRevision } : {},
content: [],
displayContent: content,
idempotencyKey: `${queuedRunId}:generated-media-transcript`,
updateMode: "inline"
});
if (!appended.ok) {
if (appended.code === "session-rebound") await deadLetterSessionDelivery(entry, "queued internal generated-media delivery lost its owning session", params.stateDir);
throw new Error(`queued internal generated-media transcript persistence failed: ${appended.reason}`);
}
if (!attachManagedOutgoingMediaToMessage({
messageId: appended.messageId,
blocks: content,
stateDir
})) throw new Error("queued internal generated-media artifact attachment failed");
} : void 0;
const evaluateResult = async (result) => {
await evaluateQueuedGeneratedMediaAgentResult({
entry,
result,
route,
...params.stateDir !== void 0 ? { stateDir: params.stateDir } : {},
...persistInternalMedia ? { persistInternalMedia } : {}
});
return true;
};
const terminalEvidence = getRestartRecoveryTerminalDeliveryEvidence(params.sessionEntry, queuedRunId);
if (terminalEvidence) return await evaluateResult(terminalEvidence);
if (hasRestartRecoveryTerminalRun(params.sessionEntry, queuedRunId)) await deadLetterSessionDelivery(entry, "queued generated-media agent turn dead-lettered without durable terminal evidence", params.stateDir);
if (params.sessionEntry?.restartRecoveryDeliverySourceRunId === queuedRunId && Boolean(params.sessionEntry.restartRecoveryDeliveryRunId)) {
await deferSessionDelivery(entry.id, AGENT_DELIVERY_OWNERSHIP_RETRY_MS, ...sessionDeliveryStateDirArgs$1(params.stateDir));
throw new SessionDeliveryDeferredError("queued generated-media agent turn is still owned by agent recovery");
}
if (entry.deliveryStartedAt !== void 0) await deadLetterSessionDelivery(entry, "queued generated-media agent turn dead-lettered after an interrupted unproven attempt", params.stateDir);
const sourceReplyDeliveryMode = "automatic";
const cronSessionId = params.sessionEntry?.cronRunContinuation?.lifecycleRevision?.trim() ? params.sessionEntry?.sessionId?.trim() : void 0;
await markSessionDeliveryAttemptStarted(entry, ...sessionDeliveryStateDirArgs$1(params.stateDir));
let accepted = false;
let response;
try {
response = await dispatchGatewayLifecycleMethod("agent", {
sessionKey: params.canonicalKey,
message: entry.message,
deliver: route.channel !== INTERNAL_MESSAGE_CHANNEL,
bestEffortDeliver: false,
channel: route.channel,
accountId: route.accountId,
to: route.to,
threadId: route.threadId,
...cronSessionId ? { sessionId: cronSessionId } : {},
inputProvenance: entry.inputProvenance,
sourceReplyDeliveryMode,
disableMessageTool: true,
forceRestartSafeTools: true,
idempotencyKey: queuedRunId
}, {
...cronSessionId ? { allowSyntheticCronRunContinuation: true } : {},
expectFinal: true,
forceSyntheticClient: true,
internalDeliveryMediaUrls: entry.expectedMediaUrls ?? [],
...entry.suppressTextDelivery === true ? { internalDeliverySuppressText: true } : {},
...params.resolveGatewayContext ? { resolveGatewayContext: params.resolveGatewayContext } : {},
onAccepted: () => {
accepted = true;
}
});
} catch (error) {
if (!accepted) throw new SessionDeliverySafeRetryError("queued generated-media agent turn failed before gateway acceptance", { cause: error });
throw error;
}
const result = getGatewayAgentResult(response);
if (!result) {
const responseStatus = response && typeof response === "object" ? response.status : void 0;
const latestEntry = loadGatewaySessionEntry(entry.sessionKey).entry;
if (responseStatus === "accepted") accepted = true;
if (responseStatus === "accepted" || responseStatus === "in_flight" || latestEntry?.restartRecoveryDeliverySourceRunId === queuedRunId && latestEntry.restartRecoveryDeliveryRunId) {
await deferSessionDelivery(entry.id, AGENT_DELIVERY_OWNERSHIP_RETRY_MS, ...sessionDeliveryStateDirArgs$1(params.stateDir));
throw new SessionDeliveryDeferredError("queued generated-media agent turn is still owned by agent recovery");
}
if (hasRestartRecoveryTerminalRun(latestEntry, queuedRunId)) {
const latestTerminalEvidence = getRestartRecoveryTerminalDeliveryEvidence(latestEntry, queuedRunId);
if (latestTerminalEvidence) return await evaluateResult(latestTerminalEvidence);
log$1.warn("queued generated-media agent turn ended without durable delivery evidence; failing closed", {
queueId: entry.id,
runId: queuedRunId
});
await deadLetterSessionDelivery(entry, "queued generated-media agent turn dead-lettered without durable terminal evidence", params.stateDir);
}
if (!accepted) throw new SessionDeliverySafeRetryError("queued generated-media agent turn returned no result before gateway acceptance");
throw new Error("queued generated-media agent turn returned no delivery result");
}
return await evaluateResult(result);
}
//#endregion
//#region src/gateway/server-restart-update-run.ts
/** The booting Gateway records only its own observations; the CLI owns pending handoffs. */
async function finalizeRestartUpdateRun(payload, pendingExpired = false) {
const updateRunId = payload.stats?.runId;
let updateRun = updateRunId ? getUpdateRun(updateRunId) : void 0;
if (updateRun) {
if (!updateRun.origin.sessionKey && payload.sessionKey) updateRun = recordUpdateRunPhase(updateRun.runId, updateRun.phase, { origin: {
sessionKey: payload.sessionKey,
...payload.deliveryContext ? { deliveryContext: {
...payload.deliveryContext,
threadId: payload.threadId
} } : {}
} });
if (updateRun.status === "running" && (updateRun.phase === "restarting" || updateRun.phase === "verifying")) updateRun = recordUpdateRunPhase(updateRun.runId, "verifying");
const runningVersion = resolveRuntimeServiceVersion();
const runningBuildId = resolveRuntimeServiceBuildId();
const expectedVersion = updateRun.after.version ?? updateRun.target.version;
const expectedBuildId = updateRun.after.buildId;
const pluginErrors = getActivePluginRegistry()?.diagnostics.filter((entry) => entry.level === "error").map((entry) => entry.message);
updateRun = recordUpdateRunVerification(updateRun.runId, {
booted: true,
serviceRunning: true,
pid: process.pid,
runningVersion,
...runningBuildId ? { runningBuildId } : {},
...expectedVersion ? { versionMatch: expectedVersion === runningVersion && (!expectedBuildId || expectedBuildId === runningBuildId) } : {},
...pluginErrors ? { pluginErrors } : {},
...payload.doctorHint ? { doctorHint: payload.doctorHint } : {}
});
if (updateRun.phase === "verifying" && updateRun.status === "running") {
const { createUpdateRunNotifier } = await import("./update-run-notice.runtime.js");
await createUpdateRunNotifier(updateRun)(updateRun, "verifying");
}
if (pendingExpired || !isPendingControlPlaneUpdateRestartSentinel(payload)) updateRun = finishUpdateRun(updateRun.runId, {
status: pendingExpired || payload.status === "error" || updateRun.verification.versionMatch === false ? "failed" : payload.status === "ok" ? "succeeded" : "skipped",
reason: pendingExpired || updateRun.verification.versionMatch === false ? "restart-unhealthy" : payload.stats?.reason ?? void 0,
after: {
version: runningVersion,
...runningBuildId ? { buildId: runningBuildId } : {}
}
});
}
return updateRun;
}
//#endregion
//#region src/gateway/server-restart-sentinel.ts
const log = createSubsystemLogger("gateway/restart-sentinel");
const RESTART_CONTINUATION_BUSY_RETRY_DELAY_MS = process.env.VITEST ? 1 : 6e3;
const RESTART_CONTINUATION_BUSY_MAX_ATTEMPTS = 20;
const CONTROL_PLANE_UPDATE_PENDING_RETRY_DELAY_MS = process.env.VITEST ? 1 : 2e3;
const CONTROL_PLANE_UPDATE_PENDING_MAX_ATTEMPTS = 900;
const RESTART_CONTINUATION_BUSY_RETRY_ERROR = "restart continuation deferred because previous run is still shutting down";
let latestUpdateRestartSentinel = null;
/** Settles every queue entry through its durable producer before cron cleanup. */
const settleQueuedSessionDelivery = async (entry, outcome) => {
await settleCorrelatedSubagentDelivery(entry, outcome);
await removeCronRunContinuationSessionIfIdle(entry.sessionKey, entry.id);
};
function sessionDeliveryStateDirArgs(stateDir) {
return stateDir === void 0 ? [] : [stateDir];
}
function cloneRestartSentinelPayload(payload) {
return payload ? structuredClone(payload) : null;
}
function enqueueRestartSentinelWake(message, sessionKey, agentId, deliveryContext) {
const eventOptions = {
sessionKey,
...deliveryContext ? { deliveryContext } : {}
};
enqueueSystemEvent(message, agentId ? withSystemEventOwner(eventOptions, agentId) : eventOptions);
requestHeartbeat({
source: "restart-sentinel",
intent: "immediate",
reason: "wake",
...agentId ? { agentId } : {},
sessionKey
});
}
async function waitForRetry(delayMs) {
await new Promise((resolve) => {
setTimeout(resolve, delayMs).unref?.();
});
}
const buildRestartContinuationMessageId = (params) => `restart-sentinel:${params.sessionKey}:${params.kind}:${params.revision}`;
function isRestartContinuationBusyPayload(payload) {
return typeof payload.text === "string" && payload.text.trim() === "⚠️ Previous run is still shutting down. Please try again in a moment.";
}
function isRestartContinuationBusyRetry(entry) {
return entry?.lastError === RESTART_CONTINUATION_BUSY_RETRY_ERROR;
}
function resolveQueuedRestartContinuationMessageId(entry) {
if (isRestartContinuationBusyRetry(entry) && entry.retryCount > 0) return `${entry.messageId}:retry:${entry.retryCount}`;
return entry.messageId;
}
function resolveQueuedSessionDeliveryContext(entry) {
if (entry.kind === "agentTurn" && entry.route) return {
channel: entry.route.channel,
to: entry.route.to,
...entry.route.accountId ? { accountId: entry.route.accountId } : {},
...entry.route.threadId ? { threadId: entry.route.threadId } : {}
};
return entry.deliveryContext;
}
async function deliverQueuedSessionDelivery(params) {
const queuedEntry = resolveCorrelatedSubagentDelivery(params.entry);
const { cfg, agentId, entry, storePath, canonicalKey } = loadGatewaySessionEntry(queuedEntry.sessionKey);
const deliveryContext = resolveQueuedSessionDeliveryContext(queuedEntry);
if (queuedEntry.kind === "systemEvent") {
const { agentId: systemEventAgentId, text } = queuedEntry;
enqueueRestartSentinelWake(text, canonicalKey, systemEventAgentId, deliveryContext);
return;
}
if (queuedEntry.expectedSessionId && (!entry?.sessionId || entry.sessionId !== queuedEntry.expectedSessionId)) {
log.warn("restart continuation skipped: session changed", {
sessionKey: canonicalKey,
queueId: queuedEntry.id,
expectedSessionId: queuedEntry.expectedSessionId,
actualSessionId: entry?.sessionId ?? null
});
enqueueRestartSentinelWake(queuedEntry.message, canonicalKey, void 0, deliveryContext);
return;
}
if (!queuedEntry.route) {
enqueueRestartSentinelWake(queuedEntry.message, canonicalKey, void 0, deliveryContext);
return;
}
if (await deliverQueuedGeneratedMediaAgentTurn({
entry: queuedEntry,
canonicalKey,
agentId,
storePath,
sessionEntry: entry,
...params.stateDir !== void 0 ? { stateDir: params.stateDir } : {},
...params.resolveGatewayContext ? { resolveGatewayContext: params.resolveGatewayContext } : {}
})) return;
if (queuedEntry.deliveryStartedAt !== void 0) {
await markSessionDeliverySettlement(queuedEntry, "moved-to-failed", ...sessionDeliveryStateDirArgs(params.stateDir));
throw new SessionDeliveryDeadLetteredError("queued agent turn dead-lettered after an interrupted unproven attempt");
}
const route = queuedEntry.route;
const messageId = resolveQueuedRestartContinuationMessageId(queuedEntry);
const userMessage = queuedEntry.message.trim();
let dispatchError;
const ctxPayload = finalizeInboundContext({
Body: userMessage,
BodyForAgent: userMessage,
BodyForCommands: "",
RawBody: userMessage,
CommandBody: "",
SessionKey: canonicalKey,
AccountId: route.accountId,
MessageSid: messageId,
Timestamp: Date.now(),
InputProvenance: {
kind: "internal_system",
sourceChannel: route.channel,
sourceTool: "restart-sentinel"
},
Provider: INTERNAL_MESSAGE_CHANNEL,
Surface: INTERNAL_MESSAGE_CHANNEL,
ChatType: route.chatType,
CommandAuthorized: true,
GatewayClientScopes: ["operator.admin"],
GatewayClientCaps: [],
ReplyToId: route.replyToId,
OriginatingChannel: route.channel,
OriginatingTo: route.to,
ExplicitDeliverRoute: false,
MessageThreadId: route.threadId
}, {
forceBodyForCommands: true,
forceChatType: true
});
await dispatchAssembledChannelTurn({
cfg,
channel: route.channel,
accountId: route.accountId,
agentId,
routeSessionKey: canonicalKey,
storePath,
ctxPayload,
recordInboundSession,
dispatchReplyWithBufferedBlockDispatcher: dispatchReplyWithBufferedBlockDispatcherCore,
replyOptions: { sourceReplyDeliveryMode: "message_tool_only" },
turnAdoptionLifecycle: {
admission: "cancel-only",
onAdopted: () => markSessionDeliveryAttemptStarted(queuedEntry, ...sessionDeliveryStateDirArgs(params.stateDir))
},
delivery: {
preparePayload: (payload) => {
if (isRestartContinuationBusyPayload(payload)) throw new SessionDeliverySafeRetryError(RESTART_CONTINUATION_BUSY_RETRY_ERROR);
return payload;
},
durable: false,
deliver: async () => ({ visibleReplySent: false }),
onError: (err, info) => {
dispatchError ??= err;
log.warn(`restart continuation dispatch failed during ${info.kind}: ${String(err)}`, { sessionKey: canonicalKey });
}
},
record: { onRecordError: (err) => {
log.warn(`restart continuation failed to record inbound session metadata: ${String(err)}`, { sessionKey: canonicalKey });
} }
});
if (dispatchError) throw toErrorObject(dispatchError, "Non-Error thrown");
}
function buildQueuedRestartContinuation(params) {
const idempotencyKey = params.idempotencyKey ?? buildRestartContinuationMessageId({
sessionKey: params.sessionKey,
kind: params.continuation.kind,
revision: params.revision
});
if (params.continuation.kind === "systemEvent") return {
kind: "systemEvent",
sessionKey: params.sessionKey,
...params.agentId ? { agentId: params.agentId } : {},
text: params.continuation.text,
...params.deliveryContext ? { deliveryContext: params.deliveryContext } : {},
idempotencyKey,
maxRetries: RESTART_CONTINUATION_BUSY_MAX_ATTEMPTS,
completionRetention: "permanent"
};
return {
kind: "agentTurn",
sessionKey: params.sessionKey,
message: params.continuation.message,
messageId: idempotencyKey,
...params.expectedSessionId ? { expectedSessionId: params.expectedSessionId } : {},
maxRetries: RESTART_CONTINUATION_BUSY_MAX_ATTEMPTS,
completionRetention: "permanent",
...params.route ? { route: params.route } : {},
...params.deliveryContext ? { deliveryContext: params.deliveryContext } : {},
idempotencyKey
};
}
async function drainRestartContinuationQueue(params) {
for (let attempt = 1; attempt <= RESTART_CONTINUATION_BUSY_MAX_ATTEMPTS; attempt += 1) {
if (!isRestartContinuationBusyRetry(await drainPendingSessionDelivery({
id: params.entryId,
logLabel: "restart continuation",
log: params.log,
bypassBackoff: true,
deliver: (entry, context = {}) => deliverQueuedSessionDelivery({
deps: params.deps,
entry,
...context.stateDir !== void 0 ? { stateDir: context.stateDir } : {}
}),
onSettled: settleQueuedSessionDelivery
}))) return;
if (attempt >= RESTART_CONTINUATION_BUSY_MAX_ATTEMPTS) return;
params.log.info(`restart continuation: entry ${params.entryId} still waiting for the previous run to clear; retrying in ${RESTART_CONTINUATION_BUSY_RETRY_DELAY_MS}ms`);
await waitForRetry(RESTART_CONTINUATION_BUSY_RETRY_DELAY_MS);
}
}
async function recoverPendingRestartContinuationDeliveries(params) {
await recoverPendingSessionDeliveries({
deliver: (entry, context = {}) => deliverQueuedSessionDelivery({
deps: params.deps,
entry,
...context.stateDir !== void 0 ? { stateDir: context.stateDir } : {},
...params.resolveGatewayContext ? { resolveGatewayContext: params.resolveGatewayContext } : {}
}),
log: params.log ?? log,
maxEnqueuedAt: params.maxEnqueuedAt,
onSettled: settleQueuedSessionDelivery
});
}
async function loadRestartSentinelStartupTask(params) {
const sentinel = await readRestartSentinel();
if (!sentinel) return null;
const payload = sentinel.payload;
const sentinelRevision = sentinel.revision;
if (payload.kind === "update") recordLatestUpdateRestartSentinel(payload);
const sessionKey = payload.sessionKey?.trim();
const message = formatRestartSentinelMessage(payload);
const updateRun = payload.kind === "update" ? await finalizeRestartUpdateRun(payload) : void 0;
const updateRunId = updateRun?.runId;
let noticeMessage = payload.kind === "update" ? renderUpdateRunReport(updateRun ?? updateRunReportInputFromSentinel(payload)).markdown : message;
const summary = summarizeRestartSentinel(payload);
const wakeDeliveryContext = mergeDeliveryContext(payload.threadId != null ? {
...payload.deliveryContext,
threadId: payload.threadId
} : payload.deliveryContext, void 0);
const run = async () => {
let routedSessionKey = sessionKey;
let wakeAgentId;
if (isPendingControlPlaneUpdateRestartSentinel(payload) && (!updateRun || updateRun.status === "running")) {
const attempt = params.attempt ?? 0;
if (attempt < CONTROL_PLANE_UPDATE_PENDING_MAX_ATTEMPTS) {
setTimeout(() => {
runWithGatewayIndependentRootWorkAdmission(async () => {
await scheduleRestartSentinelWakeAttempt({
deps: params.deps,
attempt: attempt + 1
});
}, "restart-sentinel:wake").catch((err) => {
log.warn(`restart sentinel pending update retry failed: ${formatErrorMessage(err)}`);
});
}, CONTROL_PLANE_UPDATE_PENDING_RETRY_DELAY_MS).unref?.();
return {
status: "skipped",
reason: "update-restart-pending"
};
}
log.warn(`${summary}: update restart sentinel remained pending after retry window`, {
sessionKey,
reason: payload.stats?.reason ?? null
});
if (updateRunId) {
const expiredRun = await finalizeRestartUpdateRun(payload, true);
if (expiredRun) noticeMessage = renderUpdateRunReport(expiredRun).markdown;
}
}
if (!routedSessionKey) {
if ((payload.kind === "config-patch" || payload.kind === "config-apply") && (typeof payload.message !== "string" || payload.message.trim().length === 0) && !payload.continuation && !payload.deliveryContext && payload.threadId == null) {
if (!await clearRestartSentinelIfRevision(sentinelRevision)) log.info(`${summary}: newer restart sentinel preserved while consuming config restart`);
return { status: "ran" };
}
const systemTarget = resolveSystemMainSessionTarget(getRuntimeConfig());
routedSessionKey = systemTarget.sessionKey;
wakeAgentId = systemTarget.agentId;
}
const continuation = sessionKey ? payload.continuation : void 0;
const session = loadGatewaySessionEntry(routedSessionKey);
const { cfg, entry, canonicalKey } = session;
const target = resolveUpdateRunNoticeTarget({
cfg,
sessionKey,
session,
explicitDeliveryContext: sessionKey ? payload.deliveryContext : void 0,
threadId: sessionKey ? payload.threadId : void 0
});
const route = target.kind === "route" ? target.route : void 0;
const deliveryContext = normalizeDeliveryContext(route) ?? (sessionKey ? wakeDeliveryContext : void 0);
let continuationQueueId;
let wakeQueueId;
let noticeQueueId;
let noticeQueueCreated = false;
const continuationRoute = continuation ? route : void 0;
let internalNoticeWritten = false;
if (updateRun?.verification.noticeDelivered) internalNoticeWritten = true;
else if (sessionKey && target.kind === "internal") {
const { agentId, entry: internalEntry, storePath } = target.session;
const notice = await appendAssistantMessageToSessionTranscript({
agentId,
sessionKey: canonicalKey,
expectedSessionId: internalEntry.sessionId,
expectedLifecycleRevision: internalEntry.lifecycleRevision ?? null,
storePath,
text: noticeMessage,
idempotencyKey: updateRunId ? `update-run-finished:${updateRunId}` : `restart-sentinel-notice:${canonicalKey}:${sentinelRevision}`
}).catch((error) => ({
ok: false,
reason: formatErrorMessage(error)
}));
internalNoticeWritten = notice.ok;
if (notice.ok && updateRunId) recordUpdateRunVerification(updateRunId, { noticeDelivered: true });
if (!notice.ok) log.warn(`${summary}: internal restart notice append failed; falling back to wake: ${notice.reason}`, { sessionKey: canonicalKey });
}
const routedAgentTurnContinuation = continuation?.kind === "agentTurn" && continuationRoute !== void 0;
const updateComplete = (internalNoticeWritten || updateRunId && route) && payload.kind === "update" && !continuation;
if (!routedAgentTurnContinuation && !updateComplete) wakeQueueId = await enqueueSessionDelivery(buildQueuedRestartContinuation({
sessionKey: canonicalKey,
agentId: wakeAgentId,
continuation: {
kind: "systemEvent",
text: message
},
revision: sentinelRevision,
deliveryContext,
idempotencyKey: `restart-sentinel-wake:${canonicalKey}:${sentinelRevision}`
}));
if (!sessionKey && payload.continuation) log.warn(`${summary}: continuation skipped: restart sentinel sessionKey unavailable`, {
sessionKey: canonicalKey,
continuationKind: payload.continuation.kind
});
if (continuation) continuationQueueId = await enqueueSessionDelivery(buildQueuedRestartContinuation({
sessionKey: canonicalKey,
continuation,
revision: sentinelRevision,
route: continuationRoute,
expectedSessionId: entry?.sessionId,
deliveryContext
}));
if (route && !updateRun?.verification.noticeDelivered) {
const queuedNotice = await enqueueRestartSentinelNotice({
cfg,
...route,
message: noticeMessage,
sessionKey: canonicalKey,
revision: sentinelRevision,
...updateRunId ? { deliveryIntentId: `update-run-finished:${updateRunId}` } : {}
});
noticeQueueId = queuedNotice.id;
noticeQueueCreated = queuedNotice.created;
}
if (!await clearRestartSentinelIfRevision(sentinelRevision)) log.info(`${summary}: newer restart sentinel preserved while draining durable work`, { sessionKey: canonicalKey });
if (wakeQueueId) await drainRestartContinuationQueue({
deps: params.deps,
entryId: wakeQueueId,
log
});
if (route && noticeQueueId && noticeQueueCreated) {
if (await deliverRestartSentinelNotice({
deps: params.deps,
cfg,
sessionKey: canonicalKey,
summary,
message: noticeMessage,
...route,
queueId: noticeQueueId
}) && updateRunId) recordUpdateRunVerification(updateRunId, { noticeDelivered: true });
} else if (noticeQueueId && !noticeQueueCreated) log.info(`${summary}: durable restart notice already owned`, { sessionKey: canonicalKey });
if (continuationQueueId) await drainRestartContinuationQueue({
deps: params.deps,
entryId: continuationQueueId,
log
});
return { status: "ran" };
};
return {
source: "restart-sentinel",
...sessionKey ? { sessionKey } : {},
run
};
}
async function scheduleRestartSentinelWakeAttempt(params) {
const task = await loadRestartSentinelStartupTask(params);
if (!task) return;
await runStartupTasks({
tasks: [task],
log
});
}
async function scheduleRestartSentinelWake(params) {
await scheduleRestartSentinelWakeAttempt({
...params,
attempt: 0
});
}
async function refreshLatestUpdateRestartSentinel() {
const current = await readRestartSentinel();
if (current?.payload.kind === "update" && isPendingControlPlaneUpdateRestartSentinel(current.payload)) {
latestUpdateRestartSentinel = cloneRestartSentinelPayload(current.payload);
return cloneRestartSentinelPayload(latestUpdateRestartSentinel);
}
const sentinel = await finalizeUpdateRestartSentinelRunningVersion() ?? current;
if (sentinel?.payload.kind === "update") latestUpdateRestartSentinel = cloneRestartSentinelPayload(sentinel.payload);
return cloneRestartSentinelPayload(latestUpdateRestartSentinel);
}
function getLatestUpdateRestartSentinel() {
return cloneRestartSentinelPayload(latestUpdateRestartSentinel);
}
function recordLatestUpdateRestartSentinel(payload) {
latestUpdateRestartSentinel = cloneRestartSentinelPayload(payload);
}
//#endregion
export { refreshLatestUpdateRestartSentinel as a, recoverPendingRestartContinuationDeliveries as i, getLatestUpdateRestartSentinel as n, scheduleRestartSentinelWake as o, recordLatestUpdateRestartSentinel as r, settleQueuedSessionDelivery as s, deliverQueuedSessionDelivery as t };