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.6.1: declare runtime block in bridge.json + add PROPOSAL-shared-runtimes.md (AD-managed shared portable Node/Python, content-addressed cache, spawn by absolute path) + handoff pointer
6 files changed
+166−10
HANDOFF-TO-ADOM-DESKTOP.md+18@@ -181,3 +181,21 @@ reproducing** — on AD 1.9.55–1.9.62 the wiki-cache copy spawns cleanly (AD n on the cache bridge). All 3 test boxes ran the cache copy of pup 1.3.0→1.5.0 with `source:"cache"`. So the "ship runtime only via the bundled seed" constraint is lifted: the `updateManifestUrl` auto-pull works. (Still bump the bundled seed opportunistically so a truly offline first-run is current.)++## ⭐ DESIGN PROPOSAL: AD-managed shared runtimes (Node + Python) — see PROPOSAL-shared-runtimes.md++The Node UAC/PATH pain above is a symptom of a bigger gap: **no one owns bridge runtimes.** Full write-up+in `PROPOSAL-shared-runtimes.md` (this repo). TL;DR of the recommendation (a hybrid, not either extreme):++- AD keeps a shared, **content-addressed** portable-runtime cache (`…\adom-runtimes\node-22.11.0\`,+ `python-3.12.7\`), populated from portable zips into a **user dir — no installer, no UAC**. Ideally AD+ **bundles** one Node (+ Python) so a fresh box has it with zero download.+- Bridges **DECLARE** their runtime in `bridge.json` (`runtime: {kind, version, preferred}`) — they do NOT+ install it. pup ships this declaration as of v1.6.1. Shared interpreter when compatible; a pinned version+ fetched only when a bridge needs an odd one (no duplication for the common case).+- **AD spawns bridges by the interpreter's ABSOLUTE PATH** (`…\adom-runtimes\node-22.11.0\node.exe+ server.js`). This is the linchpin — it kills BOTH the UAC (portable/user-dir) AND the stale-PATH dead-end+ (no reliance on the machine PATH) at once. Python bridges get a per-bridge venv on the shared interpreter.++This replaces the per-bridge "roll your own Node" pattern (today's status quo, and the thing that caused+this whole debugging session). `desktop_install_node`'s UAC MSI becomes a last-resort fallback.
PROPOSAL-shared-runtimes.mdadded+132@@ -0,0 +1,132 @@+# Proposal: AD-managed shared runtimes (portable Node + Python) for all bridges++**Status:** proposal to the adom-desktop (AD) build thread. Authored from the pup bridge after a full+cold-start debugging pass on three fresh Windows boxes (AdomLapper, AdomTestWin11 vbox, winvm Azure).+**Owner of the fix:** AD core (bridge runtime provisioning + spawn). Bridges only *declare* their need.++---++## The problem we hit (concrete)++pup is a Node process AD spawns as `node server.js`. On a fresh box that costs a cascade of prompts and+dead-ends, none of which are pup's to fix:++1. **UAC.** `desktop_install_node` uses Node's machine-wide **MSI**, manifested `requireAdministrator` →+ a Windows UAC dialog. A cloud AI can't click it; if the user denies it, cold-start dead-ends.+2. **Stale PATH.** Even after a *successful* install, AD's already-running process keeps its old `PATH`,+ so the next `browser_*` STILL returns `node_not_found` / `could not run npm`. Reinstalling doesn't help;+ AD must be relaunched.+3. **Firewall.** A hostless `listen()` binds all interfaces → "allow Node through the firewall?" (pup+ fixed its own side in v1.6.0 by binding loopback, but the *runtime* provisioning is still AD's).+4. **Duplication risk.** If every node bridge solves this itself, you multiply UAC + downloads + PATH pain+ by the number of bridges, and ship N copies of Node.++We proved a **no-UAC** path by hand on winvm: download the portable Node **zip** into `%LOCALAPPDATA%`,+add it to the **user** PATH (HKCU, no admin), reopen AD → pup drove Edge and opened amazon.com. Zero UAC.+But that's a per-bridge hack in pup's skill. It should be a platform capability.++## Goal++One managed runtime story so that, on any fresh box, **any** node/python bridge spawns with **no UAC, no+firewall prompt, no PATH dependency, and no per-bridge reinvention** — while still honoring a bridge that+needs a specific runtime version.++## Non-goals++- Not proposing AD sandbox/containerize bridges. Just: provide the interpreter + spawn correctly.+- Not removing per-bridge dependency isolation (node_modules / venv stay per-bridge).++---++## Design++### 1. A shared, content-addressed runtime cache owned by AD++```+%LOCALAPPDATA%\Adom Desktop\adom-runtimes\+ node-22.11.0\node.exe ...+ node-18.20.4\node.exe ... # only if some bridge pins it+ python-3.12.7\python.exe ...+```++- Populated by **portable archives** (Node `.zip`, Python `embeddable`/standalone build) extracted into a+ **user-writable** dir. **No installer, no admin, no UAC.**+- Content-addressed by exact version, so two bridges needing `node >=20` **share one** `node-22.11.0`+ copy; a bridge that pins `node-18` gets its own without disturbing the rest. No duplication for the+ common case; isolation when actually needed.+- Best case: AD **bundles** one current Node (and optionally Python) in its installer, so a fresh box has+ the default runtime with **zero download**.++### 2. Bridges DECLARE their runtime in `bridge.json` (they don't install it)++```jsonc+"runtime": {+ "kind": "node", // "node" | "python"+ "version": ">=20", // semver range the bridge supports+ "preferred": "22" // optional: pick this major when provisioning fresh+}+```++Declaring ≠ installing. This is the whole point: **centralize the mechanism, decentralize the version+declaration.** (pup already carries `dependencies.node = ">=20"`; the dedicated `runtime` block makes it+unambiguous for AD's resolver. pup ships it as of the version alongside this proposal.)++### 3. AD resolves + spawns by ABSOLUTE PATH++On bridge spawn, AD:+1. Reads `runtime` from the manifest.+2. If the shared cache has an interpreter satisfying the range → use it.+3. Else fetch the pinned/preferred version (portable zip → `adom-runtimes\…`, no UAC) → use it.+4. **Spawn with the interpreter's absolute path**, e.g.+ `…\adom-runtimes\node-22.11.0\node.exe server.js` — **never** rely on the machine `PATH`.++Spawning by absolute path is the linchpin: it eliminates **both** the UAC (portable, user-dir) **and** the+stale-PATH dead-end (AD already knows exactly where the interpreter is), in one move.++### 4. Python specifics++- Same shared interpreter cache (`python-3.12.7\`).+- Python deps are messier than Node's `node_modules`: give each python bridge its **own venv** layered on+ the shared interpreter (`bridges-cache\<bridge>\.venv`), created with `python -m venv` at spawn — shared+ interpreter, isolated packages. Mirrors how node bridges already get per-bridge `node_modules`.+- ABI sensitivity (C extensions) is why the *version declaration* matters more for Python — but the shared+ cache + per-version resolution already handles it.++---++## Why not the alternatives++- **Each bridge installs its own runtime (status quo).** Multiplies UAC/download/PATH pain per bridge and+ ships N copies of Node. This is exactly the pain that triggered this proposal.+- **One hard-pinned global runtime, no per-version support.** Breaks any bridge that needs a different+ major, and one bad shared update breaks everything at once. The content-addressed cache avoids both:+ shared when compatible, isolated when pinned.++Industry precedent for the hybrid: VS Code (bundles Node for the extension host + `engines` constraints),+nvm/uv (shared cache + `.nvmrc`/pin), Python venvs (shared interpreter + per-project envs).++---++## What AD needs to build++1. `adom-runtimes\` content-addressed cache + a portable-archive fetcher (Node zip today; Python next).+2. Read `bridge.json.runtime` (fall back to `dependencies.node` / `dependencies.python`).+3. Resolver: satisfy-from-cache-else-fetch, keyed by version range.+4. **Spawn bridges by absolute interpreter path** (drop the bare-`node` PATH lookup).+5. Optional but ideal: **bundle** one Node (and Python) in the AD installer for a zero-download fresh box.+6. Deprecate the UAC MSI path in `desktop_install_node` in favor of the portable install (or keep it as a+ last-resort fallback with the user warned — see the pup SKILL cold-start playbook).++## What bridges do++- Declare `runtime` in `bridge.json` (pup already does).+- Keep their own dependency layer (`node_modules` / `.venv`) — unchanged.+- Stop shipping/instructing their own interpreter bootstrap once AD provides this.++## Migration / back-compat++- `runtime` is additive; older AD ignores it and keeps today's behavior.+- Until AD ships this, pup's SKILL keeps the no-UAC portable-Node recipe + the UAC/PATH warnings as the+ interim path (documented, user-warned).+- Once AD spawns by absolute path from the shared cache, the interim recipe and the UAC/firewall/PATH+ warnings become dead code we can delete from the skill.
adom-bridge-puppeteer-manifest.json+4−4@@ -1,10 +1,10 @@ { "manifest_version": 1, "name": "puppeteer",- "version": "1.6.0",- "url": "https://wiki.adom.inc/download/adom/adom-desktop-puppeteer-bridge/1.6.0/adom-bridge-puppeteer-v1.6.0.zip",- "sha256": "8b908d9857f1e2dd82a7c3499f73eabcf3a97bed4296919ee02416ecc6ad062d",- "size": 138765,+ "version": "1.6.1",+ "url": "https://wiki.adom.inc/download/adom/adom-desktop-puppeteer-bridge/1.6.1/adom-bridge-puppeteer-v1.6.1.zip",+ "sha256": "908f7f647ba7c7e05ae0ad796539faba1cee17cc216fed59cd3592171c1e44bf",+ "size": 138965, "verbPrefixes": [ "browser_", "desktop_recorder_",
package.json+1−1@@ -1,6 +1,6 @@ { "slug": "adom-desktop-puppeteer-bridge",- "version": "1.6.0",+ "version": "1.6.1", "type": "app", "description": "CONTAINER-SIDE install \u2014 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) \u2014 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 \u2014 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": [
src/BRIDGE_VERSION+1−1@@ -1 +1 @@-1.6.0+1.6.1
src/bridge.json+10−4@@ -2,8 +2,8 @@ "manifest_version": 1, "name": "puppeteer", "displayName": "Puppeteer (Chrome for Testing + Native)",- "version": "1.6.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. 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 → Edge → 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, …). 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.",+ "version": "1.6.1",+ "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. 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) \u2014 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",@@ -54,6 +54,12 @@ "node": ">=20", "chrome": ">=120" },+ "runtime": {+ "kind": "node",+ "version": ">=20",+ "preferred": "22",+ "_note": "AD-managed shared-runtime declaration (see PROPOSAL-shared-runtimes.md). Bridges DECLARE their runtime; AD provisions a portable interpreter from its shared adom-runtimes cache and spawns by absolute path \u2014 no UAC, no PATH reliance. Additive: older AD ignores this and uses `dependencies` / bare `node` on PATH."+ }, "category": "Browser Automation", "tags": [ "puppeteer",@@ -68,11 +74,11 @@ }, "macos": { "supported": false,- "reason": "macOS port pending — puppeteer-core is cross-platform but the bridge's spawn config uses Windows-only killImageName 'node.exe' and Chrome for Testing path resolution hasn't been tested."+ "reason": "macOS port pending \u2014 puppeteer-core is cross-platform but the bridge's spawn config uses Windows-only killImageName 'node.exe' and Chrome for Testing path resolution hasn't been tested." }, "linux": { "supported": false,- "reason": "Linux port pending — same killImageName + Chrome resolution issues as macOS."+ "reason": "Linux port pending \u2014 same killImageName + Chrome resolution issues as macOS." } } }