diff --git a/BROWSER.md b/BROWSER.md index 0f52b41a..640bb659 100644 --- a/BROWSER.md +++ b/BROWSER.md @@ -33,7 +33,7 @@ gstack's browser is a compiled CLI binary that talks to a persistent local Chrom │ ▼ │ │ ┌──────────┐ HTTP POST ┌──────────────┐ │ │ │ browse │ ──────────────── │ Bun HTTP │ │ -│ │ CLI │ localhost:9400 │ server │ │ +│ │ CLI │ localhost:rand │ server │ │ │ │ │ Bearer token │ │ │ │ │ compiled │ ◄────────────── │ Playwright │──── Chromium │ │ │ binary │ plain text │ API calls │ (headless) │ @@ -46,7 +46,7 @@ gstack's browser is a compiled CLI binary that talks to a persistent local Chrom ### Lifecycle -1. **First call**: CLI checks `/tmp/browse-server.json` for a running server. None found — it spawns `bun run browse/src/server.ts` in the background. The server launches headless Chromium via Playwright, picks a port (9400-9410), generates a bearer token, writes the state file, and starts accepting HTTP requests. This takes ~3 seconds. +1. **First call**: CLI checks `.gstack/browse.json` (in the project root) for a running server. None found — it spawns `bun run browse/src/server.ts` in the background. The server launches headless Chromium via Playwright, picks a random port (10000-60000), generates a bearer token, writes the state file, and starts accepting HTTP requests. This takes ~3 seconds. 2. **Subsequent calls**: CLI reads the state file, sends an HTTP POST with the bearer token, prints the response. ~100-200ms round trip. @@ -94,15 +94,15 @@ No DOM mutation. No injected scripts. Just Playwright's native accessibility API ### Authentication -Each server session generates a random UUID as a bearer token. The token is written to the state file (`/tmp/browse-server.json`) with chmod 600. Every HTTP request must include `Authorization: Bearer `. This prevents other processes on the machine from controlling the browser. +Each server session generates a random UUID as a bearer token. The token is written to the state file (`.gstack/browse.json`) with chmod 600. Every HTTP request must include `Authorization: Bearer `. This prevents other processes on the machine from controlling the browser. ### Console, network, and dialog capture The server hooks into Playwright's `page.on('console')`, `page.on('response')`, and `page.on('dialog')` events. All entries are kept in O(1) circular buffers (50,000 capacity each) and flushed to disk asynchronously via `Bun.write()`: -- Console: `/tmp/browse-console.log` -- Network: `/tmp/browse-network.log` -- Dialog: `/tmp/browse-dialog.log` +- Console: `.gstack/browse-console.log` +- Network: `.gstack/browse-network.log` +- Dialog: `.gstack/browse-dialog.log` The `console`, `network`, and `dialog` commands read from the in-memory buffers, not disk. @@ -112,30 +112,22 @@ Dialogs (alert, confirm, prompt) are auto-accepted by default to prevent browser ### Multi-workspace support -Each workspace gets its own isolated browser instance with its own Chromium process, tabs, cookies, and logs. +Each workspace gets its own isolated browser instance with its own Chromium process, tabs, cookies, and logs. State is stored in `.gstack/` inside the project root (detected via `git rev-parse --show-toplevel`). -If `CONDUCTOR_PORT` is set (e.g., by [Conductor](https://conductor.dev)), the browse port is derived deterministically: +| Workspace | State file | Port | +|-----------|------------|------| +| `/code/project-a` | `/code/project-a/.gstack/browse.json` | random (10000-60000) | +| `/code/project-b` | `/code/project-b/.gstack/browse.json` | random (10000-60000) | -``` -browse_port = CONDUCTOR_PORT - 45600 -``` - -| Workspace | CONDUCTOR_PORT | Browse port | State file | -|-----------|---------------|-------------|------------| -| Workspace A | 55040 | 9440 | `/tmp/browse-server-9440.json` | -| Workspace B | 55041 | 9441 | `/tmp/browse-server-9441.json` | -| No Conductor | — | 9400 (scan) | `/tmp/browse-server.json` | - -You can also set `BROWSE_PORT` directly. +No port collisions. No shared state. Each project is fully isolated. ### Environment variables | Variable | Default | Description | |----------|---------|-------------| -| `BROWSE_PORT` | 0 (auto-scan 9400-9410) | Fixed port for the HTTP server | -| `CONDUCTOR_PORT` | — | If set, browse port = this - 45600 | +| `BROWSE_PORT` | 0 (random 10000-60000) | Fixed port for the HTTP server (debug override) | | `BROWSE_IDLE_TIMEOUT` | 1800000 (30 min) | Idle shutdown timeout in ms | -| `BROWSE_STATE_FILE` | `/tmp/browse-server.json` | Path to state file | +| `BROWSE_STATE_FILE` | `.gstack/browse.json` | Path to state file (CLI passes to server) | | `BROWSE_SERVER_SCRIPT` | auto-detected | Path to server.ts | ### Performance @@ -206,7 +198,7 @@ Tests spin up a local HTTP server (`browse/test/test-server.ts`) serving HTML fi | File | Role | |------|------| -| `browse/src/cli.ts` | Entry point. Reads `/tmp/browse-server.json`, sends HTTP to the server, prints response. | +| `browse/src/cli.ts` | Entry point. Reads `.gstack/browse.json`, sends HTTP to the server, prints response. | | `browse/src/server.ts` | Bun HTTP server. Routes commands to the right handler. Manages idle timeout. | | `browse/src/browser-manager.ts` | Chromium lifecycle — launch, tab management, ref map, crash detection. | | `browse/src/snapshot.ts` | Parses accessibility tree, assigns `@e`/`@c` refs, builds Locator map. Handles `--diff`, `--annotate`, `-C`. | diff --git a/TODO.md b/TODO.md index 9c32bbcb..0148e707 100644 --- a/TODO.md +++ b/TODO.md @@ -77,6 +77,7 @@ - Pass/fail with evidence ## Phase 5: State & Sessions + - [ ] Bundle server.ts into compiled binary (eliminate resolveServerScript() fallback chain entirely) (P2, M) - [ ] v20 encryption format support (AES-256-GCM) — future Chromium versions may change from v10 - [ ] Sessions (isolated browser instances with separate cookies/storage/history) - [ ] State persistence (save/load cookies + localStorage to JSON files)