# Handoff to the adom-desktop build thread — pup is now cloud-owned

Paste the block below into the adom-desktop build thread (the one that owns
`github.com/adom-inc/adom-desktop`). It mirrors what was done for the kicad/fusion bridges.

---

**Subject: The Puppeteer (pup) bridge moved out of adom-desktop into its own wiki repo — please treat it like kicad/fusion now.**

We took over code management of the Puppeteer bridge. It now lives at its own canonical wiki repo,
exactly like the kicad and fusion bridges:

- **Canonical repo / page:** https://wiki.adom.inc/adom/adom-desktop-puppeteer-bridge
- **Current published version:** **1.1.0** (release asset + streaming manifest are live)
- **Manifest (updateManifestUrl target):** `https://wiki.adom.inc/api/v1/pages/adom-desktop-puppeteer-bridge/files/adom-bridge-puppeteer-manifest.json`
- **Release zip:** `https://wiki.adom.inc/download/adom/adom-desktop-puppeteer-bridge/1.1.0/adom-bridge-puppeteer-v1.1.0.zip` (sha256 `525eabb0350ea0eaf52a949e9b3026f5a4a035868afe9546a7478298b9539b8c`, source-only — node_modules is reconstructed by AD's `npm install` on spawn, as today)

**What changed in 1.1.0 (the reason for all this):** cold-start self-heal. On a fresh PC (HD bundles
AD; the installer pre-installs neither Node nor Chrome for Testing), the first "open in pup" used to
fail with a generic "Failed to launch Chrome after 3 attempts" + a dev string (npx…). Now the bridge
detects a missing Chrome for Testing, auto-installs it (programmatically via the bundled
@puppeteer/browsers), and exposes `browser_readiness` + `browser_prewarm`; `/status` reports
led/summary/tooltip truthfully (yellow while installing). The `node_not_found` path you already own is
unchanged and still correct.

**Please make these adom-desktop changes (all small, mirroring kicad/fusion):**

1. **Keep pup a pre-installed bundled SEED** (do NOT remove it from `bundle.resources`) — same as
   kicad/fusion/blender. Fresh installs must still ship a working pup offline.

2. **Update the bundled seed `plugins/puppeteer/` to v1.1.0** so a fresh install ships the cold-start
   fix even before the first wiki sync. Easiest: download + unzip the 1.1.0 release zip (URL above)
   into `plugins/puppeteer/` (it's the `src/` tree at zip-root: `server.js`, `chrome.js`,
   `bridge.json`, `BRIDGE_VERSION`=1.1.0, etc.), keep the existing bundled `node_modules`. The seed's
   `bridge.json` already declares `updateManifestUrl` (see #3).

3. **Confirm the seed `bridge.json` carries `updateManifestUrl`** = the manifest URL above, so AD
   auto-pulls our updates (cache-over-bundled, numeric version_gt) via `refresh_bridges` / the 4h poll —
   exactly like kicad/fusion. (It's already set in the 1.1.0 source.) Add a short
   `plugins/puppeteer/CANONICAL.md` breadcrumb pointing at the wiki repo, like the kicad/fusion seeds.

4. **Stop treating `plugins/puppeteer` as the source of truth.** Don't hand-edit it as if live and don't
   `release-bridge.sh puppeteer` from the adom-desktop repo anymore (it would clobber the cloud-owned
   page). pup is now in the same bucket as kicad/fusion in `BRIDGE_OWNERSHIP.md` /
   `release-publish.sh` SKILLS_SRC — please add the same guard. (Also: pup's consumer SKILL now ships
   from the bridge's own wiki page, so drop `pup-SKILL.md` from the AD-core pkg's SKILLS_SRC to avoid a
   stale copy clobbering ours — same move you made for kicad/fusion.) **ALSO remove `"puppeteer"` from
   `AD_CORE_SKILL_BRIDGES` in `bridge_registry.rs`** so `bridge_list` reports
   `skillPkg = adom/adom-desktop-puppeteer-bridge` for the puppeteer bridge — that's what makes the daily
   `sync_skills` auto-install the pup skills into every container. It's hardcoded `null` (skill-in-core)
   today, which is no longer true now that the skill ships from the pup page.

5. **THE ONE AD-CORE FIX WE NEED (we can't do this from the bridge): warm Node + Chrome for Testing on
   the HD-embedded first-run.** Today the first-launch prefetch (`AUTOSTART_VERSION` task in `lib.rs`,
   ~lines 3601–3607) is **skipped when AD is embedded** — which is exactly the HD-bundled fresh-PC case,
   so nothing pre-warms and the whole cold-start cost lands on the user's first "open in pup". Please
   either:
   - re-enable that idle-time prefetch task on the embedded path (it already calls
     `start_browser_bridge`, which now self-installs Chrome for Testing), **or**
   - after AD enters embedded mode, fire `browser_prewarm` once (fire-and-forget) to kick the Chrome
     download in the background.
   Either way, gate it behind the existing Node check (if Node is missing it's a no-op / the
   `node_not_found` path stays the recovery). Net effect: a fresh HD install quietly warms pup so the
   user's first "open in pup" is instant instead of a 2–3 min download.

**Verify after your change:** on a fresh embedded boot with Node present, `bridge_list` shows pup at
1.1.0 (cache or seed), `browser_readiness` returns `{ready:true}` without the user ever asking, and
`bridge_check_updates` reaches the new page via `updateManifestUrl`. On a box without Node,
`browser_open_window` still returns the clean `node_not_found` → `desktop_install_node` path.

Questions / bugs in pup itself (a `browser_*` verb, a hint, the Chrome self-heal) → file them against
the wiki repo, not adom-desktop. Generic AD capabilities (a new lifecycle verb, the prewarm trigger
above) → adom-desktop, but don't re-absorb the bridge.

---

---

## ALSO: the bridge-authoring docs are stale — please update them (affects EVERY new bridge author)

A new user asking "make me an adom-desktop bridge for <X>" gets pointed at: the **`adom-desktop-bridges`**
Bridge SDK guide (#1 discovery hit), the **`adom-desktop-bridge-author`** skill, and the repo's
**`skills/BRIDGE_SDK.md`** (source). The RUNTIME guidance (bridge.json, verbs, health chip, rich hints,
lifecycle) is good + current. But the PUBLISH/PACKAGING guidance is outdated — and in one place WRONG.
These are AD-core-owned (we won't touch them, same as you don't touch pup), so please fix all three:

1. **WRONG — "zip/bin under `/files`".** `BRIDGE_SDK.md` "Publish + iterate" (and the `adom-desktop-bridges`
   page) tell authors the bridge zip goes on the page's `/files`. It does NOT — `/files` strips/rejects
   binaries. The zip is a **RELEASE asset** (`adom-wiki release create` + `release upload`); the manifest's
   `url` points at the release download URL with a matching `sha256`/`size`. (This is what fusion/kicad/pup
   actually do; following the doc as written produces a 404 manifest.)

2. **MISSING — the 3-artifact model.** A bridge page ships THREE things and the docs only really explain one:
   (a) the **release zip** = the runtime AD streams into bridges-cache; (b) the **pkg tarball**
   (`adom-wiki pkg publish`) = the container-side Claude/Codex skills (how the consumer skill reaches a
   container — completely absent today); (c) the optional **bundled seed** for AD-bundled bridges.

3. **MISSING — skill packaging + dev/user separation.** (a) the pkg `files` allowlist + the `skills/**`
   glob trap (a bare `"skills"` entry ships ZERO skill files); (b) keep the tarball LEAN — NO zip, NO
   `src/`, NO hero PNGs (`tar tzf` to confirm; it was 16 MB before guards); (c) **the wiki indexes EVERY
   `SKILL.md` in the repo** as an installable, Skills-tab-listed skill (`skill_count`) — so DEV/PUBLISH
   skills must be **source-only**, named NOT `SKILL.md` (e.g. `dev-skills/*.md`), or every user installs them.

4. **MISSING — `.codex/skills`.** `install.sh` should deploy the user skill to BOTH `~/.claude/skills` and
   `~/.codex/skills` (Codex is a real second agent skills home now).

5. **MISSING — discovery triggers.** Lead `discovery_triggers` with everyday USER phrases (not dev jargon)
   so "open my app in <X>" surfaces the page; the discover snippet is auto-generated from
   `discovery_triggers` + `discovery_pitch` (no separate upload step).

6. **STALE URL — the retired wiki host.** Both the `adom-desktop-bridges` page AND the
   `adom-desktop-bridge-author` skill still cite **`wiki-ufypy5dpx93o.adom.cloud`** (retired). The only host
   is `wiki.adom.inc`. Grep + scrub (incl. the `page.json` `readme` body).

**Easiest path:** lift the content from the pup bridge's now-canonical publish playbook —
`wiki.adom.inc/adom/adom-desktop-puppeteer-bridge` → `dev-skills/pup-bridge-publish.md` (it carries all six
points above, battle-tested) — and generalize the pup-specifics into the SDK guide.

---

## ⚠ CRITICAL (found LIVE 2026-06-29): a wiki-streamed pup CACHE copy CRASHES — no node_modules

When AD installs pup from the wiki — `bridge_install {manifestUrl}`, or `refresh_bridges` / the 4h poll
via `updateManifestUrl` — it streams the **source-only** zip into `bridges-cache\puppeteer\` and spawns
`node server.js`, which **immediately crashes**: `MODULE_NOT_FOUND` on `require('puppeteer')` (server.js:10).
The cache copy has **no `node_modules`**, and AD's cache-spawn path does NOT `npm install` it or reuse the
bundled `node_modules`. Because **cache-over-bundled wins, this replaced a working bundled v1.0.16 with a
broken v1.1.0** (verified live on AdomLapper; reverted with `bridge_uninstall`). This is the Node-bridge
analogue of why kicad/fusion (Python, no node_modules) stream fine but pup can't.

**Fix — please do ONE:**

- **(A) PREFERRED — give a cache-installed `spawn.kind:"node"` bridge its node_modules.** On spawn from
  the cache, AD should either (i) `npm install`/`npm ci` in the cache dir first (the bespoke
  `start_browser_bridge` ALREADY does this for the bundled pup — extend it to / route the cache copy
  through it), or (ii) reuse/symlink the bundled `node_modules` into the cache dir. Then wiki-streaming +
  `updateManifestUrl` auto-update works for pup exactly like kicad/fusion.
- **(B) OR keep pup runtime "bundled-only"** (the original model): the bundled-seed bump (handoff #2,
  which KEEPS node_modules) is the runtime delivery; wiki-streaming the runtime stays off and
  `updateManifestUrl` must NOT pull a newer runtime into the cache.

**Until (A) ships: do NOT publish a wiki pup RUNTIME version newer than the AD bundled seed** — any box
with our `updateManifestUrl` persisted would pull the newer source-only zip into the cache and crash. The
**skills pkg is unaffected** (separate path, works). Net for now: ship the pup RUNTIME via the bundled
seed (#2); treat wiki-streaming as blocked on fix (A).

## ⭐ NEW TOP PRIORITY (found LIVE 2026-07-01 on 3 fresh Windows boxes): Node install = UAC + stale PATH dead-end

Tested cold-start on AdomLapper + two genuinely fresh boxes (AdomTestWin11 vbox, winvm Azure — both
Edge-only, no Node). The **browser** side is now solved by the bridge (v1.5.0: installed-browser-first,
spawn-verified fallback, self-warm, disk guards). The remaining cold-start blockers are **both in AD-core
`desktop_install_node`** and they hard-stop the exact "AI can't recover" scenario:

1. **UAC prompt.** `desktop_install_node` installs Node via the machine-wide MSI/winget, which is
   manifested `requireAdministrator` → a **UAC dialog pops on the user's desktop** ("Node.js — OpenJS
   Foundation, allow changes?"). A cloud AI cannot click it; the user must, and John explicitly does not
   want a UAC here.
2. **Stale PATH after a *successful* install.** Even once Node installs, the already-running AD process
   keeps its OLD environment, so the very next `browser_*` STILL returns `node_not_found` /
   `could not run npm for cache bridge`. Reinstalling doesn't help (winget then says "No available upgrade"
   = already installed). The bridge can never spawn until AD is relaunched.

**Both fixed by one change — install Node the portable, user-scoped way:**
- Download the **Node ZIP** (`node-vXX-win-x64.zip`), extract `node.exe` into a user-writable dir (e.g.
  `%LOCALAPPDATA%\Adom Desktop\node\`). **No installer, no UAC.**
- Spawn bridges with that `node.exe` **by absolute path** (AD already knows where it put it) — don't rely
  on `PATH` at all. This also permanently kills the stale-PATH class of bug.
- Even better: **bundle `node.exe` in the AD installer** (~50 MB) so a fresh AD already has Node — zero
  download, zero UAC, zero PATH dependency, instant first `browser_open_window`.
- If you keep a PATH-based flow, at minimum **probe common Node locations** (`%ProgramFiles%\nodejs`,
  `%LOCALAPPDATA%\...`, nvm dirs) before declaring `node_not_found`, and refresh PATH from the registry
  after install.

**Bridge-side hint already added (SKILL.md):** if `node_not_found` persists after a successful install, the
AI is told AD's PATH is stale → relaunch AD (`desktop_relaunch_self`) rather than looping the installer.

## RESOLVED: the wiki-streamed cache crash (node_modules) is fixed in AD ≥ 1.9.55

The earlier CRITICAL section (wiki-streamed pup CACHE copy crashes with MODULE_NOT_FOUND) is **no longer
reproducing** — on AD 1.9.55–1.9.62 the wiki-cache copy spawns cleanly (AD now reconstructs node_modules
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.
