← 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
5 files changed
+209−12
bridge/BRIDGE_VERSION+1−1@@ -1 +1 @@-0.0.27+0.0.28
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" }