import { spawn } from 'node:child_process'; import path from 'node:path'; import { readPidfile, isProcessAlive } from '@visionl/daemon/dist/pidfile.js'; import { VisionLClient } from '@visionl/core'; const POLL_INTERVAL_MS = 100; const MAX_WAIT_MS = 3000; export async function ensureDaemonRunning(port: number): Promise { const client = new VisionLClient(`http://127.0.0.1:${port}`); if (await client.health()) { return client; } const pidInfo = readPidfile(); if (pidInfo && isProcessAlive(pidInfo.pid)) { if (await client.health()) { return client; } } // Spawn the browser-enabled daemon entry (boot.js) so that page/content/ // action routes are registered. Spawning server.js directly would start a // browser-less daemon that only serves /health. const daemonEntryPath = path.resolve(__dirname, '../../daemon/dist/boot.js'); spawn('node', [daemonEntryPath], { env: { ...process.env, VISIONL_PORT: String(port) }, stdio: 'ignore', detached: true, }).unref(); const startTime = Date.now(); while (Date.now() - startTime < MAX_WAIT_MS) { await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS)); if (await client.health()) { return client; } } throw new Error('DAEMON_UNREACHABLE'); }