---
name: adom-desktop-runtimes
description: >
  Managed Node and Python runtimes for Adom Bridge bridges on macOS. Use this skill when a
  node or python bridge fails to spawn, when you need to check runtime status with the
  runtimes verb, when a bridge needs Node.js or Python and the box may not have one,
  when npm dependencies for a source-only node bridge are missing, or when you want to
  understand the no-elevation portable node/python install, the prewarm on first launch,
  the Runtimes panel in the GUI, or the loopback-only bind rule for bridge processes.
---

# Managed runtimes: how Adom Bridge provisions Node and Python for bridges

Adom Bridge owns a global, portable Node and Python that every bridge shares.
A bridge never installs its own interpreter and never needs Homebrew, sudo, or
an installer. Core logic lives in `src-tauri/src/runtime_cache.rs`.

## What the managed runtimes are

Resolution order for `ensure_runtime(app, Node|Python)`, race-safe via a
per-kind install lock (two bridges asking at once never double-download):

1. Already `Ready` in the in-memory state map: return the exe.
2. A **system runtime on PATH** is used as-is (`source: system`), no download.
   On a Mac this is typically a Homebrew or nvm/pyenv install; a found exe is
   accepted only if it reports a parseable version.
3. A prior **portable copy** under `~/.adom/adom-runtimes/<kind>-<ver>/`
   (`source: cache`).
4. **Download a portable copy**: stream-download, extract, atomic rename into
   the runtime dir, emitting `runtime-status` progress events.

Pinned portable versions (one place to bump, in `runtime_cache.rs`):

- Node 22.11.0 (official nodejs.org tarball; `darwin-arm64` on Apple Silicon,
  `darwin-x64` otherwise)
- Python 3.12.13 (astral-sh python-build-standalone `install_only` tar.gz for
  `aarch64-apple-darwin` / `x86_64-apple-darwin`; full CPython with `venv`
  and `pip`)

The two hard guarantees:

1. **Never an elevation or installer prompt.** The portable runtimes are plain
   archive extracts into the user-writable `~/.adom/adom-runtimes/`. No `.pkg`,
   no sudo, no admin rights, no Homebrew dependency.
2. **Never a firewall prompt by default.** Adom Bridge passes
   `ADOM_BIND_HOST=127.0.0.1` to every bridge process, and bridges must bind
   it. A bare `server.listen(PORT)` binds `0.0.0.0` and can trigger the macOS
   "accept incoming network connections" dialog; loopback binds never do.

Bridges are spawned with the interpreter's ABSOLUTE path (never a bare
`node`/`python` PATH lookup), so a shell whose PATH lacks the runtime cannot
break a spawn.

## The `runtimes` verb (read-only)

```bash
adom-desktop runtimes
```

Available over the relay and the direct API. Returns a per-runtime snapshot:

- `state`: `absent | installing | ready | failed`
- `source`: `system | cache`
- `version` and `exe` when ready, plus `pct`/`phase` while a download is in
  flight

This is the machine-readable "what is installed and at which version" for
cloud AIs. The human-facing mirror is the **Runtimes panel** in the GUI's
Bridges sidebar: a state dot, version, and source per runtime, with live
updates from the `runtime-status` event.

## Prewarm on first launch

`runtime_cache::prewarm_all` fires shortly after boot on **every** launch
(standalone and embedded), gated by config `prewarm_runtimes` (default true).
It kicks off both `ensure_runtime` calls fire-and-forget: a system or cached
runtime resolves in milliseconds; only a runtime-less box actually downloads.
Turning `prewarm_runtimes` off means runtimes fetch lazily on the first
bridge spawn instead.

Because prewarm is async, a bridge can need Node or Python before it
finishes. The bridge auto-start path calls a non-blocking `runtime_gate`:

- `Ready` -> spawn now (the common case).
- `Warming` -> the verb returns a non-fatal `runtime_warming` result with
  `stillRunning: true` and `statusVerb: "runtimes"`. Poll and retry; this is
  not a failure.
- `Failed` -> a `runtime_failed` result; a retry re-attempts the install.

```bash
# A bridge verb returned runtime_warming? Poll until ready, then retry:
adom-desktop runtimes
# ... wait for state=ready on the needed runtime ...
adom-desktop browser_open_window '{"url": "https://example.com"}'
```

## npm installs for source-only node bridges (`ensure_node_modules`)

A node bridge's wiki zip rightly ships source-only, so its cache dir has no
`node_modules`. At spawn, `ensure_node_modules` resolves dependencies in
this order:

1. The cache dir already has `node_modules`: use it, nothing to do.
2. A **bundled seed** of the same bridge name has `node_modules` AND the
   seed's `bridge.json` version is `>=` the cache version: reuse the seed's
   modules via `NODE_PATH` for the spawn. Instant, no copy, and the seed's
   native bindings are already built for this platform. The version gate is
   the guardrail: a cache strictly newer than the seed may have different
   deps, so stale bundled modules are never reused.
3. Otherwise run `npm install --include=optional` in the cache dir.

npm runs through the **managed runtime's own npm**
(`node <node_modules/npm/bin/npm-cli.js>`), because a portable Node is not
on PATH so a bare `npm` would fail. Bare npm is used only for a system Node.

## When a node or python bridge fails to spawn: what to check

1. **Runtime state first.**
   ```bash
   adom-desktop runtimes
   ```
   - `state: installing`: the portable download is in flight. Poll and retry
     the bridge verb; do not treat `runtime_warming` as an error.
   - `state: failed`: retry the bridge verb (a retry re-attempts the
     install), or check network reachability to nodejs.org / the
     python-build-standalone release host on GitHub.
   - `state: absent` with `prewarm_runtimes` off: the first bridge spawn
     will fetch lazily; expect a one-time delay.
2. **MODULE_NOT_FOUND from a node bridge** means its dependencies did not
   resolve: verify `ensure_node_modules` ran (cache `node_modules` present,
   or a same-or-newer bundled seed exists), or that the npm install in the
   cache dir succeeded.
3. **A macOS firewall dialog appeared**: the bridge bound `0.0.0.0`
   instead of honoring `ADOM_BIND_HOST`. That is a bridge bug; bridges must
   bind `process.env.ADOM_BIND_HOST || '127.0.0.1'` (Node) or
   `os.environ.get('ADOM_BIND_HOST', '127.0.0.1')` (Python).
4. **A strange or unparseable system runtime**: `detect_system` accepts a
   PATH exe only if it reports a parseable version; anything else falls
   through to the portable download, so a broken shim never gets pinned.

## Bridge author contract (summary)

1. Bind `ADOM_BIND_HOST` (loopback), never `0.0.0.0`.
2. Do not bootstrap your own Node or Python. Adom Bridge provisions the
   interpreter and spawns your entrypoint by absolute path, with no
   elevation. Shipping a source-only node bridge is fine; the app runs npm
   for you at spawn.

Dev internals and the do-not-regress rules: `skills/MANAGED_RUNTIMES.md`.
Launch-order timeline of every background prewarm task: `skills/PREWARM.md`.
