/**
 * SessionManager — on-disk persistence and recall for indusagi agent sessions.
 *
 * Storage model:
 * Every conversation is a JSONL log: one JSON object per line, only ever appended.
 * Because we never rewrite earlier lines, two writers cannot stomp on each other,
 * and the file can be replayed from scratch to reconstruct state. Load, save,
 * branch, and compaction are all just operations layered over that log.
 *
 * Format at a glance:
 * - On disk: JSONL — exactly one JSON object per line.
 * - Location: ~/.indusvx/agent/sessions/ (overridable through the
 *   INDUSAGI_DIR / INDUSAGI_AGENT_DIR / INDUSAGI_CODING_AGENT_DIR env vars).
 * - Line kinds: messages, model switches, reasoning-tier switches, compaction
 *   markers, and a few others.
 * - Concurrency: append-only, so no in-place mutation and therefore no locks.
 * - Recovery: replay the log to rebuild the entire session.
 *
 * Capabilities:
 * - Concurrent-writer-tolerant append-only files.
 * - Forking the conversation off any prior record (branching).
 * - Separate record kinds per session event.
 * - Compaction tracking (a recap plus its metadata).
 * - Extension-private records for add-on state.
 * - Header checks and graceful tolerance of a damaged final line.
 * - Streaming reads, so opening a large log stays cheap.
 *
 * What the log looks like (each line is its own JSON object):
 * ```
 * {"type":"session","version":3,"id":"abc123","timestamp":"...","cwd":"/path","parentSession":"parent-id"}
 * {"type":"message","id":"msg1","parentId":null,"timestamp":"...","message":{...}}
 * {"type":"message","id":"msg2","parentId":"msg1","timestamp":"...","message":{...}}
 * {"type":"model_change","id":"mc1","parentId":"msg2","timestamp":"...","provider":"openai","modelId":"gpt-4"}
 * {"type":"thinking_level_change","id":"tlc1","parentId":"mc1","timestamp":"...","thinkingLevel":"high"}
 * {"type":"compaction","id":"comp1","parentId":"tlc1","timestamp":"...","summary":"...","firstKeptEntryId":"msg2","tokensBefore":5000}
 * ```
 *
 * Record kinds:
 * - "message"               — a user or assistant message
 * - "model_change"          — the active model was swapped
 * - "thinking_level_change" — the reasoning effort tier was changed
 * - "compaction"            — older history was condensed into a summary
 * - "branch_summary"        — recap left behind when a branch was abandoned
 * - "custom"                — opaque per-extension payload
 *
 * Replaying the log back into state:
 * 1. The first line is the session header.
 * 2. Walk records in order.
 * 3. Build the message list from the "message" records.
 * 4. Treat the latest "model_change" as the live model.
 * 5. Treat the latest "thinking_level_change" as the live tier.
 * 6. Use "compaction" markers to know which span was summarized.
 * 7. Keep "branch_summary" and "custom" records out of the model context.
 *
 * Branching, sketched:
 * ```typescript
 * const manager = SessionManager.open(existingPath);
 * manager.branch(someEarlierEntryId);
 * // anything appended now forms a fresh branch off that point;
 * // records on the original branch are untouched.
 * ```
 *
 * Compaction, in short:
 * - Condense the older messages into a single recap.
 * - Append a compaction record holding that recap and its metadata.
 * - Retain every original record so replay still works.
 * - When building model context, swap the recap in for the messages it covers.
 *
 * Large logs:
 * - Sessions can become very large.
 * - Reads stream the file instead of loading it all eagerly.
 * - Appends remain cheap, writing only to the file's end.
 *
 * Error handling:
 * - Lines that won't parse are silently skipped.
 * - A cut-off tail still loads everything up to the last good line.
 * - A missing session returns empty rather than throwing.
 *
 * File layout:
 * - Session directory: ~/.indusvx/agent/sessions/<encoded-cwd>/
 * - Session file: <timestamp>_<sessionId>.jsonl inside that directory
 * - The base directory is redirectable via the INDUSAGI_DIR family of env vars.
 *
 * Concurrency:
 * - Append-only JSONL requires no explicit locking.
 * - Multiple processes can append to one file safely.
 * - A reader may observe a half-written final line; the parser tolerates it.
 */
