import type { AgentMessage } from "@apholdings/jensen-agent-core"; import type { ImageContent, Message, TextContent } from "@apholdings/jensen-ai"; import { type MemoryHistorySnapshot, type MemoryItem, type Task, type TodoSnapshotItem } from "./memory.js"; import { type BashExecutionMessage, type CustomMessage } from "./messages.js"; import { type SnapshotResolution } from "./snapshot-selector-resolver.js"; import type { TodoEngineState } from "./todo/todo-engine.js"; export declare const CURRENT_SESSION_VERSION = 3; /** Custom entry type for the durable child mission<->session binding (2.6.0). */ export declare const SESSION_CHILD_BINDING_CUSTOM_TYPE = "child_mission_binding"; /** Custom entry type for persisted cold-evidence references (2.6.0). */ export declare const SESSION_EVIDENCE_REFS_CUSTOM_TYPE = "session_evidence_refs"; /** Custom entry type for the durable tool-result virtualization ledger (2.6.0). */ export declare const SESSION_TOOL_VIRTUALIZATIONS_CUSTOM_TYPE = "session_tool_virtualizations"; /** * Durable identity binding between a child AgentSession and its logical * mission. Persisted inside the session so a restored session can never * silently attach itself to a different mission. */ export interface ChildMissionBinding { sessionId: string; missionId: string; parentMissionId?: string; } export interface SessionHeader { type: "session"; version?: number; id: string; timestamp: string; cwd: string; parentSession?: string; } export interface NewSessionOptions { id?: string; parentSession?: string; } export type MemorySnapshotResolution = SnapshotResolution; export interface SessionEntryBase { type: string; id: string; parentId: string | null; timestamp: string; /** Optional persisted integrity digest for observability readers. */ payloadSha256?: string; } export interface SessionMessageEntry extends SessionEntryBase { type: "message"; message: AgentMessage; } export interface ThinkingLevelChangeEntry extends SessionEntryBase { type: "thinking_level_change"; thinkingLevel: string; } export interface ModelChangeEntry extends SessionEntryBase { type: "model_change"; provider: string; modelId: string; } export interface CompactionEntry extends SessionEntryBase { type: "compaction"; summary: string; firstKeptEntryId: string; tokensBefore: number; /** Extension-specific data (e.g., ArtifactIndex, version markers for structured compaction) */ details?: T; /** True if generated by an extension, undefined/false if pi-generated (backward compatible) */ fromHook?: boolean; } export interface BranchSummaryEntry extends SessionEntryBase { type: "branch_summary"; fromId: string; summary: string; /** Extension-specific data (not sent to LLM) */ details?: T; /** True if generated by an extension, false if pi-generated */ fromHook?: boolean; } /** * Custom entry for extensions to store extension-specific data in the session. * Use customType to identify your extension's entries. * * Purpose: Persist extension state across session reloads. On reload, extensions can * scan entries for their customType and reconstruct internal state. * * Does NOT participate in LLM context (ignored by buildSessionContext). * For injecting content into context, see CustomMessageEntry. */ export interface CustomEntry extends SessionEntryBase { type: "custom"; customType: string; data?: T; } /** Label entry for user-defined bookmarks/markers on entries. */ export interface LabelEntry extends SessionEntryBase { type: "label"; targetId: string; label: string | undefined; } /** Session metadata entry (e.g., user-defined display name). */ export interface SessionInfoEntry extends SessionEntryBase { type: "session_info"; name?: string; } /** * Custom message entry for extensions to inject messages into LLM context. * Use customType to identify your extension's entries. * * Unlike CustomEntry, this DOES participate in LLM context. * The content is converted to a user message in buildSessionContext(). * Use details for extension-specific metadata (not sent to LLM). * * display controls TUI rendering: * - false: hidden entirely * - true: rendered with distinct styling (different from user messages) */ export interface CustomMessageEntry extends SessionEntryBase { type: "custom_message"; customType: string; content: string | (TextContent | ImageContent)[]; details?: T; display: boolean; } /** Session entry - has id/parentId for tree structure (returned by "read" methods in SessionManager) */ export type SessionEntry = SessionMessageEntry | ThinkingLevelChangeEntry | ModelChangeEntry | CompactionEntry | BranchSummaryEntry | CustomEntry | CustomMessageEntry | LabelEntry | SessionInfoEntry; /** Raw file entry (includes header) */ export type FileEntry = SessionHeader | SessionEntry; /** Tree node for getTree() - defensive copy of session structure */ export interface SessionTreeNode { entry: SessionEntry; children: SessionTreeNode[]; /** Resolved label for this entry, if any */ label?: string; } export interface SessionContext { messages: AgentMessage[]; thinkingLevel: string; model: { provider: string; modelId: string; } | null; memoryItems: MemoryItem[]; todos: TodoSnapshotItem[]; } export interface SessionInfo { path: string; id: string; /** Working directory where the session was started. Empty string for old sessions. */ cwd: string; /** User-defined display name from session_info entries. */ name?: string; /** Path to the parent session (if this session was forked). */ parentSessionPath?: string; created: Date; modified: Date; messageCount: number; firstMessage: string; allMessagesText: string; } export type ReadonlySessionManager = Pick; /** Exported for testing */ export declare function migrateSessionEntries(entries: FileEntry[]): void; /** Exported for compaction.test.ts */ export declare function parseSessionEntries(content: string): FileEntry[]; export declare function getLatestCompactionEntry(entries: SessionEntry[]): CompactionEntry | null; /** * Build the session context from entries using tree traversal. * If leafId is provided, walks from that entry to root. * Handles compaction and branch summaries along the path. */ export declare function buildSessionContext(entries: SessionEntry[], leafId?: string | null, byId?: Map): SessionContext; /** Exported for testing */ export declare function loadEntriesFromFile(filePath: string): FileEntry[]; /** Exported for testing */ export declare function findMostRecentSession(sessionDir: string): string | null; export type SessionValidationResult = { ok: true; } | { ok: false; reason: string; }; /** * Validate that a persisted session file can be parsed cleanly without mutating it. * * This is intentionally stricter than `loadEntriesFromFile()`'s callers need for * normal loading: explicit resume must be able to distinguish a missing session * from a corrupted one and must never overwrite the original bytes on failure. */ export declare function validateSessionFile(filePath: string): SessionValidationResult; export type SessionListProgress = (loaded: number, total: number) => void; /** * Manages conversation sessions as append-only trees stored in JSONL files. * * Each session entry has an id and parentId forming a tree structure. The "leaf" * pointer tracks the current position. Appending creates a child of the current leaf. * Branching moves the leaf to an earlier entry, allowing new branches without * modifying history. * * Use buildSessionContext() to get the resolved message list for the LLM, which * handles compaction summaries and follows the path from root to current leaf. */ export declare class SessionManager { private sessionId; private sessionFile; private sessionDir; private cwd; private persist; private flushed; private fileEntries; private byId; private labelsById; private leafId; private constructor(); /** Switch to a different session file (used for resume and branching) */ setSessionFile(sessionFile: string): void; newSession(options?: NewSessionOptions): string | undefined; private _buildIndex; private _rewriteFile; isPersisted(): boolean; getCwd(): string; getSessionDir(): string; getSessionId(): string; getSessionFile(): string | undefined; _persist(entry: SessionEntry): void; private _appendEntry; /** Append a message as child of current leaf, then advance leaf. Returns entry id. * Does not allow writing CompactionSummaryMessage and BranchSummaryMessage directly. * Reason: we want these to be top-level entries in the session, not message session entries, * so it is easier to find them. * These need to be appended via appendCompaction() and appendBranchSummary() methods. */ appendMessage(message: Message | CustomMessage | BashExecutionMessage): string; /** Append a thinking level change as child of current leaf, then advance leaf. Returns entry id. */ appendThinkingLevelChange(thinkingLevel: string): string; /** Append a model change as child of current leaf, then advance leaf. Returns entry id. */ appendModelChange(provider: string, modelId: string): string; /** Append a compaction summary as child of current leaf, then advance leaf. Returns entry id. */ appendCompaction(summary: string, firstKeptEntryId: string, tokensBefore: number, details?: T, fromHook?: boolean): string; /** Append a custom entry (for extensions) as child of current leaf, then advance leaf. Returns entry id. */ appendCustomEntry(customType: string, data?: unknown): string; appendSessionMemory(items: MemoryItem[]): string; appendSessionTodos(todos: TodoSnapshotItem[]): string; appendTodoEngineState(state: TodoEngineState): string; getLatestTodoEngineState(): TodoEngineState | undefined; appendSessionTasks(tasks: Task[]): string; getLatestSessionMemory(): MemoryItem[]; /** * Get memory history as a timeline of snapshots. * * Walks the current branch (from root to current leaf) and extracts all * session_memory custom entries. Each entry represents a complete snapshot * of memory at that point in time. * * History is derived from persisted session entries, not an event log. * Because persistence is snapshot-based, each snapshot contains the full * memory state, not individual add/update/delete events. * * @returns Snapshots in chronological order (oldest first), with the latest * snapshot on the current branch marked as isCurrent: true. */ getMemoryHistory(): MemoryHistorySnapshot[]; /** * Resolve a memory snapshot by its entry ID within the current branch history. * * @param id - The entry ID of the snapshot to find * @returns The snapshot with that ID, or undefined if not found in current branch */ findMemorySnapshotById(id: string): MemoryHistorySnapshot | undefined; /** * Strictly resolve a snapshot selector against current-branch history. * * The reusable resolution contract itself lives in * `snapshot-selector-resolver.ts`; current-branch-only semantics come from * resolving against `getMemoryHistory()` here. */ resolveMemorySnapshotSelector(input: string): MemorySnapshotResolution; getLatestSessionTodos(): TodoSnapshotItem[]; /** * Get the latest task snapshot from the current branch. * Returns an empty array if no tasks have been persisted. */ getLatestSessionTasks(): Task[]; /** * Persist the Reliability Kernel mission document for the current session. * * Stored as an additive custom entry so existing sessions remain loadable and * compaction never destroys the authoritative mission state. Callers are * responsible for serializing the MissionRuntime document. */ appendReliabilityState(document: unknown): string; /** * Return the latest Reliability Kernel mission document on the current branch, * or undefined when this session has no persisted reliability state. * * The payload is intentionally `unknown`: the reliability subsystem owns * schema validation and deserialization so a corrupt/foreign payload is * detected there without corrupting session loading. */ getLatestReliabilityState(): { data: unknown; entryId: string; } | undefined; /** * Persist the durable child mission binding for this session. Binding is a * one-way identity correlation: it does NOT duplicate mission state (the * DurableMissionStore remains authoritative); it only records which mission * this session belongs to so a mismatched restore can fail closed. */ appendChildBinding(binding: ChildMissionBinding): string; /** Latest durable child mission binding on the current branch, if any. */ getLatestChildBinding(): ChildMissionBinding | undefined; /** Persist the latest cold-evidence references produced by the Context Governor. */ appendSessionEvidenceRefs(refs: Array<{ evidenceId: string; summary: string; }>): string; /** Latest persisted cold-evidence references on the current branch, if any. */ getLatestSessionEvidenceRefs(): Array<{ evidenceId: string; summary: string; }>; /** Persist the durable tool-result virtualization ledger (full snapshot). */ appendSessionToolVirtualizations(records: unknown[]): string; /** Latest durable tool-result virtualization ledger on the current branch, if any. */ getLatestSessionToolVirtualizations(): Array>; /** Append a session info entry (e.g., display name). Returns entry id. */ appendSessionInfo(name: string): string; /** Get the current session name from the latest session_info entry, if any. */ getSessionName(): string | undefined; /** * Append a custom message entry (for extensions) that participates in LLM context. * @param customType Extension identifier for filtering on reload * @param content Message content (string or TextContent/ImageContent array) * @param display Whether to show in TUI (true = styled display, false = hidden) * @param details Optional extension-specific metadata (not sent to LLM) * @returns Entry id */ appendCustomMessageEntry(customType: string, content: string | (TextContent | ImageContent)[], display: boolean, details?: T): string; getLeafId(): string | null; getLeafEntry(): SessionEntry | undefined; getEntry(id: string): SessionEntry | undefined; /** * Get all direct children of an entry. */ getChildren(parentId: string): SessionEntry[]; /** * Get the label for an entry, if any. */ getLabel(id: string): string | undefined; /** * Set or clear a label on an entry. * Labels are user-defined markers for bookmarking/navigation. * Pass undefined or empty string to clear the label. */ appendLabelChange(targetId: string, label: string | undefined): string; /** * Walk from entry to root, returning all entries in path order. * Includes all entry types (messages, compaction, model changes, etc.). * Use buildSessionContext() to get the resolved messages for the LLM. */ getBranch(fromId?: string): SessionEntry[]; /** * Build the session context (what gets sent to the LLM). * Uses tree traversal from current leaf. */ buildSessionContext(): SessionContext; /** * Get session header. */ getHeader(): SessionHeader | null; /** * Get all session entries (excludes header). Returns a shallow copy. * The session is append-only: use appendXXX() to add entries, branch() to * change the leaf pointer. Entries cannot be modified or deleted. */ getEntries(): SessionEntry[]; /** * Get the session as a tree structure. Returns a shallow defensive copy of all entries. * A well-formed session has exactly one root (first entry with parentId === null). * Orphaned entries (broken parent chain) are also returned as roots. */ getTree(): SessionTreeNode[]; /** * Start a new branch from an earlier entry. * Moves the leaf pointer to the specified entry. The next appendXXX() call * will create a child of that entry, forming a new branch. Existing entries * are not modified or deleted. */ branch(branchFromId: string): void; /** * Reset the leaf pointer to null (before any entries). * The next appendXXX() call will create a new root entry (parentId = null). * Use this when navigating to re-edit the first user message. */ resetLeaf(): void; /** * Start a new branch with a summary of the abandoned path. * Same as branch(), but also appends a branch_summary entry that captures * context from the abandoned conversation path. */ branchWithSummary(branchFromId: string | null, summary: string, details?: unknown, fromHook?: boolean): string; /** * Create a new session file containing only the path from root to the specified leaf. * Useful for extracting a single conversation path from a branched session. * Returns the new session file path, or undefined if not persisting. */ createBranchedSession(leafId: string): string | undefined; /** * Create a new session. * @param cwd Working directory (stored in session header) * @param sessionDir Optional session directory. If omitted, uses default (~/.pi/agent/sessions//). */ static create(cwd: string, sessionDir?: string): SessionManager; /** * Create a new persisted session with an explicit stable identity. * Used by durable child execution so the childSessionId is established * BEFORE external execution and remains stable across process restart. */ static createWithId(cwd: string, sessionDir: string | undefined, id: string): SessionManager; /** * Open a specific session file. * @param path Path to session file * @param sessionDir Optional session directory for /new or /branch. If omitted, derives from file's parent. */ static open(path: string, sessionDir?: string): SessionManager; /** * Continue the most recent session, or create new if none. * @param cwd Working directory * @param sessionDir Optional session directory. If omitted, uses default (~/.pi/agent/sessions//). */ static continueRecent(cwd: string, sessionDir?: string): SessionManager; /** Create an in-memory session (no file persistence) */ static inMemory(cwd?: string): SessionManager; /** * Fork a session from another project directory into the current project. * Creates a new session in the target cwd with the full history from the source session. * @param sourcePath Path to the source session file * @param targetCwd Target working directory (where the new session will be stored) * @param sessionDir Optional session directory. If omitted, uses default for targetCwd. */ static forkFrom(sourcePath: string, targetCwd: string, sessionDir?: string): SessionManager; /** * List all sessions for a directory. * @param cwd Working directory (used to compute default session directory) * @param sessionDir Optional session directory. If omitted, uses default (~/.pi/agent/sessions//). * @param onProgress Optional callback for progress updates (loaded, total) */ static list(cwd: string, sessionDir?: string, onProgress?: SessionListProgress): Promise; /** * List all sessions across all project directories. * @param onProgress Optional callback for progress updates (loaded, total) */ static listAll(onProgress?: SessionListProgress): Promise; /** * Resolve a persisted session by its exact session ID. * * Session IDs are opaque strings (UUIDs by default). This compares logical * IDs exactly and never treats the requested value as a filesystem path, so * path-like input such as `../../etc/passwd` simply does not match anything. * * @param sessionId Exact session ID to resolve * @param sessionDir Optional session directory to search first (e.g. an * explicit `--session-dir`). The default global sessions * directory is always searched as a fallback. */ static findById(sessionId: string, sessionDir?: string): Promise; /** * Resolve a session by exact id within a single, explicitly-known directory. * * Unlike `findById`, this never scans the global sessions directory. Durable * child sessions live in a dedicated directory, so resume startup stays fast * and deterministic (no global filesystem walk). */ static findByExactIdInDir(sessionId: string, sessionDir: string): Promise; } //# sourceMappingURL=session-manager.d.ts.map