/**
 * hosting/types — the ports an agent needs to stand up and stay up.
 *
 * `AgentHost` is "something can call me". `ConversationHost` is "something can
 * TALK to me" — a door that stays open, because `HostRequest → HostReply` is
 * one exchange and some doors are not. `SessionLifecycle` is "the conversation
 * outlives the request". All three are deliberately written in the vocabulary
 * every transport and every store already has — an input, a reply, a frame, a
 * session id, a stored blob — and in nothing else.
 *
 * **The rule these types are written under:** no runtime, product or protocol
 * gets a field, a name or an assumption here. A port shaped around one
 * provider's request envelope stops being a port and becomes that provider's
 * SDK with extra steps, and every later adapter pays for it. If a decision only
 * makes sense for one place you might deploy, it belongs in the adapter for
 * that place, not in this file. `nodeHost` is the first adapter and it does not
 * get special treatment either: its paths, its status codes and its JSON body
 * shape all live in `nodeHost.ts`, and nothing in this file knows they exist.
 *
 * Pattern: Ports & adapters (hexagonal). Role: the port side, exclusively.
 */
/// <reference types="node" />
import type { FlowchartCheckpoint } from 'footprintjs';
import type { Agent } from '../core/Agent.js';
import type { AskComponent } from '../tools/askComponent.js';
import type { CheckInRequest } from '../core/checkin.js';
import type { MiddlewareAsk } from '../core/pause.js';
import type { AgentRunCheckpoint } from '../core/runCheckpoint.js';
import type { AwaitingInput } from '../tools/inputRequest.js';
import type { Unsubscribe } from '../events/dispatcher.js';
import type { ToolArtifacts } from '../artifacts/capability.js';
import type { ArtifactWireRequest, ArtifactWireResult } from './artifactWire.js';
import type { AnswerAccountsOptions } from './answerAccounts.js';
import type { DoorIdentity } from './identityVerification.js';
import type { AdmissionPolicy } from './admission.js';
import type { IngressSink } from './ingressRecord.js';
import type { RequestSealer } from './requestSealing.js';
import type { SessionSummary, SessionWireRequest, SessionWireResult } from './sessionWire.js';
export type { Unsubscribe };
/**
 * Something a host can do BEYOND the baseline of "accept a request, deliver one
 * reply". Read it from {@link AgentHost.capabilities} and branch on it — never
 * assume it, and never infer it from the adapter's name.
 *
 * The union starts at exactly what a shipped adapter can honour today. A name
 * is added when an adapter can actually keep the promise, never in anticipation
 * of a transport that does not exist yet: a capability nobody implements is a
 * promise the library cannot keep, and pre-minting one for an imagined future
 * transport would bake that transport's assumptions in before it arrives.
 *
 *  - `'streaming'` — the caller SEES `reply.emit(...)` pieces as they arrive.
 *  - `'conversation'` — this host can also carry a two-way channel that stays
 *    open ({@link ConversationHost.serveConversations}). It joined the union
 *    when two shipped adapters honoured it, not when it was imagined.
 *
 * Declared at CONSTRUCTION and static thereafter, which is a constraint worth
 * knowing about: a capability whose truth depended on what happened to be
 * installed at call time could not be declared here honestly, so an adapter
 * that can only sometimes keep a promise does not make it.
 */
export type HostCapability = 'streaming' | 'conversation';
/**
 * One inbound request, as the transport described it.
 */
export interface HostRequest {
    /** What the caller is asking. */
    readonly input: string;
    /**
     * A person's answer to an outstanding {@link PendingAsk} — and **the one
     * thing that distinguishes a resume from a new message.**
     *
     * Present ⇒ this request answers the run that paused on this session. Absent
     * ⇒ this request is a new message. That is the whole contract, and it is a
     * FIELD rather than an inference on purpose: reading approval out of prose
     * ("yes, go ahead") is a guess, and a guess is not something a consent gate
     * may be built on.
     *
     * The port never interprets it. It is handed to `agent.resume(checkpoint,
     * decision)` exactly as it arrived — a {@link CheckInRequest} or a middleware
     * `ask` is answered with the shipped `checkInApproved()` / `checkInDeclined()`
     * vocabulary, built from the pending ask (`checkInApproved({ request:
     * pending.checkIn, by })`) so it names the request it answers, and the resume
     * refuses it for any other request or a second time (`ResumeDecisionError`);
     * a plain `askHuman` pause is answered with whatever that tool's author
     * documented. Typed `unknown` because the library does not get to decide what
     * a tool asked for.
     */
    readonly decision?: unknown;
    /**
     * The conversation this request CLAIMS to belong to — caller data, exactly as
     * the transport declared it (a JSON field, a header, a path segment).
     *
     * It is **not identity** and must never be trusted as identity on its own:
     * anyone who can reach the host can put any string here, including someone
     * else's. Authenticate the caller by your own means, then check that the
     * authenticated principal is allowed this session, before you serve it.
     *
     * Bounded at every door the library ships: an id longer than
     * `MAX_SESSION_ID_LENGTH` or holding a control character is refused before
     * any handler sees the request, because it travels into every stored row,
     * log line and trace span of the conversation.
     */
    readonly sessionId?: string;
    /**
     * WHO the transport says is calling — the end user's id, exactly as the
     * transport declared it (9.12.0).
     *
     * A different fact from {@link sessionId} beside it, and the difference is
     * the whole reason it is a second field: a session is a THREAD and this is a
     * PERSON. One conversation belongs to one user; one user has many
     * conversations; and an audit trail that reports the thread where the actor
     * belongs names the wrong party in a way nobody can see from the outside.
     *
     * **Absent when absent.** No wire derives it from the session id, from the
     * body, or from anything else — a request that carried no user is a request
     * with no `userId`, and inventing one would be worse than reporting none.
     *
     * **How much it is worth is the transport's answer, not this port's.** A
     * managed runtime whose front door authenticates the caller and forwards the
     * result is a transport whose wire can fill this in, and its adapter does. A
     * container you expose directly is not: a header there is a string anybody
     * can send, and the generic JSON wire therefore reads none — a wire that
     * promoted one to "who did this" would hand every caller the ability to sign
     * somebody else's name. Which is which is decided in the adapter, by whoever
     * knows what stands in front of it.
     */
    readonly userId?: string;
    /**
     * An artifact operation this request carries INSTEAD of a message (9.23.0)
     * — `head` (the claim ticket's metadata) or `get` (metadata + payload), for
     * one ref.
     *
     * Its PRESENCE is the discriminant, exactly as {@link decision}'s is: a
     * request carrying `artifact` redeems a ticket and never starts or resumes
     * a run — `input` and `decision` do not ride it. A handler that serves
     * artifacts answers with {@link HostReply.artifact}; a handler that does
     * not should refuse it by name rather than treat it as a message, because a
     * redemption silently answered by a model turn is a caller told nothing and
     * billed anyway. `standingAgent` answers it: the ref is resolved against
     * the serving agent's store under the requesting session's
     * identity-composed scope — exactly the scope the run's own tools used.
     */
    readonly artifact?: ArtifactWireRequest;
    /**
     * A session-history operation this request carries INSTEAD of a message
     * (9.26.0) — `list` (the verified caller's own sessions) or `transcript`
     * (one owned session's messages).
     *
     * Its PRESENCE is the discriminant, exactly as {@link artifact}'s is: a
     * request carrying `session` reads history and never starts or resumes a
     * run. Both ops REQUIRE a door that verifies identity
     * ({@link StandingAgentBaseOptions.identity}) — reading "your" conversations
     * from an unverified header is enumeration — and both are refused by name
     * where the session store keeps no owner index.
     */
    readonly session?: SessionWireRequest;
    /**
     * Transport headers with lower-cased names, as delivered. Present so a
     * handler can map its own conventions (a correlation id, a tenant) without
     * the port having to guess which ones matter.
     *
     * Since 9.26.0 this is also where a door that verifies identity reads the
     * caller's credential from — `authorization: Bearer <token>`, the one
     * vocabulary every transport in this package normalizes onto. The port still
     * does not interpret headers itself; it hands them to whoever was configured
     * to.
     */
    readonly headers?: Readonly<Record<string, string>>;
    /**
     * The sign-in this request named, as its KEY — set by a transport built
     * with a sign-in cookie (`httpHost`/`nodeHost` `signIn`), which strips the
     * cookie itself from {@link headers} before any handler sees them. The key is
     * the cookie value's SHA-256: the store is keyed by it and it cannot be
     * turned back into a cookie. Absent when the transport has no sign-in cookie
     * configured, and when the request carried none.
     *
     * Not identity: `verifyRequestIdentity` turns it into one, through the
     * deployment's sign-in source, exactly as it turns a bearer token into one.
     */
    readonly signInKey?: string;
    /** Aborted when the caller goes away. */
    readonly signal?: AbortSignal;
}
/**
 * The one reply a request gets. Exactly one of {@link HostReply.complete},
 * {@link HostReply.awaiting}, {@link HostReply.artifact} or
 * {@link HostReply.fail} ends it; a second call is ignored rather than allowed
 * to corrupt the wire.
 *
 * Three terminals for a RUN, because a run has three ends and only three: it
 * answered, it stopped to ask a person something, or it failed. Before
 * `'flowchart-v1'` there was nowhere to keep a paused run, so the middle one
 * was delivered through `fail` — an error standing in for unfinished work. It
 * is a terminal of its own now, and a pause is never reported as a failure
 * again. The fourth terminal ends a request that never was a run:
 * {@link HostRequest.artifact} redeems a claim ticket, and `artifact(...)` is
 * how the resolved ticket comes back.
 */