import type { AgentMessage } from "./types.js";
import type { ImageContent, Message, TextContent } from "../ml/index.js";
import { type BashExecutionMessage, type CustomMessage } from "./messages.js";
export declare const CURRENT_SESSION_VERSION = 3;
export interface SessionHeader {
    type: "session";
    version?: number;
    id: string;
    timestamp: string;
    cwd: string;
    parentSession?: string;
}
export interface NewSessionOptions {
    parentSession?: string;
}
export interface SessionEntryBase {
    type: string;
    id: string;
    parentId: string | null;
    timestamp: 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<T = unknown> extends SessionEntryBase {
    type: "compaction";
    summary: string;
    firstKeptEntryId: string;
    tokensBefore: number;
    /** Opaque add-on payload (for example an artifact index or structured-compaction markers). */
    details?: T;
    /** Set when an extension produced this record; left unset for ones the core emits (kept for back-compat). */
    fromHook?: boolean;
}
export interface BranchSummaryEntry<T = unknown> extends SessionEntryBase {
    type: "branch_summary";
    fromId: string;
    summary: string;
    /** Opaque add-on payload; never forwarded to the model. */
    details?: T;
    /** Set when an extension produced this record; left unset for ones the core emits. */
    fromHook?: boolean;
}
/**
 * A record extensions use to stash their own data inside the session log.
 * Tag it with customType so the extension can recognize its records later.
 *
 * Why: it lets an extension survive a session reload — on reload it filters the
 * log by its customType and rebuilds whatever internal state it needs.
 *
 * It is invisible to the model (buildSessionContext skips it). When you instead
 * want the content to reach the model, reach for CustomMessageEntry.
 */
export interface CustomEntry<T = unknown> extends SessionEntryBase {
    type: "custom";
    customType: string;
    data?: T;
}
/** A bookmark/marker that a user attaches to some other entry. */
export interface LabelEntry extends SessionEntryBase {
    type: "label";
    targetId: string;
    label: string | undefined;
}
/** Session-level metadata, such as a user-chosen display name. */
export interface SessionInfoEntry extends SessionEntryBase {
    type: "session_info";
    name?: string;
}
/**
 * A record extensions use to push their own content into the model context.
 * Tag it with customType so the extension can recognize its records later.
 *
 * Unlike CustomEntry, this one IS handed to the model: buildSessionContext
 * turns its content into a user message. Stash any model-irrelevant metadata in
 * details instead.
 *
 * display decides how the TUI treats it:
 * - false: not shown at all
 * - true: shown, but styled so it reads differently from a real user message
 */
export interface CustomMessageEntry<T = unknown> extends SessionEntryBase {
    type: "custom_message";
    customType: string;
    content: string | (TextContent | ImageContent)[];
    details?: T;
    display: boolean;
}
/** A non-header record; its id/parentId give the log its tree shape (this is what SessionManager's read methods hand back). */
export type SessionEntry = BranchSummaryEntry | CompactionEntry | CustomEntry | CustomMessageEntry | LabelEntry | ModelChangeEntry | SessionInfoEntry | SessionMessageEntry | ThinkingLevelChangeEntry;
/** Any line from the file, header included. */
export type FileEntry = SessionHeader | SessionEntry;
/** A node in the getTree() result — a copy of the session shape, safe to hand out. */
export interface SessionTreeNode {
    entry: SessionEntry;
    children: SessionTreeNode[];
    /** This entry's resolved label, when one is set. */
    label?: string;
}
export interface SessionContext {
    messages: AgentMessage[];
    thinkingLevel: string;
    model: {
        provider: string;
        modelId: string;
    } | null;
}
export interface SessionInfo {
    path: string;
    id: string;
    /** The cwd the session began in; an empty string when the session predates this field. */
    cwd: string;
    /** Display name taken from session_info records, if the user set one. */
    name?: string;
    /** Epoch millis of the session's most recent change. */
    lastModified: number;
    created: Date;
    modified: Date;
    messageCount: number;
    firstMessage: string;
    allMessagesText: string;
    /** Byte length of the session file. */
    size?: number;
}
export type ReadonlySessionManager = Pick<SessionManager, "getCwd" | "getSessionDir" | "getSessionId" | "getSessionFile" | "getLeafId" | "getLeafEntry" | "getEntry" | "getLabel" | "getBranch" | "getHeader" | "getEntries" | "getTree" | "getSessionName">;
/** Public wrapper around the in-place upgrade routine. */
export declare function migrateSessionEntries(entries: FileEntry[]): void;
/** Parse raw JSONL text into a list of records, dropping any unparseable lines. */
export declare function parseSessionEntries(content: string): FileEntry[];
export declare function getLatestCompactionEntry(entries: SessionEntry[]): CompactionEntry | null;
/**
 * Resolve the model-facing context for a session by walking its record tree.
 * When leafId is given, the walk starts there and climbs to the root.
 * Compaction summaries and branch recaps encountered on the way are applied.
 */
export declare function buildSessionContext(entries: SessionEntry[], leafId?: string | null, byId?: Map<string, SessionEntry>): SessionContext;
/** Read and parse a session file from disk into its records. */
export declare function loadEntriesFromFile(filePath: string): FileEntry[];
/** Return the path of the most recently modified valid session file in a directory, or null. */
export declare function findMostRecentSession(sessionDir: string): string | null;
export type SessionListProgress = (loaded: number, total: number) => void;
/**
 * Drives conversation sessions, each an append-only record tree persisted as JSONL.
 *
 * id/parentId on every record give the log its tree shape. A "leaf" pointer marks
 * where we are now; appending hangs a new child off that leaf. Branching just
 * moves the leaf back to an earlier record, so a new line of conversation can grow
 * without rewriting any history.
 *
 * Call buildSessionContext() for the model-ready message list — it follows the
 * root-to-leaf path and folds in any compaction summaries along the way.
 */
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();
    /** Point the manager at 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;
    /** Attach a message under the current leaf and move the leaf onto it. Returns the new record id.
     * Compaction and branch-summary messages cannot go through here on purpose:
     * they belong as their own first-class records rather than buried inside message
     * records, which keeps them easy to locate. Route those through appendCompaction()
     * and branchWithSummary() instead.
     */
    appendMessage(message: Message | CustomMessage | BashExecutionMessage): string;
    /** Record a reasoning-tier switch under the leaf and advance to it; yields the record id. */
    appendThinkingLevelChange(thinkingLevel: string): string;
    /** Record a model switch under the leaf and advance to it; yields the record id. */
    appendModelChange(provider: string, modelId: string): string;
    /** Record a compaction summary under the leaf and advance to it; yields the record id. */
    appendCompaction<T = unknown>(summary: string, firstKeptEntryId: string, tokensBefore: number, details?: T, fromHook?: boolean): string;
    /** Record an extension-owned custom entry under the leaf and advance to it; yields the record id. */
    appendCustomEntry(customType: string, data?: unknown): string;
    /** Record a session-info entry (such as a display name); yields the record id. */
    appendSessionInfo(name: string): string;
    /** Return the session's current name, drawn from the newest session_info entry that has one. */
    getSessionName(): string | undefined;
    /**
     * Record an extension-owned custom message that DOES reach the model context.
     * @param customType Tag the extension uses to find its records after a reload
     * @param content The message body (a string, or a TextContent/ImageContent array)
     * @param display TUI visibility (true shows it styled, false hides it)
     * @param details Optional metadata for the extension; never forwarded to the model
     * @returns The new record's id
     */
    appendCustomMessageEntry<T = unknown>(customType: string, content: string | (TextContent | ImageContent)[], display: boolean, details?: T): string;
    getLeafId(): string | null;
    getLeafEntry(): SessionEntry | undefined;
    getEntry(id: string): SessionEntry | undefined;
    /**
     * List the immediate children of a given entry.
     */
    getChildren(parentId: string): SessionEntry[];
    /**
     * Return the label attached to an entry, if it has one.
     */
    getLabel(id: string): string | undefined;
    /**
     * Attach a label to an entry, or remove the one it has.
     * Labels are user-supplied markers for bookmarking and navigation.
     * Pass undefined (or an empty string) to clear the existing label.
     */
    appendLabelChange(targetId: string, label: string | undefined): string;
    /**
     * Climb from an entry to the root, returning every record on the path in order.
     * Every record kind is included (messages, compaction, model changes, and so on).
     * For the model-ready message list, use buildSessionContext() instead.
     */
    getBranch(fromId?: string): SessionEntry[];
    /**
     * Produce the context handed to the model, by traversing the tree from the current leaf.
     */
    buildSessionContext(): SessionContext;
    /**
     * Return the session header record.
     */
    getHeader(): SessionHeader | null;
    /**
     * Return every non-header record as a shallow copy.
     * The log is append-only: grow it with the appendXXX() methods and move the
     * leaf with branch(). Existing records can never be edited or removed.
     */
    getEntries(): SessionEntry[];
    /**
     * Render the session as a tree, returned as a shallow defensive copy of the records.
     * A healthy session has a single root (the first record whose parentId is null).
     * Any record with a broken parent chain is surfaced as an extra root.
     */
    getTree(): SessionTreeNode[];
    /**
     * Begin a fresh branch off an earlier record.
     * The leaf pointer jumps to that record, so the next appendXXX() hangs a child
     * beneath it and a new branch begins. Nothing already on disk is touched.
     */
    branch(branchFromId: string): void;
    /**
     * Move the leaf pointer back to null, i.e. ahead of every record.
     * The next appendXXX() then writes a new root (parentId = null).
     * Handy when the user wants to re-edit the very first message.
     */
    resetLeaf(): void;
    /**
     * Branch like branch() does, but also leave behind a branch_summary record
     * that preserves a recap of the path being walked away from.
     */
    branchWithSummary(branchFromId: string | null, summary: string, details?: unknown, fromHook?: boolean): string;
    /**
     * Write out a brand-new session file holding just the root-to-leaf path for the given leaf.
     * Useful for pulling a single conversation line out of a branched session.
     * Returns the new file's path, or undefined when persistence is off.
     */
    createBranchedSession(leafId: string): string | undefined;
    /**
     * Spin up a fresh session.
     * @param cwd Working directory, saved into the session header
     * @param sessionDir Optional override for the session directory; defaults to ~/.indusvx/agent/sessions/<encoded-cwd>/.
     */
    static create(cwd: string, sessionDir?: string): SessionManager;
    /**
     * Open an existing session file.
     * @param path Path to the session file
     * @param sessionDir Optional directory used for later /new or /branch; defaults to the file's own parent.
     */
    static open(path: string, sessionDir?: string): SessionManager;
    /**
     * Resume the newest session in the directory, or start a fresh one when none exist.
     * @param cwd Working directory
     * @param sessionDir Optional override for the session directory; defaults to ~/.indusvx/agent/sessions/<encoded-cwd>/.
     */
    static continueRecent(cwd: string, sessionDir?: string): SessionManager;
    /** Open a memory-only session that never writes to disk. */
    static inMemory(cwd?: string): SessionManager;
    /**
     * Pull a session from one project directory into another.
     * Writes a new session under targetCwd that carries the source session's full history.
     * @param sourcePath Path to the session being copied from
     * @param targetCwd Working directory the new session is stored against
     * @param sessionDir Optional override for the session directory; defaults to the one for targetCwd.
     */
    static forkFrom(sourcePath: string, targetCwd: string, sessionDir?: string): SessionManager;
    /**
     * List every session stored for one directory.
     * @param cwd Working directory, used to derive the default session directory
     * @param sessionDir Optional override for the session directory; defaults to ~/.indusvx/agent/sessions/<encoded-cwd>/.
     * @param onProgress Optional progress callback, invoked as (loaded, total)
     */
    static list(cwd: string, sessionDir?: string, onProgress?: SessionListProgress): Promise<SessionInfo[]>;
    /**
     * List every session across all project directories.
     * @param onProgress Optional progress callback, invoked as (loaded, total)
     */
    static listAll(onProgress?: SessionListProgress): Promise<SessionInfo[]>;
}
