← Commit history
bridge 0.1.1: screenshot best-practice hints (page=nbrowser_screenshot, window+chrome+child-popups=desktop_screenshot_window parent/child hwnds, NEVER fullscreen) + open_window returns hwnd
6 files changed
+36−23
bridge/BRIDGE_VERSION+1−1@@ -1 +1 @@-0.1.0+0.1.1
bridge/bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "native-browser", "displayName": "Adom Native Browser (Chrome + Edge)",- "version": "0.1.0",+ "version": "0.1.1", "expectedExtensionVersion": "0.1.0", "description": "Drives the user's REAL signed-in Chrome/Edge via an installed extension (native-messaging transport). Mirrors the puppeteer bridge's verb surface as nbrowser_* but runs inside the actual logged-in profile, so cookies/SSO/saved-logins/captcha human-trust all work. The bridge binds an ephemeral loopback port (PORTS.md compliant) and writes a discovery file; the native-messaging host pumps stdio <-> this bridge; the extension never touches a TCP port.", "homepage": "https://wiki.adom.inc/adom/adom-browser-extension",
bridge/package.json+1−1@@ -1,6 +1,6 @@ { "name": "adom-native-browser-bridge",- "version": "0.1.0",+ "version": "0.1.1", "private": true, "description": "adom-desktop bridge for the Adom native-browser extension (Chrome + Edge)", "main": "server.js",
bridge/server.js+14−14@@ -796,8 +796,8 @@ const VERB_META = { nbrowser_wait: { hint: 'Sleep server-side (let a page settle) before the next verb.', related: ['nbrowser_wait_for', 'nbrowser_screenshot'], pitfalls: ['prefer nbrowser_wait_for for a DETERMINISTIC wait (selector/url/text) instead of a fixed sleep'] }, nbrowser_wait_for: { hint: 'DETERMINISTIC wait after a click/nav: block until a CSS selector exists / the URL contains a substring / the visible text contains a substring, or timeoutMs. Use this instead of nbrowser_wait + fixed sleeps + coordinate races. Returns {matched, by, url, waitedMs}.', related: ['nbrowser_click', 'nbrowser_navigate', 'nbrowser_eval'], pitfalls: ['pass at least one of selector / urlIncludes / textIncludes', 'matched:false + timedOut:true means the condition never appeared - screenshot to see the real page', 'selector matches the top document only (not inside cross-origin iframes)'] }, nbrowser_keepalive: { hint: 'Keep a session ACTIVE to avoid vendor idle sign-outs (Gusto/Intuit ~10-15 min): replays a benign mousemove/focus on an interval (NO navigation, no side-effects). Per-session; auto-stops when the window closes. Call with {stop:true} to cancel. Set at the START of a long unattended automation.', related: ['nbrowser_open_window', 'nbrowser_login_state'], pitfalls: ['client-side activity only - a vendor that expires the SERVER session regardless may still sign out; combine with periodic real actions if so', 'default interval 4min (30s..15min); pick below the vendor idle timeout'] },- nbrowser_screenshot: { hint: 'CDP screenshot - captures a BACKGROUND/occluded window WITHOUT foregrounding it. This is how you "see" a page; never pull a window forward to grab a shot.', related: ['nbrowser_eval', 'nbrowser_click'], pitfalls: ['resized <=1568 for the model; pass toFile:true to save full-res to a laptop file (pull_file it)'] },- nbrowser_screenshot_full_res: { hint: 'Full-resolution / full-page screenshot via CDP.', related: ['nbrowser_screenshot'], pitfalls: ['large base64 can be slow over the relay - prefer toFile then pull_file'] },+ nbrowser_screenshot: { hint: 'CDP screenshot of the PAGE - captures a BACKGROUND/occluded window WITHOUT foregrounding it. Your DEFAULT way to "see" a page. It captures page CONTENT only - NOT the browser chrome (URL/tabs) and NOT native popups/dialogs (Chrome password dropdown, print dialog, alert/confirm) which live OUTSIDE the page.', related: ['nbrowser_eval', 'nbrowser_click', 'nbrowser_handle_dialog'], pitfalls: ['resized <=1568 for the model; pass toFile:true to save full-res to a laptop file (pull_file it)', 'to see the WINDOW FRAME or a native popup/dropdown/dialog, use AD desktop_screenshot_window {hwnd | titleContains} (WGC): it captures the window PLUS its OWNED CHILD windows (returns a parent/child hwnd array, so you see popups like the password dropdown), and it is background-safe (no foregrounding)', '⛔ NEVER take a fullscreen / whole-monitor screenshot to inspect a browser window - it grabs the user\'s other work, is imprecise, and often implies foregrounding. Page → nbrowser_screenshot; window/chrome/popup → desktop_screenshot_window'] },+ nbrowser_screenshot_full_res: { hint: 'Full-resolution, full-PAGE screenshot via CDP (the entire scrollable page, not just the viewport). Still PAGE content only.', related: ['nbrowser_screenshot'], pitfalls: ['this is full-PAGE, NOT "fullscreen monitor" - it never captures the desktop or the user\'s other windows', 'large base64 can be slow over the relay - prefer toFile then pull_file', 'for the window frame or a native dialog/popup, use AD desktop_screenshot_window {hwnd | titleContains} (parent/child hwnds), never a monitor capture'] }, nbrowser_eval: { hint: 'Run JS in the page and get the result. Powerful - use read-only expressions to inspect state.', related: ['nbrowser_screenshot'], pitfalls: ['runs arbitrary JS on the user\'s LIVE session - never run destructive scripts'] }, nbrowser_evaluate: { hint: 'Alias of nbrowser_eval.', related: ['nbrowser_eval'], pitfalls: ['runs arbitrary JS on the user\'s LIVE session - never run destructive scripts'] }, nbrowser_input_dispatch: { hint: 'Low-level isTrusted CDP input (click/move/type/key).', related: ['nbrowser_click', 'nbrowser_type'], pitfalls: ['prefer nbrowser_click (smart selector) over raw xy'] },@@ -935,7 +935,7 @@ function dispatch(verb, args) { d('nbrowser_install_chrome', 'Install NATIVE Google Chrome for the user (Windows). Downloads Google\'s official installer + runs a quiet user-scoped install (no admin on a normal PC); escalates to a notify-first UAC on a locked-down box. NON-BLOCKING - poll nbrowser_chrome_status. Step 1 of putting the AI onto the user\'s real signed-in browser.', { elevate: 'optional "auto"(default) | true (skip quiet, straight to UAC) | false (quiet only, no prompt)' }, { ok: 'bool', installing: 'bool', installed: 'bool?', alreadyInstalled: 'bool?', executablePath: 'str?', statusVerb: 'str' }, 30, { statusVerb: 'nbrowser_chrome_status', example: {} }), d('nbrowser_chrome_status', 'READ-ONLY: is native Google Chrome installed / how is an install going? Poll after nbrowser_install_chrome.', {}, { installed: 'bool', executablePath: 'str?', phase: '"idle"|"installing"|"awaiting_uac"|"ready"|"failed"', installing: 'bool', awaitingUac: 'bool', errorCode: 'str?' }, 15, { example: {} }), d('nbrowser_ping', 'Round-trip liveness check through to the extension.', {}, { ok: 'bool', pong: 'bool' }, 15),- d('nbrowser_open_window', 'Open a NEW window in the BACKGROUND (unfocused) and return a sessionId to drive it. REGISTRATION REQUIRED: sessionId + thread + purpose, or refused (session_unregistered). Never steals focus; a topmost driven window is auto-reverted (focus_violation).', { sessionId: 'required str (your short task id)', thread: 'required str (which AI thread you are)', purpose: 'required str (human-readable, e.g. "finishing John\'s IRS signup")', url: 'required str', profile: P, userRequestedForeground: 'optional bool - raise to foreground ONLY when the user asked or a browser/auth feature requires it (default false)', foregroundReason: 'REQUIRED with userRequestedForeground - human-readable why; without it the window STAYS background. Warn the user via AD notify_user FIRST.' }, { ok: 'bool', sessionId: 'str', tabId: 'str', windowId: 'int', url: 'str', _warning: 'str?', keptBackground: 'bool?' }, 30, { example: { sessionId: 'irs', thread: 'thread-7', purpose: 'finishing John IRS signup', url: 'https://wiki.adom.inc' } }),+ d('nbrowser_open_window', 'Open a NEW window in the BACKGROUND (unfocused) and return a sessionId to drive it. REGISTRATION REQUIRED: sessionId + thread + purpose, or refused (session_unregistered). Never steals focus; a topmost driven window is auto-reverted (focus_violation).', { sessionId: 'required str (your short task id)', thread: 'required str (which AI thread you are)', purpose: 'required str (human-readable, e.g. "finishing John\'s IRS signup")', url: 'required str', profile: P, userRequestedForeground: 'optional bool - raise to foreground ONLY when the user asked or a browser/auth feature requires it (default false)', foregroundReason: 'REQUIRED with userRequestedForeground - human-readable why; without it the window STAYS background. Warn the user via AD notify_user FIRST.' }, { ok: 'bool', sessionId: 'str', tabId: 'str', windowId: 'int', hwnd: 'int (OS window handle - use with desktop_screenshot_window to capture the window+chrome+child popups)', url: 'str', _warning: 'str?', keptBackground: 'bool?' }, 30, { example: { sessionId: 'irs', thread: 'thread-7', purpose: 'finishing John IRS signup', url: 'https://wiki.adom.inc' } }), d('nbrowser_close_window', 'Close a window the agent opened. includeSpawned:true also closes everything that session SPAWNED (composes/popups).', { sessionId: 'required str', includeSpawned: 'optional bool (also close this session\'s spawned windows)', profile: P }, { ok: 'bool', closed: 'bool', sessionId: 'str', alsoClosedSpawned: '[int]' }, 20, { example: { sessionId: 'work', includeSpawned: true } }), d('nbrowser_owned', 'List everything a session owns: opened windows + adopted SPAWNED windows/tabs (compose/popup/target=_blank a driven page opened). Acknowledges spawns.', { sessionId: 'optional str (all sessions if omitted)', profile: P }, { ok: 'bool', opened: '[{sessionId,windowId,url,title,purpose,ageMs}]', spawned: '[{kind,windowId,tabId,url,ageMs,spawnedBy,opener}]' }, 20, { example: { sessionId: 'irs' } }), d('nbrowser_cleanup', 'Close ONLY what a session SPAWNED (leaked composes/popups). Never the user\'s windows, never your primary opened window.', { sessionId: 'required str', profile: P }, { ok: 'bool', closed: '[{windowId,url}]', count: 'int', keptOpenedWindow: 'int?' }, 20, { example: { sessionId: 'irs' } }),@@ -1221,7 +1221,7 @@ function dispatch(verb, args) { }); } -function onHostFrame(frame) {+async function onHostFrame(frame) { if (frame && frame.id != null && pending.has(frame.id)) { const p = pending.get(frame.id); clearTimeout(p.timer);@@ -1270,16 +1270,16 @@ function onHostFrame(frame) { if (p.verb === 'nbrowser_open_window') { const sid = payload.sessionId; // Robust hwnd capture: the diff can miss on the first pass (window not painted yet), so retry- // briefly so a window never ends up silently UNBADGED.- (async () => {- for (let i = 0; i < 4; i++) {- let neu;- try { const after = await chromeHwnds(); neu = [...after].find((h) => !preOpenHwnds.has(h)); } catch {}- if (neu) { lastHwnd = neu; if (sid) sessionHwnd.set(sid, neu); taskbar(neu, { overlay: { badge: 'adom', tooltip: 'Adom is driving this browser' } }); return; }- await new Promise((r) => setTimeout(r, 250));- }- })();- payload = { ...payload, _taskbarHint: flashHint() };+ // briefly - so a window is never silently UNBADGED, and we can hand the AI its OS hwnd for+ // desktop_screenshot_window (the window+chrome+child-popup capture technique).+ let neu = null;+ for (let i = 0; i < 4; i++) {+ try { const after = await chromeHwnds(); neu = [...after].find((h) => !preOpenHwnds.has(h)) || null; } catch {}+ if (neu) { lastHwnd = neu; if (sid) sessionHwnd.set(sid, neu); taskbar(neu, { overlay: { badge: 'adom', tooltip: 'Adom is driving this browser' } }); break; }+ await new Promise((r) => setTimeout(r, 250));+ }+ payload = { ...payload, ...(neu ? { hwnd: neu } : {}), _taskbarHint: flashHint(),+ _screenshotHint: 'To SEE this window without foregrounding it: nbrowser_screenshot {sessionId} = the PAGE bitmap (fast, default). To see the WINDOW FRAME or a native popup/dialog (Chrome password dropdown, print dialog, alert/confirm), use AD desktop_screenshot_window {hwnd:' + (neu || '<from desktop_list_windows by title>') + '} - it captures the window + its OWNED CHILD windows (parent/child hwnds). NEVER a fullscreen/monitor screenshot to inspect a browser window.' }; } // Badge any adopted SPAWNED windows surfaced by owned/list_windows (they have their own hwnd, no // sessionId, so we correlate by bounds on-demand — there is no push channel).
native-browser-bridge-manifest.json+4−4@@ -1,8 +1,8 @@ { "manifest_version": 1,- "version": "0.1.0",- "url": "https://wiki.adom.inc/download/adom/adom-browser-extension/0.1.0/native-browser-bridge-v0.1.0.bin",- "sha256": "93d416ed71c3922a1b48fba3b955bc55a697846805043c422f5c73bd658f528d",- "size": 52755,+ "version": "0.1.1",+ "url": "https://wiki.adom.inc/download/adom/adom-browser-extension/0.1.1/native-browser-bridge-v0.1.1.bin",+ "sha256": "5412f87a9f81ee457f4c2315dba5ad464446b33f600adeb260b1df64013b39e9",+ "size": 53513, "released_at": "2026-07-14T00:00:00Z" }
skills/driving-the-extension/SKILL.md+15−2@@ -308,9 +308,22 @@ a `_modal` note. **Dismiss it:** `nbrowser_handle_dialog {sessionId, accept:true `accept:false` (Cancel/Stay), or `text:"…"` to answer a prompt - then re-screenshot and continue. If an action mysteriously stops working, suspect a dialog. -## Screenshots+## Screenshots — pick the RIGHT one (page vs window vs NEVER fullscreen)+Three ways to "see", and the choice matters:+- **The PAGE → `nbrowser_screenshot {sessionId}`** (CDP). Your DEFAULT. Fast, background-safe, captures the+ page bitmap. But it shows **page content ONLY** - no browser chrome (URL/tabs) and **no native popups/+ dialogs** (Chrome's saved-password dropdown, print dialog, `alert`/`confirm`) which render OUTSIDE the page.+- **The WINDOW + chrome + native popups → `desktop_screenshot_window {hwnd | titleContains}`** (AD, WGC).+ Use this when you need the URL bar, a `chrome://` page, or a native popup/dialog. It captures the window+ **plus its OWNED CHILD windows and returns the full parent/child hwnd array** - the same technique that+ makes KiCad/Fusion error dialogs visible. Background-safe (no foregrounding). `nbrowser_open_window`+ returns the window's **`hwnd`** for exactly this; or find it via `desktop_list_windows` by title.+- **⛔ NEVER a fullscreen / whole-monitor screenshot to inspect a browser window.** It grabs the user's+ other work, is imprecise, and usually implies foregrounding. There is always a targeted option above.+ (`nbrowser_screenshot_full_res` is full-**PAGE**, not full-monitor - it still only sees the page.) ```bash-adom-desktop -t AdomLapper nbrowser_screenshot '{"sessionId":"s1","toFile":true}' # writes a laptop PNG, returns its path+adom-desktop -t AdomLapper nbrowser_screenshot '{"sessionId":"s1","toFile":true}' # PAGE bitmap → laptop PNG path+adom-desktop -t AdomLapper desktop_screenshot_window '{"hwnd":<from open_window>}' # WINDOW + chrome + child popups # then: adom-desktop -t AdomLapper pull_file '{"filePaths":["<path>"],"saveTo":"/tmp"}' ``` Screenshots are large base64 - over the relay they can time out, so **prefer `toFile:true` + `pull_file`**