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
v1.3.0: prefer already-installed browser (Chrome->Edge, fresh profile) => no Chrome-for-Testing download in the common case; CfT only last resort; drop prewarm; readiness reports browserKind/source
8 files changed
+102−108
SKILL.md+15−39@@ -1,7 +1,7 @@ --- name: pup user-invocable: true-description: "Drive Chrome browser windows on the user's desktop via the adom-desktop CLI (the Puppeteer bridge): open/close windows + tabs, navigate, screenshot, eval JS, record, drive the user's REAL signed-in Chrome/Edge/Brave. Cold-start: on a fresh PC Adom Desktop background-fetches Chrome for Testing (Tier-1 prewarm); for 'is it ready' poll AD's bridge_readiness (not browser_status). Trigger words: pup, puppeteer, browser window, open in pup, browser screenshot, browser reload, browser eval, visual debug, browser_open_window, browser_screenshot, browser_readiness, browser_prewarm, chrome for testing, open in browser."+description: "Drive Chrome browser windows on the user's desktop via the adom-desktop CLI (the Puppeteer bridge): open/close windows + tabs, navigate, screenshot, eval JS, record, drive the user's REAL signed-in Chrome/Edge/Brave. Cold-start: pup drives the browser already on the machine (installed Chrome, else Edge — every Windows PC has Edge) with a fresh isolated profile, so usually NO download; Chrome for Testing only as a last resort. For 'is it ready' use browser_readiness (not browser_status). Trigger words: pup, puppeteer, browser window, open in pup, browser screenshot, browser reload, browser eval, visual debug, browser_open_window, browser_screenshot, browser_readiness, browser_prewarm, chrome for testing, open in browser." --- # Pup — Puppeteer Browser Control (with cold-start self-heal)@@ -13,54 +13,30 @@ Bash commands (NOT MCP tools): adom-desktop <command> '<json_args>' ``` -## ⭐ FIRST-TIME / COLD-START — read this BEFORE you panic that "open in pup did nothing"+## ⭐ FIRST-TIME on a fresh PC — usually a non-event now -On a brand-new machine (e.g. the user just installed Hydrogen Desktop, which bundles Adom Desktop),-**two things pup needs are NOT pre-installed by the installer**: Node.js and **Chrome for Testing**-(the actual Chrome build pup drives). The bridge now handles both gracefully — your job is to read the-structured response and follow it, not to give up.+**pup drives the browser that's ALREADY on the machine** — installed **Chrome**, else **Microsoft Edge**+(every Windows PC ships with Edge) — launched with a **fresh isolated profile**. So on a normal box there's+**no download and no wait**: `browser_open_window` just works, and it never touches the user's real+tabs/logins. Chrome for Testing is fetched **only as a last resort** if the machine somehow has no+Chromium browser at all. -When you run `browser_open_window` on a cold PC, one of these happens — handle each:+When you run `browser_open_window`, one of these happens — handle each: | Response | What it means | What you do | |---|---|---|-| `errorCode: "node_not_found"` | Node.js isn't installed (the bridge can't even start). | Run `adom-desktop desktop_install_node '{}'`, wait for it, then retry the `browser_open_window`. |-| `errorCode: "chrome_for_testing_installing"` (`installing:true`) | **Chrome for Testing isn't cached yet; Adom Desktop is fetching it in the BACKGROUND (Tier-1 prewarm, ~150 MB, one time).** The bridge did NOT block. EXPECTED on a fresh PC — NOT a failure. | **Poll AD's `bridge_readiness` until Chrome reports ready** (usually 1–3 min), THEN re-issue the exact same `browser_open_window`. You do NOT install anything yourself. Do NOT report failure. (If it never becomes ready, `browser_prewarm` makes the bridge fetch it itself.) |-| `errorCode: "chrome_install_failed"` / `chrome_download_failed` | The Chrome download itself failed. | Read `readiness.lastError`. It's almost always no network / a proxy blocking `storage.googleapis.com`, or low disk — NOT a Node problem. Retry `browser_prewarm`; if it persists, surface `lastError` to the user. Fallback: drive their real browser via native mode (below). |-| `ok:true` / a real session | Everything was already warm. | Proceed normally. |+| `ok:true` / a real session | A browser (Chrome or Edge) was found and launched. | Proceed — the common case. |+| `errorCode: "node_not_found"` | Node.js isn't installed (the bridge can't even start). | Run `adom-desktop desktop_install_node '{}'`, wait, then retry. |+| `errorCode: "chrome_for_testing_installing"` (`installing:true`) | **RARE** — the machine has NO Chrome AND no Edge, so pup is fetching Chrome for Testing as a fallback (~150 MB, in the background; it did NOT block). | Poll **`browser_readiness`** until `{ready:true}`, then re-issue the same `browser_open_window`. **Faster fix:** ask the user to install Chrome or Edge (Edge ships with Windows) — pup then uses it instantly, no download. Don't report failure. |+| `errorCode: "chrome_install_failed"` | No Chrome/Edge AND the CfT fallback download failed. | Read `readiness.lastError` (usually no network/proxy or low disk). Ask the user to install Chrome/Edge, or drive their real browser via native mode (below). | -### "What do I have / is it ready?" → ask AD's `bridge_readiness` (NOT `browser_status`)+### "What do I have / is it ready?" -For "is pup ready / what's installed," call **`adom-desktop bridge_readiness`** — AD-core's readiness probe.-It reports server-up + Chrome-for-Testing readiness, including AD's **Tier-1 prewarm** progress.-`browser_status` only lists open sessions — it is NOT a readiness check.--### The clean cold-start recipe--```bash-# 1) Ask AD if pup is ready (poll until Chrome shows ready):-adom-desktop bridge_readiness '{}'--# 2) Once ready, open the window — it's instant:-adom-desktop browser_open_window '{"sessionId":"chip-fetcher","profile":"chip-fetcher","url":"<url>"}'-```--On a fresh PC, **Adom Desktop fetches Chrome for Testing in the background** (the bridge declares-`prewarm:["chrome-for-testing"]`); `browser_open_window` returns a non-blocking `{installing:true}` until-it's cached — it never blocks or crashes. So: poll `bridge_readiness` → then open. You never run an-installer yourself.--- **`bridge_readiness`** (AD-core) — THE readiness question: server-up + Chrome cached (+ prewarm progress).-- **`browser_readiness`** (this bridge) — pup's own **READ-ONLY** detail (Chrome build id, fetch phase). Never downloads.-- **`browser_prewarm`** — explicit fallback: have the bridge fetch Chrome itself if AD's prewarm didn't.+- **`browser_readiness`** (this bridge, **READ-ONLY**) → `{ready, browserKind, browserSource, browserExecutablePath, …}` — tells you which browser pup will drive (`chrome` / `edge` / `chrome-for-testing`) and whether it's ready. Never triggers a download.+- **`adom-desktop bridge_readiness`** (AD-core) is the platform-level "is this bridge ready?" probe — use it for the general readiness question. **`browser_status` is NOT a readiness check** (it only lists open sessions). **Always surface a verb's `_hint` verbatim** when something isn't ready. -> Why this matters: before v1.1.0, a fresh PC with no Chrome for Testing made `browser_open_window`-> fail with a generic "Failed to launch Chrome after 3 attempts" and no actionable hint — the AI-> flailed. Now the bridge detects it, installs it, and tells you to poll readiness. Don't reintroduce-> the panic; just follow the table above.- ## How to Run Commands ```bash
adom-bridge-puppeteer-manifest.json+4−9@@ -1,10 +1,10 @@ { "manifest_version": 1, "name": "puppeteer",- "version": "1.2.3",- "url": "https://wiki.adom.inc/download/adom/adom-desktop-puppeteer-bridge/1.2.3/adom-bridge-puppeteer-v1.2.3.zip",- "sha256": "995ee3218a2827b45dbfc6918dac62b977732dc16c84501adb7c563c5c85eb59",- "size": 128005,+ "version": "1.3.0",+ "url": "https://wiki.adom.inc/download/adom/adom-desktop-puppeteer-bridge/1.3.0/adom-bridge-puppeteer-v1.3.0.zip",+ "sha256": "8b80afbe95e0af54dfa2db88a12ff3b71f86711dfc3b479226b6db526cdc86f6",+ "size": 128877, "verbPrefixes": [ "browser_", "desktop_recorder_",@@ -12,10 +12,5 @@ ], "healthEndpoint": "/status", "statusVerb": "browser_readiness",- "prewarm": {- "assets": [- "chrome-for-testing"- ]- }, "released_at": "2026-06-30T00:00:00Z" }
package.json+1−1@@ -1,6 +1,6 @@ { "slug": "adom-desktop-puppeteer-bridge",- "version": "1.2.3",+ "version": "1.3.0", "type": "app", "description": "CONTAINER-SIDE install — the everyday `pup` user skill only. This pkg drops the `pup` skill (drive the browser bridge: open/close/tabs/screenshot/record, background-by-default) into ~/.claude/skills/ AND ~/.codex/skills/. It does NOT install the developer/publishing skills (pup-bridge-dev, pup-bridge-publish) — those are source-only; clone the repo (adom-wiki repo clone adom/adom-desktop-puppeteer-bridge) to get them from dev-skills/. It also does NOT contain the bridge runtime: the bridge (Node + Chrome for Testing) lives on the DESKTOP — bundled inside Adom Desktop and published here on the Releases tab as adom-bridge-puppeteer-v<ver>.zip, which Adom Desktop streams into its bridges-cache. TL;DR: `pkg install` = the pup user skill; dev skills = clone the source; the bridge = the Release zip + bundled in Adom Desktop.", "keywords": [
page.json+4−34@@ -4,15 +4,8 @@ "slug": "adom-desktop-puppeteer-bridge", "title": "Adom Desktop - Puppeteer Bridge", "brief": "The Adom Desktop Puppeteer (pup) bridge — drive Chrome for Testing (or the user's real Chrome/Edge/Brave) from the cloud: open/screenshot/eval/record, with cold-start self-heal that auto-installs Chrome for Testing on a fresh PC. Two artifacts: the Releases zip is the bridge runtime (also bundled in Adom Desktop); the pkg installs the container-side Claude skills.",- "version": "1.2.3",- "tags": [- "skills",- "pup",- "puppeteer",- "browser",- "adom-desktop",- "bridge"- ],+ "version": "1.1.0",+ "tags": [], "license": "MIT", "discovery_triggers": [ "pup",@@ -53,7 +46,7 @@ "fork the pup bridge", "puppeteer bridge source" ],- "discovery_pitch": "Use pup to drive Chrome from the cloud: open/close browser windows + tabs, navigate, screenshot, eval JS, and record windows — driving Chrome for Testing or the user's REAL Chrome/Edge/Brave, in the background so it doesn't disrupt the user. On a fresh PC, Adom Desktop background-fetches Chrome for Testing (Tier-1 prewarm) — poll bridge_readiness, then open. Call the adom-desktop browser_* verbs; the pkg installs the container-side skills.",+ "discovery_pitch": "Use pup to drive Chrome from the cloud: open/close browser windows + tabs, navigate, screenshot, eval JS, and record windows. It drives the browser ALREADY on the machine — installed Chrome, else Microsoft Edge (every Windows PC has Edge) — with a fresh isolated profile, so usually NO download and it doesn't disrupt the user; Chrome for Testing is fetched only as a last resort. For 'is it ready' use browser_readiness. Or pass nativeBrowser+nativeProfile to drive the user's REAL signed-in Chrome/Edge/Brave. Call the adom-desktop browser_* verbs.", "sample_prompts": [ { "label": "Open in pup",@@ -103,28 +96,5 @@ "created_at": "2026-05-28T05:28:43.991Z", "updated_at": "2026-06-16T12:43:19.735Z", "skills": [],- "author_name": "John Lauer",- "description": "CONTAINER-SIDE install — the everyday `pup` user skill only. This pkg drops the `pup` skill (drive the browser bridge: open/close/tabs/screenshot/record, background-by-default) into ~/.claude/skills/ AND ~/.codex/skills/. It does NOT install the developer/publishing skills (pup-bridge-dev, pup-bridge-publish) — those are source-only; clone the repo (adom-wiki repo clone adom/adom-desktop-puppeteer-bridge) to get them from dev-skills/. It also does NOT contain the bridge runtime: the bridge (Node + Chrome for Testing) lives on the DESKTOP — bundled inside Adom Desktop and published here on the Releases tab as adom-bridge-puppeteer-v<ver>.zip, which Adom Desktop streams into its bridges-cache. TL;DR: `pkg install` = the pup user skill; dev skills = clone the source; the bridge = the Release zip + bundled in Adom Desktop.",- "keywords": [- "pup",- "puppeteer",- "browser",- "adom-desktop",- "bridge",- "skills"- ],- "files": [- "SKILL.md",- "skills/**",- "install.sh",- "uninstall.sh",- "README.md",- "package.json"- ],- "dependencies": {},- "scripts": {- "install": "./install.sh",- "uninstall": "./uninstall.sh"- },- "org": "adom"+ "author_name": "John Lauer" }
src/BRIDGE_VERSION+1−1@@ -1 +1 @@-1.2.3+1.3.0
src/bridge.json+2−7@@ -2,8 +2,8 @@ "manifest_version": 1, "name": "puppeteer", "displayName": "Puppeteer (Chrome for Testing + Native)",- "version": "1.2.3",- "description": "Launches with FULL browser permissions by default (all granted — notifications/geolocation/camera/mic/midi/sensors/clipboard/wake-lock/…) so no permission prompt ever blocks full debug; strictPermissions:true opts out. Tier-1 cold-start (for AD v1.9.47+ two-tier model): pup brings its OWN browser (Chrome for Testing), so it needs no detect-block. It declares prewarm:[chrome-for-testing] so Adom Desktop fetches Chrome in the BACKGROUND and reports readiness via bridge_readiness. The bridge server starts fine WITHOUT Chrome; browser_readiness is a READ-ONLY probe (ready = server-up AND Chrome cached; never triggers a download); browser_open returns a non-blocking {installing:true} (never blocks/crashes) until Chrome is cached; browser_prewarm is an explicit bridge-side fetch fallback. Headful browser automation. Default path drives Chrome for Testing (CfT) with managed pup profiles. v1.0.6+ optional native-drive: pass nativeBrowser+nativeProfile (from desktop_list_browsers) to browser_open_window to drive the user's REAL Chrome/Edge/Brave with the actual signed-in account + org policies + device enrollment intact. Smart flow: if the browser is CLOSED, AD launches it fresh immediately with NO prompt (nothing to disrupt) and reports back ready-to-drive (launchedFresh:true); if it's OPEN without the port, AD returns errorCode:'native_relaunch_required' so the caller asks the user, then re-call with nativeRelaunch:true. AD then closes only that browser's own PIDs cleanly (WM_CLOSE — Sessions saved; CfT bridge sessions + other Chromium left untouched) and relaunches with --restore-last-session so every tab/window comes back. If the debug port is policy-blocked (e.g. Google Workspace's RemoteDebuggingAllowed disabled — affects Chrome browser-wide, all profiles), native drive returns errorCode:'native_cdp_blocked' with a `diagnosis` plus an `alternatives` list of other installed browsers — Edge usually escapes Google management. The generic CfT path (browser_open_window with no nativeBrowser) always works as a fallback.",+ "version": "1.3.0",+ "description": "Drives a browser ALREADY on the machine — installed Chrome, else Microsoft Edge (every Windows PC ships with Edge) — 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. Chrome for Testing is fetched only as a LAST RESORT if the machine has no Chromium browser at all. browser_readiness (READ-ONLY) reports which browser will be used (browserKind/browserSource) + whether it's ready; browser_open never blocks on a download. 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. v1.0.6+ optional native-drive: pass nativeBrowser+nativeProfile (from desktop_list_browsers) to browser_open_window to drive the user's REAL signed-in Chrome/Edge/Brave (their actual profile, org policies, device enrollment) — with the smart relaunch/consent flow (native_relaunch_required / native_cdp_blocked). The generic path (no nativeBrowser) always works.", "homepage": "https://wiki.adom.inc/adom/adom-desktop", "author": "Adom Inc.", "license": "MIT",@@ -15,11 +15,6 @@ "stopMethod": "kill", "killImageName": "node.exe" },- "prewarm": {- "assets": [- "chrome-for-testing"- ]- }, "verbPrefixes": [ "browser_", "desktop_recorder_",
src/chrome.js+48−2@@ -93,6 +93,48 @@ function detectChrome() { return { installed: false, executablePath: null, buildId: expectedBuildId() }; } +// ── System browsers (the FAST path — no download) ─────────────────────────────+// Every Windows PC ships with Edge, and most have Chrome — both are Chromium and+// both are drivable by puppeteer. Preferring an ALREADY-INSTALLED browser (launched+// with a FRESH isolated profile) means the common case needs NO ~150 MB Chrome-for-+// Testing download. detectSystemBrowser probes the standard install locations.+function detectSystemBrowser() {+ const pf = process.env['PROGRAMFILES'] || 'C:\\Program Files';+ const pf86 = process.env['PROGRAMFILES(X86)'] || 'C:\\Program Files (x86)';+ const lad = process.env['LOCALAPPDATA'] || '';+ const j = (base, ...rest) => (base ? path.join(base, ...rest) : null);+ const chromePaths = [+ j(pf, 'Google', 'Chrome', 'Application', 'chrome.exe'),+ j(pf86, 'Google', 'Chrome', 'Application', 'chrome.exe'),+ j(lad, 'Google', 'Chrome', 'Application', 'chrome.exe'),+ ].filter(Boolean);+ const edgePaths = [+ j(pf86, 'Microsoft', 'Edge', 'Application', 'msedge.exe'),+ j(pf, 'Microsoft', 'Edge', 'Application', 'msedge.exe'),+ ].filter(Boolean);+ for (const p of chromePaths) if (fileExists(p)) return { found: true, kind: 'chrome', executablePath: p };+ for (const p of edgePaths) if (fileExists(p)) return { found: true, kind: 'edge', executablePath: p };+ // POSIX fallbacks (pup is Windows-first, but keep dev boxes working):+ for (const p of ['/usr/bin/google-chrome', '/usr/bin/chromium', '/usr/bin/chromium-browser',+ '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',+ '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge']) {+ if (fileExists(p)) return { found: true, kind: p.toLowerCase().includes('edge') ? 'edge' : 'chrome', executablePath: p };+ }+ return { found: false };+}++// The browser pup will actually launch, in preference order:+// installed Chrome → installed Edge → cached Chrome-for-Testing → (none → fetch CfT).+// Launch uses a fresh --user-data-dir, so a system browser is driven CLEAN — it never+// touches the user's real tabs/logins (that's native-mode's job).+function resolveBrowser() {+ const sys = detectSystemBrowser();+ if (sys.found) return { found: true, kind: sys.kind, executablePath: sys.executablePath, source: 'system' };+ const cft = detectChrome();+ if (cft.installed) return { found: true, kind: 'chrome-for-testing', executablePath: cft.executablePath, source: 'cache', buildId: cft.buildId };+ return { found: false, kind: 'none', executablePath: null };+}+ // Programmatic install of the pinned CfT build. Resolves to { ok, executablePath, buildId } // or { ok:false, errorCode, error }. Deduped via _installInFlight. function installChrome({ onProgress } = {}) {@@ -212,11 +254,15 @@ async function ensureChromeReady({ background = true, onProgress } = {}) { // true (this module only runs inside the node bridge process), but we surface it // so the AI's readiness check is a single source of truth. function readiness() {+ const b = resolveBrowser(); const det = detectChrome(); const installing = !!_installInFlight || state.phase === 'installing';- const ready = det.installed && !installing;+ const ready = b.found && !installing; // a drivable browser is available (system OR cached CfT) return { ready,+ browserKind: b.kind, // 'chrome' | 'edge' | 'chrome-for-testing' | 'none'+ browserSource: b.source || null, // 'system' | 'cache' | null+ browserExecutablePath: b.executablePath || null, nodeInstalled: true, nodeVersion: process.version, chromeForTestingInstalled: det.installed,@@ -234,4 +280,4 @@ function readiness() { }; } -module.exports = { detectChrome, installChrome, ensureChromeReady, readiness, expectedBuildId, cacheDir };+module.exports = { detectChrome, detectSystemBrowser, resolveBrowser, installChrome, ensureChromeReady, readiness, expectedBuildId, cacheDir };
src/server.js+27−15@@ -1328,9 +1328,19 @@ async function getOrLaunchBrowser(profileName, { freshProfile = false, strictPer browsers.delete(profileName); } + // Prefer an ALREADY-INSTALLED browser (Chrome → Edge; every Windows box has Edge)+ // so the common case needs NO ~150 MB Chrome-for-Testing download. Falls back to a+ // cached CfT. A fresh --user-data-dir (below) keeps it CLEAN — it never touches the+ // user's real tabs/logins (that's native-mode's job).+ const resolvedBrowser = chrome.resolveBrowser();+ if (resolvedBrowser.found) {+ console.log(`[launch] browser: ${resolvedBrowser.kind} (${resolvedBrowser.source}) — ${resolvedBrowser.executablePath}`);+ }+ const launchOpts = { headless: false, defaultViewport: null,+ ...(resolvedBrowser.executablePath ? { executablePath: resolvedBrowser.executablePath } : {}), args: [ '--no-sandbox', '--disable-setuid-sandbox', '--disable-web-security', '--start-maximized', '--remote-debugging-port=0',@@ -1479,7 +1489,8 @@ async function getOrLaunchBrowser(profileName, { freshProfile = false, strictPer // ─── Helper: one detached-launch attempt, returns browser or throws ─── const tryDetachedLaunch = async (attemptNum) => { const { spawn } = require('child_process');- const chromePath = puppeteer.executablePath();+ // Launch the resolved browser (installed Chrome/Edge preferred; cached CfT fallback).+ const chromePath = resolvedBrowser.executablePath || puppeteer.executablePath(); const debugPort = 9200 + Math.floor(Math.random() * 800); const chromeArgs = [ ...launchOpts.args.filter(a => a !== '--remote-debugging-port=0'),@@ -2965,13 +2976,14 @@ const server = http.createServer(async (req, res) => { // `profile` arg. Default (no native args) is byte-for-byte the same // CfT path as v1.0.4 — strictly additive, no regressions. const wantsNative = !!(args.nativeBrowser || args.nativeProfile);- // ── Tier-1 cold-start gate (CfT path only) — READ-ONLY, never blocks ──- // pup brings its own browser (Chrome for Testing). AD v1.9.47+ backgrounds- // the Chrome-for-Testing fetch via this bridge's prewarm:["chrome-for-testing"]- // declaration, so this gate does NOT trigger a download itself (no racing- // AD-core) and NEVER blocks: if Chrome isn't cached yet it returns a pollable- // {installing:true}. The explicit bridge-side fetch fallback is browser_prewarm.- // Native drive uses the user's REAL browser (not CfT), so it skips this.+ // ── Cold-start gate (CfT path only) — READ-ONLY detect; never blocks ──+ // pup drives an ALREADY-INSTALLED browser (Chrome → Edge; EVERY Windows box+ // has Edge) with a fresh isolated profile — so readiness is almost always TRUE+ // instantly with NO download, and this gate just falls through. Only if the+ // machine has NO Chromium browser at all (no Chrome, no Edge, no cached CfT) —+ // rare — do we fall back to fetching Chrome for Testing, kicked off in the+ // BACKGROUND here (non-blocking, pollable {installing:true}). Native drive uses+ // the user's REAL browser, so it skips this. if (!wantsNative) { const rd = chrome.readiness(); // detect-only; never downloads if (!rd.ready) {@@ -2979,22 +2991,22 @@ const server = http.createServer(async (req, res) => { sendJSON(res, { success: false, errorCode: rd.lastErrorCode || 'chrome_install_failed',- error: `Chrome for Testing (the browser pup drives) failed to install: ${rd.lastError}`,- chromeForTestingInstalled: false,+ error: `No Chrome/Edge installed and the Chrome-for-Testing fallback failed to install: ${rd.lastError}`, readiness: rd,- _hint: 'A prior Chrome-for-Testing fetch failed (usually no network / a proxy blocking storage.googleapis.com, or low disk — NOT a Node problem). Retry `browser_prewarm`; if it keeps failing, surface readiness.lastError to the user. Fallback: drive the user\'s real browser via browser_open_window with nativeBrowser+nativeProfile.',+ _hint: 'This machine has no Chrome or Edge, and the Chrome-for-Testing fallback download failed (usually no network / a proxy blocking storage.googleapis.com, or low disk). Fastest fix: have the user install Chrome or Edge (Edge ships with Windows) — pup will then use it instantly with no download. Or retry browser_prewarm. Fallback: drive their real browser via browser_open_window with nativeBrowser+nativeProfile.', }); return; }+ // No Chrome/Edge AND no cached CfT — fetch CfT as a last resort, in the background.+ if (!rd.installing) chrome.ensureChromeReady({ background: true }); sendJSON(res, { success: false, errorCode: 'chrome_for_testing_installing', installing: true,- chromeForTestingInstalled: false,- statusVerb: 'bridge_readiness',+ statusVerb: 'browser_readiness', readiness: rd,- error: 'Chrome for Testing is not cached yet — Adom Desktop is fetching it in the background (Tier-1 prewarm).',- _hint: 'FIRST-TIME on this PC: AD is downloading Chrome for Testing in the background (the bridge declares prewarm:["chrome-for-testing"]); the bridge did NOT block on it. Poll AD\'s `bridge_readiness` until Chrome reports ready, THEN re-issue this exact browser_open_window. Do NOT report failure — this is expected on a fresh machine. (If it never becomes ready, call `browser_prewarm` to have the bridge fetch Chrome itself.)',+ error: 'No Chrome/Edge installed on this machine — fetching Chrome for Testing as a fallback (~150 MB, one time).',+ _hint: 'RARE: this machine has neither Chrome nor Edge, so pup is downloading Chrome for Testing as a fallback (in the background — it did NOT block). Poll `browser_readiness` until {ready:true}, THEN re-issue this browser_open_window. Faster fix: have the user install Chrome or Edge (Edge ships with Windows) and pup uses it instantly with no download. Do NOT report a hard failure — this is the no-browser edge case.', }); return; }