mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-24 10:11:14 +00:00
* fix(session): bound restart recovery across gateway restarts Co-authored-by: Mason Huang <masonxhuang@proton.me> * style(session): format restart recovery changes * fix(session): fence recovery notice state * test(session): align internal recovery types * test(session): type recovery completion fixture * style(session): satisfy recovery lint rules * test(plugin-sdk): split recovery boundary coverage * test(plugin-sdk): use tracked recovery temp dirs * fix(session): preserve stale admission errors * fix(session): close recovery lifecycle races * fix(session): rotate rejected recovery dispatch ids * fix(session): enforce recovery quarantine boundaries * fix(session): close restart recovery exhaustion gaps * fix(session): preserve recovery command narrowing * test(session): type restart recovery fixtures * fix(session): enforce restart recovery quarantine * fix(session): close restart recovery races * fix(session): type recovery lifecycle state * fix(session): enforce recovery ownership fences * fix(session): fence recovery lifecycle completion * fix(session): quarantine cross-process recovery * fix(session): retire stale recovery ownership * fix(session): restore rejected recovery admission * fix(session): preserve restored recovery target * fix(session): retry pending recovery races * fix(session): preserve concurrent recovery aborts * fix(session): quarantine recovery lifecycle state * fix(session): settle exact recovery delivery fences * fix(session): keep recovery restoration internal * fix(session): close recovery admission races * fix(session): retry durable recovery rollback * fix(session): guard cached recovery settlement * fix(session): close recovery ownership gaps * fix(session): resume after delayed recovery rollback * fix(session): finalize unrecoverable restart state * fix(session): retry admitted recovery restoration * fix(session): preserve recovery cleanup guarantees * fix(session): restore failed terminal settlement * chore(plugin-sdk): refresh API baseline * fix(session): close recovery owner retry races * test(session): type recovery owner fixture * refactor(session): remove obsolete owner validator * fix(session): preserve explicit abort recovery --------- Co-authored-by: Mason Huang <masonxhuang@proton.me> Co-authored-by: Peter Steinberger <steipete@gmail.com>
108 lines
3.8 KiB
TypeScript
108 lines
3.8 KiB
TypeScript
import { MAIN_SESSION_RECOVERY_CLEAR_PATCH } from "../agents/main-session-recovery-clear.js";
|
|
import type { SessionAccessScope } from "../config/sessions/session-accessor.js";
|
|
import type { InternalSessionEntry, SessionEntry } from "../config/sessions/types.js";
|
|
|
|
export type SessionStoreReadParams = {
|
|
agentId?: string;
|
|
env?: NodeJS.ProcessEnv;
|
|
hydrateSkillPromptRefs?: boolean;
|
|
readConsistency?: "latest";
|
|
sessionKey: string;
|
|
storePath?: string;
|
|
};
|
|
|
|
export function toSessionAccessScope(params: SessionStoreReadParams): SessionAccessScope {
|
|
// Keep plugin-facing options separate from internal accessor-only controls.
|
|
return {
|
|
sessionKey: params.sessionKey,
|
|
...(params.agentId !== undefined ? { agentId: params.agentId } : {}),
|
|
...(params.env !== undefined ? { env: params.env } : {}),
|
|
...(params.hydrateSkillPromptRefs !== undefined
|
|
? { hydrateSkillPromptRefs: params.hydrateSkillPromptRefs }
|
|
: {}),
|
|
...(params.readConsistency !== undefined ? { readConsistency: params.readConsistency } : {}),
|
|
...(params.storePath !== undefined ? { storePath: params.storePath } : {}),
|
|
};
|
|
}
|
|
|
|
export function projectPluginSessionEntry(entry: InternalSessionEntry): SessionEntry {
|
|
const { mainRestartRecovery: _mainRestartRecovery, ...publicEntry } = entry;
|
|
return {
|
|
...publicEntry,
|
|
...(entry.restartRecoveryRuns
|
|
? { restartRecoveryRuns: entry.restartRecoveryRuns.map((run) => ({ ...run })) }
|
|
: {}),
|
|
};
|
|
}
|
|
|
|
export function projectPluginSessionEntryPatch(
|
|
patch: Partial<InternalSessionEntry>,
|
|
): Partial<SessionEntry> {
|
|
const { mainRestartRecovery: _mainRestartRecovery, ...publicPatch } = patch;
|
|
return publicPatch;
|
|
}
|
|
|
|
export function projectPluginSessionStore(
|
|
store: Record<string, InternalSessionEntry>,
|
|
): Record<string, SessionEntry> {
|
|
return Object.fromEntries(
|
|
Object.entries(store).map(([sessionKey, entry]) => [
|
|
sessionKey,
|
|
projectPluginSessionEntry(entry),
|
|
]),
|
|
);
|
|
}
|
|
|
|
export function activeRecoveryFieldsForSameSession(
|
|
existingEntry: InternalSessionEntry | undefined,
|
|
nextSessionId: string | undefined,
|
|
): Partial<InternalSessionEntry> | undefined {
|
|
if (
|
|
!existingEntry ||
|
|
existingEntry.sessionId !== nextSessionId ||
|
|
existingEntry.mainRestartRecovery === undefined
|
|
) {
|
|
return undefined;
|
|
}
|
|
return {
|
|
abortedLastRun: existingEntry.abortedLastRun,
|
|
restartRecoveryRuns: existingEntry.restartRecoveryRuns,
|
|
mainRestartRecovery: existingEntry.mainRestartRecovery,
|
|
};
|
|
}
|
|
|
|
export function clearRecoveryStateForRotatedSessionPatch(
|
|
existingEntry: InternalSessionEntry,
|
|
publicPatch: Partial<SessionEntry>,
|
|
): Partial<InternalSessionEntry> {
|
|
return Object.hasOwn(publicPatch, "sessionId") &&
|
|
publicPatch.sessionId !== existingEntry.sessionId
|
|
? { ...publicPatch, ...MAIN_SESSION_RECOVERY_CLEAR_PATCH }
|
|
: publicPatch;
|
|
}
|
|
|
|
export function reconcilePluginSessionStore(params: {
|
|
internalStore: Record<string, InternalSessionEntry>;
|
|
publicStore: Record<string, SessionEntry>;
|
|
}): void {
|
|
for (const sessionKey of Object.keys(params.internalStore)) {
|
|
if (!Object.hasOwn(params.publicStore, sessionKey)) {
|
|
delete params.internalStore[sessionKey];
|
|
}
|
|
}
|
|
for (const [sessionKey, publicEntry] of Object.entries(params.publicStore)) {
|
|
const projectedEntry = projectPluginSessionEntry(publicEntry as InternalSessionEntry);
|
|
const existingRecovery = activeRecoveryFieldsForSameSession(
|
|
params.internalStore[sessionKey],
|
|
projectedEntry.sessionId,
|
|
);
|
|
const existingEntry = params.internalStore[sessionKey];
|
|
params.internalStore[sessionKey] =
|
|
existingEntry && existingEntry.sessionId !== projectedEntry.sessionId
|
|
? { ...projectedEntry, ...MAIN_SESSION_RECOVERY_CLEAR_PATCH }
|
|
: existingRecovery
|
|
? { ...projectedEntry, ...existingRecovery }
|
|
: projectedEntry;
|
|
}
|
|
}
|