Repository navigation
The production service runner: one headless process for engine + service (#955) - #958
Merged
Merged
Conversation
… engine + service The hub cutover's missing orchestration: the extension host normally spawns the engine and boots the amicode service, and the #822 boot probe only rehearses that arrangement before exiting. The hub has no extension host — this adds the permanent runner: - src/amicode_service_runner.ts — bootAmicodeServiceRunner: spawns the vendored engine password-armed (mintServerPassword + OPENCODE_SERVER_ PASSWORD, the ServerManager/probe idiom), health-waits WITH the armed credential, then boots the service through createAmicodeService exactly as startAmicodeService wires it (engine handle + shelf distRoot + engine-token auth). No fleetActivation is ever passed (H3: the hub runs the byte-identical unarmed base service). - src/amicode_service_runner_cli.ts — the env-driven CLI (AMICODE_ENGINE_ BIN / AMICODE_APP_DIST / AMICODE_SERVICE_PORT=4095 / AMICODE_ENGINE_PORT= 4094 / AMICODE_ENGINE_CWD / AMICODE_ENGINE_PASSWORD / OPENCODE_DB pin passthrough), bundled by esbuild to bin/dist/amicode-service-runner.mjs (the mcp-amico.mjs convention). SIGTERM/SIGINT → graceful teardown; any boot failure or engine death exits non-zero with a named [service-runner] FAIL reason — never a silent half-boot. - test/amicode_service_runner.test.ts — live-gated orchestration tests (real vendored engine + built dist): the six #823 probe surfaces through the runner's own orchestration, the OPENCODE_DB pin reaching the engine, the busy-fixed-port fallback; always-on fail-loud tests (missing engine bin, empty shelf, never-healthy engine → named reason + child torn down). - amicode_service/server.ts — fix a latent wedge: start() set this.server before the listen promise, so a busy fixed port left instance state that made the caller's own ephemeral fallback die on 'already running'. Ran for real against a throwaway OPENCODE_DB: all six probe surfaces PASS through the runner's boot lines; SIGTERM tears both processes down. Refs #955
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: trueThanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…ilt dist CI has no app dist: the shelf check fired before the fake engine ever spawned, so the test asserted the wrong named reason. Give the test a stub shelf (index.html only) — it isolates the engine-health path.
aarontrowbridge
marked this pull request as ready for review
September 10, 2026 08:19
This was referenced Sep 10, 2026
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.
Refs #955 (the hub cutover, spec spec-20260910-080000).
What
The hub has no extension host: the extension-host arrangement (spawn the vendored engine, boot the amicode service alongside) has no permanent home there — the #822 boot probe only rehearses it and exits. This adds the runner: ONE long-lived headless process that spawns the engine and serves the app.
src/amicode_service_runner.ts—bootAmicodeServiceRunner: engine spawn password-armed (the SAME mint convention as every spawn site:mintServerPassword()→OPENCODE_SERVER_PASSWORD, health-wait WITH the armed credential), thencreateAmicodeServicewired exactly asstartAmicodeServicedoes (engine handle + shelf distRoot + engine-token auth). NofleetActivationis ever passed — H3: the hub runs the byte-identical unarmed base service.src/amicode_service_runner_cli.ts— the env surface, bundled tobin/dist/amicode-service-runner.mjs(themcp-amico.mjsconvention; plainnode):AMICODE_ENGINE_BIN(default: the vendored path relative to the bundle),AMICODE_APP_DIST(required — a missing/unbuilt dist fails loud, the NEEDS-SETUP placeholder is the extension's degradation, not a hub shape),AMICODE_SERVICE_PORT(4095),AMICODE_ENGINE_PORT(4094),AMICODE_ENGINE_CWD,OPENCODE_DB(the canonical pin, passed through),AMICODE_ENGINE_PASSWORD(ops-owned credential for the frontdoor's?auth_token=carrier; random mint when absent).[service-runner] FAIL: <reason>— never a silent half-boot.amicode_service/server.ts:start()setthis.serverbefore the listen promise — a busy fixed port left instance state that made the caller's own ephemeral fallback die on "already running" (the wiring's fallback had the same exposure).Tests (
test/amicode_service_runner.test.ts)Live-gated (real vendored engine + built dist, the boot-probe convention): the six #823 probe surfaces asserted through the runner's own orchestration; the
OPENCODE_DBpin reaching the engine (the pinned DB file gets created/migrated); the busy-fixed-port fallback (pins the server.ts fix). Always-on fail-loud: missing engine bin / empty shelf / never-healthy engine — each a namedAmicodeServiceRunnerError.reason, the spawned child torn down.Evidence (run for real in the worktree, throwaway DB)
Runner boot lines:
Six surfaces against the runner's origin: app doc
200 text/html(not the placeholder) · bootstrap?auth_token=(no header)200· anonymous asset200 text/javascript· proxiedGET /session200 application/json([]) · proxiedGET /event200 text/event-stream·GET /amicode/profile{"ok":true,…}. Pinned DB file created (256 KiB, engine-migrated).Fail-loud (CLI):
AMICODE_ENGINE_BIN=/nonexistent/opencode node bin/dist/amicode-service-runner.mjs→[service-runner] FAIL: no engine binary at /nonexistent/opencode — set AMICODE_ENGINE_BIN …, exit 1.SIGTERM →
[service-runner] SIGTERM — tearing down engine + service/[service-runner] stopped — service closed, engine torn down, runner exits 0, engine process gone, service port closed.Baseline
typecheckclean; extension vitest: 8 pre-existing failures (clean main @ a7741ee, clean env: 9, of whichcli_gatepasses here only because a local build staged the amico-run bins) — none introduced. The runner tests: 5 live + 3 fail-loud green.