← Commit history

bridge 0.0.28: add nbrowser_install_chrome + nbrowser_chrome_status (install native Google Chrome, pup-style quiet user-scoped install + notify-first UAC escalation); readiness reports chromeInstalled

John Lauer ·381f19cbc9 ·4mo ago ·parent f78fab2
5 files changed +209−12
bridge/BRIDGE_VERSION+1−1
@@ -1 +1 @@-0.0.27\ No newline at end of file+0.0.28\ No newline at end of file
bridge/bridge.json+3−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "native-browser",   "displayName": "Adom Native Browser (Chrome + Edge)",-  "version": "0.0.27",+  "version": "0.0.28",   "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",   "author": "Adom Inc.",@@ -22,6 +22,8 @@   "verbs": [     "nbrowser_status",     "nbrowser_readiness",+    "nbrowser_chrome_status",+    "nbrowser_install_chrome",     "nbrowser_ping",     "nbrowser_open_window",     "nbrowser_close_window",
bridge/package.json+1−1
@@ -1,6 +1,6 @@ {   "name": "adom-native-browser-bridge",-  "version": "0.0.27",+  "version": "0.0.28",   "private": true,   "description": "adom-desktop bridge for the Adom native-browser extension (Chrome + Edge)",   "main": "server.js",
bridge/server.js+199−4
@@ -288,7 +288,11 @@ function discoverAdBase() { function adCommand(app, command, args) {   return new Promise((resolve) => {     let u; try { u = new URL('/command', discoverAdBase()); } catch { u = new URL('http://127.0.0.1:47200/command'); }-    const data = Buffer.from(JSON.stringify({ app, command, args: args || {} }));+    // app is OPTIONAL on AD 1.9.84+ (inferred) - omit it for top-level verbs like notify_user, keep it+    // when a caller passes one (e.g. desktop_* recording delegation).+    const payload = { command, args: args || {} };+    if (app) payload.app = app;+    const data = Buffer.from(JSON.stringify(payload));     // X-Adom-Bridge-Token is ATTRIBUTION only (AD 1.9.84) - AD injects ADOM_BRIDGE_TOKEN into our spawn     // env; sending it badges our outbound calls as "bridge" in the Activity Log. It is NOT an approval     // gate (we're trusted-by-install; AD binds 127.0.0.1 only). Omitting it is fine; a stale token 403s.@@ -409,6 +413,169 @@ function flashHint() {   return `${FLASH_HINT}${flashMode}. Get the user's attention: nbrowser_flash {sessionId}. The USER may prefer more/less noise - nbrowser_flash_mode {mode:"quiet"|"chatty"|"off"} (quiet=come-look moments only [default]; chatty=every action, for users who want to watch; off=silent). On a human-in-the-loop gate (captcha/2FA/login), nbrowser_flash so they come help. Never leave it strobing - it auto-clears on focus, or nbrowser_flash_stop.`; } +// ============================================================================================+// Native Google Chrome installer — for a user on an Edge-only box who wants the AI to drive their+// REAL Chrome. The whole point of this bridge is the AI controlling the user's native browser; if they+// have no Chrome, we get them one. Mirrors pup's proven flow: download Google's OFFICIAL offline+// installer, run a QUIET user-scoped `/silent /install` (no admin on a normal PC → %LOCALAPPDATA%\+// Google\Chrome), and on a locked-down box (VM/managed) ESCALATE to a notify-FIRST UAC (fire a sticky+// "Approve now" toast via the AD direct-API, wait for the tap, THEN raise the prompt so the secure+// desktop doesn't hide the toast; re-arm if it expires; ping peer ADs if the user is on another+// machine). Non-blocking: nbrowser_install_chrome returns immediately; poll nbrowser_chrome_status.+// ============================================================================================+const CHROME_STABLE_URL = 'https://dl.google.com/chrome/install/standalonesetup64.exe';+const chromeState = { phase: 'idle', error: null, errorCode: null, startedAt: null, finishedAt: null, awaitingUacSince: null, notifyChannel: null };+let _chromeInFlight = null;++function detectChrome() {+  if (process.platform !== 'win32') return null;+  const home = process.env.USERPROFILE || os.homedir();+  const pf = process.env['ProgramFiles'] || 'C:\\Program Files';+  const pf86 = process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)';+  const cands = [+    path.join(pf, 'Google', 'Chrome', 'Application', 'chrome.exe'),+    path.join(pf86, 'Google', 'Chrome', 'Application', 'chrome.exe'),+    path.join(home, 'AppData', 'Local', 'Google', 'Chrome', 'Application', 'chrome.exe'),+  ];+  for (const c of cands) { try { if (fs.existsSync(c)) return c; } catch {} }+  return null;+}+const _sleep = (ms) => new Promise((r) => setTimeout(r, ms));+const _psq = (s) => "'" + String(s).replace(/'/g, "''") + "'";++function _downloadChrome(url, destPath, redirects = 0) {+  return new Promise((resolve, reject) => {+    if (redirects > 5) return reject(new Error('too many redirects fetching the Chrome installer'));+    const https = require('https');+    const file = fs.createWriteStream(destPath);+    https.get(url, (resp) => {+      if ([301, 302, 303, 307, 308].includes(resp.statusCode) && resp.headers.location) {+        file.close(); try { fs.unlinkSync(destPath); } catch {}+        return resolve(_downloadChrome(resp.headers.location, destPath, redirects + 1));+      }+      if (resp.statusCode !== 200) { file.close(); try { fs.unlinkSync(destPath); } catch {} return reject(new Error(`HTTP ${resp.statusCode} fetching ${url}`)); }+      resp.pipe(file);+      file.on('finish', () => file.close(() => resolve(destPath)));+      file.on('error', (e) => { try { fs.unlinkSync(destPath); } catch {} reject(e); });+    }).on('error', (e) => { try { fs.unlinkSync(destPath); } catch {} reject(e); });+  });+}++// notify helpers via the AD direct-API (app-less on AD 1.9.84). Best-effort: null on any failure.+const _notify = (args) => adCommand(null, 'notify_user', args);+const _notifyResp = (id) => adCommand(null, 'notify_response', { id });+async function _adCanNotify() {+  const r = await adCommand(null, 'notify_user', { id: '_probe_', title: '', body: '', level: 'info', _probe: true }).catch(() => null);+  // if AD accepted (any 2xx-ish body) treat notify as available; unknown-verb → unavailable+  const body = r && r.body;+  if (!r || r.status === 0) return false;+  const err = body && (body.error || body.errorCode);+  if (err && /unknown/i.test(String(err))) return false;+  return true;+}++// Raise the UAC by launching the installer elevated (Start-Process -Verb RunAs).+function _raiseElevatedChrome(installerPath) {+  return new Promise((resolve) => {+    try {+      const { spawn } = require('child_process');+      const ps = `try { Start-Process -FilePath ${_psq(installerPath)} -ArgumentList '/silent','/install' -Verb RunAs -WindowStyle Hidden -ErrorAction Stop; exit 0 } catch { exit 3 }`;+      const child = spawn('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', ps], { windowsHide: true });+      let done = false; const fin = (c) => { if (!done) { done = true; resolve(c); } };+      child.on('error', () => fin(-1));+      child.on('exit', (code) => fin(code == null ? 0 : code));+      setTimeout(() => fin(0), 20000);+    } catch { resolve(-1); }+  });+}++// Locked-down box path: quiet install didn't land → drive the user to the UAC. Notify-FIRST if AD can+// notify (fire toast, WAIT for tap, THEN raise UAC so the secure desktop doesn't hide it; re-arm on+// expiry). If AD can't notify, raise ONCE and surface awaiting_uac for the caller to relay (no spam).+async function _elevatedChromeWithNotify(installerPath) {+  const id = 'nbrowser-chrome-install';+  chromeState.phase = 'awaiting_uac'; chromeState.awaitingUacSince = Date.now();+  const canNotify = await _adCanNotify();+  chromeState.notifyChannel = canNotify;+  const host = (() => { try { return os.hostname(); } catch { return 'this PC'; } })();+  const pollChrome = async (ms) => { const end = Date.now() + ms; while (Date.now() < end) { await _sleep(2500); const c = detectChrome(); if (c) return c; } return null; };++  if (!canNotify) {+    await _raiseElevatedChrome(installerPath);+    return await pollChrome(150 * 1000);+  }+  const OVERALL_MS = 12 * 60 * 1000; const t0 = Date.now(); let first = true;+  while (Date.now() - t0 < OVERALL_MS) {+    await _notify({ id, level: 'warning', scenario: 'reminder', buttons: [{ label: 'Approve now' }],+      title: first ? 'Chrome needs your approval' : 'Chrome still needs your approval',+      body: `Tap 'Approve now', then click YES on the Windows security prompt to finish installing Google Chrome on ${host}.` });+    first = false;+    let tapped = false; const waitEnd = Date.now() + 4 * 60 * 1000;+    while (Date.now() < waitEnd) {+      await _sleep(2500);+      const c = detectChrome();+      if (c) { await _notify({ id, level: 'success', title: 'Chrome is installed ✓', body: 'Google Chrome is ready. Next: sign into your Google account, then load the Adom extension.' }); return c; }+      const resp = await _notifyResp(id);+      const rb = resp && resp.body;+      if (rb && rb.pending === false && rb.action) { tapped = true; break; }+    }+    if (!tapped) continue;+    await _raiseElevatedChrome(installerPath);+    const c = await pollChrome(130 * 1000);+    if (c) { await _notify({ id, level: 'success', title: 'Chrome is installed ✓', body: 'Google Chrome is ready. Next: sign into your Google account, then load the Adom extension.' }); return c; }+  }+  return null;+}++// opts.elevate: 'auto' (default) quiet-then-escalate | true skip-quiet | false quiet-only-no-prompt.+function installNativeChrome(opts = {}) {+  if (_chromeInFlight) return _chromeInFlight;+  const elevate = opts.elevate === undefined ? 'auto' : opts.elevate;+  _chromeInFlight = (async () => {+    chromeState.phase = 'installing'; chromeState.error = null; chromeState.errorCode = null;+    chromeState.startedAt = Date.now(); chromeState.finishedAt = null; chromeState.awaitingUacSince = null;+    try {+      const pre = detectChrome();+      if (pre) { chromeState.phase = 'ready'; chromeState.finishedAt = Date.now(); return { ok: true, executablePath: pre, alreadyInstalled: true }; }+      if (process.platform !== 'win32') {+        chromeState.phase = 'failed'; chromeState.errorCode = 'chrome_unsupported_os';+        chromeState.error = 'Auto-installing Google Chrome is wired up for Windows only.';+        return { ok: false, errorCode: chromeState.errorCode, error: chromeState.error };+      }+      const tmp = path.join(os.tmpdir(), `chrome_installer_${process.pid}.exe`);+      await _downloadChrome(CHROME_STABLE_URL, tmp);+      let found = null;+      if (elevate !== true) {+        const { spawn } = require('child_process');+        try { spawn(tmp, ['/silent', '/install'], { windowsHide: true }); } catch {}+        for (let i = 0; i < 30; i++) { found = detectChrome(); if (found) break; await _sleep(1500); } // ~45s+      }+      if (!found && elevate !== false) found = await _elevatedChromeWithNotify(tmp);+      try { fs.unlinkSync(tmp); } catch {}+      if (!found) {+        chromeState.phase = 'failed';+        if (elevate === false) { chromeState.errorCode = 'chrome_not_detected'; chromeState.error = 'Ran the Chrome installer but chrome.exe did not appear (no elevation, elevate:false).'; }+        else if (chromeState.notifyChannel) { chromeState.errorCode = 'chrome_needs_approval'; chromeState.error = 'Chrome needs a Windows approval (UAC) that was not accepted in time. A sticky "Approve now" toast is on the desktop; tapping it re-opens the prompt.'; }+        else { chromeState.errorCode = 'chrome_needs_approval_uncced'; chromeState.error = 'Chrome needs a Windows approval (UAC), but this AD cannot pop a notification. RELAY TO THE USER: go to the machine and click YES on the Google Chrome installer prompt, then re-call nbrowser_install_chrome.'; }+        return { ok: false, errorCode: chromeState.errorCode, error: chromeState.error };+      }+      chromeState.phase = 'ready'; chromeState.finishedAt = Date.now();+      return { ok: true, executablePath: found.executablePath || found };+    } catch (e) {+      chromeState.phase = 'failed';+      const msg = (e && e.message) || String(e);+      chromeState.errorCode = /ENOSPC|space/i.test(msg) ? 'chrome_install_no_disk' : /ENOTFOUND|ETIMEDOUT|ECONNRESET|network|proxy|getaddrinfo/i.test(msg) ? 'chrome_download_network' : 'chrome_install_failed';+      chromeState.error = msg;+      return { ok: false, errorCode: chromeState.errorCode, error: msg };+    } finally { _chromeInFlight = null; }+  })();+  return _chromeInFlight;+}+function chromeStatus() {+  const exe = detectChrome();+  return { installed: !!exe, executablePath: exe || null, phase: exe ? 'ready' : chromeState.phase, error: chromeState.error, errorCode: chromeState.errorCode, installing: !!_chromeInFlight, awaitingUac: chromeState.phase === 'awaiting_uac', notifyChannel: chromeState.notifyChannel };+}+ // ============================================================================================ // VERB_META — ONE source of truth for per-verb hint/related/pitfalls. Feeds BOTH (a) nbrowser_describe // (so AD's Verbs tab shows guidance + chaining + traps) AND (b) the rich `_hint` attached to EVERY@@ -420,7 +587,9 @@ function flashHint() { const OWN_ONLY = 'ONLY operate on a window/tab YOU opened via nbrowser_open_window. NEVER navigate/reload/close a window or tab the user already had open - the bridge drives their REAL browser, so you would destroy their live page, forms, sessions, or other AI threads.'; const VERB_META = {   nbrowser_status: { hint: 'Connectivity check: connected profiles + active browser. Call this first if any verb errors "not connected".', related: ['nbrowser_profiles', 'nbrowser_ping', 'nbrowser_open_window'], pitfalls: ['a profile\'s extension SLEEPS when that profile has no open windows - it then shows disconnected; open a window to wake it'] },-  nbrowser_readiness: { hint: 'READ-ONLY cold-start probe: is the native-browser extension connected + ready to drive? NEVER spawns work or installs anything. state:"ready" = drive now; state:"needs-extension" = offer to install it (Level-2, see installing-the-extension) or it is asleep (open a window in that profile).', related: ['nbrowser_status', 'nbrowser_open_window'], pitfalls: ['"needs-extension" can mean not-installed OR installed-but-asleep - nbrowser_status\'s tooltip distinguishes', 'this verb never installs; to install, run the installing-the-extension flow', 'for a machine-wide "what do I have" answer prefer AD\'s bridge_readiness (aggregates all bridges, spawns none)'] },+  nbrowser_readiness: { hint: 'READ-ONLY cold-start probe: is the native-browser extension connected + ready to drive? NEVER spawns work or installs anything. state:"ready" = drive now; state:"needs-extension" = offer to install it (Level-2, see installing-the-extension) or it is asleep (open a window in that profile). Also reports chromeInstalled: if false, the user has no native Chrome and you can offer nbrowser_install_chrome.', related: ['nbrowser_status', 'nbrowser_open_window', 'nbrowser_install_chrome'], pitfalls: ['"needs-extension" can mean not-installed OR installed-but-asleep - nbrowser_status\'s tooltip distinguishes', 'this verb never installs; to install, run the installing-the-extension flow', 'for a machine-wide "what do I have" answer prefer AD\'s bridge_readiness (aggregates all bridges, spawns none)'] },+  nbrowser_install_chrome: { hint: '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). NON-BLOCKING - returns immediately; poll nbrowser_chrome_status. On a locked-down box it escalates to a notify-first UAC (fires a sticky "Approve now" toast, waits for the tap, then raises the prompt). This is step 1 of getting the AI onto the user\'s REAL browser - after Chrome is in, help them sign into Google + load the extension.', related: ['nbrowser_chrome_status', 'nbrowser_readiness', 'installing-the-extension'], pitfalls: ['non-blocking: do NOT expect Chrome to exist on return - POLL nbrowser_chrome_status until phase:"ready"', 'awaiting_uac means a prompt is on the user\'s desktop - relay it; do not re-trigger install', 'Windows only for now', 'this only INSTALLS Chrome; the user still signs into Google themselves (their credential) - guide them via setting-up-native-chrome'] },+  nbrowser_chrome_status: { hint: 'READ-ONLY: is native Google Chrome installed, and how is an in-flight install going? Returns {installed, executablePath, phase, installing, awaitingUac}. phase: idle|installing|awaiting_uac|ready|failed. Poll this after nbrowser_install_chrome. Never installs or spawns.', related: ['nbrowser_install_chrome', 'nbrowser_readiness'], pitfalls: ['awaiting_uac + notifyChannel:true = a toast is on the desktop, keep polling; notifyChannel:false = relay the UAC to the user yourself', 'installed:true means Chrome.exe is present - next help the user sign into Google + load the extension'] },   nbrowser_ping: { hint: 'Round-trip liveness through to the extension.', related: ['nbrowser_status'], pitfalls: [] },   nbrowser_open_window: { hint: 'Open a NEW background window YOU own and get a sessionId; do ALL your work through that sessionId. This is how you get a surface to drive WITHOUT touching the user\'s own windows.', related: ['nbrowser_navigate', 'nbrowser_screenshot', 'nbrowser_close_window'], next: 'nbrowser_navigate', pitfalls: ['opens UNFOCUSED - do NOT pass focused:true unless the user asked to watch', 'this is the safe way to work: always drive a window you opened, never the user\'s existing ones'] },   nbrowser_close_window: { hint: 'Close a window you opened. Clean up your own sessions when done.', related: ['nbrowser_list_windows'], pitfalls: ['⛔ only close a sessionId YOU opened - closing the user\'s window can wipe their live work and other AI threads'] },@@ -491,17 +660,41 @@ function dispatch(verb, args) {     if (verb === 'nbrowser_readiness') {       const live = liveConns();       const ready = live.length > 0;+      const chExe = detectChrome();       return resolve({ success: true, output: JSON.stringify({         ready,         state: ready ? 'ready' : 'needs-extension',         installing: false,         extensionConnected: ready,         profiles: live.map(([k]) => k),+        chromeInstalled: !!chExe,               // is native Google Chrome present on the box?+        chromeInstallPhase: chExe ? 'ready' : chromeState.phase,  // idle|installing|awaiting_uac|ready|failed         _hint: ready           ? `The Adom browser extension is connected (${live.length} profile(s)); drive it with nbrowser_open_window. Read-only probe - it never spawns work or installs.`-          : 'The Adom browser extension is NOT connected. It may be not-yet-installed (a Level-2 proactively-offered install: OFFER to install it for the user, then run the installing-the-extension flow - never make them do it) OR installed-but-asleep (the service worker sleeps when a profile has no open window; open one). Read-only: this verb never installs or spawns.',+          : `The Adom 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.`,       }) });     }+    // nbrowser_install_chrome - install native Google Chrome for the user (Windows). Non-blocking:+    // returns immediately; poll nbrowser_chrome_status / nbrowser_readiness. Quiet user-scoped install+    // (no admin on a normal PC); on a locked-down box escalates to a notify-first UAC.+    if (verb === 'nbrowser_install_chrome') {+      const exe = detectChrome();+      if (exe) return resolve({ success: true, output: JSON.stringify({ ok: true, installed: true, alreadyInstalled: true, executablePath: exe, _hint: 'Native Google Chrome is already installed. Next in the onboarding: help the user sign into their Google account (so passwords/bookmarks/autofill sync), then load the Adom extension (installing-the-extension). See setting-up-native-chrome.' }) });+      installNativeChrome({ elevate: (args && args.elevate !== undefined) ? args.elevate : 'auto' }); // fire-and-forget; poll status+      return resolve({ success: true, output: JSON.stringify({ ok: true, installing: true, phase: 'installing', _hint: 'Installing Google Chrome in the BACKGROUND (downloading Google\'s official installer, then a quiet user-scoped install). On a normal PC this is silent, NO prompt. On a locked-down box (VM/managed) it needs a Windows approval - the bridge fires a sticky "Approve now" toast and waits for the user to tap it, then raises the UAC. Poll nbrowser_chrome_status: phase goes installing -> awaiting_uac -> ready. Then help the user sign into Google + load the extension (setting-up-native-chrome).', statusVerb: 'nbrowser_chrome_status' }) });+    }+    // nbrowser_chrome_status - READ-ONLY: is native Chrome installed / how's an in-flight install going?+    if (verb === 'nbrowser_chrome_status') {+      const st = chromeStatus();+      const hint = st.installed+        ? 'Native Google Chrome is installed. Next: help the user sign into their Google account (open chrome://settings or accounts.google.com and turn ON sync so passwords/bookmarks/autofill carry over), then load the Adom extension (installing-the-extension). Full flow: setting-up-native-chrome.'+        : st.awaitingUac+          ? (st.notifyChannel ? 'Waiting on the user to approve the Windows security prompt. A sticky "Approve now" toast is on their desktop; tapping it re-opens the UAC. Keep polling; do NOT re-trigger install.' : 'A UAC was raised once but this AD cannot pop a notification. RELAY TO THE USER: go to the machine and click YES on the Google Chrome installer prompt, then re-poll.')+          : st.installing ? 'Chrome install in progress (downloading / silent install). Poll again in a few seconds.'+          : st.phase === 'failed' ? `Chrome install failed (${st.errorCode}): ${st.error}. Fixes depend on the code - e.g. free disk, check network, or ask the user to install Chrome manually.`+          : 'No native Chrome yet and no install running. Call nbrowser_install_chrome to install it.';+      return resolve({ success: true, output: JSON.stringify({ ...st, _hint: hint }) });+    }     // nbrowser_describe - per-verb schemas for AD's Verbs tab + inline runner (AD v1.9.23+).     // AD POSTs {command:"nbrowser_describe",args:{}} and renders each verb expandable with these I/O     // docs, prefilling the runner with `example`. One entry per declared verb (+ describe itself).@@ -514,7 +707,9 @@ 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_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.', {}, { ready: 'bool', state: '"ready"|"needs-extension"', installing: 'bool', extensionConnected: 'bool', profiles: '[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.', {}, { ready: 'bool', state: '"ready"|"needs-extension"', installing: 'bool', extensionConnected: 'bool', profiles: '[str]', chromeInstalled: 'bool', chromeInstallPhase: 'str' }, 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),         d('nbrowser_open_window', 'Open a NEW window in the BACKGROUND (unfocused) and return a sessionId to drive it.', { url: 'required str', profile: P, focused: 'optional bool (default false = background)' }, { ok: 'bool', sessionId: 'str', tabId: 'str', windowId: 'int', url: 'str' }, 30, { example: { url: 'https://wiki.adom.inc' } }),         d('nbrowser_close_window', 'Close a window the agent opened.', { sessionId: 'required str', profile: P }, { ok: 'bool', closed: 'bool', sessionId: 'str' }, 20, { example: { sessionId: 'work' } }),
native-browser-bridge-manifest.json+5−5
@@ -1,8 +1,8 @@ {   "manifest_version": 1,-  "version": "0.0.27",-  "url": "native-browser-bridge-v0.0.27.bin",-  "sha256": "c257e6d606c8fde84b5afc9c3bbce8c7a5ad4e0fd84ff7b192ce3f6a2c1513bb",-  "size": 28996,-  "released_at": "2026-07-05T12:03:13Z"+  "version": "0.0.28",+  "url": "native-browser-bridge-v0.0.28.bin",+  "sha256": "5f09c7f678cb16e1af82b68ad437987d305a750d34dbb15e8209eeed22fbd62a",+  "size": 34071,+  "released_at": "2026-07-05T12:28:54Z" }\ No newline at end of file