← Commit history

v0.16.23 (insiders): Layer 1 on a cold browser start: the startup window's handle used to be returned, so the real window was never minimized or flashed and its session could not close it; the bridge now lets the launch settle, confirms the handle still exists among the target browser's windows, and adopts and backgrounds the real one when it does not. Install skill: field notes from a true first install on a colleague's box (Layer 1 for the first window, the real click on Select Folder, the Developer-mode toggle position, what background means for a new window).

John Lauer ·5ff75e6ba5 ·1mo ago ·parent 6a0e3fe
7 files changed +60−5
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.22';           // keep in lockstep with package.json (pkg version)+const VERSION = '0.16.23';           // 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.22\ No newline at end of file+0.16.23\ 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.22",+  "version": "0.16.23",   "expectedExtensionVersion": "0.2.15",   "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.22",+  "version": "0.16.23",   "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+26
@@ -2281,6 +2281,32 @@ function dispatch(verb, args) {             try { const list = await chromeWindowsDetailed(kp.browser); hwnd = list.map((w) => w.hwnd).find((h) => !pre.has(h) && !protectedHwnds.has(h)) || null; } catch {}           }         }+        // COLD-START CORRECTION (found installing the extension on arav-rog, 2026-09-20). When the browser is+        // NOT running, its first launch creates a short-lived startup window before the real one. The launch+        // script picked that handle, so the reply named a window that died a second later: the real window was+        // never minimized or flashed, and the session could not close it afterwards. So let the launch settle,+        // confirm the handle still exists among the TARGET browser's windows, and when it does not, adopt the+        // window that is new since before the launch and give IT the background treatment.+        if (hwnd && a.foreground !== true) {+          try {+            await new Promise((r) => setTimeout(r, 1800));+            let list = await chromeWindowsDetailed(kp.browser);+            if (!list.some((w) => w.hwnd === hwnd)) {+              const before = new Set([...curWins.map((w) => w.hwnd), ...protectedHwnds]);+              let real = null;+              for (let i = 0; i < 8 && !real; i++) {+                real = list.find((w) => !before.has(w.hwnd) && String(w.title || '').trim()) || null;+                if (!real) { await new Promise((r) => setTimeout(r, 700)); list = await chromeWindowsDetailed(kp.browser); }+              }+              const dead = hwnd; hwnd = real ? real.hwnd : null; minimized = false; flashed = false;+              logActivity({ verb: '_os_window_rehome', sessionId: a.sessionId, ok: !!real, ops: `startup handle ${dead} vanished -> ${hwnd || 'unresolved'}`, internal: true });+              if (hwnd) {+                try { const mr = await abCommand('desktop', 'desktop_set_window_state', { hwnd, state: 'minimize' }); minimized = !!(mr && mr.status >= 200 && mr.status < 300); } catch {}+                try { await taskbar(hwnd, { flash: 'until_focused' }); flashed = true; } catch {}+              }+            }+          } catch {}+        }         if (a.foreground === true) announceForeground(a.foregroundReason, a.sessionId);         else if (!hwnd) {           // BACKGROUND WAS REQUESTED BUT COULD NOT BE HONORED: no new window was created (the browser
package.json+1−1
@@ -1,6 +1,6 @@ {   "slug": "nb-bridge",-  "version": "0.16.22",+  "version": "0.16.23",   "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/installing-the-extension/SKILL.md+29
@@ -210,6 +210,35 @@ button) and poll `notify_response {id}` until they click Start. Verified round-t Drive a real verb in a window **you** open (see `native-browser-safety`): e.g. `nbrowser_open_window {sessionId:"verify", thread:"<you>", purpose:"verify the extension drives", url:"https://www.digikey.com"}` -> `nbrowser_screenshot`. +## Field notes (arav-rog, 2026-09-20 - a true first install into a colleague's managed Chrome profile)++A box with the bridge and nothing else: no extension folder, no native-messaging host, Chrome closed,+three people's signed-in profiles. Start to "Extension loaded" took about four minutes. What the run taught:++- **Your first window comes from Layer 1, not `nbrowser_open_window`.** With no extension anywhere there is+  nothing to answer `open_window`. Use `nbrowser_open_os_window {profile, url:"https://..."}` (an http(s)+  URL is required), take its `hwnd`, then `desktop_navigate {hwnd, url:"chrome://extensions"}`.+- **On a cold browser start, trust the hwnd only from bridge 0.16.23 on.** Before it, the first launch's+  short-lived startup window was returned, `desktop_navigate` timed out against it, and the session could+  not close the real window later. If a returned hwnd screenshots as a tiny strip or navigate times out,+  list the browser's windows and use the one that carries the page title.+- **Select Folder: go straight to the REAL click on Chrome too.** `desktop_ui_click {contains:"Select+  Folder"}` answered `error` AND closed the picker without committing, and Chrome said nothing: no card, no+  toast, no error. `desktop_find_control` for its rect then `desktop_click` (SendInput) with the picker in+  front loaded it first time ("Extension loaded" toast). Treat the UIA invoke as a way to LOSE the picker.+- **The Developer-mode toggle sits just right of its label.** `desktop_find_control {contains:"Developer+  mode"}` returns the LABEL rect (not invokable); click about 42 px right of its right edge, same y.+- **A managed profile ("Your profile is managed by adom.inc") did not block load-unpacked.**+- **What "background" means for a newly created window.** Windows inserts a window created without+  activation directly beneath the ACTIVE window. When the person is in another app, that is behind their+  work. When nothing of theirs is active (a shell flyout, a remote-desktop overlay), the new window lands+  above their apps without taking focus. It is never raised or focused by nb; if it must be fully out of+  sight, `desktop_set_window_state {hwnd, state:"bottom"}` sends it behind everything.+- **Always finish by closing what you opened** and checking `nbrowser_os_windows` shows only the user's own+  windows. A Layer-1 window whose session lost its hwnd closes with the two-step `nbrowser_force_close`.+- The extension connects within about ten seconds of loading; `nbrowser_readiness` then lists the profile+  at the loaded version, and it stays `ready` after every window of yours is closed.+ ## Chrome AND Edge They are independent. Repeat the flow per browser (matching scheme: `chrome://` vs `edge://`; the toggle sits top-right on Chrome, bottom-left on Edge). The host is registered per browser, so each connects its