/** * The pinned runtime image. * * A digest, not a tag. Upstream publishes 40-char git SHAs plus moving * `main`/`latest` aliases and no semver at all, and its own docs say to * "prefer immutable digests". Pinning one means an agent release cannot have * the browser changed underneath it. * * Digest of `latest` as of 2026-08-22, built from popcorn-oss * `4df751c25c7b329699033957b93f6486837e4acb`. */ export declare const BROWSER_RUNTIME_IMAGE: string; export interface ContainerBrowser { /** Local viewer origin — what `share_browser_view` tunnels. */ viewerUrl: string; /** Browser-level CDP websocket, for `connectCdpUrl`. */ cdpUrl: string; /** Loopback port the viewer is published on. */ viewerPort: number; /** Docker's name for this container. */ name: string; /** Whether the image had to be pulled first — worth telling the developer. */ pulled: boolean; /** Which slot this browser occupies, for diagnostics. */ slot: number; stop: () => Promise; } export interface ContainerOptions { /** Loopback port for the viewer. Allocated when omitted. */ viewerPort?: number; /** Loopback port for full CDP. Allocated when omitted. */ cdpPort?: number; /** Override the pinned image, e.g. a local `build.sh` output. */ image?: string; /** Framebuffer size, which is also the browser window's size. */ width?: number; height?: number; /** Page to open on start. */ url?: string; } /** * Where the container's Chromium profile lives on the host. * * Mounted, unlike the reference dev script, for two reasons. Provider authoring * means signing in once and capturing an authenticated request, which a profile * that dies with the container makes impossible. And the profile holds the * persisted fingerprint identity — canvas/audio seed, timezone, locale — which * upstream warns must stay stable, since "flipping any of them against the same * cookie jar is itself a bot signal". */ export declare function containerProfileDir(slot?: number): string; /** Docker CLI present AND its daemon reachable. */ export declare function dockerAvailable(): Promise; /** Message for the case we cannot fix for them. */ export declare function dockerMissingMessage(): string; /** * The `docker run` argv. * * A pure function because every flag here is load bearing and none of it is * obvious from the outside: which ports are loopback-only, that the image is * digest-pinned, that the profile is mounted. Tests assert on this directly * rather than trying to observe a running container. */ export declare function buildRunArgs(opts: ContainerOptions & { viewerPort: number; cdpPort: number; }, image: string, name: string, profileDir: string): string[]; /** * Start a browser container and wait until it is actually serving. * * Ports are allocated per call, so concurrent sessions never land on the same * container. Pass `viewerPort` to pin one — re-attaching with the same port * reuses the container that is already there. */ export declare function startContainer(options?: ContainerOptions): Promise; /** * Remove containers whose owning process is gone. * * Shutdown cleanup covers an orderly exit, but a SIGKILL or a crash runs * nothing — and a 1.3 GB container with a live browser in it should not outlive * the session that made it. Reaping at startup catches those, and the owner-PID * label keeps it safe: a container whose owner still runs belongs to another * session and is left alone. */ export declare function reapOrphanedContainers(): Promise;