← Commit history

v0.16.6 (insiders): Layer 1 on a fresh box: profiles with no signed-in account are listed (anonymous:true, keyed by directory such as edge:Default) and nbrowser_open_os_window can open URLs in them; before this a virgin Windows install with only an unsigned Edge had no nb path at all. Skills: wake-then-reload rule (a woken profile runs stale code while announcing the new version), restoring a missing native-messaging host without the installer, taskbar badge behaviour on combined buttons, Mouser's automation challenge.

John Lauer ·d26bf03185 ·2mo ago ·parent c8e147b
9 files changed +51−10
bin/nb-dashboard+1−1
@@ -20,7 +20,7 @@ const { execFile, spawn } = require('child_process'); const fs = require('fs'); const crypto = require('crypto'); -const VERSION = '0.16.5';           // keep in lockstep with package.json (pkg version)+const VERSION = '0.16.6';           // keep in lockstep with package.json (pkg version) // SEND TO AI IS UN-PARKED (John: "yeah, we are re-enabling the send to ai. it seems to be good now"). // The ah build thread certified the crash family dead on the gate-fixed build and the delivery leg is // proven end to end. The park const is DELETED rather than flipped, so no dead branch can resurrect it.
bridge/BRIDGE_VERSION+1−1
@@ -1 +1 @@-0.16.5\ No newline at end of file+0.16.6\ No newline at end of file
bridge/bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "native-browser",   "displayName": "Adom Native Browser",-  "version": "0.16.5",+  "version": "0.16.6",   "expectedExtensionVersion": "0.2.10",   "description": "Drives the user's REAL signed-in Chrome/Edge, in two layers. Layer 1 (nb) needs NOTHING installed and works on any machine: nbrowser_open_os_window (open a URL in the user's real profile, background+flashing, the OAuth/consent path), nbrowser_os_windows, nbrowser_profiles, nbrowser_wake_profile, nbrowser_force_close. Layer 2 (nbe, the Adom Native Browser Extension) unlocks the rest: CDP driving (navigate/click/read/screenshot) inside their logged-in profile. The bridge binds an ephemeral loopback port and the native-messaging host pumps stdio <-> bridge.",   "homepage": "https://wiki.adom.inc/adom/nb-bridge",
bridge/package.json+1−1
@@ -1,6 +1,6 @@ {   "name": "adom-native-browser-bridge",-  "version": "0.16.5",+  "version": "0.16.6",   "private": true,   "description": "Native-browser bridge for Bridge (ab): layer 1 (nb) plus layer 2 via nbe, the Adom Native Browser Extension (Chrome + Edge).",   "main": "server.js",
bridge/server.js+11−5
@@ -1477,7 +1477,7 @@ const VERB_META = {   nbrowser_fullscreen: { hint: 'Fullscreen the window (hides tabs/URL bar) - great for clean recordings of a BACKGROUND window. ' + NO_FG, related: ['nbrowser_window_state'], pitfalls: ['F11-via-key does NOT work; use this verb', 'stays in the background (auto-reverted) unless userRequestedForeground:true - it does not raise the window over the user\'s work'] },   nbrowser_login_state: { hint: 'Is the profile signed in to a URL right now (via chrome.cookies)?', related: ['nbrowser_credentials'], pitfalls: ['cookie-based inference, not definitive - a page may still challenge for 2FA even when auth cookies exist', 'never returns the password; Chrome autofills it when you focus the field'] },   nbrowser_credentials: { hint: 'Read-only QUERY of the browser\'s own saved-login store (which vendors the user can auto-login to). Never returns passwords.', related: ['nbrowser_login_state', 'nbrowser_navigate'], pitfalls: ['if a vendor is not listed, the user has no saved login - ask them to save one once', 'never exposes the password; Chrome autofills it when you focus the field'] },-  nbrowser_profiles: { hint: 'List every BROWSER x PROFILE you can drive + which is active. Each row carries browser (chrome|edge), email, displayName (the human label the user sees on the browser profile button, e.g. "John Personal" vs "adom.inc"), and profileDir. Pick work-vs-personal from the email DOMAIN + displayName, never from which window happens to be focused. Drive several in parallel with profile:"<key>".', related: ['nbrowser_use_profile', 'nbrowser_profile_block', 'nbrowser_resolve_profiles'], pitfalls: ['active is only the default when no profile is passed', 'an entry with unresolved:true has no email yet - nbrowser_resolve_profiles first, or you may drive the wrong account', 'the same email can live in Chrome AND Edge - match browser too', 'asleep:true = the profile exists (Local State) but has no open window so its extension is not running - it cannot be driven until the user opens a window in it; never claim a profile "does not exist" when it is merely asleep'] },+  nbrowser_profiles: { hint: 'List every BROWSER x PROFILE you can drive + which is active. Profiles with NO signed-in account are listed too (anonymous:true, key browser:<dir> such as edge:Default): they cannot be driven by email but Layer 1 (nbrowser_open_os_window) can open URLs in them, which is the whole nb path on a fresh box. Each row carries browser (chrome|edge), email, displayName (the human label the user sees on the browser profile button, e.g. "John Personal" vs "adom.inc"), and profileDir. Pick work-vs-personal from the email DOMAIN + displayName, never from which window happens to be focused. Drive several in parallel with profile:"<key>".', related: ['nbrowser_use_profile', 'nbrowser_profile_block', 'nbrowser_resolve_profiles'], pitfalls: ['active is only the default when no profile is passed', 'an entry with unresolved:true has no email yet - nbrowser_resolve_profiles first, or you may drive the wrong account', 'the same email can live in Chrome AND Edge - match browser too', 'asleep:true = the profile exists (Local State) but has no open window so its extension is not running - it cannot be driven until the user opens a window in it; never claim a profile "does not exist" when it is merely asleep'] },   nbrowser_use_profile: { hint: 'Set the default profile that verbs target.', related: ['nbrowser_profiles', 'nbrowser_pin_session'], pitfalls: ['you can still override per-call with profile:"<email>"'] },   nbrowser_pin_session: { hint: 'Pin a sessionId to a profile so EVERY verb carrying that sessionId auto-routes to it (no cross-profile mistakes when driving several accounts). open_window pins automatically to the profile it opened in; use this to re-pin explicitly.', related: ['nbrowser_profiles', 'nbrowser_resolve_profiles', 'nbrowser_open_window'], pitfalls: ['pins are per-session; a verb with an explicit profile: still overrides the pin', 'if the pinned profile goes offline, verbs fall back to the active profile'] },   nbrowser_open_os_window: { hint: 'EXTENSION-FREE: open a URL in the user\'s REAL browser profile (Chrome or Edge) when nbe is not installed there - the graceful-degradation path for flows where the USER does the clicking, above all OAuth consent (adom-google, Autodesk APS: they are already signed in; they just click Approve). BACKGROUND by default: brief blip, then minimized + ORANGE flashing taskbar button = "come click when ready". Registration (sessionId+thread+purpose) required like every window.', related: ['nbrowser_profiles', 'nbrowser_os_windows', 'nbrowser_force_close', 'nbrowser_wake_profile'], pitfalls: ['you CANNOT read, drive, or screenshot this window - no extension in that profile; poll the outcome out-of-band (OAuth token poll) or ask the user', 'if the extension IS installed in the profile, prefer wake_profile + open_window (full control, guards, cleanup)', 'foreground:true needs foregroundReason (+ notify_user first) - background+flash is almost always right', 'the background MINIMIZE never touches the workspace/editor window or another thread\'s live session (they are excluded); a plain new window just adds to the running browser, it does not restart it', 'opens MINIMIZED + flashing by default; to raise it later use desktop_bring_to_front {hwnd} (returned in the result) - only when the user asks to see it, never for your own convenience', 'close it afterwards with nbrowser_force_close {hwnd} - never leave auth litter', 'every response nudges the nbe install - relay the upsell to the user at a natural moment, never mid-auth'] },@@ -1982,7 +1982,7 @@ function dispatch(verb, args) {       const want = String(a.profile || a.email || '');       const wantEmail = (want.includes(':') ? want.split(':').slice(1).join(':') : want).toLowerCase();       const wantBrowser = want.includes(':') ? want.split(':')[0] : (a.browser || null);-      const kp = knownProfiles().find((pr) => pr.email.toLowerCase() === wantEmail && (!wantBrowser || pr.browser === wantBrowser));+      const kp = knownProfiles().find((pr) => (pr.email.toLowerCase() === wantEmail || pr.profileDir.toLowerCase() === wantEmail) && (!wantBrowser || pr.browser === wantBrowser));       if (!kp) return resolve({ success: false, error: `no local browser profile matches '${want}'`, _hint: 'nbrowser_profiles lists every profile on this machine (live + asleep) with emails and displayNames - pick the identity the task needs.' });       if (isBlocked(kp.key)) return resolve({ success: false, error: `profile '${kp.key}' is blocklisted`, _hint: 'The user blocked this profile from being driven.' });       if (a.foreground === true && !a.foregroundReason) {@@ -2272,7 +2272,7 @@ function dispatch(verb, args) {       const findLive = () => { for (const [k, c] of conns) if (c.sock && !c.sock.destroyed && String(c.profile.email || '').toLowerCase() === wantEmail && (!wantBrowser || c.profile.browser === wantBrowser)) return k; return null; };       const already = findLive();       if (already) return resolve({ success: true, output: JSON.stringify({ ok: true, alreadyLive: true, profile: already, _hint: 'Profile is already connected - drive it now (open_window still opens in the BACKGROUND by default).' }) });-      const kp = knownProfiles().find((pr) => pr.email.toLowerCase() === wantEmail && (!wantBrowser || pr.browser === wantBrowser));+      const kp = knownProfiles().find((pr) => (pr.email.toLowerCase() === wantEmail || pr.profileDir.toLowerCase() === wantEmail) && (!wantBrowser || pr.browser === wantBrowser));       if (!kp) return resolve({ success: false, error: `no local browser profile matches '${want}'`, _hint: 'nbrowser_profiles lists every profile on this machine (live + asleep). Check the email/browser.' });       if (isBlocked(kp.key)) return resolve({ success: false, error: `profile '${kp.key}' is blocklisted`, _hint: 'The user blocked this profile. nbrowser_profile_unblock first (with the user\'s consent).' });       const local = process.env.LOCALAPPDATA;@@ -2574,7 +2574,7 @@ function dispatch(verb, args) {         if (want0) {           const wantEmail0 = (String(want0).includes(':') ? String(want0).split(':').slice(1).join(':') : String(want0)).toLowerCase();           const wb0 = String(want0).includes(':') ? String(want0).split(':')[0] : (args && args.browser) || null;-          const kp0 = knownProfiles().find((pr) => pr.email.toLowerCase() === wantEmail0 && (!wb0 || pr.browser === wb0));+          const kp0 = knownProfiles().find((pr) => (pr.email.toLowerCase() === wantEmail0 || pr.profileDir.toLowerCase() === wantEmail0) && (!wb0 || pr.browser === wb0));           if (kp0) { const st0 = adomExtStateForProfile(kp0.browser, kp0.profileDir); diag = ` DIAGNOSIS for ${kp0.key} (${kp0.displayName}, ${kp0.profileDir}): ${extFixHint(kp0.browser, kp0.profileDir, st0)} If it IS installed+enabled, nbrowser_wake_profile {profile:"${kp0.key}"} wakes it with NO window and NO foreground.`; }         }       } catch {}@@ -2941,10 +2941,16 @@ function knownProfiles() {       const cache = (j.profile && j.profile.info_cache) || {};       for (const [dir, info] of Object.entries(cache)) {         const email = String((info && info.user_name) || '');-        if (!email.includes('@')) continue;         const names = browser === 'edge' ? ['Edge Profile Picture.png', 'Google Profile Picture.png'] : ['Google Profile Picture.png', 'Edge Profile Picture.png'];         let hasAvatar = false;         for (const n of names) if (fs.existsSync(path.join(root, dir, n))) { hasAvatar = true; break; }+        if (!email.includes('@')) {+          // ANONYMOUS PROFILE (release proof on the virgin VM, 2026-09-04): a browser profile with no signed-in+          // account used to be invisible, so a fresh Windows box with only an unsigned Edge had NO nb path at+          // all, not even Layer 1. It is addressable by its directory instead: edge:Default, chrome:Profile 2.+          out.push({ email: '', key: `${browser}:${dir}`, browser, displayName: String((info && info.name) || dir), profileDir: dir, hasAvatar, anonymous: true });+          continue;+        }         out.push({ email, key: `${browser}:${email}`, browser, displayName: String((info && info.name) || ''), profileDir: dir, hasAvatar });       }     } catch {}
package.json+1−1
@@ -1,6 +1,6 @@ {   "slug": "nb-bridge",-  "version": "0.16.5",+  "version": "0.16.6",   "type": "app",   "title": "NB - Native Browser Bridge",   "description": "This package installs the SKILLS into your container so your AI knows how to drive your real signed-in Chrome and Edge; Bridge loads the bridge runtime itself from the release zip.",
skills/adom-native-browser/SKILL.md+8
@@ -12,6 +12,14 @@ human-trust signals all apply. It sails through **login walls and captchas** tha Chrome-for-Testing. When a task needs the user's *real* browser or their *real* logins, this is the tool - not pup. +## A fresh box with no signed-in browser (Layer 1 still works, from 0.16.6)++`nbrowser_profiles` lists profiles with NO signed-in account as `anonymous:true`, keyed by directory+(`edge:Default`, `chrome:Profile 2`). They cannot be driven by email and have no extension, but+`nbrowser_open_os_window {profile:"edge:Default", url}` opens URLs in them, which is the whole nb path on+a virgin Windows install (winvm, 2026-09-04: no Chrome, unsigned Edge, and before 0.16.6 the profile list+was empty so nb had nothing to open).+ ## When to reach for this (vs pup)  | The task | Tool |
skills/driving-the-extension/SKILL.md+11
@@ -296,6 +296,17 @@ This is **not** screenshot→eyeball-a-pixel→click automation, so it does **no   the app state. **An unknown `input_dispatch` type is REFUSED (`unknown_input_type`)**; before 0.2.10 it fell   through to a click, which on a canvas editor with a tool armed placed four clicks for four zoom attempts.   Valid types: click | dblclick | rightclick | move | hover | wheel | type | key.+- **Taskbar badge and progress on a COMBINED taskbar (2026-09-04, proven at native resolution on+  ConfRoomROG):** ab paints the Adom badge and the progress bar correctly (a KiCad button showed both),+  but a Chrome or Edge GROUP button shows only its most recently ACTIVE window's overlay and progress, and+  a window nb opened in the background is never that window. So on a taskbar set to combine buttons the+  badge is invisible until the driven window is activated; on a taskbar set to never combine (John's+  laptop) every driven window has its own button and the badge shows. Do not "fix" this by foregrounding.+  Also: a Chrome button stuck orange/red is Chrome's own attention state (a download, a sign-in prompt);+  `flash:stop` does not clear it, ab `desktop_clear_window_attention {hwnd}` does.+- **Mouser answers a debugger-attached window with "Verification Required" (slide to verify)** on the+  office egress, and its Layer-1 window body stayed blank there too, so treat Mouser as a site that blocks+  this IP for automation; DigiKey, TI, Google and the wiki render fully in the background. - **Click-by-`text` that matches >1 visible element is refused (`ambiguous_target`, v0.1.0)** - two   "Next"/"Back" buttons, three "Yes" radios. Pass a unique `selector`, or `{debug:true}` to see the   numbered candidate overlay and pick one, or `acceptAmbiguous:true` to take the top-scored match. Verify
skills/installing-the-extension/SKILL.md+16
@@ -239,6 +239,22 @@ The update recipe, in order: 3. `nbrowser_dev_reload {profile}` PER PROFILE: a bare dev_reload only reloads the active profile, the second    profile keeps the old build until you reload it by name. 4. Wait ~20s, re-read readiness: every profile in `extensionVersions` must show the new version.+5. **A profile you WAKE later needs its own reload too, and readiness will lie to you until then** (found+   2026-09-04 on two machines): a profile that was asleep during the update starts its service worker+   from a stale script cache. It announces the NEW manifest version (readiness shows 0.2.10) while still+   running the OLD module code (no `windowEtiquette` on its replies). `nbrowser_dev_reload {profile}` on+   that profile clears it. Rule: after updating the folder, reload EVERY profile you ever drive, including+   the ones you wake afterwards; a version number in readiness is not proof the code changed.++**When the host folder is gone (the extension is loaded but never connects).** On ConfRoomROG the+extension folder survived but `host\` (native-host.js, native-host.bat, host-manifest.json) and the two+HKCU `NativeMessagingHosts\inc.adom.native_browser` keys were missing, so the extension could never dial+the bridge: readiness stayed `needs-extension`, wake_profile showed a `pending-N` that vanished in seconds.+Restore it WITHOUT running `install/install.ps1` (that script also copies `bridge\*` over ab's installed+cache and would downgrade a tier-managed bridge): write the three host files from the page's `host/`+(`desktop_write_file`, manifest rendered with the absolute `native-host.bat` path and the loaded extension+id in `allowed_origins`), then register `HKCU:\Software\Google\Chrome\NativeMessagingHosts\inc.adom.native_browser`+and the `Microsoft\Edge` twin with the manifest path (no UAC). The next wake connects.  ## Recovery / common failures - **Load unpacked missing** -> Developer mode is off (Step A) or the page didn't navigate (re-`desktop_navigate`, verify by screenshot).