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.
name: native-browser-safety description: "SAFETY rules for any agent driving the Adom browser extension (nbrowser_* verbs). This bridge drives the user's REAL, signed-in Chrome/Edge - their live tabs, forms, sessions, saved logins, and other AI threads. Read this BEFORE driving. The cardinal rule: only ever act on a window/tab YOU opened (nbrowser_open_window); NEVER navigate, reload, or close a window/tab the user already had open (this includes the OS-level desktop_navigate) - doing so destroyed a user's other Claude threads once. Also: background by default (never foreground unless asked), the CDP "debugging" bar, credentials/autologin are gated and never expose passwords, recording is WGC-first, and the user blocklist is hands-off. Match on: browser safety, agent browser isolation, don't touch my tabs, navigated my window, wiped my threads, never reuse a window, background by default, real browser safety, nbrowser safety, drive the real browser safely. Part of the adom-browser-extension."
Part of adom-browser-extension. Verb surface: driving-the-extension. Recording: native-browser-recording.
Driving the user's REAL browser safely
nbrowser_* is not pup. It runs inside the user's actual, signed-in Chrome/Edge, so every action
lands on their live browser: open tabs they are reading, forms they are filling, authenticated
sessions, saved logins, and other AI threads' tabs. That power is the whole point (it sails
through logins and captchas pup cannot), and it is exactly why a careless move is destructive. These
rules are not optional polish; one of them exists because an agent wiped a user's other Claude threads.
⛔ Cardinal rule: only ever drive a window/tab YOU opened
Open your own surface and work only in it. nbrowser_open_window gives you a background window +
a sessionId; do ALL your driving through that sessionId.
REGISTER every window (v0.1.0+). nbrowser_open_window now REQUIRES three fields or it refuses
(session_unregistered):
nbrowser_open_window {sessionId:"<short task id>", thread:"<which AI thread you are>",
purpose:"<human-readable, e.g. finishing John's IRS signup>", url:"…"}
purpose shows up in nbrowser_status and the Session Monitor so the user can see who is driving each
window and why. No sessionId/thread/purpose = no window.
⛔ Track & reap the windows a page SPAWNS (the leaked-compose fix)
A page you drive can open a new window you did not ask for: clicking an email in Google Contacts
pops a Gmail compose window; target=_blank links, window.open, OAuth popups, and PDF viewers do
the same. These are standalone windows in no session - historically unreachable, so they leaked.
Now they are auto-adopted into your session. The moment one appears:
- a
spawned_windowevent fires (seenbrowser_events), and - your next driving verb is refused (
unacknowledged_spawns) until you deal with it.
Deal with it: nbrowser_owned {sessionId} lists everything you own (opened + spawned); then either
keep what you need or nbrowser_cleanup {sessionId} to close the litter (it closes ONLY what you
spawned, never the user's own windows). End every task by closing what you own:
nbrowser_close_window {sessionId, includeSpawned:true}. If you ever find a TRUE orphan ABE can't
attribute, nbrowser_force_close is a deliberately hard, two-step last resort (it can never close
another thread's window). It has TWO forms behind the SAME gate (ticket → attest with a written
reason): {windowId} for windows the extension can see, and {hwnd} (v0.1.23+, from
desktop_list_windows) for browser windows the extension CANNOT reach — e.g. Chrome/Edge litter that a
browser_open/pup call or an HD test left behind, or a window in a profile with no extension. The hwnd
form only targets real browser windows and sends a graceful WM_CLOSE. So cleaning up ANOTHER tool's
browser litter is allowed — but only through this gate, with a real justification, and never a window a
live thread owns.
⚠️ Pitfall — reading a contact spawns a compose: to read a person's email/phone in Google Contacts,
do not click the mailto/email link (that spawns a compose window). Navigate the person's contact URL
or nbrowser_fetch_url the data instead.
NEVER navigate, reload, close, or repurpose a window or tab the user already had open:
- not with
nbrowser_navigate/nbrowser_reload/nbrowser_close_tab/nbrowser_close_window, - and not with the OS-level
desktop_navigateeither (same destruction, different door).
Why it is this strict: navigating a user's active tab yanks their live page out from under them and
wipes anything unsaved - including other running AI threads. That actually happened - twice
(2026-06 and again 2026-07-08, mid-Meet-call), which is why as of extension v0.0.21 the code
enforces it: every mutating verb refuses a bare call (no_target - the active-tab fallback is
gone) and refuses any target outside an agent-opened window (not_owned). There is no
agent-passable override; refusals return a teaching _hint, and each connection's first response
carries a one-time _safety banner. Do not attempt to work around a refusal - including
"restorative" moves like history.back() on the user's tab. If you damaged their state, STOP and
tell them; more uninvited commands make it worse.
Map identities BEFORE driving. nbrowser_profiles first, every time: pick the browser:email
profile matching the identity the task needs (a task about [email protected] → that profile, never
"the browser that happens to be active" - Chrome and Edge each hold different accounts).
The only thing you may do to one of the user's existing windows is desktop_bring_to_front to surface
it for them - and only when they explicitly ask to see it.
⛔ Background by default - never foreground unless asked
The user is almost always doing something else on their computer - reading, typing, on a call, in
another app. Your work has to DEFAULT to the background, especially when you open new browser
windows: a window that jumps in front of them interrupts what they were doing, and their next
keystrokes land in whatever control you just surfaced. Opening in the background is therefore the
default posture, not a nicety; foregrounding is opt-in and happens ONLY when the user explicitly
asks to watch. The verbs are built to stay out of their way:
nbrowser_open_window opens unfocused, nbrowser_open_tab opens active:false,
nbrowser_switch_window does not raise. Leave those defaults alone. You can fully see and
drive a window without foregrounding it: nbrowser_screenshot (CDP) and desktop_screenshot_window
(WGC) both capture a background/occluded window, and WGC records one too. So there is never a
reason to pull a window forward. Foregrounding is the user's choice (desktop_bring_to_front),
made by them, only when they want to watch.
This is not just etiquette - foregrounding corrupts your own work. When you steal focus while the
user is typing, their next keystrokes land in whatever control you just focused (a search box, a
text field), silently changing your state and derailing the task (real case: a foregrounded
chrome://extensions search turned into Adoms mid-flow and the target extension vanished). Note this
applies to SendInput verbs too - desktop_type / desktop_press_key / desktop_click foreground
the target before sending. Prefer background UIA (desktop_ui_click Invoke, desktop_ui_set SetValue);
if a step genuinely needs foreground keyboard input, caption the user to pause, do the one action, and
return focus immediately.
⚠️ Foreground can happen even when you NEVER call bring_to_front. Opening a new window, and
especially desktop_navigate (its omnibox SetValue + posted-Enter commit can surface/activate the
window to land the Enter), may bring a browser window to the FRONT despite focused:false. The rule is
about the outcome, not the verb: after any open or navigate, check desktop_list_windows (a window
at z:0 is topmost = you surfaced it) and treat a window that jumped forward as a disruption. Minimize
opens and navigations, drive chrome:// pages only when the user is not mid-task, and never repeatedly
surface a window. (Observed live: a desktop_navigate to chrome://extensions left the window at z:0
even though no bring_to_front was called and the window was opened focused:false.)
The bridge now ENFORCES all of this in code (ext >= 0.0.22) - you cannot shove a window in front of
the user even by accident. open_window/open_tab open in the background regardless of a stray
focused:true/active:true (those legacy flags are IGNORED); switch_window/focus never raise; and
after every open/navigate/switch/state change the bridge checks the z-order - if a driven window landed
topmost, it is auto-sent back to the background (focus returned to the user's previous window, or the
window minimized if the user was in another app) and the response carries a _warning +
focusAutoReverted:true. Each auto-correction is logged as a focus_violation event
(nbrowser_events) so the user can see when a thread tried to steal focus.
Foregrounding takes real work now (v0.1.0+), because it STEALS the user's keyboard focus. Users
automating the browser almost always have 5 other things in the foreground; a raised window captures
their keystrokes (real incident: it typed a user's chat message into a username field and dismissed
Chrome's password dropdown, twice). So a bare userRequestedForeground:true is no longer enough -
without a foregroundReason the window stays background. To genuinely foreground (some auth pages /
browser features truly need it):
- Warn the user first with an AD notify:
adom-desktop notify_user {message:"Adom is about to bring a browser window forward for <reason>"}. - Then pass
userRequestedForeground:true+foregroundReason:"<why>". - Do the ONE thing that needed the foreground, then return to the background so you stop capturing focus.
Every justified raise is logged as a
foreground_raisedevent. And never fall back to an OS-level launch (launch_process/Start-Process/msedge/chrome --new-window) ordesktop_bring_to_frontto surface a window - that is the exact rude behavior the enforcement exists to stop; if a background open is ever blocked, hand the user a shareable link instead.
The "debugging this browser" bar is expected - and now steadier
CDP verbs (click, type, screenshot, eval, ...) attach chrome.debugger, which makes Chrome show
the yellow "Adom for Chrome & Edge" started debugging this browser bar. It is a Chrome security
guarantee and cannot be hidden on consumer Chrome (Adom-managed machines launch with
--silent-debugger-extension-api, which removes it). Two things you should know:
- It slides in/out and REFLOWS the page - each transition shifts content up/down, which can move where a click lands. As of v0.1.0 the idle-detach window is long (~45s) so the bar stays STEADY through a drive session instead of flickering between verbs; it still clears once you genuinely stop (not left up forever). Do a burst of driving, then let it settle - don't attach/detach rapidly.
- It only appears on a window you TARGETED. CDP verbs no longer fall back to the user's active tab -
nbrowser_screenshot/nbrowser_eval/nbrowser_errorsneed a sessionId/tabId, so the bar can never land on the user's own tab. If you only need to read/navigate your own window, prefer the native verbs (list/navigate - no bar).
Credentials and auto-login are gated - and you never see secrets
nbrowser_credentialsis a read-only query of which vendors the user has a saved login for. It never returns passwords; Chrome autofills those when you focus the field.- Auto-login is opt-in per call (
confirm:true+ an explicithost:) so it can never fire for an arbitrary site. The password is decrypted on the laptop and injected by the extension - the agent never sees it. - Treat everything you read from the user's logged-in sessions (
fetch_url, page content,evalresults) as sensitive. Do not exfiltrate it; use it for the task at hand.
Respect the user's blocklist
nbrowser_profile_block marks a profile hands-off; the bridge refuses to drive it until
nbrowser_profile_unblock. That is the user telling you no. Never work around it.
Recording: WGC first (not the extension's picker)
To record a browser window, use the core adom-desktop verb desktop_record_window_start (WGC), or
nbrowser_record_start {method:"wgc"} which delegates to it: background, high-fps, no picker, no
banner, captures occluded, no focus theft. The extension's own getdisplaymedia/tabcapture path is
a refused-by-default fallback (Chrome forces a manual share picker). Full detail: native-browser-recording.
The badge is the user's trust tell - never fake it, never blank it
While you drive, the bridge automatically paints the window's taskbar button with a composite badge: the profile's own circle avatar + the Adom favicon in its corner. That is how the user knows, at a glance, that an AI is in that browser and which profile it is. Three rules:
- Don't manage it. It applies at
open_window, follows your drive verbs, and idle-expires (~3 min of silence) back to the plain native avatar. A badge that lingers with nothing driving is a bug, not a feature - never re-apply it to a window you aren't actively working. - Never blank the overlay slot. Chrome/Edge keep the profile avatar there;
overlay:{badge:"none"}is safe (the bridge RESTORES the native avatar), but never try to clear the slot by other means. - The orange flash is the "come look" cue and the user can silence it (
flash_mode:"off"). Errors, human-gates (captcha/2FA/login), and fresh spawned windows auto-flash inquietmode; flash manually at task-done. Respectoffabsolutely. The bounded activity trail (nbrowser_activity) records who drove what and when - if the user asks "why is my browser badged?", query it instead of guessing.
Every verb tells you how to behave - read the hints
Every nbrowser_* response carries a _hint (what you got, what to do next, the trap to avoid), and
nbrowser_describe lists each verb's hint / related / pitfalls. When unsure, call
nbrowser_describe once and read the pitfalls - they encode these safety rules per verb.
The one-line version
Open your own window, work in the background, leave the user's tabs and focus untouched, keep their secrets secret, honor the blocklist, and record with WGC. If a move would change something the user did not ask you to change, don't.
---
name: native-browser-safety
description: "SAFETY rules for any agent driving the Adom browser extension (nbrowser_* verbs). This bridge drives the user's REAL, signed-in Chrome/Edge - their live tabs, forms, sessions, saved logins, and other AI threads. Read this BEFORE driving. The cardinal rule: only ever act on a window/tab YOU opened (nbrowser_open_window); NEVER navigate, reload, or close a window/tab the user already had open (this includes the OS-level desktop_navigate) - doing so destroyed a user's other Claude threads once. Also: background by default (never foreground unless asked), the CDP \"debugging\" bar, credentials/autologin are gated and never expose passwords, recording is WGC-first, and the user blocklist is hands-off. Match on: browser safety, agent browser isolation, don't touch my tabs, navigated my window, wiped my threads, never reuse a window, background by default, real browser safety, nbrowser safety, drive the real browser safely. Part of the adom-browser-extension."
---
Part of **adom-browser-extension**. Verb surface: **driving-the-extension**. Recording: **native-browser-recording**.
# Driving the user's REAL browser safely
`nbrowser_*` is not pup. It runs **inside the user's actual, signed-in Chrome/Edge**, so every action
lands on their live browser: open tabs they are reading, forms they are filling, authenticated
sessions, saved logins, and **other AI threads' tabs**. That power is the whole point (it sails
through logins and captchas pup cannot), and it is exactly why a careless move is destructive. These
rules are not optional polish; one of them exists because an agent wiped a user's other Claude threads.
## ⛔ Cardinal rule: only ever drive a window/tab YOU opened
**Open your own surface and work only in it.** `nbrowser_open_window` gives you a background window +
a `sessionId`; do ALL your driving through that `sessionId`.
**REGISTER every window (v0.1.0+).** `nbrowser_open_window` now REQUIRES three fields or it refuses
(`session_unregistered`):
```
nbrowser_open_window {sessionId:"<short task id>", thread:"<which AI thread you are>",
purpose:"<human-readable, e.g. finishing John's IRS signup>", url:"…"}
```
`purpose` shows up in `nbrowser_status` and the Session Monitor so the user can see who is driving each
window and why. No sessionId/thread/purpose = no window.
## ⛔ Track & reap the windows a page SPAWNS (the leaked-compose fix)
A page you drive can open a **new window you did not ask for**: clicking an email in Google Contacts
pops a **Gmail compose window**; `target=_blank` links, `window.open`, OAuth popups, and PDF viewers do
the same. These are standalone windows in **no session** - historically unreachable, so they leaked.
Now they are **auto-adopted** into your session. The moment one appears:
- a `spawned_window` event fires (see `nbrowser_events`), and
- your **next driving verb is refused (`unacknowledged_spawns`)** until you deal with it.
Deal with it: `nbrowser_owned {sessionId}` lists everything you own (`opened` + `spawned`); then either
keep what you need or **`nbrowser_cleanup {sessionId}`** to close the litter (it closes ONLY what you
spawned, never the user's own windows). **End every task** by closing what you own:
`nbrowser_close_window {sessionId, includeSpawned:true}`. If you ever find a TRUE orphan ABE can't
attribute, `nbrowser_force_close` is a deliberately hard, two-step last resort (it can never close
another thread's window). It has TWO forms behind the SAME gate (ticket → attest with a written
reason): **`{windowId}`** for windows the extension can see, and **`{hwnd}`** (v0.1.23+, from
`desktop_list_windows`) for browser windows the extension CANNOT reach — e.g. Chrome/Edge litter that a
`browser_open`/pup call or an HD test left behind, or a window in a profile with no extension. The hwnd
form only targets real browser windows and sends a graceful WM_CLOSE. So cleaning up ANOTHER tool's
browser litter is allowed — but only through this gate, with a real justification, and never a window a
live thread owns.
⚠️ **Pitfall — reading a contact spawns a compose:** to read a person's email/phone in Google Contacts,
**do not click the mailto/email link** (that spawns a compose window). Navigate the person's contact URL
or `nbrowser_fetch_url` the data instead.
**NEVER** navigate, reload, close, or repurpose a window or tab the user already had open:
- not with `nbrowser_navigate` / `nbrowser_reload` / `nbrowser_close_tab` / `nbrowser_close_window`,
- and **not with the OS-level `desktop_navigate` either** (same destruction, different door).
Why it is this strict: navigating a user's active tab yanks their live page out from under them and
**wipes anything unsaved - including other running AI threads**. That actually happened - twice
(2026-06 and again 2026-07-08, mid-Meet-call), which is why as of **extension v0.0.21 the code
enforces it**: every mutating verb refuses a bare call (`no_target` - the active-tab fallback is
gone) and refuses any target outside an agent-opened window (`not_owned`). There is **no
agent-passable override**; refusals return a teaching `_hint`, and each connection's first response
carries a one-time `_safety` banner. Do not attempt to work around a refusal - including
"restorative" moves like `history.back()` on the user's tab. If you damaged their state, STOP and
tell them; more uninvited commands make it worse.
**Map identities BEFORE driving.** `nbrowser_profiles` first, every time: pick the `browser:email`
profile matching the identity the task needs (a task about `[email protected]` → that profile, never
"the browser that happens to be active" - Chrome and Edge each hold different accounts).
The only thing you may do to one of the user's existing windows is `desktop_bring_to_front` to surface
it for them - and only when they explicitly ask to see it.
## ⛔ Background by default - never foreground unless asked
**The user is almost always doing something else on their computer** - reading, typing, on a call, in
another app. Your work has to **DEFAULT to the background, especially when you open new browser
windows**: a window that jumps in front of them interrupts what they were doing, and their next
keystrokes land in whatever control you just surfaced. Opening in the background is therefore the
**default posture, not a nicety**; foregrounding is **opt-in and happens ONLY when the user explicitly
asks to watch.** The verbs are built to stay out of their way:
`nbrowser_open_window` opens **unfocused**, `nbrowser_open_tab` opens `active:false`,
`nbrowser_switch_window` does **not** raise. **Leave those defaults alone.** You can fully see and
drive a window without foregrounding it: `nbrowser_screenshot` (CDP) and `desktop_screenshot_window`
(WGC) both capture a **background/occluded** window, and WGC records one too. So there is never a
reason to pull a window forward. Foregrounding is the **user's** choice (`desktop_bring_to_front`),
made by them, only when they want to watch.
This is not just etiquette - **foregrounding corrupts your own work.** When you steal focus while the
user is typing, **their next keystrokes land in whatever control you just focused** (a search box, a
text field), silently changing your state and derailing the task (real case: a foregrounded
`chrome://extensions` search turned into `Adoms` mid-flow and the target extension vanished). Note this
applies to **SendInput verbs too** - `desktop_type` / `desktop_press_key` / `desktop_click` foreground
the target before sending. Prefer background UIA (`desktop_ui_click` Invoke, `desktop_ui_set` SetValue);
if a step genuinely needs foreground keyboard input, caption the user to pause, do the one action, and
return focus immediately.
⚠️ **Foreground can happen even when you NEVER call `bring_to_front`.** Opening a new window, and
especially **`desktop_navigate`** (its omnibox SetValue + posted-Enter commit can surface/activate the
window to land the Enter), may bring a browser window to the FRONT despite `focused:false`. The rule is
about the **outcome, not the verb**: after any open or navigate, check `desktop_list_windows` (a window
at `z:0` is topmost = you surfaced it) and treat a window that jumped forward as a disruption. Minimize
opens and navigations, drive `chrome://` pages only when the user is not mid-task, and never repeatedly
surface a window. (Observed live: a `desktop_navigate` to `chrome://extensions` left the window at `z:0`
even though no `bring_to_front` was called and the window was opened `focused:false`.)
**The bridge now ENFORCES all of this in code (ext >= 0.0.22) - you cannot shove a window in front of
the user even by accident.** `open_window`/`open_tab` open in the background regardless of a stray
`focused:true`/`active:true` (those legacy flags are IGNORED); `switch_window`/`focus` never raise; and
after every open/navigate/switch/state change the bridge checks the z-order - if a driven window landed
topmost, it is **auto-sent back to the background** (focus returned to the user's previous window, or the
window minimized if the user was in another app) and the response carries a `_warning` +
`focusAutoReverted:true`. Each auto-correction is logged as a `focus_violation` event
(`nbrowser_events`) so the user can see when a thread tried to steal focus.
**Foregrounding takes real work now (v0.1.0+), because it STEALS the user's keyboard focus.** Users
automating the browser almost always have 5 other things in the foreground; a raised window captures
their keystrokes (real incident: it typed a user's chat message into a username field and dismissed
Chrome's password dropdown, twice). So a bare `userRequestedForeground:true` is **no longer enough** -
without a `foregroundReason` the window **stays background**. To genuinely foreground (some auth pages /
browser features truly need it):
1. **Warn the user first** with an AD notify: `adom-desktop notify_user {message:"Adom is about to bring a browser window forward for <reason>"}`.
2. Then pass **`userRequestedForeground:true` + `foregroundReason:"<why>"`**.
3. Do the ONE thing that needed the foreground, then return to the background so you stop capturing focus.
Every justified raise is logged as a `foreground_raised` event. And **never** fall back to an OS-level
launch (`launch_process` / `Start-Process` / `msedge`/`chrome --new-window`) or `desktop_bring_to_front`
to surface a window - that is the exact rude behavior the enforcement exists to stop; if a background
open is ever blocked, hand the user a shareable link instead.
## The "debugging this browser" bar is expected - and now steadier
CDP verbs (`click`, `type`, `screenshot`, `eval`, ...) attach `chrome.debugger`, which makes Chrome show
the yellow `"Adom for Chrome & Edge" started debugging this browser` bar. It is a Chrome security
guarantee and **cannot be hidden on consumer Chrome** (Adom-managed machines launch with
`--silent-debugger-extension-api`, which removes it). Two things you should know:
- **It slides in/out and REFLOWS the page** - each transition shifts content up/down, which can move
where a click lands. As of v0.1.0 the idle-detach window is **long (~45s)** so the bar stays STEADY
through a drive session instead of flickering between verbs; it still clears once you genuinely stop
(not left up forever). Do a burst of driving, then let it settle - don't attach/detach rapidly.
- **It only appears on a window you TARGETED.** CDP verbs no longer fall back to the user's active tab -
`nbrowser_screenshot`/`nbrowser_eval`/`nbrowser_errors` need a sessionId/tabId, so the bar can never
land on the user's own tab. If you only need to read/navigate your own window, prefer the native verbs
(list/navigate - no bar).
## Credentials and auto-login are gated - and you never see secrets
- `nbrowser_credentials` is a **read-only query** of which vendors the user has a saved login for. It
**never returns passwords**; Chrome autofills those when you focus the field.
- Auto-login is **opt-in per call** (`confirm:true` + an explicit `host:`) so it can never fire for an
arbitrary site. The password is decrypted on the laptop and injected by the extension - the agent
never sees it.
- Treat everything you read from the user's logged-in sessions (`fetch_url`, page content, `eval`
results) as **sensitive**. Do not exfiltrate it; use it for the task at hand.
## Respect the user's blocklist
`nbrowser_profile_block` marks a profile **hands-off**; the bridge refuses to drive it until
`nbrowser_profile_unblock`. That is the user telling you no. Never work around it.
## Recording: WGC first (not the extension's picker)
To record a browser window, use the core adom-desktop verb `desktop_record_window_start` (WGC), or
`nbrowser_record_start {method:"wgc"}` which delegates to it: background, high-fps, no picker, no
banner, captures occluded, no focus theft. The extension's own `getdisplaymedia`/`tabcapture` path is
a refused-by-default fallback (Chrome forces a manual share picker). Full detail: **native-browser-recording**.
## The badge is the user's trust tell - never fake it, never blank it
While you drive, the bridge automatically paints the window's taskbar button with a **composite badge:
the profile's own circle avatar + the Adom favicon in its corner**. That is how the user knows, at a
glance, that an AI is in that browser and *which profile* it is. Three rules:
- **Don't manage it.** It applies at `open_window`, follows your drive verbs, and idle-expires (~3 min
of silence) back to the plain native avatar. A badge that lingers with nothing driving is a bug, not
a feature - never re-apply it to a window you aren't actively working.
- **Never blank the overlay slot.** Chrome/Edge keep the profile avatar there; `overlay:{badge:"none"}`
is safe (the bridge RESTORES the native avatar), but never try to clear the slot by other means.
- **The orange flash is the "come look" cue** and the user can silence it (`flash_mode:"off"`). Errors,
human-gates (captcha/2FA/login), and fresh spawned windows auto-flash in `quiet` mode; flash manually
at task-done. Respect `off` absolutely.
The bounded activity trail (`nbrowser_activity`) records who drove what and when - if the user asks
"why is my browser badged?", query it instead of guessing.
## Every verb tells you how to behave - read the hints
Every `nbrowser_*` response carries a `_hint` (what you got, what to do next, the trap to avoid), and
`nbrowser_describe` lists each verb's `hint` / `related` / `pitfalls`. When unsure, call
`nbrowser_describe` once and read the `pitfalls` - they encode these safety rules per verb.
## The one-line version
Open your own window, work in the background, leave the user's tabs and focus untouched, keep their
secrets secret, honor the blocklist, and record with WGC. If a move would change something the user
did not ask you to change, don't.