import type { Message, Provider } from "@prestyj/ai";
import type { CompletedItem } from "../ui/app-items.js";
interface BaseEntry {
    id: string;
    parentId: string | null;
    timestamp: string;
}
export interface MessageEntry extends BaseEntry {
    type: "message";
    message: Message;
}
export interface ModelChangeEntry extends BaseEntry {
    type: "model_change";
    provider: Provider;
    model: string;
}
export interface ThinkingLevelChangeEntry extends BaseEntry {
    type: "thinking_level_change";
    level: string;
}
export interface CompactionEntry extends BaseEntry {
    type: "compaction";
    originalCount: number;
    newCount: number;
    summary: string;
}
export interface LabelEntry extends BaseEntry {
    type: "label";
    label: string;
}
export interface CustomEntry extends BaseEntry {
    type: "custom";
    kind: string;
    data: unknown;
}
export declare const DISPLAY_ITEM_CUSTOM_KIND = "display_item";
/** Custom-entry kind for a Nolan Grout (mentor agent) turn. Nolan's advisory
 *  conversation is NOT part of the LLM message history (EZ Coder never sees it),
 *  but it's persisted alongside the build session so it survives resume. Stored
 *  as a `custom` entry with `parentId: null` so it is NEVER on the message DAG
 *  branch — this keeps it out of `getMessages()` AND avoids racing the build
 *  session's leaf pointer (Nolan runs concurrently). `afterMessageCount` is the
 *  number of non-system messages that existed when the turn was recorded, used
 *  to interleave Nolan turns back into the transcript chronologically. */
export declare const NOLAN_TURN_CUSTOM_KIND = "nolan_turn";
export interface NolanTurnPayload {
    version: 1;
    question: string;
    reply: string;
    afterMessageCount: number;
}
export type SessionEntry = MessageEntry | ModelChangeEntry | ThinkingLevelChangeEntry | CompactionEntry | LabelEntry | CustomEntry;
export interface SessionHeader {
    type: "session";
    version: 2;
    id: string;
    timestamp: string;
    cwd: string;
    provider: Provider;
    model: string;
    leafId: string | null;
}
export interface SessionInfo {
    id: string;
    path: string;
    timestamp: string;
    /** Timestamp of the most recent message (falls back to creation timestamp). */
    lastActivity: string;
    cwd: string;
    messageCount: number;
}
export interface BranchInfo {
    /** The entry ID where this branch diverges from its parent branch */
    branchPointId: string;
    /** The leaf (tip) entry ID of this branch */
    leafId: string;
    /** Number of entries in this branch after the branch point */
    entryCount: number;
    /** Timestamp of the first entry in the branch */
    timestamp: string;
}
export declare class SessionManager {
    private sessionsDir;
    private warnedPersistCodes;
    /** Called once per error code when session persistence fails (e.g. ENOSPC). */
    onPersistError?: (error: NodeJS.ErrnoException) => void;
    constructor(sessionsDir: string);
    /**
     * Session persistence must never crash a live session. Disk-full (ENOSPC),
     * permission, or quota errors during transcript writes are reported once
     * per error code and otherwise swallowed — the in-memory session keeps going.
     */
    private handlePersistError;
    private dirForCwd;
    create(cwd: string, provider: Provider, model: string): Promise<{
        id: string;
        path: string;
        header: SessionHeader;
    }>;
    load(sessionPath: string): Promise<{
        header: SessionHeader;
        entries: SessionEntry[];
    }>;
    list(cwd: string): Promise<SessionInfo[]>;
    getMostRecent(cwd: string): Promise<string | null>;
    findById(cwd: string, sessionId: string): Promise<string | null>;
    /**
     * Delete session files older than `maxAgeDays` across ALL project dirs.
     * Age is judged by file mtime, so a session that's still being appended to
     * is never considered old. Best-effort: per-file errors are skipped so a
     * locked or vanished file can't break startup. Empty project dirs left
     * behind are removed. Returns what was freed for logging.
     */
    pruneOldSessions(options: {
        maxAgeDays: number;
        keepPaths?: string[];
    }): Promise<{
        deletedFiles: number;
        freedBytes: number;
    }>;
    appendEntry(sessionPath: string, entry: SessionEntry): Promise<void>;
    updateLeaf(sessionPath: string, leafId: string): Promise<void>;
    private updateLeafUnsafe;
    /**
     * Get messages for the current branch. If leafId is set, walks the
     * DAG from leaf to root. Otherwise returns all entries linearly.
     */
    getMessages(entries: SessionEntry[], leafId?: string | null): Message[];
    getDisplayItems(entries: SessionEntry[], _leafId?: string | null): CompletedItem[];
    /** Read all persisted Nolan turns in file order. Returns them regardless of
     *  branch (Nolan turns are not chained into the DAG), validated + normalized. */
    getNolanTurns(entries: SessionEntry[]): NolanTurnPayload[];
    /**
     * Ensure every assistant message with tool_use blocks is followed by a tool
     * message containing matching tool_result entries. Inserts synthetic
     * tool_result messages where needed to prevent Anthropic API 400 errors.
     */
    static repairToolPairs(messages: Message[]): Message[];
    /**
     * Build a lookup Map from entry id → entry. Reusable across multiple
     * getBranch / listBranches calls on the same entry set.
     */
    private buildIndex;
    /**
     * Walk the DAG from a leaf entry back to the root, returning entries
     * in chronological order (root → leaf). This is the "branch" — the
     * path through the conversation tree that leads to the given leaf.
     *
     * Accepts an optional pre-built index to avoid redundant Map allocations
     * when called in a loop.
     */
    getBranch(entries: SessionEntry[], leafId: string | null, byId?: Map<string, SessionEntry>): SessionEntry[];
    /**
     * List all branches (leaf nodes) in a session's entry DAG.
     * A leaf is any entry whose id is not referenced as a parentId by any other entry.
     */
    listBranches(entries: SessionEntry[]): BranchInfo[];
}
export {};
//# sourceMappingURL=session-manager.d.ts.map