Design Host Browser Bridge For Docker
tasks24/29
1. Feasibility
- Analyze the host Patchright/Playwright server options and document the persistent-profile constraint. (Finding:
launchServer()does not acceptuserDataDir; persistent profiles require a host process owning alaunchPersistentContext. Bridge shape captured indesign-notes/host-bridge-feasibility-spike.md.) - Prove the chosen host persistent-context bridge from Docker against a visible host browser with a dedicated PDPP profile. (Deferred to the implementation tranche; the design names this as a required validation, not a completed proof.)
- Analyze host Chrome-over-CDP from Docker and document the Patchright stealth tradeoff. (Findings: loses Patchright launch-side stealth; remote-debugging-port broadcasts to all local processes; only acceptable as a documented escape hatch with a dedicated
--user-data-dir.) - Confirm host setup on macOS and Linux; note Windows requirements if not tested. (Expected: macOS/Windows have
host.docker.internal; Linux Compose requiresextra_hosts: ["host.docker.internal:host-gateway"]. Actual platform validation remains part of the implementation tranche.) - Decide whether the host bridge should be launched manually, by a helper script, or by Docker Compose instructions. (Decision: launched manually by the operator via a small in-repo CLI; Compose only documents the env vars and
extra_hosts; Compose does not auto-start the bridge.)
2. Design
- Pick the recommended first implementation path. (Host Patchright persistent-context bridge; see
design.md.) - Specify environment variables and connection/auth model for the local bridge. (Host:
PDPP_HOST_BRIDGE_PORT,PDPP_HOST_BRIDGE_TOKEN,PDPP_HOST_BRIDGE_PROFILE_ROOT,PDPP_HOST_BRIDGE_LOG. Container:PDPP_HOST_BROWSER_BRIDGE_URL,PDPP_HOST_BROWSER_BRIDGE_TOKEN,PDPP_HOST_BROWSER_BRIDGE_DAILY_CHROME. Loopback bind, shared-secret token, origin/host check.) - Specify default profile path and explicit daily-profile escape hatch, if any. (Default:
~/.pdpp/profiles/<connector-or-subject>/. Escape hatch:PDPP_HOST_BROWSER_BRIDGE_DAILY_CHROME, off by default, name intentionally explicit.) - Specify run/timeline/dashboard behavior when the bridge is required but unavailable. (Typed
host_browser_bridge_unavailablefailure; distinct dashboard state; copy-paste fix in error copy. Newkind=host_browser_requiredinteraction for steps that need a real host window.)
3. Implementation Planning
- Identify code touchpoints across
browser-launch.ts, Docker/Compose docs, run interaction rendering, and connector runtime diagnostics. (Listed indesign.md§ "Implementation Touchpoints".) - Choose one connector for the first vertical slice. (ChatGPT — already browser-backed, no money-movement risk, exercises Cloudflare/login/OTP.)
- Define validation steps for "user sees host browser, completes interaction, connector continues." (See
design.md§ "First Vertical Slice → Validation Flow".)
4. Validation
-
openspec validate design-host-browser-bridge-for-docker --strict -
openspec validate --all --strict
5. Implementation Slice (branch implement-host-browser-bridge)
- Container-side env resolver (
packages/polyfill-connectors/src/host-browser-bridge-config.ts) — fail-closed parsing with stable failure codehost_browser_bridge_unavailable, unit-tested. - Container-side acquisition router (
packages/polyfill-connectors/src/browser-launch.ts:acquireBrowserForConnector) — picks bridge vs native isolated launcher; throwsHostBrowserBridgeUnavailableErroron misconfig or unreachable bridge; emits the daily-Chrome warning on opt-in. - Connector runtime wiring (
packages/polyfill-connectors/src/connector-runtime.ts:acquireBrowser) — surfaces the stable failure code in the terminal-error message so the controller renders the deployment-config error state. - Host bridge CLI (
packages/polyfill-connectors/bin/host-browser-bridge.ts) — owns PatchrightlaunchPersistentContext, readsDevToolsActivePort, exposes a token-gated WebSocket reverse proxy, prints the env exports operators need, closes the host browser on SIGINT/SIGTERM. - Bind-host parameter (
--bind-host/PDPP_HOST_BRIDGE_BIND_HOST) with safe default127.0.0.1and explicit acknowledgement (--allow-public-bind) for0.0.0.0. Linux operators must set this to the docker bridge IP (typically172.17.0.1); the bridge emits a startup warning on Linux when 127.0.0.1 is used. Empirically validated end-to-end: Linux container reaches a172.17.0.1-bound bridge throughhost.docker.internal, and is correctly unreachable for a127.0.0.1-bound bridge — the original assumption that "host-gateway delivers to host loopback" was wrong. - Compose passthrough (
docker-compose.yml) — declares the three bridge env vars and addsextra_hosts: ["host.docker.internal:host-gateway"]so Linux Compose can reach the host (when the bridge is bound to the docker bridge IP). - Operator docs (
README.md,.env.docker.example) — split macOS/Windows vs Linux flows; honest about which bind host each platform needs. - Unit tests for env resolution, token/auth behavior, bridge-unavailable failure, CLI argv parsing, bind-host validation, banner content.
- Integration tests for the bridge proxy (
bin/host-browser-bridge-proxy.test.ts) covering: HTTP root, 401 on missing token, frame round-trip with right token, Host-header rejection, non-loopback bind path. These exercise the samestartBridgeServerthe CLI uses. - CDP-frame proxy correctness —
WebSocket.RawDatatyping instead ofas Buffer; sanitizer for receive-only close codes (1004, 1005, 1006, 1015) so a clean clientws.close()doesn't crash the proxy. -
pnpm --dir packages/polyfill-connectors run verify(typecheck + ultracite). -
pnpm --dir packages/polyfill-connectors run test(751/751 pass; 5 baseline skipped). - Manual end-to-end proof: run the host bridge against a real ChatGPT profile, attach from a Compose run, complete an OTP/Cloudflare interaction in the visible host browser, observe the connector finish. (Not run in this slice — requires a live ChatGPT account and a running Compose stack with active credentials. See merge-queue note.)
- Dashboard render of
host_browser_bridge_unavailableas a distinct deployment-config state. (Deferred to a follow-up web slice; the runtime already surfaces the stable code in the terminal-error message so the dashboard can pattern-match without further runtime changes.) -
kind=host_browser_requiredinteraction emission from connectors. (Deferred. Today the visible-host-browser flow is implicit: the bridge launches a window the operator sees while the connector blocks on its existingauto-logininteraction handshake. Adding an explicit kind is a connector-side spec change worth its own slice.)