/** * Crash-recovery snapshots: write, offer, load, retire. * * A recovery snapshot (`/recovery/recovery-.jsonl`) is a * periodic autosave of a live conversation. It exists so that a process that * dies between clean saves does not take the conversation with it. Split out * of session-persistence.ts, which keeps the durable-store half (save / load / * last-session pointer); both halves resolve every path through the shared * scope layer in session-persistence-scope.ts. * * SUPERSESSION, when is a snapshot still live crash data? * A snapshot is offered when it is strictly newer than ITS OWN session's * durable store file (`/sessions/.jsonl`), or when that * session has no store file at all. It is NOT judged against the last-session * pointer, which advances on every turn-completion persist of ANY session: * under that older rule, one message typed in an unrelated session (or in a * `--continue`d resume of this one) silently buried a snapshot that still held * unsaved messages, with no UI path left to reach it. Judging a snapshot * against its own session's store keeps that impossible, an unrelated * session's activity cannot bury it, and a clean shutdown of the snapshot's * own session both writes the store AND deletes the snapshot, so the surviving * snapshot of a session whose store is older is a crash by definition. * * LIVENESS, is anything still writing it? * The unsolicited boot offer additionally skips any snapshot whose file was * touched within LIVE_REFRESH_WINDOW_MS: a file being rewritten right now * belongs to a process that is still running, not to a crash. The signal is * the file's own mtime rather than a marker file, because a marker only * exists in versions that write one, see LIVE_REFRESH_WINDOW_MS. The * explicit per-session probe (`checkRecoveryForSession`) does NOT apply this * rule: it answers a direct question about a named session, and the honest * answer there is what the caller asked for. */ import type { SessionReturnContextSummary } from './session-return-context.js'; import type { SessionSurface } from './session-surface.js'; import { type SessionPersistenceOptions, type SessionSnapshot } from './session-persistence-scope.js'; export type RecoveryFileInfo = { title: string; timestamp: number; sessionId: string; returnContext?: SessionReturnContextSummary | undefined; }; export declare function writeRecoveryFile(snapshot: SessionSnapshot, sessionId: string, title?: string, options?: SessionPersistenceOptions): void; /** * Delete a per-session recovery snapshot (after a clean save, or once its * conversation has been restored). With an explicit `sessionId` only that * session's file is removed, for the surface form, this also tries the * legacy shared per-session file (see legacySharedRecoveryFile), so a * snapshot restored via the dual-read fallback is retired from wherever it * actually lived. Without a `sessionId`, every recovery snapshot in the * scoped directory is cleared: this is the explicit FULL-RESET path, for a * caller that genuinely means "discard all crash state for this surface". It * is deliberately NOT what the prompted/silent recovery flows use, * `consumeRecovery`, `removeRecoveryPoint` and `autoRestoreRecovery` retire * exactly the one snapshot file they offered or loaded, so accepting one * session's snapshot can never destroy another session's. The full reset also * does NOT touch the legacy shared directory, which is shared across every * project that ever used this surfaceRoot before per-project scoping * existed; a bulk clear there could delete an unrelated project's crash * snapshot. Only a session-id-keyed operation (ids are unique) reaches into * it. A missing file is fine either way. */ export declare function deleteRecoveryFile(options?: SessionPersistenceOptions, sessionId?: string): void; /** * The newest crash-recovery snapshot that has not been superseded by a clean * save of its own session (see the SUPERSESSION note at the top of this file). * Returns null when no live crash snapshot exists. * * The surface form ALSO consults the legacy, home-anchored, fully unscoped * shared recovery directory (see legacySharedRecoveryDir) as a standing * one-time offer for a pre-upgrade snapshot that predates per-project * scoping entirely. That directory cannot be mapped to a project * deterministically, a snapshot found there might belong to a different * project that happened to share this surfaceRoot before scoping existed. * This is a deliberate, accepted tradeoff: offering a possibly-unrelated * snapshot once is judged better than silently losing a genuinely-relevant * one forever. * * A snapshot a live process is still refreshing is NOT offered: this is an * unsolicited boot-time question, and asking about state nobody lost is the * defect this rule exists to prevent (see LIVE_REFRESH_WINDOW_MS for why * freshness rather than a marker file is the signal). An explicit request * about a named session still answers honestly, see checkRecoveryForSession. */ export declare function checkRecoveryFile(options?: SessionPersistenceOptions): RecoveryFileInfo | null; /** * Whether ONE specific session has a live crash snapshot, a snapshot strictly * newer than that session's own durable store file, under exactly the rule * `checkRecoveryFile` uses. Returns its meta, or null when that session has no * snapshot or its snapshot was already superseded. * * This is the probe for a consumer's resume-a-named-session flow (`--continue` * and friends): loading the store copy directly would silently drop the tail of * messages that only the snapshot holds, so the consumer asks here first and * routes a non-null answer through its recovery prompt instead of resuming the * shorter copy. Like the keyed forms of `consumeRecovery` / * `removeRecoveryPoint`, it finds the snapshot in whichever directory it * actually lives (canonical first, then the legacy shared dir). */ export declare function checkRecoveryForSession(surface: SessionSurface, sessionId: string): RecoveryFileInfo | null; /** * Load a recovery snapshot's conversation. With an explicit `sessionId` the * matching per-session file is loaded; without one, the newest crash snapshot * (the same one checkRecoveryFile offers) is loaded. * * The surface form ALSO dual-reads the legacy shared recovery directory when * the canonical (scoped) location has nothing, see checkRecoveryFile's doc * comment for the cross-project caveat this accepts, and * legacySharedRecoveryDir for why it can never be migrated instead. */ export declare function loadRecoveryConversation(options?: SessionPersistenceOptions, sessionId?: string): SessionSnapshot | null; /** * A one-line receipt sink, the structural shape of FeatureAnnouncementStore's * announce-once queue (`record(id, text)`), so auto-restore can enqueue its * receipt into the same attach-time queue surfaces drain, without this module * depending on the config layer. */ export interface RecoveryReceiptSink { record(id: string, text?: string): boolean; } /** The outcome of a silent auto-restore. */ export interface RecoveryRestoreResult { readonly snapshot: SessionSnapshot; readonly info: RecoveryFileInfo; /** The one-line receipt describing the restore. */ readonly receipt: string; } /** * Silent crash-recovery restore. When a live crash snapshot exists (one its own * session has not superseded with a clean save), its conversation is loaded and * returned WITHOUT a prompt, its snapshot file is cleared, and a single one-line * receipt is enqueued into the receipts sink (exactly once per snapshot session) * so the restore surfaces as a receipt rather than an interruption. Returns null * when there is nothing to restore. * * This is the SDK-side replacement for the old restore-and-collide dance: with * per-session snapshot files there is no shared recovery file to guard, so the * consuming surface's collision-preservation workaround is no longer needed. */ export declare function autoRestoreRecovery(options?: SessionPersistenceOptions, receipts?: RecoveryReceiptSink): RecoveryRestoreResult | null; /** The outcome of the prompted "yes, resume it" recovery flow (`consumeRecovery`). */ export interface RecoveryConsumeResult { /** The loaded conversation, or null when there was nothing to consume. */ readonly snapshot: SessionSnapshot | null; /** True once a recovery snapshot was found, loaded, and deleted. */ readonly consumed: boolean; } /** * The "yes, resume it" primitive for a consumer's PROMPTED recovery flow (as * opposed to `autoRestoreRecovery`'s silent path): loads a session's recovery * snapshot and deletes its file in one operation, load-then-delete. If the * load finds nothing (or fails; `loadRecoveryConversation` converts a read * failure into `null` rather than throwing), the snapshot file is left * untouched, retirement only follows a successful load, so a bad read can * never destroy data that was never actually recovered. * * Contract: the SDK never applies the loaded snapshot to any conversation on * its own here. The caller decides what happens to the messages it gets * back, this keeps the prompted path honest, so retirement can't be * forgotten (unlike a hand-rolled load-then-maybe-delete sequence, where a * consumer can forget the delete half entirely). * * Exactly ONE snapshot is retired: the file that was loaded, in whichever * directory it actually lives (canonical or the legacy shared dir). Omitting * `sessionId` selects the newest snapshot, the same one `checkRecoveryFile` * offers, and still retires only that one file. Another session's * never-loaded snapshot is never collateral damage. */ export declare function consumeRecovery(surface: SessionSurface, sessionId?: string): RecoveryConsumeResult; /** The outcome of the prompted "no, and remove it" recovery flow (`removeRecoveryPoint`). */ export interface RecoveryRemoveResult { /** True when a recovery snapshot existed on disk and was deleted; false when there was nothing to remove. */ readonly removed: boolean; } /** * The "no, and remove it" primitive for a consumer's prompted recovery flow: * deletes a session's recovery snapshot WITHOUT loading it, and reports * honestly whether there was anything there to delete. Like `consumeRecovery`, * this never touches any conversation object, it only clears the on-disk * snapshot. * * Symmetrically with `consumeRecovery`, exactly ONE snapshot is retired: the * identified one, in whichever directory it actually lives. Omitting * `sessionId` declines the snapshot currently being offered (the newest) and * removes only that file, every other session's snapshot survives untouched. */ export declare function removeRecoveryPoint(surface: SessionSurface, sessionId?: string): RecoveryRemoveResult; //# sourceMappingURL=session-recovery.d.ts.map