import { type AuthenticateRequest, type AuthenticateResponse, type AuthMethod, type CreateElicitationResponse, type ContentBlock, type AgentNotificationMethod, type AgentNotificationParamsByMethod, type AgentRequestMethod, type AgentRequestParamsByMethod, type AgentRequestResponsesByMethod, type DeleteSessionRequest, type DisableProviderRequest, type DisableProviderResponse, type ListSessionsRequest, type ListSessionsResponse, type ListProvidersRequest, type ListProvidersResponse, type LogoutRequest, type LogoutResponse, type PromptRequest, type PromptResponse, type RequestPermissionResponse, type SendRequestOptions, type SessionConfigOption, type SessionModeState, type SessionNotification, type SetProviderRequest, type SetProviderResponse, type SetSessionConfigOptionRequest, type SetSessionConfigOptionResponse, type SetSessionModeRequest, type SetSessionModeResponse } from "@agentclientprotocol/sdk"; import type { TSchema } from "typebox"; import { type AgentHistoryEntry, type McpServerConfig } from "@automatalabs/shared-types"; import type { Backend, BackendId, ProviderErrorMetadata, StructuredSource } from "./backend.js"; import { type NegotiatedCapabilities } from "./capabilities.js"; import { type AcpEventSink } from "./events.js"; import { type ElicitationResolver, type PermissionResolver, type ToolPolicy } from "./permissions.js"; import { UsageAccumulator } from "./usage.js"; import { type ClientHandlers } from "./client-handlers.js"; import { type AuthStore, type BackendAuthMachine, type ConnectionAuthStamp } from "./auth/auth-store.js"; import type { ProviderStore } from "./provider-store.js"; import { type TypedSessionFailure } from "./typed-failures.js"; /** Cross-agent vendor extension for injecting content into a live prompt turn. */ export declare const SESSION_STEERING_METHOD: "_session/steering"; /** Cross-agent vendor extension carrying turn-TERMINAL state for LOADED sessions (the re-attach * arm's authoritative completion evidence — see `InteractiveSession.awaitCurrentTurn`): the * `_session/loaded_turn/query` request answers whether the loaded session's founding turn is * still running at the backend ("running"), observably completed while the host was down * ("completed" — the replay's trailing assistant message is the turn's FINAL message), or ended * without a terminal message ("interrupted" — nothing is running, re-issue is safe), and the * `_session/loaded_turn/ended` notification fires when a turn that was "running" at query time * ends (with its stop reason, or its error). Backends without the extension degrade * guest-visibly through the same strict advertisement gate as steering. */ export declare const LOADED_TURN_QUERY_METHOD: "_session/loaded_turn/query"; export declare const LOADED_TURN_ENDED_METHOD: "_session/loaded_turn/ended"; /** The founding-turn terminal classification a capable backend answers with (see the * `LOADED_TURN_QUERY_METHOD` docs). */ export type LoadedTurnStatus = "completed" | "running" | "interrupted"; /** Exact `_session/loaded_turn/query` wire request. */ export interface LoadedTurnQueryRequest { sessionId: string; } /** Exact `_session/loaded_turn/query` wire response. */ export interface LoadedTurnQueryResponse { status: LoadedTurnStatus; } /** Exact `_session/loaded_turn/ended` wire notification. `stopReason` is the ACP stop-reason * vocabulary for a turn that ended with a response; `error` replaces it for a turn that ended * by failing (the seam then rejects the founding call with the error instead of settling). */ export interface LoadedTurnEndedNotification { sessionId: string; stopReason?: string; error?: { name: string; message: string; }; } /** Every outcome an ACP steering agent can resolve with. */ export type SteeringOutcome = "injected" | "startedNewTurn" | "failed"; /** Exact `_session/steering` wire request. Optional `_meta` uses the same outgoing custom-meta * gate as other session requests, independently of the initialize steering advertisement. */ export interface SteeringRequest { sessionId: string; prompt: ContentBlock[]; _meta?: Record; } /** Exact `_session/steering` wire response. */ export interface SteeringResponse { outcome: SteeringOutcome; } /** Grace for a cancelled prompt/config lifecycle to settle before close + process quarantine. */ export declare const CANCEL_NOT_HONORED_GRACE_MS = 5000; export declare const PI_CHILD_CLEANUP_DEADLINE_MS = 5000; export declare const PI_CLOSE_DELIVERY_MARGIN_MS = 1000; export declare const PI_CLOSE_SESSION_TIMEOUT_MS: number; export declare const PI_PROCESS_SHUTDOWN_ENVELOPE_MS = 66000; export declare const PI_PROCESS_EXIT_MARGIN_MS = 1000; export declare const PI_DISPOSE_SIGKILL_GRACE_MS: number; interface RawResultSuccess { type: string; subtype: string; structured_output?: unknown; } /** Per-session accumulator: assistant text, tool history, usage, the Claude raw structured_output, * and the permission policy/resolver used to answer permission requests for THIS session. */ declare class SessionState { readonly cwd: string; readonly policy: ToolPolicy; readonly permissionResolver?: PermissionResolver | undefined; readonly elicitationResolver?: ElicitationResolver | undefined; readonly label?: string | undefined; readonly runId?: string | undefined; readonly callIndex?: number | undefined; readonly initializeMeta?: Readonly> | undefined; readonly mcpServerIds: readonly string[]; private readonly retainSessionLog; readonly textChunks: string[]; readonly history: AgentHistoryEntry[]; readonly usage: UsageAccumulator; readonly pendingPermissions: Set<(outcome: RequestPermissionResponse) => void>; readonly pendingElicitations: Set<(outcome: CreateElicitationResponse) => void>; readonly urlElicitationIds: Set; readonly mcpConnectionIds: Set; rawResultSuccess: RawResultSuccess | undefined; providerErrorMetadata: ProviderErrorMetadata | undefined; modes: SessionModeState | null | undefined; /** The latched typed session failure (codex-acp's negotiated extension — see typed-failures.ts): * the newest ERROR record the server has published for THIS session over the asynchronous * `session_info_update` channel, or undefined when it never published one. Advisory `warning` * records never enter it. Kept in the server's own vocabulary rather than collapsed to a * boolean, because the revision/identity rules are what make a duplicated or reordered frame * harmless. */ private typedSessionFailure; /** The latch as it stood when the current turn began. The server keeps the same snapshot for the * same reason (`recoverableSessionFailure` in its prompt path): it is what distinguishes a * failure this turn RAISED from one it merely inherited from an earlier turn. */ private turnStartTypedSessionFailure; private turnStartIndex; private finalMessageStartIndex; /** Text-chunk indexes that begin a distinct assistant message. ACP's * optional `messageId` is authoritative when present; for adapters * that omit it, an intervening content update (tool/thought/plan/user) * ends the in-flight message. Streaming deltas within one message are * never recorded here and therefore concatenate verbatim. */ private readonly assistantMessageStartIndexes; private assistantMessageBoundaryPending; private activeAssistantMessageId; /** The re-attach arm's transcript probe (phase D): where the LOADED * session's founding turn starts — the assistant-text length after the * LAST replayed user message (the founding turn's prompt). Tracked from * the session/update stream; only meaningful for sessions re-opened via * `session/load` (whose replay streams in BEFORE the load response). */ private loadedTurnStartIndex; /** Whether the transcript ever showed a user message (a turn started at * all — a session whose replay has none never received its prompt). */ private sawUserMessage; /** The KIND of the transcript's last content event: an assistant message * chunk is a PROGRESS event, never a terminal marker by itself — the * re-attach arm's completion evidence is a terminal assistant message * on a SETTLED stream (no updates for the loaded-turn settle grace), * not a trailing chunk at an arbitrary instant (phase-D review: a * trailing chunk used to be treated as proof of completion, so partial * output of a still-streaming turn could be settled as success). Any * other trailing content (a user message, a tool call, a thought, a * plan) means the founding turn ended without a terminal message — * not observable as a successful completion. */ private trailingContentKind; /** Monotonic wall-clock of the session's most recent update (the * re-attach arm's stream-settled probe — `applyUpdate` is synchronous * on the wire, so this is the authoritative last-progress instant). */ private lastUpdateAt; /** The re-attach arm's update watchers (woken by every session/update). */ private readonly updateWatchers; /** * The load boundary — the phase-D review round-2 fix for the re-attach * arm's completion evidence. `session/load` obliges the agent to replay * the ENTIRE persisted conversation and only then resolve the request, * so the transcript is complete AT load resolution; anything applied * after that instant is LIVE CONTINUATION evidence of a turn still * running at the backend. `markLoadBoundary()` (called by the runner * synchronously after the load response) snapshots the replay-complete * state, and every CONTENT update applied after the mark flips * `sawPostLoadContentUpdate` — the seam's "the turn may still be * running" signal. Bookkeeping updates (usage, mode, available * commands, session info) are NOT continuation evidence (claude's * adapter emits an `available_commands_update` right after every * load). */ private loadBoundary; private sawPostLoadContentUpdate; /** The loaded-turn TERMINAL state (the `_session/loaded_turn` extension's authoritative * completion evidence): the `_session/loaded_turn/ended` notification this session received * (a turn that was running at load ended), or null when no such notification arrived. The * seam's `awaitCurrentTurn` waits on this instead of guessing from a quiet gap. */ private loadedTurnEnded; /** The loaded-turn-ended watchers (woken by every ended notification). */ private readonly loadedTurnEndedWatchers; /** `label`/`runId`/`callIndex` are carried here ONLY so the MultiplexClient can stamp them onto emitted * events as context — they never affect routing or the wire request. */ constructor(cwd: string, policy: ToolPolicy, permissionResolver?: PermissionResolver | undefined, elicitationResolver?: ElicitationResolver | undefined, label?: string | undefined, runId?: string | undefined, callIndex?: number | undefined, initializeMeta?: Readonly> | undefined, modes?: SessionModeState | null, mcpServerIds?: readonly string[], retainSessionLog?: boolean); /** Mark the start of a new turn so currentTurnText()/structured_output read only this turn. * Long-lived interactive sessions can opt out of retaining old text/history because hosts * stream live events and keep their own transcript; clearing here prevents dead logs from * growing for the lifetime of a held-open session. */ beginTurn(): void; currentTurnText(): string; /** The turn's assistant text with the §5 message joiner: deltas of one * message concatenate verbatim; distinct messages join with "\n\n". */ foldedTurnText(): string; private foldedTextFrom; private markAssistantMessageBoundary; private beginAssistantMessageChunk; /** The turn's FINAL assistant message: only the chunks streamed after the last content event * that isn't an assistant message chunk (tool call, thought, plan). A backend whose schema * constraint applies turn-wide (Codex) emits schema-shaped intermediate progress messages; * structured extraction must read this, never the whole-turn concatenation. */ finalMessageText(): string; applyUpdate(update: SessionNotification["update"]): void; applyRawMessage(message: RawResultSuccess | undefined): void; /** * Fold one typed-session-failure frame (from any ACP `_meta`) into the latch. A `_meta` that * carries none — every other backend's, every older codex-acp's — leaves the latch untouched. * * SUPERSESSION. A frame for the same `id` at a revision we have already seen is stale and is * dropped, so a duplicate or a reordered delivery can neither re-raise a superseded failure nor * roll one back to an older category. Only an `error` record enters the latch: a * `severity: "warning"` frame (a retry hint or deprecation notice) is advisory and must never * fail a turn, so it is read (and returned) but not latched. The server no longer publishes an * explicit "cleared" frame — it retires a failure by simply not re-publishing it, and the * per-turn baseline comparison (`turnTypedSessionFailure`) is what keeps a stale latch off a * later turn. * * Returns the frame that was READ (whether or not it superseded the latch), so a caller holding * an authoritative frame — a turn's own terminal failure, which is authoritative for that turn * by construction — can act on it without re-parsing. */ applyTypedSessionFailure(meta: unknown): TypedSessionFailure | undefined; /** * The typed session failure this turn RAISED, or undefined when the session has none active or * only carries one inherited from an earlier turn. Compared against the turn-start snapshot by * the same (id, revision) rule the server uses to decide whether a failure is still the one it * saw when the turn began. */ turnTypedSessionFailure(): TypedSessionFailure | undefined; /** The loaded-session founding-turn observability probe: whether the * transcript shows a turn ever started (a user message) and the KIND of * the trailing content event. The trailing kind is PROGRESS evidence, * not completion by itself — the re-attach arm classifies completion * from the LOAD BOUNDARY (the transcript as of load resolution) plus * whether any content update followed the load (see * `loadBoundaryState` and `InteractiveSession.awaitCurrentTurn`). */ loadedTurnState(): { hasUserMessage: boolean; trailingContentKind: 'assistant-message' | 'other'; }; /** The most recent instant a session/update arrived for this session * (the re-attach arm's stream-settled clock; `applyUpdate` runs * synchronously on the wire, so the timestamp is authoritative). */ lastUpdateAtMs(): number; /** * Mark the load boundary (see the field docs): called by the runner * synchronously after the `session/load` response, when the replay is * complete and the transcript holds the entire persisted conversation. * Idempotent: the FIRST mark wins (a re-load over the same handle keeps * the original boundary). */ markLoadBoundary(): void; /** * The load-boundary probe (the re-attach arm's completion evidence; * see `InteractiveSession.awaitCurrentTurn`): the replay-complete * transcript state captured at load resolution, plus whether any * CONTENT update arrived after the boundary (live-continuation * evidence). `marked: false` when the handle was never load-marked (a * session that did not come from the runner's `loadSession` path) — * the seam refuses rather than guessing. */ loadBoundaryState(): { marked: boolean; hasUserMessage: boolean; trailingContentKind: 'assistant-message' | 'other'; sawPostLoadContentUpdate: boolean; }; /** Record the loaded-turn terminal notification (a turn that was * running at load ended — with its stop reason, or its error) and * wake the seam's watchers. Idempotent per session: the FIRST ended * notification wins (a re-sent notification after a reconnect cannot * overwrite the recorded terminal state). */ recordLoadedTurnEnded(notification: { stopReason?: string; error?: { name: string; message: string; }; }): void; /** The recorded loaded-turn terminal state, or null when the running * turn has not ended (yet). */ loadedTurnEndedState(): { stopReason?: string; error?: { name: string; message: string; }; } | null; /** Watch the loaded-turn-ended channel: the listener fires when the * `_session/loaded_turn/ended` notification arrives (and immediately * for a notification that already arrived). Returns the unsubscribe * thunk. The re-attach arm's authoritative terminal wait. */ subscribeLoadedTurnEnded(listener: () => void): () => void; /** Watch the session/update stream: the listener fires after every * applied update. Returns the unsubscribe thunk. The re-attach arm * waits on this instead of polling, so a long still-running turn is * observed with zero busy work. */ subscribeUpdates(listener: () => void): () => void; /** The founding turn's assistant text, folded by assistant-message * boundary exactly like `foldedTurnText()`. This keeps replayed and * post-load deltas byte-identical while separating narration from a * later answer when tool/thought/plan activity intervenes. */ loadedTurnText(): string; /** Settle every deferred permission still parked on this session. Used by release/cancel/death * teardown so an interactive resolver can never strand an ACP prompt turn. */ settlePendingPermissions(): void; /** Same teardown guarantee for elicitation/create: a parked human prompt must not survive * release/cancel/death after its ACP session is gone. */ settlePendingElicitations(): void; } /** Internal typed sentinel for the continuation path. It is deliberately thrown only after * initialize completes and before any session/load or session/resume wire request is sent. */ export declare class ReattachCapabilityUnavailable extends Error { readonly backendId: BackendId; readonly sessionId: string; constructor(backendId: BackendId, sessionId: string); } export interface AcpSessionOptions { /** Absolute working directory for the ACP session (worktree isolation). */ cwd: string; /** The schema for this run, if any (drives the backend's session/prompt `_meta`). */ schema: TSchema | undefined; policy: ToolPolicy; /** Session-scoped permission resolver. When present it wins over the runner default and * replaces the synchronous ToolPolicy auto-response path for this session. */ permissionResolver?: PermissionResolver; /** Session-scoped elicitation resolver. When present it wins over the runner default for this * session; initialize-time advertisement still depends on the runner-wide resolver. */ elicitationResolver?: ElicitationResolver; signal?: AbortSignal; /** Client-provided MCP servers to attach at session/new. Omitted => `[]` (the default). */ mcpServers?: McpServerConfig[]; /** Generic session-scoped `_meta` passthrough (RunOptions.meta). Merged FIRST, under the * backend-computed `_meta` and the runId stamp, so user keys never clobber the schema / * correlation channels. Omitted => the request `_meta` is whatever the backend set. */ meta?: Record; /** Engine run id, stamped onto session/new `_meta` (META_KEYS.runId) as a correlation id. * Omitted => no runId `_meta` is stamped (the request `_meta` is whatever the backend set). */ runId?: string; /** `RunOptions.label`, propagated onto emitted events as context. NOT sent on the wire. */ label?: string; /** `RunOptions.callIndex`, propagated onto emitted events as context. NOT sent on the wire. */ callIndex?: number; /** CODEX-ONLY session instruction overrides. The backend folds these into session/new `_meta` * (bare keys) for the codex-acp adapter; the Claude backend ignores them. Omitted => unset. */ baseInstructions?: string; developerInstructions?: string; /** Retain accumulated assistant text/tool history for the lifetime of this ACP session. * Default true preserves run()'s diagnostic history contract. Held-open interactive sessions * pass false because hosts stream live events / keep their own transcript; retaining old turns * there is dead memory for day-long sessions. */ retainSessionLog?: boolean; } /** Notified by a PooledConnection when its process dies, so the pool can drop it. */ export interface PooledConnectionDeps { onDead(connection: PooledConnection): void; /** Optional typed event sink. When present, every ACP notification / permission request / * session lifecycle change on this connection is bubbled up through it (additive observability; * it is invoked AFTER the drain accumulation and never affects the run). */ onEvent?: AcpEventSink; /** Runner-wide permission resolver default. SessionState.permissionResolver overrides it. */ permissionResolver?: PermissionResolver; /** Runner-wide elicitation resolver default. SessionState.elicitationResolver overrides it. */ elicitationResolver?: ElicitationResolver; /** Initialize-time elicitation advertisement; fixed per connection, so it is driven by the * runner-wide resolver rather than session-scoped responders attached later. */ advertiseElicitation?: boolean; /** Initialize-time client auth advertisement (§1.2); fixed per connection like elicitation. * Undefined (the default) omits the `auth` capability entirely — the default-OFF baseline. */ authCapabilities?: { terminal?: boolean; gateway?: boolean; }; /** The runner's single auth store (§2). When present, this connection reconciles to the current * intent at the end of `initialize` (replay for in-process creds), overlays the spawn env with * host-collected env values, and carries a generation stamp the pool gates selection on. * Undefined => no auth wiring, byte-identical to the pre-auth baseline (default-OFF). */ authStore?: AuthStore; /** The runner's single provider-intent store. When present, this connection replays the recorded * `providers/set` intents at the end of `initialize` (provider routing is in-process agent state * for e.g. codex-acp — the same dispose-after-configure class as auth gap 3) and carries a * generation stamp the pool gates selection on. Undefined or empty => byte-identical baseline. */ providerStore?: ProviderStore; /** Client-side ACP fs/terminal handlers advertised once and routed by sessionId. */ clientHandlers?: ClientHandlers; /** Deterministic disposal-clock seam. Production uses the platform timers. */ disposeTimer?: { set(callback: () => void, ms: number): { unref?(): void; }; clear(timer: { unref?(): void; }): void; }; } /** * One long-lived ACP server subprocess + its held ACP client connection. Initialized ONCE and * reused across agent() calls; it multiplexes many concurrent sessions. The process is NOT killed * between sessions — only dispose() (pool teardown) or a crash ends it. */ export declare class PooledConnection { readonly backendId: BackendId; /** Process-unique connection id, used only to tag `BackendAuthMachine` events (§2.3). */ readonly id: string; /** The held ACP connection (fluent client() app); session/* calls go through its * `agent` ClientContext via the typed wrappers below. */ private readonly connection; private readonly backend; private readonly child; private readonly client; private readonly onDead; private readonly onEvent; private readonly clientHandlers; private readonly advertiseElicitation; private readonly authCapabilities; private readonly authStore; private readonly providerStore; private readonly disposeTimer; /** Which intent-generation THIS process reflects (§2.4). Starts at -1/false so a connection with * no applied intent is stale against a machine that has ever advanced past generation 0. */ authStamp: ConnectionAuthStamp; /** Which provider-store generation THIS process replayed at initialize. Starts at 0 (== the * store's empty-pool generation), so the pre-provider baseline is never stale; the first * recorded intent advances the store past it and recycles older processes. */ providerStamp: number; /** Set by the pool when a busy stale connection must be recycled once it drains (§2.6). While set, * the connection is never handed a new session and is disposed-and-dropped on release. */ recyclePending: boolean; /** Set true at the start of dispose() so the graceful-shutdown death is NOT reported as a crash. */ private disposing; private disposePromise; /** Resolves once `initialize` completed (or rejects if the process died first). */ private readonly ready; /** Resolves when the process dies; `race()` turns it into a thrown, descriptive error. */ private readonly whenDead; private resolveDead; private deathError; /** Set from the one-time initialize handshake; undefined until it completes (or if it failed). */ private negotiated; private _alive; private _activeSessions; /** Synchronous process-exclusive reservation for one injected StructuredOutput run. */ private _injectedRunReserved; private stderrTail; /** * Detached commands can outlive an ACP parent's graceful exit. Capture their identities before * that exit so disposal retains a route to them until they have died or an escalation kills them. */ private readonly retainedDescendants; private constructor(); /** Spawn the backend and kick off the single `initialize`. Returns immediately; callers await * readiness implicitly via openSession(). */ static create(backend: Backend, deps: PooledConnectionDeps): PooledConnection; get alive(): boolean; get activeSessions(): number; get injectedRunReserved(): boolean; /** Reserve this process for one injected run. Selection calls this without awaiting. */ tryReserveInjectedRun(): boolean; /** Release the injected reservation after the owning session release has settled. */ releaseInjectedRun(): void; /** The capabilities negotiated on this connection's one-time initialize handshake, or undefined * until it completes — derived-state-behind-a-getter, like `alive`/`activeSessions`. */ get capabilities(): NegotiatedCapabilities | undefined; /** Drop the backend-declared bare `_meta` keys the connected agent did not advertise support * for (see gateCustomMeta). Applied to BOTH session/new and session/prompt `_meta`. */ gateCustomMeta(meta: Record | undefined): Record | undefined; private assertSupportedMcpServers; private sessionRequestMeta; private assertLifecycleSupported; private assertAuthProviderSupported; /** Mark this connection dead exactly once, then ask the pool to evict it. Idempotent. */ private die; private stderrSuffix; /** Resolve this connection's `BackendAuthMachine` from the runner's store, or undefined when no * store is wired (default-OFF) — the machine is keyed by the backend's poolKey (§2.3). */ private authMachine; /** The client `auth` advertisement for THIS connection (§1.2), refined per-backend by the pure-data * `AuthProfile.clientAuthCapabilities` (§3.1). When the host advertised nothing (default-OFF) the * key is omitted verbatim — no profile is consulted, so behavior stays byte-identical. When the * host opted in, the backend's profile maps the host affordances (`onAuth` ⇐ gateway desired, * `terminal` ⇐ host TTY) onto the method TYPES this backend can actually service (e.g. codex never * advertises terminal; opencode never advertises gateway). A custom backend has NO profile → the * host's advertisement passes through unchanged (conformance-by-absence, §3.5). */ private effectiveAuthCapabilities; /** Mark this connection current against `generation` (nothing more to apply). */ private stampApplied; /** true iff the current intent is an in-process (gateway) cred, so an idle connection can be * re-primed with an authenticate RPC replay instead of being recycled (§2.6). */ canLiveReapply(machine: BackendAuthMachine): boolean; /** Reconcile this connection to the current intent at the end of `initialize` (§2.5). For an * in-process cred, replay `authenticate({methodId,_meta})`; for disk (incl. env-at-spawn) a fresh process * already carries the credential, so only stamp it current. */ private applyAuthIntent; /** Idle in-process connection: re-send `authenticate` and re-stamp so the next session opens under * the current gateway cred without a process recycle (§2.6). Returns true iff a re-apply ran. */ reapplyAuthIfStale(machine: BackendAuthMachine): Promise; /** Fire-and-forget idle live re-apply (§2.6). Any failure disposes the connection so the pool * respawns a fresh process rather than serving a session under a failed replay. */ scheduleReapply(machine: BackendAuthMachine): void; /** Mark a BUSY stale connection for recycle once it drains (§2.6): finish in-flight prompts under * the auth they started with, then dispose-and-drop on release. */ markForRecycleWhenIdle(_machine: BackendAuthMachine): void; /** Race a wire call against process death so a crash surfaces a clear error instead of hanging * on a JSON-RPC response that will never come. */ race(op: Promise): Promise; private initialize; /** Replay the recorded `providers/set` intents at the end of `initialize`, then stamp the * generation this process reflects. Advertise-gated: an agent that advertises the unstable * providers block replays every recorded intent; a replay FAILURE throws, failing the connection * loudly because silently opening sessions without the host-configured gateway routing would * mis-route traffic. A fresh process that does NOT advertise providers WHILE routing is still * configured (`intentsFor` non-empty) hits the same wall from the other side — an intent can only * have been recorded against an advertising agent, so a lost advertisement means the agent surface * regressed under us (an npx-resolved backend version change, a command override/wrapper, a custom * backend whose advertisement depends on startup state). Stamping it current would silently route * every later session direct-to-provider instead of through the gateway, so we FAIL LOUDLY here * too, non-recoverably, with both operator exits named. With no recorded intent — the default-OFF * baseline, or after a `disable` emptied the intents — there is nothing to route, so a * non-advertising process is stamped current, byte-identical to the pre-provider baseline. */ private applyProviderIntents; /** * Open a new per-agent session on this pooled connection: session/new { cwd }, register its * accumulator for routing, and return a SessionHandle. `activeSessions` is reserved * synchronously (before the first await) so the pool's load accounting is race-free. */ openSession(opts: AcpSessionOptions, onReleased?: () => void): Promise; /** Reserve a session slot before initialize, then let the caller shape session/new with * negotiated capabilities in hand. */ openPreparedSession(prepare: (connection: PooledConnection) => AcpSessionOptions | Promise, onReleased?: () => void): Promise; /** Reserve one connection slot, await initialize, choose the best currently-advertised reopen * method, prepare against that same ready connection, and reattach under the single reservation. */ openPreparedReattachedSession(sessionId: string, prepare: (connection: PooledConnection) => AcpSessionOptions | Promise, onReleased?: () => void): Promise<{ handle: SessionHandle; method: "resume" | "load"; }>; private openReadySession; /** Reopen an existing session and replay its transcript through the router before resolving. */ loadSession(sessionId: string, opts: AcpSessionOptions): Promise; /** Reopen an existing session without transcript replay. */ resumeSession(sessionId: string, opts: AcpSessionOptions): Promise; /** Create a new independent session seeded from an existing session's conversation context. */ forkSession(sourceSessionId: string, opts: AcpSessionOptions): Promise; private rawAgentRequest; private reattachSession; /** Reattach under a reservation already owned by the caller. */ private reattachReadySession; /** session/list on a dedicated connection, gated on the initialize advertisement. */ listSessions(request: ListSessionsRequest, label?: string): Promise; /** session/delete on a dedicated connection, gated on the initialize advertisement. */ deleteSession(request: DeleteSessionRequest, label?: string): Promise; /** Authentication methods advertised in initialize, available without opening a session. */ authMethods(): Promise; /** authenticate has no AgentCapabilities gate; authMethods advertises choices, not method support. */ authenticate(request: AuthenticateRequest, label?: string): Promise; /** providers/list on a dedicated connection, gated on the unstable providers advertisement. */ listProviders(request: ListProvidersRequest, label?: string): Promise; /** providers/set on a dedicated connection, gated on the unstable providers advertisement. */ setProvider(request: SetProviderRequest, label?: string): Promise; /** providers/disable on a dedicated connection, gated on the unstable providers advertisement. */ disableProvider(request: DisableProviderRequest, label?: string): Promise; /** logout on a dedicated connection, gated on agentCapabilities.auth.logout. */ logout(request: LogoutRequest, label?: string): Promise; /** session/prompt on this connection, raced against process death. */ prompt(request: PromptRequest): Promise; /** Driven `_session/steering` extension request. The top-level initialize advertisement is * checked before any wire request. rawAgentRequest preserves the response object instead of * applying an SDK standard-method mapper, and its internal race surfaces process death. */ steerSession(request: SteeringRequest, label?: string): Promise; /** Driven `_session/loaded_turn/query` extension request (the re-attach * arm's authoritative founding-turn classification): asks whether the * loaded session's founding turn is still running at the backend, or * ended while the host was down. Strictly capability-gated on the * initialize `_meta.loadedTurn.supported === true` advertisement — a * backend without the extension rejects before any wire request (the * "same gate" the seam's degradation keys on). */ queryLoadedTurn(sessionId: string, label?: string): Promise; /** session/set_config_option on this connection, raced against process death. */ setSessionConfigOption(request: SetSessionConfigOptionRequest): Promise; /** session/set_mode on this connection, raced against process death. */ setSessionMode(request: SetSessionModeRequest): Promise; /** RAW protocol escape hatch: this makes the full ACP spec reachable (for example * session/set_mode, session/fork, authenticate). Prefer the named wrappers when they exist * because they preserve engine semantics such as accumulation/drain and usage recording; * calling session/prompt here bypasses those paths. */ request(method: Method, params: AgentRequestParamsByMethod[Method], options?: SendRequestOptions): Promise; request(method: string, params?: Params, options?: SendRequestOptions): Promise; /** RAW protocol notification escape hatch. Prefer named wrappers when they exist because * wrapper paths carry engine-specific lifecycle semantics that raw protocol calls do not. */ notify(method: Method, params: AgentNotificationParamsByMethod[Method]): Promise; notify(method: string, params?: Params): Promise; /** Send ACP cancel for one session. SessionHandle owns the grace/close escalation policy. */ cancelSession(sessionId: string): Promise; /** Quarantine a child whose session ignored cancellation. Existing sibling sessions drain; * an already-idle connection disposes immediately, otherwise the final release disposes it. */ quarantineAfterIgnoredCancel(): void; /** * Release a session: move it to teardown-only routing, free the load slot, and best-effort * session/close on the wire (capability-gated, bounded, never fatal). The PROCESS is NOT * killed — it returns to the pool for the next agent() call. */ releaseSession(sessionId: string, keepOpen?: boolean): Promise; /** Snapshot descendants while the ACP parent can still prove their lineage. */ private retainDescendants; /** Synchronously signal retained descendants, verifying Linux PID identity before every kill. */ private killRetainedDescendants; /** Wait until all retained descendants have exited, so pool disposal cannot release them early. */ private waitForRetainedDescendants; /** * Synchronous best-effort force kill for process-exit and bounded shutdown paths. The parent * ACP server gets an isolated process group; retained detached descendants remain reachable * after a graceful parent exit so the host deadline can still tear down their process groups. */ killNow(): void; /** Close the process (pool teardown): end stdin, SIGTERM, escalate to SIGKILL, await exit. */ dispose(): Promise; private disposeOwned; } export declare function isChildCleanupError(error: unknown): boolean; /** * One agent() run's ACP session on a pooled connection. Owns the per-session cwd/schema/policy, * the model-selection state, and the abort wiring. On release() it lets go of the session * WITHOUT killing the pooled process. Implements StructuredSource for the backend's native read. */ export declare class SessionHandle implements StructuredSource { private readonly pooled; readonly sessionId: string; private readonly state; private readonly opts; private readonly onReleased?; private configOptions; private removeAbort; private releasePromise; private abortCancellation; private activeTurn; private resolveReleaseStarted; private readonly releaseStarted; constructor(pooled: PooledConnection, sessionId: string, state: SessionState, configOptions: SessionConfigOption[], opts: AcpSessionOptions, onReleased?: (() => void) | undefined); /** Per-session usage accumulator (read by the runner on BOTH success and error paths). */ get usage(): UsageAccumulator; /** Structured provider metadata observed before the current prompt rejected. */ get providerErrorMetadata(): ProviderErrorMetadata | undefined; /** Diagnostic message/tool history accumulated across this session's run. */ get history(): AgentHistoryEntry[]; /** Assistant text accumulated across the retained session log. */ get text(): string; /** Agent-advertised session mode catalog plus the currently active mode, if supported. */ get modes(): SessionModeState | null | undefined; /** The connection-level initialize response parsed before this session was opened. */ get capabilities(): NegotiatedCapabilities | undefined; /** Stable per-session initialize metadata snapshot used by refs and every event context. */ get initializeMeta(): Readonly> | undefined; /** The agent-advertised session config options in their verbatim ACP wire shapes. */ get advertisedConfigOptions(): SessionConfigOption[]; /** Pass the routed model id straight to the agent. Its catalog and validation are authoritative. */ selectModel(spec: string): Promise; /** Apply authored session config options verbatim in deterministic option-id order. */ setConfigOptions(options: Record | undefined): Promise; /** Set one session config option via the wire method and adopt the echoed catalog. * A boolean value drives a `type: "boolean"` option (the request must carry the type * discriminator on the wire); a string value drives a select option. */ private applyConfigOption; /** Switch the session's operating mode through ACP's strict confinement channel. */ setMode(modeId: string): Promise; /** Send a prompt turn and drain it; returns the final PromptResponse. */ prompt(content: string | ContentBlock[], promptMeta?: Record): Promise; /** * The typed session failure that ended this turn, if any (codex-acp's negotiated extension — * see typed-failures.ts). Two channels, in priority order: * * 1. `PromptResponse._meta` — the TERMINAL failure. Unambiguous: the server put it there * instead of rejecting the request or streaming the provider's prose as assistant output, * so the turn resolved `end_turn` with nothing to show for itself. Always fails the turn. * 2. the asynchronous latch — a failure published on `session_info_update` that this turn * RAISED (not one inherited from an earlier turn). The server does not treat these as * terminal, and neither do we while the turn still produced output: a late unattributed * error must not retroactively fail a turn that answered. It IS applied to a turn that * produced no assistant text, where it is the only account of why — strictly better than * the AGENT_EMPTY_OUTPUT / schema-repair-ladder path that would otherwise run blind. * Assistant text is the whole of this backend's output surface (its structured results are * read off the same stream), so an empty accumulator really does mean nothing came back. * * Usage is recorded before this runs, so a walled turn still reports the tokens it burned. */ private terminalTypedSessionFailure; private typedSessionFailureError; /** Inject content into the currently running prompt turn through the negotiated steering * extension. This deliberately owns no turn state: it does not begin/replace a turn, accumulate * output, record usage, or retry a late `startedNewTurn` outcome. */ steer(content: string | ContentBlock[], promptMeta?: Record): Promise; /** StructuredSource — the latest turn's assistant text. */ currentTurnText(): string; /** The latest turn's assistant text with the §5 chunk joiner: separate * assistant MESSAGES join with "\n\n" (the result fold the runner * returns; consecutive chunks of one message join with ""). */ foldedTurnText(): string; /** StructuredSource — the latest turn's FINAL assistant message (see SessionState). */ finalMessageText(): string; /** StructuredSource — Claude's raw structured_output for the latest turn, if any. */ rawStructuredOutput(): unknown; /** The loaded-session founding-turn observability probe (see * `InteractiveSession.awaitCurrentTurn`): whether the replayed transcript * shows a turn ever started, and the KIND of the trailing content event. * The trailing kind is PROGRESS evidence, never completion by itself — * the seam classifies completion from the LOAD BOUNDARY plus whether * any content update followed the load (see `loadBoundaryState`). * Added for the REPL broker's re-attach arm; additive passthrough to * `SessionState`. */ loadedTurnState(): { hasUserMessage: boolean; trailingContentKind: 'assistant-message' | 'other'; }; /** Mark the load boundary (see `markLoadBoundary` on `SessionState`): * the runner calls this synchronously after the `session/load` response * — the replay is complete at that instant, and any CONTENT update * applied afterwards is live-continuation evidence. */ markLoadBoundary(): void; /** The load-boundary probe (see `loadBoundaryState` on `SessionState`). */ loadBoundaryState(): { marked: boolean; hasUserMessage: boolean; trailingContentKind: 'assistant-message' | 'other'; sawPostLoadContentUpdate: boolean; }; /** The most recent instant a session/update arrived for this session * (the re-attach arm's stream-settled clock). Added for the REPL * broker's re-attach arm; additive passthrough to `SessionState`. */ lastUpdateAtMs(): number; /** Watch the session/update stream (fires after every applied update; * returns the unsubscribe thunk). Added for the REPL broker's re-attach * arm; additive passthrough to `SessionState`. */ subscribeUpdates(listener: () => void): () => void; /** The founding turn's assistant text (the transcript accumulated after * the last user-message boundary). Added for the REPL broker's re-attach * arm; additive passthrough to `SessionState`. */ loadedTurnText(): string; /** The recorded `_session/loaded_turn/ended` terminal state (the * re-attach arm's authoritative completion evidence), or null when a * running founding turn has not ended yet. Added for the REPL broker's * re-attach arm; additive passthrough to `SessionState`. */ loadedTurnEndedState(): { stopReason?: string; error?: { name: string; message: string; }; } | null; /** Watch the loaded-turn-ended channel (fires when the * `_session/loaded_turn/ended` notification arrives — and immediately * when one already arrived). Returns the unsubscribe thunk. Added for * the REPL broker's re-attach arm; additive passthrough to * `SessionState`. */ subscribeLoadedTurnEnded(listener: () => void): () => void; /** Cancel the active turn. A backend that does not settle within the grace window is closed and * its pooled child is quarantined for recycle after sibling sessions drain. */ cancel(): Promise; private cancelAfterAbort; private cancelTurn; private cancelAndEscalate; /** Let go of this session WITHOUT killing the pooled process; idempotent. * `keepOpen` skips the release-time best-effort `session/close` so an agent-persisted * session stays re-openable via session/load|resume (RunOptions.keepSession). */ release(options?: { keepOpen?: boolean; }): Promise; private releaseOwned; } export {}; //# sourceMappingURL=acp-client.d.ts.map