/**
 * The agent's browser, streamed to a human (#802, part of #609).
 *
 * The browser hand-off gate (#796) parks an agent and asks someone to deal with a login wall or a
 * captcha. The browser it is parked on is headless and owned by the agent (#793), so there is
 * nothing for that person to click. This serves it: the latest screencast frame as MJPEG, and
 * clicks/keys back in over POST.
 *
 * Why the agent hosts this rather than the dashboard driving Chrome directly: Chrome refuses
 * DevTools socket connections carrying an `Origin` header unless launched with
 * `--remote-allow-origins`, and opening that up would let any page the user happens to visit
 * drive the agent's browser. The debug port stays unreachable from the web; this bridge is the
 * only way in.
 *
 * Why MJPEG rather than a WebSocket: an `<img>` renders `multipart/x-mixed-replace` natively
 * and input is a plain POST, so the dashboard needs no client library and the framework needs
 * no new dependency — Node's global WebSocket is enough to talk to Chrome.
 */
export interface BrowserStream {
    /** Where the dashboard points an `<img>` (`/stream`) and posts input (`/input`). */
    url: string;
    /** The loopback port {@link url} is on, published on the agent's log so the daemon can proxy it (#813). */
    port: number;
    /** Stop streaming and close the server. Safe to call twice. */
    close(): Promise<void>;
}
/** One page Chrome is showing, from `/json/list`. */
export interface CdpPageTarget {
    id: string;
    type: string;
    url: string;
    webSocketDebuggerUrl?: string;
}
/**
 * The page a human should be looking at: the agent's current one.
 *
 * Chrome lists targets most-recently-used first, so the first `page` is the one the agent is
 * working in. Picking by position is what keeps the pane from going blind when the agent opens
 * a tab — the failure the spike hit. Ignores targets with no socket (a crashed or detached
 * tab) rather than returning something unusable.
 */
export declare function pickActivePage(targets: readonly CdpPageTarget[]): CdpPageTarget | undefined;
/** The input a human can send back through the pane. Coordinates are in page pixels. */
export type BrowserInput = {
    type: 'click';
    x: number;
    y: number;
} | {
    type: 'key';
    text: string;
} | {
    type: 'scroll';
    x: number;
    y: number;
    deltaY: number;
} | {
    type: 'navigate';
    url: string;
};
/** A CDP call the bridge makes on the human's behalf. */
export interface CdpCall {
    method: string;
    params: Record<string, unknown>;
}
/**
 * The CDP calls one input maps to, or `[]` for anything unrecognized — a malformed POST must
 * never reach Chrome. A click is press + release (Chrome ignores a lone `mousePressed`), and
 * text goes through `insertText` so it types the character rather than a key code, which is
 * what makes non-ASCII and password managers behave.
 */
export declare function inputToCdp(input: BrowserInput): CdpCall[];
/** The MJPEG part header for one frame. */
export declare function framePart(boundary: string, jpeg: Buffer): Buffer;
/** What the bridge needs from a CDP connection, so a test can stand in for Chrome. */
export interface CdpSession {
    send(method: string, params?: Record<string, unknown>): Promise<unknown>;
    on(event: 'Page.screencastFrame', handler: (params: {
        data: string;
        sessionId: number;
    }) => void): void;
    close(): void;
}
/** How the bridge reaches a page. Injectable: the real one speaks WebSocket to Chrome. */
export type CdpConnect = (webSocketDebuggerUrl: string) => Promise<CdpSession>;
/**
 * Start the bridge. Returns undefined when Chrome has no page to stream — the caller carries
 * on without a pane rather than failing the agent.
 *
 * The stream is bound to loopback explicitly: the frames can contain whatever the human is
 * typing, including a password, so this must not be reachable from the network. For the same
 * reason no frame is ever written to disk or into the agent's event log.
 */
export declare function startBrowserStream(opts: {
    browserUrl: string;
    connect: CdpConnect;
    listTargets?: (browserUrl: string) => Promise<CdpPageTarget[]>;
    /** How often to check whether the agent moved to another tab. 0 disables following. */
    followIntervalMs?: number;
    /** How often to re-send the newest frame so a still page still paints (#818). */
    repeatIntervalMs?: number;
    /**
     * The page the human would be looking at changed (#1455 item 6b): fired for the first real
     * (http/https) page and on every change after — a navigation in place or a followed tab
     * switch. Never fired for about:blank or chrome:// (the browser idling is not the agent
     * showing something), and never twice for the same URL in a row.
     */
    onPage?: (url: string) => void;
}): Promise<BrowserStream | undefined>;
/**
 * Talk CDP to Chrome over its debugger socket. Node's global WebSocket is enough, which is
 * what keeps this dependency-free.
 */
export declare const connectCdp: CdpConnect;
//# sourceMappingURL=browser-stream.d.ts.map