import * as net from 'net';
import { BrowserService } from './service.js';
import type { IPCRequest, IPCResponse } from './types.js';
export interface IPCRequestOptions {
    autoStartDaemon?: boolean;
    /**
     * Opt-in client-side deadline in ms — most IPC actions (browser automation,
     * long-running recordings) have no fixed budget and must NOT get one by
     * default. Set explicitly for a request that genuinely has one, e.g.
     * `request-self-update`, whose handler answers immediately (it kicks the
     * install off in the background — see `triggerSelfUpdateInBackground`), so its
     * client deadline (`DAEMON_SELF_UPDATE_TRIGGER_TIMEOUT_MS`) is a short
     * "did the daemon accept the trigger?" bound, not a wait on the install.
     */
    timeoutMs?: number;
}
export declare class BrowserServiceNotRunningError extends Error {
    constructor();
}
export declare function formatBrowserServiceNotRunningError(daemonRunning?: boolean): string;
export declare function getSocketPath(): string;
/** Is the daemon reachable? A real connect probe on every platform — a socket
 * file existing on disk is not proof a daemon is listening on it. */
export declare function isBrowserServiceReachable(): Promise<boolean>;
/**
 * Is a *reachable* daemon actually responsive, or is its event loop wedged
 * (PHNX-3411)? Retries {@link probeBrowserServiceResponsive} up to
 * {@link RESPONSIVENESS_PROBE_ATTEMPTS} times so a single transient miss (a GC
 * pause) never condemns a healthy daemon; returns true as soon as any attempt
 * gets a reply, and false only when every attempt fails.
 */
export declare function isBrowserServiceResponsive(endpoint?: string, attempts?: number): Promise<boolean>;
/**
 * Wait until the browser service is genuinely reachable, or throw.
 *
 * Re-probes across the whole window rather than latching on the first accept, so
 * it survives an IPC-server restart that happens mid-wait (PHNX-3289): a restart
 * just resets the consecutive-accept counter, and the loop keeps going until the
 * daemon is *stably* up or the deadline passes. Bounded and fail-loud — a daemon
 * that never comes up throws a message naming the endpoint and the budget, never
 * a silent hang.
 */
export declare function waitForBrowserService(_socketPath: string, timeoutMs?: number): Promise<void>;
/** Outcome of {@link stopBrowserService} — what the service-scoped stop did. */
export interface BrowserServiceStopResult {
    /** The browser IPC service was reachable before the stop was requested. */
    wasRunning: boolean;
    /** The shared daemon stayed running while only browser IPC was stopped. */
    daemonRunning: boolean;
    /** A leftover `browser.sock` file was unlinked (POSIX only). */
    socketCleared: boolean;
}
/**
 * Stop only the daemon-hosted browser IPC service so a subsequent `start` comes
 * up clean (PHNX-3605). The short-lived browser client persists the
 * `browser-ipc` service off and asks the daemon to apply that transition over
 * its existing SIGHUP control path. It never stops or restarts the shared
 * supervisor, so routines, usage sync, secrets, and every sibling service keep
 * their process and in-flight work.
 *
 * Once the endpoint is quiet, a stale socket left by a hard-crashed browser
 * service is removed. If the daemon event loop itself is wedged and cannot
 * consume SIGHUP, the endpoint remains reachable and this fails loud with the
 * deliberate operator-owned whole-daemon restart command; the client never
 * escalates into that process-wide action itself.
 */
export declare function stopBrowserService(): Promise<BrowserServiceStopResult>;
/**
 * Remove a leftover browser socket FILE, but only when nothing is listening on
 * it — re-probing liveness IMMEDIATELY before the unlink to close the TOCTOU
 * window (PHNX-3289 review). Between {@link stopBrowserService}'s quiesce loop
 * deciding the endpoint was unreachable and this unlink, a concurrent
 * `browser start` could bind a NEW listener on the same path; an unconditional
 * unlink would then delete a LIVE daemon's socket — the exact two-servers orphan
 * this function exists to prevent. A listener seen here means the wedge is already
 * resolved (a fresh daemon owns the path), so its socket is left intact. Returns
 * true only when a genuinely dead file was removed.
 */
export declare function clearDeadSocketFile(endpoint: string, socketPath: string): Promise<boolean>;
/**
 * One long-lived connection to the existing browser service. Requests are
 * serialized so the daemon's newline-delimited responses always map to the
 * caller that produced them, while the process and socket stay warm between
 * actions.
 */
