/**
 * RelayClient — long-lived WebSocket connection to the Aexol backend's
 * `/agent-connection` endpoint.
 *
 * Responsibilities:
 *  - Open and maintain a single WS to the relay, authenticated with the
 *    machine JWT via `Authorization: Bearer <jwt>`.
 *  - Reply `{kind:"pong"}` to backend `{kind:"ping"}`. Backend closes
 *    `4408 heartbeat-timeout` if it doesn't hear from us within 90s; we
 *    rely on the backend pings rather than emitting our own (single source
 *    of liveness, no jitter on our side).
 *  - **Watchdog timer:** tracks `lastActivityMs` on every received frame /
 *    ping. Every 15 s, if no activity within `WATCHDOG_MS` (120 s = 2×
 *    backend timeout), force-closes the socket (4001 "watchdog-timeout")
 *    and triggers a reconnect. Detects silent backend death (Docker OOM
 *    kill, network partition without TCP RST) where the socket stays open
 *    but no data flows.
 *  - **Pre-reconnect health check:** before opening a new WS, does a quick
 *    HTTP GET to `backendUrl/health` with a 5 s timeout. If the backend is
 *    unhealthy or unreachable, skips the WS attempt and re-schedules. This
 *    avoids a slow TCP connect timeout (up to 75 s on some platforms) when
 *    the backend is down.
 *  - On unexpected close, reconnect forever with exponential backoff +
 *    ±20% jitter, capped at 30s. There is no "give up" state — if the
 *    machine is offline for hours, we just keep trying. Operators can
 *    `Ctrl-C` to stop.
 *  - Buffer outbound frames in a small queue while the socket is closed.
 *    Capped at 100 frames; oldest is dropped on overflow.
 *
 * Events (typed via `RelayClientEvents`):
 *  - `open`            - connected and welcomed
 *  - `welcome`         - backend `{kind:"welcome"}` frame
 *  - `frame`           - any non-pong, non-welcome frame
 *  - `close`           - socket closed (we may reconnect after)
 *  - `error`           - non-fatal error (parse failure, send failure)
 *  - `reconnect-scheduled` - emitted with the delay in ms before each retry
 *  - `auth-failed`     - WS upgrade rejected with 401/403 (caller should re-register)
 *
 * Threading model: `node:events` is single-threaded; all callbacks fire on
 * the same event loop turn. Listeners must be cheap.
 */
import { EventEmitter } from "node:events";
import WebSocket from "ws";
export interface RelayClientOptions {
    /** Full WS URL, e.g. wss://api.aexol.ai/agent-connection */
    relayUrl: string;
    /** Bearer JWT for the WS handshake. */
    machineJwt: string;
    /**
     * Backend HTTP(S) base URL for the pre-reconnect health check
     * (`GET <backendUrl>/health`). When omitted, the health check is
     * skipped (tests, local-only deploys without a /health endpoint).
     */
    backendUrl?: string;
    /**
     * Override the WebSocket constructor (tests inject a fake or a wrapper).
     * Default: `ws` package's `WebSocket`.
     */
    webSocketImpl?: typeof WebSocket;
    /**
     * Override `fetch` (tests inject a stub for health checks).
     * Default: global `fetch`.
     */
    fetchImpl?: typeof fetch;
    /**
     * Optional logger. Defaults to console. Tests pass a noop.
     */
    logger?: Pick<Console, "log" | "warn" | "error">;
    /**
     * Override `process.exit` for tests. Called when the backend evicts us
     * with `replaced-by-newer-registration` to avoid a reconnect ping-pong.
     * Default: `process.exit`.
     */
    exit?: (code: number) => never;
}
/** Strongly-typed event surface. Used for ergonomic `.on(...)` callsites. */
export interface RelayFrame {
    /** Echoed-out shape; concrete type varies by Batch. */
    kind?: string;
    [key: string]: unknown;
}
export declare class RelayClient extends EventEmitter {
    private readonly relayUrl;
    private readonly machineJwt;
    private readonly backendUrl;
    private readonly WS;
    private readonly fetchImpl;
    private readonly logger;
    private readonly exit;
    private ws;
    private disposed;
    private reconnectAttempt;
    private reconnectTimer;
    private sendQueue;
    /** Set true when the WS error handler sees an HTTP 401/403 upgrade rejection. */
    private authFailed;
    /** Timestamp of last received frame / ping — drives the watchdog. */
    private lastActivityMs;
    private watchdogTimer;
    constructor(opts: RelayClientOptions);
    /** Open the connection. Idempotent — calling twice is a no-op. */
    connect(): void;
    /**
     * Send a frame. If the socket is open it goes immediately; otherwise
     * it's queued (capped at 100 — oldest dropped) and flushed on the next
     * successful `open`.
     *
     * Returns `true` if the frame was sent or queued, `false` if it was
     * dropped due to dispose.
     */
    send(frame: RelayFrame | string): boolean;
    /**
     * Close the connection and stop reconnecting. After dispose, this client
     * is dead — create a new one to reconnect.
     */
    dispose(): void;
    private openSocket;
    private startWatchdog;
    private stopWatchdog;
    /**
     * Pre-reconnect health check: quick HTTP GET to `<backendUrl>/health`.
     * Returns true when the backend is reachable (or when no backendUrl is
     * configured, which skips the check entirely). Returns false when the
     * backend is unhealthy/down — the caller should re-schedule.
     */
    private healthCheck;
    private scheduleReconnect;
}
//# sourceMappingURL=client.d.ts.map