← Commit history

v0.16.42 (insiders): a session-scoped tab list no longer widens to the whole profile when that session's window is gone: it refuses with session_window_gone, so nbrowser_state stops reporting dead sessions as alive on whatever the USER is reading (it showed John's Gmail); extension 0.2.20

John Lauer ·7632b6b53c ·21d ago ·parent c484351
8 files changed +32−9
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.41';           // keep in lockstep with package.json (pkg version)+const VERSION = '0.16.42';           // 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.41\ No newline at end of file+0.16.42\ No newline at end of file
bridge/bridge.json+2−2
@@ -2,8 +2,8 @@   "manifest_version": 1,   "name": "native-browser",   "displayName": "Adom Native Browser",-  "version": "0.16.41",-  "expectedExtensionVersion": "0.2.19",+  "version": "0.16.42",+  "expectedExtensionVersion": "0.2.20",   "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",   "author": "Adom Inc.",
bridge/package.json+1−1
@@ -1,6 +1,6 @@ {   "name": "adom-native-browser-bridge",-  "version": "0.16.41",+  "version": "0.16.42",   "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",
extension/manifest.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 3,   "name": "Adom Native Browser Extension",   "short_name": "Adom",-  "version": "0.2.19",+  "version": "0.2.20",   "description": "Layer 2 of Adom Native Browser (nb): lets your Adom AI drive your real, signed-in Chrome or Edge, using your own logins.",   "minimum_chrome_version": "116",   "background": {
extension/src/native.js+9
@@ -804,6 +804,15 @@ export async function switchTab(args = {}) { export async function listTabs(args = {}) {   await ensureLoaded();   const windowId = await resolveWindowId(args);+  // A SESSION-SCOPED list must never widen to the whole profile. When the session's window is gone,+  // querying every tab returned the USER's tabs instead, so nbrowser_state showed dead sessions as alive+  // on whatever the user was reading (John's Gmail, caught by the battery 2026-10-21). Refuse instead.+  if (args.sessionId && windowId == null) {+    const e = new Error(`session '${args.sessionId}' has no live window (it was closed, or the browser restarted), so there are no tabs to list`);+    e.errorCode = 'session_window_gone';+    e.hint = "This is NOT the user's tabs - a session-scoped list never falls back to them. Open a new window with nbrowser_open_window, or drop the session. nbrowser_list_windows {mine:false} shows what is actually open.";+    throw e;+  }   const tabs = await chrome.tabs.query(windowId != null ? { windowId } : {});   const active = tabs.find((t) => t.active);   const owned = ownedWindowSet();
package.json+1−1
@@ -1,6 +1,6 @@ {   "slug": "nb-bridge",-  "version": "0.16.41",+  "version": "0.16.42",   "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.",
tools/verb-battery.mjs+16−2
@@ -73,6 +73,20 @@ const T = {   profiles: () => { const r = call('nbrowser_profiles'); const ps = r.profiles || []; ctx.profileCount = ps.length; const def = ps.find((p) => p.profile === r.active); const pups = ps.filter((p) => p.origin === 'pup').length; return A(ps.length > 0 && !(def && def.origin === 'pup'), `${ps.length} profiles, default ${r.active} (origin ${def ? def.origin : 'none'}), ${pups} pup connection(s) set aside`, `default is pup's browser or no profiles: ${JSON.stringify(r).slice(0, 160)}`); },   resolve_profiles: () => { const r = call('nbrowser_resolve_profiles'); return A(r.ok !== false, 'resolved', JSON.stringify(r).slice(0, 120)); },   // An Edge-only box is a PASS: nb drives whatever native browser is there (nb-bridge#46).+  // A dead session must never report the USER's tabs (caught 2026-10-21: every dead session showed John's+  // Gmail and alive:true). A session-scoped list refuses; state then says the window is gone.+  dead_session_scoping: () => {+    const sid = SID + '-dead';+    const o = call('nbrowser_open_window', { sessionId: sid, thread: THREAD, purpose: 'battery: dead-session scoping', profile: PROFILE, url: HARNESS });+    if (o.ok !== true) return [false, 'could not open the probe window: ' + JSON.stringify(o).slice(0, 120)];+    call('nbrowser_close_window', { sessionId: sid });+    const t = call('nbrowser_list_tabs', { sessionId: sid });+    const st = call('nbrowser_state', { sessionId: sid, all: true });+    const row = (st.sessions || []).find((x) => x.sessionId === sid) || {};+    const refused = t.ok !== true && t.errorCode === 'session_window_gone';+    const honest = !row.url || row.alive === false;+    return A(refused && honest, `list_tabs refused (${t.errorCode}) and state reports the window gone`, JSON.stringify({ errorCode: t.errorCode, url: row.url, alive: row.alive }).slice(0, 180));+  },   // nb-bridge#51 item 8: a CDP refusal must arrive as a structured errorCode + hint, never Chrome's raw text.   eval_on_browser_page: () => {     const sid = SID + '-cdpblock';@@ -302,7 +316,7 @@ const T = { // ─── coverage map: EVERY VERB_META verb decides its fate here ─────────────── export const COVERAGE = {   status: 'live', readiness: 'live', ping: 'live', progress: 'live', scroll: 'live', input_unknown_type: 'guard', hover_raw_xy: 'live', apply_settings: 'skip:internal bridge-to-extension push, exercised by the settings test through nbrowser_settings', whoami: 'live', profiles: 'live',-  resolve_profiles: 'live', prewarm: 'live', eval_on_browser_page: 'live', guard_profile_required: 'guard', open_minimized: 'live', chrome_status: 'live', describe: 'live', activity: 'live',+  resolve_profiles: 'live', prewarm: 'live', dead_session_scoping: 'live', eval_on_browser_page: 'live', guard_profile_required: 'guard', open_minimized: 'live', chrome_status: 'live', describe: 'live', activity: 'live',   os_windows: 'live', wake_profile: 'live', rescan: 'live', resync_sessions: 'live',   open_window: 'live', pin_session: 'live', use_profile: 'live', list_windows: 'live',   window_state: 'live', switch_window: 'live', navigate: 'live', reload: 'live',@@ -390,7 +404,7 @@ if (import.meta.url === `file://${process.argv[1]}`) {     'record_start','record_status','record_stop','record_list',     'download','download_wait','login_state','login_form','credentials','totp','clear_cookies','add_extension_origin',     'state','demo','open_os_window','guard_autologin','guard_record_fallback','guard_force_close','guard_close_all','profile_block','profile_unblock',-    'events_and_owned','handle_dialog','cleanup','owned','close_window','eval_on_browser_page'];+    'events_and_owned','handle_dialog','cleanup','owned','close_window','dead_session_scoping','eval_on_browser_page'];   for (const name of order) {     if (TARGET_LOST) { record(name, 'skip', true, `not run - ${TARGET_LOST}`); continue; }     const fn = T[name]; if (!fn) { record(name, '??', false, 'no test fn'); continue; }