/** * Managed-Chrome acquisition — attach when a Chrome already owns the profile, launch otherwise. * * Ported mechanism-for-mechanism from mirasim's simulator chrome provider, where every step was * earned by a measured failure: * * - `--remote-debugging-port=0`: Chrome only WRITES `DevToolsActivePort` into the profile when it * picks the port itself (with an explicit port the file never appears). That file is what lets * the next caller ATTACH instead of launching — a launch costs ~1.6s and ~1GB across 8 * processes, an attach costs one HTTP round trip. * - Adopt with a SHORT deadline: a browser left behind by a dead session, whose renderer the * system has since frozen, still answers the HTTP ping and then hangs the CDP handshake. * Waiting it out was measured at thirty seconds (two stacked CDP timeouts); replacing it is * about one — and thirty seconds is long enough that the agent concludes the browser is gone. * - Exit-watcher on the port poll: a Chrome that refuses the profile EXITS within a few hundred * milliseconds, and the poll would spend its whole deadline waiting for a file nobody will * write. Noticing the exit turns a 15s dead wait into an immediate, accurate failure. * - Kill by identity: the only teardown that works for a browser launched by an earlier, dead * process is SIGTERM to the pids whose own command line names our `--user-data-dir`. Never * anything broader, so the user's own Chrome is unreachable by construction. * * The acquired browser deliberately OUTLIVES this process: the profile keeps logins, the * published port lets the next session adopt it, and the endpoint file below lets a host * (e.g. mirasim's preview) find it read-only. Callers disconnect; they do not close. */ import { type Browser, type BrowserContext, type Page } from "playwright-core"; export declare function defaultProfileDir(): string; /** * Is the profile the USER'S OWN browser rather than our disposable device? * * A host can point `PI_GUI_PROFILE_DIR` at the user's real Chrome data so the agent inherits * their logins and extensions (mirasim calls this browser takeover). Everything in this file * otherwise assumes the browser is ours to kill, move and empty — assumptions that turn into * damage the moment the profile is theirs. With this set: * * - we NEVER kill by profile dir (that SIGTERM would land on the browser they are using); * - a launch we perform leaves its window where they can see it, and only the window the * agent opens for itself is parked off-screen (see the driver's own window); * - an unusable endpoint is an error, not something to clear away and replace. * * Set by the host at spawn; never inferred, because guessing wrong in this direction destroys * a window someone was working in. */ export declare function isAttachedProfile(): boolean; /** Chrome binary: explicit env, then platform install paths, then PATH names. */ export declare function findChrome(): string | null; /** The port an already-running Chrome published into its profile, or null. */ export declare function readDevToolsPort(profileDir: string): number | null; /** Cheap liveness probe for a port we did not spawn ourselves. */ export declare function pingDevTools(port: number, timeoutMs?: number): Promise; /** * Open a blank tab through the CDP HTTP endpoint, reviving a browser with zero page targets. * * ONLY for an EXTERNALLY-attached endpoint (`--cdp`, `PI_GUI_CDP_URL`, the conventional :9222): * those are dev/eval targets that a caller pointed us at on purpose, where an emptied browser * is a nuisance to route around, not a decision to respect. The MANAGED browser deliberately * does not use this — there, zero page targets means the user closed the window, and reopening * it would be us overruling them (see {@link hasPageTarget}). */ export declare function openBlankTab(cdpUrl: string): Promise; /** * Does this endpoint have a real PAGE to drive? * * "The endpoint answers" is NOT "the browser is usable": closing the last window leaves the * process alive and `/json/version` answering, with only `background_page` and `service_worker` * targets left (measured). A user who closed the window has closed the browser as far as * everyone is concerned, so this — not the ping — is the liveness question. * * We used to answer it by opening a blank tab through `PUT /json/new`, inherited from a * predecessor that treated an emptied browser as worth reviving. That reopened the very window * the user had just closed, which is why the agent looked like it was ignoring them. */ export declare function hasPageTarget(cdpUrl: string): Promise; /** * Pids of browser processes whose `--user-data-dir` is exactly ours. The profile directory is * the only identity that survives a restart of THIS process (the child handle is gone whenever * we adopted). Renderer/GPU helpers (`--type=`) are skipped — signalling them is a storm. */ export declare function browserPids(profileDir: string): Promise; /** Last-resort teardown by identity: SIGTERM, a beat to die, SIGKILL any survivor. */ export declare function killByProfileDir(profileDir: string): Promise; /** * Where the running managed browser is published, so a host process (mirasim's preview, a * debugger) can find it without asking us: `~/.pi-gui/run//endpoint.json`. * Modelled on Chrome's own `DevToolsActivePort` — a file, because a stale file is a harmless * wrong answer the reader re-verifies with one ping, while a dead socket is a hang. */ /** * The user's "I want to look at it" flag, as a FILE beside the endpoint. * * The browser is a background device by contract: it lives off-screen and never takes the * screen or the keyboard on its own. Exactly one thing overrides that — the user clicking the * preview panel — and that click happens in a DIFFERENT process (the host's UI). A file is the * whole handshake: the host creates it on reveal and removes it on park, and this process only * ever reads it, one `existsSync` before it would otherwise push the window back off-screen. * While it exists, the window belongs to the user and we do not touch its position. */ export declare function revealFlagFor(profileDir: string): string; /** True while the user has asked to see the browser (see {@link revealFlagFor}). */ export declare function userWantsVisible(profileDir: string): boolean; export declare function endpointFileFor(profileDir: string): string; /** * Where the driver publishes action points for observers, next to endpoint.json: * one JSON line per successful action (`{ts, kind, x?, y?, url}`), so a host compositing * a live preview can animate a pointer where each action lands. Same contract as the * endpoint file: best-effort, observer-only, never load-bearing. */ export declare function actionsFileFor(profileDir: string): string; /** * Have the managed browser READY — adopt-or-launch — and leave it exactly where the mode says: * off-screen in background mode, on-screen only if the user has already asked to see it. * * It used to force the window on-screen and front, on the theory that a manual open means "I * want to look at it". That was wrong, and it was the single loudest way this feature disturbed * people: the host's toolbar button calls this, so one click put the browser on the user's * screen for good — and from then on every page-opened tab raised it over whatever they were * doing. Starting a browser and LOOKING at one are different requests; only the preview panel's * click means the second (see {@link revealFlagFor}). * * The browser outlives this process (the caller is `pi-gui browser open`, which exits after). */ export declare function openManagedBrowser(profileDir?: string): Promise; /** * Open a tab WITHOUT raising the browser. `context.newPage()` activates the window — measured: * it makes Chrome the frontmost application even while the window sits off-screen, which steals * the user's keyboard mid-sentence. CDP's own `Target.createTarget` takes a `background` flag * that does not, so every tab this driver opens goes through here. */ export declare function newBackgroundPage(ctx: BrowserContext): Promise; /** * Push the window back off-screen unless the user asked to see it. Called after every action: * a tab the PAGE opened (a `target=_blank` search result, a popup) raises the browser and we * cannot stop that at the source, so the window is put back a beat later instead. */ export declare function keepInBackground(ctx: BrowserContext, page: Page, profileDir: string): Promise; /** * The ladder: adopt the browser that already owns the profile; kill a frozen leftover by * identity; launch fresh; recover once from a port-less profile squatter. Every exit is either * a connected browser or an error whose message says what to do. */ export declare function acquireManagedBrowser(profileDir?: string): Promise<{ browser: Browser; cdpUrl: string; profileDir: string; }>;