Adom Desktop - Puppeteer Bridge
Public Made by Adomby adom
The Adom Desktop Puppeteer (pup) bridge — drive a real headful browser on the user's desktop from the cloud: open/screenshot/eval/record. Cold-start drives the browser ALREADY on the machine (installed Chrome, else Microsoft Edge — every Windows PC ships with Edge) with a fresh isolated profile, so the common case needs NO download; Chrome for Testing is fetched only as a last resort. Two artifacts: the Releases zip is the bridge runtime (also bundled in Adom Desktop); the pkg installs the container-side Claude skills.
name: pup-windows-sessions-tabs user-invocable: true description: "pup (Puppeteer bridge) windows, sessions, and tabs: how sessionIds work and why to task-prefix them, WINDOW OWNERSHIP so you never steal another AI thread's window (owner + session_owned_by_another_thread + takeover), the BACKGROUND-by-default rule (foreground:true is the only way to show a window; sizing/positioning does NOT foreground), running MANY TABS in one window instead of many windows, switching/listing/closing sessions and tabs, and verifying what you actually opened. Read when opening/reusing pup windows, managing tabs, deciding background vs foreground, or seeing errorCode session_owned_by_another_thread. Trigger words: pup sessionId, browser_open_window owner, session_owned_by_another_thread, takeover window, pup background, foreground:true, browser_open_tab, browser_switch_tab, browser_list_tabs, browser_switch_window, browser_list_windows, browser_close_window, pup tabs, many tabs one window, don't steal window, browser_lower_os_window, browser_raise_os_window, browser_alert_window."
Parent skill: adom-desktop-puppeteer-bridge · See also: pup (start here), pup-screenshots-recording, pup-browsers-and-chrome.
pup — windows, sessions & tabs
A session = one browser window (a fresh isolated Chrome/Edge process) that holds many tabs.
You address everything by sessionId. profile (defaults to sessionId) names the on-disk cookie jar.
sessionIds — task-prefix them, and pass owner
browser_open_window {sessionId} either creates a window (new id) or reuses/navigates an existing
one (id already exists). Because sessions are shared across every AI thread on this desktop, a generic
id collides:
- Task-prefix the id:
"<your-task>-web"(e.g.wikifix-preview), neverwiki/test/main. - Always pass
owner:"<your-task>". It's an advisory label that lets pup protect your window. - Pick a stable
profileacross runs if you want the cookie jar to persist (user logs in once, reuse for months):{"sessionId":"vendor-fetch","profile":"vendor","owner":"vendor-fetch", …}.
Window ownership — do NOT steal another AI thread's window
If you browser_open_window a sessionId that already exists with a new url, pup NAVIGATES that window
— destroying whatever another thread was doing there. pup guards this:
- Declared cross-owner reuse is REFUSED —
errorCode: session_owned_by_another_thread(also onbrowser_navigate/browser_close/browser_close_window). The_hinttells you the current owner- url + age.
- If YOU get that error — the window isn't yours. Open your OWN with a unique task-prefixed sessionId.
Pass
takeover:trueONLY if the user explicitly told you to repurpose that window. - Anonymous grabs are allowed but loudly reprimanded in the
_hint(with the previous url so you can put it back) — so ALWAYS declareownerto be a good citizen and to get protection. - Reusing your OWN window is good — same sessionId + same owner = a smooth refresh (e.g. re-rendering a page you just republished). Ownership only blocks cross-thread grabs.
- Unsure what exists?
browser_status '{}'lists every window with itsowner, currenturl, andageMinutes. Run it at the start of a task (especially after a context compaction) before picking an id.
# right — owned, task-prefixed
adom-desktop browser_open_window '{"sessionId":"wikifix-preview","owner":"wikifix","url":"<url>"}'
# wrong — generic + anonymous: may hijack another thread's window (pup reprimands you in _hint)
adom-desktop browser_open_window '{"sessionId":"wiki","url":"<url>"}'
Background by default — pup NEVER pops onto the user's screen unless asked
pup is the AI's workspace, not the user's. Every window opens in the background (behind the user's windows, no focus stolen) and stays fully drivable + screenshottable there — you do NOT lower it yourself.
foreground:trueis the ONLY thing that shows a window — and it is GATED (v1.8.70). Not sizing, not navigating, not screenshotting, not recording setup. Foregrounding requires BOTHforeground:trueANDforegroundReason:"<quote/paraphrase what the user said>"(min 10 chars). Without the reason the open still succeeds but the window stays BACKGROUND (foregroundDenied:'foreground_reason_required'). The reason is logged, recorded inlastAgentUpdate, and surfaced aslastForegroundin status — the user can always audit who put a window on their screen and why. Pass it ONLY when the user explicitly wants to WATCH (a demo, a "show me", a live recording); after the show-me,browser_lower_os_windowto return it.- Sizing/positioning does NOT foreground —
browser_open_window {width,height,x,y}places the window at an exact size/spot for clean screenshot framing while it stays in the background. So you can frame a shot without ever disturbing the user's main work. browser_alert_windowflashes the taskbar (a non-intrusive "it's ready/done" nudge) — prefer it over raising the window. Use it for an explicit end-of-task "it's ready".browser_raise_os_window/browser_focus_windowbring a window forward — GATED the same way: REFUSED (foreground_reason_required) unless you passforegroundReason. Use only for an explicit user show-me;browser_lower_os_windowtucks it back after. Raise steals focus and can leak a mid-typed keystroke into the page (pup blurs the page's focused element first to reduce it) — to just signal "I updated this window", use the auto-flash /browser_alert_window, NOT raise.
Signalling what you changed — auto-flash + updateNote (so the user follows along)
When you drive a background window the user can't tell you touched it — and across several tabs they lose track of WHICH one you just changed. pup handles both, automatically:
- Auto-flash: every mutating verb (
navigate,reload,back/forward,switch_tab,click,type,press_key,input_dispatch,eval,open_tab,open_window) flashes that window's taskbar icon orange — without stealing focus, self-suppressed while it's foreground, and cleared when the user clicks it. It's debounced to one flash per window per ~5s burst, so a wholeopen→navigate→ fill→screenshotsequence flashes once, not five times. - Tune it:
{"silent":true}on any one call suppresses that flash;browser_open_window {"autoFlash":false}silences the whole session (for a background poller); and a pure read should passbrowser_eval {"readOnly":true}so a polling loop doesn't flash or count as an update. - Say what you did: pass
updateNote":"<short what-I-did>"on any mutating verb. pup recordslastAgentUpdate {tabId, verb, note, ts}and surfaces it top-level and per-tab inbrowser_list_tabs/browser_list_windows— so the user (or a Session Monitor) sees which tab you staged and why. Add anupdateNoteon the meaningful steps of a multi-tab task.
# default: background — drive it invisibly, user never sees a thing
adom-desktop browser_open_window '{"sessionId":"job-web","owner":"job","url":"<url>"}'
adom-desktop browser_screenshot '{"sessionId":"job-web"}'
# only if the user asked to SEE it:
adom-desktop browser_open_window '{"sessionId":"demo-web","owner":"demo","url":"<url>","foreground":true}'
Taskbar look — split vs grouped (user preference, browser_configure)
Each pup window wears the Adom Pup icon; how they arrange on the taskbar is the USER's choice:
split(default): one taskbar button PER WINDOW, each badged with its own tab count (no badge = 1 tab). Best for telling AI threads apart: "the button with the 8 is my adom-google review window."grouped: all pup windows stack under ONE "Adom Pup" button; its badge shows the WINDOW count. Tidy taskbar for users who open many windows.
browser_configure {"taskbarGrouping":"grouped"} switches live (persists across updates).
Ask the user before changing it — it's their taskbar. Read current: browser_configure {}.
One window, many tabs (multi-URL tasks)
For a vendor fetch / multi-doc review, put the URLs in tabs of one window (one taskbar icon), not separate windows:
adom-desktop browser_open_window '{"sessionId":"task-web","owner":"task","url":"https://first.example.com"}'
adom-desktop browser_open_tab '{"sessionId":"task-web","url":"https://second.example.com"}' # → { tabId: "tab-2", tabCount: 2 }
adom-desktop browser_switch_tab '{"sessionId":"task-web","tabId":"tab-2"}'
adom-desktop browser_screenshot '{"sessionId":"task-web","tabId":"tab-2"}' # tabId optional — omit for the active tab
adom-desktop browser_close_tab '{"sessionId":"task-web","tabId":"tab-2"}'
browser_open_tabreturns{tabId, tabCount, activeTabId}; pass thetabIdtoswitch/close/screenshot/eval/navigate(omit to target the active tab).- Closing the last tab closes the window.
- Popup tabs (e.g. a PDF the page opens) auto-attach and appear in
browser_list_tabs; a closed popup's last URL is retained so you canbrowser_fetch_urlit if you missed it.
Don't double-open; reuse correctly
- New session entirely →
browser_open_window. - Same session, new page in the same tab →
browser_navigate(withtabIdif multiple tabs). - A second view in the same session →
browser_open_tab(NOT a second window). - Switch which window is "active" →
browser_switch_window; list them →browser_list_windows.
Opening the same content under a new sessionId leaves the user staring at two identical windows — so
browser_status first and reuse when the URL is already open.
Clean up after yourself — close windows when the task is done
pup never auto-closes a window (a "stale" one might belong to a thread that's just paused between turns). So every window you open lingers on the user's real desktop until you close it. Left alone, a day of tasks piles up a dozen dead pup windows the user has to find and close.
- When you finish a task,
browser_close_windowthe windows you opened for it. Same for one-off windows (a quick screenshot/scrape) — close them right after. - Check what's yours:
browser_statuslists every window with itsownerandageMinutes, and now nudges you (a🧹 CLEANUPhint) when windows are old (≥30 min) or piling up. Act on it — close the ones you're done with. Only close windows you own; never close another thread's. - Long-running work? Reuse ONE window (
browser_navigate/browser_open_tab) instead of opening a fresh window each step, so there's only one thing to clean up. - If you truly need a window to persist across turns (a login you'll reuse for hours), that's fine — but it's a deliberate choice, and you still own closing it when done.
Verify what you opened — ok:true is not enough
browser_open_window returns ok:true as soon as navigation starts, not when content rendered. Confirm
with browser_eval '{"sessionId":"…","expr":"document.title"}' (fastest — 404s, login walls, anti-bot
blocks all change the title) or a browser_screenshot when the title isn't discriminating.
Verbs at a glance
| Verb | Does |
|---|---|
browser_open_window |
Create (or reuse) a session/window. Args: sessionId, owner, url?, profile?, foreground?, width/height/x/y?, takeover?, updateNote?, autoFlash? (false = silence session) |
browser_navigate |
Drive an existing tab to a URL (no new window) — sessionId, url, tabId? |
browser_open_tab / browser_switch_tab / browser_close_tab / browser_list_tabs |
Tabs within one window |
browser_switch_window / browser_list_windows |
Multiple sessions |
browser_status |
List every session + owner/url/ageMinutes (NOT a readiness check) |
browser_lower_os_window / browser_raise_os_window / browser_focus_window / browser_alert_window |
Z-order / focus / taskbar flash |
browser_close_window (one session) / browser_close (all) |
Close — destructive |
---
name: pup-windows-sessions-tabs
user-invocable: true
description: "pup (Puppeteer bridge) windows, sessions, and tabs: how sessionIds work and why to task-prefix them, WINDOW OWNERSHIP so you never steal another AI thread's window (owner + session_owned_by_another_thread + takeover), the BACKGROUND-by-default rule (foreground:true is the only way to show a window; sizing/positioning does NOT foreground), running MANY TABS in one window instead of many windows, switching/listing/closing sessions and tabs, and verifying what you actually opened. Read when opening/reusing pup windows, managing tabs, deciding background vs foreground, or seeing errorCode session_owned_by_another_thread. Trigger words: pup sessionId, browser_open_window owner, session_owned_by_another_thread, takeover window, pup background, foreground:true, browser_open_tab, browser_switch_tab, browser_list_tabs, browser_switch_window, browser_list_windows, browser_close_window, pup tabs, many tabs one window, don't steal window, browser_lower_os_window, browser_raise_os_window, browser_alert_window."
---
**Parent skill:** adom-desktop-puppeteer-bridge · See also: `pup` (start here), `pup-screenshots-recording`, `pup-browsers-and-chrome`.
# pup — windows, sessions & tabs
A **session** = one browser **window** (a fresh isolated Chrome/Edge process) that holds **many tabs**.
You address everything by `sessionId`. `profile` (defaults to `sessionId`) names the on-disk cookie jar.
## sessionIds — task-prefix them, and pass `owner`
`browser_open_window {sessionId}` either **creates** a window (new id) or **reuses/navigates** an existing
one (id already exists). Because sessions are **shared across every AI thread on this desktop**, a generic
id collides:
- **Task-prefix the id:** `"<your-task>-web"` (e.g. `wikifix-preview`), never `wiki` / `test` / `main`.
- **Always pass `owner:"<your-task>"`.** It's an advisory label that lets pup protect your window.
- Pick a **stable `profile`** across runs if you want the cookie jar to persist (user logs in once, reuse
for months): `{"sessionId":"vendor-fetch","profile":"vendor","owner":"vendor-fetch", …}`.
## Window ownership — do NOT steal another AI thread's window
If you `browser_open_window` a sessionId that already exists **with a new url**, pup NAVIGATES that window
— destroying whatever another thread was doing there. pup guards this:
1. **Declared cross-owner reuse is REFUSED** — `errorCode: session_owned_by_another_thread` (also on
`browser_navigate` / `browser_close` / `browser_close_window`). The `_hint` tells you the current owner
+ url + age.
2. **If YOU get that error** — the window isn't yours. Open your OWN with a unique task-prefixed sessionId.
Pass **`takeover:true`** ONLY if the user explicitly told you to repurpose that window.
3. **Anonymous grabs are allowed but loudly reprimanded** in the `_hint` (with the previous url so you can
put it back) — so ALWAYS declare `owner` to be a good citizen and to get protection.
4. **Reusing your OWN window is good** — same sessionId + same owner = a smooth refresh (e.g. re-rendering
a page you just republished). Ownership only blocks *cross-thread* grabs.
5. **Unsure what exists?** `browser_status '{}'` lists every window with its `owner`, current `url`, and
`ageMinutes`. Run it at the start of a task (especially after a context compaction) before picking an id.
```bash
# right — owned, task-prefixed
adom-desktop browser_open_window '{"sessionId":"wikifix-preview","owner":"wikifix","url":"<url>"}'
# wrong — generic + anonymous: may hijack another thread's window (pup reprimands you in _hint)
adom-desktop browser_open_window '{"sessionId":"wiki","url":"<url>"}'
```
## Background by default — pup NEVER pops onto the user's screen unless asked
**pup is the AI's workspace, not the user's.** Every window opens **in the background** (behind the user's
windows, no focus stolen) and stays fully drivable + screenshottable there — you do NOT lower it yourself.
- **`foreground:true` is the ONLY thing that shows a window — and it is GATED (v1.8.70).** Not
sizing, not navigating, not screenshotting, not recording setup. Foregrounding requires BOTH
`foreground:true` AND `foregroundReason:"<quote/paraphrase what the user said>"` (min 10 chars).
Without the reason the open still succeeds but the window stays BACKGROUND
(`foregroundDenied:'foreground_reason_required'`). The reason is logged, recorded in
`lastAgentUpdate`, and surfaced as `lastForeground` in status — the user can always audit who
put a window on their screen and why. Pass it ONLY when the user explicitly wants to WATCH (a
demo, a "show me", a live recording); after the show-me, `browser_lower_os_window` to return it.
- **Sizing/positioning does NOT foreground** — `browser_open_window {width,height,x,y}` places the window
at an exact size/spot **for clean screenshot framing** while it stays in the background. So you can frame
a shot without ever disturbing the user's main work.
- **`browser_alert_window`** flashes the taskbar (a non-intrusive "it's ready/done" nudge) — prefer it over
raising the window. Use it for an explicit end-of-task "it's ready".
- `browser_raise_os_window` / `browser_focus_window` bring a window forward — **GATED the same way:
REFUSED (`foreground_reason_required`) unless you pass `foregroundReason`.** Use only for an
explicit user show-me; `browser_lower_os_window` tucks it back after. **Raise steals focus and can
leak a mid-typed keystroke into the page** (pup blurs the page's focused element first to reduce
it) — to just signal "I updated this window", use the auto-flash / `browser_alert_window`, NOT raise.
## Signalling what you changed — auto-flash + `updateNote` (so the user follows along)
When you drive a background window the user can't tell you touched it — and across several tabs they lose
track of WHICH one you just changed. pup handles both, automatically:
- **Auto-flash:** every **mutating** verb (`navigate`, `reload`, `back`/`forward`, `switch_tab`, `click`,
`type`, `press_key`, `input_dispatch`, `eval`, `open_tab`, `open_window`) flashes that window's taskbar
icon orange — **without stealing focus**, self-suppressed while it's foreground, and cleared when the
user clicks it. It's **debounced to one flash per window per ~5s burst**, so a whole `open→navigate→
fill→screenshot` sequence flashes once, not five times.
- **Tune it:** `{"silent":true}` on any one call suppresses that flash; `browser_open_window
{"autoFlash":false}` silences the whole session (for a background poller); and a pure read should pass
`browser_eval {"readOnly":true}` so a polling loop doesn't flash or count as an update.
- **Say what you did:** pass **`updateNote":"<short what-I-did>"`** on any mutating verb. pup records
`lastAgentUpdate {tabId, verb, note, ts}` and surfaces it **top-level and per-tab** in
`browser_list_tabs` / `browser_list_windows` — so the user (or a Session Monitor) sees which tab you
staged and why. Add an `updateNote` on the meaningful steps of a multi-tab task.
```bash
# default: background — drive it invisibly, user never sees a thing
adom-desktop browser_open_window '{"sessionId":"job-web","owner":"job","url":"<url>"}'
adom-desktop browser_screenshot '{"sessionId":"job-web"}'
# only if the user asked to SEE it:
adom-desktop browser_open_window '{"sessionId":"demo-web","owner":"demo","url":"<url>","foreground":true}'
```
## Taskbar look — split vs grouped (user preference, `browser_configure`)
Each pup window wears the Adom Pup icon; how they arrange on the taskbar is the USER's choice:
- **`split` (default):** one taskbar button PER WINDOW, each badged with its own tab count
(no badge = 1 tab). Best for telling AI threads apart: "the button with the 8 is my
adom-google review window."
- **`grouped`:** all pup windows stack under ONE "Adom Pup" button; its badge shows the
WINDOW count. Tidy taskbar for users who open many windows.
`browser_configure {"taskbarGrouping":"grouped"}` switches live (persists across updates).
**Ask the user before changing it — it's their taskbar.** Read current: `browser_configure {}`.
## One window, many tabs (multi-URL tasks)
For a vendor fetch / multi-doc review, put the URLs in **tabs of one window** (one taskbar icon), not
separate windows:
```bash
adom-desktop browser_open_window '{"sessionId":"task-web","owner":"task","url":"https://first.example.com"}'
adom-desktop browser_open_tab '{"sessionId":"task-web","url":"https://second.example.com"}' # → { tabId: "tab-2", tabCount: 2 }
adom-desktop browser_switch_tab '{"sessionId":"task-web","tabId":"tab-2"}'
adom-desktop browser_screenshot '{"sessionId":"task-web","tabId":"tab-2"}' # tabId optional — omit for the active tab
adom-desktop browser_close_tab '{"sessionId":"task-web","tabId":"tab-2"}'
```
- `browser_open_tab` returns `{tabId, tabCount, activeTabId}`; pass the `tabId` to
`switch`/`close`/`screenshot`/`eval`/`navigate` (omit to target the active tab).
- **Closing the last tab closes the window.**
- Popup tabs (e.g. a PDF the page opens) auto-attach and appear in `browser_list_tabs`; a closed popup's
last URL is retained so you can `browser_fetch_url` it if you missed it.
## Don't double-open; reuse correctly
- New session entirely → `browser_open_window`.
- Same session, new page in the same tab → `browser_navigate` (with `tabId` if multiple tabs).
- A second view in the same session → `browser_open_tab` (NOT a second window).
- Switch which window is "active" → `browser_switch_window`; list them → `browser_list_windows`.
Opening the same content under a new `sessionId` leaves the user staring at two identical windows — so
`browser_status` first and reuse when the URL is already open.
## Clean up after yourself — close windows when the task is done
pup **never auto-closes** a window (a "stale" one might belong to a thread that's just paused
between turns). So every window you open lingers on the **user's real desktop** until *you* close it.
Left alone, a day of tasks piles up a dozen dead pup windows the user has to find and close.
- **When you finish a task, `browser_close_window` the windows you opened for it.** Same for one-off
windows (a quick screenshot/scrape) — close them right after.
- **Check what's yours:** `browser_status` lists every window with its `owner` and `ageMinutes`, and
now nudges you (a `🧹 CLEANUP` hint) when windows are old (≥30 min) or piling up. Act on it — close
the ones you're done with. Only close windows **you own**; never close another thread's.
- **Long-running work?** Reuse ONE window (`browser_navigate` / `browser_open_tab`) instead of opening
a fresh window each step, so there's only one thing to clean up.
- If you truly need a window to persist across turns (a login you'll reuse for hours), that's fine —
but it's a deliberate choice, and you still own closing it when done.
## Verify what you opened — `ok:true` is not enough
`browser_open_window` returns `ok:true` as soon as navigation *starts*, not when content rendered. Confirm
with `browser_eval '{"sessionId":"…","expr":"document.title"}'` (fastest — 404s, login walls, anti-bot
blocks all change the title) or a `browser_screenshot` when the title isn't discriminating.
## Verbs at a glance
| Verb | Does |
|---|---|
| `browser_open_window` | Create (or reuse) a session/window. Args: `sessionId`, `owner`, `url?`, `profile?`, `foreground?`, `width/height/x/y?`, `takeover?`, `updateNote?`, `autoFlash?` (false = silence session) |
| `browser_navigate` | Drive an existing tab to a URL (no new window) — `sessionId`, `url`, `tabId?` |
| `browser_open_tab` / `browser_switch_tab` / `browser_close_tab` / `browser_list_tabs` | Tabs within one window |
| `browser_switch_window` / `browser_list_windows` | Multiple sessions |
| `browser_status` | List every session + `owner`/`url`/`ageMinutes` (NOT a readiness check) |
| `browser_lower_os_window` / `browser_raise_os_window` / `browser_focus_window` / `browser_alert_window` | Z-order / focus / taskbar flash |
| `browser_close_window` (one session) / `browser_close` (all) | Close — destructive |