framework
Version:
The (AI) Framework: turnkey, zero-config AI orchestration that wraps a coding-agent CLI (Claude Code) as a black box and takes you from an idea to a running app. Vite for AI.
114 lines • 5.13 kB
TypeScript
/**
* 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