/** * Connection pool: per-alias persistent ssh2 connections with multi-hop jump * support, the acquire / dispose / sweep lifecycle, and the pooled exec path. */ import { Client, type ConnectConfig } from 'ssh2'; import type { ExecResult, SshHostEntry } from '../protocol.ts'; import { type HostStore } from '../store.ts'; /** Default engine knobs. */ export interface EngineOptions { /** Connections idle longer than this are closed (ms). */ idleTimeoutMs?: number; /** SSH handshake timeout (ms). */ connectTimeoutMs?: number; /** Keepalive ping interval (ms). */ keepaliveIntervalMs?: number; /** Cap on captured stdout/stderr bytes per exec (ms). */ maxOutputBytes?: number; /** Default exec timeout (ms). */ defaultExecTimeoutMs?: number; /** Default cluster concurrency. */ defaultMaxWorkers?: number; /** SFTP concurrent channel count for transfers. */ sftpConcurrency?: number; } /** Default engine knobs (applied when an option is omitted). */ export declare const DEFAULTS: Required; /** One pooled connection record. */ export interface PoolRecord { client: Client; /** Jump-chain clients kept alive under the target. */ hops: Client[]; idleAt: number; /** Pinned connections (tunnels) are never swept. */ pinned: boolean; broken: boolean; /** Operations currently running on this connection (sweep guard). */ inFlight: number; } /** * The slice of the engine the pool and exec paths need. The host class * (engine.ts facade) satisfies this structurally. */ export interface PoolEngine { readonly store: HostStore; readonly opts: Required; readonly pool: Map; readonly acquireQueue: Map>; } /** Build the ssh2 connect config for one entry (key read from disk). */ export declare function buildConnectConfig(entry: SshHostEntry, sock: ConnectConfig['sock'] | undefined, opts: Required): ConnectConfig; /** Resolve the ssh2 agent path for 'agent' auth. */ export declare function resolveAgentPath(agentPath?: string): string | undefined; /** Keyboard-interactive handler callback for 2FA / dynamic prompt flow. */ export type KeyboardInteractiveHandler = (name: string, instructions: string, instructionsLang: string, prompts: Array<{ prompt: string; echo: boolean; }>, finish: (responses: string[]) => void) => void; /** Connect one ssh2 client (resolve on ready, reject on error/close). */ export declare function connectClient(config: ConnectConfig, onKeyboardInteractive?: KeyboardInteractiveHandler): Promise; /** Cap captured output at the configured byte budget (marks truncation). */ export declare function appendOutput(target: { text: string; truncated: boolean; }, chunk: Buffer, maxBytes: number): void; /** * Parse an OpenSSH jump hop written as an address: `[user@]host[:port]` * (an IPv6 literal must be bracketed). Returns undefined when the value is not * an address at all. */ export declare function parseJumpSpec(spec: string): { host: string; port?: number; user?: string; } | undefined; /** * Resolve one ProxyJump spec: an alias configured in this plugin wins, and any * other value is read as an OpenSSH address whose credentials come from the * target entry (an ad-hoc hop has no stored auth of its own). */ export declare function resolveHop(engine: PoolEngine, entry: SshHostEntry, spec: string): SshHostEntry; /** * Build one full jump chain for an entry: hop clients connected through in * order, each forwarding a stream to the next destination, ending with the * target client. Shared by the pool and standalone shell sessions. * * A ProxyCommand is the transport that reaches its own host, so the entry's * own command seeds the chain when there is no jump chain, and the first hop's * command is used when the hop carries one. */ export declare function connectChain(engine: PoolEngine, entry: SshHostEntry, onKeyboardInteractive?: KeyboardInteractiveHandler): Promise<{ client: Client; hops: Client[]; }>; /** Connect (or reuse) the pooled chain for one alias; pins nothing. */ export declare function acquire(engine: PoolEngine, alias: string): Promise; /** * Tear down one alias's record. When `record` is given and no longer the * pooled record for the alias (a concurrent acquire replaced it), nothing * is torn down — the connection belongs to someone else now. */ export declare function disposeRecord(engine: PoolEngine, alias: string, record?: PoolRecord): void; /** End one record's client and hop chain (best-effort, safe to repeat). */ export declare function endRecordChain(record: PoolRecord): void; /** Close connections idle beyond the threshold (skips pinned and in-flight). */ export declare function sweepPool(engine: PoolEngine): void; /** * Run `fn` with a live client for `alias`, reconnecting (up to the * attempt budget) when the connection broke mid-flight. */ export declare function withClient(engine: PoolEngine, alias: string, fn: (client: Client) => Promise, attempts?: number): Promise; /** Run one command on `alias` (reusing the pooled connection). */ export declare function execCommand(engine: PoolEngine, alias: string, command: string, timeoutMs?: number): Promise;