openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
190 lines (189 loc) • 8.48 kB
JavaScript
import { w as resolveStateDir } from "./paths-D2sRr1a_.js";
import { c as getAgentEventLifecycleGeneration, l as isAgentEventLifecycleGenerationCurrent, t as assertAgentRunLifecycleGenerationCurrent } from "./agent-events-CoxiItUi.js";
import { f as listAgentRunsForSession } from "./agent-run-registry-CKYKdfNd.js";
import { Gt as applySessionEntryReplacements } from "./session-accessor-YsytfDtG.js";
import { a as listSessionEntriesReadOnly } from "./session-accessor.sqlite-entry-CWk3jL7s.js";
import { r as captureGatewaySessionWorkAdmissions } from "./session-lifecycle-admission-CS8v45tk.js";
import { n as listActiveEmbeddedRunSessionIds, r as listActiveEmbeddedRunSessionKeys } from "./active-run-projections-C4mdt8WG.js";
import { d as hasCurrentProcessOwner, f as mainSessionRecoveryLog, h as resolveRestartRecoveryStorePaths, i as isMainRestartRecoveryCandidate, m as normalizeStringSet, o as normalizeMainSessionRecoveryRunFences, p as normalizeFiniteTimestamp, r as isMainRestartRecoveryAggregateTerminalOnly, s as transitionMainSessionRecovery, u as discoverRestartRecoveryStorePaths } from "./main-session-recovery-state-DD1DvBOq.js";
import { randomUUID } from "node:crypto";
//#region src/agents/main-session-recovery/main-session-restart-recovery-marking.ts
async function markRecoveryStore(params) {
return await applySessionEntryReplacements({
storePath: params.storePath,
sessionKeys: params.sessionKey ? [params.sessionKey] : void 0,
statuses: params.statuses,
requireWriteSuccess: true,
assertCommitAllowed: params.assertCommitAllowed,
update: (entries) => {
const replacements = [];
const counts = {
marked: 0,
skipped: 0
};
for (const { sessionKey, entry } of entries) {
const plan = params.plan(entry, sessionKey);
if (!plan) continue;
if (!isMainRestartRecoveryCandidate(entry, sessionKey)) {
counts.skipped++;
continue;
}
if (plan.action === "retire_terminal") {
transitionMainSessionRecovery(entry, {
kind: "observe",
cycleId: randomUUID(),
lifecycleGeneration: getAgentEventLifecycleGeneration(),
sessionKey
});
replacements.push({
sessionKey,
entry
});
counts.skipped++;
continue;
}
if (plan.replaceRuns) entry.restartRecoveryRuns = plan.runs;
if (plan.forceRestartSafeTools) entry.restartRecoveryForceSafeTools = true;
transitionMainSessionRecovery(entry, {
kind: "mark_interrupted",
cycleId: randomUUID(),
now: Date.now(),
...plan
});
replacements.push({
sessionKey,
entry
});
counts.marked++;
}
return {
result: counts,
replacements
};
}
});
}
async function markRestartAbortedMainSessions(params) {
const activeRuns = [...params.activeRuns];
const currentLifecycleGeneration = getAgentEventLifecycleGeneration();
const result = {
marked: 0,
skipped: 0
};
const activeAdmissions = captureGatewaySessionWorkAdmissions(params.resolveGatewayContext);
if (activeRuns.length === 0 && activeAdmissions.targets.size === 0) return result;
const storePaths = /* @__PURE__ */ new Set();
const stateDir = params.stateDir ?? resolveStateDir(process.env);
const configs = [params.cfg, ...params.additionalCfgs ?? []].filter(Boolean);
for (const cfg of configs.length > 0 ? configs : [void 0]) try {
for (const storePath of await discoverRestartRecoveryStorePaths({
cfg,
stateDir
})) storePaths.add(storePath);
} catch (err) {
if (!cfg) throw err;
mainSessionRecoveryLog.warn(`failed to resolve configured session stores for restart marker: ${String(err)}`);
}
for (const storePath of activeAdmissions.targets.keys()) storePaths.add(storePath);
for (const storePath of storePaths) {
const sessionKeys = listSessionEntriesReadOnly({
storePath,
projection: "list",
clone: false
}).filter(({ sessionKey, entry }) => activeRuns.some((run) => run.sessionKey === sessionKey && run.sessionId === entry.sessionId) || activeAdmissions.isActive({
scope: storePath,
sessionKey,
sessionId: entry.sessionId
})).map(({ sessionKey }) => sessionKey);
for (const selectedSessionKey of sessionKeys) {
let isCurrent;
try {
const storeResult = await markRecoveryStore({
storePath,
sessionKey: selectedSessionKey,
assertCommitAllowed: () => {
if (isCurrent && !isCurrent()) throw new Error("Restart recovery owner changed before commit");
},
plan: (entry, sessionKey) => {
const matchingActiveRuns = activeRuns.filter((run) => run.sessionKey === sessionKey && run.sessionId === entry.sessionId && (entry.status === "running" || run.observedAt === void 0 || normalizeFiniteTimestamp(entry.updatedAt) === void 0 || entry.updatedAt < run.observedAt && run.lifecycleGeneration !== currentLifecycleGeneration) && params.isActiveRun?.(run) !== false);
const matchedActiveAdmission = activeAdmissions.isActive({
scope: storePath,
sessionKey,
sessionId: entry.sessionId
});
if (matchingActiveRuns.length === 0 && !matchedActiveAdmission) return;
const wasRunning = entry.status === "running";
const runs = normalizeMainSessionRecoveryRunFences([
...(entry.restartRecoveryRuns ?? []).filter((run) => run.lifecycleGeneration === currentLifecycleGeneration),
...listAgentRunsForSession({
sessionKey,
sessionId: entry.sessionId
}),
...matchingActiveRuns.map(({ runId, lifecycleGeneration }) => ({
runId,
lifecycleGeneration
}))
]);
isCurrent = () => isAgentEventLifecycleGenerationCurrent(currentLifecycleGeneration) && (matchedActiveAdmission && activeAdmissions.isActive({
scope: storePath,
sessionKey,
sessionId: entry.sessionId
}) || matchingActiveRuns.some((run) => params.isActiveRun?.(run) !== false));
return {
action: "mark",
forceRestartSafeTools: matchedActiveAdmission,
replaceRuns: true,
resetRuntime: !wasRunning,
runs
};
}
});
result.marked += storeResult.marked;
result.skipped += storeResult.skipped;
} catch (error) {
assertAgentRunLifecycleGenerationCurrent(currentLifecycleGeneration);
if (!isCurrent || isCurrent()) throw error;
result.skipped++;
}
}
}
if (result.marked > 0) mainSessionRecoveryLog.warn(`marked ${result.marked} interrupted main session(s) for restart recovery${params.reason ? ` (${params.reason})` : ""}`);
return result;
}
async function markStartupOrphanedMainSessionsForRecovery(params) {
const result = {
marked: 0,
skipped: 0
};
const providedActiveSessionIds = params.activeSessionIds === void 0 ? void 0 : normalizeStringSet(params.activeSessionIds);
const providedActiveSessionKeys = params.activeSessionKeys === void 0 ? void 0 : normalizeStringSet(params.activeSessionKeys);
const updatedBeforeMs = normalizeFiniteTimestamp(params.updatedBeforeMs);
const resolveActiveSessionIds = () => providedActiveSessionIds ?? normalizeStringSet(listActiveEmbeddedRunSessionIds());
const resolveActiveSessionKeys = () => providedActiveSessionKeys ?? normalizeStringSet(listActiveEmbeddedRunSessionKeys());
const storePaths = (await resolveRestartRecoveryStorePaths(params)).filter((storePath) => !params.startupCheckedStorePaths?.has(storePath));
for (const storePath of storePaths) {
const storeResult = await markRecoveryStore({
storePath,
statuses: ["running"],
plan: (entry, sessionKey) => {
if (entry.status !== "running" || entry.abortedLastRun === true) return;
const updatedAt = normalizeFiniteTimestamp(entry.updatedAt);
if (updatedBeforeMs !== void 0 && updatedAt !== void 0 && updatedAt > updatedBeforeMs) return;
if (hasCurrentProcessOwner({
activeSessionIds: resolveActiveSessionIds(),
activeSessionKeys: resolveActiveSessionKeys(),
entry,
sessionKey
})) return;
return isMainRestartRecoveryAggregateTerminalOnly(entry) ? { action: "retire_terminal" } : { action: "mark" };
}
});
result.marked += storeResult.marked;
result.skipped += storeResult.skipped;
}
storePaths.forEach((storePath) => params.startupCheckedStorePaths?.add(storePath));
if (result.marked > 0) mainSessionRecoveryLog.warn(`marked ${result.marked} startup-orphaned main session(s) for restart recovery`);
return result;
}
//#endregion
export { markStartupOrphanedMainSessionsForRecovery as n, markRestartAbortedMainSessions as t };