openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
226 lines (225 loc) • 10.5 kB
JavaScript
import { l as normalizeOptionalString } from "./string-coerce-CIXf7egm.js";
import { r as truncateUtf16Safe } from "./utf16-slice-D_ngcYKd.js";
import { k as parseCronRunScopeSuffix } from "./session-key-BnWWjqNc.js";
import { t as createSubsystemLogger } from "./subsystem-Dy2tqXOS.js";
import { c as getAgentEventLifecycleGeneration } from "./agent-events-CoxiItUi.js";
import "./session-accessor-YsytfDtG.js";
import { d as patchSessionEntryCore } from "./session-accessor.sqlite-entry-CWk3jL7s.js";
import { i as buildAgentRunTerminalOutcomeFromLifecycleEvent, o as classifyAgentRunTerminalOutcome } from "./agent-run-terminal-outcome-CigeY75d.js";
import { t as renderUserFacingText } from "./user-facing-text-0KenW-TQ.js";
import { r as loadGatewaySessionEntry } from "./session-utils-store-CInT2loy.js";
import "./session-utils-Cai0_C6U.js";
import { n as projectMainSessionRecoveryLifecycle, t as isMainSessionRecoveryLifecycleEvent } from "./main-session-recovery-lifecycle-1GvLnUmT.js";
//#region src/agents/agent-lifecycle-parent-state.ts
function isAgentLifecycleYieldedWaiting(event) {
return event.phase === "end" && event.yielded === true && event.livenessState === "paused" && event.stopReason === "end_turn" && event.aborted !== true && event.status !== "cancelled" && event.status !== "timed_out" && event.timeoutPhase == null && event.error == null;
}
//#endregion
//#region src/gateway/session-lifecycle-state.ts
const restartRecoveryLog = createSubsystemLogger("main-session-restart-recovery");
const SESSION_RUN_ERROR_MAX_CHARS = 160;
function isFiniteTimestamp(value) {
return typeof value === "number" && Number.isFinite(value) && value > 0;
}
function resolveLifecyclePhase(event) {
const phase = typeof event.data?.phase === "string" ? event.data.phase : "";
return phase === "start" || phase === "end" || phase === "error" ? phase : null;
}
const SESSION_STATUS_BY_TERMINAL_CLASSIFICATION = {
success: "done",
timeout: "timeout",
cancellation: "killed",
failure: "failed"
};
function resolveTerminalOutcome(event) {
const phase = resolveLifecyclePhase(event);
return buildAgentRunTerminalOutcomeFromLifecycleEvent({
phase: phase === "error" ? "error" : "end",
data: event.data,
endedAt: event.data?.endedAt ?? event.ts
});
}
function resolveSettledLifecycleTerminalOutcome(event) {
const phase = resolveLifecyclePhase(event);
if (phase !== "end" && phase !== "error") return;
const outcome = resolveTerminalOutcome(event);
return isAgentLifecycleYieldedWaiting({
phase,
yielded: event.data?.yielded,
livenessState: event.data?.livenessState,
stopReason: outcome.stopReason,
aborted: event.data?.aborted,
status: event.data?.status,
timeoutPhase: event.data?.timeoutPhase,
error: event.data?.error
}) ? void 0 : outcome;
}
function resolveSessionRunError(outcome, status) {
if (status !== "failed" && status !== "timeout" || !outcome.error) return;
const sanitized = renderUserFacingText(outcome.error, { errorContext: true }).replace(/\s+/g, " ").trim();
return sanitized ? truncateUtf16Safe(sanitized, SESSION_RUN_ERROR_MAX_CHARS) : void 0;
}
function resolveLifecycleStartedAt(existingStartedAt, event) {
if (isFiniteTimestamp(event.data?.startedAt)) return event.data.startedAt;
if (isFiniteTimestamp(existingStartedAt)) return existingStartedAt;
return isFiniteTimestamp(event.ts) ? event.ts : void 0;
}
function resolveLifecycleEndedAt(event) {
if (isFiniteTimestamp(event.data?.endedAt)) return event.data.endedAt;
return isFiniteTimestamp(event.ts) ? event.ts : void 0;
}
function resolveRuntimeMs(params) {
const { startedAt, endedAt, existingRuntimeMs } = params;
if (isFiniteTimestamp(startedAt) && isFiniteTimestamp(endedAt)) return Math.max(0, endedAt - startedAt);
if (typeof existingRuntimeMs === "number" && Number.isFinite(existingRuntimeMs) && existingRuntimeMs >= 0) return existingRuntimeMs;
}
function deriveGatewaySessionLifecycleSnapshot(params) {
const phase = resolveLifecyclePhase(params.event);
if (!phase) return {};
const existing = params.session ?? void 0;
if (phase === "start") {
const startedAt = resolveLifecycleStartedAt(existing?.startedAt, params.event);
return {
updatedAt: startedAt ?? existing?.updatedAt,
status: "running",
lastRunError: void 0,
startedAt,
endedAt: void 0,
runtimeMs: void 0,
abortedLastRun: false
};
}
const startedAt = resolveLifecycleStartedAt(existing?.startedAt, params.event);
const endedAt = resolveLifecycleEndedAt(params.event);
const updatedAt = endedAt ?? existing?.updatedAt;
const terminal = resolveSettledLifecycleTerminalOutcome(params.event);
const status = terminal ? SESSION_STATUS_BY_TERMINAL_CLASSIFICATION[classifyAgentRunTerminalOutcome(terminal)] : "running";
return {
updatedAt,
status,
lastRunError: terminal ? resolveSessionRunError(terminal, status) : void 0,
startedAt,
endedAt,
runtimeMs: resolveRuntimeMs({
startedAt,
endedAt,
existingRuntimeMs: existing?.runtimeMs
}),
abortedLastRun: status === "killed"
};
}
function derivePersistedSessionLifecyclePatch(params) {
const snapshot = deriveGatewaySessionLifecycleSnapshot({
session: params.entry ?? void 0,
event: params.event
});
const snapshotPatch = {
...snapshot,
updatedAt: typeof snapshot.updatedAt === "number" ? snapshot.updatedAt : void 0
};
const projection = projectMainSessionRecoveryLifecycle({
currentLifecycleGeneration: getAgentEventLifecycleGeneration(),
entry: params.entry,
event: params.event,
snapshotPatch
});
if (projection.action === "suppress") return {};
const phase = resolveLifecyclePhase(params.event);
const runId = normalizeOptionalString(params.event.runId);
const clientRunId = normalizeOptionalString(params.event.clientRunId) ?? runId;
return {
...projection.patch,
...phase === "start" ? {
lifecycleRunId: runId,
lastRunId: void 0
} : projection.patch.status && projection.patch.status !== "running" ? {
lifecycleRunId: void 0,
lastRunId: clientRunId
} : {}
};
}
function deriveGatewaySessionLifecycleProjectionPatch(params) {
const { restartRecoveryRuns: _restartRecoveryRuns, lifecycleRunId: _lifecycleRunId, ...patch } = derivePersistedSessionLifecyclePatch(params);
return patch;
}
function isRestartRecoveryLifecycleEvent(params) {
return isMainSessionRecoveryLifecycleEvent(params);
}
/**
* Reject pre-reset runs and explicitly older runs sharing one session so late
* lifecycle events cannot overwrite a newer run's authoritative state.
*/
function isStaleLifecycleEventForSession(params) {
if (params.owningSessionId && params.currentSessionId && params.owningSessionId !== params.currentSessionId) return true;
const eventRunId = normalizeOptionalString(params.eventRunId);
const currentRunId = normalizeOptionalString(params.currentRunId);
if (eventRunId && currentRunId && eventRunId === currentRunId) return false;
return isFiniteTimestamp(params.eventStartedAt) && isFiniteTimestamp(params.currentStartedAt) && params.eventStartedAt < params.currentStartedAt;
}
function acceptsCronRunContinuationLifecycleEvent(params) {
const marker = params.entry.cronRunContinuation;
if (marker?.phase === "running") return true;
const runId = params.event.runId?.trim();
return Boolean(marker?.phase === "continuing" && runId && marker.ownerRunId === runId);
}
let lifecyclePersistenceVersion = 0;
function readSessionLifecyclePersistenceVersion() {
return lifecyclePersistenceVersion;
}
async function persistGatewaySessionLifecycleEvent(params) {
const phase = resolveLifecyclePhase(params.event);
if (!phase) return;
const sessionEntry = loadGatewaySessionEntry(params.sessionKey, {
...params.agentId ? { agentId: params.agentId } : {},
clone: false
});
if (!sessionEntry.entry) return;
const owningSessionId = typeof params.event.sessionId === "string" && params.event.sessionId ? params.event.sessionId : void 0;
const exactCronRun = parseCronRunScopeSuffix(sessionEntry.canonicalKey).runId !== void 0;
let terminalRecovery;
if (await patchSessionEntryCore({
storePath: sessionEntry.storePath,
sessionKey: sessionEntry.canonicalKey
}, async (storedEntry) => {
terminalRecovery = void 0;
const entry = storedEntry;
if (exactCronRun && !acceptsCronRunContinuationLifecycleEvent({
entry,
event: params.event
})) return null;
if (isStaleLifecycleEventForSession({
owningSessionId,
currentSessionId: entry.sessionId,
eventRunId: params.event.runId,
currentRunId: entry.lifecycleRunId,
eventStartedAt: params.event.data?.startedAt,
currentStartedAt: entry.startedAt
})) return null;
const eventRunId = normalizeOptionalString(params.event.runId);
const eventClientRunId = normalizeOptionalString(params.event.clientRunId);
const terminalRunId = normalizeOptionalString(entry.lastRunId);
if (phase === "start" && entry.status !== "running" && terminalRunId !== void 0 && (eventRunId === terminalRunId || eventClientRunId === terminalRunId)) return null;
const patch = derivePersistedSessionLifecyclePatch({
entry,
event: params.event
});
const recoveryTerminalRunId = normalizeOptionalString(params.event.runId);
const terminalOutcome = params.event.mainSessionRestartRecovery === true && params.event.lifecycleGeneration === getAgentEventLifecycleGeneration() && recoveryTerminalRunId !== void 0 && (phase === "end" || phase === "error") ? resolveSettledLifecycleTerminalOutcome(params.event) : void 0;
if (terminalOutcome && recoveryTerminalRunId && Object.keys(patch).length > 0) terminalRecovery = {
runId: recoveryTerminalRunId,
outcome: terminalOutcome
};
return Object.keys(patch).length > 0 ? patch : null;
}, {
skipMaintenance: true,
takeCacheOwnership: true,
requireWriteSuccess: true,
...params.assertCommitAllowed ? { assertCommitAllowed: params.assertCommitAllowed } : {}
}) && terminalRecovery) {
const message = `main-session restart recovery terminal: session=${sessionEntry.canonicalKey} run=${terminalRecovery.runId} status=${terminalRecovery.outcome.status} reason=${terminalRecovery.outcome.reason}`;
restartRecoveryLog[terminalRecovery.outcome.status === "ok" ? "info" : "warn"](message);
}
lifecyclePersistenceVersion += 1;
}
//#endregion
export { persistGatewaySessionLifecycleEvent as a, isStaleLifecycleEventForSession as i, deriveGatewaySessionLifecycleSnapshot as n, readSessionLifecyclePersistenceVersion as o, isRestartRecoveryLifecycleEvent as r, isAgentLifecycleYieldedWaiting as s, deriveGatewaySessionLifecycleProjectionPatch as t };