export interface HostReply {
    /** Deliver the final answer and end the reply. */
    complete(output: string): void;
    /**
     * End the reply with **unfinished work**: the run stopped to ask a person
     * something, the paused run is stored, and a later request carrying
     * {@link HostRequest.decision} continues it.
     *
     * This is not a failure and must not be reported as one. The agent did not
     * break, no work was lost, and there is nothing to retry — there is a question
     * outstanding. An adapter that maps this onto a 5xx, an error counter or a
     * dead-letter queue is telling every dashboard it feeds something that is not
     * true.
     *
     * Optional on the TYPE for the same reason {@link HostReply.emit} is: a
     * minimal adapter need not implement it. Every shipped adapter does. When it
     * is absent the composer still STORES the paused run — the store is not the
     * transport's business — and ends the reply with a named refusal instead, so
     * the pause is never lost merely because the wire could not describe it.
     */
    awaiting?(pending: PendingAsk): void;
    /**
     * End the reply with a **resolved artifact** (9.23.0): the metadata for a
     * `head`, metadata + payload for a `get`. The terminal a request carrying
     * {@link HostRequest.artifact} ends through when the ref resolved; a ref
     * that did not resolve ends through `fail` with the one indistinguishable
     * not-found refusal.
     *
     * Optional on the TYPE for the same reason {@link HostReply.awaiting} is: a
     * minimal adapter need not implement it, and every shipped adapter does.
     * When it is absent the composer ends the reply with a named refusal
     * (`ArtifactNotCarriedError`) instead — the resolution is not lost quietly
     * merely because the wire could not describe it.
     */
    artifact?(result: ArtifactWireResult): void;
    /**
     * End the reply with **resolved session history** (9.26.0): the caller's own
     * sessions for a `list`, one owned session's messages for a `transcript`.
     *
     * The terminal a request carrying {@link HostRequest.session} ends through.
     * A session the verified caller does not own does not end here — it ends
     * through `fail` with the one indistinguishable not-found, because "exists
     * but not yours" is an oracle for which ids are real.
     *
     * Optional on the TYPE for the same reason {@link artifact} is: a minimal
     * adapter need not implement it, and every shipped adapter does. When it is
     * absent the composer ends the reply with a named refusal
     * (`SessionsNotCarriedError`) rather than improvising a body shape no client
     * was written against.
     */
    sessions?(result: SessionWireResult): void;
    /**
     * A piece of the answer, as it is produced.
     *
     * Optional on the TYPE so a minimal adapter need not implement it — every
     * shipped adapter does. Whether the caller SEES the pieces as they arrive is
     * the whole difference between hosts, and that is what `'streaming'` in
     * {@link AgentHost.capabilities} reports. A host without it buffers what it is
     * handed and the authoritative `complete(output)` is what the caller
     * receives; the buffer is settled by the completion, never sent alongside it,
     * because a chunk is a preview of the same text and delivering both would
     * hand the caller the answer twice.
     *
     * Handler code is identical either way: emit freely, complete once.
     */
    emit?(chunk: string): void;
    /**
     * The turn's artifact store, **already bound** to the scope this request's
     * claim tickets are redeemed under — so host code that files its OWN
     * artifacts for a turn (a story, the person's clicks, anything joined to it)
     * files them where `artifact-get` will look for the same caller, without
     * ever composing a scope.
     *
     * ── Awaited, inside the turn — and bounded ────────────────────────────────
     * Not a terminal. The composer calls it exactly ONCE per turn, AWAITS it
     * while the session's lane is still held, and only then ends the reply with
     * `complete` or `awaiting` — so a ticket filed here exists before the body
     * that carries it is composed. Never called for a request that is not a turn
     * (an artifact redemption, a session op) or for a turn that ends through
     * `fail`.
     *
     * The wait is BOUNDED: the hook and every operation it started must settle
     * within `turnArtifactsTimeoutMs` (default `TURN_ARTIFACTS_TIMEOUT_MS`, 5 s)
     * and before the request's own `signal` aborts. Whichever comes first ends
     * the hand-over: the binding is revoked, what is in flight is NOT cancelled
     * (it may still land, stamped with this turn's session and run), the cause is
     * reported, and the terminal is delivered. In the shared `{ agent }` shape
     * every session waits behind this turn's lane, so the hook's I/O is paid by
     * everyone — keep it to filing.
     *
     * ── Live only for its turn ─────────────────────────────────────────────────
     * The binding is live while the hand-over is: after the composer has waited
     * for every operation started through it (a `put` that was not awaited
     * included), or after the bound ended the wait, it REVOKES it. A verb called
     * later rejects with `TurnArtifactsExpiredError` — a rejection already
     * handled, so a floating late call cannot crash the process — and is reported
     * as `'expired'`. File inside the hook; never hold the binding.
     *
     * ── Never decides the reply, never silent ──────────────────────────────────
     * A hook that throws or rejects, an operation that fails (awaited or not), the
     * bound, the abort and a late call are all put on the serving agent's stream
     * as `agentfootprint.artifacts.hand_over_failed { cause, op?, errorClass?,
     * errorCode? }` — class only, stamped with the session — and the FIRST of
     * them on the ingress record (`IngressRecord.turnArtifactsFailure`). None of
     * them turns a delivered answer, or a stored question, into a failure.
     * Awaiting another turn of the same standing agent from inside the hook can
     * never finish (that turn queues behind this one); the bound ends the wait.
  
     * ── The scope, and what it is not ─────────────────────────────────────────
     * The scope is the one {@link HostRequest.artifact} redemption re-composes
     * for this request: the verified user (or the transport's own `userId` when
     * no verifier is configured), the stored conversation's tenant and
     * namespace, else the session rung `{ conversationId: sessionId }`. One
     * owner of that tuple — the library — so an app cannot derive a slightly
     * different one. It is where the DOOR redeems, which is not always where the
     * run filed its own recording: at an unverified door a conversation that
     * carries an identity of its own can put the recording elsewhere (the known
     * edges are listed in `src/hosting/README.md`).
     *
     * Every put is stamped `origin: { runId }` with the run this turn executed,
     * or carries no origin when it executed none (a partial answer to an input
     * request, or its cancellation); a caller's own `origin` is always dropped.
     * A PAUSED turn's run id names a run that files no recording — a pause is
     * not a finished run, and the resumed run records under its own id — so a
     * reader joining on it must say "paused, no recording", not "missing". Each
     * filing lands on the serving agent's record as
     * `agentfootprint.artifacts.minted` with no `tool` field, its meta naming the
     * session and the run it was filed FOR — captured when the binding was
     * built, never read from whichever run is live when a late fact lands.
     *
     * When there is nothing to bind, the value SAYS so rather than inventing a
     * scope — see {@link TurnArtifacts}.
     *
     * Optional, and feature-detected: a host that does not implement it is
     * served exactly as before.
     *
     * @example  File a turn's story and carry its ticket on the reply
     *   turnArtifacts: async (turn) => {
     *     if (!turn.bound) return;
     *     const story = await turn.artifacts.put({ kind: 'story/turn', mediaType: 'application/json', data });
     *     ticketForThisReply = story.ref; // the body `complete` composes next can carry it
     *   },
     */
    turnArtifacts?(turn: TurnArtifacts): void | Promise<void>;
    /** End the reply with a failure. */
    fail(error: Error): void;
}
/**
 * What {@link HostReply.turnArtifacts} is handed: this turn's artifact store,
 * bound to exactly one scope — or the reason there is none.
 *
 * `artifacts` has the shape a tool gets as `ctx.artifacts`: five verbs, none
 * of which takes a scope, so a binding can reach this request's scope and no
 * other. The scope itself is deliberately NOT on the value — a tuple a host
 * can read is a tuple a host can copy, edit and put under. The verbs work only
 * while the hand-over is live (then `TurnArtifactsExpiredError`, already
 * handled, reported as `'expired'`).
 *
 * A filing's `origin.runId` is the run the turn executed — none for a turn that
 * executed none. On a PAUSED turn that run files no recording (a pause is not a
 * finished run; the resumed run records under its own id), so a reader joining
 * a filing to "its recording" by run id must read the absence as "paused, no
 * recording", never as "missing".
 *
 * `bound: false` says why nothing was bound, checked in this order:
 *  - `'no-session'` — the request named no session. An anonymous run scopes
 *    its artifacts to its own run id, which no later request can present, so
 *    there is no scope to bind — and none is invented. Wins over `'no-store'`
 *    when both apply.
 *  - `'no-store'` — the serving agent has no artifact store.
 */
export type TurnArtifacts = {
    readonly bound: true;
    readonly artifacts: ToolArtifacts;
} | {
    readonly bound: false;
    readonly reason: 'no-session' | 'no-store';
};
/**
 * What {@link StandingAgentHandle.artifactsForRequest} reads off a request:
 * WHICH conversation (`sessionId`) and WHO is asking — the transport's headers
 * (the bearer token a configured verifier checks), the sign-in KEY (a sign-in
 * the server keeps) and, at a door with no verifier, its `userId` claim. The
 * same fields, read the same way, as a turn or a redemption on the wire.
 *
 * The fields are {@link HostRequest}'s own, so inside a handler the request the
 * transport built goes in AS IT IS — `handle.artifactsForRequest(request)` —
 * and no credential can be left behind. A {@link HostConversation} goes in the
 * same way.
 *
 * `headers` takes a Node / Express `IncomingHttpHeaders` as it is: a value
 * that arrived as an array (a repeated header) is not read, and a REPEATED
 * `authorization` — two credentials — is refused as `'unverified'`.
 */
