






























@@ -148,6 +148,7 @@ export const COMMAND_STDERR_TAIL_CHARS = 256 * 1024;
148148export const COMMAND_FAILURE_STDOUT_TAIL_CHARS = 64 * 1024;
149149export const COMMAND_TIMEOUT_MS = 30 * 60 * 1000;
150150export const COMMAND_TIMEOUT_KILL_GRACE_MS = 5_000;
151+const COMMAND_PROCESS_TREE_EXIT_POLL_MS = 25;
151152export const REMOTE_SETUP_COMMAND_TIMEOUT_MS = 90 * 60 * 1000;
152153const REMOTE_ROOT = "/tmp/openclaw-telegram-user-crabbox";
153154const CREDENTIAL_SCRIPT = fileURLToPath(new URL("./telegram-user-credential.ts", import.meta.url));
@@ -608,6 +609,44 @@ function commandProcessTreeAlive(child: ChildProcess) {
608609}
609610}
610611612+async function waitForCommandProcessTreeExit(child: ChildProcess, timeoutMs: number) {
613+const deadlineAt = Date.now() + timeoutMs;
614+while (Date.now() < deadlineAt) {
615+if (!commandProcessTreeAlive(child)) {
616+return true;
617+}
618+await new Promise((resolvePoll) => {
619+setTimeout(resolvePoll, COMMAND_PROCESS_TREE_EXIT_POLL_MS);
620+});
621+}
622+return !commandProcessTreeAlive(child);
623+}
624+625+async function finishTimedOutCommandProcessTree(
626+child: ChildProcess,
627+options: {
628+forceKillAt: number | undefined;
629+timeoutKillGraceMs: number;
630+},
631+) {
632+if (!commandProcessTreeAlive(child)) {
633+activeCommandChildren.delete(child);
634+return;
635+}
636+const graceRemainingMs =
637+options.forceKillAt === undefined
638+ ? options.timeoutKillGraceMs
639+ : Math.max(0, options.forceKillAt - Date.now());
640+if (graceRemainingMs > 0) {
641+await waitForCommandProcessTreeExit(child, graceRemainingMs);
642+}
643+if (commandProcessTreeAlive(child)) {
644+signalCommandTree(child, "SIGKILL");
645+await waitForCommandProcessTreeExit(child, options.timeoutKillGraceMs);
646+}
647+activeCommandChildren.delete(child);
648+}
649+611650function untrackCommandChild(child: ChildProcess) {
612651if (!commandProcessTreeAlive(child)) {
613652activeCommandChildren.delete(child);
@@ -664,6 +703,7 @@ export function runCommand(params: {
664703let settled = false;
665704let stdoutLimitError: string | null = null;
666705let timeoutError: Error | null = null;
706+ let forceKillAt: number | undefined;
667707 let killTimer: NodeJS.Timeout | undefined;
668708const timeoutMs = params.timeoutMs ?? COMMAND_TIMEOUT_MS;
669709const timeoutKillGraceMs = params.timeoutKillGraceMs ?? COMMAND_TIMEOUT_KILL_GRACE_MS;
@@ -684,6 +724,7 @@ export function runCommand(params: {
684724 )}`,
685725);
686726signalCommandTree(child, "SIGTERM");
727+forceKillAt = Date.now() + timeoutKillGraceMs;
687728killTimer = setTimeout(() => {
688729signalCommandTree(child, "SIGKILL");
689730}, timeoutKillGraceMs);
@@ -736,9 +777,16 @@ export function runCommand(params: {
736777settled = true;
737778untrackCommandChild(child);
738779if (timeoutError) {
739-signalCommandTree(child, "SIGKILL");
780+const error = timeoutError;
740781clearTimers();
741-reject(timeoutError);
782+void finishTimedOutCommandProcessTree(child, {
783+ forceKillAt,
784+ timeoutKillGraceMs,
785+}).then(
786+() => reject(error),
787+(cleanupError: unknown) =>
788+reject(cleanupError instanceof Error ? cleanupError : new Error(String(cleanupError))),
789+);
742790return;
743791}
744792clearTimers();
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。