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)

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.
# 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.
    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.