/**
 * Agent Pool — manages per-session Agent instances for concurrent access.
 *
 * Each session (folder chat, private chat, capture) gets its own Agent
 * with independent conversation history. Shared resources (tools, system
 * prompt, model) are created once and passed to each new agent.
 *
 * Per-session locking ensures only one call runs per session at a time,
 * while different sessions can run in parallel.
 */
import { Agent } from '@mariozechner/pi-agent-core';
import type { AuthUser } from '../services/auth-service.js';
import type { MutationEvent } from './tools/graph-tools.js';
import type { ArtifactCallback, ArtifactToolTarget } from './tools/artifact-tools.js';
export interface AgentContext {
    user: AuthUser | null;
    /**
     * The RAW client session id for this turn (the chat's id as the client knows
     * it — what conversation-scratch images are keyed by). Distinct from the
     * pool's resolved session key (`user:<id>:<clientId>`). Lets session-scoped
     * tools (e.g. `save_file_to_assets`) target the current conversation's
     * scratch images rather than the user's other chats. Optional — unset for
     * CLI/non-web contexts.
     */
    clientSessionId?: string | null;
    onHighlight?: ((ids: string[]) => void) | null;
    onMutation?: ((m: MutationEvent) => void) | null;
    onArtifact?: ArtifactCallback | null;
    currentArtifactTarget?: ArtifactToolTarget | null;
    /**
     * Called by tool wrappers at the start (`enter`) and end (`leave`) of any
     * mutation that targets a folder. Powers the "X's agent is acting" sidebar
     * chip via wsHub.handleAgentActivity. Optional — no-op if unset.
     */
    onAgentActivity?: ((action: 'enter' | 'leave' | 'heartbeat', repo: string) => void) | null;
    /**
     * Wired only by the quick-capture flow. When the agent calls the
     * `nothing_to_capture` tool, this receives the structured reason so the
     * UI can render a toast without going through freeform assistant prose
     * (which is variable-quality and leaks the followups marker).
     */
    onNothingToCapture?: ((reason: string) => void) | null;
}
export interface SharedAgentConfig {
    /** Factory to create a fresh Agent instance with tools, prompt, and model. */
    createAgent: (userId?: number) => Agent;
    /**
     * Callback to load custom tools async (called once per new agent). The
     * pool passes the session's `userId` so per-user MCP connector OAuth
     * tokens can be resolved at tool-build time — without this, the agent
     * runtime can't read tokens stored under `(user, connector)`.
     *
     * Must return a Promise that resolves when tool loading is done (or
     * rejects on failure — the pool catches and treats it as "load gave
     * up, proceed with bundled tools"). `withAgent` awaits this on the
     * first chat call for the session so the first turn doesn't race
     * against still-loading MCP connector tools. The promise resolves
     * once and subsequent turns see it as a no-op.
     */
    loadCustomTools?: (agent: Agent, userId?: number) => Promise<void>;
    /** Base sessions directory. */
    sessionsDir: string;
}
export declare class AgentPool {
    private pool;
    private reapTimer;
    private config;
    /** Per-session context — set before each call, read by tool closures. */
    private contextMap;
    /**
     * Turn-scoped binding: the Agent instance currently executing a turn → that
     * turn's session id. Set for the duration of `fn` in `withAgent`, kept
     * INDEPENDENT of `pool`/`contextMap` so a mid-turn eviction (invalidateUser /
     * invalidateAll / reap) can't strip a running turn of its context.
     *
     * Tool closures resolve their session via `findSessionForAgent`, which scans
     * `pool` by agent identity — but eviction deletes the pool entry, so that
     * scan would return null and every session-scoped tool would silently lose
     * context (create_artifact fails with "No artifact canvas is available in
     * this context"; getUser falls back to a stale default; onMutation/onHighlight
     * no-op). This map is the durable fallback findSessionForAgent checks first.
     */
    private activeTurns;
    constructor(config: SharedAgentConfig);
    /**
     * Get the current context for a session (used by tool closures).
     * Returns null user if no context is set.
     */
    getContext(sessionId: string): AgentContext;
    /**
     * Acquire an agent for a session, run a callback, then release.
     * Per-session locking ensures serialized access within the same session.
     * Different sessions run in parallel.
     */
    withAgent<T>(sessionId: string, ctx: AgentContext, fn: (agent: Agent) => Promise<T>, opts?: {
        userId?: number;
        fallbackHistory?: Array<{
            role: string;
            content: string;
        }>;
    }): Promise<T>;
    /** Get the raw Agent for a session (for subscribe/streaming). Must be used with withAgent. */
    getAgent(sessionId: string): Agent | null;
    /** Abort the agent for a session. */
    abort(sessionId: string): void;
    /**
     * Evict every cached agent instance. Persists each session's history
     * first so no in-memory transcript is lost. Next chat for any session
     * spins up a fresh agent (via `createAgent` from the config), which
     * re-reads model/provider/api_key via `resolveApiKey` — picking up
     * whatever the store now has.
     *
     * Used as the eviction hook on secret/setting mutations: when the
     * operator rotates an API key or switches providers via Settings UI,
     * the running agent's captured apiKey is stale until restart. This
     * is the surgical fix instead of restarting the whole process.
     */
    invalidateAll(): void;
    /**
     * Evict every pool entry owned by a specific user. Used as the eviction
     * hook on per-user OAuth mutations: when a user OAuths a new connector
     * (or revokes one), their running agent's tool list — captured at
     * createAgentInstance time — no longer matches reality. Sessions
     * belonging to OTHER users stay in the pool untouched.
     *
     * Saved sessions are written to disk before removal, same as
     * `invalidateAll`, so no transcript is lost.
     */
    invalidateUser(userId: number): void;
    /** Save all sessions and stop the reaper. */
    shutdown(): Promise<void>;
    /** Clear all saved session files for a user. */
    clearSessions(userId?: number): number;
    get size(): number;
    /** Find the session ID for a given agent instance. */
    findSessionForAgent(agent: Agent): string | null;
    private getOrCreate;
    private loadSession;
    /**
     * Bound chat-context growth from artifact source. An ArtifactBundle inlines
     * all of its HTML/CSS/JS, so it's large — and it rides in history in two
     * places: `read_artifact` / `inspect_artifact` tool RESULTS, and the bundle
     * ARGUMENT of `create_artifact` calls. Left alone, the bundle reinflates the
     * prompt every turn until the provider overflows and returns an empty turn
     * (the "I didn't have a response" bug). After each turn we replace those
     * payloads in-place with a compact stub; the agent calls read_artifact again
     * when it needs the current source. Only large payloads are touched, so a
     * small artifact stays inline (no needless re-read). Defensive throughout —
     * a malformed message must never break session persistence.
     */
    private compactArtifactHistory;
    private saveSession;
    private getUserDir;
    /**
     * Delete a session's persisted state: evict the warm in-memory entry AND
     * unlink its on-disk JSON. Used when a chat is deleted — the per-session JSON
     * holds base64 copies of any vision images the agent saw, so it must be
     * removed for the "delete chat → images gone" promise to hold (composer-vision).
     * `sessionId` is the RESOLVED id the pool keys by (e.g. `user:7:abc`).
     */
    deleteSession(userId: number | undefined, sessionId: string): void;
    /** Delete EVERY persisted session for a user (the reset / delete-all path). */
    deleteAllSessions(userId: number | undefined): void;
    /** Unlink a session's on-disk JSON (best-effort, path-traversal guarded). */
    private removeSessionFile;
    private pruneSessions;
    private reap;
}
