Browser Control
Use this skill when the user needs advanced Playwright control of an already running browser session rather than launch a local dev browser from source.
For simple page inventory and element actions, prefer the unified browser page tools first:
interpreter-app tools builtin-interpreter interpreter_whole_computer_state_get --json '{}'
interpreter-app tools builtin-interpreter interpreter_browser_page_inspect --json '{"tab_ref":"<tab_ref>"}'
- the matching
builtin-interpreter browser page trace/click/type/select/scroll tool using the exact tab_ref, frame_id, ref_id, and target_identity fields returned by inventory/inspect
Use this skill for advanced Playwright-in-tab work after you have an exact browser-control tab ref or session key, or when the simple builtin-interpreter page primitives cannot express the task.
This skill is for advanced live browser control:
run browser-action JavaScript through the js_repl app tool: interpreter-app tools builtin-js-repl js_repl --json '{"code":"..."}'
for multi-line JavaScript, prefer --stdin-arg code, which reads the raw JavaScript from stdin with no JSON escaping:
interpreter-app tools builtin-js-repl js_repl --stdin-arg code <<'JS'
globalThis.page = await globalThis.ensurePage();
console.log(globalThis.page.url());
JS
actions that trigger navigation or slow network work can outlive the default execution timeout; pass timeout_ms in the same call (--json '{"timeout_ms":300000}' --stdin-arg code <<'JS' ... JS). A timed-out call keeps the kernel and globalThis state, and the action may still have completed in the background, so reread page state before retrying instead of repeating the action blindly
when acting on the browser, put the JavaScript in the code argument of a builtin-js-repl js_repl call; do not answer with a fenced JavaScript block for the user to run, and do not run raw node instead
use the unified builtin-interpreter browser page tools for ordinary page inspect/click/type/select/scroll tasks, and use this Browser Use-shaped browser-control path for advanced Playwright work instead of opening a new Interpreter in-app browser tab
treat js_repl as a persistent JavaScript kernel: top-level let, const, class, and function names remain declared after each call in the same thread; interpreter-app tools builtin-js-repl js_repl_reset --json '{}' clears kernel state
write every browser-control snippet so it can run more than once without Identifier has already been declared
in the desktop app, assume Interpreter should already be running the local browser bridge
import interpreter-browser-control with await import("interpreter-browser-control"); the helper imports playwright-core and exposes raw Playwright as globalThis.playwright, globalThis.chromium, and the live globalThis.page
if the task already names a browser session key or browser-control tab ref, use it directly and skip discovery
inspect the local browser-session status endpoint only when the task did not already tell you which session to use
connect to Interpreter's browser-control bridge with chromium.connectOverCDP(...)
reuse the existing pages from that connection
do not call browser.close() on the user's live browser
do not use Interpreter's built-in browser tools, workstation browser/email tabs, or interpreter-app layout get|set as substitutes for this workflow
use Interpreter layout tools for files, workspace UI layout, and local app previews, not for controlling the user's browser
if the user explicitly asks to try opening a website inside the Interpreter app, warn them first that the in-app browser is a separate session and they should not expect to be signed in there
Because js_repl persists across turns, all browser-control bootstrap code must be idempotent and should store long-lived state on globalThis rather than redeclaring top-level let or const bindings.
Preconditions
- In the desktop app, Interpreter's browser bridge should already be running on
127.0.0.1:19988.
- In non-desktop runtimes, ensure the browser bridge/server is running on
127.0.0.1:19988.
- The user must already have at least one live browser tab exposed to Interpreter.
interpreter-app tools builtin-js-repl js_repl must be available.
If those preconditions are not met, stop and tell the user what is missing.
Hard Rules
js_repl needs no discovery: call interpreter-app tools builtin-js-repl js_repl directly instead of inspecting interpreter-app tools list first.
- Never provide browser-control JavaScript as visible Markdown instead of running it through
builtin-js-repl js_repl.
- Never use web search, capability probing, or generic discovery as a substitute for the
js_repl browser-control path.
- Never use Interpreter workstation context browser tabs or email tabs as evidence of the user's live external browser state.
- Never use
interpreter-app layout get|set to open or manipulate an external website as a workaround for browser control.
- Never fall back to Interpreter's legacy built-in browser tool server for this skill.
- If
interpreter-app tools builtin-js-repl js_repl is unavailable, say that directly instead of trying a different browser path.
- Do not assume a fresh
js_repl runtime. Re-running bootstrap code must be safe.
- Do not declare top-level browser-control variables with
const or let, including imports, browser, context, page, tab, or selectedBrowserSessionId.
REPL Safety
- Prefer
globalThis.foo ??= ... for long-lived browser-control state.
- Define helpers with
globalThis.helper ??= async function helper() { ... } so follow-up turns can reuse them safely.
- Put imported modules on
globalThis, for example globalThis.browserControlRuntime ??= await import("interpreter-browser-control").
- Do not redeclare top-level
let or const bindings like browser, page, or browserBridgeHttpEndpoint across turns.
- Do not write top-level follow-up snippets that rebind
page from globalThis.ensurePage() across turns. Persist the handle on globalThis.page and act through that instead.
- Do not reference
selectedBrowserSessionId without the globalThis. prefix.
- If a handle is missing or stale, repair the
globalThis state with the idempotent bootstrap instead of pasting a second conflicting bootstrap.
Default Workflow
- Run the browser-control JavaScript through
interpreter-app tools builtin-js-repl js_repl. The JavaScript belongs in that tool call's code argument, not in the assistant message.
- Set
globalThis.selectedBrowserSessionId first if the task already names the exact browser session key or browser-control tab ref.
- Run the idempotent bootstrap once so
agent.browser, display, and globalThis.ensurePage() exist.
- Inspect
/extensions/status only if the task did not already name the session.
- Call
globalThis.page = await globalThis.ensurePage();.
- Prefer the Browser Use-shaped
agent.browser primitives for Playwright-backed observation and tab management, and use normal Playwright APIs on globalThis.page when you need raw Playwright.
- Keep follow-up turns small and reuse the existing globals.
- Leave the browser running when finished.
Bootstrap
globalThis.selectedBrowserSessionId ??= undefined;
globalThis.page ??= undefined;
globalThis.browserControlRuntime ??= await import("interpreter-browser-control");
await globalThis.browserControlRuntime.setupInterpreterBrowserControl({
globals: globalThis,
sessionId: globalThis.selectedBrowserSessionId,
});
globalThis.page = await globalThis.ensurePage();
globalThis.tab = await agent.browser.tabs.selected();
console.log({
browserSessionId: globalThis.selectedBrowserSessionId,
title: await globalThis.page.title().catch(() => ""),
url: globalThis.page.url(),
});
Usage Notes
- Keep the first
js_repl call small. Do not paste a giant helper when the task already names the session you should use.
- Separate session selection from bootstrap:
globalThis.selectedBrowserSessionId = "<stableKey-or-tab-ref>";
then bootstrap if needed,
then globalThis.page = await globalThis.ensurePage();.
- If the task already tells you which session or tab ref to use, set
globalThis.selectedBrowserSessionId = "<that value>" before running globalThis.ensurePage(). Use the exact tab_ref from interpreter_whole_computer_state_get when available, including refs shaped like <profile-key>:chrome-tab:<chrome-tab-id>; when browser policy allows that page, the runtime claims an observed tab for Playwright control and selects the matching page. Use browser_profile_policy_id / extension_stable_key or an exact tab_ref as the control identity; browser_profile_name and browser_profile_path are display context, not CDP session keys.
- The bootstrap installs Browser Use-shaped globals:
agent.browser.nameSession(name)
agent.browser.tabs.selected(), list(), get(id), and new()
agent.browser.user.openTabs()
- tab methods:
goto(url), reload(), back(), forward(), close(), title(), and url()
tab.playwright methods matching the Browser Use skill API such as domSnapshot(), screenshot(), getByRole(), getByText(), locator(), waitForLoadState(), and waitForURL()
tab.cua methods matching the Browser Use skill API such as click(), double_click(), drag(), get_visible_screenshot(), keypress(), move(), scroll(), and type()
tab.dev.logs() and tab.clipboard.*
- Screenshots returned by
tab.playwright.screenshot() and tab.cua.get_visible_screenshot() are image objects with toBase64(). Use await display(image) to save them; the tool result reports the saved image path, which you can open with view_image when you need to see it.
- Raw Playwright is also available: use
globalThis.page for the live page, globalThis.chromium for the browser type, and globalThis.playwright for the imported playwright-core module.
- Write ordinary Playwright code after connecting.
- After bootstrap, follow-up turns should usually only set the session if needed, call
globalThis.ensurePage(), and act.
- Prefer this follow-up shape:
globalThis.page = await globalThis.ensurePage();
then await globalThis.page.locator(...).click();
- If a turn starts fresh or the kernel reset, rerun the bootstrap so
globalThis.ensurePage() exists again before touching page.
- If multiple browser sessions are connected and the task did not already choose one, stop and ask the user for the desired
stableKey or exact tab ref.
- Prefer
getByRole, locator, and explicit waits when needed.
- If multiple pages exist, inspect
context.pages() and choose the correct one explicitly.
- If the user wants a specific tab, confirm which page URL/title you are acting on before doing destructive actions.
- For search, lookup, record-review, or form workflows, reread the page after each submit/navigation before deciding the result. Verify that the intended field accepted the intended value, that the page is in the expected search/results/detail state, and that the result text supports the conclusion.
- Distinguish "no matching record found" from "record found, but requested content not found inside it." Only report no matching record after the visible search results or detail page confirms there is no matching record for the identifier you submitted.
- If the browser bridge disconnects or no pages are present, stop and ask the user to fix the browser side. In the desktop app, that usually means confirming the app is still running and at least one tab is already shared with Interpreter.
- If browser actions fail because of Settings > Browser, do not try to edit those settings yourself. You may read
browserAccessPolicy via interpreter_settings_get, but only the user can change it.
Search And Form Result Verification
Before writing a file or giving a final answer from a browser search or form:
- Read the current URL/title and visible page text after the action.
- Confirm the target field was the field the user intended, not a nearby filter, grouping, or unrelated control.
- Confirm the submitted value is still present in the target field or reflected on the result/detail page when the app exposes it.
- If the page is still on a filter, grouping, stale result, or unrelated detail page, navigate back to the clean search form and retry from the verified field instead of continuing.
- Keep outcome labels precise: use one label for "no matching record found" and a different label for "matching record exists, requested item not found."
Minimal Follow-up Turn
globalThis.selectedBrowserSessionId = "<stableKey-or-tab-ref>";
globalThis.page = await globalThis.ensurePage();
globalThis.tab = await agent.browser.tabs.selected();
await globalThis.tab.goto("https://www.wellsfargo.com/");
console.log({
title: await globalThis.tab.title(),
url: await globalThis.tab.url(),
});
Browser Use-Shaped Example
await agent.browser.nameSession("Browser task");
globalThis.tab = await agent.browser.tabs.selected();
console.log(await globalThis.tab.playwright.domSnapshot());
await display(await globalThis.tab.cua.get_visible_screenshot());
Troubleshooting
Identifier has already been declared: reuse globalThis state and do not redeclare top-level names like const page = ... in follow-up turns.
X is not defined: initialize that value on globalThis or rerun the idempotent bootstrap. If the missing value is selectedBrowserSessionId, use globalThis.selectedBrowserSessionId.
- Bridge connected but no page: ask the user to make a browser tab accessible to Interpreter first.
- Multiple sessions found: ask the user which
stableKey to use, then set globalThis.selectedBrowserSessionId explicitly.
Disconnecting
Do not close the user's browser. If you need to discard local handles, do this instead:
globalThis.page = undefined;
globalThis.context = undefined;
globalThis.browser = undefined;
Scope
This skill assumes normal Playwright-over-CDP usage. Do not assume extension-specific fork APIs or internal helper utilities unless they are explicitly available in the runtime.
1---2name: browser-control3description: Use this skill when the user needs advanced Playwright control of an already running browser session through Interpreter's app-managed browser bridge and the `builtin-js-repl` `js_repl` tool, after simple browser page work cannot be handled by the unified `builtin-interpreter` browser page tools.4---56# Browser Control78Use this skill when the user needs advanced Playwright control of an already running browser session rather than launch a local dev browser from source.910For simple page inventory and element actions, prefer the unified browser page tools first:1112- `interpreter-app tools builtin-interpreter interpreter_whole_computer_state_get --json '{}'`13- `interpreter-app tools builtin-interpreter interpreter_browser_page_inspect --json '{"tab_ref":"<tab_ref>"}'`14- the matching `builtin-interpreter` browser page trace/click/type/select/scroll tool using the exact `tab_ref`, `frame_id`, `ref_id`, and `target_identity` fields returned by inventory/inspect1516Use this skill for advanced Playwright-in-tab work after you have an exact browser-control tab ref or session key, or when the simple `builtin-interpreter` page primitives cannot express the task.1718This skill is for advanced live browser control:1920- run browser-action JavaScript through the `js_repl` app tool: `interpreter-app tools builtin-js-repl js_repl --json '{"code":"..."}'`21- for multi-line JavaScript, prefer `--stdin-arg code`, which reads the raw JavaScript from stdin with no JSON escaping:2223 ```bash24 interpreter-app tools builtin-js-repl js_repl --stdin-arg code <<'JS'25 globalThis.page = await globalThis.ensurePage();26 console.log(globalThis.page.url());27 JS28 ```2930- actions that trigger navigation or slow network work can outlive the default execution timeout; pass `timeout_ms` in the same call (`--json '{"timeout_ms":300000}' --stdin-arg code <<'JS' ... JS`). A timed-out call keeps the kernel and `globalThis` state, and the action may still have completed in the background, so reread page state before retrying instead of repeating the action blindly3132- when acting on the browser, put the JavaScript in the `code` argument of a `builtin-js-repl js_repl` call; do not answer with a fenced JavaScript block for the user to run, and do not run raw `node` instead33- use the unified `builtin-interpreter` browser page tools for ordinary page inspect/click/type/select/scroll tasks, and use this Browser Use-shaped browser-control path for advanced Playwright work instead of opening a new Interpreter in-app browser tab34- treat `js_repl` as a persistent JavaScript kernel: top-level `let`, `const`, `class`, and `function` names remain declared after each call in the same thread; `interpreter-app tools builtin-js-repl js_repl_reset --json '{}'` clears kernel state35- write every browser-control snippet so it can run more than once without `Identifier has already been declared`36- in the desktop app, assume Interpreter should already be running the local browser bridge37- import `interpreter-browser-control` with `await import("interpreter-browser-control")`; the helper imports `playwright-core` and exposes raw Playwright as `globalThis.playwright`, `globalThis.chromium`, and the live `globalThis.page`38- if the task already names a browser session key or browser-control tab ref, use it directly and skip discovery39- inspect the local browser-session status endpoint only when the task did not already tell you which session to use40- connect to Interpreter's browser-control bridge with `chromium.connectOverCDP(...)`41- reuse the existing pages from that connection42- do not call `browser.close()` on the user's live browser43- do not use Interpreter's built-in browser tools, workstation browser/email tabs, or `interpreter-app layout get|set` as substitutes for this workflow44- use Interpreter layout tools for files, workspace UI layout, and local app previews, not for controlling the user's browser45- if the user explicitly asks to try opening a website inside the Interpreter app, warn them first that the in-app browser is a separate session and they should not expect to be signed in there4647Because `js_repl` persists across turns, all browser-control bootstrap code must be idempotent and should store long-lived state on `globalThis` rather than redeclaring top-level `let` or `const` bindings.4849## Preconditions5051- In the desktop app, Interpreter's browser bridge should already be running on `127.0.0.1:19988`.52- In non-desktop runtimes, ensure the browser bridge/server is running on `127.0.0.1:19988`.53- The user must already have at least one live browser tab exposed to Interpreter.54- `interpreter-app tools builtin-js-repl js_repl` must be available.5556If those preconditions are not met, stop and tell the user what is missing.5758## Hard Rules5960- `js_repl` needs no discovery: call `interpreter-app tools builtin-js-repl js_repl` directly instead of inspecting `interpreter-app tools list` first.61- Never provide browser-control JavaScript as visible Markdown instead of running it through `builtin-js-repl js_repl`.62- Never use web search, capability probing, or generic discovery as a substitute for the `js_repl` browser-control path.63- Never use Interpreter workstation context browser tabs or email tabs as evidence of the user's live external browser state.64- Never use `interpreter-app layout get|set` to open or manipulate an external website as a workaround for browser control.65- Never fall back to Interpreter's legacy built-in browser tool server for this skill.66- If `interpreter-app tools builtin-js-repl js_repl` is unavailable, say that directly instead of trying a different browser path.67- Do not assume a fresh `js_repl` runtime. Re-running bootstrap code must be safe.68- Do not declare top-level browser-control variables with `const` or `let`, including imports, `browser`, `context`, `page`, `tab`, or `selectedBrowserSessionId`.6970## REPL Safety7172- Prefer `globalThis.foo ??= ...` for long-lived browser-control state.73- Define helpers with `globalThis.helper ??= async function helper() { ... }` so follow-up turns can reuse them safely.74- Put imported modules on `globalThis`, for example `globalThis.browserControlRuntime ??= await import("interpreter-browser-control")`.75- Do not redeclare top-level `let` or `const` bindings like `browser`, `page`, or `browserBridgeHttpEndpoint` across turns.76- Do not write top-level follow-up snippets that rebind `page` from `globalThis.ensurePage()` across turns. Persist the handle on `globalThis.page` and act through that instead.77- Do not reference `selectedBrowserSessionId` without the `globalThis.` prefix.78- If a handle is missing or stale, repair the `globalThis` state with the idempotent bootstrap instead of pasting a second conflicting bootstrap.7980## Default Workflow81821. Run the browser-control JavaScript through `interpreter-app tools builtin-js-repl js_repl`. The JavaScript belongs in that tool call's `code` argument, not in the assistant message.832. Set `globalThis.selectedBrowserSessionId` first if the task already names the exact browser session key or browser-control tab ref.843. Run the idempotent bootstrap once so `agent.browser`, `display`, and `globalThis.ensurePage()` exist.854. Inspect `/extensions/status` only if the task did not already name the session.865. Call `globalThis.page = await globalThis.ensurePage();`.876. Prefer the Browser Use-shaped `agent.browser` primitives for Playwright-backed observation and tab management, and use normal Playwright APIs on `globalThis.page` when you need raw Playwright.887. Keep follow-up turns small and reuse the existing globals.898. Leave the browser running when finished.9091## Bootstrap9293```javascript94globalThis.selectedBrowserSessionId ??= undefined;95globalThis.page ??= undefined;96globalThis.browserControlRuntime ??= await import("interpreter-browser-control");9798await globalThis.browserControlRuntime.setupInterpreterBrowserControl({99 globals: globalThis,100 sessionId: globalThis.selectedBrowserSessionId,101});102103globalThis.page = await globalThis.ensurePage();104globalThis.tab = await agent.browser.tabs.selected();105106console.log({107 browserSessionId: globalThis.selectedBrowserSessionId,108 title: await globalThis.page.title().catch(() => ""),109 url: globalThis.page.url(),110});111```112113## Usage Notes114115- Keep the first `js_repl` call small. Do not paste a giant helper when the task already names the session you should use.116- Separate session selection from bootstrap:117 `globalThis.selectedBrowserSessionId = "<stableKey-or-tab-ref>";`118 then bootstrap if needed,119 then `globalThis.page = await globalThis.ensurePage();`.120- If the task already tells you which session or tab ref to use, set `globalThis.selectedBrowserSessionId = "<that value>"` before running `globalThis.ensurePage()`. Use the exact `tab_ref` from `interpreter_whole_computer_state_get` when available, including refs shaped like `<profile-key>:chrome-tab:<chrome-tab-id>`; when browser policy allows that page, the runtime claims an observed tab for Playwright control and selects the matching page. Use `browser_profile_policy_id` / `extension_stable_key` or an exact `tab_ref` as the control identity; `browser_profile_name` and `browser_profile_path` are display context, not CDP session keys.121- The bootstrap installs Browser Use-shaped globals:122 - `agent.browser.nameSession(name)`123 - `agent.browser.tabs.selected()`, `list()`, `get(id)`, and `new()`124 - `agent.browser.user.openTabs()`125 - tab methods: `goto(url)`, `reload()`, `back()`, `forward()`, `close()`, `title()`, and `url()`126 - `tab.playwright` methods matching the Browser Use skill API such as `domSnapshot()`, `screenshot()`, `getByRole()`, `getByText()`, `locator()`, `waitForLoadState()`, and `waitForURL()`127 - `tab.cua` methods matching the Browser Use skill API such as `click()`, `double_click()`, `drag()`, `get_visible_screenshot()`, `keypress()`, `move()`, `scroll()`, and `type()`128 - `tab.dev.logs()` and `tab.clipboard.*`129- Screenshots returned by `tab.playwright.screenshot()` and `tab.cua.get_visible_screenshot()` are image objects with `toBase64()`. Use `await display(image)` to save them; the tool result reports the saved image path, which you can open with `view_image` when you need to see it.130- Raw Playwright is also available: use `globalThis.page` for the live page, `globalThis.chromium` for the browser type, and `globalThis.playwright` for the imported `playwright-core` module.131- Write ordinary Playwright code after connecting.132- After bootstrap, follow-up turns should usually only set the session if needed, call `globalThis.ensurePage()`, and act.133- Prefer this follow-up shape:134 `globalThis.page = await globalThis.ensurePage();`135 then `await globalThis.page.locator(...).click();`136- If a turn starts fresh or the kernel reset, rerun the bootstrap so `globalThis.ensurePage()` exists again before touching `page`.137- If multiple browser sessions are connected and the task did not already choose one, stop and ask the user for the desired `stableKey` or exact tab ref.138- Prefer `getByRole`, `locator`, and explicit waits when needed.139- If multiple pages exist, inspect `context.pages()` and choose the correct one explicitly.140- If the user wants a specific tab, confirm which page URL/title you are acting on before doing destructive actions.141- For search, lookup, record-review, or form workflows, reread the page after each submit/navigation before deciding the result. Verify that the intended field accepted the intended value, that the page is in the expected search/results/detail state, and that the result text supports the conclusion.142- Distinguish "no matching record found" from "record found, but requested content not found inside it." Only report no matching record after the visible search results or detail page confirms there is no matching record for the identifier you submitted.143- If the browser bridge disconnects or no pages are present, stop and ask the user to fix the browser side. In the desktop app, that usually means confirming the app is still running and at least one tab is already shared with Interpreter.144- If browser actions fail because of Settings > Browser, do not try to edit those settings yourself. You may read `browserAccessPolicy` via `interpreter_settings_get`, but only the user can change it.145146## Search And Form Result Verification147148Before writing a file or giving a final answer from a browser search or form:149150- Read the current URL/title and visible page text after the action.151- Confirm the target field was the field the user intended, not a nearby filter, grouping, or unrelated control.152- Confirm the submitted value is still present in the target field or reflected on the result/detail page when the app exposes it.153- If the page is still on a filter, grouping, stale result, or unrelated detail page, navigate back to the clean search form and retry from the verified field instead of continuing.154- Keep outcome labels precise: use one label for "no matching record found" and a different label for "matching record exists, requested item not found."155156### Minimal Follow-up Turn157158```javascript159globalThis.selectedBrowserSessionId = "<stableKey-or-tab-ref>";160globalThis.page = await globalThis.ensurePage();161globalThis.tab = await agent.browser.tabs.selected();162await globalThis.tab.goto("https://www.wellsfargo.com/");163console.log({164 title: await globalThis.tab.title(),165 url: await globalThis.tab.url(),166});167```168169### Browser Use-Shaped Example170171```javascript172await agent.browser.nameSession("Browser task");173globalThis.tab = await agent.browser.tabs.selected();174console.log(await globalThis.tab.playwright.domSnapshot());175await display(await globalThis.tab.cua.get_visible_screenshot());176```177178### Troubleshooting179180- `Identifier has already been declared`: reuse `globalThis` state and do not redeclare top-level names like `const page = ...` in follow-up turns.181- `X is not defined`: initialize that value on `globalThis` or rerun the idempotent bootstrap. If the missing value is `selectedBrowserSessionId`, use `globalThis.selectedBrowserSessionId`.182- Bridge connected but no page: ask the user to make a browser tab accessible to Interpreter first.183- Multiple sessions found: ask the user which `stableKey` to use, then set `globalThis.selectedBrowserSessionId` explicitly.184185## Disconnecting186187Do not close the user's browser. If you need to discard local handles, do this instead:188189```javascript190globalThis.page = undefined;191globalThis.context = undefined;192globalThis.browser = undefined;193```194195## Scope196197This skill assumes normal Playwright-over-CDP usage. Do not assume extension-specific fork APIs or internal helper utilities unless they are explicitly available in the runtime.