import type { EventBus } from '../kernel/events.js'; import type { Request, Response } from '../types/provider.js'; /** * ReplayLogStore — sidecar store for deterministic-replay support * (idea #2 from IDEAS.md). One JSONL file per session, recording * every provider request/response pair so the same agent loop can * be re-run later with frozen API responses. * * Why a sidecar (not the session JSONL)? * * Same reason as `AnnotationsStore` — the session log is * event-sourced and append-only; a provider request payload can be * tens of kilobytes (especially with long conversation history), * and we want replay to be opt-in (recorded only when the user * runs with `--replay` or a future equivalent). Mixing it into * the event log would inflate every read for replay-irrelevant * paths. * * File layout: `sessionScopedPath(dir, sessionId, '.replay.jsonl')`, * one entry per line. * Each entry: `{ hash, ts, request, response }`. The `hash` is * computed via `hashRequest` so lookups are O(1) by hash. * * Concurrency: per-session write queue (same pattern as * `AnnotationsStore`). Reads are lock-free; the write chain makes * the append + rehash sequence atomic. */ export interface ReplayEntry { hash: string; ts: string; request: Request; response: Response; } export interface ReplayLogStoreOptions { /** Root sessions directory used with `sessionScopedPath(..., '.replay.jsonl')`. */ dir: string; /** * Cap on the number of entries per session. When a `record` would * push the file beyond this, the oldest entries are evicted (LRU * by insertion order). Set to `Infinity` to disable rotation. * Defaults to 1000 — a single LLM call averages ~5KB serialized * (messages + tools + response), so 1000 entries is ~5MB per * session which is a reasonable upper bound. */ maxEntries?: number | undefined; events?: EventBus; traceId?: string; } export declare class ReplayLogStore { private readonly dir; private readonly events; private readonly traceId; private readonly writeChains; /** Per-session hash → on-disk location, with lazy entry hydration. */ private readonly cache; /** Per-session entry count on disk, to detect when compaction is needed. */ private readonly diskCount; private readonly maxEntries; private static readonly MAX_CACHED_SESSIONS; constructor(opts: ReplayLogStoreOptions); /** * Record a request/response pair. Idempotent on hash: a second * `record` for the same hash is a no-op (the existing entry wins). * Returns the hash. */ record(input: { sessionId: string; request: Request; response: Response; }): Promise; /** * Look up an entry by hash. Returns `null` when the request has * not been recorded for this session. O(1) after the first call * per session (in-memory cache). */ lookup(sessionId: string, hash: string): Promise; /** All recorded entries for a session, in insertion order. */ load(sessionId: string): Promise; /** * List every session id that has a replay log in the store dir. * Returns an array of `{ sessionId, entryCount, path }` sorted * by sessionId for stable output. Used by `wstack replay --list`. */ list(): Promise>; private filePath; private countEntries; private parseReplayLine; private readAll; private writeAll; private ensureCache; private hydrateEntry; /** * Read and parse the entry at `location`, returning it ONLY when its hash * matches `hash`. A stale offset can land on a different valid line; without * this identity check that line would be returned as if it were the * requested entry (silent wrong data). Returns null on any mismatch/failure. */ private readEntryAt; private enqueue; } //# sourceMappingURL=replay-log-store.d.ts.map