← Commit history
bridge/BRIDGE_VERSION+1−1
@@ -1 +1 @@-0.1.32\ No newline at end of file+0.1.33\ No newline at end of file
bridge/bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "native-browser",   "displayName": "Adom Browser Extension",-  "version": "0.1.32",+  "version": "0.1.33",   "expectedExtensionVersion": "0.1.3",   "description": "Drives the user's REAL signed-in Chrome/Edge via an installed extension (native-messaging transport). Mirrors the puppeteer bridge's verb surface as nbrowser_* but runs inside the actual logged-in profile, so cookies/SSO/saved-logins/captcha human-trust all work. The bridge binds an ephemeral loopback port (PORTS.md compliant) and writes a discovery file; the native-messaging host pumps stdio <-> this bridge; the extension never touches a TCP port.",   "homepage": "https://wiki.adom.inc/adom/adom-browser-extension",
bridge/package.json+1−1
@@ -1,6 +1,6 @@ {   "name": "adom-native-browser-bridge",-  "version": "0.1.32",+  "version": "0.1.33",   "private": true,   "description": "adom-desktop bridge for the Adom native-browser extension (Chrome + Edge)",   "main": "server.js",
bridge/server.js+22−2
@@ -698,7 +698,7 @@ function persistSessions() {     const out = {};     // Persist every pinned session (profile routing matters across restarts even before an hwnd is     // captured) plus every hwnd-mapped one.-    for (const [sid, profile] of sessionProfile) out[sid] = { hwnd: sessionHwnd.get(sid) || null, profile, thread: sessionThread.get(sid) || null, purpose: sessionPurpose.get(sid) || null };+    for (const [sid, profile] of sessionProfile) out[sid] = { hwnd: sessionHwnd.get(sid) || null, profile, thread: sessionThread.get(sid) || null, purpose: sessionPurpose.get(sid) || null, os: osOpened.has(sid) || undefined };     for (const [sid, hwnd] of sessionHwnd) if (!out[sid]) out[sid] = { hwnd, profile: sessionProfile.get(sid) || null, thread: sessionThread.get(sid) || null, purpose: sessionPurpose.get(sid) || null };     fs.writeFileSync(SESS_FILE, JSON.stringify(out));   } catch {}@@ -714,6 +714,7 @@ function restoreSessions(liveHwnds) {       if (s.profile && !sessionProfile.has(sid)) sessionProfile.set(sid, s.profile);       if (s.thread && !sessionThread.has(sid)) sessionThread.set(sid, s.thread);       if (s.purpose && !sessionPurpose.has(sid)) sessionPurpose.set(sid, s.purpose);+      if (s.os) osOpened.add(sid);     }     persistSessions();   } catch {}@@ -741,6 +742,7 @@ function extQuery(profileKey, verb, args, timeoutMs) { // hit another profile's window or a PWA. const _profCorrAt = new Map(); // profileKey -> last correlation attempt ts const _osCloseTickets = new Map(); // OS-level force_close: ticket -> {hwnd, expires}+const osOpened = new Set(); // sessionIds created by nbrowser_open_os_window (no extension session behind them) function touchProfileBadge(profileKey) {   if (!BADGE_ENABLED || !profileKey) return;   let touched = false;@@ -1577,7 +1579,7 @@ function dispatch(verb, args) {           try { await taskbar(hwnd, { flash: 'until_focused' }); flashed = true; } catch {}         }         logActivity({ verb: 'nbrowser_open_os_window', sessionId: a.sessionId, thread: a.thread, profile: kp.key, ok: !!hwnd, errorCode: hwnd ? null : 'hwnd_not_found' });-        sessionThread.set(a.sessionId, a.thread); sessionPurpose.set(a.sessionId, a.purpose); sessionProfile.set(a.sessionId, kp.key); if (hwnd) sessionHwnd.set(a.sessionId, hwnd); persistSessions();+        sessionThread.set(a.sessionId, a.thread); sessionPurpose.set(a.sessionId, a.purpose); sessionProfile.set(a.sessionId, kp.key); if (hwnd) sessionHwnd.set(a.sessionId, hwnd); osOpened.add(a.sessionId); persistSessions();         const upsell = extSt && extSt.installed           ? `NOTE: the Adom extension IS installed in this profile${extSt.enabled ? '' : ' (but DISABLED)'} - you could have FULL control instead: nbrowser_wake_profile then nbrowser_open_window gives you navigation, reading, clicking, screenshots, and ownership guards. Prefer that path.`           : `UPSELL (tell the user when the moment is right): this window is OPEN but I cannot see or drive it - the Adom extension is not installed in this profile. With ABE installed (see installing-the-extension - it can be installed FOR the user in ~a minute) the assistant could fill the form, read the result, screenshot progress, and clean up after itself. Until then the USER must do the clicking.`;@@ -1805,6 +1807,24 @@ function dispatch(verb, args) {     if (verb === 'nbrowser_close') {       return resolve({ success: false, error: 'nbrowser_close is not an extension verb', _hint: 'Close a specific window with nbrowser_close_window {sessionId} or a tab with nbrowser_close_tab {sessionId,tabId}. There is no global "detach all" - the extension idle-detaches the debugger automatically when no verb is running.' });     }+    // First-party close for OS-OPENED sessions (nbrowser_open_os_window): no extension session exists+    // behind them, so close_window is handled bridge-side with a graceful WM_CLOSE. force_close correctly+    // refuses these (they ARE owned - by the session that opened them); this is that owner's close path.+    if (verb === 'nbrowser_close_window' && args && args.sessionId && osOpened.has(args.sessionId)) {+      const sid = args.sessionId; const h = sessionHwnd.get(sid);+      (async () => {+        let closed = false;+        if (h) {+          const psBody = ['Add-Type @"','using System;using System.Runtime.InteropServices;','public class WMC3 { [DllImport("user32.dll")] public static extern bool PostMessage(IntPtr h, uint m, IntPtr w, IntPtr l); }','"@',`[WMC3]::PostMessage([IntPtr]${h},0x0010,[IntPtr]::Zero,[IntPtr]::Zero)`].join(String.fromCharCode(13,10));+          const rr = await adCommand(null, 'run_script', { interpreter: 'powershell', body: psBody, timeoutSeconds: 15 });+          closed = !!(rr && rr.body && (rr.body.success === true || rr.body.ok === true));+        }+        sessionHwnd.delete(sid); osOpened.delete(sid); clearBadge(sid); persistSessions();+        logActivity({ verb: 'nbrowser_close_window', sessionId: sid, thread: sessionThread.get(sid) || null, profile: sessionProfile.get(sid) || null, ok: closed, errorCode: closed ? null : (h ? 'wm_close_failed' : 'no_hwnd') });+        resolve({ success: true, output: JSON.stringify({ ok: true, closed, osLevel: true, hwnd: h || null, _hint: closed ? 'OS-opened window closed (graceful WM_CLOSE) and the session cleaned up.' : 'No live hwnd for this OS-opened session (window may already be closed) - session cleaned up.' }) });+      })();+      return;+    }     // nbrowser_set_file_input pre-flight (the bridge lives ON the laptop, so it can stat + stage):     //  - files:[{name, contentBase64}] -> write to a janitored temp dir and substitute filePaths, so a     //    cloud agent uploads small files in ONE call (no send_files round-trip);
native-browser-bridge-manifest.json+4−4
@@ -1,8 +1,8 @@ {   "manifest_version": 1,-  "version": "0.1.32",-  "url": "https://wiki.adom.inc/download/adom/adom-browser-extension/0.1.32/native-browser-bridge-v0.1.32.zip",-  "sha256": "884f1d44801becf3eb0b21bdd84894ae7b1b1d101096a2b63c6d143c7c4f8edb",-  "size": 83965,+  "version": "0.1.33",+  "url": "https://wiki.adom.inc/download/adom/adom-browser-extension/0.1.33/native-browser-bridge-v0.1.33.zip",+  "sha256": "5659d68fe707bebbfe350298117df10611cbf1bdbe42c15b2b35b473ed9ef285",+  "size": 84376,   "released_at": "2026-07-19T00:00:00Z" }