import type { EventBus } from '../kernel/events.js'; import type { Logger } from '../types/logger.js'; import type { SecretScrubber } from '../types/secret-scrubber.js'; import type { ForkedSession, ResumedSession, SessionData, SessionEvent, SessionForkOptions, SessionMetadata, SessionStore, SessionSummary, SessionWriter, WorkspaceCheckpointRef, WorkspaceMaterializationResult } from '../types/session.js'; export interface SessionStoreOptions { dir: string; /** * Active project root used to revalidate persisted file-observation hashes * during resume. Omit for stores that only inspect/archive transcripts. */ projectRoot?: string | undefined; /** Optional EventBus for emitting session diagnostics. */ events?: EventBus | undefined; /** * Optional scrubber override. A DefaultSecretScrubber is always installed * so legacy plaintext is sanitized before caching/projection and new writes * retain write-time protection. */ secretScrubber?: SecretScrubber | undefined; /** * Optional guard consulted by {@link DefaultSessionStore.delete} before * removing a session. Returns `true` if the session is currently in use by * any live process (e.g. it is the active session of another terminal, TUI, * or WebUI in this project). The store ALWAYS also checks `active.json` * directly; this callback widens the check to cross-process live sessions * via the SessionRegistry. When omitted, only the `active.json` check runs. * Resolves to a human-readable reason when in use, or `null` when safe. */ isSessionInUse?: ((sessionId: string) => Promise) | undefined; /** Logger for structured warnings. Falls back to console.warn when omitted. */ logger?: Logger | undefined; } export declare class DefaultSessionStore implements SessionStore { private readonly dir; private readonly events?; private readonly secretScrubber; private readonly projectRoot?; private readonly checkpointCas?; private readonly isSessionInUse?; private readonly logger; /** * In-memory cache for load() results, keyed by session ID. The cache is * invalidated when the file's mtimeMs or size changes (indicating the * file was written to). This eliminates redundant full-file reads and * JSON parses when the same session is loaded multiple times within the * store's lifetime (e.g., webui session detail views, list() fallbacks). * * Max size is capped to prevent unbounded memory growth in long-running * processes. When the limit is reached, the oldest entry is evicted. */ private readonly _loadCache; private _loadCacheBytes; private _indexCache; private readonly shardManifestCache; private static readonly LOAD_CACHE_MAX_ENTRIES; private static readonly LOAD_CACHE_MAX_BYTES; private static readonly LIST_SCAN_CONCURRENCY; constructor(opts: SessionStoreOptions); /** * Emit a structured warning. Uses the configured Logger when available; * falls back to console.warn(JSON) so warnings are never silently dropped. */ private logWarn; private scrubSummaries; /** * Clear the load() cache. Useful for testing or when the caller knows * the file has changed externally (e.g., another process wrote to it). */ clearLoadCache(sessionId?: string): void; private deleteLoadCacheEntry; private emitRead; private emitWrite; private emitError; /** Absolute path to the session index file. */ private get indexFile(); /** Join session ID to its absolute path within the store directory. */ private sessionPath; private shardManifestPath; private shardKeyForSessionId; private invalidateShardManifestBySessionId; /** * Ensure the directory implied by the session ID exists. When the ID * contains a date prefix like `2026-06-06/...`, this creates the date * subdirectory so sessions group naturally by day. */ private ensureShardDir; create(meta: Omit): Promise; fork(id: string, opts?: SessionForkOptions): Promise; materializeWorkspaceCheckpoint(checkpoint: WorkspaceCheckpointRef, targetRoot: string): Promise; resume(id: string): Promise; load(id: string): Promise; /** * Fast-path loader that skips message reconstruction and adjacency repair. * * Use this for callers that only need the raw event stream + session * metadata — e.g. session listers, analytics, audit, and the TUI's * "events only" views. It avoids the message array build and * repairToolUseAdjacency cost on large session files (a long agent * run can have 50k+ events; rebuilding messages is O(events) and * allocates per-block, so skipping it is a meaningful win). * * The returned data.messages is an empty array; data.toolCallEnds * is computed from the raw events. usage is the sum across all * llm_response events — same as full load(). */ loadEventsOnly(id: string): Promise; private loadInternal; /** * Streaming search over a session's JSONL. Walks the file once, parses * each event lazily, and yields only the events that match `predicate`. * Stops as soon as `opts.limit` matches are collected. * * Why this exists: `load()` parses the entire file into memory and * rebuilds `messages`/`toolCallEnds` for every caller. `search()` only * needs to know which events contain matching text — a per-line * predicate is enough. The full parse work (and the `_loadCache` poll) * is wasted in that case. * * Memory: O(hits) regardless of file size. Disk: one linear scan, * terminated at `limit` if the caller asked for one. * * Errors: missing file yields []. Corrupt lines are skipped (same * policy as `load()`). Aborting via `signal` rejects with `AbortError`. */ searchEvents(id: string, predicate: (event: SessionEvent, eventIndex: number, ts: string) => boolean, opts?: { limit?: number | undefined; signal?: AbortSignal | undefined; }): Promise>; list(limit?: number): Promise; /** * List sessions matching filter criteria, using the cached index. * Filters are applied BEFORE sorting and slicing, so the caller gets * exactly `limit` matching sessions — not a slice of a larger fetch. * * This avoids the DefaultSessionReader pattern of fetching 1000 sessions * then linear-filtering: the index is already in memory (readIndex * caches it), and the filter runs over the cached array without any * additional disk I/O. */ listFiltered(criteria: { since?: string | undefined; until?: string | undefined; provider?: string | undefined; model?: string | undefined; minTokens?: number | undefined; titleContains?: string | undefined; limit?: number | undefined; }): Promise; private indexAppendCount; private static readonly COMPACT_EVERY; /** Append a session summary to the index. */ private appendToIndex; /** Append a tombstone entry for a deleted session. */ private writeTombstone; /** * Compact the index: read all entries, drop tombstones, deduplicate * (keep latest per session), and rewrite atomically. Acquires the index * file lock so a concurrent append (this process or another wstack in the * same project) can't be overwritten by the rewrite. */ private compactIndex; /** * Lock-free compaction body. The caller MUST already hold the index file * lock (via withFileLock(this.indexFile, ...)). Uses atomicWrite for the * rewrite so the temp file gets a random suffix (no collision between two * compactions) and the Windows transient-EPERM rename retry. */ private compactIndexInner; /** * Read the index file and return deduplicated session summaries. * Entries with a matching tombstone are filtered out. * Returns empty array when the index doesn't exist or is corrupt. */ private readIndex; /** * Rebuild the index from disk by scanning all sessions and writing a * fresh _index.jsonl. Useful after manual cleanup or index corruption. */ rebuildIndex(): Promise; private listFromDirectoryScan; private collectShardKeys; private readOrBuildShardManifest; private collectSessionFilesInShard; private collectSessionFiles; /** Recursively collect session IDs from date-shard subdirectories. * IDs include the date-prefix path (e.g. "2026-06-06/17-46-57Z_…"). * Skips `.jsonl`/`.summary.json` root files, dot-files, and * sub-directories that belong to fleet/subagent sessions. */ private collectSessionIds; private summaryFor; private readSummaryManifest; private summaryHeaderFor; /** * Delete a session and all associated files: JSONL, summary, plan/todos * sidecars, and the session directory (fleet.json, shared/, subagents/). * * Individual file deletions are best-effort (logged as structured warnings), * but a tombstone is always written so readIndex() filters this session out. * If the session directory itself can't be removed, the error is surfaced * to the caller so prune() can report it. */ private deleteSession; /** * Read the session id currently marked active in `active.json`, or `null` * when the lock is absent/unreadable. Shared by {@link delete} and * {@link prune} to avoid clobbering a session a live process is writing to. */ private readActiveSessionId; delete(id: string): Promise; rename(id: string, name: string): Promise; prune(maxAgeDays?: number): Promise; clearHistory(id: string): Promise; private summarize; private iterSessionEvents; } //# sourceMappingURL=session-store.d.ts.map