export interface ArtifactsForRequestInput {
    readonly sessionId?: string;
    readonly headers?: Readonly<Record<string, string | readonly string[] | undefined>>;
    readonly userId?: string;
    /**
     * The sign-in this request named, as its KEY — {@link HostRequest.signInKey},
     * or on an app's own route `readSignIn(req.headers, cookieName).key`. Never
     * the cookie: the seam does not read cookies, and a key cannot be turned
     * back into one.
     *
     * Verified exactly as the turn door verifies it (`verifyRequestIdentity`
     * with the key): an ended, expired or unknown sign-in is `'unverified'`
     * (`'expired'`), a sign-in store that cannot answer is `'unavailable'`, and
     * a key together with a bearer token is `'unverified'`
     * (`'two-credentials'`). Absent, the request is judged as one that carried
     * no sign-in — at a door that requires a credential, `'unverified'`
     * (`'no-token'`), exactly as the turn door would judge it without its key.
     */
    readonly signInKey?: string;
}
/**
 * What {@link StandingAgentHandle.artifactsForRequest} answers: the serving
 * agent's artifact store bound to the scope a REDEMPTION by this caller of this
 * session would read — or why nothing was bound.
 *
 * `artifacts` has the {@link TurnArtifacts} shape: five verbs, no scope on the
 * value, never the unscoped store. Its verbs count against
 * `artifactOpsPerSession` with the wire's redemptions (a verb past the bound
 * rejects with `ArtifactOpsBusyError`), and the binding is REVOKED when the
 * instance it is bound to is retired from the pool or the host is closed —
 * a verb STARTED after that rejects with `RequestArtifactsRevokedError`
 * (already handled; ask the handle again); one already in flight completes.
 *
 * `bound: false` says why, checked in this order:
 *  - `'unverified'` — a verifier is configured and the request did not pass it
 *    (no credential where one is required, a token that does not verify, a
 *    sign-in that ended or expired, a token AND a sign-in, a claimed user the
 *    credential does not prove). `error` is the verifier's refusal; a host's
 *    401.
 *  - `'unavailable'` — the verifier could not answer (`VerifierUnavailableError`,
 *    an identity-provider outage, a sign-in store that is down). `error` says
 *    which; a host's 503, never a 401 — the caller's credential was not
 *    judged.
 *  - `'no-session'` — the request named no session.
 *  - `'invalid-session'` — the session id is one the wire refuses at the door
 *    (empty, over the length bound, or a character outside visible ASCII —
 *    `InvalidSessionIdError` as `error`); a host's 400.
 *  - `'not-found'` — at a verifying door, no conversation THIS caller can
 *    open under that id: somebody else's, one nobody signed for, or one whose
 *    first turn has not persisted (unless that turn is this caller's, in
 *    flight). One reason for all of them, as redemption answers them with one
 *    not-found. Never builds or evicts a pooled instance to find out. Having
 *    no live instance is NOT by itself a reason: past that ownership check a
 *    lane-less session binds through the pool's reader — at a door with no
 *    verifier, a session nobody has chatted in yet included, which is where a
 *    guide filed before its first turn goes — and whether a ref exists is the
 *    store's answer (`head` → `null`).
 *  - `'no-store'` — the serving agent has no artifact store.
 */
export type ArtifactsForRequestResult = {
    readonly bound: true;
    readonly artifacts: ToolArtifacts;
} | {
    readonly bound: false;
    readonly reason: 'unverified' | 'unavailable' | 'no-session' | 'invalid-session' | 'not-found' | 'no-store';
    /** Present with `'unverified'`, `'unavailable'` and `'invalid-session'`. */
    readonly error?: Error;
};
/**
 * What `standingAgent(...)` adds to the host adapter's own handle.
 */
export interface StandingAgentHandle {
    /**
     * The serving agent's artifact store, bound to the scope a redemption by this
     * request's caller of this session reads — for the paths that are not a chat
     * turn: a read before a run (a panel redeeming a payload by ref), or an
     * app-owned route that files its own artifacts beside a conversation.
     *
     * "The caller" is what the door knows: at a door with a verifier, the person
     * the token or the sign-in PROVES; at a door with no verifier, the session id and the
     * transport's `userId` claim are the key, by law — exactly as on the wire.
     *
     * Inside a handler, hand it the request the transport built —
     * `handle.artifactsForRequest(request)` — so the sign-in key
     * ({@link HostRequest.signInKey}) goes in with the headers. On an app's own
     * route, take the sign-in cookie off first and pass only its key:
     * `const { key, headers } = readSignIn(req.headers, door.cookieName)`, then
     * `{ sessionId, headers, signInKey: key }`.
     *
     * The host never composes the scope. This runs the door's own steps with the
     * door's own instances: the configured verifier, the session-id check, the
     * session store (woken and hydrated as a redemption does), the redemption
     * door's ownership rule, the ONE scope composer redemption and
     * `reply.turnArtifacts` use, and the store of the instance serving that
     * session — never building or evicting a pooled instance to find it. So a ref
     * filed through it is redeemed on the wire by the same caller, and a ref
     * another caller filed answers exactly like a ref that never existed.
     *
     * A filing made through it carries NO `origin`: this call executes no run,
     * so there is no run id the library can vouch for (a caller's own `origin`
     * is dropped, as on every binding). The caller is already the scope, and
     * its `minted` fact names the session.
     *
     * @example  An Express route that reads a payload by ref before the turn runs
     *   const handle = await standingAgent({ agent, sessions, host, identity: door.identity });
     *   app.get('/panel/:sessionId/:ref', async (req, res) => {
     *     const { key, headers } = readSignIn(req.headers, door.cookieName); // the cookie comes off
     *     const found = await handle.artifactsForRequest({
     *       sessionId: req.params.sessionId,
     *       headers,
     *       ...(key !== undefined && { signInKey: key }),
     *     });
     *     if (!found.bound) {
     *       const status = { unverified: 401, unavailable: 503, 'invalid-session': 400 } as Record<string, number>;
     *       return res.status(status[found.reason] ?? 404).end();
     *     }
     *     const payload = await found.artifacts.get(req.params.ref); // null: missing, expired or not yours
     *     return payload ? res.json(payload.data) : res.status(404).end();
     *   });
     *
     * @throws HostClosedError after `close()` — including a call that was
     *   waiting (on the verifier or the session store) when `close()` ran: it
     *   binds nothing and builds nothing, exactly like the wire.
     * @throws UnreadableEnvelopeError when the stored conversation is present but
     *   cannot be read (the turn door's law — never composed into a scope).
     * @throws whatever the session store's own `onWake` / `hydrate` throws (an
     *   outage is the store's to name, not a refusal value).
     */
    artifactsForRequest(request: ArtifactsForRequestInput): Promise<ArtifactsForRequestResult>;
}
/**
 * What you hand {@link AgentHost.serve}. Throwing is treated exactly like
 * calling `reply.fail(err)` — a handler that throws is a failed request, never
 * a hung one.
 */
export type HostHandler = (request: HostRequest, reply: HostReply) => void | Promise<void>;
/** A live host. */
export interface HostHandle {
    /**
     * Stop taking new requests, let the in-flight ones finish, then release the
     * transport. Idempotent, so a shutdown hook and an explicit close can
     * coexist. Requests arriving after it are refused with a
     * {@link HostClosedError} naming the adapter.
     *
     * "Release the transport" is as strong as the port can be, because an
     * adapter may be serving on a transport the CALLER owns rather than one it
     * created. Releasing one of those means handing the routes back — never
     * closing it — and a request arriving afterwards is the caller's to answer,
     * not this host's to refuse.
     */
    close(): Promise<void>;
}
/**
 * A request the HOST refused before any handler saw it — the door guard's
 * decision about a caller: a cross-site shape, a Host this door does not answer
 * for, a session id it will not carry.
 *
 * Only decisions travel here. A body that is merely malformed (unparseable
 * JSON, an unknown wire op, a body over the ceiling) is a broken request, not a
 * decision about who is knocking, and stays the transport's own 4xx.
 *
 * Carries the refusal's class and the fact of a credential — never a header,
 * never the credential, never the session id. It is what
 * {@link AgentHost.onRefusal} hands a listener, and a listener is free to log
 * all of it.
 */
export interface HostRefusal {
    /**
     * Which door refused: `'request'` — the one-exchange door, refused before
     * the body was read or before the handler was called (which of the
     * composer's turn / session-op / artifact doors it was headed for is not
     * recorded) — or `'conversation'`, an upgrade refused before the 101.
     */
    readonly door: 'request' | 'conversation';
    /** The named refusal the caller was answered with. Its `code` is stable. */
    readonly error: Error & {
        readonly code: string;
    };
    /** Whether an `authorization: Bearer …` credential was PRESENT. Never its value. */
    readonly bearerPresent: boolean;
}
/**
 * The port: something that can carry requests to one handler and carry its
 * replies back.
 */
export interface AgentHost {
    /**
     * Which adapter this is. Every refusal names it, so an error tells you WHO
     * refused rather than leaving you to guess which layer you are looking at.
     */
    readonly name: string;
    /** What this adapter can do beyond the baseline. Feature-detect; never assume. */
    readonly capabilities: readonly HostCapability[];
    /** Start serving. Resolves once the host is actually live. */
    serve(handler: HostHandler): Promise<HostHandle>;
    /**
     * Hear about every request this host refuses BEFORE a handler sees it — on
     * any of its doors, the conversation door included. Returns the way to stop
     * listening.
     *
     * Optional, and feature-detected: a host that refuses nothing on its own
     * has nothing to report. Every host built on `httpHost` has it, and
     * `standingAgent` subscribes whenever `onIngressDecision` is set — which is
     * how a forged request the host turned away still lands in the ingress
     * record, though no run, no handler and no verifier ever saw it.
     *
     * A listener that throws is contained: the refusal is still answered.
     */
    onRefusal?(listener: (refusal: HostRefusal) => void): Unsubscribe;
}
/**
 * A door that stays open: one session-scoped, two-way channel.
 *
 * The distinction this type exists for, in the words of the field report that
 * bought it: **`HostRequest → HostReply` is one exchange, and this door is a
 * conversation.** A request has one reply and then it is over. A conversation
 * has neither side taking turns by rule, no reply count, and an end that either
 * side can call.
 *
 * ── Frames are STRINGS here, deliberately ───────────────────────────────────
 * What the frames MEAN is the consumer's contract, not this port's: one
 * consumer pushes tool calls down the channel, another speaks a standardized
 * agent↔UI protocol, a third exchanges long-running task updates. JSON is what
 * all three happen to use and none of them agree on beyond that, so the port
 * carries text and stays out of it. Binary is a capability question, deferred
 * until a consumer needs it rather than guessed at now.
 *
 * ── What is NOT here ────────────────────────────────────────────────────────
 * No authentication, no chunking, no heartbeat, no protocol framing. See
 * {@link ConversationLimits} for why the last two are absences with a reason
 * rather than gaps.
 */
