Skip to content

Add named Browser REPL executors for concurrent REPL calls - #466

Open
rgarcia wants to merge 7 commits into
mainfrom
hypeship/repl-executors
Open

rgarcia wants to merge 7 commits into
mainfrom
hypeship/repl-executors

Conversation

@rgarcia

@rgarcia rgarcia commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Summary

POST /repl runs every call in one Node runtime per browser, so callers can't run independent stateful tasks at the same time, and a timeout or reset wipes everyone's state. This adds named executors to the Browser REPL, matching the model POST /playwright/execute already uses.

  • POST /repl takes an optional executor name (^[A-Za-z0-9_-]{1,64}$). Each executor is its own runtime process with its own bindings, modules, and browser connection. Calls on different executors run concurrently; calls on one executor run one at a time.
  • Calls without executor run on an always-present default executor that behaves as before.
  • A timeout, crash, OOM, or reset only ends that executor's state. A timeout still ends the executor's process: unlike Playwright calls, REPL cells share state with later cells, so code abandoned by a timeout can't be left running.
  • Tabs: each named executor owns a background tab the API opens on its first call. Its helpers attach to that tab, and newTab() creates tabs, without making them the active tab, so a named executor doesn't take focus from the live view or the default executor. ensureRealTab() returns to that tab even while it's blank. A named executor never falls back to another page: if its tab is closed mid-cell, helpers fail with a clear error, and the next call opens a new tab without losing state.
  • Limits and endpoints: at most 8 named executors (409 with the executor list). GET /repl/executors lists them, default first. DELETE /repl/executors/{name}?close_tab=true stops a named executor; the running call and queued calls fail with "executor was deleted", and its tab is closed. Deleting default restarts it: the running call fails with "executor was restarted", and queued calls run on a new process without waiting. No idle reaping; graceful API shutdown removes named executors and closes their tabs.
  • Response fields: new_state (call started from empty state), terminated, and tab: {target_id, created}. repl_id and repl_terminated are kept but marked deprecated.
  • Custom WebMCP tools stay owned by default. Named executors don't start the custom-tool registry, because its page-side cleanup would remove default's registrations. webmcp.addCustomTools, listCustomTools, and removeCustomTool throw a clear error there. Every executor can still discover and invoke tools. Moving the registry to a shared tools host so every executor can register tools is a follow-up.
  • Heap logging: each runtime keeps the 512 MiB heap limit and logs a warning when it passes 90% (re-armed below 80%). The API watches runtime stderr and logs an error with the executor name when a runtime hits the limit.

Changes

  • cmd/api/api/browser_repl.go: browserReplManager is now one executor, with its own socket, an optional tab owner, a recorded tab ID, a restart path that doesn't wait for queued calls, and a retire path for delete and shutdown.
  • cmd/api/api/browser_repl_executors.go: registry of named executors, the cap, and the execute/list/delete handlers.
  • cmd/api/api/browser_repl_heap.go: stderr watcher for V8 heap-limit aborts.
  • cmd/api/api/executor_tabs.go: the tab owner and name pattern, moved out of the Playwright executor code so both executor kinds share them.
  • runtime/: executor name and home tab; non-activating attach for named executors; tab_missing answered without running code; attached tab reported in each response; repl.executor; heap monitor.
  • openapi.yaml (+ regenerated lib/oapi/oapi.go and lib/events/category_gen.go) and docs/repl.md.

Testing

  • New unit tests (browser_repl_executors_test.go) run against the real REPL runtime bundle: concurrency across executors, state isolation, a timeout ending only one executor, tab ownership across a reset, the cap and 409, invalid first calls not holding slots, a failed first call not tearing down a queued call, delete during tab open (tab still closed), delete with in-flight and queued calls, close_tab=false, default restart without waiting for queued calls, idle restart, custom-tool rejection in named executors, a start failure not holding a slot, shutdown closing tabs, and the heap watcher. They passed 3 runs with -race. The full cmd/api unit suite and the runtime tests pass.
  • New e2e subtest TestBrowserReplAPI/<image>/named_executors, against real Chromium:
    • a named executor navigates its own tab without changing the active tab (checked through Playwright's default executor);
    • ensureRealTab() stays on a blank home tab;
    • two executors' 2s waits overlap;
    • closing an executor's tab reopens it without losing state;
    • closing it mid-cell fails helpers instead of attaching to another tab;
    • newTab() in a named executor leaves the active tab unchanged;
    • delete closes the tabs.
  • The full TestBrowserReplAPI (headless and headful), TestPlaywrightExecuteAPI (including the custom WebMCP subtests), and TestPlaywrightDaemonRecovery pass locally against chromium-headless/chromium-headful images with this branch's API binary and runtime bundles layered on.

POST /repl takes an optional executor name. Each executor runs in its own
runtime process with its own state, so calls on different executors run
concurrently and a timeout, crash, or reset only ends that executor's state.
Calls without a name run on an always-present default executor. Named
executors own a background tab the API opens, are capped at 8, and are
listed and deleted through /repl/executors. Responses report new_state,
terminated and the attached tab; repl_id and repl_terminated are deprecated.
Runtimes log a warning at 90% of their heap limit and the API logs an error
when one hits it.

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread server/runtime/browser-cdp-client.ts Outdated
Comment thread server/runtime/browser-repl.ts
Comment thread server/cmd/api/api/custom_webmcp.go
Restart and call admission now share a lock, so a restart cannot miss a just-admitted call. A named executor that never got a tab is dropped only once no call holds it, including after invalid first calls. Delete or restart during startup fails the call like a queued call, and a tab opened during a delete is still recorded and closed. Named executors never fall back to another tab, and tabs they create stay in the background. Shutdown retires executors concurrently, and tab.created reports the tab the API opened even when the cell ends on another tab.

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread server/runtime/browser-cdp-client.ts
Comment thread server/cmd/api/api/browser_repl.go Outdated
…tor tab

Custom WebMCP operations on the default executor register with restart like executions. Custom-tool methods in named executors throw synchronously. newTab(url) in a named executor whose tab was closed opens a new tab instead of failing, and a reset that opens a tab while its executor is deleted fails like other interrupted calls.

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread server/runtime/browser-repl.ts
Comment thread server/cmd/api/api/browser_repl.go

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit fdf9e8d. Configure here.

Comment thread server/cmd/api/api/browser_repl.go
Retire can stop waiting for an in-flight operation (shutdown budget or a cancelled delete); the operation now terminates the child on its way out instead of leaving it running.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant