/**
 * pool — the state behind `mcpClientPool`: who holds which client, what is
 * charged, and what ends when.
 *
 * Pattern: one state machine, mutated only between awaits.
 * Role:    §§ 5–7 of the design. Every mutation of the sets and the budget runs
 *          synchronously — a check and the move it allows are never split by
 *          an await — so two callers cannot both take the last slot.
 *
 *   - ADMISSION: every listing, call, acquire and ending is admitted against
 *     `maxOperations` before anything is sent. At each interaction the lazy
 *     sweep (revoked leases, idle ends, deferred ends) runs FIRST, so it
 *     reserves its endings before the new operation is admitted.
 *   - ONE OPEN PER KEY, from a plan resolved at that moment, under the pool's own
 *     signal and `initTimeoutMs`, never a caller's. A caller that gives up
 *     detaches alone; the operation that started the open keeps its charge until
 *     the open's work settles. Past the deadline every waiter is refused at once
 *     (`init-timeout`), and until the open's work settles the key cannot open
 *     again. A failure reaches every current waiter and is not cached.
 *   - THE HANDOFF: every operation waiting for an open holds a RESERVATION on
 *     the client until it takes its lease, so between the open settling and
 *     that lease the client is busy — nobody can evict, idle-end or end it. The
 *     client's state is checked again after every wait, and the lease taken in
 *     the same step: a lease is never handed out on a client that is ending.
 *   - MAKING ROOM: the principal's, then the pool's, least recently used IDLE
 *     client moves to the ending set — or `PoolFullError` at once.
 *   - ENDING reserves an ending slot and an operation slot FIRST; when either is
 *     full the client stays in the open set, counted, reported deferred, and is
 *     ended when slots free. Every end goes through `terminate()` — but a client
 *     whose handle this process lost: nothing can be sent for it, so its ending
 *     is its local close alone, charged the same way until it settles.
 *   - SETTLEMENT, never an abort, returns a slot (`work.ts`).
 *   - NOTHING UNCHECKED: on a pooled client every request carries a guard — its
 *     call's, or the client's open, listing or ending — and a request with none
 *     is refused before it is sent (`request-unmatched`), at the watched `send`
 *     and at the pool's own fetch alike.
 */
