app
Adom Bridge
Public Made by Adomby adom
Adom Bridge unleashes your AI onto your full PC: full power, full safety. The AI breaks out of the container it runs in and onto your real machine, managing and running your entire laptop as you: launch apps, move files, drive any window, control KiCad, Fusion 360 or your real signed-in browser. Works with any AI, cloud or local (Claude Desktop, Claude Code, Codex), no Hydrogen required. Bridge and its bridges are an operating system for AI, with managed Node and Python runtimes, and a human-onl
← Commit history
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