export declare class BrowserIPCConnection {
    private readonly socket;
    private buffer;
    private pending;
    private tail;
    private closed;
    constructor(socket: net.Socket);
    request(request: IPCRequest): Promise<IPCResponse>;
    close(): Promise<void>;
    private requestOnce;
    private handleData;
    private fail;
}
export declare class BrowserIPCServer {
    private server;
    /** Filesystem identity of the POSIX binding this server created. */
    private socketIdentity;
    private service;
    /** Live client connections, so {@link stop} can end them rather than wait. */
    private connections;
    /** In-flight stop, so a second SIGTERM awaits the same close, never skips it. */
    private stopping;
    constructor(service: BrowserService);
    start(): Promise<void>;
    /**
     * Stop accepting, release the binding, and only then resolve.
     *
     * RUSH-2421: this used to call `server.close()` and move on. `close()` is
     * asynchronous — it stops accepting immediately but the binding is not
     * actually released until the `'close'` event fires — so `stop()` resolved
     * while the socket (or named pipe) was still held. The daemon's
     * `handleShutdown` awaits this before it exits, and a successor's
     * `claimDaemonInstance` waits for that exit as its proof the predecessor's
     * resources are free (SING-11). Resolving early made that proof false: the
     * newcomer could bind before the incumbent's binding was gone, which is the
     * two-servers-on-one-socket orphan the eviction protocol exists to prevent.
     *
     * The first version of this waited out a timeout instead, and that made every
     * graceful daemon stop escalate to `killTree`: `close()` does not complete
     * while a connection is open, browser clients hold one open on purpose, and
     * the timeout was set equal to the daemon's own SIGTERM grace window
     * (`STOP_GRACE_MS`) — so `handleShutdown` was still inside `stop()` when
     * `stopDaemon` gave up waiting and killed it. So we END the connections
     * rather than wait for them: `close()` stops accepting, destroying the
     * tracked sockets lets it complete, and the timeout is only a backstop for a
     * socket that will not die, deliberately far below `STOP_GRACE_MS`.
     */
    stop(): Promise<void>;
    private doStop;
    /** Release the listening binding, resolving only once it is genuinely gone. */
    private closeServer;
    /**
     * Resolve `request.task` from caller identity when omitted. Page verbs may
     * create; done/stop never create. Returns an error response to short-circuit
     * the action, or undefined when the request is ready (task filled in).
     */
    private bindTask;
    private handleRequest;
}
/**
 * Decide whether a running daemon is stale enough to name an operator refresh.
 *
 * FORWARD ONLY: recommend a refresh when the daemon is *older* than this CLI.
 * An older CLI rides a newer daemon without noise. A client never performs the
 * refresh itself — two installs sharing one daemon dir (keyed off $HOME) must
 * not flap the daemon or interrupt one another's hosted services.
 *
 * `undefined`/`'unknown'` means the daemon is too old to answer the `version`
 * action reliably — don't recommend an action on that ambiguous signal.
 *
 * When numeric compare cannot order two distinct strings (e.g. two
 * `0.0.0-dev.*` builds), surface the mismatch without deciding either client
 * owns the shared process.
 */
export declare function shouldRecommendDaemonRefresh(daemonVersion: string | undefined, clientVersion: string): boolean;
/**
 * Reconcile the running daemon's version with ours without evicting it. If the
 * daemon is serving stale code, ask the daemon to run its OWN self-update path
 * (`request-self-update`, same fail-closed check/install/verify/exit
 * SelfUpdateService's periodic tick runs, self-update-service.ts) rather than
 * telling a human to run `agents daemon restart` by hand. A browser client
 * still owns neither the shared supervisor nor its sibling services, so
 * version skew can never be permission for THIS client to stop or restart
 * that process directly (PHNX-3605 holds) — it only asks the daemon to run a
 * path the daemon already runs on its own schedule, which only exits once the
 * daemon itself has verified the new version is good. If self-update declines
 * (dev build, shadowed install, already current, or a real failure) this
 * degrades to the same "nothing changed" outcome PHNX-3605 shipped.
 */
export declare function reconcileDaemonVersion(): Promise<void>;
/** Test-only: `reconcileDaemonVersion` runs at most once per real process — this clears that latch so a test file can drive more than one branch. */
export declare function resetVersionReconciliationForTest(): void;
export declare function sendIPCRequest(request: IPCRequest, opts?: IPCRequestOptions): Promise<IPCResponse>;
/**
 * Fill actor / launchId / sessionId from the calling process when the request
 * left them blank. Explicit values on the request always win.
 */
export declare function stampCallerIdentity(request: IPCRequest, env?: NodeJS.ProcessEnv): IPCRequest;
/**
 * Open one connection that can serve many browser operations. This uses the
 * same daemon readiness and version-reconciliation path as sendIPCRequest;
 * only the client process and IPC socket lifetime differ.
 */
export declare function connectBrowserIPC(opts?: IPCRequestOptions): Promise<BrowserIPCConnection>;
/**
 * Exported (rather than kept module-private) so `ipc.test.ts` can drive the
 * `opts.timeoutMs` client-side deadline directly against a raw `net.Server`
 * test double that never responds — the hermetic way to prove the timeout
 * actually fires without needing a slow real IPC action to provoke it.
 */
export declare function sendRawIPCRequest(request: IPCRequest, opts?: IPCRequestOptions): Promise<IPCResponse>;
