/** * transcript-journal-replay.ts, folding a transcript journal back into a live * conversation at resume. * * transcript-journal.ts owns the file: append, replay, quarantine, rotate. This * owns what a surface does with what came back, apply it to the conversation it * just hydrated, persist the result so the gap is closed for good, and rotate * the journal that filled it. * * Recovery protocol * ───────────────── * 1. Call replayJournal() with the journal path and the snapshot timestamp. * 2. If no records are newer than the snapshot, rotate the (now-stale) journal * silently and return. * 3. If records are found, apply the final record's messages, each journal * record carries the full conversation snapshot at that moment, so the record * with the newest timestamp is the authoritative post-crash state (resilient * to seq collisions across re-inits onto a stale journal file). * 4. Rebuild the conversation history and call the snapshot writer so the gap is * durably closed before the user sees the restored conversation. * 5. Rotate the journal (it is no longer needed as a gap-filler). * 6. Return a result so the caller can emit an honest notice. * * The conversation and the snapshot writer are both injected: this decides WHAT * the restored state is, never where a surface keeps its sessions. */ import type { ConversationManager, ConversationMessageSnapshot } from '../core/conversation.js'; import type { SessionSurface } from './session-surface.js'; /** * What replay needs a conversation to be able to do. * * `rebuildHistory` is optional because it is a RENDERING concern: a surface that * paints a transcript re-lays it out after the messages change, and a headless * one has nothing to rebuild. */ export type JournalReplayConversation = Pick & { rebuildHistory?: () => void; }; export interface ReplayIntoConversationOptions { /** Absolute path to the journal file for this session. */ readonly journalPath: string; /** * The `timestamp` field from the loaded session snapshot (SessionMeta). * Only journal records with ts > snapshotTimestamp are replayed. */ readonly snapshotTimestamp: number; /** The live conversation to mutate with replayed messages. */ readonly conversation: JournalReplayConversation; /** Session ID, used when creating the post-replay journal instance for rotate(). */ readonly sessionId: string; /** * Persist the restored conversation so the gap is durably closed. * Called with the final replayed message list. Best-effort, failures * are swallowed so recovery never hard-fails a resume. */ readonly persistSnapshot: (messages: ConversationMessageSnapshot[]) => void; } export interface ReplayIntoConversationResult { /** Number of journal records that post-dated the snapshot. 0 if nothing to replay. */ readonly replayed: number; /** True if the journal tail was corrupt (quarantined). */ readonly hadCorruptTail: boolean; } /** * Replay journal records newer than `snapshotTimestamp` onto `conversation`. * * Returns a result object so the caller can emit an appropriate notice. * Never throws, all errors are swallowed to preserve the "best-effort" * recovery contract. */ export declare function replayJournalIntoConversation(options: ReplayIntoConversationOptions): ReplayIntoConversationResult; /** * Build the journal path for a session off the surface that owns it, then call * {@link replayJournalIntoConversation}. */ export declare function replayJournalForSession(options: Omit & { readonly surface: SessionSurface; }): ReplayIntoConversationResult; //# sourceMappingURL=transcript-journal-replay.d.ts.map