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.13: probe AD notify capability, degrade honestly (raise UAC once + relay hint), stop blind re-raise; notify-first ordering when AD exposes notify
4 files changed
+100−38
adom-bridge-puppeteer-manifest.json+4−4@@ -1,10 +1,10 @@ { "manifest_version": 1, "name": "puppeteer",- "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,+ "version": "1.8.13",+ "url": "https://wiki.adom.inc/download/adom/adom-desktop-puppeteer-bridge/1.8.13/adom-bridge-puppeteer-v1.8.13.zip",+ "sha256": "3b90ecd842bd2d8727c37eec14f4b4f68f853422a38e3e7e219eb9a0f9bcc0df",+ "size": 138306, "verbPrefixes": [ "browser_", "desktop_recorder_",
src/BRIDGE_VERSION+1−1@@ -1 +1 @@-1.8.12+1.8.13
src/bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "puppeteer", "displayName": "Puppeteer (Chrome for Testing + Native)",- "version": "1.8.12",+ "version": "1.8.13", "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+94−32@@ -444,7 +444,8 @@ function readiness() { 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, // 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+ chromeStableAwaitingApproval: chromeStableState.phase === 'awaiting_uac', // a UAC is up on the machine running pup; the user must click YES there+ chromeStableNotifyChannel: chromeStableState.notifyChannel === undefined ? null : !!chromeStableState.notifyChannel, // could the bridge actually notify the user? (false on AD builds that don't expose notify_user to the direct-API — the AI must relay the approval ask) chromeStableError: chromeStableState.error, puppeteerLoadError: _puppeteerLoadError, };@@ -507,14 +508,15 @@ function _resolveAdApiBase() { } // 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) {+// `app` defaults to the probed namespace notify_user lives under (see _probeAdNotifyCap).+function adCommand(command, args, app = 'desktop', 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 body = JSON.stringify({ app, command, args: args || {} }); const u = new URL(base + '/command'); const req = http.request( { hostname: u.hostname, port: u.port, path: u.pathname, method: 'POST',@@ -527,8 +529,40 @@ function adCommand(command, args, timeoutMs = 8000) { } catch { resolve(null); } }); }-const adNotify = (args) => adCommand('notify_user', args);-const adNotifyResponse = (id) => adCommand('notify_response', { id });+function _adGet(pathname, timeoutMs = 6000) {+ return new Promise((resolve) => {+ let base; try { base = _resolveAdApiBase(); } catch { return resolve(null); }+ if (!base) return resolve(null);+ try {+ const http = require('http'); const u = new URL(base + pathname);+ const req = http.get({ hostname: u.hostname, port: u.port, path: u.pathname },+ (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); });+ } catch { resolve(null); }+ });+}+// Probe whether this AD build actually exposes notify_user through the direct-API.+// As of AD 1.9.79 it does NOT — the `desktop` app only dispatches `desktop_*` verbs,+// and top-level verbs (notify_user/notify_response) return "Unknown desktop command".+// So a bridge cannot fire a toast until AD adds it (see HANDOFF/AD build prompt). We+// probe /commands and cache the answer: {app} if reachable, null if not. This is the+// gate that stops us from blindly raising a UAC the user is never told about.+let _adNotifyCap = undefined; // undefined=unprobed | null=unavailable | {app}+async function _probeAdNotifyCap() {+ if (_adNotifyCap !== undefined) return _adNotifyCap;+ _adNotifyCap = null;+ try {+ const cmds = await _adGet('/commands');+ if (cmds && cmds.desktop) {+ const list = cmds.desktop.commands || cmds.desktop || [];+ if (Array.isArray(list) && (list.includes('notify_user') || list.includes('desktop_notify_user'))) _adNotifyCap = { app: 'desktop' };+ }+ } catch {}+ return _adNotifyCap;+}+const adNotify = (args) => adCommand('notify_user', args, (_adNotifyCap && _adNotifyCap.app) || 'desktop');+const adNotifyResponse = (id) => adCommand('notify_response', { id }, (_adNotifyCap && _adNotifyCap.app) || 'desktop'); const _sleep = (ms) => new Promise(r => setTimeout(r, ms)); // PowerShell single-quote escaping (double any embedded quote).@@ -554,45 +588,68 @@ function _raiseElevatedInstall(installerPath) { } // 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.+// Chrome needs elevation. TWO modes, gated on whether AD actually lets us notify:+//+// (A) AD notify IS reachable → notify-FIRST: fire a sticky "Approve now" toast and+// WAIT for the user to tap it BEFORE raising the UAC. This is the correct order —+// raising the UAC first puts up the secure desktop, which HIDES the toast (the bug+// that made the first cut fire prompts the user was never told about). On tap we+// raise one UAC; if it expires we re-notify (sticky) and wait again. Autonomous,+// zero AI turns.+//+// (B) AD notify is NOT reachable (AD ≤1.9.79 doesn't expose notify_user to the direct-+// API) → we CANNOT tell the user, so we DO NOT spam UACs. Raise the prompt exactly+// ONCE, mark awaiting_uac with a clear hint the AI/caller must relay, and poll. No+// blind re-raise loop. (Pending the AD change, this is the honest degradation.) 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;+ const cap = await _probeAdNotifyCap();+ chromeStableState.notifyChannel = !!cap;++ const pollChrome = async (ms) => {+ const end = Date.now() + ms;+ while (Date.now() < end) { await _sleep(2500); const c = listSystemBrowsers().find(x => x.kind === 'chrome'); if (c) return c; }+ return null;+ };++ // (B) No way to notify — one prompt, no spam, clear state for the caller to relay.+ if (!cap) {+ console.log('[chrome] AD notify NOT reachable via direct-API — raising the UAC ONCE (no re-arm loop) and surfacing awaiting_uac for the caller to relay');+ chromeStableState.awaitingApprovalSince = Date.now();+ await _raiseElevatedInstall(installerPath);+ return await pollChrome(150 * 1000); // ~2.5 min for the user to click the single prompt+ }++ // (A) Notify-first with re-arm on tap.+ const OVERALL_MS = 12 * 60 * 1000;+ const t0 = Date.now();+ let first = true; 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.",+ 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. (Standard Google installer.)", }); first = false;- await _raiseElevatedInstall(installerPath);- const roundEnd = Date.now() + ROUND_MS;- while (Date.now() < roundEnd) {+ // WAIT for the tap before raising the UAC (so the toast isn't hidden by the secure desktop).+ let tapped = false;+ const waitEnd = Date.now() + 4 * 60 * 1000; // re-notify (sticky) if untouched for 4 min+ while (Date.now() < waitEnd) { 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;- }+ 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);- }+ if (resp && resp.pending === false && resp.action) { tapped = true; break; } }- // Round elapsed with no install → loop top re-notifies (sticky) + re-raises.+ if (!tapped) continue; // untouched → loop top re-fires the sticky toast+ await _raiseElevatedInstall(installerPath); // they opted in → one UAC now+ const c = await pollChrome(130 * 1000);+ 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; }+ // UAC expired/declined → loop top re-notifies and waits for another tap. }- return null; // gave up after OVERALL_MS — a sticky reminder is still on screen+ return null; } // opts.elevate: 'auto' (default) = quiet first, escalate to the notified UAC path if@@ -645,10 +702,15 @@ function installChromeStable(opts = {}) { 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 {+ } else if (chromeStableState.notifyChannel) { 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.';+ chromeStableState.error = 'Chrome needs a Windows approval (UAC) that was not accepted in time. A sticky "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." });+ } else {+ // No notify channel on this AD — we raised ONE UAC and could not tell the user.+ // The CALLER (AI) must relay this; do NOT silently spam more prompts.+ chromeStableState.errorCode = 'chrome_stable_needs_approval_uncced';+ chromeStableState.error = 'Chrome needs a Windows approval (UAC) on THIS machine, but this AD build does not expose notify_user to bridges via the direct-API, so pup could not pop a notification. A UAC was raised once. RELAY TO THE USER: "Go to the machine running pup and click YES on the Google Chrome installer prompt", then re-call browser_use {browser:"chrome", install:true}. pup drives Edge meanwhile.'; } return { ok: false, errorCode: chromeStableState.errorCode, error: chromeStableState.error }; }