app
Pup - the AI's own browser
Public Made by Adomby adom
pup gives your AI a real browser on your desktop that it drives completely, in the background, signed in as you: windows that never jump in front of your work, one shared profile that learns your logins, and every window labeled with its AI thread's name in your taskbar or dock.
← Commit history
v1.8.12: Chrome install escalates to a notified UAC on locked-down boxes (bridge fires AD toasts via direct-API + one-tap re-arm)
5 files changed
+171−31
adom-bridge-puppeteer-manifest.json+4−4@@ -1,10 +1,10 @@ { "manifest_version": 1, "name": "puppeteer",- "version": "1.8.11",- "url": "https://wiki.adom.inc/download/adom/adom-desktop-puppeteer-bridge/1.8.11/adom-bridge-puppeteer-v1.8.11.zip",- "sha256": "79dd9c8bb74e6cce31253e678a01cfa948fa6df0ca7e06628157a312d071e5c1",- "size": 133816,+ "version": "1.8.12",+ "url": "https://wiki.adom.inc/download/adom/adom-desktop-puppeteer-bridge/1.8.12/adom-bridge-puppeteer-v1.8.12.zip",+ "sha256": "b2b8ad3c15017ef123adda783ca36f5967a20a4604c951001fce63f5a2d34c1e",+ "size": 137156, "verbPrefixes": [ "browser_", "desktop_recorder_",
src/BRIDGE_VERSION+1−1@@ -1 +1 @@-1.8.11+1.8.12
src/bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "puppeteer", "displayName": "Puppeteer (Chrome for Testing + Native)",- "version": "1.8.11",+ "version": "1.8.12", "description": "Drives a browser ALREADY on the machine \u2014 installed Chrome, else Microsoft Edge (every Windows PC ships with Edge) \u2014 launched with a FRESH isolated profile, so the common case has NO ~150 MB Chrome-for-Testing download and it never touches the user's real tabs/logins. Binds its local server to LOOPBACK (127.0.0.1) only, so no Windows Firewall 'allow Node through the firewall' prompt ever pops. Chrome for Testing is fetched only as a LAST RESORT if the machine has no Chromium browser at all. SPAWN-VERIFIED fallback: pup tries each detected browser (Chrome \u2192 Edge \u2192 CfT) and actually launches it, falling through to the next if one won't start (and handling browsers like Edge that relaunch onto another PID), so a corrupt/partial install never hard-fails an open. The browser that launches is cached as the default (persisted across restarts) so it isn't re-probed each time. On spawn the bridge SELF-WARMS: detects browsers + disk, and only if the box has no Chrome/Edge and no cached CfT does it background-fetch CfT so the first open is instant. browser_use pins a specific browser (chrome | edge | cft | auto); {browser:'chrome',install:true} downloads & installs REAL Google Chrome (user-scoped, no admin) on an Edge-only box. browser_readiness (READ-ONLY) reports which browser will be used + the full candidate list + the default + free disk. Every failure returns a specific errorCode + _hint (chrome_install_no_disk, chrome_download_network, chrome_install_permission, chrome_stable_not_detected, \u2026). Launches with FULL browser permissions by default for full debug (strictPermissions:true opts out). Headful automation: open/close windows+tabs, navigate, screenshot, eval JS, record. pup is a GENERIC, non-profile browser only \u2014 every session is a fresh isolated Chrome/Edge process, never the user's real signed-in browser; profile defaults to sessionId. To drive the user's REAL Chrome/Edge (their profile, cookies, SSO), use the Adom browser EXTENSION's nbrowser_* verbs \u2014 a separate bridge, not pup's job.", "homepage": "https://wiki.adom.inc/adom/adom-desktop", "author": "Adom Inc.",
src/chrome.js+162−22@@ -216,7 +216,7 @@ function resolveBrowser() { // browser: 'auto' | 'chrome' | 'edge' | 'cft' | 'chrome-for-testing' (or pass executablePath) // 'cft' installs Chrome for Testing in the background if it isn't cached, and pins it // as the default so once it lands every open uses it. Returns a pickSummary.-function setDefaultBrowser({ browser, executablePath, install } = {}) {+function setDefaultBrowser({ browser, executablePath, install, elevate } = {}) { const b = String(browser || '').toLowerCase().replace(/[\s_-]/g, ''); if (executablePath) { if (!fileExists(executablePath)) return { ok: false, errorCode: 'browser_not_found', error: `No executable at ${executablePath}.`, ...pickSummary() };@@ -236,12 +236,16 @@ function setDefaultBrowser({ browser, executablePath, install } = {}) { // Not installed. For Chrome we can download+install the REAL browser on request. if (b === 'chrome' && install) { writeDefault({ kind: 'chrome', executablePath: null, source: 'system', forced: true, pendingInstall: true, verifiedAt: Date.now() });- installChromeStable(); // background; browser_use polling + readiness track it- return { ok: true, installing: true, installingChrome: true, ...pickSummary() };+ installChromeStable({ elevate }); // background; browser_use polling + readiness track it+ return {+ ok: true, installing: true, installingChrome: true,+ _hint: "Installing Google Chrome in the background. On a normal PC this is silent with NO prompt. On a locked-down box (a VM, managed device) it needs a Windows approval — the bridge will pop a native notification asking the user to click YES on the UAC, and if it expires it re-notifies with a one-tap 'Approve now'. Poll browser_readiness: chromeStablePhase goes installing → awaiting_uac (waiting on the user's click) → ready. pup drives Edge meanwhile, so nothing is blocked.",+ ...pickSummary(),+ }; } const avail = listSystemBrowsers().map(c => c.kind).join(', ') || 'none'; const extra = b === 'chrome'- ? " Pass {browser:'chrome', install:true} to download & install Google Chrome (~90 MB, user-scoped, no admin prompt), or use browser:'cft' / browser:'edge'."+ ? " Pass {browser:'chrome', install:true} to download & install Google Chrome (~90 MB, user-scoped — silent on a normal PC; on a locked-down box it prompts the user for a Windows approval via a notification), or use browser:'cft' / browser:'edge'." : " Edge ships with every Windows PC — if it's genuinely missing, use browser:'chrome' (install:true) or browser:'cft'."; return { ok: false, errorCode: 'browser_not_found', error: `No installed ${b} found. Installed system browsers: ${avail}.${extra}`, ...pickSummary() }; }@@ -439,7 +443,8 @@ function readiness() { diskFreeMb: diskFreeMb(), // free space on the CfT-cache volume (null = unmeasurable) lowDisk: (() => { const f = diskFreeMb(); return f != null && f < CFT_MIN_FREE_MB; })(), installingChrome: !!_chromeStableInFlight, // a real-Google-Chrome install is in flight- chromeStablePhase: chromeStableState.phase,+ chromeStablePhase: chromeStableState.phase, // idle|installing|awaiting_uac|ready|failed+ chromeStableAwaitingApproval: chromeStableState.phase === 'awaiting_uac', // a UAC toast is on the user's desktop; they need to click YES chromeStableError: chromeStableState.error, puppeteerLoadError: _puppeteerLoadError, };@@ -447,10 +452,13 @@ function readiness() { // ── Real Google Chrome (stable) installer ───────────────────────────────────── // When the user explicitly wants CHROME (not CfT) on an Edge-only box, fetch Google's-// official offline installer and run it silently. Run as the (non-admin) user, Chrome's-// installer drops a USER-SCOPED install into %LOCALAPPDATA%\Google\Chrome — no UAC —-// which listSystemBrowsers() already finds. An elevated/admin context may install-// machine-wide and could raise a UAC prompt on the desktop (surfaced via the hint).+// official offline installer and run it. Two phases (installChromeStable):+// 1. QUIET user-scoped `/silent /install` → drops into %LOCALAPPDATA%\Google\Chrome+// with NO prompt on a normal PC (this is the common, unchanged path).+// 2. If that doesn't land (a locked-down box / VM where the installer needs admin),+// ESCALATE: raise the UAC elevated AND drive the user to it with an AD toast fired+// from this bridge via the direct-API — re-notifying with a sticky one-tap+// 'Approve now' if the prompt expires. See _elevatedInstallWithNotify. const chromeStableState = { phase: 'idle', error: null, errorCode: null, startedAt: null, finishedAt: null }; let _chromeStableInFlight = null; const CHROME_STABLE_URL = 'https://dl.google.com/chrome/install/standalonesetup64.exe';@@ -473,8 +481,126 @@ function _download(url, destPath, redirects = 0) { }); } -function installChromeStable() {+// ── Talking back to Adom Desktop from inside the bridge ───────────────────────+// AD spawned us; it also runs a loopback-only direct-API on the SAME machine+// (`POST /command {app,command,args}`, same dispatcher as the CLI/WS path). That+// lets THIS bridge fire a native toast + poll the clicked button on its OWN — no+// AI turn in the loop — which is exactly what an elevation prompt needs: nudge+// the user to the UAC, and if they walk away, re-nudge with a one-tap retry.+// Resolution order: env ADOM_DIRECT_API_URL → ~/.adom/direct-api-port → default.+let _adApiBase = undefined;+function _resolveAdApiBase() {+ if (_adApiBase !== undefined) return _adApiBase;+ _adApiBase = null;+ try {+ const env = process.env.ADOM_DIRECT_API_URL;+ if (env) { _adApiBase = env.replace(/\/+$/, ''); return _adApiBase; }+ const home = process.env.USERPROFILE || os.homedir();+ const pf = path.join(home, '.adom', 'direct-api-port');+ if (fileExists(pf)) {+ const hp = fs.readFileSync(pf, 'utf8').trim(); // "127.0.0.1:47200"+ if (hp) { _adApiBase = `http://${hp}`; return _adApiBase; }+ }+ } catch {}+ _adApiBase = 'http://127.0.0.1:47200'; // documented default; harmless if unreachable+ return _adApiBase;+}+// Fire an AD verb via the direct-API. Best-effort: resolves null on any failure so+// a missing/asleep AD never blocks the install (the UAC still appears regardless).+function adCommand(command, args, timeoutMs = 8000) {+ return new Promise((resolve) => {+ let base;+ try { base = _resolveAdApiBase(); } catch { return resolve(null); }+ if (!base) return resolve(null);+ try {+ const http = require('http');+ const body = JSON.stringify({ app: 'desktop', command, args: args || {} });+ const u = new URL(base + '/command');+ const req = http.request(+ { hostname: u.hostname, port: u.port, path: u.pathname, method: 'POST',+ headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) } },+ (resp) => { let d = ''; resp.on('data', c => d += c); resp.on('end', () => { try { resolve(JSON.parse(d)); } catch { resolve(null); } }); }+ );+ req.on('error', () => resolve(null));+ req.setTimeout(timeoutMs, () => { try { req.destroy(); } catch {}; resolve(null); });+ req.write(body); req.end();+ } catch { resolve(null); }+ });+}+const adNotify = (args) => adCommand('notify_user', args);+const adNotifyResponse = (id) => adCommand('notify_response', { id });+const _sleep = (ms) => new Promise(r => setTimeout(r, ms));++// PowerShell single-quote escaping (double any embedded quote).+function _psq(s) { return `'${String(s).replace(/'/g, "''")}'`; }++// Raise a UAC by launching the installer ELEVATED. Start-Process -Verb RunAs pops+// the Windows security prompt on the user's desktop. Resolves an exit code:+// 0 → prompt accepted (or already elevated); installer launched+// 3 → user declined / cancelled / the prompt expired+// <=0? → spawn failure+function _raiseElevatedInstall(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); // Start-Process returns fast; guard a hung shell+ } catch { resolve(-1); }+ });+}++// Locked-down box (e.g. a VM) path: the quiet user-scoped install didn't land, so+// Chrome needs elevation. Drive the user to the UAC via AD toasts, and — crucially —+// if the prompt expires while they're away, re-notify with a sticky "Approve now"+// button and re-raise a FRESH UAC the instant they tap it. Runs autonomously in the+// background (this is already a background job) so it costs the user zero AI turns.+async function _elevatedInstallWithNotify(installerPath) {+ const id = 'pup-chrome-install';+ const OVERALL_MS = 12 * 60 * 1000; // keep a live prompt available for ~12 min+ const ROUND_MS = 130 * 1000; // ~a UAC's own lifetime before it self-dismisses+ const t0 = Date.now();+ let first = true;+ chromeStableState.phase = 'awaiting_uac';+ chromeStableState.error = null; chromeStableState.errorCode = null;+ while (Date.now() - t0 < OVERALL_MS) {+ await adNotify({+ id, level: 'warning', scenario: 'reminder', buttons: [{ label: 'Approve now' }],+ title: first ? 'Installing Chrome — one click needed' : 'Chrome install still needs your OK',+ body: first+ ? 'A Windows security prompt is opening now — click YES to finish installing Google Chrome. (Standard Google installer.)'+ : "The approval prompt closed before you clicked. Tap 'Approve now', then click YES on the Windows prompt — it pops instantly, no waiting on me.",+ });+ first = false;+ await _raiseElevatedInstall(installerPath);+ const roundEnd = Date.now() + ROUND_MS;+ while (Date.now() < roundEnd) {+ await _sleep(2500);+ const c = listSystemBrowsers().find(x => x.kind === 'chrome');+ if (c) {+ await adNotify({ id, level: 'success', title: 'Chrome is installed ✓', body: 'Google Chrome is ready — pup will drive it from now on.' });+ return c;+ }+ const resp = await adNotifyResponse(id);+ if (resp && resp.pending === false && resp.action) {+ // They asked to retry NOW → fresh UAC immediately, restart this round's clock.+ await _raiseElevatedInstall(installerPath);+ }+ }+ // Round elapsed with no install → loop top re-notifies (sticky) + re-raises.+ }+ return null; // gave up after OVERALL_MS — a sticky reminder is still on screen+}++// opts.elevate: 'auto' (default) = quiet first, escalate to the notified UAC path if+// that doesn't land; true = skip quiet, go straight to the UAC path; false = quiet+// only, no prompt (old behaviour — fail with a hint if it needs admin).+function installChromeStable(opts = {}) { if (_chromeStableInFlight) return _chromeStableInFlight;+ const elevate = opts.elevate === undefined ? 'auto' : opts.elevate; _chromeStableInFlight = (async () => { chromeStableState.phase = 'installing'; chromeStableState.error = null; chromeStableState.errorCode = null; chromeStableState.startedAt = Date.now(); chromeStableState.finishedAt = null;@@ -495,21 +621,35 @@ function installChromeStable() { const tmp = path.join(os.tmpdir(), `chrome_installer_${process.pid}.exe`); console.log(`[chrome] downloading Google Chrome installer → ${tmp}`); await _download(CHROME_STABLE_URL, tmp);- console.log('[chrome] running Chrome installer silently (/silent /install) ...');- const { spawn } = require('child_process');- await new Promise((resolve, reject) => {- let done = false;- const child = spawn(tmp, ['/silent', '/install'], { windowsHide: true });- child.on('error', (e) => { if (!done) { done = true; reject(e); } });- child.on('exit', () => { if (!done) { done = true; resolve(); } });- setTimeout(() => { if (!done) { done = true; resolve(); } }, 180000); // installer may background; we poll next- });+ let found = null;- for (let i = 0; i < 40; i++) { found = listSystemBrowsers().find(x => x.kind === 'chrome'); if (found) break; await new Promise(r => setTimeout(r, 1500)); }+ // Phase 1 — quiet, user-scoped silent install. On a normal box this lands with+ // NO prompt; only if it doesn't do we escalate. (Skipped when elevate===true.)+ if (elevate !== true) {+ console.log('[chrome] running Chrome installer silently (/silent /install) ...');+ const { spawn } = require('child_process');+ try { spawn(tmp, ['/silent', '/install'], { windowsHide: true }); } catch {}+ for (let i = 0; i < 30; i++) { found = listSystemBrowsers().find(x => x.kind === 'chrome'); if (found) break; await _sleep(1500); } // ~45s+ }++ // Phase 2 — escalate: Chrome needs elevation, so drive the user to the UAC with+ // AD toasts + a re-arm-on-tap loop. (elevate:false opts out and just fails.)+ if (!found && elevate !== false) {+ console.log('[chrome] quiet install did not yield chrome.exe — escalating to an elevated install with a user prompt');+ found = await _elevatedInstallWithNotify(tmp);+ }+ try { fs.unlinkSync(tmp); } catch {} if (!found) {- chromeStableState.phase = 'failed'; chromeStableState.errorCode = 'chrome_stable_not_detected';- chromeStableState.error = 'Ran the Chrome installer but chrome.exe did not appear. A Windows UAC/elevation prompt may be waiting on the user\'s desktop — ask them to accept it — or the install was blocked. pup still works with Edge in the meantime.';+ chromeStableState.phase = 'failed';+ if (elevate === false) {+ chromeStableState.errorCode = 'chrome_stable_not_detected';+ chromeStableState.error = 'Ran the Chrome installer but chrome.exe did not appear (elevation was declined via elevate:false). pup still works with Edge in the meantime.';+ } else {+ chromeStableState.errorCode = 'chrome_stable_needs_approval';+ chromeStableState.error = 'Chrome needs a Windows approval (UAC) that was not accepted in time. A "Approve now" notification is on the user\'s desktop — tapping it re-opens the prompt. pup still works with Edge in the meantime.';+ await adNotify({ id: 'pup-chrome-install', level: 'warning', scenario: 'reminder', buttons: [{ label: 'Approve now' }], title: 'Chrome install paused', body: "Ran out of time waiting for approval. Tap 'Approve now' whenever you're back and click YES — Chrome finishes in seconds." });+ } return { ok: false, errorCode: chromeStableState.errorCode, error: chromeStableState.error }; } chromeStableState.phase = 'ready'; chromeStableState.finishedAt = Date.now();
src/server.js+3−3@@ -2502,7 +2502,7 @@ const server = http.createServer(async (req, res) => { V('desktop_record_stop', 'Stop the desktop screen recording; returns the file.', {}, 30, {}), V('browser_readiness', 'Readiness + which browser pup will drive (installed Chrome/Edge, or cached Chrome for Testing) + the full candidate list and current default. READ-ONLY; never downloads.', {}, 10, {}), V('browser_prewarm', 'Install Chrome for Testing WITHOUT opening a window, and pin it as the default. Use when you specifically want CfT rather than the machine\'s installed Chrome/Edge.', { wait: 'optional bool — block until installed', setDefault: 'optional bool (default true) — pin CfT as the default' }, 240, {}),- V('browser_use', 'Pin (or clear) the browser pup drives by default: chrome | edge | cft | auto (or an explicit executablePath). "cft" fetches Chrome for Testing then pins it. With {browser:"chrome", install:true} it DOWNLOADS & INSTALLS real Google Chrome (user-scoped, no admin) on an Edge-only box, then pins it. Persists across restarts; launch still spawn-verifies + falls back.', { browser: '"chrome" | "edge" | "cft" | "auto"', install: 'optional bool — with browser:"chrome", install real Chrome if missing', executablePath: 'optional — pin an exact browser binary' }, 240, {}),+ V('browser_use', 'Pin (or clear) the browser pup drives by default: chrome | edge | cft | auto (or an explicit executablePath). "cft" fetches Chrome for Testing then pins it. With {browser:"chrome", install:true} it DOWNLOADS & INSTALLS real Google Chrome on an Edge-only box, then pins it — SILENT on a normal PC; on a locked-down box (VM/managed device) it needs a Windows approval, so the bridge pops a native "click YES on the UAC" notification and, if it expires, re-notifies with a one-tap "Approve now" (all autonomous — no AI turn). Poll browser_readiness.chromeStablePhase: installing → awaiting_uac → ready. Persists across restarts; launch still spawn-verifies + falls back.', { browser: '"chrome" | "edge" | "cft" | "auto"', install: 'optional bool — with browser:"chrome", install real Chrome if missing', elevate: 'optional "auto"(default)|true|false — auto: quiet install, prompt only if it needs admin; false: never prompt; true: go straight to the prompt', executablePath: 'optional — pin an exact browser binary' }, 240, {}), V('browser_lower_os_window', 'Send a pup window to the BACKGROUND (behind the user\'s windows) without stealing focus — still fully drivable. The default for open; use this to re-hide a window you foregrounded.', { sessionId: 'optional str' }, 15, {}), V('browser_raise_os_window', 'Bring a pup window to the FOREGROUND (user\'s screen). Use SPARINGLY — only when the user must SEE it (a demo, recording, "show me"). Alias: browser_focus_window.', { sessionId: 'optional str' }, 15, {}), V('browser_alert_window', 'Flash the taskbar icon for a pup window — a non-intrusive "it\'s ready/done" nudge that does NOT steal focus. Prefer this over raising the window.', { sessionId: 'optional str' }, 15, {}),@@ -3780,7 +3780,7 @@ const server = http.createServer(async (req, res) => { // for Testing in the background if not cached, then pins it. Persists across // bridge restarts. The launch path still spawn-verifies + falls back, so a // pinned browser that later breaks won't hard-fail an open.- const result = chrome.setDefaultBrowser({ browser: args && args.browser, executablePath: args && args.executablePath, install: !!(args && args.install) });+ const result = chrome.setDefaultBrowser({ browser: args && args.browser, executablePath: args && args.executablePath, install: !!(args && args.install), elevate: args ? args.elevate : undefined }); const rd = chrome.readiness(); const availTxt = (result.candidates || []).map(c => `${c.kind}(${c.source})`).join(', ') || 'none'; let hint;@@ -3789,7 +3789,7 @@ const server = http.createServer(async (req, res) => { } else if (result.cleared) { hint = `Default cleared → AUTO. pup will drive the first browser that launches (${availTxt}) and cache it. Next browser_open_window applies it.`; } else if (result.installingChrome) {- hint = `Downloading & installing REAL Google Chrome (~90 MB, user-scoped — no admin prompt in most cases) and pinning it as the default. Poll browser_readiness: watch installingChrome/chromeStablePhase until Chrome appears (browserKind:"chrome", source:"system"). Until then pup uses ${availTxt}. If it stalls, a UAC prompt may be waiting on the user's desktop — ask them to accept it.`;+ hint = `Downloading & installing REAL Google Chrome (~90 MB, user-scoped) and pinning it as the default. On a normal PC this is SILENT (no prompt). On a locked-down box (VM / managed device) it needs a Windows approval — the bridge pops a native "click YES on the UAC" notification on the user's desktop, and if it expires it re-notifies with a one-tap "Approve now" (no AI turn needed). Poll browser_readiness: chromeStablePhase installing → awaiting_uac (waiting on the user's click; chromeStableAwaitingApproval:true) → ready. Until then pup drives ${availTxt}, so nothing is blocked. Pass {browser:"chrome", install:true, elevate:false} to skip the prompt path, or elevate:true to go straight to it.`; } else if (result.installing) { hint = `Pinned Chrome for Testing as the default and started the ~150 MB download in the background. Poll browser_readiness until ready:true — until then pup falls back to installed Chrome/Edge (${availTxt}). Once cached, every open uses CfT.`; } else {