Repository navigation
Conversation
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.
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.
…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.
# Conflicts: # server/lib/oapi/oapi.go
# Conflicts: # server/lib/oapi/oapi.go
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ 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.
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Summary
POST /replruns every call in one Node runtime per browser, so callers can't run independent stateful tasks at the same time, and a timeout orresetwipes everyone's state. This adds named executors to the Browser REPL, matching the modelPOST /playwright/executealready uses.POST /repltakes an optionalexecutorname (^[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.executorrun on an always-presentdefaultexecutor that behaves as before.resetonly 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.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.GET /repl/executorslists them,defaultfirst.DELETE /repl/executors/{name}?close_tab=truestops a named executor; the running call and queued calls fail with "executor was deleted", and its tab is closed. Deletingdefaultrestarts 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.new_state(call started from empty state),terminated, andtab: {target_id, created}.repl_idandrepl_terminatedare kept but marked deprecated.default. Named executors don't start the custom-tool registry, because its page-side cleanup would removedefault's registrations.webmcp.addCustomTools,listCustomTools, andremoveCustomToolthrow 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.Changes
cmd/api/api/browser_repl.go:browserReplManageris 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_missinganswered without running code; attached tab reported in each response;repl.executor; heap monitor.openapi.yaml(+ regeneratedlib/oapi/oapi.goandlib/events/category_gen.go) anddocs/repl.md.Testing
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 fullcmd/apiunit suite and the runtime tests pass.TestBrowserReplAPI/<image>/named_executors, against real Chromium:ensureRealTab()stays on a blank home tab;newTab()in a named executor leaves the active tab unchanged;TestBrowserReplAPI(headless and headful),TestPlaywrightExecuteAPI(including the custom WebMCP subtests), andTestPlaywrightDaemonRecoverypass locally againstchromium-headless/chromium-headfulimages with this branch's API binary and runtime bundles layered on.