export interface HostConversation {
    /**
     * The conversation this channel CLAIMS to belong to — caller data, exactly as
     * the transport declared it, and **not identity**. The whole of
     * {@link HostRequest.sessionId}'s warning applies here word for word: anyone
     * who can reach the host can put any string here, including someone else's.
     *
     * A conversation and a request carrying the same string are the same
     * session's, as far as this port is concerned. What that entitles either of
     * them to is yours to decide, above the port.
     */
    readonly sessionId?: string;
    /**
     * Transport headers with lower-cased names, as delivered — so a handler can
     * map its own conventions without the port guessing which ones matter.
     *
     * This is also where an adapter puts credentials that its transport spells
     * some other way: a bearer token a browser could only send as a subprotocol
     * arrives here as an ordinary `authorization` header, because a port field
     * spelled the way one vendor spells it is how a port stops being one.
     *
     * Never the sign-in cookie: a host built with one strips it (see
     * {@link signInKey}).
     */
    readonly headers?: Readonly<Record<string, string>>;
    /**
     * The sign-in the handshake carried, as its KEY (see
     * {@link HostRequest.signInKey}). When it is present the door already
     * checked it before the 101, re-checks it before handing on every inbound
     * frame, and closes the conversation when the sign-in ends.
     */
    readonly signInKey?: string;
    /**
     * Host → far side. One frame, delivered whole.
     *
     * Refuses BY NAME rather than dropping quietly in two cases: a conversation
     * that has ended (`ConversationClosedError`), and a frame past the ceiling the
     * adapter declared (`FrameTooLargeError`). A dropped frame on a channel that
     * looks open is the failure mode this port exists to make impossible.
     */
    send(frame: string): void;
    /**
     * Far side → host. Returns an unsubscribe.
     *
     * Frames that arrive BEFORE the first subscriber are held and delivered to
     * it, up to the bound the adapter declares
     * ({@link ConversationLimits.maxPendingBytes}) — an `async` handler that
     * awaits anything before subscribing would otherwise silently lose the far
     * side's opening frame, which on a channel whose first frame is a greeting is
     * every conversation.
     */
    onFrame(cb: (frame: string) => void): Unsubscribe;
    /**
     * The end, delivered exactly once per subscriber — including to a subscriber
     * that arrives after it already happened, which is answered immediately
     * rather than never.
     */
    onClose(cb: (reason: ConversationClose) => void): Unsubscribe;
    /**
     * End it politely: flush what is queued, tell the far side, then stop.
     * Idempotent — the first call owns the ending and later ones do nothing.
     */
    close(reason?: string): void;
}
/**
 * How a conversation ended, in terms every transport can answer.
 *
 * `by` is the fact a consumer branches on and the reason there is no numeric
 * code here: what all three of "a browser-parked channel", "a UI protocol" and
 * "a long-running task exchange" need to know is whether the far side said
 * goodbye, whether we did, or whether it broke — and a transport's own numbers
 * answer that only if you already know that transport. Adapters render their
 * own vocabulary (a close code, a timeout, a ceiling) into {@link reason}.
 */
export interface ConversationClose {
    /**
     *  - `'far-side'` — they ended it.
     *  - `'host'` — we did, through {@link HostConversation.close}.
     *  - `'transport'` — nobody ended it; it broke or timed out.
     */
    readonly by: 'far-side' | 'host' | 'transport';
    /** What was said about it, when anything was. */
    readonly reason?: string;
}
/**
 * What you hand {@link ConversationHost.serveConversations} — called once per
 * conversation, with that conversation.
 *
 * Throwing ends THAT conversation with a stated reason and never the host: one
 * caller's bad frame is not an outage.
 */
export type ConversationHandler = (conversation: HostConversation) => void | Promise<void>;
/**
 * The ceilings a door imposes, **declared rather than discovered**.
 *
 * ── Why the port does not just handle them ──────────────────────────────────
 * A transport that caps frame size or idles out must SAY so, and then get out
 * of the way. Hiding a 32KB cap inside auto-chunking would be the adapter
 * deciding a protocol question for every consumer at once — how a message is
 * split, how the pieces are numbered, how the far side knows the last one has
 * landed — and those answers differ per consumer. Same for liveness: a
 * heartbeat is frames on somebody's protocol, and inventing them puts bytes on
 * the wire that the consumer's parser never agreed to.
 *
 * So the port's job is to make the ceiling VISIBLE and let the layer above act:
 * chunk above the port, heartbeat above the port.
 *
 * ── Enforced vs reported ────────────────────────────────────────────────────
 * A door enforces what it IS and reports what it SITS BEHIND, and the doc on
 * each field says which. Absent means "no ceiling this adapter knows of", never
 * "no ceiling" — the runtime in front of you may have one it never told us
 * about.
 */
export interface ConversationLimits {
    /**
     * Largest single frame this door carries, in bytes of UTF-8.
     *
     * **Enforced, both directions**, by the door that declares it: an inbound
     * frame past it ends the conversation with a stated reason, and
     * {@link HostConversation.send} past it refuses by name instead of
     * truncating or silently splitting.
     *
     * A frame the transport delivered in pieces counts in TOTAL — the port's
     * frame is the whole message, not the transport's packet, so fragmentation
     * cannot be used to walk around the ceiling.
     */
    readonly maxFrameBytes?: number;
    /**
     * How long the transport tolerates silence before it closes the channel.
     *
     * **Reported, not imposed.** The door declaring it usually is not the thing
     * enforcing it — a runtime's front door idles a socket out long before the
     * process inside notices — and a consumer that needs the channel to stay up
     * sends its own heartbeat frames on its own protocol. Making that possible is
     * the whole reason this number is written down.
     */
    readonly idleMs?: number;
    /**
     * How much the door holds for you before the first
     * {@link HostConversation.onFrame} subscriber exists, in bytes.
     *
     * **Enforced**, and a ceiling on the DOOR rather than on the transport: the
     * pre-subscribe buffer that stops an `async` handler from losing the opening
     * frame is a queue somebody else fills and this process pays for, so it gets
     * a number and a stated overflow instead of growing until the host dies. Past
     * it, the conversation ends with a reason naming this bound.
     *
     * Bounded in BYTES rather than in frames on purpose: a frame count would
     * still admit `count × maxFrameBytes` of memory, which is the same unbounded
     * queue with an extra step.
     */
    readonly maxPendingBytes?: number;
}
/**
 * The port: something that can carry conversations to one handler.
 *
 * It sits BESIDE {@link AgentHost} rather than inside it, because a transport
 * that can carry a request cannot necessarily carry a conversation, and one
 * that carries conversations need not answer requests at all. A host that does
 * both implements both and declares `'conversation'` in
 * {@link AgentHost.capabilities}.
 */
export interface ConversationHost {
    /** Which adapter this is. Every refusal names it. */
    readonly name: string;
    /** What this adapter can do beyond the baseline. Feature-detect; never assume. */
    readonly capabilities: readonly HostCapability[];
    /**
     * What this door caps, as declared facts. Absent means this adapter knows of
     * no ceiling — never that there is none.
     */
    readonly conversationLimits?: ConversationLimits;
    /**
     * Start taking conversations. Resolves once the door is actually open.
     *
     * The handle's `close()` ends every live conversation politely and then
     * releases the door.
     */
    serveConversations(handler: ConversationHandler): Promise<HostHandle>;
}
/**
 * A session packed for storage.
 *
 * `format` names WHAT is inside, so a reader that does not know the shape
 * refuses BY NAME instead of restoring a session it cannot actually read.
 * Formats are ADDED, never redefined: an old runtime meeting a new format says
 * so and stops, which is the only safe thing it can do with a payload it cannot
 * interpret. Two exist:
 *
 *   • `'conversation-v1'` — a conversation and only a conversation. Every turn
 *     that ran to an answer stores this.
 *   • `'flowchart-v1'` — a run that stopped mid-flow to ask a person something:
 *     the engine's own checkpoint, the conversation as of the pause, and the
 *     outstanding ask. 7.14 shipped the version field for exactly this day, and
 *     said so; this is that day.
 *
 * The union is discriminated on `format`, so a reader that switches on it is
 * exhaustive by construction and a third format tomorrow breaks the switch at
 * compile time rather than at 3am.
 */
export type CheckpointEnvelope = ConversationEnvelope | PausedRunEnvelope;
/** A conversation packed for storage — what a turn that ANSWERED leaves behind. */
export interface ConversationEnvelope {
    /** Names the shape of `data`. Unknown values are refused, never guessed at. */
    readonly format: 'conversation-v1';
    /** The conversation itself. */
    readonly data: AgentRunCheckpoint;
    /** Wall-clock when it was packed. Diagnostic. */
    readonly savedAt: number;
}
/** A paused run packed for storage — what a turn that ASKED leaves behind. */
export interface PausedRunEnvelope {
    /** Names the shape of `data`. Unknown values are refused, never guessed at. */
    readonly format: 'flowchart-v1';
    /** The paused run. */
    readonly data: PausedRun;
    /** Wall-clock when it was packed. Diagnostic. */
    readonly savedAt: number;
}
/**
 * A run that stopped to ask a person something, in the three pieces a session
 * actually needs: what continues it, what it has said so far, and what it is
 * waiting on.
 *
 * ── JSON, honestly ───────────────────────────────────────────────────────────
 * A `FlowchartCheckpoint` is **JSON-safe to resume from, and not byte-identical
 * through JSON.** `JSON.stringify` drops any property whose value is
 * `undefined`, and a real paused agent run has a dozen of them. Every one
 * measured sits in `executionTree` / `subflowResults` — the diagnostic halves
 * the engine keeps for narrative and BTS. `sharedState`, which is the half
 * `agent.resume()` actually reads, round-trips unchanged, because footprintjs's
 * TypedScope already JSON-round-trips every object write on its way into
 * committed state.
 *
 * So: store it anywhere that speaks JSON and resume works. Do not assert that
 * what came back deep-equals what went in — `key: undefined` comes back as no
 * key at all, and a test written to expect otherwise is testing `JSON`, not
 * this library.
 */
export interface PausedRun {
    /** The engine checkpoint — everything `agent.resume(checkpoint, decision)` needs. */
    readonly checkpoint: FlowchartCheckpoint;
    /**
     * The conversation as of the pause, in the same shape every other turn stores.
     *
     * Kept alongside the checkpoint so a session that is waiting on a person is
     * still a readable conversation: a support view can show what was said, and a
     * runtime that cannot resume this run can still see the turn that led to the
     * question.
     */
    readonly conversation: AgentRunCheckpoint;
    /** What the run is waiting on, as data. */
    readonly pending: PendingAsk;
    /**
     * What the WIRE that served the pause recorded about it — the ids it handed
     * the caller for the answer — so a later request on that wire is matched to
     * this pause and continued as the same invocation (`streamHandler` keeps the
     * AG-UI adapter's record here). Absent for a pause a door with nothing to
     * remember stored, and opaque to the store and to every other door.
     */
    readonly wire?: PausedWire;
    /**
     * Set when a resume of this pause failed — in the run, or in storing its
     * result — after its decision may have been used: it reached a ledger, so
     * the call may have run (a resume that never took the decision to a ledger
     * leaves the pause unmarked). The pause is answered: a decision for it is
     * refused (`ResumeDecisionError`, `'already-answered'`), whether its call ran
     * is not known to the host, and the next message continues the conversation
     * in `conversation` — what this session stored (the run's own, kept mid-run
     * under durable writes, else as of the pause), every call left open settled
     * as unknown, and a call the person declined settled as declined. `at` is
     * when the host marked it. Absent on every pause still waiting — and code
     * that reads stored pauses itself (`readPausedRun`) must honour it: an
     * answered pause is a conversation to continue, never a question to show.
     */
    readonly answered?: {
        readonly at: number;
    };
}
/** One wire protocol's own record of a pause it served — see {@link PausedRun.wire}. */
export interface PausedWire {
    /** The protocol that recorded it: the protocol adapter's `name`. */
    readonly protocol: string;
    /** Plain JSON, the protocol's own; ids and names, never a value of the run. */
    readonly record: unknown;
}
/**
 * The question a paused run is waiting on — the part of a pause that is safe to
 * hand to whoever asked.
 *
 * **It deliberately carries no checkpoint.** The engine checkpoint holds the
 * entire shared state of the run: the system prompt, the whole conversation,
 * every tool result. That belongs in the store, which the operator chose and
 * controls, and not in a reply to whoever posted the request. The caller gets
 * the question; the store gets the state.
 */
export interface PendingAsk {
    /** Typed missing-input collection. Respond with {requestId, values}; never approval. */
    readonly awaitingInput?: AwaitingInput;
    /** The session holding the paused run — where the decision has to be sent back. */
    readonly sessionId?: string;
    /** The tool that asked, when the run recorded which one it was. */
    readonly tool?: string;
    /** The question in plain words, when the pause carried one. */
    readonly question?: string;
    /**
     * Present ONLY when a tool declared `checkIn` — the typed ask plus its
     * evidence pack (what the tool will do, what context the run read, which
     * context drove the choice, the run so far). Answer with
     * `checkInApproved({ request: pending.checkIn, by })` / `checkInDeclined(...)`
     * — `checkIn.id` names the request, and the decision answers it once.
     */
    readonly checkIn?: CheckInRequest;
    /**
     * Present ONLY when a `toolMiddleware` answered `ask` — the question and the
     * middleware that put it. Answered with the same decision vocabulary a
     * check-in uses, deliberately: a person approving is a person approving —
     * `checkInApproved({ request: pending.ask, by })`, naming `ask.id`.
     */
    readonly ask?: MiddlewareAsk;
    /**
     * Which REGISTERED screen component collects the answer (9.24.0) — the
     * typed half of the question, lifted from whichever pause kind carried it
     * so a screen has ONE place to look. Ids and props only, never markup: the
     * registry lives in the frontend, and a screen that does not know the id
     * falls back to the prose `question` exactly as before.
     *
     * `props` is the small inline half; `propsRef` is a claim ticket the screen
     * redeems through the artifact wire (`head` then `get`) under the SAME
     * session identity every other redemption presents — a 200-option picker
     * rides the store, not this reply and not the checkpoint. The ref was
     * validated to resolve when the ask was raised.
     *
     * The answer comes back through {@link HostRequest.decision} unchanged —
     * the component changes how the question is asked, never what the answer
     * is. A screen may render the decision as words; the words are display,
     * the structured decision is the record.
     */
    readonly component?: AskComponent;
    /**
     * Exactly what the tool passed to `askHuman()` / `pauseHere()`, uninterpreted.
     * For a plain pause this is the whole of what the tool's author chose to say,
     * and the library is not entitled to summarise it.
     */
    readonly pauseData: unknown;
}
/**
 * How often a run's progress is written to the session store — the trade
 * between latency and how much a crash can cost you.
 *
 *  - `'exit'` (default) — one write, when the run finishes. The behaviour every
 *    release before 7.19 had, spelled out rather than implied. A crash mid-run
 *    loses the whole turn.
 *  - `'async'` — a write is STARTED whenever the conversation changes and never
 *    waited on. The run never slows down; the store is behind by however much
 *    the newest un-landed write carried. At most one write is in flight and the
 *    newest snapshot supersedes any queued one, so what a crash leaves is always
 *    a PREFIX of the run, never a mixture.
 *  - `'sync'` — persist-then-proceed. The same trigger, but **iteration N's
 *    tools do not execute until iteration N-1's write has landed**, and the
 *    answer is not delivered until the last write has landed. You pay the
 *    store's latency once per iteration, knowingly, and in exchange the amount
 *    of work a crash can re-run has a number: **the current iteration, and
 *    nothing before it.**
 *
 * ── The bound, stated exactly ────────────────────────────────────────────────
 * A commit boundary is a whole stage, and the agent dispatches ALL of one
 * iteration's tool calls inside one stage body. So under `'sync'` a crash
 * re-executes the tools of the iteration that was in flight — never an earlier
 * one. That is the same idempotency requirement `resumeOnError` has always
 * carried, now with a boundary instead of a warning: mutating tools must be
 * idempotent, keyed on stable call content rather than `ctx.toolCallId`.
 */
export type DurabilityMode = 'exit' | 'async' | 'sync';
/**
 * Why a session is being woken.
 *
 *  - `'invoke'` — a request arrived for that session.
 *  - `'resume'` — that request carries a person's decision for a run which
 *    paused earlier.
 *  - `'artifact'` — that request redeems an artifact ref (or binds through
 *    `artifactsForRequest`), and the stored conversation decides something:
 *    its identity tuple composes the scope (a request carrying a `userId`) or
 *    its owner decides who may redeem (a verifying door). Fired only when the
 *    resolution actually reads the store; a session-only resolution at a door
 *    with no verifier composes its scope from the request alone and wakes
 *    nothing — whether its ref exists is the artifact store's answer.
 *  - `'transcript'` — a verified owner is READING that session's messages back
 *    (9.26.0). Nothing runs and nothing is written; the store is woken because
 *    it is about to be read from, which is the only promise this hook ever
 *    made.
 *
 * `'resume'` was absent until 7.19, `'artifact'` until 9.23 and `'transcript'`
 * until 9.26, because nothing could produce them: naming reasons nothing fires
 * would be an interface describing a system that does not exist. Something
 * produces each of them now.
 */
export type WakeReason = 'invoke' | 'resume' | 'artifact' | 'transcript';
/**
 * The port: where a conversation lives between requests.
 *
 * Deliberately two required methods. Anything a real store also wants — a TTL,
 * a scan, a delete — is that store's own API, not a demand this port makes of
 * every store that will ever implement it.
 *
 * ── Claiming the port ───────────────────────────────────────────────────────
 * The two required methods are what TypeScript checks. What a store has to
 * *behave* like is a battery every shipped store runs against and any store
 * can: `sessionLifecycleConformance`, exported from this same subpath. It was
 * written after a flaw in THIS interface's semantics turned up in four
 * independent adapters at once — every one of them tested, and every one of
 * them tested only against its own doubles, which is precisely why none of
 * them could see it. Run it against your store; a case your store cannot
 * satisfy is DECLARED by name with its reason, never skipped.
 */
