← Commit history

Delete dev-skills/pup-bridge-dev.md

John Lauer ·3231463c39 ·4mo ago ·parent 622c7d6
1 file changed −204
dev-skills/pup-bridge-dev.mddeleted−204
@@ -1,204 +0,0 @@-----name: pup-bridge-dev-user-invocable: false-description: "DEVELOPER skill for building, publishing, and maintaining the Adom Desktop Puppeteer (pup) bridge. NOT needed by general users — they want the `pup` skill. Read this when editing the bridge code, cutting a new version, publishing to the wiki, wiring the cold-start Chrome-for-Testing self-heal, or understanding the pkg-vs-release-vs-bundled artifact model. Trigger words: pup bridge dev, publish pup bridge, build puppeteer bridge, bridge_install pup, adom-desktop-puppeteer-bridge, cold-start self-heal, browser_readiness internals, pup release, pup manifest, ship pup bridge."------# pup-bridge-dev — building & shipping the Puppeteer bridge--The canonical source of truth for the pup bridge is **this wiki repo**-(`wiki.adom.inc/adom/adom-desktop-puppeteer-bridge`), extracted from `adom-desktop/plugins/puppeteer`-on 2026-06-29 to be cloud-owned like the kicad/fusion bridges. Read `CLAUDE.md` (ownership boundary)-and `PUBLISHING.md` (step-by-step recipe) in this repo alongside this skill.--## The THREE artifacts — know which is which (this is the #1 confusion)--A user installs TWO different things from this one page. They are not the same:--| Artifact | What it is | Who installs it | How |-|---|---|---|---|-| **pkg tarball** (Packages tab) | the **container-side Claude skills** (`SKILL.md` + `skills/*`) — docs that teach the cloud AI how to drive pup. **No bridge runtime.** | a cloud **container** | `adom-wiki pkg install adom/adom-desktop-puppeteer-bridge`, or auto via AD's `sync_skills` |-| **release zip** (Releases tab) | the **bridge RUNTIME** (`server.js`, `chrome.js`, `bridge.json`, node deps reconstructed on spawn) — the code that actually drives Chrome on the desktop | **Adom Desktop**, streamed into `bridges-cache\puppeteer` | `bridge_install {manifestUrl}` / `refresh_bridges` (via `updateManifestUrl`) |-| **bundled seed** (inside the AD installer) | a copy of the same runtime, shipped in the NSIS for first-run/offline | every AD install | ships with Adom Desktop; superseded by a newer cache copy (cache-over-bundled, numeric version) |--So: **the pkg is skills (container). The bridge is the zip (desktop) + the bundled seed.** Keep the-pkg description saying exactly that, or people think the tarball contains the bridge — it does not.--## How pup reaches a container — auto-discovery → auto-install → verbs--The full chain when a user on a fresh container says *"open my app in pup"*:--1. **Discovery.** The wiki's `discover` index (fed by this page's `discovery_triggers` — which now lead-   with `pup`, `open in pup`, `open my app in pup`, screenshot/record/tabs) matches the phrase. The-   container's regenerated `adom-wiki-discover` skill surfaces this page, so the AI proposes it.-2. **Auto-install the pkg.** The AI runs `adom-wiki skills install adom/adom-desktop-puppeteer-bridge`-   (a.k.a. `pkg install`). That extracts the tarball and runs `install.sh`, which drops `SKILL.md` +-   `skills/*` into **both `~/.claude/skills/` and `~/.codex/skills/`**. The `pup` skill is now loaded.-3. **The skill calls the bridge.** `pup`'s SKILL.md tells the AI to run `adom-desktop browser_*` verbs.-   AD routes `browser_*` to the puppeteer bridge.-4. **The bridge runtime.** It's already on the desktop: AD ships pup as a **bundled seed** (and supersedes-   it from this page's release zip via `updateManifestUrl` / `bridge_install`). On a truly fresh PC the-   cold-start self-heal (chrome.js) installs Chrome for Testing on first `browser_open_window`.--**Two install paths, keep BOTH healthy:**-- **Discovery path (above)** — works once `discovery_triggers` include the user phrases (done) + the-  discover index is regenerated. This is the "user said pup → AI installs the skill" path.-- **AD `sync_skills` path** — AD's daily `maybe_background_skill_sync` reads `bridge_list` → each bridge's-  `skillPkg` → `pkg install`s it into the container. For pup this requires AD to report-  `skillPkg = adom/adom-desktop-puppeteer-bridge` for the `puppeteer` bridge. **Today AD hardcodes it to-  `null`** (`AD_CORE_SKILL_BRIDGES = ["puppeteer"]` in `bridge_registry.rs`, from when pup's skill lived-  in the AD-core pkg). That must be removed now that the skill ships from this page — it's in the AD handoff.--## Repo layout--- `src/` — the runnable bridge (what the release zip contains, at zip-root). `server.js`, `chrome.js`,-  `credential_vault.js`, `bridge.json`, `BRIDGE_VERSION`, node `package.json`/`package-lock.json`, html/icons.-- `SKILL.md` (root) — the **user** skill (`name: pup`). Deployed to `~/.claude/skills/adom-desktop-puppeteer-bridge/`.-- `dev-skills/pup-bridge-dev/` — THIS dev skill. `dev-skills/pup-bridge-publish/` — the publish recipe.-  Both are **source-only** (in `dev-skills/`, NOT `skills/`): NOT in the installable pkg and NOT on the-  wiki Skills tab — you get them via `adom-wiki repo clone`. Everyday users only get the `pup` user skill.-- `adom-bridge-puppeteer-manifest.json` (root) — the streaming manifest AD's `updateManifestUrl` points at.-- `package.json` (root) — the **adompkg descriptor** (slug/version/type/description) for `pkg publish`. NOT the node one.-- `install.sh` / `uninstall.sh` — deploy `SKILL.md` + `skills/*` into the container's `~/.claude/skills/`.-- `page.json`, `README.md`, `*.png` heroes — the wiki page presentation (the heroes are gitignored so the-  container pkg stays lean — they're already uploaded as page assets).--## Cold-start self-heal — the architecture (what 1.1.0 added, the reason this repo exists)--On a fresh PC (HD bundles AD; the installer pre-installs neither Node nor Chrome for Testing), the first-`browser_open_window` used to fail with a generic "Failed to launch Chrome after 3 attempts" + a dev-string. Now:--- **`src/chrome.js`** — `detectChrome()` (validates the ACTUAL exe, not "a dir exists"), `installChrome()`-  (programmatic via the bundled `@puppeteer/browsers`, pinned to the buildId puppeteer expects — never-  'stable', or launch() won't find it), an install-state singleton (dedup + progress), `ensureChromeReady()`-  (the gate — background install, returns immediately), `readiness()` (powers the verb + chip).-- **`browser_open_window` gate** (CfT path only; native-drive skips it): missing CfT → kick off a-  non-blocking install + return `{errorCode:"chrome_for_testing_installing", statusVerb:"browser_readiness",-  _hint:"poll then retry"}`. Install fail → `chrome_install_failed`/`chrome_download_failed` (no silent-  continue-to-doomed-launch).-- **`browser_readiness`** (poll) + **`browser_prewarm`** (install without opening a window).-- **`/status`** emits `led`/`summary`/`tooltip` truthfully (yellow installing, red failed, green ready).-- **Node** is handled by AD core BEFORE the bridge spawns (`node_not_found` → `desktop_install_node`) —-  not this bridge's job (the bridge is a node process; it can't run without node).-- **The one thing this bridge can't do:** pre-warm before first use (it isn't running until the first-  call). AD/HD must call `browser_prewarm` on embedded first-run — tracked in `HANDOFF-TO-ADOM-DESKTOP.md`.--## Field-tested cold-start learnings (v1.3–1.5, from 3 real fresh Windows boxes)--Tested on AdomLapper (all browsers), AdomTestWin11 (fresh vbox, Edge-only), winvm (fresh Azure, Edge-only).-What we learned and baked into the code:--- **Prefer the installed browser, don't download CfT.** Every Windows PC ships with Edge; most have-  Chrome. `resolveBrowser()`/`launchCandidates()` return [pinned default → Chrome → Edge → cached CfT];-  the launch loop **spawn-verifies** each and falls through on failure. A fresh isolated `--user-data-dir`-  keeps the system browser CLEAN (never touches the user's real tabs). CfT is now a genuine last resort.-- **Edge "exits immediately" was a false negative.** Edge (and some Chrome channels) spawn a LAUNCHER that-  relaunches the real browser onto a DIFFERENT PID, then the launcher exits. The old poll treated-  launcher-death as failure → killed good Edge launches. Fix: probe CDP FIRST each tick, and on-  launcher-death do a final CDP grace probe before giving up (`tryDetachedLaunch`).-- **A failed candidate poisons the shared profile dir.** An aborted Edge leaves `SingletonLock` /-  `DevToolsActivePort` in the userDataDir → the next candidate (Chrome/CfT) then also "exits immediately."-  Fix: `killChromeHoldingProfile()` (lock cleanup) BETWEEN candidates, not just between attempts.-- **Persist the chosen browser** (`pup-browser-default.json` in the CfT cache dir). `markVerified()` caches-  the browser that actually launched (auto default); `browser_use` pins one (forced default). Avoids-  re-probing every open. Forced beats auto; launch still falls back so a broken pin never hard-fails.-- **Node install has a PATH-staleness trap (AD-core, but guide the AI).** `desktop_install_node` installs-  Node, but the already-running AD process keeps its OLD PATH — so the very next `browser_*` STILL returns-  `node_not_found` / `could not run npm`. Reinstalling doesn't help. AD must be relaunched-  (`desktop_relaunch_self`) to pick up the new PATH. This is the exact "installed node, still broken"-  dead-end. Documented in SKILL.md + HANDOFF (AD should probe common node install dirs / refresh PATH, not-  rely on the stale env). winget also returns non-zero when Node is ALREADY present ("No available upgrade")-  — that's success, not failure.-- **Self-warm on spawn** (`chrome.warmup()` from `server.listen`): detect browsers + disk the moment AD-  installs/updates the bridge, so the first open is instant. Only background-fetches CfT if the box has NO-  Chrome/Edge AND no cached CfT (never on an Edge-only box — Edge is right there).-- **Disk + specific error codes.** `diskFreeMb()` gates the CfT install and the cold-start; failures map to-  `chrome_install_no_disk` / `chrome_download_network` / `chrome_install_permission` (not a generic string)-  so the AI's hint is actionable. `readiness()` surfaces `diskFreeMb`/`lowDisk`.-- **Install REAL Chrome on request.** `browser_use {browser:"chrome", install:true}` downloads Google's-  official offline installer and runs it silently; run as the (non-admin) user it installs USER-SCOPED into-  `%LOCALAPPDATA%\Google\Chrome` (no UAC), which `listSystemBrowsers()` already finds. Elevated context may-  install machine-wide + raise UAC — surfaced via `chrome_stable_not_detected` hint.-- **Verbose hints everywhere.** Every open returns `browser` (what we drove) + `availableBrowsers` +-  `defaultBrowser` + an `_hint` explaining how to switch/install. Goal: the AI resolves any browser-  situation in as few turns as possible without a human.-- **Desktop permission prompts gate open-ended verbs (learned live).** Core AD verbs + this bridge's-  `browser_*` verbs run with NO prompt. But `shell_execute` / `run_script` / arbitrary scripts pop an AD-  "Shell command needs approval" dialog (Approve 1h/24h/Bypass) — and the call **hangs until approved**-  (we saw `shell_execute` time out on the vbox with the popup waiting; winvm had it auto-approved and-  showed nothing). Two consequences baked into SKILL.md: (1) prefer core/`browser_*` verbs over shell;-  (2) before any `shell_execute`, `notify_user` the person to approve the popup. This is a separate gate-  from the Windows UAC (admin elevation) — don't conflate them.-- **Windows Firewall prompt = bind loopback (fixed v1.6.0).** A hostless `server.listen(PORT)` binds ALL-  interfaces (0.0.0.0), so Windows pops "Allow Node.js through the firewall (public/private)?" on first-  listen — a THIRD scary prompt after UAC + shell-approval, and worse on a fresh portable-node path the-  firewall has never seen. Fix: `server.listen(PORT, '127.0.0.1', ...)`. AD only ever talks to the bridge-  over loopback, and loopback sockets are firewall-exempt → the prompt never appears.-- **Zero-prompt Node bootstrap IS possible with typed verbs (PROVEN on winvm).** pup is a Node process, so-  it can't self-install Node — but the AI can, using AD's UNGATED typed verbs (only `shell_execute` /-  `run_script` / `wsl_exec*` prompt; everything typed does not). Proven flow, zero popups: `send_files` the-  portable Node zip → `launch_process` `C:\Windows\System32\tar.exe -xf` to extract → `registry_get` +-  `registry_set` HKCU\Environment\Path (append, REG_SZ) → user reopens AD (app restart, no prompt) so it-  re-reads PATH. NO UAC, NO shell-approval, NO firewall. The recipe lives in SKILL.md. The only human step-  is the AD reopen, which the shared-runtime proposal (absolute-path spawn) removes. `desktop_install_node`-  (UAC MSI) + `shell_execute` (approval popup) are fallbacks only.--## Cold-start bugs found on fresh boxes (AD-owned — report, don't try to fix in pup)--- **AD npm-install can't find `node` for lifecycle scripts (fixed ~AD 1.9.69).** On AD 1.9.63–1.9.65 the-  managed runtime ran `npm install` by absolute path but didn't put node on the child-process PATH, so any-  dep with a native/postinstall script that shells to `node` (keytar, sharp, **puppeteer**'s `install.mjs`)-  died: `'node' is not recognized`. Pup-side mitigation shipped v1.7.1: `sharp`+`keytar` are-  `optionalDependencies` + `sharp` loads gracefully (screenshots fall back to capture-size via a PNG-header-  dimension read). AD fixed the PATH on ~1.9.69 (npm install now succeeds on a fresh Edge-only box).-- **AD node-gate false-negative (open as of AD 1.9.69).** On a fresh box, `runtimes` shows-  `node.state:ready` (adom-managed), the bridge is `status:running` with a live pid, and `browser_readiness`-  (the declared **statusVerb**) works — but EVERY other `browser_*` verb returns AD-core `node_not_found`.-  The node pre-gate only exempts the statusVerb and does a stale/PATH-based node check that misses the-  portable managed runtime. pup CANNOT fix this (node_not_found fires in AD before the bridge). Reported on-  discussion #59; SKILL tells the AI to recognize it (readiness OK + runtimes ready + other verbs-  node_not_found = AD bug, don't loop desktop_install_node).-- **Takeaway:** when a fresh-box open fails, first read `runtimes` + `bridge_list` (pid/status) + try-  `browser_readiness`. If the runtime is ready and the bridge is running but verbs fail, it's an AD gate/-  provisioning bug — report it with that evidence; it is not a pup code bug.--## Publishing (the wiki has TWO storage layers — don't mix them)--Full recipe in `PUBLISHING.md`. The load-bearing rules:--1. **The bridge `.zip` → a RELEASE asset** (`adom-wiki release create` + `release upload`). NEVER-   `repo push` a `.zip` — it's gitignored and silently skipped; the manifest URL would 404.-2. **The manifest JSON + source → the git repo** (`adom-wiki repo push`). The manifest's `url` is the-   RELEASE download URL; its `sha256`/`size` MUST match the served asset (releases preserve your sha;-   re-download to confirm or `bridge_install` rejects it).-3. **The skills → a PKG** (`adom-wiki pkg publish`). The pkg description (root `package.json`) must say-   "container-side skills." `pkg pack` honors `.gitignore` for exclusion (no separate npmignore) — that's-   why the heroes are gitignored, to keep the tarball lean. Set per-version notes with `adom-wiki pkg notes`.-4. **Version display gotcha:** a RELEASE shows up in `adom-wiki pkg info` as a row with `type:null` (a-   wiki UI conflation). The real pkg rows have `type:"app"`. Use `release list` for true releases.-5. **`bridge.json` + `BRIDGE_VERSION` lockstep**, and keep the version **>** the AD bundled seed so-   cache-over-bundled wins.--## Testing a new version on a real desktop--```bash-adom-desktop targets    # multiple desktops → --target <name> is required-adom-desktop --target <name> bridge_install '{"manifestUrl":"https://wiki.adom.inc/api/v1/pages/adom-desktop-puppeteer-bridge/files/adom-bridge-puppeteer-manifest.json","force":true}'-adom-desktop --target <name> browser_readiness '{}'   # proves the new code is live-```--- **No Node on the box** → `browser_*` returns `node_not_found` before the bridge spawns (AD core; correct).-  That's a genuine cold-start demo of the FIRST link; it can't exercise `chrome.js` (no node to run it).-- **A box with Node but no Chrome for Testing** is the only place to see the full Chrome self-install live-  (`browser_open_window` → `chrome_for_testing_installing` → poll `browser_readiness` → opens). Most dev-  boxes already have CfT, so `readiness()` just returns `ready:true`.-- The streamed zip is **source-only**; AD runs `npm install` in the cache on first spawn (sharp/keytar-  ship prebuilds). The bundled seed already has node_modules. Don't put node_modules in the zip.--## Don't--- Don't edit `adom-desktop/plugins/puppeteer` as if it's live, and don't `release-bridge.sh puppeteer`-  from the adom-desktop repo — that clobbers this cloud-owned page.-- Don't ship the bridge runtime in the pkg tarball or the skills in the release zip. Two artifacts, two homes.-- Don't install 'stable' Chrome — pin to puppeteer's expected buildId (`chrome.js` does this).