feat(sandbox): bundle host-service into the Blaxel image
Third of five. Stacked on #6497 → #6489.
Puts host-service inside the sandbox image. After this a sandbox boots a working host-service with terminals; nothing in the desktop app talks to it yet.
It works end to end
[host-service] listening on http://localhost:4879
[host-service:db] Initialized at /data/host.db, migrations from /app/drizzle
[pty-daemon] spawned pid=246 socket=/tmp/superset-ptyd-….sock daemonVersion=0.3.0
Through the private preview URL, trpc/health.check returns {"status":"ok","cloudRegistered":false,…}. cloudRegistered: false is expected — nothing registers as a host, by design.
Auth is two independent layers
Verified against the running sandbox rather than assumed:
| Request | Result |
|---|---|
| no preview token | 401 — Blaxel's edge rejects |
| preview token, no PSK | 401 UNAUTHORIZED — host-service rejects |
| preview token + PSK | 200 |
So a leaked preview token alone does not grant access to protected procedures. Consequence for PR 4: cloudWorkspace.access must return the host-service secret alongside the preview token, since clients need both.
Four things the image has to get right
Each of these was a real failure found by booting the bundle, not by reading:
npm pkg set type=module— the bundle is ESM; without it Node parses/app/*.jsas CommonJS and dies on the firstimport.- A resolution-only package set. The bundle marks several deps external and then imports them at module load, so they must resolve even where the code path never runs:
@mastra/duckdb,@anush008/tokenizers,onnxruntime-node,libsql,@parcel/watcher,@xterm/headless. Found by booting and installing whatever it asked for until it reached env validation. - Distinct build-context names.
addLocalDir's third argument defaults to the source's basename, sopackages/host-service/distandpackages/pty-daemon/distboth becameCOPY dist— silently shipping host-service's bundle as the pty daemon. Caught by reading the generated Dockerfile. /pty-daemon/node_modulessymlink. The daemon is a separate process importing node-pty, but Node resolves upward from/pty-daemon/distwhile the install lives in/app. Symlinked rather than installed twice, so the daemon and host-service can't diverge on the native addon's version.
Worth knowing
That resolution-only list is mostly mastra's storage/embedding stack reached through provider-auth's credential store. onnxruntime-node and duckdb are not small, and they exist purely to satisfy an import. Trimming host-service's mastra dependency would shrink both this list and the image materially — worth its own piece of work, not this PR.
Also: the Blaxel image build failed once with a generic States.TaskFailed and succeeded on retry with no changes. Second time I've seen that, so treat build failures as worth one retry before diagnosing.
Verification
typecheck 0 · lint 0 · booted from the built image in a real sandbox · pty-daemon spawned · tRPC reachable through the preview URL with both auth layers exercised.
Summary by cubic
Bundles host-service and pty-daemon into the Blaxel sandbox image so a sandbox boots a working host-service with terminals on 4879. Previously the image omitted them; now it ships their bundles and DB migrations, enforces ESM, and avoids native compilation.
- Copies the
host-servicebundle anddrizzlemigrations into/appand thepty-daemonbundle into/pty-daemon/distusing distinct build-context names to preventdistcollisions. - Installs pinned native deps from the
host-servicepackage (better-sqlite3,node-pty) and a resolution-only set for externalized imports:@mastra/duckdb,@anush008/tokenizers,onnxruntime-node,libsql,@parcel/watcher,@xterm/headless; fails the build ifnode-ptyprebuilds are missing. - Sets
npm pkg set type=module; symlinks/pty-daemon/node_modulesto/app/node_modulesso both processes resolve the same native addons. - Requires a prebuilt
host-servicebundle. Action: runbun run --cwd packages/host-service build:host. - No desktop app wiring changes.