#!/usr/bin/env node /** * @file Detached helper spawned by `server/lib/self-restart.js`. Polls the * old dashboard PID until it exits (server/index.js's own SIGTERM handler * has a 5s force-exit backstop, so this never waits forever), then starts a * fresh server from the same entry point and exits itself. The new server's * stdout/stderr are appended to the log file it is given (the checkout's * `data/agenthub-server.log`, the one `agenthub start` writes), so a crash * after an update still leaves Node's stack trace behind. * @author Nguyễn Ngọc Trí Vĩ */ require("../server/lib/windows-hide").install(); // before child_process is read const fs = require("fs"); const path = require("path"); const { spawn } = require("child_process"); const { rotateIfLarge } = require("./log-rotate"); const [, , pidArg, entry, logArg] = process.argv; const pid = parseInt(pidArg, 10); // ponytail: fixed poll cadence/cap, not configurable — this script has one caller. const POLL_MS = 200; const MAX_WAIT_MS = 20_000; function isAlive(p) { try { process.kill(p, 0); return true; } catch { return false; } } /** Append fd for the new server's output, or "ignore" when there is none. */ function openLog(file) { if (!file) return "ignore"; try { fs.mkdirSync(path.dirname(file), { recursive: true }); rotateIfLarge(file); const fd = fs.openSync(file, "a"); fs.writeSync(fd, `[${new Date().toISOString()}] restart-helper: old server ${pid} gone, starting ${entry}\n`); return fd; } catch { return "ignore"; // a log must never stop the restart } } function restart() { const out = openLog(logArg); const child = spawn(process.execPath, [entry], { detached: true, stdio: ["ignore", out, out], env: process.env, }); child.unref(); process.exit(0); } function waitAndRestart(waited = 0) { if (isAlive(pid) && waited < MAX_WAIT_MS) { setTimeout(() => waitAndRestart(waited + POLL_MS), POLL_MS); return; } restart(); } waitAndRestart();