/** * Persistence layer for per-session recovery state. * * {@link RecoveryStateStore} manages on-disk recovery state files in the * project's `.rolebox/state/` directory — the same location used by the loop, * dispatch, and graph stores. Each session gets its own file keyed by session * ID (`recovery-${sessionID}.json`). * * Writes use the same atomic-file pattern as {@link LoopStore} (via * `atomicWriteSync` / the unique-temp-file pattern in `fs-util.ts`) so readers * never observe partially written state. Async writes are generation-guarded: * a superseded in-flight write aborts before its rename, so the on-disk state * always reflects the highest-generation write. * * @module recovery/state */ import type { RecoveryState, RecoveryAttempt } from "./types.ts"; /** * File-backed store for per-session recovery state. * * Supports both async fire-and-forget writes (`save`) and synchronous writes * (`saveSync`) for use during shutdown or critical sections where the async * path may not have flushed yet. A dirty-map tracks pending async writes and * can be drained via `flushSync`. */ export declare class RecoveryStateStore { private workspaceDir; private dirty; /** * Monotonic write generation per session. Every write intent (save, * saveSync, flushSync via saveSync, delete) bumps it; an in-flight async * write whose captured generation is stale aborts before its rename, so a * superseded write can never clobber newer on-disk state. */ private writeGen; constructor(workspaceDir: string); /** Bump and return the next write generation for `sessionID`. */ private nextWriteGen; /** Resolve the on-disk path for a given session's recovery state file. */ private filePath; /** * Load recovery state for a session from disk. * * Returns `null` when no state file exists or when the file cannot be * parsed — the caller treats this as "no prior recovery state". */ load(sessionID: string): RecoveryState | null; /** * Persist recovery state asynchronously (fire-and-forget). * * The write is recorded in the dirty map and scheduled. Callers that need * guarantees about durability before proceeding should use {@link saveSync} * or {@link flushSync}. */ save(sessionID: string, state: RecoveryState): void; /** * Internal async writer — swallows errors and logs them. Stages the content * into a per-write unique temp file, then — before committing the rename — * aborts (discarding the temp file and leaving the dirty map untouched) if a * newer write for the same session has superseded this one. */ private writeAsync; /** * Persist recovery state synchronously. * * Guarantees the file is on disk before returning. Use during shutdown, * process-exit handlers, or critical-path sections where an incomplete * write would corrupt recovery continuity. */ saveSync(sessionID: string, state: RecoveryState): void; /** * Record a single recovery attempt and persist the updated state. * * Automatically deduplicates: if an attempt with the same `timestamp` and * `strategy` already exists in the loaded state, it is not added again. * This prevents double-counting when `recordAttempt` is called multiple * times for the same logical event (e.g. after a restart before the async * write completed). * * When no prior state exists for the session, creates a fresh state * skeleton with zeroed metrics. */ recordAttempt(sessionID: string, attempt: RecoveryAttempt): void; /** * Update the state of an active recovery chain in-place. * * Merges the provided fields into the chain's current state, creating the * chain entry if it does not yet exist. The full state is persisted after * each update. */ updateChainState(sessionID: string, chainKey: string, update: { currentStep?: number; startTime?: number; totalAttempts?: number; }): void; /** * Remove the recovery state file for a session from disk. * * Silently succeeds when no file exists. The dirty map is also cleaned up * to prevent stale async writes from recreating the file. */ delete(sessionID: string): void; /** * Drain all pending async writes synchronously. * * Call before shutdown, process-exit, or any point where in-flight async * writes must not be lost. Each session's generation is bumped (via * {@link saveSync}) so the still-running async writes for those sessions * abort as superseded instead of overwriting the flushed state. After this * call the dirty map is empty. */ flushSync(): void; /** * Load all persisted recovery states from disk. * * Because recovery state files are discovered per-session ID via explicit * calls to {@link load}, this method is primarily a hook for startup * recovery where the set of known session IDs is provided externally. * * Currently returns an empty map — callers should use {@link load} per * session ID instead. */ loadAll(): Map; } //# sourceMappingURL=state.d.ts.map