import type { AgentRecord } from '../tools/agent/index.js'; import type { SessionReturnContextSummary } from '../runtime/session-return-context.js'; import type { ConversationTitleSource } from '../core/conversation.js'; import type { SessionSurface } from '../runtime/session-surface.js'; /** * Metadata for a saved session (the first JSONL line). */ export interface SessionMeta { title: string; model: string; provider: string; timestamp: number; titleSource?: ConversationTitleSource | undefined; returnContext?: SessionReturnContextSummary | undefined; /** File format version written into the JSONL meta line. Present on files saved after schemaVersion was introduced. Missing on older files (treat as version 0). */ schemaVersion?: number | undefined; /** * Who caused this save: `'user'` for an explicit save the user asked for * (e.g. a `/save` command, never expired by the session-conversations * retention store, see runtime/retention/append-only-registry.ts), `'auto'` * for an automatic save (e.g. shutdownRuntime's save-on-exit), which the * bounded default retention policy may reclaim. Defaults to `'auto'` when * omitted at save time. A file with no `saveSource` at all (written before * this field existed) is treated as `'user'` by the retention store, never * assume an old file is safe to expire. * * INVARIANT, `'user'` is STICKY. Once a session file is stamped `'user'`, * no `'auto'` (or omitted) save over the same file can downgrade it back to * `'auto'`; SessionManager.save re-reads the existing file's stamp and keeps * `'user'`. Without this, an automatic periodic save of the same session id * (persistConversation, which defaults to `'auto'`) would quietly strip the * retention exemption off a conversation the user explicitly asked to keep, * and the next sweep would be free to delete it. Only an explicit * `saveSource: 'user'` ever changes the stamp, always upward. */ saveSource?: 'user' | 'auto' | undefined; } /** * Summary info for listing saved sessions. */ export interface SessionInfo { name: string; title: string; model: string; provider: string; timestamp: number; messageCount: number; filePath: string; titleSource?: ConversationTitleSource | undefined; returnContext?: SessionReturnContextSummary | undefined; } /** * SessionManager - Handles saving and loading named conversation sessions * as JSONL files under the configured surface session directory. * * Format: each line is a JSON object. * Line 0: { type: 'meta', ...SessionMeta } * Line N: { type: 'message', ...message fields } */ /** * Current schema version written to session files. * Increment when the file format changes in a backward-incompatible way. * Readers accept: version undefined (legacy, treated as 0), version <= CURRENT, and * version > CURRENT (future, logged as a warning, accepted with best-effort parsing). */ export declare const CURRENT_SESSION_SCHEMA_VERSION = 1; /** * Turn a session name (or session id) into the filename stem its durable * store file uses: `/.jsonl`. Module-level so a caller that * needs a session's store path, e.g. the recovery layer asking "is this * snapshot older than its own session's last clean save?" in * runtime/session-recovery.ts, derives exactly the same filename this class * writes, without constructing a SessionManager just to reach the rule. * {@link SessionManager.sanitizeName} delegates here, so there is one rule, * not two that can drift apart. */ export declare function sanitizeSessionName(name: string): string; export declare class SessionManager { private sessionsDir; constructor(baseDir: string, options?: { readonly surfaceRoot?: string | undefined; readonly sessionsDir?: string | undefined; /** * A declare-once `SessionSurface` (see platform/runtime/session-surface.ts). * When given, `sessionsDir` is resolved from `surface.sessionsDir`, * taking priority over an explicit `sessionsDir` or `surfaceRoot` option. */ readonly surface?: SessionSurface | undefined; }); /** * Remove any `.tmp-*` files left behind by a crashed write. * Cleanup errors are logged and startup continues. */ private _cleanupOrphanTempFiles; /** * Atomically write content to filePath via a temp file + fsync(file) + rename + fsync(dir). * Protects against partial writes and directory-entry reversion on power loss: * 1. Write content to a tmp file in the same directory. * 2. fsync the tmp file to flush its data to storage. * 3. rename the tmp file into place (atomic on POSIX). * 4. fsync the parent directory to flush the directory entry, without * this step, on power loss after rename the directory entry can * revert and the renamed file disappears. * Mirrors the reference implementation in platform/security/user-auth.ts * (atomicWriteSecretFile), which performs both fsyncs. */ private _atomicWrite; /** * Read just the `saveSource` stamp off an existing session file's meta line * (line 0), without loading the conversation. Returns undefined when the * file is absent, unreadable, or carries no readable stamp. */ private _readExistingSaveSource; /** * The `saveSource` to stamp on this write. `'user'` is sticky: an explicit * `'user'` always wins, and an `'auto'`/omitted save over a file already * stamped `'user'` PRESERVES `'user'` rather than downgrading it (see the * invariant on {@link SessionMeta.saveSource}). */ private _resolveSaveSource; /** * Save conversation messages to a JSONL session file. * Overwrites if file already exists. * Returns the sanitized filename used (may differ from input name). */ save(name: string, messages: object[], meta: SessionMeta, agentRecords?: AgentRecord[]): { filePath: string; sanitizedName: string; }; /** * Load a session from JSONL. Returns meta and messages (excluding removed ones). * Throws if the file does not exist or cannot be parsed. */ load(name: string): { meta: SessionMeta; messages: object[]; agentRecords: AgentRecord[]; }; /** * List all saved sessions with metadata, sorted by most recent first. */ list(): SessionInfo[]; /** * Get just the metadata for a session without loading all messages. * Returns null if the session does not exist or meta cannot be parsed. */ getMeta(name: string): SessionMeta | null; /** * Rename a session by rewriting its meta line with a new title. * The file is stored under the sanitized name, rename updates the title * field inside the file but does NOT rename the file itself. * Throws if the session does not exist. */ rename(name: string, newTitle: string): void; /** * Delete a session file. * Throws if the session does not exist. */ delete(name: string): void; /** * Search all sessions for messages containing the query string (case-insensitive). * Returns sessions with match count and up to 3 context snippets per session. */ search(query: string): Array<{ session: SessionInfo; matchCount: number; snippets: string[]; }>; /** * Sanitize a session name into a safe filename. * Replaces spaces with hyphens, strips non-alphanumeric/hyphen/underscore chars, * collapses multiple hyphens, trims leading/trailing hyphens. */ sanitizeName(name: string): string; } //# sourceMappingURL=manager.d.ts.map