export interface SessionLifecycle {
    /** The stored conversation, or `undefined` for a session that has none yet. */
    hydrate(sessionId: string): Promise<CheckpointEnvelope | undefined>;
    /**
     * Store the conversation for this session. Last write wins — with ONE
     * exception, and it is the only thing this method ever refuses.
     *
     * **A store that keeps an owner index refuses a turn signed by somebody
     * other than the established owner** (`SessionOwnershipConflictError`,
     * 9.36.1), and refuses it whole: neither the index nor the ENVELOPE changes.
     * Until 9.36.1 the write-once rule protected only the index, so such a write
     * kept the first writer's `owner` and stored the second writer's entire
     * conversation — leaving the index naming one person and the stored
     * conversation naming another. The person the index named listed it, opened
     * it, and read the other one's conversation.
     *
     * A turn carrying a LEANER identity is not refused. It claims nobody, so it
     * contradicts nobody, and the established owner stands — see `listByUser`.
     *
     * The composer refuses a foreign turn at the door long before this, when a
     * verifier is configured. This is the store keeping the same promise for
     * every caller that holds it directly, and for a door with no verifier.
     */
    persist(sessionId: string, envelope: CheckpointEnvelope): Promise<void>;
    /**
     * Called once per served request, before `hydrate`, for stores that need to
     * spin something up before they can answer. Errors from it fail the request —
     * a store that could not wake cannot be read from either.
     */
    onWake?(sessionId: string, reason: WakeReason): void | Promise<void>;
    /**
     * OPTIONAL (9.26.0) — the sessions this user owns, newest first.
     *
     * Feature-detected, never assumed. The two required methods are a key/value
     * map and most stores are exactly that; demanding a secondary index of every
     * implementation that will ever exist would be this port breaking its own
     * rule ("anything a real store also wants is that store's API, not a demand
     * this port makes"). A store that leaves this absent makes
     * `{ op: 'session-list' }` refuse BY NAME, naming the store's limitation —
     * never answer "you have no sessions", which is an answer nobody could
     * distinguish from the truth.
     *
     * **Ownership is derived, never declared.** `persist` takes no owner and
     * gains none: a store fills its index from the stored envelope itself
     * (`envelopeOwner`), which reads the `principal` on the conversation the
     * composer put there. A store that let a caller state an owner would be a
     * store where owning somebody's session is a matter of asking for it.
     *
     * **And established ONCE.** The first turn that signs for a conversation owns
     * it; no later write moves that — not a leaner identity (which would erase
     * it) and not a different one (which would transfer it). Every shipped store
     * implements the index that way, and a custom one that let the last writer
     * win would undo every ownership check made against it one turn later.
     *
     * The two halves of "no later write moves it" are NOT symmetrical, and
     * 9.36.1 exists because they were treated as if they were:
     *
     *  - a **leaner** turn is stored, and the owner is kept. It claims nobody, so
     *    it contradicts nobody. Refusing it would break a flow this contract
     *    blesses in the sentence above.
     *  - a **different** turn is REFUSED — index and envelope both. Keeping the
     *    index while storing the payload is not "not moving" the owner; it is
     *    moving the CONVERSATION out from under them, which is worse. Don't
     *    write this rule yourself: `resolveSessionOwner(sessionId, stored,
     *    incoming)` is the one implementation, and it is what every shipped store
     *    now calls.
     *
     * ── Which of the two says who owns a session ────────────────────────────────
     * This index is the authority for LISTING and for the session-history ops:
     * `ownerOf` is the question `{ op: 'session-transcript' }` asks, and nothing
     * re-derives an answer behind it. An ordinary TURN does not ask it — it
     * re-derives from the envelope it has just hydrated (`envelopeOwner`), which
     * is the same derivation and one round-trip cheaper, and which is what keeps
     * a store with NO index protected exactly as well as one with a fast one.
     *
     * Those two can never name different people: that is what the refusal above
     * buys, and it is the only reason a turn is safe to answer from the envelope.
     * They CAN differ in one direction — an index that names somebody over a
     * conversation that names nobody, which is what a leaner turn leaves behind.
     * That state only ever REFUSES: the owner can still list and read the
     * session, and a turn on it is refused at any verifying door, for everybody.
     * Stated because it is a real asymmetry and not a bug to be surprised by.
     *
     * A conversation that ran anonymously has no owner and appears in nobody's
     * list. That is the honest consequence of deriving rather than inventing.
     */
    listByUser?(userId: string, options?: SessionListOptions): Promise<SessionListPage>;
    /**
     * OPTIONAL (9.26.0) — who owns one session, or `undefined` for a session
     * that does not exist OR names no owner.
     *
     * The deliberate ambiguity is the same one `ArtifactStore.get` makes:
     * "missing" and "not yours" must be indistinguishable from the outside, and
     * a store that answered them differently would hand a caller an oracle for
     * which session ids are real.
     *
     * Implement it beside `listByUser` — a door that can list but not check
     * ownership can hand somebody a list and then refuse to open any of it.
     *
     * This is the ownership INDEX, and it is not a second source of truth beside
     * the identity embedded in the stored envelope: `persist` refuses any write
     * that would make the two name different people. A store that answers this
     * from something a caller supplied, rather than from what it derived, has
     * built a permission system out of a request field.
     */
    ownerOf?(sessionId: string): Promise<string | undefined>;
    /**
     * OPTIONAL (9.42.0) — how conversations in this store stop existing.
     *
     * Feature-detected exactly like the two members above, and for the same
     * reason: the two required methods are a key/value map, and demanding a
     * retention mechanism of every implementation that will ever exist would be
     * this port breaking its own rule ("anything a real store also wants is that
     * store's API, not a demand this port makes"). A store that leaves this
     * absent makes {@link sessionRetention} refuse BY NAME, naming the store's
     * limitation and which shipped stores do implement it — never a silent
     * no-op, which is the one failure mode a retention feature must not have,
     * because from the outside "nothing was deleted" and "everything is fine"
     * look identical until somebody asks how long you keep conversations.
     *
     * ── Why it is one member with two arms, and not one verb ────────────────────
     * Because there are exactly two honest implementations of retention, and a
     * port member only half the shipped stores could implement would not be a
     * port member at all:
     *
     *  - some stores **delete when you ask them to** — a `Map`, a table, a file.
     *    A sweep is one loop or one statement there, and the caller's cron
     *    decides when.
     *  - some stores **cannot honestly sweep, because the backend already
     *    expires rows on a policy the operator configured** out of band. Asking
     *    such a store to sweep would mean a query plus one delete per row, at an
     *    unbounded cost, duplicating a job the service does for free — and a
     *    store that reported "0 deleted" from a backend that deletes plenty
     *    would be lying by omission.
     *
     * So the answer is a DISCRIMINATED UNION and `deletedBy` is the field a
     * consumer branches on: one feature check, then one exhaustive branch the
     * compiler enforces. See {@link SessionRetention}.
     *
     * ── The shape that was deliberately NOT chosen ──────────────────────────────
     * A stated expiry at persist time — `persist(id, envelope, { expiresAt })` —
     * reads well and is the wrong shape twice over. It puts a demand on the one
     * method every store must implement, so every store that will ever exist has
     * to honour it; and it cannot be feature-detected, because `typeof
     * store.persist === 'function'` is true whether or not the third argument is
     * read. A store that ignored it would keep every conversation forever while
     * its caller believed retention was configured. An optional member that is
     * absent refuses; an optional ARGUMENT that is ignored does not.
     *
     * ── Why not the shape the ARTIFACT port already uses ────────────────────────
     * That sibling port states an expiry at MINT (`ttlMs` → a stamped
     * `expiresAt`) and sweeps a scope when a put next touches it. It is the right
     * shape THERE because every artifact store in that family holds its own
     * bytes, so every one of them can sweep. This family does not: two of its
     * stores are managed services that expire rows on a policy nobody here
     * controls, and one has no delete at all. A port copies its sibling's
     * vocabulary only where the sibling's assumption also holds.
     *
     * ── What this is not ────────────────────────────────────────────────────────
     * Not a wire op. No door in this package deletes conversations on request,
     * and the composer never expires anything on its own: retention is an
     * operator's job with an operator's blast radius, and a request that could
     * delete somebody's history because it named a cutoff is not a feature.
     *
     * @example  A cleanup job that works against any store that can answer
     *   const policy = sessionRetention(sessions);
     *   if (policy.deletedBy === 'this-store') {
     *     const { forgotten, more } = await policy.forgetOlderThan(Date.now() - THIRTY_DAYS);
     *     log(`forgot ${forgotten} conversations${more ? ', more remain' : ''}`);
     *   } else {
     *     log(`the backend expires these on '${policy.expiresOn}': ${policy.enableWith}`);
     *   }
     */
    retention?(): SessionRetention;
}
/**
 * How one store's conversations stop existing — the answer
 * {@link SessionLifecycle.retention} gives, in the two shapes a store can
 * honestly give it.
 *
 * `deletedBy` is the discriminant, and it is spelled as WHO does the deleting
 * rather than as a mechanism, because that is the fact a caller acts on: one
 * arm hands you something to call, the other hands you something to configure.
 */
export type SessionRetention = SessionSweep | SessionExpiryPolicy;
/**
 * This store deletes, when you ask it to.
 *
 * The shape for a store that holds its own bytes — a map, a table, a file.
 * Nothing sweeps on a timer of its own: a library that started an interval
 * would be deciding your process's lifetime for you, and a sweep that runs
 * without anybody asking is a deletion nobody can point at afterwards.
 */
export interface SessionSweep {
    readonly deletedBy: 'this-store';
    /**
     * Forget every session whose stored `savedAt` is STRICTLY BEFORE `before`
     * (epoch milliseconds), and say how many went.
     *
     * Strictly before, so that passing the same cutoff twice is stable and a
     * session written exactly at the boundary survives — an off-by-one here
     * deletes a conversation somebody is still holding.
     *
     * The clock is the ENVELOPE's `savedAt` — the same value the listing sorts
     * on — and not the store's own wall clock. That makes a sweep a pure
     * function of what is stored: the same cutoff over the same rows forgets the
     * same conversations, in a test and at 3am.
     *
     * Bounded by `options.limit` ({@link DEFAULT_SWEEP_LIMIT} when absent), so a
     * first sweep of a store that has been running for a year cannot become one
     * unbounded transaction. `more` says whether another call has work to do.
     *
     * Deleting a session that is not there is not an error, and a sweep that
     * matches nothing answers `{ forgotten: 0, more: false }` rather than
     * refusing — a cleanup job runs on a schedule, and most of its runs have
     * nothing to do.
     */
    forgetOlderThan(before: number, options?: SessionSweepOptions): Promise<SessionSweepResult>;
}
/**
 * The BACKEND deletes, on a policy an operator configured — this store only
 * writes what that policy reads, and can say exactly what to turn on.
 *
 * There is nothing to call here on purpose. A method that did nothing but
 * report "the backend handles it" would be a method somebody puts in a cron
 * job, and a cron job that deletes nothing is worse than no cron job: it looks
 * like retention is running.
 */
export interface SessionExpiryPolicy {
    readonly deletedBy: 'the-backend';
    /**
     * Whether this store is actually stamping an expiry on every write.
     *
     * `false` means the mechanism exists and nothing is using it — the store was
     * built without a retention setting, so the backend's policy has nothing to
     * act on and conversations live until somebody deletes them. Reported rather
     * than implied, because "there is a TTL field" and "things are expiring" are
     * different facts and only one of them answers an auditor.
     */
    readonly active: boolean;
    /**
     * What the backend's policy acts on — a stored field name, or the setting
     * that starts the clock. Named so an operator can configure the policy
     * against the right thing rather than guessing from a schema dump.
     */
    readonly expiresOn: string;
    /**
     * What an operator does to turn the policy on, in one step they can act on
     * — with this store's own collection, database or resource already filled
     * in where the step needs one.
     *
     * A sentence rather than a code, for the same reason the refusals in this
     * package print a whole command instead of naming a concept: the operator
     * reading this is the person who can fix it in sixty seconds, and only if
     * nobody makes them go and look it up first.
     */
    readonly enableWith: string;
}
/** Options for {@link SessionSweep.forgetOlderThan}. */
export interface SessionSweepOptions {
    /**
     * The most sessions to forget in this call. Default
     * {@link DEFAULT_SWEEP_LIMIT}; stores may cap it lower.
     *
     * A bound rather than "delete everything matching", because the first sweep
     * after this feature is turned on is the biggest one that store will ever
     * do, and it is the one most likely to hold a write lock long enough to be
     * noticed by every request behind it.
     */
    readonly limit?: number;
}
/** What one sweep did. */
export interface SessionSweepResult {
    /** How many conversations this call forgot. */
    readonly forgotten: number;
    /**
     * True when sessions older than the cutoff REMAIN — i.e. the limit was
     * reached. A caller that loops until this is `false` drains the backlog in
     * bounded steps; one that ignores it simply sweeps again next time.
     */
    readonly more: boolean;
}
/**
 * How many sessions one {@link SessionSweep.forgetOlderThan} forgets when the
 * caller names no limit.
 *
 * Big enough that an ordinary nightly sweep finishes in one call, small enough
 * that the first sweep of a neglected store is a series of short transactions
 * rather than one long one.
 */
