← Commit history

0.2.3: Claude-for-Chrome detection in the degradation ladder (human fallback awareness, never drivable)

John Lauer ·5ccf704c1e ·3mo ago ·parent 173d93b
6 files changed +57−11
bridge/BRIDGE_VERSION+1−1
@@ -1 +1 @@-0.2.2+0.2.3
bridge/bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "native-browser",   "displayName": "Adom Native Browser",-  "version": "0.2.2",+  "version": "0.2.3",   "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.2.2",+  "version": "0.2.3",   "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+35−3
@@ -1419,9 +1419,11 @@ function dispatch(verb, args) {       const skewHint = staleOnDisk.length         ? ` ⛔ VERSION SKEW (likely the cause of "Native host has exited"): ${staleOnDisk.map((x) => `${x.profile} has extension ${x.installed}`).join(', ')}, but this bridge (${VERSION}) expects extension ${EXPECTED_EXT}. An extension/host that old cannot complete the native-messaging handshake, so the host exits with no message and the SW never connects. FIX: update the extension in that profile to ${EXPECTED_EXT} (re-sync the extension/ + host/ folders per installing-the-extension), then nbrowser_dev_reload {"profile":"<profile>"}. Note: the extension line (${EXPECTED_EXT}) and the skills-package line (a 0.5.x number on the wiki page) are DIFFERENT version streams - match the extension against expectedExtensionVersion, not the pkg version.`         : '';+      const claudeExt = claudeExtAnywhere();       return resolve({ success: true, output: JSON.stringify({         ready,         state: ready ? 'ready' : 'needs-extension',+        claudeExtension: claudeExt,             // Claude for Chrome presence (human-fallback awareness; never drivable)         installing: false,         extensionConnected: ready,         profiles: live.map(([k]) => k),@@ -1438,7 +1440,7 @@ 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.`-          : `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). '}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,+          : `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,       }) });     }     // nbrowser_install_chrome - install native Google Chrome for the user (Windows). Non-blocking:@@ -1475,7 +1477,7 @@ function dispatch(verb, args) {       const verbs = [         d('nbrowser_status', 'Is the extension connected? Reports ext id/version + active browser.', {}, { ok: 'bool', ext: 'str', version: 'str', engine: 'str', browser: '"chrome"|"edge"' }, 15),         d('nbrowser_activity', 'Bounded "who drove what, when" activity trail (max 600 entries / ~1h, in-memory, resets on bridge restart). Returns recent verb entries + a per-session summary. Debug a stray badge or see what is running.', { sessionId: 'optional str', thread: 'optional str', profile: 'optional str', verb: 'optional str', errorsOnly: 'optional bool', sinceMs: 'optional int', limit: 'optional int (<=600)' }, { ok: 'bool', entries: '[{agoMs,verb,sessionId,thread,profile,ok,errorCode}]', activeSessions: '[{sessionId,thread,purpose,count,lastAgoMs}]' }, 15, { example: {} }),-        d('nbrowser_readiness', 'READ-ONLY cold-start probe: is the native-browser extension connected + ready to drive? Never spawns work or installs. Use for "is it ready / do I have the browser extension" instead of nbrowser_status. Also flags a STALE extension (loaded build older than this bridge expects) so you fix it up-front instead of hitting "verb not implemented yet" mid-task.', {}, { ready: 'bool', state: '"ready"|"needs-extension"', installing: 'bool', extensionConnected: 'bool', profiles: '[str]', extensionVersions: '{profile:version}', expectedExtensionVersion: 'str|null', extensionStale: 'bool', staleProfiles: '[str]', chromeInstalled: 'bool', chromeInstallPhase: 'str' }, 15, { example: {} }),+        d('nbrowser_readiness', 'READ-ONLY cold-start probe: is the native-browser extension connected + ready to drive? Never spawns work or installs. Use for "is it ready / do I have the browser extension" instead of nbrowser_status. Also flags a STALE extension (loaded build older than this bridge expects) so you fix it up-front instead of hitting "verb not implemented yet" mid-task.', {}, { ready: 'bool', state: '"ready"|"needs-extension"', installing: 'bool', extensionConnected: 'bool', profiles: '[str]', extensionVersions: '{profile:version}', expectedExtensionVersion: 'str|null', extensionStale: 'bool', staleProfiles: '[str]', chromeInstalled: 'bool', chromeInstallPhase: 'str', claudeExtension: '{installed:bool, profiles:[str]} - Claude for Chrome presence (human fallback; never drivable)' }, 15, { example: {} }),         d('nbrowser_install_chrome', 'Install NATIVE Google Chrome for the user (Windows). Downloads Google\'s official installer + runs a quiet user-scoped install (no admin on a normal PC); escalates to a notify-first UAC on a locked-down box. NON-BLOCKING - poll nbrowser_chrome_status. Step 1 of putting the AI onto the user\'s real signed-in browser.', { elevate: 'optional "auto"(default) | true (skip quiet, straight to UAC) | false (quiet only, no prompt)' }, { ok: 'bool', installing: 'bool', installed: 'bool?', alreadyInstalled: 'bool?', executablePath: 'str?', statusVerb: 'str' }, 30, { statusVerb: 'nbrowser_chrome_status', example: {} }),         d('nbrowser_chrome_status', 'READ-ONLY: is native Google Chrome installed / how is an install going? Poll after nbrowser_install_chrome.', {}, { installed: 'bool', executablePath: 'str?', phase: '"idle"|"installing"|"awaiting_uac"|"ready"|"failed"', installing: 'bool', awaitingUac: 'bool', errorCode: 'str?' }, 15, { example: {} }),         d('nbrowser_ping', 'Round-trip liveness check through to the extension.', {}, { ok: 'bool', pong: 'bool' }, 15),@@ -1802,7 +1804,7 @@ function dispatch(verb, args) {         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 nbe 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.`;+          : `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 nbe 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.${claudeExtAnywhere().installed ? ' (The user DOES have the Claude for Chrome extension - a fair interim: they can ask Claude in their browser to work this page interactively. nbe remains the automation path for ANY AI thread.)' : ''}`;         // STABLE SUCCESS CONTRACT (fusion-bridge report, 2026-07-21): a fired launch IS success for a         // consent flow - "the page is now in front of the user". hwnd resolution is a SEPARATE fact.         // Callers must never have to regex _hint. ok/launched = did the browser launch; hwndResolved =@@ -2452,6 +2454,36 @@ function adomExtStateForProfile(browser, profileDir) {     return { checked: true, installed: false };   } catch { return { checked: false }; } }+// CLAUDE-EXTENSION DETECTION (John, 2026-08-10): "Claude for Chrome" is a REAL layer-2-class agent+// extension many users already have. We cannot drive it (closed control plane: it pairs with the+// user's claude.ai account, no local API), so it is NEVER a programmatic fallback - but the+// degradation ladder should KNOW it exists so the upsell can offer the honest human fallback:+// "your Claude extension can work this page interactively; nbe adds full automation for any AI."+function claudeExtStateForProfile(browser, profileDir) {+  try {+    const local = process.env.LOCALAPPDATA;+    if (!local || !profileDir) return { checked: false };+    const root = browser === 'edge' ? path.join(local, 'Microsoft', 'Edge', 'User Data') : path.join(local, 'Google', 'Chrome', 'User Data');+    for (const f of ['Secure Preferences', 'Preferences']) {+      let j; try { j = JSON.parse(fs.readFileSync(path.join(root, profileDir, f), 'utf8')); } catch { continue; }+      const settings = (j.extensions && j.extensions.settings) || {};+      for (const [, ent] of Object.entries(settings)) {+        const nm = String((ent && ent.manifest && ent.manifest.name) || '');+        if (!/^claude$/i.test(nm.trim())) continue;+        return { checked: true, installed: true, version: (ent.manifest && ent.manifest.version) || null, enabled: ent.state !== 0 };+      }+    }+    return { checked: true, installed: false };+  } catch { return { checked: false }; }+}+function claudeExtAnywhere() {+  const profiles = [];+  for (const kp of knownProfiles()) {+    const st = claudeExtStateForProfile(kp.browser, kp.profileDir);+    if (st.installed && st.enabled) profiles.push(kp.key);+  }+  return { installed: profiles.length > 0, profiles };+} // The unpacked source dir of the extension, learned from ANY profile that has it - so a fix hint can // say exactly what path to Load-unpacked into the broken profile. function adomExtKnownPath() {
dev-skills/developing-the-bridge/SKILL.md+14
@@ -402,3 +402,17 @@ filesystem paths**. ab's data dir is `%LOCALAPPDATA%\Adom Bridge\bridges-cache\` `.ps1` installers hardcode that cache path, so they silently install into a directory ab no longer reads. Verify every path literal against the ab source, and keep binaries (`.gif` too, not just `.png`) out of any bulk text rewrite.++## ⭐ Claude for Chrome in the degradation ladder (John, 2026-08-10)++Many users already have Anthropic's "Claude" extension in their real Chrome (John does, both+profiles). It is a layer-2-CLASS agent extension we can NEVER drive: its control plane is closed+(pairs with the user's claude.ai account; no native-messaging host, no local API). So it is not a+programmatic fallback, and piggybacking it would also bypass everything nb's trust story promises+(registration, badges, activity trail, credential gates) - a security regression, not a feature.++What we DO instead: `claudeExtStateForProfile()` detects it from the same Preferences scan we use+for our own extension, `nbrowser_readiness` reports `claudeExtension:{installed, profiles}`, and+the needs-extension hint + the layer-1 upsell mention the HONEST human fallback: the user can ask+Claude in their browser to work the page interactively, while nbe remains the automation path for+ANY AI thread (Adom is not Claude-only - that is the differentiator to state, not hide).
native-browser-bridge-manifest.json+5−5
@@ -1,8 +1,8 @@ {   "manifest_version": 1,-  "version": "0.2.2",-  "url": "https://wiki.adom.inc/download/adom/nb-bridge/0.2.2/native-browser-bridge-v0.2.2.zip",-  "sha256": "4f7373fb47dc02297c0b034a949a8c35a44cb8f4efe638c37456ad5ddf067546",-  "size": 100725,-  "released_at": "2026-08-09T10:50:47Z"+  "version": "0.2.3",+  "url": "https://wiki.adom.inc/download/adom/nb-bridge/0.2.3/native-browser-bridge-v0.2.3.zip",+  "sha256": "593e0ec16dfc47e8f568f3d8e76a5448d998f1b22f4ce12e9ac35a6076770000",+  "size": 101449,+  "released_at": "2026-08-13T13:38:42Z" }