Agent bridge: the loopback HTTP API driving live tabs - #469
Conversation
The HTTP API the agent's tools call to drive the user's live tabs: node:http on 127.0.0.1 with a random port and a per-launch bearer token (timing-safe compare), JSON routes for state/tabs/navigate/read/ snapshot/click/type/scroll/back/forward/reload/screenshot/top-sites and the trusted-input + paste endpoints, load-settle waits after mutating actions, a 1 MB body cap, and the 0600 discovery file (atomic write, deleted on shutdown). Electron-free by design: the manager injects a BridgeActions facade, so the whole route table is unit-tested with fakes.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 011d8f6725
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| await new Promise<void>((resolve, reject) => { | ||
| server.once('error', reject); | ||
| server.listen(0, '127.0.0.1', () => resolve()); | ||
| }); |
There was a problem hiding this comment.
Track bridge startup before awaiting listen
If two startup paths call startBridge() before the first listen() callback fires, or shutdown calls stopBridge() while this await is pending, running is still null, so each caller can create a separate server and stopBridge() can miss the in-flight one entirely. The later server overwrites the only tracked handle, leaving the earlier bridge listening with its own bearer token until process exit; cache an in-flight start promise or mark the server as starting before awaiting listen().
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed — bridge startup shares one in-flight server across concurrent starts (9813bcf).
Review follow-up: two racing startBridge() calls (or one during a pending listen) could each open a server, leaking a live bridge with its own token until exit. An in-flight start promise now dedupes them.
What
The security boundary of the whole feature (part 3 of 5): a small HTTP server in the app's main process that lets the AI agent drive the same live tabs the user is looking at.
127.0.0.1), random port, per-launch 256-bit bearer token (timing-safe compare). Everything except/healthrequires auth.~/.cowork/browser-bridge.jsonholds{port, token, pid}, written atomically with 0600 perms and deleted on shutdown — no stale credentials pointing at dead processes. The spawned server also gets env vars; the file is authoritative.click-at,press,insert-text) andpasteendpoints from part 2.{error}message.Verification
For QA
This one is user-testable without the UI. With the app running (any build that includes it):
cat ~/.cowork/browser-bridge.json— expect a port + long token, file readable only by you (ls -lshows-rw-------).curl http://127.0.0.1:<port>/health→{"ok":true}with no auth.curl http://127.0.0.1:<port>/state→ 401.Authorization: Bearer <token>→ 200 with tab state.