





















@@ -1,6 +1,6 @@
11#!/usr/bin/env node
223-import { spawnSync } from "node:child_process";
3+import { spawn } from "node:child_process";
44import { existsSync, readFileSync, statSync } from "node:fs";
55import { access } from "node:fs/promises";
66import module from "node:module";
@@ -84,6 +84,102 @@ const resolvePackagedCompileCacheDirectory = () => {
8484);
8585};
868687+const respawnSignals =
88+process.platform === "win32"
89+ ? ["SIGTERM", "SIGINT", "SIGBREAK"]
90+ : ["SIGTERM", "SIGINT", "SIGHUP", "SIGQUIT"];
91+const respawnSignalExitGraceMs = 1_000;
92+const respawnSignalForceKillGraceMs = 1_000;
93+94+const runRespawnedChild = (command, args, env) => {
95+const child = spawn(command, args, {
96+stdio: "inherit",
97+ env,
98+});
99+const listeners = new Map();
100+// This intentionally overlaps with src/entry.compile-cache.ts; keep the
101+// respawn supervision behavior in sync until the launcher can share TS code.
102+// Give the child a moment to honor forwarded signals, then exit the wrapper so
103+// a child that ignores SIGTERM cannot keep the launcher alive indefinitely.
104+let signalExitTimer = null;
105+let signalForceKillTimer = null;
106+const detach = () => {
107+for (const [signal, listener] of listeners) {
108+process.off(signal, listener);
109+}
110+listeners.clear();
111+if (signalExitTimer) {
112+clearTimeout(signalExitTimer);
113+signalExitTimer = null;
114+}
115+if (signalForceKillTimer) {
116+clearTimeout(signalForceKillTimer);
117+signalForceKillTimer = null;
118+}
119+};
120+const forceKillChild = () => {
121+try {
122+child.kill(process.platform === "win32" ? "SIGTERM" : "SIGKILL");
123+} catch {
124+// Best-effort shutdown fallback.
125+}
126+};
127+const requestChildTermination = () => {
128+try {
129+child.kill("SIGTERM");
130+} catch {
131+// Best-effort shutdown fallback.
132+}
133+signalForceKillTimer = setTimeout(() => {
134+forceKillChild();
135+process.exit(1);
136+}, respawnSignalForceKillGraceMs);
137+signalForceKillTimer.unref?.();
138+};
139+const scheduleParentExit = () => {
140+if (signalExitTimer) {
141+return;
142+}
143+signalExitTimer = setTimeout(() => {
144+requestChildTermination();
145+}, respawnSignalExitGraceMs);
146+signalExitTimer.unref?.();
147+};
148+for (const signal of respawnSignals) {
149+const listener = () => {
150+try {
151+child.kill(signal);
152+} catch {
153+// Best-effort signal forwarding.
154+}
155+scheduleParentExit();
156+};
157+try {
158+process.on(signal, listener);
159+listeners.set(signal, listener);
160+} catch {
161+// Unsupported signal on this platform.
162+}
163+}
164+child.once("exit", (code, signal) => {
165+detach();
166+if (signal) {
167+process.exit(1);
168+}
169+process.exit(code ?? 1);
170+});
171+child.once("error", (error) => {
172+detach();
173+process.stderr.write(
174+`[openclaw] Failed to respawn launcher: ${
175+ error instanceof Error ? (error.stack ?? error.message) : String(error)
176+ }\n`,
177+);
178+process.exit(1);
179+});
180+return true;
181+};
182+87183const respawnWithoutCompileCacheIfNeeded = () => {
88184if (!isSourceCheckoutLauncher()) {
89185return false;
@@ -100,22 +196,13 @@ const respawnWithoutCompileCacheIfNeeded = () => {
100196OPENCLAW_SOURCE_COMPILE_CACHE_RESPAWNED: "1",
101197};
102198delete env.NODE_COMPILE_CACHE;
103-const result = spawnSync(
199+return runRespawnedChild(
104200process.execPath,
105201[...process.execArgv, fileURLToPath(import.meta.url), ...process.argv.slice(2)],
106-{
107-stdio: "inherit",
108- env,
109-},
202+env,
110203);
111-if (result.error) {
112-throw result.error;
113-}
114-process.exit(result.status ?? 1);
115204};
116205117-respawnWithoutCompileCacheIfNeeded();
118-119206const respawnWithPackagedCompileCacheIfNeeded = () => {
120207if (isSourceCheckoutLauncher() || isNodeCompileCacheDisabled()) {
121208return false;
@@ -136,24 +223,23 @@ const respawnWithPackagedCompileCacheIfNeeded = () => {
136223NODE_COMPILE_CACHE: desiredDirectory,
137224OPENCLAW_PACKAGED_COMPILE_CACHE_RESPAWNED: "1",
138225};
139-const result = spawnSync(
226+return runRespawnedChild(
140227process.execPath,
141228[...process.execArgv, fileURLToPath(import.meta.url), ...process.argv.slice(2)],
142-{
143-stdio: "inherit",
144- env,
145-},
229+env,
146230);
147-if (result.error) {
148-throw result.error;
149-}
150-process.exit(result.status ?? 1);
151231};
152232153-respawnWithPackagedCompileCacheIfNeeded();
233+const waitingForCompileCacheRespawn =
234+respawnWithoutCompileCacheIfNeeded() || respawnWithPackagedCompileCacheIfNeeded();
154235155236// https://nodejs.org/api/module.html#module-compile-cache
156-if (module.enableCompileCache && !isNodeCompileCacheDisabled() && !isSourceCheckoutLauncher()) {
237+if (
238+!waitingForCompileCacheRespawn &&
239+module.enableCompileCache &&
240+!isNodeCompileCacheDisabled() &&
241+!isSourceCheckoutLauncher()
242+) {
157243try {
158244module.enableCompileCache(resolvePackagedCompileCacheDirectory());
159245} catch {
@@ -297,17 +383,19 @@ const tryOutputBrowserHelp = () => {
297383return true;
298384};
299385300-if (!isHelpFastPathDisabled() && (await tryOutputBareRootHelp())) {
301-// OK
302-} else if (!isHelpFastPathDisabled() && tryOutputBrowserHelp()) {
303-// OK
304-} else {
305-await installProcessWarningFilter();
306-if (await tryImport("./dist/entry.js")) {
386+if (!waitingForCompileCacheRespawn) {
387+if (!isHelpFastPathDisabled() && (await tryOutputBareRootHelp())) {
307388// OK
308-} else if (await tryImport("./dist/entry.mjs")) {
389+} else if (!isHelpFastPathDisabled() && tryOutputBrowserHelp()) {
309390// OK
310391} else {
311-throw new Error(await buildMissingEntryErrorMessage());
392+await installProcessWarningFilter();
393+if (await tryImport("./dist/entry.js")) {
394+// OK
395+} else if (await tryImport("./dist/entry.mjs")) {
396+// OK
397+} else {
398+throw new Error(await buildMissingEntryErrorMessage());
399+}
312400}
313401}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。