import type { Session, ChatMessage, SubtaskMeta } from './types.js';
declare class SessionManager {
    private sessions;
    private cleanupTimer?;
    /**
     * Per-key promise chain used to serialize writes that perform a
     * read-modify-write on the in-memory session + persistent JSONL log.
     *
     * Without this, two concurrent {@link addMessage} calls on the same key
     * could interleave: A reads cached session, B reads cached session, both
     * push a different message into the same array, then both write — the
     * later write wins and the earlier message is lost from disk (the
     * in-memory copy is fine because both pushes happened on the same object
     * reference, but the JSONL log only reflects the most recent appendFile +
     * meta save).
     *
     * The lock is keyed by `${platform}:${channelId}:${threadId}`; different
     * threads still proceed in parallel.
     */
    private writeQueues;
    start(): Promise<void>;
    stop(): void;
    /**
     * Run `fn` while holding a per-key serial lock. Subsequent calls with the
     * same key wait until prior ones settle (success OR failure). Returns the
     * value of `fn`. Internal use only — keeps {@link addMessage} race-free
     * without introducing an external dep like p-queue.
     */
    private withLock;
    /**
     * Get or create a session for a conversation
     * Session key: `${platform}:${channelId}:${threadId}`
     */
    getOrCreateSession(platform: string, channelId: string, threadId: string, agent: string): Promise<Session>;
    /**
     * Get existing session without creating a new one
     * Returns undefined if no session exists or it's expired
     */
    getExistingSession(platform: string, channelId: string, threadId: string): Promise<Session | undefined>;
    /**
     * Switch the agent for a session.
     *
     * Generates a new session id but preserves thread identity AND every
     * thread-level field that isn't agent-specific:
     *   - usage             (per-thread /stats roll-up)
     *   - subtasks/active   (subtask state lives at thread level)
     *   - claudeSessionId   (Claude UUID survives /oc → /cc round-trips so the
     *                        underlying ~/.claude/projects jsonl keeps continuing
     *                        when the user comes back to claude)
     *
     * `model` and `variant` are reset because they live in different namespaces
     * across CLIs (`opencode` model ≠ `claude` model); carrying them across
     * would just feed the new agent an unrecognized argument.
     */
    switchAgent(platform: string, channelId: string, threadId: string, newAgent: string): Promise<Session>;
    /**
     * Append a message to the session history.
     *
     * Performance: instead of re-serializing the entire session JSON every
     * turn, the message body is appended to a JSONL log file alongside the
     * metadata (which gets a tiny atomic update for `lastActivity`).
     *
     * Concurrency: the entire read-modify-write is serialized per session key
     * via {@link withLock} so two concurrent calls on the same thread (e.g.
     * the IM message landing event AND a tool-result event arriving within
     * the same ms) cannot lose either message to a lost-update race.
     */
    addMessage(platform: string, channelId: string, threadId: string, message: ChatMessage): Promise<void>;
    /**
     * Persist `model` / `variant` / arbitrary patchable fields. Used by
     * `/model`, `/think` etc so the change survives a restart between turns.
     * Mutates the in-memory session in place AND writes metadata atomically.
     */
    patchSession(platform: string, channelId: string, threadId: string, patch: Partial<Pick<Session, 'model' | 'variant' | 'agent' | 'planMode'>>): Promise<Session | undefined>;
    /**
     * Persist claude-code resumable session bookkeeping (UUID + primed flag).
     * Returns the updated session, or undefined if no session exists yet for
     * this thread. Caller is expected to ensure the session exists first.
     */
    setClaudeSessionId(platform: string, channelId: string, threadId: string, claudeSessionId: string): Promise<Session | undefined>;
    markClaudeSessionPrimed(platform: string, channelId: string, threadId: string): Promise<void>;
    /**
     * Persist opencode's native session id (`ses_…`) once we've seen it in the
     * adapter's stream. Idempotent — calling with the same id is a no-op so
     * the per-event callback can fire as many times as opencode sends events.
     */
    setOpencodeSessionId(platform: string, channelId: string, threadId: string, opencodeSessionId: string): Promise<Session | undefined>;
    /**
     * Persist codex's native thread id (UUID) once we've seen it in the
     * adapter's `thread.started` event. Idempotent — same id may fire multiple
     * times per spawn. Mirrors setOpencodeSessionId.
     */
    setCodexSessionId(platform: string, channelId: string, threadId: string, codexSessionId: string): Promise<Session | undefined>;
    /**
     * Persist Antigravity's (`agy`) conversation UUID after the first run
     * creates `~/.gemini/antigravity-cli/conversations/<uuid>.pb`. The
     * adapter detects the file via a before/after readdir snapshot and
     * calls back via `opts.onAgentSessionId(id)`; this setter persists
     * it so the next turn passes `--conversation <id>`. Mirrors
     * setOpencodeSessionId / setCodexSessionId. Cleared by /new.
     */
    setAntigravitySessionId(platform: string, channelId: string, threadId: string, antigravitySessionId: string): Promise<Session | undefined>;
    /**
     * Increment the per-session usage roll-up after a successful agent
     * invocation. Used by router.callAgentWithHistory to power /stats.
     */
    recordUsage(platform: string, channelId: string, threadId: string, delta: {
        costUsd: number;
        promptChars: number;
        responseChars: number;
        durationMs: number;
    }): Promise<void>;
    /**
     * Reset conversation history (keep session but clear messages)
     */
    resetConversation(platform: string, channelId: string, threadId: string): Promise<Session | undefined>;
    /**
     * Get session with messages (convenience method)
     */
    getSessionWithHistory(platform: string, channelId: string, threadId: string): Promise<{
        session: Session;
        messages: ChatMessage[];
    } | undefined>;
    /**
     * Create or get a subtask session (independent from parent).
     */
    getOrCreateSubSession(platform: string, channelId: string, threadId: string, subtaskId: number, agent: string): Promise<Session>;
    /**
     * Set active subtask id on parent session — subsequent messages route to the subtask.
     */
    setActiveSubtask(platform: string, channelId: string, threadId: string, taskId: number | null): Promise<void>;
    /**
     * Get subtask metadata list from parent session.
     */
    getSubtasks(platform: string, channelId: string, threadId: string): Promise<SubtaskMeta[]>;
    /**
     * Scan all session files on disk and return every subtask, flattened, with
     * its parent platform/channelId/threadId/agent attached so the dashboard
     * can render subtasks across all conversations.
     *
     * Session files live as `<sanitized-key>.json` under SESSIONS_DIR. The
     * sanitized key is one-way (sha256-prefix per non-alnum char), so we
     * cannot reverse it — but each session file preserves the original
     * platform/channelId/threadId fields, which is what we need.
     */
    listAllSubtasks(opts?: {
        agent?: string;
    }): Promise<Array<SubtaskMeta & {
        platform: string;
        channelId: string;
        threadId: string;
        parentAgent: string;
        parentSessionId: string;
    }>>;
    /**
     * Update subtask metadata in parent session.
     */
    updateSubtask(platform: string, channelId: string, threadId: string, taskId: number, patch: Partial<SubtaskMeta>): Promise<void>;
    /**
     * Get next subtask id and persist the increment.
     *
     * Previously returned 1 when the parent session didn't exist yet, but
     * never created one — second call returned 1 again, leading to subtask
     * id collisions. Now we lazy-create the parent session so the counter
     * increments durably from the first call.
     */
    nextSubtaskId(platform: string, channelId: string, threadId: string, agent?: string): Promise<number>;
    /**
     * Persist the full session (metadata + messages). Used for the legacy
     * one-file format on resetConversation() and switchAgent() — anywhere
     * the messages array itself was rewritten. Atomic via tmp+rename.
     */
    private saveSession;
    /** Persist metadata only (no messages payload), atomically. */
    private saveSessionMeta;
    /** Crash-safe write: tmp file + atomic rename.
     *
     *  Recovers from ENOENT (parent dir missing) by mkdir-recursive + retry
     *  exactly once. This keeps the manager robust to environments where
     *  start() wasn't called yet — tests in particular tend to import
     *  sessionManager without going through the start() lifecycle, and
     *  saveSessionMeta's outer try/catch would otherwise swallow the ENOENT
     *  and silently produce a no-op write (the bug that caused
     *  session-subtasks.test.ts to fail on a fresh CI runner). */
    private atomicWrite;
    private loadSession;
    private cleanup;
}
export declare const sessionManager: SessionManager;
export {};
//# sourceMappingURL=session.d.ts.map