← Commit history
v0.16.57 (insiders): nb-bridge#55: the save dialog is found through a plain Win32 enumeration when ab's window list does not return it (it will not list a dialog owned by an elevated process, which is why six runs reported 'no save dialog appeared' while one sat in front of the user), and the pointless 'Show me' button is gone from a toast about a window that is already in front
6 files changed
+52−11
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.56'; // keep in lockstep with package.json (pkg version)+const VERSION = '0.16.57'; // 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.56+0.16.57
bridge/bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "native-browser", "displayName": "Adom Native Browser",- "version": "0.16.56",+ "version": "0.16.57", "expectedExtensionVersion": "0.2.22", "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.56",+ "version": "0.16.57", "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+47−6@@ -3168,6 +3168,41 @@ const exportSayClear = () => abCommand(null, 'desktop_caption', { id: EXPORT_CAP // present in the user's screenshot and absent in ours seconds later. The file Chrome writes cannot be // raced like that. The bridge runs ON the machine, so it just looks for it. Either nb fills the dialog // with its own path, or the user presses Save with Chrome's default - both land here.+// ab's window list does not return a dialog owned by an ELEVATED process (it will not click into one, so+// it does not list it), which is why six runs reported "no save dialog appeared" while one sat in front+// of the user. A plain Win32 enumeration sees it regardless, so ask the OS directly (John, 2026-10-29).+async function findSaveDialogWin32() {+ const ps = [+ 'Add-Type @"',+ 'using System;using System.Text;using System.Runtime.InteropServices;',+ 'public class NBW{',+ ' [DllImport("user32.dll")] public static extern bool EnumWindows(EnumProc f, IntPtr l);',+ ' [DllImport("user32.dll")] public static extern int GetWindowTextW(IntPtr h, StringBuilder s, int n);',+ ' [DllImport("user32.dll")] public static extern bool IsWindowVisible(IntPtr h);',+ ' [DllImport("user32.dll")] public static extern bool GetWindowRect(IntPtr h, out R r);',+ ' public struct R{public int left,top,right,bottom;}',+ ' public delegate bool EnumProc(IntPtr h, IntPtr l);',+ '}',+ '"@',+ '$out = New-Object System.Collections.ArrayList',+ '[void][NBW]::EnumWindows({ param($h,$l)',+ ' $sb = New-Object System.Text.StringBuilder 300',+ ' [void][NBW]::GetWindowTextW($h,$sb,300); $t = $sb.ToString()',+ ' if ($t -match "Export Passwords|Save As" -and [NBW]::IsWindowVisible($h)) {',+ ' $r = New-Object NBW+R; [void][NBW]::GetWindowRect($h,[ref]$r)',+ ' [void]$out.Add(@{ hwnd=[int64]$h; title=$t; left=$r.left; top=$r.top; right=$r.right; bottom=$r.bottom })',+ ' }',+ ' return $true }, [IntPtr]::Zero)',+ 'if ($out.Count -eq 0) { "{}" } else { $out[0] | ConvertTo-Json -Compress }',+ ].join('; ');+ try {+ const r = await abCommand(null, 'run_script', { interpreter: 'powershell', scriptB64: Buffer.from(ps, 'utf8').toString('base64'),+ reason: 'nb-bridge#55: find the export save dialog (ab does not list an elevated one)', timeoutSeconds: 20 });+ const out = String((r && r.body && (r.body.stdout || (r.body.data && r.body.data.stdout))) || '').trim();+ const j = JSON.parse(out || '{}');+ return j && j.hwnd ? j : null;+ } catch { return null; }+} function newLoginCsvSince(sinceMs, ownPath) { const dirs = [path.dirname(ownPath), path.join(os.homedir(), 'Downloads'), path.join(os.homedir(), 'Documents'), path.join(os.homedir(), 'Desktop')]; const hits = [];@@ -3211,8 +3246,7 @@ async function exportSavedLogins(opts) { // click falls back to opening Adom Bridge's own app, which is never what the person wanted. await _notify({ id: 'nb-export-logins', level: 'warning', scenario: 'reminder', focus: { hwnd }, title: `${brand} will ask you to confirm`,- body: `Adom is handing your saved ${brand} logins to pup so it can sign you in. ${brand} asks for Windows Hello once - approve it and this finishes by itself. Click this to see the window.`,- buttons: [{ label: 'Show me' }] }).catch(() => {});+ body: `Adom is handing your saved ${brand} logins to pup so it can sign you in. ${brand} asks for Windows Hello once - approve it and this finishes by itself.` }).catch(() => {}); const done = async (res) => { await exportSayClear(); try { await dispatch('nbrowser_close_window', { sessionId: sid }); } catch {} return res; }; const nav = await abCommand(null, 'desktop_navigate', { hwnd, url: `${scheme}://password-manager/settings`,@@ -3309,7 +3343,7 @@ async function exportSavedLogins(opts) { const watchSince = watchSince0; const deadline = Date.now() + timeoutSec * 1000; let saved = false, foundFile = null, toldToSave = false;- let sawDialogAt = 0, dialogVisibleMs = null;+ let sawDialogAt = 0, dialogVisibleMs = null, lastWin32Probe = 0; while (Date.now() < deadline) { await _sleep(300); // THE AUTHORITATIVE SIGNAL: did a passwords CSV appear? Whoever pressed Save, we are done.@@ -3323,8 +3357,16 @@ async function exportSavedLogins(opts) { } const wins = await abCommand(null, 'desktop_list_windows', {}).catch(() => null); const list = (() => { try { return (wins.body.data || wins.body).windows || []; } catch { return []; } })();- const dlg = list.find((w) => (w.className === '#32770' || /^#32770/.test(String(w.className || '')))+ let dlg = list.find((w) => (w.className === '#32770' || /^#32770/.test(String(w.className || ''))) && /save|export|passwords/i.test(String(w.title || '')));+ // Not in ab's list? It may be elevated. Ask Win32 directly, but only every ~2s: this costs a+ // PowerShell spawn, and polling it five times a second would be its own problem.+ if (!dlg && Date.now() - lastWin32Probe > 2000) {+ lastWin32Probe = Date.now();+ const w32 = await findSaveDialogWin32();+ if (w32) dlg = { hwnd: w32.hwnd, title: w32.title, elevatedOrHidden: true,+ rect: { x: w32.left, y: w32.top, w: w32.right - w32.left, h: w32.bottom - w32.top, right: w32.right, top: w32.top } };+ } if (!dlg) { // If the dialog is up but we have not matched it, SAY SO rather than leaving the person guessing // at a file picker that appeared out of nowhere.@@ -3374,8 +3416,7 @@ async function exportSavedLogins(opts) { await exportSay(`${brand} is asking where to save your logins and Adom could not fill it in. Click Cancel to stop - nothing is exported. (Adom asked for this to load pup with your logins.)`, { persist: true }); await _notify({ id: 'nb-export-logins', level: 'warning', scenario: 'reminder', focus: { hwnd: stuck.hwnd }, title: 'A save dialog is waiting on your screen',- body: `${brand} is asking where to save your exported logins. Press Save and Adom picks the file up and deletes it, or press Cancel to stop - nothing is exported either way until you choose. Click this to see it.`,- buttons: [{ label: 'Show me' }] }).catch(() => {});+ body: `${brand} is asking where to save your exported logins. Press Save and Adom picks the file up and deletes it, or press Cancel to stop - nothing is exported either way until you choose.` }).catch(() => {}); } else { await exportSay(`Adom stopped waiting for the ${brand} confirmation. Nothing was exported.`, { persist: false, duration: 8000 }); }
package.json+1−1@@ -1,6 +1,6 @@ { "slug": "nb-bridge",- "version": "0.16.56",+ "version": "0.16.57", "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.",