Adom Desktop - Puppeteer Bridge
Public Made by Adomby adom
The Adom Desktop Puppeteer (pup) bridge — drive a real headful browser on the user's desktop from the cloud: open/screenshot/eval/record. Cold-start drives the browser ALREADY on the machine (installed Chrome, else Microsoft Edge — every Windows PC ships with Edge) with a fresh isolated profile, so the common case needs NO download; Chrome for Testing is fetched only as a last resort. Two artifacts: the Releases zip is the bridge runtime (also bundled in Adom Desktop); the pkg installs the container-side Claude skills.
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:
- UAC.
desktop_install_nodeuses Node's machine-wide MSI, manifestedrequireAdministrator→ a Windows UAC dialog. A cloud AI can't click it; if the user denies it, cold-start dead-ends. - Stale PATH. Even after a successful install, AD's already-running process keeps its old
PATH, so the nextbrowser_*STILL returnsnode_not_found/could not run npm. Reinstalling doesn't help; AD must be relaunched. - 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). - 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, Pythonembeddable/standalone build) extracted into a user-writable dir. No installer, no admin, no UAC. - Content-addressed by exact version, so two bridges needing
node >=20share onenode-22.11.0copy; a bridge that pinsnode-18gets 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)
"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:
- Reads
runtimefrom the manifest. - If the shared cache has an interpreter satisfying the range → use it.
- Else fetch the pinned/preferred version (portable zip →
adom-runtimes\…, no UAC) → use it. - Spawn with the interpreter's absolute path, e.g.
…\adom-runtimes\node-22.11.0\node.exe server.js— never rely on the machinePATH.
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 withpython -m venvat spawn — shared interpreter, isolated packages. Mirrors how node bridges already get per-bridgenode_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
adom-runtimes\content-addressed cache + a portable-archive fetcher (Node zip today; Python next).- Read
bridge.json.runtime(fall back todependencies.node/dependencies.python). - Resolver: satisfy-from-cache-else-fetch, keyed by version range.
- Spawn bridges by absolute interpreter path (drop the bare-
nodePATH lookup). - Optional but ideal: bundle one Node (and Python) in the AD installer for a zero-download fresh box.
- Deprecate the UAC MSI path in
desktop_install_nodein 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
runtimeinbridge.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
runtimeis additive; older AD ignores it and keeps today's behavior.- Until AD ships this, pup's SKILL carries a PROVEN zero-prompt interim recipe (typed verbs: send_files + launch_process/tar + registry_set; only an AD reopen remains). desktop_install_node (UAC) / shell_execute (approval) are fallbacks.
- 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.
# 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 carries a PROVEN zero-prompt interim recipe (typed verbs: send_files +
launch_process/tar + registry_set; only an AD reopen remains). desktop_install_node (UAC) / shell_execute
(approval) are fallbacks.
- 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.