← Commit history
bridge/BRIDGE_VERSION+1−1
@@ -1 +1 @@-0.1.35\ No newline at end of file+0.1.36\ 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.35",+  "version": "0.1.36",   "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.35",+  "version": "0.1.36",   "private": true,   "description": "adom-desktop bridge for the Adom native-browser extension (Chrome + Edge)",   "main": "server.js",
bridge/server.js+30−6
@@ -1163,7 +1163,7 @@ const VERB_META = {   nbrowser_profiles: { hint: 'List every BROWSER x PROFILE you can drive + which is active. Each row carries browser (chrome|edge), email, displayName (the human label the user sees on the browser profile button, e.g. "John Personal" vs "adom.inc"), and profileDir. Pick work-vs-personal from the email DOMAIN + displayName, never from which window happens to be focused. Drive several in parallel with profile:"<key>".', related: ['nbrowser_use_profile', 'nbrowser_profile_block', 'nbrowser_resolve_profiles'], pitfalls: ['active is only the default when no profile is passed', 'an entry with unresolved:true has no email yet - nbrowser_resolve_profiles first, or you may drive the wrong account', 'the same email can live in Chrome AND Edge - match browser too', 'asleep:true = the profile exists (Local State) but has no open window so its extension is not running - it cannot be driven until the user opens a window in it; never claim a profile "does not exist" when it is merely asleep'] },   nbrowser_use_profile: { hint: 'Set the default profile that verbs target.', related: ['nbrowser_profiles', 'nbrowser_pin_session'], pitfalls: ['you can still override per-call with profile:"<email>"'] },   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 ABE 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', 'close it afterwards with nbrowser_force_close {hwnd} - never leave auth litter', 'every response nudges the ABE install - relay the upsell to the user at a natural moment, never mid-auth'] },+  nbrowser_open_os_window: { hint: 'EXTENSION-FREE: open a URL in the user\'s REAL browser profile (Chrome or Edge) when ABE 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', 'REFUSES (live_profile) if the target browser is hosting the user\'s workspace / an active AI thread - never launch a sibling browser into the profile the user is working in; use a DIFFERENT profile or pup. confirmLiveProfile:true overrides AND protects the workspace/other-session windows from minimize/close', 'close it afterwards with nbrowser_force_close {hwnd} - never leave auth litter', 'every response nudges the ABE 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 ABE session that owns it. THE reliable probe when your own desktop_list_windows returns 0/null (wrong --target, AD 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 abeSession set belongs to a LIVE thread - never force-close it', 'ok:false = the bridge\'s AD 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_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:'] },@@ -1354,7 +1354,7 @@ function dispatch(verb, args) {         d('nbrowser_profiles', 'List connected BROWSER×PROFILE targets + which is active.', {}, { ok: 'bool', profiles: '[{profile,email,browser,active,blocked,live}]', active: 'str' }, 15, { example: {} }),         d('nbrowser_use_profile', 'Set the default profile action verbs target.', { profile: 'required str (key or email)' }, { ok: 'bool', active: 'str' }, 15, { example: { profile: 'chrome:[email protected]' } }),         d('nbrowser_pin_session', 'Pin a sessionId to a profile so every verb on that session auto-routes there (prevents cross-profile mistakes). open_window pins automatically.', { sessionId: 'required str', profile: 'required str (key or email)' }, { ok: 'bool', sessionId: 'str', pinnedTo: 'str' }, 15, { example: { sessionId: 'schwab', profile: 'chrome:[email protected]' } }),-        d('nbrowser_open_os_window', 'EXTENSION-FREE open of a URL in the user\'s real browser profile (OAuth consent etc). Background by default (minimize + orange taskbar flash); foreground needs foregroundReason. Returns hwnd (close later via force_close). Upsells the ABE install in every response.', { sessionId: 'required str', thread: 'required str', purpose: 'required str', profile: 'required str (key or email)', url: 'required http(s) str', browser: 'optional chrome|edge', foreground: 'optional bool', foregroundReason: 'required if foreground' }, { ok: 'bool', hwnd: 'int?', minimized: 'bool', flashed: 'bool', extensionInstalled: 'bool' }, 20, { example: { sessionId: 'gauth', thread: 'adom-google-setup', purpose: 'open Google OAuth consent for adom-google', profile: 'chrome:[email protected]', url: 'https://accounts.google.com/o/oauth2/auth?...' } }),+        d('nbrowser_open_os_window', 'EXTENSION-FREE open of a URL in the user\'s real browser profile (OAuth consent etc). Background by default (minimize + orange taskbar flash); foreground needs foregroundReason. Returns hwnd (close later via force_close). Upsells the ABE install in every response.', { sessionId: 'required str', thread: 'required str', purpose: 'required str', profile: 'required str (key or email)', url: 'required http(s) str', browser: 'optional chrome|edge', foreground: 'optional bool', foregroundReason: 'required if foreground', confirmLiveProfile: 'optional bool (override the live_profile guard)' }, { ok: 'bool', hwnd: 'int?', minimized: 'bool', flashed: 'bool', extensionInstalled: 'bool' }, 20, { example: { sessionId: 'gauth', thread: 'adom-google-setup', purpose: 'open Google OAuth consent for adom-google', profile: 'chrome:[email protected]', url: 'https://accounts.google.com/o/oauth2/auth?...' } }),         d('nbrowser_os_windows', 'OS-level browser-window inventory from the laptop (bridge-side): all Chrome/Edge/pup windows with hwnd/title/rect/z + which ABE session owns each. Reliable even when the caller\'s desktop_list_windows returns 0 (wrong target / AD hiccup) or a profile has no extension.', {}, { ok: 'bool (false = AD link down, data unknown)', count: 'int', windows: '[{hwnd,title,rect,z,kind,abeSession}]' }, 15, { example: {} }),         d('nbrowser_wake_profile', 'Wake an asleep browser profile with NO window (launches --no-startup-window; nothing foregrounds). Profile becomes live/drivable when its extension connects.', { profile: 'required str (key or email)', browser: 'optional chrome|edge' }, { ok: 'bool', woke: 'bool', alreadyLive: 'bool?', profile: 'str' }, 15, { example: { profile: 'chrome:[email protected]' } }),         d('nbrowser_resolve_profiles', 'Force every connected extension to re-resolve its Google email + re-announce, so pending-N/empty-email keys become chrome:<email>/edge:<email>.', {}, { ok: 'bool', profiles: '[{profile,email,browser}]' }, 15, { example: {} }),@@ -1546,6 +1546,27 @@ function dispatch(verb, args) {       if (a.foreground === true && !a.foregroundReason) {         return resolve({ success: false, errorCode: 'foreground_unjustified', error: 'foreground:true requires foregroundReason (and you should notify_user first).', _hint: 'Background is the default and is almost always right: the window opens, minimizes, and its taskbar button FLASHES orange - the user clicks it when THEY are ready. Only pass foreground:true + foregroundReason for a flow that genuinely breaks when unfocused.' });       }+      // ── LIVE-PROFILE GUARD + protected-hwnd hardening (John, 2026-07-19 incident: an Edge open into the+      // Default profile - which was hosting the user's live workspace/AI-thread - likely disrupted it).+      // Two protections: (1) if the target browser has a window that looks like the Adom WORKSPACE or an+      // active AI-thread host, REFUSE unless confirmLiveProfile:true - never blindly launch a sibling+      // browser into the profile the user is working in; (2) whatever we do open, the background+      // watcher is handed an EXCLUDE list (protectedHwnds) so it can NEVER minimize/close the workspace+      // or another thread's live session window, even in the same profile.+      const BRAND = kp.browser === 'edge' ? /Microsoft.?\s*Edge\s*$/i : /Google Chrome\s*$/i;+      const WORKSPACE = /(?:-\s*Editor\s*-|Hydrogen|Adom Workspace|code-server|claude\.ai|galliaApril)/i;+      let curWins = [];+      try { curWins = await chromeWindowsDetailed(); } catch {}+      const protectedHwnds = new Set();+      const hostWins = [];+      for (const w of curWins) { const t = String(w.title || ''); if (BRAND.test(t) && WORKSPACE.test(t)) { protectedHwnds.add(w.hwnd); hostWins.push(t); } }+      // another THREAD's live extension-driven session pinned to this exact profile (not my own, not os-opened)+      for (const [sid, pk] of sessionProfile) { if (pk === kp.key && sid !== a.sessionId && !osOpened.has(sid)) { const h = sessionHwnd.get(sid); if (h) protectedHwnds.add(h); } }+      if ((hostWins.length || protectedHwnds.size) && a.confirmLiveProfile !== true) {+        return resolve({ success: false, errorCode: 'live_profile',+          error: `live_profile: the ${kp.browser} profile '${kp.displayName}' appears to be hosting the user's live Adom workspace or an active AI session${hostWins.length ? ` (window: "${hostWins[0].slice(0, 60)}")` : ''}. Launching another browser window into the SAME profile can disrupt that running session (a real incident).`,+          _hint: 'Do NOT open into the profile that runs the workspace/thread. Prefer a DIFFERENT profile for the task, or pup (browser_open_window) for a throwaway. Only if you are CERTAIN it is safe, re-call with confirmLiveProfile:true - the workspace/other-thread windows are then explicitly protected from minimize/close, but a browser can still get grumpy about a sibling launch into a live profile.' });+      }       const local = process.env.LOCALAPPDATA;       const exes = kp.browser === 'edge'         ? ['C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe', 'C:/Program Files/Microsoft/Edge/Application/msedge.exe']@@ -1572,13 +1593,17 @@ function dispatch(verb, args) {             '  public static List<long> Browsers() { var o = new List<long>(); EnumProc cb = delegate(IntPtr h, IntPtr l) { var sb = new StringBuilder(64); GetClassName(h, sb, 64); if (sb.ToString().StartsWith("Chrome_WidgetWin")) o.Add((long)h); return true; }; EnumWindows(cb, IntPtr.Zero); return o; }',             '}',             '"@',+            // PROTECTED windows (workspace / other threads' live sessions) are treated as pre-existing AND+            // are never eligible to be the "new" window we minimize - belt AND suspenders vs the incident.+            `$protected = @(${[...protectedHwnds].map((h) => `[long]${h}`).join(',')})`,             '$pre = [WWatch]::Browsers()',+            'foreach ($p in $protected) { if ($pre -notcontains $p) { $pre += $p } }',             `Start-Process -FilePath "${exe}" -ArgumentList '${argStr}'`,             '$deadline = (Get-Date).AddSeconds(10)',             '$found = 0',             'while ((Get-Date) -lt $deadline) {',             '  $now = [WWatch]::Browsers()',-            '  foreach ($h in $now) { if ($pre -notcontains $h) { [WWatch]::ShowWindow([IntPtr]$h, 6) | Out-Null; $found = $h; break } }',+            '  foreach ($h in $now) { if (($pre -notcontains $h) -and ($protected -notcontains $h)) { [WWatch]::ShowWindow([IntPtr]$h, 6) | Out-Null; $found = $h; break } }',             '  if ($found -ne 0) { break }',             '  Start-Sleep -Milliseconds 30',             '}',@@ -1589,13 +1614,12 @@ function dispatch(verb, args) {           if (m && m[1] !== '0') { hwnd = Number(m[1]); minimized = true; }           if (hwnd) { try { await taskbar(hwnd, { flash: 'until_focused' }); flashed = true; } catch {} }         } else {-          let pre = new Set();-          try { pre = new Set((await chromeWindowsDetailed()).map((w) => w.hwnd)); } catch {}+          const pre = new Set([...curWins.map((w) => w.hwnd), ...protectedHwnds]);           adCommand('desktop', 'launch_process', { path: exe, args: [`--profile-directory=${kp.profileDir}`, '--new-window', url] });           const t0 = Date.now();           while (!hwnd && Date.now() - t0 < 8000) {             await new Promise((r) => setTimeout(r, 500));-            try { const list = await chromeWindowsDetailed(); hwnd = list.map((w) => w.hwnd).find((h) => !pre.has(h)) || null; } catch {}+            try { const list = await chromeWindowsDetailed(); hwnd = list.map((w) => w.hwnd).find((h) => !pre.has(h) && !protectedHwnds.has(h)) || null; } catch {}           }         }         logActivity({ verb: 'nbrowser_open_os_window', sessionId: a.sessionId, thread: a.thread, profile: kp.key, ok: !!hwnd, errorCode: hwnd ? null : 'hwnd_not_found' });
native-browser-bridge-manifest.json+4−4
@@ -1,8 +1,8 @@ {   "manifest_version": 1,-  "version": "0.1.35",-  "url": "https://wiki.adom.inc/download/adom/adom-browser-extension/0.1.35/native-browser-bridge-v0.1.35.zip",-  "sha256": "54d2f434ed4c5aab6f8731ba6ca87ad42f7285dbaae7f7058acc1345b2c984e2",-  "size": 85103,+  "version": "0.1.36",+  "url": "https://wiki.adom.inc/download/adom/adom-browser-extension/0.1.36/native-browser-bridge-v0.1.36.zip",+  "sha256": "9993ef6e034ebe1c6acff8ad96396aee09008a80bc573342811f8a0ac4c87f0e",+  "size": 86244,   "released_at": "2026-07-19T00:00:00Z" }