export declare const DEFAULT_SWEEP_LIMIT = 1000;
/** Paging for {@link SessionLifecycle.listByUser} — the cursor convention every
 *  listing in this package follows. */
export interface SessionListOptions {
    /** Continuation token from a previous page. Omit for the first page. */
    readonly cursor?: string;
    /** Maximum rows this page. Stores may cap it lower. */
    readonly limit?: number;
}
/** One page of a user's sessions. Never carries message content — a listing
 *  says WHICH conversations exist, and `session-transcript` says what is in
 *  one. */
export interface SessionListPage {
    readonly sessions: readonly SessionSummary[];
    /** Present iff more pages exist. */
    readonly cursor?: string;
}
/**
 * What to do when a request arrives for a session that already has a run in
 * flight.
 *
 *  - `'reject'` (default) — refuse it, naming the run that is already going.
 *    A user who double-submits gets one answer and one refusal, not two runs
 *    racing to write the same conversation.
 *  - `'enqueue'` — queue it. It starts after the active run has persisted, so
 *    the second turn sees the first turn's stored state rather than the state
 *    it was about to overwrite.
 *
 * This governs the SAME session only. A request for a different session is
 * never refused — there is nothing wrong with it; it simply waits its turn.
 */
export type ConcurrentInvokePolicy = 'reject' | 'enqueue';
/**
 * Everything {@link standingAgent} takes EXCEPT which agent answers — that is
 * the one choice with two shapes, and it lives on
 * {@link StandingAgentOptions}.
 *
 * Generic in the host's own handle type so composing does not cost you what
 * the adapter told you. `nodeHost` hands back the URL it actually bound —
 * which is the only way to find out when you asked for port `0` — and passing
 * it through `standingAgent` keeps that, without the port having to know that
 * "a URL" is a thing some adapters have.
 */
export interface StandingAgentBaseOptions<TH extends HostHandle = HostHandle> {
    /** Where conversations live between requests. */
    readonly sessions: SessionLifecycle;
    /** What carries requests in. */
    readonly host: AgentHost & {
        serve(handler: HostHandler): Promise<TH>;
    };
    /** Default `'reject'`. See {@link ConcurrentInvokePolicy}. */
    readonly onConcurrentInvoke?: ConcurrentInvokePolicy;
    /**
     * Verify WHO is calling, instead of believing a header (9.26.0).
     *
     * With this set, every request's `Authorization: Bearer <token>` is checked
     * BEFORE the run's identity/scope is composed, and the proven user id is the
     * one that reaches `EventMeta.principal`, `ctx.identity`, the memory
     * namespace and the artifact scope. A request that NAMES a user it cannot
     * prove is refused by name — never downgraded to anonymous, never served
     * under the name it claimed.
     *
     * **It also decides whose sessions are whose.** With a verifier configured,
     * EVERY door that opens a stored conversation asks one question first — the
     * two session-history ops and an ordinary turn alike: does this session
     * belong to the caller who proved who they are? A turn naming somebody
     * else's session is refused with the same indistinguishable
     * `SessionNotFoundError` a transcript gets, before a line of that
     * conversation is hydrated into a model's context and before anything is
     * written back. Ownership is the `principal` the first turn signed with, and
     * no later turn moves it.
     *
     * The consequence to plan for: conversations stored BEFORE a door started
     * verifying name no owner, so they cannot be continued at one that does. That
     * is a loud refusal by design — the alternative is a door that hands old
     * conversations to whoever names them first.
     *
     * Unset — the default — nothing changes: `HostRequest.userId` is read exactly
     * as it has been since 9.12.0, and every release's behaviour before this one
     * is byte-identical. Which is right depends on what stands in front of you,
     * and that is a fact only the deployment knows.
     *
     * @example
     *   identity: { verify: jwksIdentity({
     *     jwksUrl: 'https://idp.example.com/.well-known/jwks.json',
     *     issuer:  'https://idp.example.com/',
     *     audience:'my-api',
     *   }).verify }
     */
    readonly identity?: DoorIdentity;
    /**
     * Decide whether a request runs at all, BEFORE any model is called (9.26.0).
     *
     * Called once per turn with the verified caller, the session and what that
     * caller has spent inside a rolling window; it answers `'allow'`,
     * `{ queue: true }` (run it, but behind this session's other work) or
     * `{ refuse: '<sentence>' }`. `turnsPerHour({ limit })` is the shipped
     * reference policy.
     *
     * Unset — the default — no accounting is kept, no listener is installed, and
     * not one line of this runs.
     *
     * **The honest boundary, stated once:** accounting is PER PROCESS. Two
     * containers each keep their own window, so a limit of 20 across three
     * replicas is a limit of 60. Centralize by writing a policy that consults
     * your own store — the `decide` seam is exactly where that goes.
     *
     * And the second one: a turn is counted when it is ADMITTED, so a request
     * the lane then refuses as a concurrent run still counts against the
     * window. Counting later would let a burst of simultaneous requests each be
     * decided against a window none of them had joined.
     */
    readonly admission?: AdmissionPolicy;
    /**
     * Seal the credential a request carries for its run — a gateway's
     * per-request token in a header, read by a `requestCredentials` provider
     * (`agentfootprint/security`) for the tools that declare a `needs` for it.
     *
     * Called once per request that RUNS (a turn or an answer — never an
     * artifact or session op, never a refused request), after the caller is
     * verified and the session's ownership is checked, with the request's
     * headers ({@link HostRequest.headers}: `nodeHost` and `httpHost` deliver
     * them) and nothing else. Return
     * `requestContext({ secrets })`, or `undefined` for a request that carries
     * none; the handle goes to THAT request's `run()` / `resume()` only — never
     * the reply, the stored session or the checkpoint, so a resumed run is
     * served the RESUMING request's credential. A handle any host was handed
     * before is refused; a sealer that throws refuses the request in fixed words
     * (`ERR_REQUEST_NOT_SEALED`, 400) — so decrypt at vend time
     * (`decodedWith`), not here. The HANDLE never reaches a reply; a tool that
     * returns the credential's value itself does (`hostFilled` scrubs an echo).
     *
     * Unset — the default — no run carries a request, exactly as before.
     *
     * @example
     *   sealRequest: ({ headers }) =>
     *     headers['x-crm-token'] === undefined
     *       ? undefined
     *       : requestContext({ secrets: { crmToken: headers['x-crm-token'] } }),
     */
    readonly sealRequest?: RequestSealer;
    /**
     * Where this door's ingress decisions go (9.32.0) — one record per request,
     * including every request that never became a run.
     *
     * `auditExport()` is a record of RUNS, so a 401 from `identity.verify` and a
     * 429 from `admission.decide` are not in it: they happen before an agent
     * exists. Two independent review rounds named that gap. This is where the
     * turned-away go — and where the SERVED are recorded too, because an absence
     * is only readable against a census.
     *
     * **It does not join the audit hash chain.** Nothing here is hashed,
     * sequenced or verifiable by `verifyAuditBundle`; it is a stream you chain
     * into your own sink. Claiming otherwise would make this the very
     * looks-like-evidence failure it exists to close.
     *
     * The record carries classes and identifiers only — never the token, never a
     * header, never a claim set, never an error's message. See
     * {@link IngressRecord} for the field-by-field contract and for the one thing
     * it deliberately does not cover (a body the transport's own wire grammar
     * refused as malformed before this composer ever saw it).
     *
     * The HOST's own refusals are on it too: set this, and the composer
     * subscribes to the host's `onRefusal`, so a forged request the door guard
     * turned away, or a session id over the bound, is recorded
     * (`'cross-site-refused'` / `'refused'`, door `'request'` or
     * `'conversation'`) though no handler ever saw it.
     *
     * Unset — the default — nothing is built, nothing is wrapped, nothing is
     * subscribed, and the reply the handler uses is the host's own object, to the
     * byte.
     *
     * @example
     *   onIngressDecision: (record) => {
     *     if (record.outcome !== 'served') securityLog.write(record);
     *   }
     */
    readonly onIngressDecision?: IngressSink;
    /**
     * How often a run's progress becomes crash-survivable. Default `'exit'` —
     * one write when the run finishes, which is what every release before this
     * one did. See {@link DurabilityMode} for what the other two buy and cost.
     *
     * Under `'exit'` nothing is attached to the agent at all: no observer, no
     * per-commit work, no barrier. An agent served this way behaves and performs
     * exactly as it did in 7.18.
     */
    readonly durability?: DurabilityMode;
    /**
     * What `close()` does to the telemetry enabled on the agent (8.12.0).
     * Default `'flush'`.
     *
     *  - `'flush'` — drain every strategy the agent has enabled, and leave them
     *    running. This is the default because it is what everyone already
     *    believed happened: an exporter that batches otherwise loses whatever it
     *    had buffered when the server stops. Draining is safe on an agent this
     *    composer only BORROWED — it ships data and disables nothing.
     *  - `'flush-and-stop'` — drain, then release: timers cleared, clients
     *    closed, further events dropped. Say this only when the agent's life
     *    ends with the host, because stopping is terminal.
     *  - `'none'` — touch nothing, exactly as releases before 8.12.0 did.
     */
    readonly shutdown?: 'flush' | 'flush-and-stop' | 'none';
    /**
     * Signals whose arrival should close this host (8.12.0). Off by default,
     * and that is a deliberate refusal rather than an omission.
     *
     * **A library must not grab signals.** `process.on('SIGTERM', …)` is not
     * observation: Node's default action for SIGTERM is to terminate, and
     * ADDING any listener suppresses that default. A library that installs one
     * behind your back can turn a container's graceful stop into a thirty-second
     * wait for SIGKILL in an application that never asked for it. Handlers are
     * process-global and cannot be arbitrated between libraries, ten runners
     * would mean ten handlers and a `MaxListenersExceededWarning`, only Node has
     * signals at all, and no library can know your exit policy.
     *
     * A composition root may, because it already owns the shape of the process
     * — and this one asks first. When you pass signals here, this composer:
     *
     *   1. installs one listener per signal;
     *   2. on arrival, runs the same `close()` you would have called — the host
     *      stops taking requests, in-flight runs finish, telemetry drains per
     *      {@link StandingAgentBaseOptions.shutdown};
     *   3. removes its own listeners and RE-RAISES the signal, so the process
     *      dies exactly the way the platform expects rather than by an exit code
     *      this library invented.
     *
     * `close()` removes the listeners too, so a handle you close yourself leaves
     * nothing installed.
     *
     * @example
     *   await standingAgent({ agent, sessions, host, shutdownOn: ['SIGTERM', 'SIGINT'] });
     */
    readonly shutdownOn?: readonly NodeJS.Signals[];
    /**
     * How long a turn's artifact hand-over (`HostReply.turnArtifacts`) may hold
     * the reply — the hook AND every operation it started — before the composer
     * stops waiting. Default {@link TURN_ARTIFACTS_TIMEOUT_MS} (5 s). The
     * request's own `signal` ends the wait earlier when the caller hangs up.
     *
     * On expiry the binding is revoked, nothing in flight is cancelled (it may
     * still land, attributed to its own session and run), the cause is reported
     * (`agentfootprint.artifacts.hand_over_failed { cause: 'timeout' }`), the
     * terminal is delivered and the lane released — so one host's hung store or
     * hook can hold neither every other session (the shared shape), nor a pooled
     * lane past `maxActiveSessions`, nor `close()` on the SIGTERM path.
     *
     * Must be a positive, finite number of milliseconds — a bound that can never
     * fire is not a bound; anything else is refused at construction. Only read
     * when the host implements the hook.
     */
    readonly turnArtifactsTimeoutMs?: number;
    /**
     * Serve "Explain this answer": the `{ op: 'answer-account', ref }` wire op,
     * which returns one answer's plain-words account (`AnswerAccount` plus its
     * allow-listed show-me leaves), computed HERE from the recording the ref
     * names. The browser never receives the recording through it.
     *
     * Off unless given — a host without it answers the op with the unknown-op
     * refusal before reading anything (`false` says the same). `true` opts in
     * with every default; the
     * object form sets the app's `declarations` (validated now, never taken
     * from a request), the recording ceiling and the cache size. See
     * {@link AnswerAccountsOptions}.
     *
     * The op follows `artifact-get`'s own path — the same ownership check, the
     * same one not-found, lane-free — and adds one refusal of its own: a
     * recording over the ceiling (`ERR_RECORDING_TOO_LARGE_FOR_ACCOUNT`, 413).
     *
     * @example
     *   await standingAgent({ agent, sessions, host, answerAccounts: {
     *     declarations: { tools: { lookup_volumes: { rowsAt: 'volumes' } } },
     *   } });
     */
    readonly answerAccounts?: AnswerAccountsOptions | boolean;
    /**
     * How many artifact operations — `artifact-head`, `artifact-get` and
     * `answer-account`, counted together — ONE session may have in flight at
     * once. Default {@link DEFAULT_ARTIFACT_OPS_PER_SESSION} (8).
     *
     * Artifact ops are lane-free (they never wait behind a run) and turn
     * admission does not see them, so this is their bound: a session over it is
     * refused with `ArtifactOpsBusyError` (`ERR_ARTIFACT_OPS_BUSY`, 429) while
     * every other session is served. Counted after the ownership check, so a
     * stranger cannot spend another session's slots. A screen that redraws many
     * panes at once should redeem them a few at a time, or raise this.
     * `Infinity` means no bound; anything else must be a positive whole number.
     */
    readonly artifactOpsPerSession?: number;
}
/**
 * How many artifact operations one session may have in flight by default —
 * enough for a screen redrawing a handful of panes, too few for one session
 * to crowd the event loop every session shares. See
 * {@link StandingAgentBaseOptions.artifactOpsPerSession}.
 */
