interface DaemonState
Private

All state one daemon process owns; serve() constructs the single instance and threads it through every handler.

import type * as Server from './server.ts';

// Internal — a snapshot of the lifecycle flags the handlers coordinate on.
const flags = { shuttingDown: false, consecutiveCrashes: 0, listenSucceeded: true };
flags.consecutiveCrashes; // 0 — reset after any run that leaves the browser connected

Properties

browser: PlaywrightBrowser | null

Resolved Chrome handle. null until the initial in-flight launch settles (see browserReady) and again briefly during recoverBrowser. Run handlers MUST go through awaitBrowser(state) rather than reading this field directly — it's null until the first awaiting caller resolves it. The split lets run listen() and write the info file immediately, instead of blocking on Chrome launch. Under CI load Chrome launch tail latencies regularly exceed 100 s; gating socket readiness on Chrome would propagate that latency to every daemon start/status/ ping and exhaust client-side spawn timeouts in both the Node and Deno paths (CI runs 26006815757 + 26007923495).

browserReady: Promise<PlaywrightBrowser>

Promise of the in-flight Chrome launch. Settles exactly once for the initial launch and is replaced whenever recoverBrowser runs. Awaited inside handleRun (via awaitBrowser) so a run request that arrives before Chrome is up waits the same amount of time it would have waited for the synchronous launch — but daemon start/ping/status return as soon as the socket is up. Decoupling Chrome from socket readiness is the fix for the "Daemon did not start within Ns" timeout class on slow CI.

baseConfig: ResolvedConfig

Captured at startup; used to relaunch the browser on crash recovery.

cwd: string
startedAt: number
pkgMtime: number
runQueue: Promise<unknown>
pendingClients: Set<net.Socket>
socketServer: net.Server
idleTimer: NodeJS.Timeout | null
socketPath: string
infoPath: string
lockPath: string

Reset to 0 after any run that left the browser connected.

true after listen() resolves. Gates file cleanup in shutdown: a process whose listen() failed (concurrent-spawn EADDRINUSE) does not own the socket or info file, so the loser must not unlink them — only the winner does.

Single-source esbuild incremental-context cache, persisted across daemon runs. Mutated by reference inside buildIncrementally; disposed on daemon shutdown.

pageSlot: DaemonRunHandles["pageSlot"]

Single-source Page slot for single-group daemon runs. Lives across runs; Browser.setup reuses slot.page (when connected) instead of newPage(). Closed on daemon shutdown; cleared (set to null) when the page disconnects.