import { Session, User } from '@supabase/supabase-js';
/**
 * Strip NUL characters (U+0000) from strings and object keys — Postgres rejects
 * them in jsonb and text (22P05). Walks the structure rather than
 * round-tripping JSON, which would also match escape text in legitimate content.
 */
export declare function stripNullBytes<T>(value: T): T;
export interface AuthSession {
    access_token: string;
    refresh_token: string | null;
    device_id?: string;
}
interface DeviceData {
    user_id: string;
    device_name: string;
    capabilities: any;
    status: string;
    last_seen: string;
}
export declare class RemoteChannel {
    private client;
    private channel;
    /** Legacy listener, on its own public channel so a private-channel auth
     * failure can't take both transports down. Removed at the flip (009). */
    private legacyChannel;
    private heartbeatInterval;
    private connectionCheckInterval;
    /** Device the heartbeat timer maintains; null = stopped, so re-arm is inert. */
    private heartbeatDeviceId;
    private statusWriteChain;
    /** Tokens from the last setSession / TOKEN_REFRESHED, for setOffline(). */
    private lastKnownSession;
    /** Set by unsubscribe(): suppresses status/heartbeat writes so they can't
     * land after setOffline()'s durable write. */
    private shuttingDown;
    private deviceId;
    private deviceName;
    private onToolCall;
    private authListenerRegistered;
    /** False when presence publishing failed on an otherwise healthy channel;
     * the health check retries, since SUBSCRIBED won't fire again. */
    private presenceTracked;
    /** Last capability value written (null = never), to avoid redundant writes. */
    private transportCapableWritten;
    /** Re-entrancy guard: on a wedged socket each track() buffers for the full
     * 10s push timeout, so 10s health ticks would stack pushes. */
    private isTrackingPresence;
    private lastDeviceStatus;
    private lastChannelState;
    private reconnectAttempt;
    private isRecreatingChannel;
    private joiningSince;
    private _user;
    get user(): User | null;
    initialize(url: string, key: string): void;
    setSession(session: AuthSession): Promise<{
        error: any;
    }>;
    getSession(): Promise<{
        data: {
            session: Session | null;
        };
        error: any;
    }>;
    findDevice(deviceId: string): Promise<{
        id: any;
        device_name: any;
    } | null>;
    updateDevice(deviceId: string, updates: any): Promise<{
        data: any[] | null;
        error: import("@supabase/postgrest-js").PostgrestError | null;
    }>;
    createDevice(deviceData: DeviceData): Promise<{
        data: any;
        error: null;
    }>;
    registerDevice(capabilities: any, currentDeviceId: string | undefined, deviceName: string, onToolCall: (payload: any) => void): Promise<void>;
    /**
     * Publish presence, retrying a non-'ok' result — track() resolves with a
     * status rather than rejecting, and absent presence reads as offline on the
     * server. `presenceTracked` lets the health check retry later.
     */
    private trackPresenceWithRetry;
    private trackPresenceInner;
    /**
     * The complete `capabilities` JSONB value. One place only: every write
     * replaces the whole column, so a second literal would silently drop keys.
     */
    private capabilitiesPayload;
    /**
     * Advertise (or withdraw) the broadcast capability. Only true while genuinely
     * reachable that way — the server uses it to pick a transport, to read absent
     * presence as offline, and to choose the sweep tier, so every change must
     * re-arm the heartbeat.
     */
    private setTransportCapable;
    /**
     * Legacy postgres_changes listener on its own public channel. Best-effort:
     * failures are logged, never thrown. Removed at the flip (009).
     */
    private createLegacyChannel;
    /** Tear down the legacy channel (best effort). */
    private removeLegacyChannel;
    /** Create and subscribe the private channel (initial join and recreation). */
    private createChannel;
    /** Hand a call to device.ts, observing the rejection — the handler is async
     * and an unhandled rejection terminates the process. */
    private dispatchToolCall;
    /**
     * Handle a 'new_call' doorbell. It carries ids only; the row is fetched by
     * primary key and fed through the same handler as a postgres_changes
     * payload, so device.ts stays transport-agnostic.
     */
    private onDoorbell;
    /**
     * Tell the server a result row is written. Fire-and-forget: a failed send
     * just falls back to the server's 10s recovery poll. MUST run only after
     * updateCallResult() resolves, so the server's fetch-by-id sees a terminal row.
     */
    notifyResult(callId: string): Promise<void>;
    /**
     * Compact connection state for logs — e.g. "socket=open(1) ch=errored attempt=3".
     * readyState 1=OPEN (a 1 while joins keep failing = a half-open socket being reused),
     * 3=CLOSED, '-'=no socket. Reads realtime-js internals defensively; never throws.
     */
    private connState;
    /**
     * Check if channel is connected, recreate if not.
     */
    private checkConnectionHealth;
    /**
     * Run an async op but reject if it doesn't settle within `ms`, so a hung await
     * can't leave isRecreatingChannel stuck true and disable the watchdog. Mirrors
     * closeWithTimeout() in desktop-commander-integration.ts.
     */
    private sleep;
    /**
     * Block until realtime-js has left the 'disconnecting' state it enters on
     * disconnect(), so the next subscribe() actually dials a socket instead of
     * hitting connect()'s early return. Bounded either way — worst case we cost
     * a recreate SOCKET_SETTLE_MAX_MS.
     */
    private waitForSocketSettled;
    private withTimeout;
    /**
     * Recreate the channel by destroying old one and creating fresh instance.
     */
    private recreateChannel;
    /**
     * Claim a call. True only when THIS update flipped the row pending ->
     * executing, which is what makes dual delivery safe across processes.
     * .eq('status','pending') makes it conditional; .select('id') makes the
     * result observable. On a transient DB error it returns true (execute
     * anyway), matching prior behaviour — so device.ts's in-memory guard is what
     * actually guarantees exactly-once within a process.
     */
    markCallExecuting(callId: string): Promise<boolean>;
    updateCallResult(callId: string, status: string, result?: any, errorMessage?: string | null): Promise<void>;
    /**
     * Reachable by SOME transport — the private channel or, during the
     * transition, the independent legacy one. Gates the heartbeat and `status`:
     * asking only about the private channel starves last_seen for a device whose
     * legacy channel is fine, and the 45s sweep then blacks it out.
     * Collapses to a single check at the flip (009).
     */
    private isReachable;
    /**
     * Set `status` from actual reachability. `status` is transport-agnostic (the
     * server filters on it), so it must not follow one channel's health — the
     * private channel's error path re-fires on every rejoin and would oscillate
     * the row against the heartbeat. Same predicate as the heartbeat gate.
     */
    private syncReachabilityStatus;
    /**
     * Serialize the channel-callback status writes. They fire from un-awaited
     * callbacks, and inside recreateChannel() a teardown's 'offline' and the
     * fresh join's 'online' land ~100-300ms apart — unordered, 'offline' can win
     * and leave a healthy device undispatchable until the next heartbeat.
     *
     * Not the single writer: updateHeartbeat, registerDevice and setOffline's
     * subprocess write status directly, so this is not total ordering.
     */
    private queueStatusWrite;
    /**
     * Heartbeat cadence for the tier this device is CURRENTLY in. Follows the
     * capability flag (what the server actually tiers its sweep on), not the
     * build — see LEGACY_HEARTBEAT_INTERVAL.
     */
    private heartbeatIntervalMs;
    updateHeartbeat(deviceId: string): Promise<void>;
    startHeartbeat(deviceId: string): void;
    /** Arm (or re-arm) the last_seen timer at the current tier's cadence. */
    private scheduleHeartbeat;
    stopHeartbeat(): void;
    setOnlineStatus(deviceId: string, status: 'online' | 'offline'): Promise<void>;
    setOffline(deviceId: string | undefined): Promise<void>;
    unsubscribe(): Promise<void>;
}
export {};