export declare const DEFAULT_ARTIFACT_OPS_PER_SESSION = 8;
/** How many sessions a `agentFactory` pool holds before it evicts the least
 *  recently used one. See {@link StandingAgentPoolOptions.maxActiveSessions}. */
export declare const DEFAULT_MAX_ACTIVE_SESSIONS = 100;
/**
 * How long a turn's artifact hand-over may hold its reply by default — the
 * hook plus the operations it started. Five seconds: the order of
 * `TOOL_TEARDOWN_TIMEOUT_MS`, and for the same reason — both sit on the
 * shutdown path (`close()` drains in-flight requests), where an unbounded wait
 * turns a container stop into a wait for SIGKILL — while leaving a store room
 * for a few ordinary writes. See
 * {@link StandingAgentBaseOptions.turnArtifactsTimeoutMs}.
 */
export declare const TURN_ARTIFACTS_TIMEOUT_MS = 5000;
/**
 * One shared agent, serving every session — the original shape, and still the
 * right one for most deployments.
 *
 * An `Agent` instance holds per-run state on itself, so one instance can only
 * be in one run at a time. That is why this shape SERIALIZES globally: not a
 * tuning choice, a correctness requirement. See
 * {@link StandingAgentPoolOptions.agentFactory} for the shape that runs
 * sessions in parallel, and `standingAgent`'s own doc for the full comparison.
 */
export interface StandingAgentSharedOptions<TH extends HostHandle = HostHandle> extends StandingAgentBaseOptions<TH> {
    /**
     * The agent that answers. ONE instance, shared by every session — which is
     * why this shape runs one request at a time, globally (see
     * {@link ConcurrentInvokePolicy} for the separate question of two turns of
     * the SAME conversation).
     *
     * The composer only BORROWS it: `close()` drains its telemetry and leaves it
     * usable, per {@link StandingAgentBaseOptions.shutdown}.
     */
    readonly agent: Agent;
    /** Refused beside `agent` — see {@link StandingAgentPoolOptions.agentFactory}. */
    readonly agentFactory?: undefined;
    /** Meaningless without a pool; refused rather than ignored. */
    readonly maxActiveSessions?: undefined;
}
/**
 * One agent PER ACTIVE SESSION, built on demand — sessions run in parallel
 * (9.10.0).
 */
export interface StandingAgentPoolOptions<TH extends HostHandle = HostHandle> extends StandingAgentBaseOptions<TH> {
    /** Refused beside `agentFactory` — two spellings of one choice. */
    readonly agent?: undefined;
    /**
     * Build an agent. Called once per session that arrives and is not already in
     * the pool; the instance it returns serves that session and nobody else.
     *
     * ── What it buys ──────────────────────────────────────────────────────────
     * **Sessions run at the same time.** Two people asking two questions are two
     * instances and two runs; neither waits for the other. Within one session the
     * turns still serialize — on that session's own instance — which is the same
     * correctness rule the shared shape enforces globally, applied where it
     * actually binds.
     *
     * ── What it costs ─────────────────────────────────────────────────────────
     * An instance per active session: memory, and whatever your agent builds at
     * construction (a provider client, tool wiring). The pool is bounded by
     * {@link StandingAgentPoolOptions.maxActiveSessions} and evicts least
     * recently used, which is invisible to the user because the CONVERSATION
     * lives in the session store — an evicted session re-hydrates onto a fresh
     * instance on its next request.
     *
     * ── The one law ───────────────────────────────────────────────────────────
     * **Return a NEW agent every call.** A factory that hands back an instance it
     * has already handed back is refused BY NAME on the spot: two sessions on one
     * instance is the exact corruption the pool exists to prevent, and it would
     * otherwise show up as one user's answer appearing in another user's
     * conversation with nothing in the recording to say so. Build the agent
     * INSIDE the factory; do not close over one.
     *
     * Instances the factory makes are the composer's, so it stops them — on
     * eviction, and on `close()` unless
     * {@link StandingAgentBaseOptions.shutdown} is `'none'`.
     *
     * @example
     *   await standingAgent({
     *     agentFactory: () => Agent.create({ provider, model }).system('…').build(),
     *     sessions: sqliteSessions({ file: './sessions.db' }),
     *     host: nodeHost({ port: 8080 }),
     *     maxActiveSessions: 200,
     *   });
     */
    readonly agentFactory: () => Agent;
    /**
     * How many sessions hold an instance at once. Default
     * {@link DEFAULT_MAX_ACTIVE_SESSIONS} (100). Must be a positive integer.
     *
     * When a new session arrives at a full pool, the least recently used session
     * that is NOT running is retired: its tool sessions are closed with reason
     * `'evicted'`, its agent is shut down, and its conversation stays in the
     * session store. Nothing about that is visible to the person on the other
     * end — their next message hydrates the same conversation onto a fresh
     * instance.
     *
     * **A running session is never evicted.** If every session in the pool is
     * busy, the pool grows past this number rather than tearing down a live run:
     * the bound is on how many idle instances are RETAINED, and losing somebody's
     * answer to a cache policy is not a trade this composer will make on your
     * behalf. It comes back under the bound as soon as a run finishes.
     */
    readonly maxActiveSessions?: number;
}
/**
 * Options for {@link standingAgent} — pick ONE of the two agent shapes.
 *
 *  - `{ agent }` — one instance shared by every session, serialized globally.
 *    See {@link StandingAgentSharedOptions}.
 *  - `{ agentFactory }` — one instance per active session, sessions in
 *    parallel. See {@link StandingAgentPoolOptions}.
 *
 * Passing both is refused by name at construction: they are two spellings of
 * the same decision, and which one won would be invisible.
 */
export type StandingAgentOptions<TH extends HostHandle = HostHandle> = StandingAgentSharedOptions<TH> | StandingAgentPoolOptions<TH>;
//# sourceMappingURL=types.d.ts.map