← Commit history
0.3.5: readiness means DRIVABLE - pending-* no longer counts as ready; unroutable pending fails fast as extension_resolving instead of hanging
5 files changed
+32−12
bridge/BRIDGE_VERSION+1−1@@ -1 +1 @@-0.3.4+0.3.5
bridge/bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "native-browser", "displayName": "Adom Native Browser",- "version": "0.3.4",+ "version": "0.3.5", "expectedExtensionVersion": "0.2.0", "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.3.4",+ "version": "0.3.5", "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+24−4@@ -105,6 +105,12 @@ function saveBlocklist() { try { fs.writeFileSync(BLOCKFILE, JSON.stringify({ bl function isBlocked(key) { return blocklist.has(key); } function liveConns() { return [...conns.entries()].filter(([k, c]) => c.sock && !c.sock.destroyed && !isBlocked(k)); }+// A `pending-*` key is a connected extension whose PROFILE IDENTITY has not resolved yet. targetConn+// cannot route to it, so it must never count as "ready" - reporting ready:true off one told the AI to+// drive and every drive then failed 'extension not connected' (or hung 30s with no profile arg).+const isPending = (k) => /^pending-/.test(String(k || ''));+function drivableConns() { return liveConns().filter(([k]) => !isPending(k)); }+function pendingConns() { return liveConns().filter(([k]) => isPending(k)); } function anyConnected() { return liveConns().length > 0; } // Pick the connection an action verb should target: explicit args.profile, else the active profile, // else the only/most-recent live one. Returns null if none drivable (or the target is blocklisted).@@ -138,8 +144,15 @@ function targetConn(args) { for (const [k, c] of conns) if (k === pin && c.sock && !c.sock.destroyed && !isBlocked(k)) return { conn: c, key: k, pinned: true }; // pinned profile no longer live → fall through to the normal default rather than hard-failing. }- const live = liveConns();- if (!live.length) return null;+ const drivable = drivableConns();+ const live = drivable.length ? drivable : [];+ if (!live.length) {+ // Connected but unresolved (pending-*) is NOT drivable - say so fast instead of forwarding into a+ // socket that will never answer (issue: readiness said ready, drives then hung for 30s).+ const pend = pendingConns();+ if (pend.length) return { unresolved: true, pending: pend.map(([k]) => k) };+ return null;+ } const act = live.find(([k]) => k === activeProfile); const [k, c] = act || live[live.length - 1]; return { conn: c, key: k };@@ -1392,7 +1405,8 @@ function dispatch(verb, args) { // installs. This bridge has no heavy runtime (tiny Node), so "readiness" == the Level-2 extension // being connected. ab's own bridge_readiness aggregates across bridges; this reports THIS one. if (verb === 'nbrowser_readiness') {- const live = liveConns();+ const live = drivableConns();+ const pend = pendingConns(); const ready = live.length > 0; const chExe = detectChrome(); // Per-profile extension version + stale check. A loaded-unpacked extension does NOT auto-update@@ -1438,7 +1452,8 @@ function dispatch(verb, args) { const claudeExt = claudeExtAnywhere(); return resolve({ success: true, output: JSON.stringify({ ready,- state: ready ? 'ready' : 'needs-extension',+ state: ready ? 'ready' : (pend.length ? 'resolving' : 'needs-extension'),+ pendingProfiles: pend.map(([k]) => k), // connected but identity unresolved: NOT drivable claudeExtension: claudeExt, // Claude for Chrome presence (human-fallback awareness; never drivable) installing: false, extensionConnected: ready,@@ -1456,6 +1471,8 @@ function dispatch(verb, args) { ? `STALE EXTENSION: ${staleDetail}; this bridge expects ${EXPECTED_EXT}. New verbs (wait_for/keepalive/download_wait/set_date/totp/robust-click) will return "verb not implemented yet" on the stale profile(s). SELF-HEAL: (1) refresh the unpacked extension dir to the current build (installing-the-extension -> Updating, or re-sync the extension/ folder), then (2) nbrowser_dev_reload {"profile":"<staleProfile>"} - it re-reads disk in 2 passes and reconnects in ~10s. If the on-disk build is ALREADY current, step (2) alone heals it.` + skewHint : ready ? `nbe (the Adom Native Browser Extension) is connected (${live.length} profile(s)) and current (v${EXPECTED_EXT || '?'}); drive it with nbrowser_open_window. Read-only probe - it never spawns work or installs.`+ : pend.length+ ? `nbe IS connected (${pend.length} socket(s)) but its PROFILE IDENTITY has not resolved yet (${pend.map(([k]) => k).join(', ')}), so NOTHING is drivable yet - every drive verb will refuse 'extension not connected' until it resolves. This is normal for a few seconds after a browser start. FIX: nbrowser_wake_profile {profile:"chrome:<email>"} (from nbrowser_profiles) forces the resolve, or the user opening any window in that profile completes it. Poll this verb until state:"ready".` : `nbe (the Adom Native Browser Extension) is NOT connected. ${chExe ? '' : 'This box has NO native Google Chrome (Edge may be present). If the user wants the AI to drive their REAL Chrome, offer nbrowser_install_chrome (installs Google Chrome, mostly no prompt). '}${claudeExt.installed ? `NOTE: the user HAS the Claude for Chrome extension (${claudeExt.profiles.join(', ')}) - you cannot drive it (closed control plane), but the honest interim is real: they can ask Claude in their browser to work a page interactively, while you offer nbe for full automation from any AI thread. ` : ''}It may be not-yet-installed (a Level-2 proactively-offered install: OFFER to install it, then run installing-the-extension - never make them do it) OR installed-but-asleep (the SW sleeps when a profile has no open window). Full onboarding: the setting-up-native-chrome skill. Read-only: never installs or spawns.` + skewHint + hostHint, }) }); }@@ -2222,6 +2239,9 @@ function dispatch(verb, args) { sessionProfile.delete(tgt.sessionId); persistSessions(); tgt = targetConn(args); // repin below - a pending-* pin was never a real identity }+ if (tgt && tgt.unresolved) {+ return resolve({ success: false, errorCode: 'extension_resolving', error: `the extension is connected but its profile identity has not resolved yet (${(tgt.pending || []).join(', ')}), so there is nothing drivable to route to`, _hint: 'This is normal for a few seconds after a browser start. Run nbrowser_profiles to see the real profiles, then nbrowser_wake_profile {profile:"chrome:<email>"} to force the resolve - or just poll nbrowser_readiness until state:"ready" (it reports state:"resolving" while this is true). Failing fast on purpose: forwarding to an unresolved connection used to hang the caller for the full timeout.' });+ } if (tgt && tgt.mismatch) { return resolve({ success: false, errorCode: 'profile_mismatch', error: `profile_mismatch: session '${tgt.sessionId}' is pinned to profile '${tgt.pin}', but you called with profile '${tgt.wantKey}'.`, _hint: `Refused so you never act on the WRONG account (personal vs corporate). Drop the profile arg (the session already routes to '${tgt.pin}'), or open a NEW registered session in '${tgt.wantKey}' if you truly meant that account.` }); }
native-browser-bridge-manifest.json+5−5@@ -1,8 +1,8 @@ { "manifest_version": 1,- "version": "0.3.4",- "url": "https://wiki.adom.inc/download/adom/nb-bridge/0.3.4/native-browser-bridge-v0.3.4.zip",- "sha256": "3ca007845a04ab9d73ec2327ef9387c090f4cb7cbda336386854b1fb79b1c963",- "size": 107028,- "released_at": "2026-08-19T08:24:41Z"+ "version": "0.3.5",+ "url": "https://wiki.adom.inc/download/adom/nb-bridge/0.3.5/native-browser-bridge-v0.3.5.zip",+ "sha256": "65ebfd4ab939f918ba1dacc894a1b73e62575a709a2d68216c64a267ba6ffc55",+ "size": 107771,+ "released_at": "2026-08-19T08:49:54Z" }