openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
220 lines (219 loc) • 10.2 kB
JavaScript
import { c as getAgentEventLifecycleGeneration } from "./agent-events-CoxiItUi.js";
import { Gt as applySessionEntryReplacements } from "./session-accessor-YsytfDtG.js";
import { a as isMainSessionRecoveryPending, i as isMainRestartRecoveryCandidate, s as transitionMainSessionRecovery } from "./main-session-recovery-state-DD1DvBOq.js";
import { a as scheduleMainSessionRecoveryMutation, i as retryMainSessionRecoveryMutation } from "./main-session-recovery-lifecycle-1GvLnUmT.js";
import { randomUUID } from "node:crypto";
//#region src/agents/main-session-recovery/main-session-recovery-store.ts
function matchesReservation(entry, reservation) {
const state = entry.mainRestartRecovery;
return entry.sessionId === reservation.sessionId && state?.cycleId === reservation.cycleId && state.reservation?.runId === reservation.runId && state.reservation.lifecycleGeneration === reservation.lifecycleGeneration;
}
function currentGenerationRequiredBy(command) {
if (command.kind === "validate_foreground" || command.kind === "bind_foreground_run") return command.claim.lifecycleGeneration;
return "lifecycleGeneration" in command ? command.lifecycleGeneration : void 0;
}
async function commitMainSessionRecovery(params) {
const reservationCleanup = params.command.kind === "cancel_reservation" || params.command.kind === "abandon_reservation" ? params.command.reservation : void 0;
const recoveryAdmission = params.command.kind === "admit_recovery" || params.command.kind === "validate_recovery" ? params.command : void 0;
const ownerClaim = params.command.kind === "claim_foreground" ? params.command : void 0;
const exactOwnerClaim = params.command.kind === "validate_foreground" || params.command.kind === "release_foreground" ? params.command.claim : void 0;
const scansAliases = Boolean(params.scanAliases || reservationCleanup || recoveryAdmission || exactOwnerClaim);
return await applySessionEntryReplacements({
requireWriteSuccess: params.requireWriteSuccess,
...scansAliases ? {} : { sessionKeys: [params.target.sessionKey] },
storePath: params.target.storePath,
update: (entries) => {
if (params.shouldContinue?.() === false) return { result: { transition: {
kind: "rejected",
reason: "stale_generation"
} } };
const expectedGeneration = currentGenerationRequiredBy(params.command);
if (expectedGeneration && expectedGeneration !== getAgentEventLifecycleGeneration()) return { result: { transition: {
kind: "rejected",
reason: "stale_generation"
} } };
const selected = entries.find(({ sessionKey }) => sessionKey === params.target.sessionKey);
let candidate = params.expectedSessionId && selected?.entry.sessionId !== params.expectedSessionId || ownerClaim && selected?.entry.sessionId !== ownerClaim.sessionId ? void 0 : selected;
if (reservationCleanup) candidate = entries.find(({ entry }) => matchesReservation(entry, reservationCleanup)) ?? selected;
else if (recoveryAdmission) candidate = entries.find(({ entry }) => {
const reservation = entry.mainRestartRecovery?.reservation;
return entry.sessionId === recoveryAdmission.sessionId && reservation?.runId === recoveryAdmission.runId && reservation.lifecycleGeneration === recoveryAdmission.lifecycleGeneration;
}) ?? selected;
else if (exactOwnerClaim) candidate = entries.find(({ entry }) => {
const state = entry.mainRestartRecovery;
return state?.cycleId === exactOwnerClaim.cycleId && state.foregroundClaims?.lifecycleGeneration === exactOwnerClaim.lifecycleGeneration && state.foregroundClaims.tokens.includes(exactOwnerClaim.claimId);
}) ?? selected;
else if (ownerClaim && (!selected || selected.entry.sessionId !== ownerClaim.sessionId)) candidate = entries.find(({ entry }) => entry.sessionId === ownerClaim.sessionId);
else if (params.scanAliases && params.expectedSessionId) candidate = entries.find(({ entry }) => entry.sessionId === params.expectedSessionId);
if (!candidate) return { result: {
entry: selected?.entry,
sessionKey: selected?.sessionKey,
transition: {
kind: "rejected",
reason: "session_replaced"
}
} };
const entry = candidate.entry;
const previousRecoveryState = entry.mainRestartRecovery;
let command;
if (ownerClaim) command = ownerClaim.sessionKey === candidate.sessionKey ? ownerClaim : {
...ownerClaim,
sessionKey: candidate.sessionKey
};
else if ((params.command.kind === "observe" || params.command.kind === "inspect") && params.command.sessionKey !== candidate.sessionKey) command = {
...params.command,
sessionKey: candidate.sessionKey
};
else command = params.command;
const transition = transitionMainSessionRecovery(entry, command);
const changed = previousRecoveryState !== entry.mainRestartRecovery || transition.kind !== "foreground_validated" && transition.kind !== "no_change" && transition.kind !== "observed" && transition.kind !== "rejected";
return {
result: {
entry,
sessionKey: candidate.sessionKey,
transition
},
...changed ? { replacements: [{
sessionKey: candidate.sessionKey,
entry
}] } : {}
};
}
});
}
async function refreshMainSessionRecoveryOwner(lease, runId) {
const result = await commitMainSessionRecovery({
command: runId ? {
kind: "bind_foreground_run",
claim: lease,
runId
} : {
kind: "validate_foreground",
claim: lease
},
requireWriteSuccess: true,
target: lease
});
return (runId ? result.transition.kind === "applied" : result.transition.kind === "foreground_validated") && result.entry && result.sessionKey ? {
lease: runId ? {
...lease,
runId
} : lease,
entry: result.entry,
sessionKey: result.sessionKey
} : void 0;
}
async function claimMainSessionRecoveryOwner(params) {
const command = {
kind: "claim_foreground",
cycleId: randomUUID(),
lifecycleGeneration: params.lifecycleGeneration,
sessionId: params.sessionId,
sessionKey: params.target.sessionKey,
claimId: randomUUID(),
...params.runId ? { runId: params.runId } : {}
};
let claim = await commitMainSessionRecovery({
command,
requireWriteSuccess: true,
target: params.target
});
if (claim.transition.kind === "rejected" && claim.transition.reason === "session_replaced") claim = await commitMainSessionRecovery({
command,
requireWriteSuccess: true,
scanAliases: true,
target: params.target
});
if (claim.transition.kind === "foreground_claimed") {
if (!claim.entry || !claim.sessionKey) return {
kind: "invalidated",
reason: "state_changed"
};
return {
kind: "claimed",
lease: {
...claim.transition.claim,
storePath: params.target.storePath
},
entry: claim.entry,
sessionKey: claim.sessionKey
};
}
if (claim.transition.kind === "rejected" && claim.transition.reason === "stale_generation") return {
kind: "invalidated",
reason: claim.transition.reason
};
if (!claim.entry && (params.allowMissingSession || params.replacementSessionId)) return { kind: "not_required" };
const healthyExpectedSession = claim.entry && claim.entry.abortedLastRun !== true && claim.entry.restartRecoveryRuns === void 0 && claim.entry.mainRestartRecovery === void 0 && (claim.entry.sessionId === params.sessionId || claim.entry.sessionId === params.replacementSessionId);
if (claim.entry?.sessionId === params.sessionId && claim.sessionKey && !isMainRestartRecoveryCandidate(claim.entry, claim.sessionKey)) return { kind: "not_required" };
if (healthyExpectedSession) return { kind: "not_required" };
return {
kind: "invalidated",
reason: claim.transition.kind === "rejected" ? claim.transition.reason : "state_changed"
};
}
async function inspectMainSessionRecoveryRequired(params) {
const command = {
kind: "inspect",
lifecycleGeneration: params.lifecycleGeneration,
sessionKey: params.target.sessionKey
};
let result = await commitMainSessionRecovery({
command,
expectedSessionId: params.expectedSessionId,
requireWriteSuccess: true,
target: params.target
});
if (result.transition.kind === "rejected" && result.transition.reason === "session_replaced") result = await commitMainSessionRecovery({
command,
expectedSessionId: params.expectedSessionId,
requireWriteSuccess: true,
scanAliases: true,
target: params.target
});
if (result.transition.kind === "observed") return result.transition.view.status === "inactive" ? { kind: "not_required" } : { kind: "required" };
if (result.transition.kind === "rejected" && result.transition.reason === "session_replaced") return !result.entry && params.allowMissingSession ? { kind: "not_required" } : {
kind: "invalidated",
reason: result.transition.reason
};
return {
kind: "invalidated",
reason: result.transition.kind === "rejected" ? result.transition.reason : "state_changed"
};
}
async function releaseMainSessionRecoveryOwnerWithRetries(lease) {
const released = await retryMainSessionRecoveryMutation(async () => commitMainSessionRecovery({
command: {
kind: "release_foreground",
claim: lease
},
requireWriteSuccess: true,
target: lease
}));
const { entry, sessionKey } = released;
if (released.transition.kind !== "applied" && released.transition.kind !== "no_change" || !entry || !sessionKey || entry.sessionId !== lease.sessionId || !isMainSessionRecoveryPending(entry, sessionKey)) return;
return {
sessionId: entry.sessionId,
sessionKey,
storePath: lease.storePath
};
}
async function releaseMainSessionRecoveryOwner(lease) {
if (!lease) return;
try {
return await releaseMainSessionRecoveryOwnerWithRetries(lease);
} catch (error) {
scheduleMainSessionRecoveryMutation({
mutation: () => releaseMainSessionRecoveryOwnerWithRetries(lease),
onSuccess: async (pending) => {
if (pending) {
const { scheduleMainSessionRecoveryPendingTarget } = await import("./main-session-recovery-owner-release-hw1YE5s5.js");
scheduleMainSessionRecoveryPendingTarget(pending);
}
}
});
throw error;
}
}
//#endregion
export { releaseMainSessionRecoveryOwner as a, refreshMainSessionRecoveryOwner as i, commitMainSessionRecovery as n, inspectMainSessionRecoveryRequired as r, claimMainSessionRecoveryOwner as t };