← Commit history

hd -> hydrogen prose; rename docs/adom-hydrogen.md

Kyle Bergstedt ·f9a6742178 ·2mo ago ·parent 1d5723d
1 file changed +58−58
main-SKILL.md+58−58
@@ -40,7 +40,7 @@ For readers of older copies of this skill: a lot has landed in the last don't exist or miss ones that do.  - **Embedded `--start-hidden` honored + window preference** (v1.8.119+) —-  when HD spawns AD with `--embedded --start-hidden`, AD now stays hidden+  when Hydrogen spawns AD with `--embedded --start-hidden`, AD now stays hidden   (v1.8.102–118 force-showed it). The release default respects the launch   flags; the machine owner can opt into always-show via the Settings   checkbox (config `always_show_window_on_launch`). Same preference pattern@@ -59,7 +59,7 @@ don't exist or miss ones that do.  - **Shell auto-approve is a user preference** (v1.8.116+) — per-command   approval is the default everywhere now. Durable opt-in via AD's Settings-  checkbox or `shell_auto_approve '{"permanent": true}'`; HD passes+  checkbox or `shell_auto_approve '{"permanent": true}'`; Hydrogen passes   `--shell-approval <prompt|1h|24h|permanent>` when spawning AD embedded   (session-only) and owns the setting while embedded. The old v1.8.44   embedded-permanent default (and its leak into standalone sessions) is@@ -111,7 +111,7 @@ don't exist or miss ones that do. - **`version.json` `.cli` slot** (v1.8.113+). Additive schema — pre-v1.8.113   readers ignore. Each populated platform has `binary_url` / `binary_sha256`   / `binary_size`. Today the manifest carries `cli.linux_x86_64` and-  `cli.windows_x64`. HD / sibling apps should prefer this typed lookup+  `cli.windows_x64`. Hydrogen / sibling apps should prefer this typed lookup   over hard-coding filenames.  - **Windows CLI bundled with AD** (v1.8.114+). Installing AD drops@@ -130,7 +130,7 @@ don't exist or miss ones that do.   `serve` subcommand probes `127.0.0.1:8766/health` before binding and   skips with a friendly message if another relay is already answering   (override with `--force-bind`). Both fixes matter for the mirrored--  networking WSL2 setup HD uses.+  networking WSL2 setup Hydrogen uses.  ## Starting the Relay Server @@ -310,10 +310,10 @@ Persists to the same `~/.adom/config.json` the GUI uses, so entries survive a GU  HD-style usage: ```bash-# At HD container startup+# At Hydrogen container startup adom-desktop server_add '{"name":"hydrogen-workspace","url":"ws://localhost:8765","autoConnect":true}' -# At HD container shutdown+# At Hydrogen container shutdown adom-desktop server_remove '{"name":"hydrogen-workspace"}' ``` @@ -331,11 +331,11 @@ adom-desktop --target azurevm desktop_screenshot_screen adom-desktop --target all hd_status        # every box at once, results keyed by name ``` -**No-target behavior:** with exactly ONE AD connected, no `--target` is needed (back-compat). With MULTIPLE connected and no `--target`, the call FAILS with `{error:"ambiguous_target", connected:[...], _hint}` (exit 1) — it won't guess which box. A bad name → `{error:"target_not_found", requested, connected:[...]}` (exit 1). Set each AD's name with the `--client-name <name>` launch flag (HD passes this when spawning the embedded AD), the Settings field, or `config.json` `client_name`. Full design + the relay-routing internals: [`skills/MULTI_AD_ADDRESSING.md`](MULTI_AD_ADDRESSING.md).+**No-target behavior:** with exactly ONE AD connected, no `--target` is needed (back-compat). With MULTIPLE connected and no `--target`, the call FAILS with `{error:"ambiguous_target", connected:[...], _hint}` (exit 1) — it won't guess which box. A bad name → `{error:"target_not_found", requested, connected:[...]}` (exit 1). Set each AD's name with the `--client-name <name>` launch flag (Hydrogen passes this when spawning the embedded AD), the Settings field, or `config.json` `client_name`. Full design + the relay-routing internals: [`skills/MULTI_AD_ADDRESSING.md`](MULTI_AD_ADDRESSING.md).  #### Direct HTTP API for sibling apps (v1.8.25+) -If you're authoring a sibling Tauri app on the same machine (Adom Hydrogen, or any future "Adom-family" app), you can skip the CLI binary entirely and POST commands straight into the running adom-desktop GUI on `127.0.0.1:47200` (was `127.0.0.1:8770` through v1.8.32 — moved to be a better neighbor to HD on 47080+ and avoid the 8000-range collision risk). The endpoint runs inside the GUI process (loopback-only bind), uses the same dispatcher the WS path uses, and returns the same JSON shape as `adom-desktop <verb>` — including every `_hint` field.+If you're authoring a sibling Tauri app on the same machine (Adom Hydrogen, or any future "Adom-family" app), you can skip the CLI binary entirely and POST commands straight into the running adom-desktop GUI on `127.0.0.1:47200` (was `127.0.0.1:8770` through v1.8.32 — moved to be a better neighbor to Hydrogen on 47080+ and avoid the 8000-range collision risk). The endpoint runs inside the GUI process (loopback-only bind), uses the same dispatcher the WS path uses, and returns the same JSON shape as `adom-desktop <verb>` — including every `_hint` field.  | Method | Path | Body | Returns | |---|---|---|---|@@ -366,36 +366,36 @@ curl -X POST http://127.0.0.1:47200/command \  **What requires the CLI fallback:** verbs returning a structured `errorCode:"cli_required"` from this endpoint — currently `pull_file`, `send_files`, `shell_execute`. These use binary streaming or multi-minute approval polling that doesn't fit a single synchronous HTTP request. `GET /status` returns the full list at runtime so callers can branch defensively. -Full integration guide (loopback trust model, recipes for HD startup/shutdown, retry/backoff patterns, port-discovery code in Rust + TypeScript) lives in [`skills/DIRECT_API.md`](DIRECT_API.md) in this repo, and as a wiki asset attached to `apps/adom-desktop`.+Full integration guide (loopback trust model, recipes for Hydrogen startup/shutdown, retry/backoff patterns, port-discovery code in Rust + TypeScript) lives in [`skills/DIRECT_API.md`](DIRECT_API.md) in this repo, and as a wiki asset attached to `apps/adom-desktop`.  #### Embedded mode — AD bundled inside Adom Hydrogen (v1.8.42+) -When Adom Hydrogen (HD) bundles AD, AD enters "embedded mode" at boot. HD owns the system-tray icon (AD's is suppressed), HD owns the Adom Cloud login (HD's session token is handed to AD via Phase 4's existing channels), HD owns auto-updates (AD's wiki-poll is disabled — HD bundles a fresh AD on every HD release), and HD spawns AD hidden (its tray "Open Adom Desktop" surfaces the window on demand). Standalone AD users see zero change — none of the signals fire.+When Adom Hydrogen (Hydrogen) bundles AD, AD enters "embedded mode" at boot. Hydrogen owns the system-tray icon (AD's is suppressed), Hydrogen owns the Adom Cloud login (Hydrogen's session token is handed to AD via Phase 4's existing channels), Hydrogen owns auto-updates (AD's wiki-poll is disabled — Hydrogen bundles a fresh AD on every Hydrogen release), and Hydrogen spawns AD hidden (its tray "Open Adom Desktop" surfaces the window on demand). Standalone AD users see zero change — none of the signals fire.  **Detection** — three-signal cascade (any one triggers embedded mode): -1. `--embedded` CLI flag (what HD passes on every spawn)+1. `--embedded` CLI flag (what Hydrogen passes on every spawn) 2. `ADOM_EMBEDDED=1` env var (backup channel)-3. `%LOCALAPPDATA%\Adom Desktop\embedded.json` marker file (survives AD restarts; written by HD's installer; deleted by HD's uninstaller)+3. `%LOCALAPPDATA%\Adom Desktop\embedded.json` marker file (survives AD restarts; written by Hydrogen's installer; deleted by Hydrogen's uninstaller) -**CLI flags HD passes when spawning AD**:+**CLI flags Hydrogen passes when spawning AD**:  ```bash adom-desktop.exe --embedded --start-hidden \   --relay-url ws://127.0.0.1:8765 --relay-name adom-hydrogen \-  --session-token <hd's-stored-token>+  --session-token <hydrogen's-stored-token> ```  - `--embedded` → enter embedded mode (also writes the marker)-- `--start-hidden` → boot with main window invisible. **v1.8.119+: honored in embedded mode** (HD owns the surface; AD stays in the tray and opens via `desktop_window_show`). v1.8.102–118 had a bug where embedded mode force-showed the window even with `--start-hidden`; that's fixed. The machine owner can still flip AD's "Always show the window at launch" Settings checkbox (config `always_show_window_on_launch`, default OFF) to override and always show — a personal-dev-box preference, not the product default.-- `--shell-approval <prompt|1h|24h|permanent>` → HD's passthrough of its user's shell-approval choice (v1.8.116+, session-only)+- `--start-hidden` → boot with main window invisible. **v1.8.119+: honored in embedded mode** (Hydrogen owns the surface; AD stays in the tray and opens via `desktop_window_show`). v1.8.102–118 had a bug where embedded mode force-showed the window even with `--start-hidden`; that's fixed. The machine owner can still flip AD's "Always show the window at launch" Settings checkbox (config `always_show_window_on_launch`, default OFF) to override and always show — a personal-dev-box preference, not the product default.+- `--shell-approval <prompt|1h|24h|permanent>` → Hydrogen's passthrough of its user's shell-approval choice (v1.8.116+, session-only) - `--relay-url <url>` → upsert this relay via existing `server_add` dedup; default name `adom-hydrogen` - `--relay-name <name>` → override the default name - `--session-token <tok>` → already covered by Phase 4 handoff (env / CLI arg / file) -**Docker introspection** — `adom-desktop desktop_embedded_status` returns `{embedded, owner, source, enteredVia, pendingRelayUrl, pendingRelayName, startHidden, markerPath, markerExists}` so cloud-side callers can branch on whether AD is standalone or HD-managed. **v1.8.73+:** `enteredVia` reports `"launch-flag"` (HD spawned AD as a child via `--embedded`) or `"runtime-adopt"` (AD was running standalone; HD adopted it at runtime) — see "Runtime adopt + release" below.+**Docker introspection** — `adom-desktop desktop_embedded_status` returns `{embedded, owner, source, enteredVia, pendingRelayUrl, pendingRelayName, startHidden, markerPath, markerExists}` so cloud-side callers can branch on whether AD is standalone or HD-managed. **v1.8.73+:** `enteredVia` reports `"launch-flag"` (Hydrogen spawned AD as a child via `--embedded`) or `"runtime-adopt"` (AD was running standalone; Hydrogen adopted it at runtime) — see "Runtime adopt + release" below. -**New verbs HD uses to drive AD** (all also usable standalone):+**New verbs Hydrogen uses to drive AD** (all also usable standalone):  | Verb | Purpose | |---|---|@@ -407,16 +407,16 @@ adom-desktop.exe --embedded --start-hidden \ | `desktop_embedded_status` | Introspect current embedded state (incl. `enteredVia`) | | `desktop_embedded_enter` | **v1.8.73+** runtime adopt: standalone → embedded as `runtime-adopt` | | `desktop_embedded_release` | **v1.8.73+** revert to standalone. **v1.8.77+: always reverts, never exits AD** (regardless of `entered_via`) — use `desktop_shutdown` for explicit exit |-| `desktop_logout` | Clear AD's `~/.adom/session.json` (HD's sign-out propagation) |+| `desktop_logout` | Clear AD's `~/.adom/session.json` (Hydrogen's sign-out propagation) |  #### Runtime adopt + release (v1.8.73+) — the LaunchFlag-vs-RuntimeAdopt distinction -HD has two ways to put AD into embedded mode:+Hydrogen has two ways to put AD into embedded mode: -1. **Spawn AD with `--embedded`** (LaunchFlag) — HD launches a fresh AD process as its child. AD enters embedded with `entered_via=launch-flag`. When HD subsequently dies ungracefully (without calling `/embedded/release` or `desktop_shutdown` first), AD's 60s `embedded_monitor` tick notices HD's control port is dead and **exits AD** (the "no hanging chad" rule — no tray-less orphan).-2. **Adopt at runtime via `POST /embedded/enter`** (RuntimeAdopt) — AD was already running standalone; HD calls the direct-API endpoint to flip AD into embedded mode without a respawn. AD enters embedded with `entered_via=runtime-adopt`, preserving its open state, current PID, all in-flight connections. When HD subsequently dies ungracefully, AD's monitor **reverts AD to standalone** — tray restored, footer pill hidden, marker deleted. AD process stays alive.+1. **Spawn AD with `--embedded`** (LaunchFlag) — Hydrogen launches a fresh AD process as its child. AD enters embedded with `entered_via=launch-flag`. When Hydrogen subsequently dies ungracefully (without calling `/embedded/release` or `desktop_shutdown` first), AD's 60s `embedded_monitor` tick notices Hydrogen's control port is dead and **exits AD** (the "no hanging chad" rule — no tray-less orphan).+2. **Adopt at runtime via `POST /embedded/enter`** (RuntimeAdopt) — AD was already running standalone; Hydrogen calls the direct-API endpoint to flip AD into embedded mode without a respawn. AD enters embedded with `entered_via=runtime-adopt`, preserving its open state, current PID, all in-flight connections. When Hydrogen subsequently dies ungracefully, AD's monitor **reverts AD to standalone** — tray restored, footer pill hidden, marker deleted. AD process stays alive. -The rule is simple: **whoever started AD owns AD's death**. If AD started itself (standalone → runtime adopt), HD's death doesn't take AD down with it.+The rule is simple: **whoever started AD owns AD's death**. If AD started itself (standalone → runtime adopt), Hydrogen's death doesn't take AD down with it.  **Two new direct-API endpoints** (v1.8.73+, sugar over `POST /command`): @@ -429,20 +429,20 @@ curl -s -X POST http://127.0.0.1:47200/embedded/enter curl -s -X POST http://127.0.0.1:47200/embedded/release ``` -**Probe target (v1.8.75+):** the 60s `embedded_monitor` checks HD's *control port* (default `47084`, discovered from `%APPDATA%\adom-hydrogen\ports.json` key `control`), not HD's legacy `:9001` webview-eval port. Same discovery `hd_api` uses. Pre-v1.8.75 this was hardcoded to `:9001` — HD doesn't bind that port anymore, so the probe always reported HD dead and every embedded AD reverted/exited within 60s of HD starting.+**Probe target (v1.8.75+):** the 60s `embedded_monitor` checks Hydrogen's *control port* (default `47084`, discovered from `%APPDATA%\adom-hydrogen\ports.json` key `control`), not Hydrogen's legacy `:9001` webview-eval port. Same discovery `hd_api` uses. Pre-v1.8.75 this was hardcoded to `:9001` — Hydrogen doesn't bind that port anymore, so the probe always reported Hydrogen dead and every embedded AD reverted/exited within 60s of Hydrogen starting. -**Live frontend update (v1.8.76+):** the "Embedded · HD" footer pill responds to Tauri `mode-changed` events emitted on every transition. So if AD's window is open when HD adopts it (or HD releases it), the pill flips immediately — no page reload, no manual refresh.+**Live frontend update (v1.8.76+):** the "Embedded · Hydrogen" footer pill responds to Tauri `mode-changed` events emitted on every transition. So if AD's window is open when Hydrogen adopts it (or Hydrogen releases it), the pill flips immediately — no page reload, no manual refresh. -**Stale-marker safety (v1.8.74+):** if the marker file says `entered_via=launch-flag` AND HD's control port isn't responding at boot, AD deletes the marker BEFORE exiting. Without the delete, every subsequent manual launch (no `--embedded` flag) would hit the same exit branch and AD would be unlaunchable until the user manually rm'd the marker. The delete-before-exit makes the next launch start standalone normally.+**Stale-marker safety (v1.8.74+):** if the marker file says `entered_via=launch-flag` AND Hydrogen's control port isn't responding at boot, AD deletes the marker BEFORE exiting. Without the delete, every subsequent manual launch (no `--embedded` flag) would hit the same exit branch and AD would be unlaunchable until the user manually rm'd the marker. The delete-before-exit makes the next launch start standalone normally.  **Shell auto-approve is a user preference** (v1.8.116+ — replaces the v1.8.44 embedded-permanent default). The safe default everywhere is per-command approval. Resolution order at AD boot: -1. `--shell-approval <prompt|1h|24h|permanent>` launch flag — HD's passthrough of its user's choice when spawning AD embedded. **Session-only**: never persisted by AD.+1. `--shell-approval <prompt|1h|24h|permanent>` launch flag — Hydrogen's passthrough of its user's choice when spawning AD embedded. **Session-only**: never persisted by AD. 2. The durable Settings checkbox ("Auto-approve shell commands permanently", config field `shell_auto_approve_permanent`, default OFF). Also set/cleared via `shell_auto_approve '{"permanent": true|false}'`. 3. A restored timed grant from a previous session (+1h/+24h buttons, `duration_secs` calls). 4. Otherwise: prompt per command. -In embedded mode **HD owns the preference** (HD is the parent, AD the child): AD's Settings checkbox goes read-only with "Managed by Adom Hydrogen", and the footer shows "**Permanent** *(managed by Adom Hydrogen)*" when armed. HD changes it at runtime with the same verb:+In embedded mode **Hydrogen owns the preference** (Hydrogen is the parent, AD the child): AD's Settings checkbox goes read-only with "Managed by Adom Hydrogen", and the footer shows "**Permanent** *(managed by Adom Hydrogen)*" when armed. Hydrogen changes it at runtime with the same verb:  ```bash # revoke everything (timed + permanent + the saved checkbox):@@ -461,7 +461,7 @@ KiCad, Fusion, Browser/Puppeteer, and all third-party bridges now bind OS-assign - The CLI verb namespace (`kicad_*`, `fusion_*`, `browser_*`, plus any third-party `<bridge>_*`) is the contract. Always go through that. - adom-desktop's direct API forwards to whatever port each bridge is on at the moment of the call. - `bridge_list` reports `spawn.runtimePort` per bridge for debugging — do NOT hardcode it anywhere.-- Why this changed: HD's bridges also wanted 8772/8773/8851; we used to collide silently. Dynamic ports = clean coexistence.+- Why this changed: Hydrogen's bridges also wanted 8772/8773/8851; we used to collide silently. Dynamic ports = clean coexistence.  ### Managing bridges (the AI fully controls bridges on its own) @@ -1983,9 +1983,9 @@ adom-desktop desktop_pull_glob "{  ### Adom Hydrogen (`hd_*`) — proxy + build/lifecycle for the sibling Tauri app -When the user has **Adom Hydrogen** running (a sibling Tauri v2 app at `C:\Github\adom-hydrogen`), built-in `hd_*` verbs reach into it. NOT a separate bridge process — adom-desktop proxies HTTP calls directly to HD's control API.+When the user has **Adom Hydrogen** running (a sibling Tauri v2 app at `C:\Github\adom-hydrogen`), built-in `hd_*` verbs reach into it. NOT a separate bridge process — adom-desktop proxies HTTP calls directly to Hydrogen's control API. -**Port discovery (v1.8.72+):** HD's control API lives on **port `47084`** (default; the actual port is read from `%APPDATA%\adom-hydrogen\ports.json` key `control` on every `hd_api` call, fallback 47084). Pre-v1.8.72 the verbs were hardcoded to HD's legacy `:9001` webview-eval port, which HD doesn't bind anymore — that's why `hd_status` reporting "HD not running" while HD's window is visible is the upgrade signal.+**Port discovery (v1.8.72+):** Hydrogen's control API lives on **port `47084`** (default; the actual port is read from `%APPDATA%\adom-hydrogen\ports.json` key `control` on every `hd_api` call, fallback 47084). Pre-v1.8.72 the verbs were hardcoded to Hydrogen's legacy `:9001` webview-eval port, which Hydrogen doesn't bind anymore — that's why `hd_status` reporting "Hydrogen not running" while Hydrogen's window is visible is the upgrade signal.  #### The canonical generic verb — `hd_api` (v1.8.72+) @@ -1995,7 +1995,7 @@ adom-desktop hd_api '{"method":"POST","path":"/setup/run-step","body":{"id":"wel adom-desktop hd_api '{"method":"GET","path":"/wsl/status"}' ``` -Generic HTTP pass-through to any HD endpoint. Raw JSON in `body` — no curl-through-the-relay quote mangling. Returns `{success, status, body, port, method, path}` mirroring HD's response. If HD isn't running, returns `{ok:false, error:"HD not reachable on :47084", _hint:"..."}` instead of timing out.+Generic HTTP pass-through to any Hydrogen endpoint. Raw JSON in `body` — no curl-through-the-relay quote mangling. Returns `{success, status, body, port, method, path}` mirroring Hydrogen's response. If Hydrogen isn't running, returns `{ok:false, error:"Hydrogen not reachable on :47084", _hint:"..."}` instead of timing out.  **Public denylist** — these paths are refused with `{ok:false, error:"not_allowed_in_public", denylist_match:"..."}` on public AD builds (use the internal `hd-dev` cargo feature to bypass): @@ -2004,30 +2004,30 @@ Generic HTTP pass-through to any HD endpoint. Raw JSON in `body` — no curl-thr /container/reset, /workspace/reset, /workspace/destroy ``` -#### Inspect + drive a running HD (v1.8.15+, post-v1.8.72 rework)+#### Inspect + drive a running Hydrogen (v1.8.15+, post-v1.8.72 rework)  | Verb | What | Status | |---|---|---|-| `hd_status` | Composes AD-side facts (running, repo SHA) + HD's `/workspace/health` + `/buildinfo` relayed | ✓ shipped |+| `hd_status` | Composes AD-side facts (running, repo SHA) + Hydrogen's `/workspace/health` + `/buildinfo` relayed | ✓ shipped | | `hd_api` | Generic HTTP pass-through (above) | ✓ v1.8.72+ |-| `hd_log '{"tail":30}'` | Tail `%APPDATA%\adom-hydrogen\adom-hydrogen.log` (reads disk — works EVEN when HD is down) | ✓ shipped |+| `hd_log '{"tail":30}'` | Tail `%APPDATA%\adom-hydrogen\adom-hydrogen.log` (reads disk — works EVEN when Hydrogen is down) | ✓ shipped | | `hd_open_url '{"url":"...","browser":"chrome","profileDir":"Default"}'` | POST `/open-in-profile` — open URL in a specific browser profile | ✓ shipped | | `hd_browser_profiles` | GET `/browser-profiles` — enumerate browsers + profiles | ✓ shipped |-| `hd_screenshot` / `hd_shot` | Find HD window, capture lossless PNG via PrintWindow; `hd_shot` adds region-scoped variant | ✓ shipped |-| `hd_eval '{"js":"..."}'` | POST `/eval` — run JS in HD's main webview | ⚠ **Moved to `hd-dev` in v1.8.72.** Public returns `verb_moved_to_hd_dev`. Use `hd_api '{"method":"POST","path":"/eval","body":{"js":"..."}}'` |+| `hd_screenshot` / `hd_shot` | Find Hydrogen window, capture lossless PNG via PrintWindow; `hd_shot` adds region-scoped variant | ✓ shipped |+| `hd_eval '{"js":"..."}'` | POST `/eval` — run JS in Hydrogen's main webview | ⚠ **Moved to `hd-dev` in v1.8.72.** Public returns `verb_moved_to_hd_dev`. Use `hd_api '{"method":"POST","path":"/eval","body":{"js":"..."}}'` | | `hd_iframe_eval '{"js":"...","contextIndex":0}'` | POST `/iframe-eval` — JS in code-server iframe via CDP | ⚠ Moved to `hd-dev`. Use `hd_api` equivalent | | `hd_reload_vscode` | POST `/reload-vscode` | ⚠ Moved to `hd-dev`. Use `hd_api` equivalent | | `hd_container_exec '{"command":"..."}'` | POST `/container-exec` | ⚠ Moved to `hd-dev`. Use `hd_api` equivalent |-| `hd_launch` / `hd_stop` / `hd_restart` | HD process lifecycle | ✓ shipped |+| `hd_launch` / `hd_stop` / `hd_restart` | Hydrogen process lifecycle | ✓ shipped | | `hd_ship` | Atomic build+relaunch+verify-SHA (see below) | ✓ shipped |-| `hd_build*` (v1.8.16+) | Build pipeline orchestration | ⚠ Moved to `hd-dev` in v1.8.72 (HD developers only) |+| `hd_build*` (v1.8.16+) | Build pipeline orchestration | ⚠ Moved to `hd-dev` in v1.8.72 (Hydrogen developers only) |  #### Atomic ship — `hd_ship` (v1.8.49+) — START HERE for post-push rebuilds  **One verb, one call, one structured pass/fail.** Replaces the 4-step chain `hd_stop` → `hd_build_rust` → poll `hd_build_status` → `hd_launch` → `curl :47084/buildinfo`. Eliminates the "is the new code actually running?" guesswork after every push.  Sequence inside one call:-1. taskkill HD + poll until zero instances (frees the exe lock)+1. taskkill Hydrogen + poll until zero instances (frees the exe lock) 2. `git fetch && git reset --hard origin/main && rev-parse HEAD` — asserts `HEAD == expectSha` (fails fast if GitHub hasn't replicated the push yet) 3. if `clean:true`, `cargo clean -p adom-hydrogen` (defeats stale-relink) 4. `cargo build` and parse stderr for `Compiling adom-hydrogen` to know whether anything actually compiled@@ -2050,12 +2050,12 @@ Returns on failure: `{ok:false, failedAt:'args'|'kill'|'git'|'clean'|'build'|'la | `failedAt` | What it means | |---|---| | `args` | `expectSha` missing / malformed |-| `kill` | Couldn't taskkill HD (rare — usually permissions) |+| `kill` | Couldn't taskkill Hydrogen (rare — usually permissions) | | `git` | `git fetch / reset` failed, OR `HEAD != expectSha` after reset (push hasn't replicated to origin/main yet — retry in a few seconds) | | `clean` | `cargo clean` failed (rare) | | `build` | `cargo build` failed — read `_hint` for next step (usually `hd_build_log` to see the compile error) | | `launch` | Cargo finished but `adom-hydrogen.exe` wouldn't start — `binary_missing` or process spawn error |-| `verify` | HD launched but `/buildinfo` either never responded or reported a different SHA than `expectSha` (the stale-relink trap — retry with `clean:true`) |+| `verify` | Hydrogen launched but `/buildinfo` either never responded or reported a different SHA than `expectSha` (the stale-relink trap — retry with `clean:true`) |  Long-running: cold cargo build can take 2-3 min. CLI WS timeout is 12 min — call as a single sync invocation and wait. Don't poll inside `hd_ship`; it does its own internal polling. @@ -2063,7 +2063,7 @@ Long-running: cold cargo build can take 2-3 min. CLI WS timeout is 12 min — ca  Use these directly only when you need finer control than `hd_ship` provides (e.g. frontend-only build, watching log stream live, manual launch sequencing). For the common "I pushed code, get it running" path use `hd_ship`. -The relay's `shell_execute` has a 30s timeout; HD's `pnpm build` + `cargo build` exceed that, so the build is split into async-spawn verbs + sync poll/tail verbs:+The relay's `shell_execute` has a 30s timeout; Hydrogen's `pnpm build` + `cargo build` exceed that, so the build is split into async-spawn verbs + sync poll/tail verbs:  ```bash # Frontend-only rebuild, watch progress, relaunch (no hd_ship since hd_ship is Rust-build-only)@@ -2115,13 +2115,13 @@ Returns:  **`relinkOnly:true` on success is the silent-fail trap to look for** — your push didn't actually rebuild because cargo found nothing to recompile. Usually means your source changes weren't pulled, or you touched a file the workspace doesn't track. Use `hd_ship` with `clean:true` to force a fresh build. -**`exeLocked:true` on failure** + `holderPids` tells you exactly which PIDs to taskkill before retry; or just use `hd_ship` which kills HD before building.+**`exeLocked:true` on failure** + `holderPids` tells you exactly which PIDs to taskkill before retry; or just use `hd_ship` which kills Hydrogen before building.  ##### `hd_launch` — start the debug binary (v1.8.52+ enriched) -Args: `killExisting` — optional bool, default `false`. When `false` and HD is already running, refuses with `reason:'already_running'` so you don't accidentally attach to a stale process. When `true`, taskkill the existing instance with a 600 ms grace period before launching fresh.+Args: `killExisting` — optional bool, default `false`. When `false` and Hydrogen is already running, refuses with `reason:'already_running'` so you don't accidentally attach to a stale process. When `true`, taskkill the existing instance with a 600 ms grace period before launching fresh. -Returns on launch: `{ok:true, launched:true, wasAlreadyRunning:bool, killedFirst:bool, pid:int (HD's actual PID, not the cmd-wrapper), exePath, builtSha:string|null, _hint}`.+Returns on launch: `{ok:true, launched:true, wasAlreadyRunning:bool, killedFirst:bool, pid:int (Hydrogen's actual PID, not the cmd-wrapper), exePath, builtSha:string|null, _hint}`.  Returns on refusal: `{ok:false, launched:false, wasAlreadyRunning:bool, pid:int|null, killedFirst:false, reason, _hint}`. `reason` ∈: - `build_in_progress` — wait for `hd_build_status.building:false`@@ -2129,7 +2129,7 @@ Returns on refusal: `{ok:false, launched:false, wasAlreadyRunning:bool, pid:int| - `already_running` — pass `killExisting:true` to force-kill+relaunch, or use `hd_restart` - `binary_missing` — `target/debug/adom-hydrogen.exe` not on disk; run `hd_build_rust` (or `hd_ship`) first -**`launched:true, wasAlreadyRunning:false, killedFirst:false` is the proof you need that truly fresh code is running.** Any other combo means you should compare `builtSha` to HD's actual `/buildinfo.build_sha`.+**`launched:true, wasAlreadyRunning:false, killedFirst:false` is the proof you need that truly fresh code is running.** Any other combo means you should compare `builtSha` to Hydrogen's actual `/buildinfo.build_sha`.  ##### `hd_stop` / `hd_restart` / `hd_build_log` / `hd_build_tail` @@ -2152,16 +2152,16 @@ The response includes the applied `lingerSecs` so you can confirm what landed. H  #### Named-region screenshots — `hd_shot` (v1.8.54+) -**One verb replaces the `desktop_list_windows | grep 'Hydrogen' → desktop_screenshot_window <hwnd>` chain** (3 calls + fragile HWND parsing). Captures a named region of HD, produces BOTH a full-res PNG and a Claude-safe-resized copy, and returns rich metadata so the AI never has to chain screenshot → pull → convert → crop → resize → Read manually.+**One verb replaces the `desktop_list_windows | grep 'Hydrogen' → desktop_screenshot_window <hwnd>` chain** (3 calls + fragile HWND parsing). Captures a named region of Hydrogen, produces BOTH a full-res PNG and a Claude-safe-resized copy, and returns rich metadata so the AI never has to chain screenshot → pull → convert → crop → resize → Read manually. -Each shot is **self-identifying**: it includes the running build's SHA + local build time read from HD's `/buildinfo` so you can confirm you're looking at the build you expect before trusting what you see (no more "this screenshot was from the previous binary, not my fix" confusion).+Each shot is **self-identifying**: it includes the running build's SHA + local build time read from Hydrogen's `/buildinfo` so you can confirm you're looking at the build you expect before trusting what you see (no more "this screenshot was from the previous binary, not my fix" confusion). -Regions are deterministic — they resolve via HD window bounds + CDP element rects queried through HD's `/eval` endpoint. No AI-supplied pixel coords.+Regions are deterministic — they resolve via Hydrogen window bounds + CDP element rects queried through Hydrogen's `/eval` endpoint. No AI-supplied pixel coords.  Args: - `region` — **REQUIRED**. One of:-  - `full` — whole HD window-  - `titlebar` — top ~40 px strip (HD's build SHA / timestamp readout lives here)+  - `full` — whole Hydrogen window+  - `titlebar` — top ~40 px strip (Hydrogen's build SHA / timestamp readout lives here)   - `vscode` — VS Code iframe pane via CDP element rect   - `setup-panel` — Setup Steps panel (`.setup-panel` / `.setup-panel-output`)   - `wiki` — wiki / browser tab pane@@ -2177,7 +2177,7 @@ Returns:   fullSize:[w,h], safeSize:[w,h],   clipRect:[x,y,w,h],          # captured-image pixels (NOT screen pixels)   capturedAtLocal,-  runningBuildSha,             # from HD's /buildinfo — null if HD doesn't expose it yet+  runningBuildSha,             # from Hydrogen's /buildinfo — null if Hydrogen doesn't expose it yet   runningBuildLocal,   hdWindowFound:bool, regionFound:bool,   windowOuter:[w,h], capturedSize:[w,h],@@ -2206,12 +2206,12 @@ adom-desktop hd_shot '{"region":"full"}'  Edge cases: - **Unknown region** — refusal: `{ok:false, error, knownRegions:[...]}`. No fallback — fix the call.-- **`regionFound:false`** — the element rect wasn't in HD's DOM. The panel/iframe may not be mounted yet, OR HD's markup changed and `region_selectors()` in `hd_bridge.rs` is out of date. The shot is **still produced** (falls back to the full window) so you have something to look at; check `_hints.regionMiss` for the reason.-- **`runningBuildSha:null`** — HD's `/buildinfo` isn't exposed yet on the running version. The shot is still produced; you just can't self-verify which build it represents.+- **`regionFound:false`** — the element rect wasn't in Hydrogen's DOM. The panel/iframe may not be mounted yet, OR Hydrogen's markup changed and `region_selectors()` in `hd_bridge.rs` is out of date. The shot is **still produced** (falls back to the full window) so you have something to look at; check `_hints.regionMiss` for the reason.+- **`runningBuildSha:null`** — Hydrogen's `/buildinfo` isn't exposed yet on the running version. The shot is still produced; you just can't self-verify which build it represents.  **Always pull_file the `safePath` and Read THAT** — it's already sized for Claude's image analysis. The `fullPath` is for shotlog / disk archival, NOT direct Read (may exceed Claude's per-image budget). The `_hints.pull` field gives you the exact `pull_file` command to copy-paste. -**Old workaround to drop:** Do NOT use `desktop_list_windows | filter 'Hydrogen' | desktop_screenshot_window <hwnd>` anymore for HD — `hd_shot` replaces that entire pattern. Generic `desktop_screenshot_window` still applies to non-HD windows (other apps, dialogs, etc.).+**Old workaround to drop:** Do NOT use `desktop_list_windows | filter 'Hydrogen' | desktop_screenshot_window <hwnd>` anymore for Hydrogen — `hd_shot` replaces that entire pattern. Generic `desktop_screenshot_window` still applies to non-HD windows (other apps, dialogs, etc.).  ### TCP Port-forward Tunnels — `tunnel open|close|list|check|rdp` + `display` (v1.8.85+, reworked v1.8.147) @@ -2339,7 +2339,7 @@ Every `wsl_exec` / `run_script bash` spawns a `wsl.exe` client. If WSL's service    adom-desktop wsl_recover '{"confirm":true}'    # reap the candidates    adom-desktop wsl_recover '{"confirm":true,"sparePids":[1234]}'   # also protect pid 1234    ```-   It classifies each `wsl.exe`: childless + (0-CPU & old | runaway-CPU) → **candidate**; everything else → **spared**. It **spares any `wsl.exe` with child processes** (that's almost certainly the live distro, e.g. the HD `Adom-Workspace`), honors `sparePids`, and never touches `wslservice`/`wslhost`/`vmmem`. Review each candidate's `cmd` in the dry-run to confirm the live workspace isn't among them, then confirm. **Never** `wsl --shutdown` (it kills the live workspace too). Reaping childless orphans often cascades — their now-parentless children exit on their own (observed 114 → 18 from reaping 54).+   It classifies each `wsl.exe`: childless + (0-CPU & old | runaway-CPU) → **candidate**; everything else → **spared**. It **spares any `wsl.exe` with child processes** (that's almost certainly the live distro, e.g. the Hydrogen `Adom-Workspace`), honors `sparePids`, and never touches `wslservice`/`wslhost`/`vmmem`. Review each candidate's `cmd` in the dry-run to confirm the live workspace isn't among them, then confirm. **Never** `wsl --shutdown` (it kills the live workspace too). Reaping childless orphans often cascades — their now-parentless children exit on their own (observed 114 → 18 from reaping 54). 3. **status wsl block** — `{wslProcessCount, wedged, _hint}` lets you see a pile-up *before* it blocks work. Path differs by transport: **`status.desktop.wsl`** via the CLI/relay (`adom-desktop status` — nested like `desktop.apps`), or top-level **`status.wsl`** on the local direct API (`GET /status`). `wedged:true` at/above `WSL_WARN_AT=50`.  #### Long-running in-distro work + distro lifecycle — golden-image bakes (v1.8.161+)@@ -2363,7 +2363,7 @@ adom-desktop wsl_list                                      # {distros:[{name,sta  **systemd user session — `viaRoot`.** On a `systemd=true` distro, `-u <user>` can fail "Failed to start the systemd user session for '<user>'". Pass `"viaRoot": true` to `wsl_exec_async` — AD runs `-u root -- runuser -u <user> -- bash -l`, dropping to the user WITHOUT depending on `[email protected]`. -`wsl_import` / `wsl_export` / `wsl_unregister` are async (jobId + poll) too, so big imports/exports never EOF. All are **strictly scoped to the named distro** — there is no global `wsl --shutdown` path (that would kill the live HD workspace), and `wsl_unregister` refuses `Adom-Workspace`. Job ids live in AD's memory only (not persisted across an AD restart; the log file survives). The wedge defense above still applies — an over-cap spawn returns `wsl_wedged`.+`wsl_import` / `wsl_export` / `wsl_unregister` are async (jobId + poll) too, so big imports/exports never EOF. All are **strictly scoped to the named distro** — there is no global `wsl --shutdown` path (that would kill the live Hydrogen workspace), and `wsl_unregister` refuses `Adom-Workspace`. Job ids live in AD's memory only (not persisted across an AD restart; the log file survives). The wedge defense above still applies — an over-cap spawn returns `wsl_wedged`.  ## Detecting App Installation