Adom Browser Extension
Public Made by Adomby adom
Your AI's hands in your real, signed-in browser. The Adom Browser Extension (abe) works with both Chrome and Edge: build, drive, and test web apps, and log past vendor walls, in the browser you already use - cookies, SSO, saved logins and captcha trust intact. The nbrowser_* bridge (pup's counterpart) runs in your actual logged-in profile.
Ownership guardrails: reprimand AI threads into tracking + cleaning up windows they open/spawn
Problem: AI threads leak untracked windows they spawn — no way to find or close them
Real incident. An AI thread driving a user's real Chrome was filling an IRS form. To read a child's contact card, it clicked email addresses inside Google Contacts. Each click silently spawned a Gmail compose window. When the thread tried to close them, it couldn't — and here's exactly why, proven from live ABE state:
- The composes opened as standalone Chrome windows (their own windowIds), not tabs in the thread's session window. Gmail compose always pops a standalone window.
- ABE's ownership model only records windows/tabs created via
open_window/open_tab. A window a driven page spawns (compose,target=_blank,window.open, OAuth popup, PDF viewer) is never adopted into the originating session. - Every ABE close/drive verb is session-keyed:
nbrowser_close_windowtakes asessionId, not a windowId (it errorsunknown sessionId 'undefined'otherwise). A spawned window belongs to no session → no sessionId reaches it → no ABE verb can close it. Permanent leak. - Separately,
nbrowser_list_windowsdid not return the calling thread's own session, so the agent had no way to reconcile "what is mine."
The linchpin: the popup belongs to no session, so there is no sessionId, so no cleanup path exists.
Design: registration + reprimand guardrails (fail-fast, teaching)
Make it structurally impossible to open an untracked window, forget a spawned one, or end a task leaving litter. Every guard is teaching — it refuses with a _hint carrying the corrected call, never a bare "no." Consistent with ABE's existing guard philosophy.
1. session_unregistered (the cornerstone). open_window/open_tab refuse unless the caller passes sessionId + thread (which AI thread) + purpose (human-readable, e.g. "finishing John's Trump Account signup"). Persist {sessionId, thread, purpose, createdAt}. Surface purpose in status/list so the Session Monitor shows who is driving each window and why.
2. unacknowledged_spawns (fixes this incident). Add chrome.tabs.onCreated (has openerTabId) + chrome.windows.onCreated listeners. When the opener belongs to a known session, adopt the new window/tab as spawned with {spawnedBy, openerTabId, reason, url, createdAt}, emit a spawned_window event, and refuse the session's next mutating verb until the thread acknowledges the spawn (adopt-keep or close). You cannot forget your own litter.
3. dirty_session. On a thread's done/disconnect while its session still owns windows, reprimand with the list + the cleanup call. Nag (non-fatal) in nbrowser_status while owned windows remain.
4. stale_coordinates. Refuse a raw {x,y} click if the page scrolled or re-rendered since those coords could have been valid. (This is the bug where a cached "Next" coordinate had become the "Back" button and popped a "Go back to Step 1?" modal repeatedly.) Force re-resolve by selector/text.
5. ambiguous_target. If click-by-text matches >1 visible element (two "Next"/"Back", three "Yes" radios), refuse and demand a disambiguating selector instead of silently clicking the first.
6. profile_mismatch. Refuse any verb where the named profile doesn't match the session's pinned profile. Guards against acting on the wrong Google account (personal vs corporate).
7. no_keepalive (warning). If a session is driven >10 min against a host known to idle-timeout with no keepalive set, warn. (An IRS idle-timeout mid-task cost a full ID.me re-login here.)
New / changed verbs
nbrowser_owned {sessionId}→ everything the session owns, splitopenedvsspawned, each with windowId/tabId, url, title, ageMs, opener, reason.list_windows/list_tabs: addowner/spawnedByfields +mine:truefilter; fix the caller's-own-session-missing bug.close_window/close_tab: accept adopted spawns. Addnbrowser_close_window {sessionId, includeSpawned:true}andnbrowser_cleanup {sessionId}(closes only what the session spawned; never touches the user's pre-existing windows). Refuseclose(close-all) unlessconfirmCloseAll:true.
Where it lives
Repo C:/Users/john/adom-browser-extension — extension/src/sw.js (service worker: session map, onCreated adoption, guards) + bridge/ verb surface (arg validation, _hint text). Extend tools/check-guard-coverage.mjs so CI fails if any verb ships unclassified for registration, spawn-awareness, and the targeting guards. Bump manifest.json. Then bump the driving-the-extension skill: new verbs, all reprimands, an end-of-task cleanup ritual (call nbrowser_owned, close every spawned), and the pitfall that clicking a mailto/email in Gmail-linked Contacts spawns a compose window (prefer navigating the person URL or fetch_url to read a contact).
Test plan (use the incident as the fixture)
- Open a session with
sessionId/thread/purpose; go to Google Contacts. - Click a contact's email → spawns a compose window. Assert:
spawned_windowevent fires; the compose is innbrowser_owned.spawned; the next mutating verb is refusedunacknowledged_spawns. nbrowser_cleanup {sessionId}closes the compose; the user's pre-existing windows are untouched.open_windowwith nothread/purpose→ refusedsession_unregistered.- A cached
{x,y}click after a scroll → refusedstale_coordinates. nbrowser_list_windowsnow returns the caller's own session.
Acceptance
An AI thread cannot open an untracked window, cannot proceed with unacknowledged spawns, and cannot end with owned windows still open without being told. nbrowser_owned reliably returns opened + spawned. The exact incident is fully recoverable via nbrowser_cleanup. Guard-coverage check passes and covers the new guards. Do not weaken the existing focus/foreground (focus_violation) or cross-thread (session_owned_by_another_thread) guards — compose with them.
Priority: #1 + #2 together would have fully prevented this incident; #4 + #5 would have saved most of the wasted clicking.