Phase 1A.1: bundleable server foundation for desktop app - #169
Merged
Conversation
Adds the runtime contract + build pipeline that Phase 1B's Electron desktop app will use to spawn the autonomos-server as a child process. Purely additive — the existing make dev / make prod / pm2 paths continue to work unchanged. What lands: - packages/server/src/cli-args.ts: --port and --embedded argv parser, --help - packages/server/src/embedded-mode.ts: localhost-only bind + AUTONOMOS_READY stdout signal for parent-process discovery - packages/server/src/index.ts: wires both, also fixes a latent fragility where existsSync(dashboardDist) returned true on a tsc-only artifact dir (now checks for index.html explicitly, prefers _embedded_dashboard over fallback path) - packages/server/build/embed-dashboard.ts: copies dashboard/dist into the server tree so the bundler sees it - packages/server/build/build-binary.ts: orchestrates bun build --target=node, copies embedded dashboard next to bundled JS for runtime resolution - scripts/test-1a1-isolated.sh: full isolated smoke test (separate config dir, dedicated port, never touches ~/.autonomos/) - docs/research/: consolidated design notes + Phase 1A.1 proposal + Phase 1B/1C sketches capturing the full plan Build deviation from original proposal: bun build --compile is blocked by a hard ABI mismatch between Bun 1.3.10 (ABI 137) and node-pty's prebuilt (ABI 141). Both the static-binary path AND --target=bun fail because the Bun runtime can't load node-pty at all. Pivoted to --target=node so the bundle runs under Node, which loads node-pty cleanly. Phase 1B's Electron bundles Node + this bundle. Static-binary aspiration deferred until either Bun's ABI matches or PTY is refactored to Bun's native API. Smoke test results (./scripts/test-1a1-isolated.sh on darwin-arm64): ✓ AUTONOMOS_READY signal received in 2s ✓ /api/host returns 200 ✓ Dashboard / serves embedded HTML ✓ SIGTERM triggers clean shutdown in 2s ✓ ~/.autonomos/ untouched (test ran in isolation) ✓ make dev path still boots cleanly (regression check) Contract Phase 1B will rely on: Spawn: node dist/<platform>/index.js --port=0 --embedded Discovery: parse "AUTONOMOS_READY port=<N>" from child stdout Shutdown: send SIGTERM, child exits within ~2s Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
nox-0x
approved these changes
May 13, 2026
nox-0x
left a comment
Collaborator
There was a problem hiding this comment.
Approving — Phase 1A.1 is cleanly additive: new CLI flags, embedded-mode plumbing, and a node-target bundle pipeline behind a smoke test that exercises the readiness signal end-to-end. Existing make dev / make prod / pm2 paths are untouched.
Spot-checks that came back clean:
--port=0path:cliArgs.port ?? (Number(process.env.PORT) || 3000)correctly preserves an explicit 0 (since??is nullish-only) and feeds it toserve(), thenserver.address()is read inside the listening callback so the OS-assigned port is what gets announced. Matches the stated contract.parsePort: rejects NaN, non-integers, negatives, and >65535;--portwith missing value throws with a clear message.- Dashboard resolution fix: checking for
index.html(not directory existence) genuinely fixes the latenttsc -b/Vite-bundle confusion described in the PR. AUTONOMOS_READYsignal: emitted on stdout in the listening callback, after the actual port is known — exactly what an Electron parent needs to pattern-match.- Bind host:
127.0.0.1in embedded mode, undefined (host default) otherwise. No behavioral change for standalone.
Minor / follow-up only (not blocking):
- The
parseCliArgscall +--helpexit are sandwiched between two groups ofimportstatements (lines 14–48 inindex.ts). Due to ESM hoisting this still runs after all imports, so the help short-circuit only avoids the heavy top-level work (provider validation,seedDefaultTemplates,migrateIfNeeded) — not the module-load side effects. That matches the PR's claim, but biome'sorganizeImportswill likely shuffle the imports together on next save, which is the cleaner shape. parseCliArgsitself has no unit test. It's small and pure, and the isolated smoke test exercises the happy path via--port/--embedded, so this is a low-priority backfill — useful if more flags accrete.AUTONOMOS_READYis emitted beforeinitGateway()/resumeActiveAgents()/initScheduler()run. The HTTP listener is up so requests succeed, but agent state may still be hydrating for a short window. Matches existing standalone init ordering, so not a new issue — worth a sentence in the Phase 1B integration contract so the Electron shell doesn't assume "ready = fully hydrated."
…ss semantics CI failure: biome's organizeImports rule flagged the parseCliArgs call sandwiched between import groups. Reviewer @nox-0x flagged the same issue separately. Moved all imports to the top, then the parseCliArgs + --help short-circuit, then the rest of the imperative startup work. Also added a comment clarifying that --help only avoids imperative work, not import-time side effects (since ESM hoists all imports regardless). Review observation #3 (readiness semantics): AUTONOMOS_READY signal means "HTTP listener accepting connections" — NOT "agents fully hydrated." Gateway init, resumeActiveAgents, and scheduler startup run in the same tick but may finish slightly later. Documented this in: - embedded-mode.ts (next to the signal emitter) - phase-1b-sketch.md (in the integration contract section) So Phase 1B's Electron shell can load the webview as soon as the signal fires, with the understanding that "fully populated UI" may lag by ~ms. Verified: - npx biome check packages/ → clean (only pre-existing warnings unrelated to 1A.1) - ./scripts/test-1a1-isolated.sh → all checks still pass Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Phase 1A.1 of the desktop-app initiative: adds the runtime contract + build pipeline that Phase 1B's Electron desktop app will use to spawn the autonomos-server as a child process. Purely additive —
make dev,make prod, and pm2 continue to work unchanged.What ships
--portand--embeddedargv flags;--helpshort-circuits before migrations127.0.0.1) in embedded modeAUTONOMOS_READY port=<N>after listener is upexistsSync(dashboardDist)returned true on a tsc-only artifact dir; now checks forindex.htmlexplicitlybun build --target=nodeproduces a Node-runnable bundle + embedded dashboard./scripts/test-1a1-isolated.sh— full smoke test with isolated config dir and dedicated port; never touches~/.autonomos/Build deviation from original proposal
bun build --compile(static binary) was blocked by an ABI mismatch: Bun 1.3.10 runtime uses ABI 137, node-pty's prebuilt is ABI 141. Both the compiled binary path AND--target=bunfail because the Bun runtime can't load node-pty at all.Pivoted to
--target=node. The bundle runs under Node, which loads node-pty cleanly. For Phase 1B, Electron bundles Node + this bundle in the .app — same end-user outcome.Static-binary aspiration deferred until either Bun's ABI matches node-pty OR we switch the PTY implementation to Bun's native API. Captured in
~/.claude/projects/.../memory/project_nodepty_bun_compile.mdso future-us doesn't waste time retrying the same approach.Phase 1B integration contract
Test plan
./scripts/test-1a1-isolated.shpasses end-to-end:/api/hostreturns 200/serves embedded HTML~/.autonomos/untouched (isolation works)make devpath (tsx) still boots the server cleanly with the new code--helpshort-circuits before migrations / provider validationtsc -bcleanWhat's NOT in this PR (deferred)
status,stop,upgrade,install-service) — Phase 1Cinstall.shweb installer — Phase 1C🤖 Generated with Claude Code