summaryrefslogtreecommitdiffhomepage
path: root/packages/host-bin/src/main.ts
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-05 15:52:42 +0900
committerAdam Malczewski <[email protected]>2026-06-05 15:52:42 +0900
commitdded4cc5570f47167edf3ea36f232ad4a32bcbc4 (patch)
treedbcb57a7daad05b6daa6e24d0e192c47e036890a /packages/host-bin/src/main.ts
parent2bf8f9ec9714cbd33a0ecfb7b40dd7a335180fb7 (diff)
downloaddispatch-dded4cc5570f47167edf3ea36f232ad4a32bcbc4.tar.gz
dispatch-dded4cc5570f47167edf3ea36f232ad4a32bcbc4.zip
feat(observability): host-bin supervises the collector (spawn-first / drain-last / restart) — 288 tests
host-bin spawns the out-of-process collector before serving (real Bun.spawn adapted to a ChildHandle), restarts on unexpected exit (backoff + restart-guard cap), drains on SIGINT/SIGTERM (collector final-drain, SIGKILL fallback on timeout). createCollectorSupervisor takes an injected spawn so the lifecycle is unit-tested with a fake (no real subprocess). Collector failures never crash the app (D3 subordinate/fail-safe). New env DISPATCH_TRACE_DB (default ./.dispatch-data/traces.db). Verified: tsc -b clean, 288 tests (279 + 9 supervisor), biome 0/0. Live (clean single run): 1 collector during, trace DB auto-populated (nested easy-view), 0 collectors after shutdown.
Diffstat (limited to 'packages/host-bin/src/main.ts')
-rw-r--r--packages/host-bin/src/main.ts28
1 files changed, 28 insertions, 0 deletions
diff --git a/packages/host-bin/src/main.ts b/packages/host-bin/src/main.ts
index 0c795b8..4e4b3e4 100644
--- a/packages/host-bin/src/main.ts
+++ b/packages/host-bin/src/main.ts
@@ -22,6 +22,8 @@ import { extension as sessionOrchestratorExt } from "@dispatch/session-orchestra
import { createSqliteStorage, extension as storageSqliteExt } from "@dispatch/storage-sqlite";
import { extension as toolReadFileExt } from "@dispatch/tool-read-file";
import { createServer, extension as transportHttpExt } from "@dispatch/transport-http";
+import type { ChildHandle } from "./collector-supervisor.js";
+import { createCollectorSupervisor } from "./collector-supervisor.js";
import { configMapToAccess, envToConfigMap } from "./config.js";
function createEmptySecrets(): SecretsAccess {
@@ -63,6 +65,23 @@ async function boot(): Promise<void> {
const logDeps: LogDeps = { now: () => Date.now(), newId: () => crypto.randomUUID() };
const logger = createLogger({ extensionId: "host-bin" }, logSink, logDeps);
+ const traceDbPath = process.env.DISPATCH_TRACE_DB ?? "./.dispatch-data/traces.db";
+
+ const supervisor = createCollectorSupervisor({
+ spawn: (cmd: string[]) => {
+ const proc = Bun.spawn(cmd, { stdout: "inherit", stderr: "inherit" });
+ const handle: ChildHandle = {
+ kill: (signal?: string) => proc.kill(signal as NodeJS.Signals),
+ exited: proc.exited,
+ };
+ return handle;
+ },
+ journalPath,
+ dbPath: traceDbPath,
+ logger: logger.child({ extensionId: "collector-supervisor" }),
+ });
+ supervisor.start();
+
const dbPath = process.env.DISPATCH_DB ?? "./.dispatch-data/dispatch.db";
mkdirSync(dirname(dbPath), { recursive: true });
const sqliteBackend = createSqliteStorage({ path: dbPath });
@@ -100,6 +119,15 @@ async function boot(): Promise<void> {
// Port precedence: BACKEND_PORT (the rewrite's assigned port) → PORT → default.
const port = Number(process.env.BACKEND_PORT) || Number(process.env.PORT) || 24203;
const server = Bun.serve({ fetch: app.fetch, port });
+
+ const shutdown = async () => {
+ logger.info("Shutting down — draining collector");
+ await supervisor.stop();
+ process.exit(0);
+ };
+ process.on("SIGINT", shutdown);
+ process.on("SIGTERM", shutdown);
+
logger.info(`Dispatch listening on http://localhost:${server.port}`);
console.info(`Dispatch listening on http://localhost:${server.port}`);
}