import type { Tool } from '../../tools/tools.js';
import type { McpTerminateOutcome } from '../types.js';
import { ClientSet, type PreviousEnd } from './clientSet.js';
import { SharedDefinitions, type Definition, type DefinitionList } from './definitions.js';
import { McpPoolRefusedError, PoolFullError, type McpPoolLimit } from './errors.js';
import { type PoolKey } from './identityKey.js';
import { type PoolSettings } from './options.js';
import { type ClientPlan } from './plan.js';
import { PooledClient, type EndReason } from './pooledClient.js';
import { Reporter, type UseFacts } from './record.js';
import { OperationBudget, Work, type Operation, type OperationKind, type WorkKind } from './work.js';
/** An app lease, as the pool tracks it for `maxLeaseMs`. */
export interface HeldLease {
    readonly client: PooledClient;
    readonly takenAt: number;
    /** Revoked: its next use throws `lease-expired`. */
    revoked: boolean;
    /** Released: idempotent. */
    released: boolean;
}
/** Where a list's definitions live: a client's own, or a shared entry. */
export interface ServedList {
    readonly list: DefinitionList;
    readonly shared: boolean;
    /** The definitions context: `pc:` or `sh:`. */
    readonly context: string;
}
/** What `stats()` returns: counts, never keys. */
export interface McpPoolStats {
    /** Clients in the open set: opening, busy or idle. */
    readonly open: number;
    readonly busy: number;
    readonly idle: number;
    readonly opening: number;
    /** Clients being ended, until each ending's work has settled. */
    readonly ending: number;
    /** Operations charged. */
    readonly operations: number;
    /** Shared definition entries kept. */
    readonly entries: number;
    /** Charged work past its deadline that has not settled, by what it is doing. */
    readonly unsettled: Readonly<Record<WorkKind, number>>;
    /** Endings whose remote outcome was not confirmed. */
    readonly unresolvedEndings: number;
}
export declare class Pool {
    readonly settings: PoolSettings;
    readonly clients: ClientSet;
    readonly budget: OperationBudget;
    readonly shared: SharedDefinitions;
    readonly reporter: Reporter;
    /** The pool's own signal: what every open, listing and call runs under; aborted at `close()`. */
    private readonly lifetime;
    private readonly leases;
    private readonly settleWaiters;
    /** Waiting for some client to become idle (`terminate`, `close`). */
    private readonly idleWaiters;
    closed: boolean;
    unresolvedEndings: number;
    constructor(settings: PoolSettings);
    now(): number;
    get signal(): AbortSignal;
    /**
     * The lazy sweep, run at the start of every interaction: leases past
     * `maxLeaseMs` revoked, idle clients ended, deferred endings retried — all of
     * it before a new operation is admitted.
     */
    interact(): void;
    /**
     * Admit one operation, or refuse at once: `closed`, or `PoolFullError`
     * (`limit: 'operations'`) — before anything is sent.
     */
    admit(kind: OperationKind, phase: WorkKind, callerSignal: AbortSignal | undefined): Operation;
    refused(reason: McpPoolRefusedError['reason'], cause?: unknown): McpPoolRefusedError;
    full(limit: McpPoolLimit): PoolFullError;
    /**
     * A library lease on `key`'s client for `operation`, opening one when the key
     * has none (inside the operation's slot). `plan` is the plan resolved for a
     * new client; a key that already has one keeps its own. The lease is
     * released when the operation SETTLES.
     */
    leaseClient(key: PoolKey, operation: Operation, facts: UseFacts, plan?: ClientPlan): Promise<PooledClient>;
    /**
     * Wait for `client`'s open inside `operation`, holding a reservation on it
     * all the while — between the open settling and this lease the client is
     * busy, so nobody can evict, idle-end or end it — then check it again and
     * lease it in the same step, with no wait between. `undefined` when it no
     * longer serves: the caller's next round finds the key's next client.
     */
    private leaseOnceOpen;
    /** `client` may have just become idle: a deferred ending may run, and a waiter for idleness may go on. */
    private becameIdle;
    private takeLease;
    /** One lease ended: the client may now be idle, and a deferred ending may run. */
    releaseLease(client: PooledClient): void;
    private notifyIdle;
    /** An app lease: a hold on a client for as long as the app keeps it, at most `maxLeaseMs`. */
    holdLease(client: PooledClient): HeldLease;
    /** End an app lease — at most once, whether released or revoked. */
    endLease(lease: HeldLease): void;
    private revokeExpiredLeases;
    /** Every app lease is revoked: the pool is closing. */
    revokeAllLeases(): void;
    /**
     * Open a client for `key`: make room, resolve its plan, admit it to the open
     * set, and start the one open. The operation that started it tracks the open's
     * settlement, so its charge stays until the open's work has settled.
     */
    private openFor;
    /** The one open of `client`, under the pool's own signal and `initTimeoutMs`. */
    private startOpen;
    private dropFailedOpen;
    /** Wait for `client`'s open inside `operation`: the caller's signal detaches the caller only. */
    private waitOpen;
    /** What `mcpClient` is opened with: the connection-shaping options, the pool's signal. */
    private clientOptions;
    /** What only a pool asks of the client it opens (`clientPort.ts`). */
    private seamFor;
    /**
     * The guard for a message on `client` that carries none of its own: a tool
     * call always brings its call's guard, so one without it is refused; any
     * other request runs under the client's open, listing or ending — and with
     * none under way, it is refused too. Nothing pooled is sent unchecked.
     */
    private guardOf;
    /** The guard of a request nothing covers: it refuses, so the request is never sent. */
    private readonly unmatched;
    /**
     * The pool's own fetch, outermost on every pooled transport: a request is
     * checked against its guard (the pooled call's, else the client's phase) and
     * counted under it; the guard's signal is linked in; and the answer is read
     * for the two pieces of evidence the pool classifies on — a 404 the MCP server
     * itself sent to a request that carried its session id, and a handle this
     * process lost. The client's own standalone stream (a GET), a notification and
     * an answer to the server's own request are the client's, not an operation's:
     * they pass untouched. Any other request with no guard is refused unsent.
     */
    private pooledFetch;
    /**
     * Make room for a new client of `key`: the principal's least recently used
     * idle client, then the pool's, moves to the ending set — or `PoolFullError`
     * at once, naming the budget. There is no queue.
     */
    private makeRoom;
    /**
     * Move `victim` to the ending set to make room. When the slots cannot be
     * reserved, the arrival is refused at once, and the victim stays in the open
     * set, counted, reported deferred — ended once slots free.
     */
    private evict;
    /** An ending slot and an operation slot, both, or which one is full. */
    private reserveEnding;
    /**
     * End `client` for `reason` — when it is idle and both slots can be reserved.
     * Otherwise it stays in the open set, still counted, and is reported deferred
     * (once); it is ended when its last lease ends or slots free.
     */
    endClient(client: PooledClient, reason: EndReason): void;
    private defer;
    /**
     * Move `client` into the ending set under `operation`, and end it through
     * `terminate()` — or, for a lost handle, through its local close alone.
     */
    private startEnding;
    private noteEnded;
    /**
     * A client whose handle this process lost: nothing can be sent for it, so it
     * leaves its key at once — the next use opens the key's next client — and is
     * ended under the ending rule like any other: its ending is its LOCAL close,
     * charged an ending slot and an operation slot until that close settles, its
     * remote outcome recorded unresolved. Busy, or with no slots free, it waits —
     * still counted in the open set, reported deferred.
     */
    lostHandle(client: PooledClient): void;
    /** A client whose session the server ended: no new lease; ended (through `terminate()`) when idle. */
    expired(client: PooledClient): void;
    private sweepIdle;
    /** Retry every deferred ending whose client is now idle. */
    retryDeferred(): void;
    /** Where `client`'s definitions are served from: its shared entry, or its own list. */
    servedListOf(client: PooledClient): ServedList;
    /**
     * The definitions context `client` serves under NOW: the app's `share` asked
     * again over the client's own configuration. A changed answer moves the
     * client to the new context and stops serving its own list, so every tool
     * bound under the old one is refused before it is sent.
     *
     * @throws `binding-changed`, `share`'s error as its cause, when the answer
     *   cannot be had — never the old context
     */
    refreshContext(client: PooledClient): PooledClient['definitions'];
    /** Does `context` still name the definitions context `client` serves under? False when it cannot be had. */
    contextHolds(client: PooledClient, context: string): boolean;
    /**
     * The list `client` serves under NOW, listed again on it when it cannot be
     * served as it stands — one listing at a time per list; a second caller joins
     * it inside its own slot. Every attempt asks the app's names again
     * (`refreshContext`), so a listing that a moved context discarded or refused
     * at its send is listed once more under the context of now, never written
     * into the one it began under. A listing raced by an invalidation is
     * discarded too. Refused (`stale-tools`) when three attempts serve nothing.
     * What it returns was checked against the context with no wait since.
     */
    ensureListed(client: PooledClient, operation: Operation): Promise<ServedList>;
    /**
     * What `client` can serve under the context of now — asked of the app's names
     * here — or, when nothing can, the list it should list. Too long to share:
     * the conversation's own client keeps that list, under the same context.
     */
    private servableNow;
    /**
     * One listing on `client` for `served`'s context. Its requests are sent only
     * while that context holds — asked at every boundary they cross — and its
     * answer is written only if the context still holds when it arrives and the
     * list's generation held: an answer the app's names moved under belongs to
     * no context, and is dropped.
     */
    private startListing;
    /**
     * A request failed: when the pool's fetch read the server's own 404 for the
     * session, or this process lost the handle, the client leaves its key — the
     * next use opens the next client under the SAME key — and the error says so.
     */
    classifyFailure(client: PooledClient, work: Work, error: unknown): RequestEvidenceOutcome;
    /** The server said a client's tool list changed: that client's list and its shared entry. */
    private toolsChangedOn;
    /** The app's permission signal: every list in the scope. */
    permissionsChanged(scope: {
        readonly tenant?: string;
        readonly principal?: string;
    }): void;
    /** The server changed: every list. */
    toolsChanged(): void;
    /**
     * Stop new leases on every client `scope` names, at once; wait for their
     * in-flight work up to `waitMs`; then end each one whose slots can be
     * reserved. The rest are reported deferred and ended when they can be.
     */
    terminate(matches: (key: PoolKey) => boolean, reason: EndReason, waitMs: number): Promise<readonly McpTerminateOutcome[]>;
    /** End each of `clients` whose slots can be reserved; the outcomes of those that started. */
    private endNow;
    /** Wait until every client in `clients` is idle, or `waitMs` passed. */
    private waitIdle;
    /**
     * Close: refuse new work, revoke app leases, abort in-flight work through the
     * pool's own signal, then end every client as slots can be reserved — at most
     * `maxEnding` at a time — in rounds, each bounded by `waitMs` (and each
     * ending's answer by its own deadline). What could not be ended yet is
     * reported deferred and ended when slots free; work still unsettled stays
     * charged until it settles.
     */
    closeAll(waitMs: number): Promise<readonly McpTerminateOutcome[]>;
    /** Wait until an ending slot and an operation slot are free, or `waitMs` passed. */
    private waitSlots;
    /** Refuse new work, revoke app leases, abort in-flight work through the pool's signal. */
    shutDown(): void;
    /** Resolve once every charged operation and every ending has settled. */
    settled(): Promise<void>;
    private quiet;
    private notifySettled;
    stats(): McpPoolStats;
    /** The bound shape of every definition in `served`, for a client of `key`. */
    definitionsOf(served: ServedList): readonly Definition[];
    /** The tools a list's client-own entries carry, by name (per-client lists only). */
    ownTool(served: ServedList, name: string): Tool | undefined;
}
/** What a failed request proved about its client, if anything. */
export type RequestEvidenceOutcome = 'expired' | 'handle-lost' | undefined;
/** Re-exported for the bound tool: which end reasons name a previous client. */
export type { PreviousEnd };
