52 lines
1.6 KiB
TypeScript
52 lines
1.6 KiB
TypeScript
import { spawn } from "node:child_process";
|
|
import { hasSupervisorHint } from "./supervisor-markers.js";
|
|
|
|
type RespawnMode = "spawned" | "supervised" | "disabled" | "failed";
|
|
|
|
export type GatewayRespawnResult = {
|
|
mode: RespawnMode;
|
|
pid?: number;
|
|
detail?: string;
|
|
};
|
|
|
|
function isTruthy(value: string | undefined): boolean {
|
|
if (!value) {
|
|
return false;
|
|
}
|
|
const normalized = value.trim().toLowerCase();
|
|
return normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on";
|
|
}
|
|
|
|
function isLikelySupervisedProcess(env: NodeJS.ProcessEnv = process.env): boolean {
|
|
return hasSupervisorHint(env);
|
|
}
|
|
|
|
/**
|
|
* Attempt to restart this process with a fresh PID.
|
|
* - supervised environments (launchd/systemd): caller should exit and let supervisor restart
|
|
* - OPENCLAW_NO_RESPAWN=1: caller should keep in-process restart behavior (tests/dev)
|
|
* - otherwise: spawn detached child with current argv/execArgv, then caller exits
|
|
*/
|
|
export function restartGatewayProcessWithFreshPid(): GatewayRespawnResult {
|
|
if (isTruthy(process.env.OPENCLAW_NO_RESPAWN)) {
|
|
return { mode: "disabled" };
|
|
}
|
|
if (isLikelySupervisedProcess(process.env)) {
|
|
return { mode: "supervised" };
|
|
}
|
|
|
|
try {
|
|
const args = [...process.execArgv, ...process.argv.slice(1)];
|
|
const child = spawn(process.execPath, args, {
|
|
env: process.env,
|
|
detached: true,
|
|
stdio: "inherit",
|
|
});
|
|
child.unref();
|
|
return { mode: "spawned", pid: child.pid ?? undefined };
|
|
} catch (err) {
|
|
const detail = err instanceof Error ? err.message : String(err);
|
|
return { mode: "failed", detail };
|
|
}
|
|
}
|