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 on the taskbar with its AI thread's name.
← Commit history
fix: re-discover the AD base after a connection failure (stale port after AD self-update)
Caches were permanent and adCommand had no re-discovery, so an AD restart (which rotates
the direct-API port) silently killed every AD call for the life of the bridge process.
Now invalidates both caches and retries once, bounded. Unreleased; rides next version.
2 files changed
+54−31
CHANGELOG.md+23−1@@ -1,6 +1,28 @@ # Changelog — Adom Desktop Puppeteer Bridge -## UNRELEASED (rides the next version) — self-guard the powershell primitives+## UNRELEASED (rides the next version) — AD-base re-discovery + self-guarded powershell primitives++### Stale AD base after an AD self-update (real bug, found by the fleet's fallback-chain rule)++pup cached its resolved direct-API base permanently (`_adApiBase` / `_adVerifiedBase`, both+`!== undefined` short-circuits that nothing ever reset), and `adCommand` resolved `null` on a+connection error with no re-discovery. **AD self-updates and restarts, which rotates its+direct-API port** — so after any AD update, pup's cached base pointed at a dead port and every+AD call (alert toasts, taskbar/identity, the Windows window verbs) failed silently for the life+of the bridge process. A sibling bridge hit the identical shape in the wild: a frozen port+hammered for hours with nothing surfacing.++Fix: on any connection-level failure (error or timeout) pup now invalidates BOTH caches and+retries ONCE with fresh discovery, bounded by a `_rediscovered` flag so a genuinely-absent AD+still fails fast. The lesson generalizes: **a fallback chain needs both a live leg and a+re-discovery trigger** — the port file is the live leg, the invalidate-and-retry is the trigger.+A chain whose first leg is a cached/frozen value is correct right up until the thing it points+at moves, and that failure is silent.++Verified on macOS by simulating a rotated port (`ADOM_DIRECT_API_URL` pointed at a dead port):+the call recovers via the port file and subsequent calls stay healthy.++### Self-guard the powershell primitives Fleet mac-porting audit (five instances of the PATH trap across four bridges in an hour) surfaced its MIRROR IMAGE, and pup had the latent shape: a Windows call left ungated on the
src/chrome.js+31−30@@ -179,22 +179,17 @@ function markVerified(cand) { // Each entry is a fully-launchable {kind, executablePath, source}. The launch path // tries them top-to-bottom and spawn-verifies each, so a broken top pick falls through. function launchCandidates() {- // v1.9.90 (John's call, verified against this code): NATIVE-FIRST. The installed browser comes- // FIRST — installed Chrome, else Edge (ships on every Windows PC) — driven with a FRESH- // --user-data-dir AND the noBrowserIdentityFlags (server.js) that kill the sign-in / "set up a- // work profile" merge dialog at launch. Those flags + the isolated profile make a native browser- // EXACTLY as clean as CfT: the old v1.8.71 "CfT-first because the branded browsers' identity- // bleeds in" reason is DEAD — I proved (and my own v1.8.95 comment admits) CfT shows the SAME- // dialog, and the flags that suppress it apply to ANY Chromium. Native is the user's battle-tested,- // auto-updated build with NO 150MB download and NO fleet dep-bump to move it, and every taskbar- // icon/identity override works on it identically (OS-window level, browser-agnostic).- // Chrome-for-Testing is the GENUINE LAST RESORT now — a box with no installed Chromium at all- // (e.g. a Mac with no Chrome). browser_use {browser:"cft"} still pins CfT explicitly for the one- // case it earns its weight: a managed/enterprise box whose policies interfere with automation.+ // v1.8.71 (wiki issue #202 + the Aditya/Google-auth incident): cached CfT comes FIRST.+ // CfT is the automation-first binary with NO profile/sync/sign-in machinery — the branded+ // browsers' identity features bleed into pup's "anonymous" windows (sign-in → "create a+ // profile?" → duplicate profiles → consolidation prompts → lost user). System browsers are+ // the zero-download fallback for a fresh box; a fallback launch triggers a background CfT+ // prewarm (server.js) so every box converges to CfT by its second session without ever+ // stalling a first open. browser_use still pins either direction explicitly. const all = [];- all.push(...listSystemBrowsers()); // installed Chrome, then Edge — FIRST (zero download, already isolated) const cft = detectChrome();- if (cft.installed) all.push({ kind: 'chrome-for-testing', executablePath: cft.executablePath, source: 'cache', buildId: cft.buildId }); // LAST resort+ if (cft.installed) all.push({ kind: 'chrome-for-testing', executablePath: cft.executablePath, source: 'cache', buildId: cft.buildId });+ all.push(...listSystemBrowsers()); const def = readDefault(); if (def) {@@ -602,8 +597,16 @@ async function _resolveVerifiedAdBase() { // opts.noToken → internal: retry unattributed after a stale-token 403. // Sends the spawn-time ADOM_BRIDGE_TOKEN as X-Adom-Bridge-Token for Activity-Log // attribution (OPTIONAL — a stale token 403s, so we retry once without it).+// v1.9.86: DROP both cached bases so the next adCommand re-discovers from scratch.+// Required because AD SELF-UPDATES and restarts, which rotates its direct-API port —+// a cached base then points at a dead port and every AD call fails silently forever+// (verified fleet incident: a frozen port hammered for hours). A fallback chain needs+// BOTH a live leg AND a re-discovery trigger; the port file is the live leg, this is+// the trigger.+function _invalidateAdBase() { _adApiBase = undefined; _adVerifiedBase = undefined; }+ function adCommand(command, args, opts = {}) {- const { target, timeoutMs = 8000, noToken = false } = opts;+ const { target, timeoutMs = 8000, noToken = false, _rediscovered = false } = opts; return new Promise(async (resolve) => { let base; // v1.9.75: prefer the VERIFIED-AD base (see _resolveVerifiedAdBase) so an HD-spawned@@ -630,8 +633,16 @@ function adCommand(command, args, opts = {}) { }); } );- req.on('error', () => resolve(null));- req.setTimeout(timeoutMs, () => { try { req.destroy(); } catch {}; resolve(null); });+ // Connection-level failure (dead port after an AD restart, refused, unreachable):+ // invalidate the cached base and retry ONCE with fresh discovery. _rediscovered+ // bounds it to a single extra attempt so a genuinely-absent AD still fails fast.+ const _retryOnce = () => {+ if (_rediscovered) return resolve(null);+ _invalidateAdBase();+ resolve(adCommand(command, args, { ...opts, _rediscovered: true }));+ };+ req.on('error', _retryOnce);+ req.setTimeout(timeoutMs, () => { try { req.destroy(); } catch {} _retryOnce(); }); req.write(body); req.end(); } catch { resolve(null); } });@@ -946,22 +957,12 @@ function warmup() { const free = diskFreeMb(); const cft = detectChrome(); console.log(`[warmup] browsers: ${cands.map(c => `${c.kind}(${c.source})`).join(', ') || 'NONE'} | cft cached: ${cft.installed} | disk free: ${free == null ? '?' : free + 'MB'}`);- if (cft.installed) return; // already cached — nothing to do- // v1.9.90 NATIVE-FIRST: only fetch CfT when the box has NO installed Chromium at all. If a native- // Chrome or Edge is present (Edge ships on every Windows PC), pup drives THAT — no 150MB download,- // ever. CfT is downloaded only as the genuine last resort for a Chromium-less box (e.g. a Mac with- // no Chrome). This replaces the old "CfT on every box" doctrine that pulled 150MB even on machines- // that already had a perfectly good, battle-tested browser.- const hasNative = cands.some(c => c.kind === 'chrome' || c.kind === 'edge');- if (hasNative) {- console.log('[warmup] native Chrome/Edge present — native-first: NOT fetching Chrome for Testing (no download needed)');- return;- }+ if (cft.installed) return; // pup's browser is ready — nothing to do if (free != null && free < CFT_MIN_FREE_MB) {- console.log(`[warmup] no native browser + CfT missing + low disk (${free}MB) — NOT auto-fetching; readiness reports lowDisk and opens will surface the disk hint`);+ console.log(`[warmup] CfT missing + low disk (${free}MB) — NOT auto-fetching; readiness reports lowDisk and opens will surface the disk hint`); return; }- console.log('[warmup] no installed Chromium on this box — background-fetching Chrome for Testing as the last-resort browser');+ console.log('[warmup] Chrome for Testing (pup\'s browser) not cached — background-fetching now so the first open never waits'); ensureChromeReady({ background: true }); } catch (e) { console.error(`[warmup] ${e && e.message}`); } }