← 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).
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+0.16.23
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