import { type Message, type Provider, type Usage } from "@kenkaiiii/gg-ai";
import type { AgentTurnTiming } from "@kenkaiiii/gg-agent";
import type { CompletedItem } from "../ui/app-items.js";
import { type StorageNormalizationMetrics } from "./session-storage.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";
export declare const TURN_METRIC_CUSTOM_KIND = "turn_metric";
export type TurnMetricCost = {
    status: "known";
    usd: number;
    source: string;
    effectiveAt: string;
} | {
    status: "unavailable";
    reason: string;
};
export interface TurnMetricPayload {
    version: 1;
    turn: number;
    provider: Provider;
    model: string;
    stopReason: string;
    usage: Usage;
    timing: AgentTurnTiming;
    cost: TurnMetricCost;
}
/** Custom-entry kind for a Ken Kai (mentor agent) turn. Ken's advisory
 *  conversation is NOT part of the LLM message history (GG 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 (Ken runs concurrently). `afterMessageCount` is the
 *  number of non-system messages that existed when the turn was recorded, used
 *  to interleave Ken turns back into the transcript chronologically. */
export declare const KEN_TURN_CUSTOM_KIND = "ken_turn";
export interface KenTurnPayload {
    version: 1;
    question: string;
    reply: string;
    afterMessageCount: number;
    /** Read-only: branch messages that preceded this entry in FILE order. Never
     *  persisted — see {@link RecordedPosition}. */
    recordedAfterMessageCount?: number;
}
/**
 * Read-time position rescue for transcript markers.
 *
 * `afterMessageCount` is authoritative, but historical sessions were rewritten
 * by compaction without rebasing it (markers were re-persisted carrying indices
 * from the much longer pre-compaction transcript). Those anchors then replay far
 * too late or past the end — the "everything bunched at the bottom" symptom.
 *
 * File order gives an independent, always-in-range estimate: the number of
 * branch messages already written when the marker line was appended. It's only
 * consulted when the stored anchor is out of range, so healthy sessions are
 * untouched.
 */
export interface RecordedPosition {
    recordedAfterMessageCount?: number;
}
/** Custom-entry kind for an autopilot verdict marker. Mirrors `ken_turn`:
 *  persisted as a `custom` entry with `parentId: null` so it's never on the
 *  message DAG (GG Coder never sees it) but survives resume/compaction and
 *  interleaves back into the transcript via `afterMessageCount`. Covers all
 *  four terminal/near-terminal autopilot markers so a resumed session renders
 *  the exact same Ken bubble the live run showed — never the raw verdict
 *  keyword (e.g. `ALL_CLEAR`) the model actually replied with. */
export declare const AUTOPILOT_MARKER_CUSTOM_KIND = "autopilot_marker";
export interface AutopilotMarkerPayload extends RecordedPosition {
    version: 1;
    phase: "prompted" | "done" | "human" | "capped" | "plan_approved";
    reason?: string;
    body?: string;
    afterMessageCount: number;
}
/** Custom-entry kind for a generic app transcript marker (plan-mode banner,
 *  task header, error row, user-bubble display hint). Same not-on-the-DAG
 *  treatment as Ken turns / autopilot markers: persisted with `parentId: null`
 *  so the LLM never sees it, anchored by `afterMessageCount` so the host can
 *  interleave it back into the transcript on resume. */
export declare const APP_MARKER_CUSTOM_KIND = "app_transcript_marker";
export interface AppMarkerPayload extends RecordedPosition {
    version: 1;
    kind: "plan" | "task" | "error" | "user_hint" | "compaction" | "agent_handoff"
    /** Mid-session model/provider change; `data` carries { from, to, provider }. */
     | "model_switch";
    afterMessageCount: number;
    /** Kind-specific display fields (reason/title/headline/kenSent/counts/…). */
    data: Record<string, unknown>;
}
export type SessionEntry = MessageEntry | ModelChangeEntry | ThinkingLevelChangeEntry | CompactionEntry | LabelEntry | CustomEntry;
export interface SessionHeader {
    type: "session";
    version: 2;
    id: string;
    /** Stable identity shared by checkpoint files created during compaction. */
    conversationId?: string;
    /** Stable display fallback retained when checkpoint messages contain only internal summaries. */
    preview?: 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 SessionMaintenanceMetrics extends StorageNormalizationMetrics {
    deletedFiles: number;
    deletedBytes: number;
    archivedFiles: number;
    archivedSourceBytes: number;
    archivedBytes: number;
    bytesSaved: number;
    failures: 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 static activePathsByRoot;
    private static maintenanceByRoot;
    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, options?: {
        conversationId?: string;
        preview?: string;
    }): Promise<{
        id: string;
        path: string;
        header: SessionHeader;
    }>;
    registerActivePath(sessionPath: string): void;
    unregisterActivePath(sessionPath: string): void;
    private protectedSessionBases;
    load(sessionPath: string): Promise<{
        header: SessionHeader;
        entries: SessionEntry[];
        path: string;
    }>;
    private readSessionInfo;
    private sessionCandidates;
    list(cwd: string): Promise<SessionInfo[]>;
    getMostRecent(cwd: string): Promise<string | null>;
    findById(cwd: string, sessionId: string): Promise<string | null>;
    private storageDirectories;
    private logicalSessionBases;
    private removeStoragePath;
    pruneOldSessions(options: {
        maxAgeDays: number;
        keepPaths?: string[];
    }): Promise<{
        deletedFiles: number;
        freedBytes: number;
    }>;
    runMaintenance(options: {
        retentionDays: number;
        keepPaths?: string[];
        now?: number;
    }): Promise<SessionMaintenanceMetrics>;
    private runMaintenanceUnsafe;
    appendEntry(sessionPath: string, entry: SessionEntry): Promise<void>;
    appendTurnMetric(sessionPath: string, payload: TurnMetricPayload): 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[];
    /**
     * Walk entries in file order, tracking how many branch (non-system) messages
     * have been written so far, and hand each custom entry that count. This is
     * the independent position estimate behind {@link RecordedPosition}.
     */
    private mapCustomEntriesInFileOrder;
    /** Read all persisted Ken turns in file order. Returns them regardless of
     *  branch (Ken turns are not chained into the DAG), validated + normalized. */
    getKenTurns(entries: SessionEntry[], leafId?: string | null): KenTurnPayload[];
    /** Read all persisted app transcript markers in file order, validated +
     *  normalized (same not-on-the-DAG treatment as Ken turns). */
    getAppMarkers(entries: SessionEntry[], leafId?: string | null): AppMarkerPayload[];
    /** Read validated per-turn usage and timing records in file order. */
    getTurnMetrics(entries: SessionEntry[]): TurnMetricPayload[];
    /** Read all persisted autopilot markers in file order, validated + normalized
     *  (same not-on-the-DAG treatment as Ken turns). */
    getAutopilotMarkers(entries: SessionEntry[], leafId?: string | null): AutopilotMarkerPayload[];
    /**
     * 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