/**
 * A minimal Discord gateway client (#680): the inbound half of the Discord integration, which
 * until now was outbound-only — a webhook `POST` can notify, but it cannot read a reply (#627).
 *
 * Deliberately hand-rolled over the global `WebSocket` rather than pulling in `discord.js`. The
 * package has three runtime dependencies and builds everything else on node builtins behind
 * injectable seams; a client library for the handful of opcodes below would be the largest
 * dependency in the package by an order of magnitude.
 *
 * Implements only what a chat bot needs: identify, heartbeat, resume, and message events.
 */
/** Gateway opcodes we act on. */
export declare const OP: {
    readonly dispatch: 0;
    readonly heartbeat: 1;
    readonly identify: 2;
    readonly resume: 6;
    readonly reconnect: 7;
    readonly invalidSession: 9;
    readonly hello: 10;
    readonly heartbeatAck: 11;
};
/**
 * Gateway intents. `MESSAGE_CONTENT` is privileged: it must be enabled on the application in
 * Discord's developer portal, or the gateway connects and every message arrives with an empty
 * `content`. That failure is silent, which is why {@link DiscordGateway} logs it explicitly.
 */
export declare const INTENTS: {
    readonly guildMessages: number;
    readonly directMessages: number;
    readonly messageContent: number;
};
/** The intents a chat bot needs: messages in channels and DMs, plus their text. */
export declare const CHAT_INTENTS: number;
/**
 * The socket seam. Mirrors the slice of `WebSocket` this uses, so a test drives the protocol
 * with a fake and no network — the same shape the watchers use for `fetch`.
 */
export interface GatewaySocket {
    send(data: string): void;
    close(): void;
    onMessage(handler: (data: string) => void): void;
    onClose(handler: () => void): void;
    onError(handler: (err: unknown) => void): void;
}
/** Opens a {@link GatewaySocket} for a url. */
export type SocketFactory = (url: string) => GatewaySocket;
/** A cancellable timer, so heartbeats are deterministic under test. */
export interface Timer {
    stop(): void;
}
/** Schedules `fn` every `ms`. Default wraps `setInterval` and unrefs it. */
export type IntervalFactory = (fn: () => void, ms: number) => Timer;
/** Schedules `fn` once after `ms`. Default wraps `setTimeout` and unrefs it. */
export type DelayFactory = (fn: () => void, ms: number) => Timer;
/** One inbound chat message, narrowed to what routing needs. */
export interface DiscordMessage {
    id: string;
    channelId: string;
    /** Message text. Empty when the privileged MESSAGE_CONTENT intent is not enabled. */
    content: string;
    authorId: string;
    authorName: string;
    /** Whether the author is a bot (including us): never act on these, or two bots loop forever. */
    fromBot: boolean;
    /** Set when the message replies to another, used to thread an answer back to its gate. */
    replyToId?: string;
}
/** What {@link DiscordGateway} reports to its owner. */
export interface GatewayHandlers {
    onMessage(message: DiscordMessage): void;
    /** Connected and identified; carries our own user id so we can ignore our own messages. */
    onReady?(selfId: string): void;
    /** Non-fatal diagnostics (a failed resume, a missing intent). */
    onLog?(message: string): void;
}
/** Injectable seams for {@link DiscordGateway}. */
export interface GatewayDeps {
    socket?: SocketFactory;
    interval?: IntervalFactory;
    delay?: DelayFactory;
    url?: string;
}
/**
 * A connected Discord bot session. Owns the socket, the heartbeat, and the resume state; hands
 * every chat message to its {@link GatewayHandlers}.
 *
 * Errors are swallowed into `onLog` rather than thrown: a notifier must never take the daemon
 * down, which is the same contract the intervention/activity watchers follow.
 */
export declare class DiscordGateway {
    private readonly token;
    private readonly handlers;
    private readonly deps;
    private socket;
    private heartbeat;
    private sequence;
    private sessionId;
    private resumeUrl;
    private acked;
    private selfId;
    /** Set by {@link stop}, so a socket closing on our own terms never reconnects. */
    private stopped;
    /** Consecutive reconnects, for the backoff. Reset once a connection actually works. */
    private attempts;
    private pendingReconnect;
    constructor(token: string, handlers: GatewayHandlers, deps?: GatewayDeps);
    /** Open the connection and identify. Safe to call once; use {@link stop} to end it. */
    connect(): void;
    /**
     * Close the connection for good. This is what takes the bot offline on `Ctrl+C` (#680): the
     * daemon calls it from its shutdown block, and no reconnect follows.
     */
    stop(): void;
    /** Our own user id once READY has landed. */
    get userId(): string | undefined;
    private open;
    /**
     * A closed socket we did not close ourselves: resume if we can, else identify fresh.
     *
     * Backed off, and that is not a nicety: a connection that fails immediately (offline, a bad
     * token) closes as fast as it opens, so reconnecting inline is a tight loop that pins a core
     * and gets the bot rate-limited. Doubles to a cap, and resets once a connection works.
     */
    private reopen;
    private receive;
    private onHello;
    private identify;
    private resume;
    private sendHeartbeat;
    private onDispatch;
    private send;
    private log;
}
/** Narrow a MESSAGE_CREATE payload; `undefined` when it is not a shape we can use. */
export declare function parseMessage(data: unknown): DiscordMessage | undefined;
//# sourceMappingURL=gateway.d.ts.map