← Commit history
nb 0.4.12: wake_profile escalates to a minimized window when windowless wake fails (Chromium never starts load-unpacked SWs without a window - measured on ROG Chrome + laptop Edge); battery auto-discovers the profile
6 files changed
+44−10
bridge/BRIDGE_VERSION+1−1@@ -1 +1 @@-0.4.11+0.4.12
bridge/bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "native-browser", "displayName": "Adom Native Browser",- "version": "0.4.11",+ "version": "0.4.12", "expectedExtensionVersion": "0.2.8", "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.4.11",+ "version": "0.4.12", "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+21−1@@ -1339,7 +1339,7 @@ const VERB_META = { 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'] }, nbrowser_os_windows: { hint: 'OS-level browser-window inventory measured ON the laptop by the bridge: every Chrome/Edge/pup window (hwnd, title, rect, z) annotated with the nbe session that owns it. THE reliable probe when your own desktop_list_windows returns 0/null (wrong --target, ab hiccup) or when a profile has no extension (Edge) so list_windows cannot see it.', related: ['nbrowser_force_close', 'nbrowser_list_windows', 'nbrowser_profiles'], pitfalls: ['a window with nbeSession set belongs to a LIVE thread - never force-close it', 'ok:false = the bridge\'s ab link is down, meaning "unknown", NOT "no windows"'] },- nbrowser_wake_profile: { hint: 'Wake an ASLEEP profile (exists in nbrowser_profiles with asleep:true but not drivable) WITHOUT opening any window: launches the browser with --no-startup-window so the profile + its extension load in background mode - nothing foregrounds, no focus is stolen. When it returns woke:true, drive normally.', related: ['nbrowser_profiles', 'nbrowser_open_window', 'nbrowser_resolve_profiles'], pitfalls: ['NEVER launch a foreground browser window to wake a profile - this verb exists so waking honors background-by-default', 'woke:false usually means the Adom extension is not installed in THAT profile (extensions are per-profile)'] },+ nbrowser_wake_profile: { hint: 'Wake an ASLEEP profile (exists in nbrowser_profiles with asleep:true but not drivable). Tries windowless first (--no-startup-window); if the extension does not dial (Chromium never starts a LOAD-UNPACKED extension SW without a real window - measured), it ESCALATES to ONE minimized background window in that profile (windowOpened:true, still no foreground, no focus theft). Leave that window open - it is what keeps the extension alive. noWindow:true forbids the escalation.', related: ['nbrowser_profiles', 'nbrowser_open_window', 'nbrowser_resolve_profiles'], pitfalls: ['NEVER launch a foreground browser window to wake a profile - the escalation window is minimized and background by design', 'woke:false with windowOpened:true means even a real window did not connect the extension - check install/enable/host registration', 'closing the browser\'s LAST window exits it and puts the profile back to sleep - keep the wake window open while driving'] }, nbrowser_resolve_profiles: { hint: 'Force every connected extension to re-resolve its Google email and re-announce, so pending-N / empty-email keys eagerly become chrome:<email> / edge:<email> - call it up front so you never drive the wrong (unresolved) profile.', related: ['nbrowser_profiles', 'nbrowser_pin_session', 'nbrowser_whoami'], pitfalls: ['takes ~2.5s (waits for each re-announce)', 'a profile not signed into Google stays email-less - target it by id-key or browser:'] }, nbrowser_whoami: { hint: 'Ask ONE profile to (re)resolve + report its Google email (and re-announce it to the bridge). For all profiles at once use nbrowser_resolve_profiles.', related: ['nbrowser_resolve_profiles', 'nbrowser_profiles'], pitfalls: ['signedIn:false = this browser profile has no Google account, so no email'] }, nbrowser_profile_block: { hint: 'Blocklist a profile so it is NEVER driven (user safety whitelist; persists).', related: ['nbrowser_profile_unblock', 'nbrowser_profiles'], pitfalls: ['respect the blocklist - it is the user telling you hands-off'] },@@ -2137,6 +2137,26 @@ function dispatch(verb, args) { _lastResolveNudge = 0; resolvePendingProfiles(); if (Date.now() - t0 > 12000) { const st = adomExtStateForProfile(kp.browser, kp.profileDir);+ // MEASURED (2026-08-23, ROG Chrome + laptop Edge): a --no-startup-window launch does NOT start+ // a LOAD-UNPACKED extension's service worker - Chromium only spins unpacked SWs up once a real+ // window exists. Since our whole install base is load-unpacked (until the store listings+ // clear), the windowless promise silently fails exactly where we ship. ESCALATE instead of+ // shrugging: when the extension is installed+enabled but never dialed, open ONE minimized+ // background window in that profile (no focus, no foreground - the same posture as+ // open_os_window) and keep polling. The window must STAY (closing the last window exits the+ // browser and kills the SW again) - we say so and hand back the hwnd.+ if (st && st.installed && st.enabled !== false && !(args && args.noWindow)) {+ abCommand('desktop', 'launch_process', { path: exe, args: [`--profile-directory=${kp.profileDir}`, '--window-position=-32000,-32000', '--start-minimized', 'https://wiki.adom.inc/adom/nb-bridge'] });+ const t1 = Date.now();+ const poll2 = () => {+ const k2 = findLive();+ if (k2) return resolve({ success: true, output: JSON.stringify({ ok: true, woke: true, profile: k2, tookMs: Date.now() - t0, windowOpened: true, _hint: 'Windowless wake was NOT enough (Chromium does not start a LOAD-UNPACKED extension\'s service worker without a real window - measured, not theory), so nbe opened ONE minimized background window in this profile; nothing was foregrounded and no focus was stolen. LEAVE that window open while you work: it is what keeps the extension alive, and closing the browser\'s last window puts the profile back to sleep. Drive normally now via nbrowser_open_window.' }) });+ _lastResolveNudge = 0; resolvePendingProfiles();+ if (Date.now() - t1 > 15000) return resolve({ success: true, output: JSON.stringify({ ok: false, woke: false, profile: kp.key, extensionState: st, windowOpened: true, _hint: `Even a real minimized window did not get the extension to dial within 15s. DIAGNOSIS: ${extFixHint(kp.browser, kp.profileDir, st)}` }) });+ setTimeout(poll2, 700);+ };+ return setTimeout(poll2, 2500);+ } return resolve({ success: true, output: JSON.stringify({ ok: false, woke: false, profile: kp.key, extensionState: st, _hint: `Launched the profile in background mode but its extension did not connect within 12s. DIAGNOSIS: ${extFixHint(kp.browser, kp.profileDir, st)}` }) }); } setTimeout(poll, 700);
native-browser-bridge-manifest.json+5−5@@ -1,8 +1,8 @@ { "manifest_version": 1,- "version": "0.4.11",- "url": "https://wiki.adom.inc/download/adom/nb-bridge/0.4.11/native-browser-bridge-v0.4.11.zip",- "sha256": "15333d3b55a391509fdde42468363e44e673a2aa1c23f46cede4d4f977529e7d",- "size": 106296,- "released_at": "2026-08-22T14:55:58Z"+ "version": "0.4.12",+ "url": "https://wiki.adom.inc/download/adom/nb-bridge/0.4.12/native-browser-bridge-v0.4.12.zip",+ "sha256": "7b35d68cca60f2dada0dbdbebbcdf6b58382773ce1dcf8cb7e70d3fe5bddb1e3",+ "size": 107092,+ "released_at": "2026-08-23T12:16:51Z" }
tools/verb-battery.mjs+15−1@@ -19,7 +19,10 @@ import { fileURLToPath } from 'node:url'; const root = join(dirname(fileURLToPath(import.meta.url)), '..'); const argv = Object.fromEntries(process.argv.slice(2).map((a, i, arr) => a.startsWith('--') ? [a.slice(2), arr[i + 1]] : []).filter(Boolean)); const TARGET = argv.target || 'AdomLapper';-const PROFILE = argv.profile || 'chrome:[email protected]';+// PROFILE: --profile wins; otherwise DISCOVER from the box itself. A hardcoded laptop default made a+// ConfRoomROG run fail 30/84 with one root cause (unknown profile → no drivable session → cascade,+// 2026-08-23). Prefer a live profile; else the first asleep-but-installed chrome, else edge.+let PROFILE = argv.profile || null; const THREAD = 'nb verb battery'; const SID = 'nbbattery'; const HARNESS = 'https://wiki.adom.inc/blob/app/nb-bridge/demo/test-harness.html';@@ -272,7 +275,18 @@ function metaCount() { return [...s.slice(i, e + 1).matchAll(/^ {2}nbrowser_[a-z0-9_]+:\s*\{/gm)].length; } +function discoverProfile() {+ const r = call('nbrowser_profiles', {});+ const ps = (r && r.profiles) || [];+ const live = ps.find((p) => p.live && p.browser === 'chrome') || ps.find((p) => p.live);+ if (live) return live.profile;+ const installed = ps.find((p) => p.extensionInstalled && p.browser === 'chrome') || ps.find((p) => p.extensionInstalled);+ if (installed) { call('nbrowser_wake_profile', { profile: installed.profile }); return installed.profile; }+ return ps.length ? ps[0].profile : 'chrome:[email protected]';+}+ if (import.meta.url === `file://${process.argv[1]}`) {+ if (!PROFILE) { PROFILE = discoverProfile(); console.log(`profile auto-discovered: ${PROFILE}`); } console.log(`nb verb battery — target:${TARGET} profile:${PROFILE} harness:${HARNESS}\n`); // TEARDOWN-FIRST: a prior run (crashed, ctrl-C'd, or mis-argued) can leave the battery session // alive with a pinned profile; the profile_mismatch guard then correctly refuses everything.