← Commit history
v0.1.5: profile-level badge correlation (profile email in OS title is stable while pages navigate)
5 files changed
+22−12
bridge/BRIDGE_VERSION+1−1@@ -1 +1 @@-0.1.4+0.1.5
bridge/bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "native-browser", "displayName": "Adom Native Browser (Chrome + Edge)",- "version": "0.1.4",+ "version": "0.1.5", "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.4",+ "version": "0.1.5", "private": true, "description": "adom-desktop bridge for the Adom native-browser extension (Chrome + Edge)", "main": "server.js",
bridge/server.js+15−5@@ -470,7 +470,9 @@ function matchHwnd(list, bounds, title) { // each window's OS hwnd by BOUNDS (profile-independent). This badges adopted SPAWNED windows AND // SELF-HEALS agent-OPENED windows: if the open-time badge landed on the wrong profile's window (the old // diff bug), a list call re-correlates by bounds, clears the STALE badge, and paints the correct one.-async function badgeSpawnsIn(payload) {+function emailOf(key) { const s = String(key || ''); const i = s.indexOf(':'); return i >= 0 ? s.slice(i + 1) : s; }+async function badgeSpawnsIn(payload, profileKey) {+ const email = emailOf(profileKey); const spawns = []; // { windowId, bounds, title } const owned = []; // { sid, bounds, title } if (payload && Array.isArray(payload.spawned)) for (const s of payload.spawned) if (s.kind === 'window' && s.windowId != null) spawns.push({ windowId: s.windowId, bounds: s.bounds, title: s.title });@@ -483,11 +485,18 @@ async function badgeSpawnsIn(payload) { if (!fresh.length && !owned.length) return; let list; try { list = await chromeWindowsDetailed(); } catch { return; } for (const s of fresh) { const h = matchHwnd(list, s.bounds, s.title); if (h) { spawnHwnd.set(s.windowId, h); taskbar(h, { overlay: { badge: 'adom', tooltip: 'Adom is driving this browser' } }); } }+ // PROFILE-LEVEL badge: the OS window title for Google pages contains the profile email, and Windows+ // taskbar buttons group by profile — so badging ANY window of the driven profile lights the right+ // button. This is STABLE while pages navigate (unlike a per-window title), which is why per-window+ // correlation was flaky on the actively-driven work profile. Prefer it; fall back to per-window match.+ let profileHwnd = null;+ if (email.includes('@')) { const m = list.find((w) => (w.title || '').includes(email)); if (m) profileHwnd = m.hwnd; } for (const o of owned) {- const h = matchHwnd(list, o.bounds, o.title);+ const h = profileHwnd || matchHwnd(list, o.bounds, o.title); if (!h) continue; const old = sessionHwnd.get(o.sid);- if (old !== h) { if (old) taskbar(old, { overlay: { badge: 'none' } }).catch(() => {}); sessionHwnd.set(o.sid, h); lastHwnd = h; clearBadge(o.sid); applyBadge(o.sid, h); }+ if (old !== h) { if (old && old !== h) taskbar(old, { overlay: { badge: 'none' } }).catch(() => {}); sessionHwnd.set(o.sid, h); lastHwnd = h; clearBadge(o.sid); applyBadge(o.sid, h); }+ else touchBadge(o.sid); } } // A "come look" moment = a verb in COMELOOK, OR a semantic signal in the payload that the user should@@ -1239,7 +1248,7 @@ function dispatch(verb, args) { pending.delete(id); resolve({ success: false, error: `verb ${verb} timed out`, _hint: 'extension did not reply in time' }); }, timeoutFor(verb));- pending.set(id, { resolve, timer, verb, toFile: !!(args && args.toFile) });+ pending.set(id, { resolve, timer, verb, toFile: !!(args && args.toFile), profileKey: tgt.key }); sock.write(JSON.stringify({ id, verb, args: args || {} }) + '\n'); }; // PIN the session to whatever profile this window opened in, so every later verb carrying this@@ -1311,6 +1320,7 @@ async function onHostFrame(frame) { // 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 } catch {} if (neu) { lastHwnd = neu; if (sid) sessionHwnd.set(sid, neu); applyBadge(sid, neu); break; } // applyBadge = badge + arm idle-expiry await new Promise((r) => setTimeout(r, 250));@@ -1320,7 +1330,7 @@ async function onHostFrame(frame) { } // Badge any adopted SPAWNED windows surfaced by owned/list_windows (they have their own hwnd, no // sessionId, so we correlate by bounds on-demand — there is no push channel).- if (p.verb === 'nbrowser_owned' || p.verb === 'nbrowser_list_windows') { badgeSpawnsIn(payload).catch(() => {}); }+ if (p.verb === 'nbrowser_owned' || p.verb === 'nbrowser_list_windows') { badgeSpawnsIn(payload, p.profileKey).catch(() => {}); } if (p.verb === 'nbrowser_close_window' || p.verb === 'nbrowser_cleanup') { // clean up the taskbar when we stop driving const sid = payload.sessionId; const h = (sid && sessionHwnd.get(sid)) || lastHwnd;
native-browser-bridge-manifest.json+4−4@@ -1,8 +1,8 @@ { "manifest_version": 1,- "version": "0.1.4",- "url": "https://wiki.adom.inc/download/adom/adom-browser-extension/0.1.4/native-browser-bridge-v0.1.4.bin",- "sha256": "b46af4f0432d4489a4c7ceba88c19ce3173b33f978acb5dafbbdaf3a6f28161a",- "size": 54596,+ "version": "0.1.5",+ "url": "https://wiki.adom.inc/download/adom/adom-browser-extension/0.1.5/native-browser-bridge-v0.1.5.bin",+ "sha256": "37900c24ec1d583f201f74577c41523c0864cf4fa7bc4025e907eee80767edad",+ "size": 54953, "released_at": "2026-07-14T00:00:00Z" }