← Commit history

v0.1.10: correlation - badge ONLY the window open_window created (new-hwnd diff; PWA-safe); remove profile-email re-correlation that hit the Gmail PWA. Bridge finds the avatar file + hands AD the path (overlay.avatar); AD stays generic (no folder reading)

John Lauer ·840d5c7b75 ·3mo ago ·parent 288dcec
5 files changed +31−27
bridge/BRIDGE_VERSION+1−1
@@ -1 +1 @@-0.1.9+0.1.10
bridge/bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "native-browser",   "displayName": "Adom Native Browser (Chrome + Edge)",-  "version": "0.1.9",+  "version": "0.1.10",   "expectedExtensionVersion": "0.1.2",   "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.9",+  "version": "0.1.10",   "private": true,   "description": "adom-desktop bridge for the Adom native-browser extension (Chrome + Edge)",   "main": "server.js",
bridge/server.js+24−20
@@ -469,7 +469,11 @@ function avatarForProfile(profileKey) {       const j = JSON.parse(fs.readFileSync(path.join(root, 'Local State'), 'utf8'));       const cache = (j.profile && j.profile.info_cache) || {};       for (const [dir, info] of Object.entries(cache)) {-        if (String((info && info.user_name) || '').toLowerCase() === email.toLowerCase()) { result = { userDataDir: root, profileDir: dir }; break; }+        if (String((info && info.user_name) || '').toLowerCase() === email.toLowerCase()) {+          const pic = path.join(root, dir, 'Google Profile Picture.png');+          result = { userDataDir: root, profileDir: dir, avatarPath: fs.existsSync(pic) ? pic : null };+          break;+        }       }     }   } catch {}@@ -479,30 +483,28 @@ function avatarForProfile(profileKey) { function applyBadge(sid, hwnd, profileKey) {   if (!sid || !hwnd) return;   const overlay = { badge: 'adom', tooltip: 'Adom is driving this browser' };-  const av = avatarForProfile(profileKey || sessionProfile.get(sid)); // composite onto the profile avatar if we can find it-  if (av) { overlay.profileDir = av.profileDir; overlay.userDataDir = av.userDataDir; }+  // The BRIDGE is the browser expert: WE find the profile's avatar file and hand AD the path; AD just+  // composites the Adom mark onto it (AD stays generic - no browser/folder knowledge). If we can't find+  // an avatar, AD falls back to the plain badge.+  const av = avatarForProfile(profileKey || sessionProfile.get(sid));+  if (av && av.avatarPath) overlay.avatar = av.avatarPath;   taskbar(hwnd, { overlay }); badgeOn.add(sid); armBadgeIdle(sid); } // Called on every DRIVE verb: the badge tracks ACTIVE driving, not mere ownership. On the first activity // (or after a bridge restart) it lazily correlates the driven profile's taskbar window; then it just // re-arms the idle timer. An IDLE owned window (a thread opened it then went quiet) is NEVER badged here, // so it fades and stays faded — fixing the "personal profile badged when nothing's driving it" bug.-const badgeTry = new Map(); // sid -> last correlate-attempt ts (throttles the desktop_list_windows call)-async function touchBadge(sid, profileKey) {+function touchBadge(sid, profileKey) {   if (!sid) return;-  let h = sessionHwnd.get(sid);-  if (!h) {-    const last = badgeTry.get(sid) || 0;-    if (Date.now() - last < 8000) return; // throttle correlation attempts-    badgeTry.set(sid, Date.now());-    const email = emailOf(profileKey);-    if (!email.includes('@')) return;-    try { const list = await chromeWindowsDetailed(); const m = list.find((w) => (w.title || '').includes(email)); if (m) { h = m.hwnd; sessionHwnd.set(sid, h); } } catch {}-    if (!h) return;-  }+  // ONLY the window we captured at open_window (the real browser window the AI created). We deliberately+  // do NOT re-correlate by profile/title here - that is exactly what reached the wrong window (a Gmail PWA+  // that merely had the profile email in its title). If the captured hwnd is gone (e.g. bridge restarted),+  // no badge until the thread opens a fresh window. Correctness over persistence.+  const h = sessionHwnd.get(sid);+  if (!h) return;   if (!badgeOn.has(sid)) applyBadge(sid, h, profileKey); else armBadgeIdle(sid); }-function clearBadge(sid) { const t = badgeIdle.get(sid); if (t) clearTimeout(t); badgeIdle.delete(sid); badgeOn.delete(sid); badgeTry.delete(sid); }+function clearBadge(sid) { const t = badgeIdle.get(sid); if (t) clearTimeout(t); badgeIdle.delete(sid); badgeOn.delete(sid); } // Capture the driven window's hwnd by DIFFING the real-Chrome window list across the open (the new // hwnd is the one we opened). Robust vs Windows foreground-lock - no GetForegroundWindow guessing. let preOpenHwnds = new Set();@@ -1401,9 +1403,11 @@ async function onHostFrame(frame) {             const list = await chromeWindowsDetailed();             // Correlate by BOUNDS first (profile-independent + precise — the new-window DIFF can't tell             // profiles apart and badged the WRONG profile's taskbar button). Diff only as a fallback.-            neu = matchHwnd(list, payload.bounds, payload.title); // title-first (DPI-safe), then bounds-            if (!neu) neu = list.map((w) => w.hwnd).find((h) => !preOpenHwnds.has(h)) || null; // diff fallback (title empty while loading)-            if (!neu) { const email = emailOf(p.profileKey); if (email.includes('@')) { const m = list.find((w) => (w.title || '').includes(email)); if (m) neu = m.hwnd; } } // profile-level fallback+            // The window the AI JUST created = the new hwnd (set-difference vs the pre-open snapshot). This+            // is reliable, DPI-safe, and PWA-safe: it's always the real browser window we opened, never a+            // Gmail/Chat PWA or some other profile window. Title/bounds are only a fallback.+            neu = list.map((w) => w.hwnd).find((h) => !preOpenHwnds.has(h)) || null;+            if (!neu) neu = matchHwnd(list, payload.bounds, payload.title);           } catch {}           if (neu) { lastHwnd = neu; if (sid) sessionHwnd.set(sid, neu); applyBadge(sid, neu, p.profileKey); break; } // applyBadge = badge (composited onto the profile avatar) + arm idle-expiry           await new Promise((r) => setTimeout(r, 250));@@ -1425,7 +1429,7 @@ async function onHostFrame(frame) {         }       }       maybeAutoFlash(p.verb, payload);-      if (payload && payload.sessionId && ACTIVITY.has(p.verb)) touchBadge(payload.sessionId, p.profileKey).catch(() => {}); // badge = ACTIVE driving; lazily correlates, idle-expires when commands stop+      if (payload && payload.sessionId && ACTIVITY.has(p.verb)) touchBadge(payload.sessionId, p.profileKey); // keep the open-captured badge alive while driving; idle-expires when commands stop       const sid = payload && payload.sessionId;       logActivity({ verb: p.verb, sessionId: sid || null, thread: sessionThread.get(sid) || null, profile: p.profileKey || null, ok: payload && payload.ok !== false, errorCode: (payload && payload.errorCode) || null });       resolve_ok(p.resolve, payload);
native-browser-bridge-manifest.json+4−4
@@ -1,8 +1,8 @@ {   "manifest_version": 1,-  "version": "0.1.9",-  "url": "https://wiki.adom.inc/download/adom/adom-browser-extension/0.1.9/native-browser-bridge-v0.1.9.bin",-  "sha256": "c2753b101d02ea99a3d819a47e1967cb8088026b833746bf3a1d8e8bd48687c4",-  "size": 57677,+  "version": "0.1.10",+  "url": "https://wiki.adom.inc/download/adom/adom-browser-extension/0.1.10/native-browser-bridge-v0.1.10.bin",+  "sha256": "1d1f216598c539f8f77cfb0d787bc807fe781e4b99680597c9b21bec1b021089",+  "size": 57819,   "released_at": "2026-07-15T00:00:00Z" }\ No newline at end of file