openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
961 lines (960 loc) • 40.7 kB
JavaScript
import { F as resolveTimerTimeoutMs, N as resolveOptionalIntegerOption, j as resolveIntegerOption, p as clampPositiveTimerTimeoutMs, u as asPositiveFiniteNumber } from "./number-coercion-CkhMiRw8.mjs";
import { d as toErrorObject } from "./error-coercion-W9BtigRm.mjs";
import { t as createDeferredCore } from "./deferred-D0La5CRk.mjs";
import "./src-BirUqOZp.mjs";
import { t as expectDefined } from "./expect-lbe3Hgrh.mjs";
import { t as killProcessTree } from "./kill-tree-ecObdMeW.mjs";
import { n as getFileLockProcessStartTime } from "./pid-alive-CPS_shDt.mjs";
import { t as sleep } from "./sleep-dMoNLoKC.mjs";
import { t as hasErrnoCode } from "./errno-FR4CRyya.mjs";
import { t as isPromiseLike } from "./promise-like-D7-l5Fsp.mjs";
import { t as BrokerChild } from "./child-CjoOAFNP.mjs";
import { a as isPlainCommandSignalFailure, c as resolveProcessExitCode, i as isPlainCommandExitFailure, n as createSanitizedCommandError, o as readCommandProcessFailure, r as hasCommandProcessCleanupError, s as recordCommandProcessFailure, t as CommandProcessCleanupError } from "./exec-result-DXhDHhYI.mjs";
import { a as getWindowsSystem32ExePath } from "./windows-install-roots-QtLay04M.mjs";
import { i as resolveWindowsConsoleEncoding, n as decodeWindowsOutputBuffer } from "./windows-encoding-_s5Ne1BN.mjs";
import { n as truncateUtf8Suffix } from "./utf8-truncate-_hf7tp13.mjs";
import { t as scheduleAdoptedChildZombieReapAfterExit } from "./scoped-child-reaper-DaAKDMJt.mjs";
import { n as isChildProcessTreeAlive } from "./child-process-tree-Dg8n1gpZ.mjs";
import { a as runOutsideCommandProcessScope, c as spawnCommandWithInvocation, i as retainCommandProcessCleanup, l as waitForCommandProcessGroupExit, r as resolveCommandProcessSignal, s as spawnCommand, t as COMMAND_PROCESS_GROUP_EXIT_CONFIRMATION_MS, u as waitForCommandSpawn } from "./exec-spawn-rD1j0ZHJ.mjs";
import { t as setProcessTimeout } from "./process-deadline-DfRyZVK2.mjs";
import process from "node:process";
import { constants } from "node:os";
import { isUtf8 } from "node:buffer";
import { StringDecoder } from "node:string_decoder";
const EXIT_STDIO_MAX_DRAIN_MS = 1e3;
/** Native and broker exit notifications can precede output EOF. */
function hasChildProcessExited(child) {
return child.exitCode != null || child.signalCode != null;
}
/**
* Execa waits for stdout/stderr after the direct child exits. Bound that wait
* when detached descendants keep inherited pipes open, while still draining
* short output tails. The returned cleanup must run after awaiting the child.
*/
function releaseChildProcessOutputAfterExit(child) {
let idleTimer;
let releaseTimer;
let deadlineTimer;
const cleanup = () => {
clearTimeout(idleTimer);
clearTimeout(releaseTimer);
clearTimeout(deadlineTimer);
child.removeListener("exit", onExit);
child.stdout?.removeListener("data", onData);
child.stderr?.removeListener("data", onData);
};
const release = () => {
cleanup();
child.stdout?.destroy();
child.stderr?.destroy();
};
const scheduleRelease = () => {
releaseTimer ??= setTimeout(release, 0);
releaseTimer.unref();
};
const armIdleTimer = () => {
clearTimeout(idleTimer);
clearTimeout(releaseTimer);
releaseTimer = void 0;
idleTimer = setTimeout(scheduleRelease, 100);
idleTimer.unref();
};
const onData = () => {
if (deadlineTimer) armIdleTimer();
};
const onExit = () => {
deadlineTimer = setTimeout(() => {
deadlineTimer = void 0;
scheduleRelease();
}, EXIT_STDIO_MAX_DRAIN_MS);
deadlineTimer.unref();
armIdleTimer();
};
child.stdout?.on("data", onData);
child.stderr?.on("data", onData);
if (hasChildProcessExited(child)) onExit();
else child.once("exit", onExit);
return cleanup;
}
//#endregion
//#region src/process/exec-output.ts
const DEFAULT_COMMAND_OUTPUT_MAX_BYTES = 16777216;
const MAX_PRESERVED_PENDING_LINE_BYTES = 8192;
function createCapturedOutputBuffers() {
return {
chunks: [],
bytes: 0,
truncatedBytes: 0,
preservedLines: [],
decoder: new StringDecoder("utf8"),
pendingLine: ""
};
}
function resolveMaxOutputBytes(value, stream) {
return resolveIntegerOption(asPositiveFiniteNumber(typeof value === "number" ? value : value?.[stream]), DEFAULT_COMMAND_OUTPUT_MAX_BYTES, { min: 1 });
}
function resolveOutputCapture(value, stream) {
return (typeof value === "string" ? value : value?.[stream]) ?? "tail";
}
function shouldTerminateOnOutput(value, limit) {
return typeof value === "boolean" ? value : value?.[limit] === true;
}
function appendCapturedOutput(capture, chunk, maxBytes, mode) {
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
if (mode === "discard") {
capture.truncatedBytes += buffer.byteLength;
return;
}
if (mode === "head") {
const remaining = Math.max(0, maxBytes - capture.bytes);
if (remaining > 0) {
const kept = buffer.subarray(0, remaining);
capture.chunks.push(kept.byteLength < buffer.byteLength ? Buffer.from(kept) : kept);
capture.bytes += kept.byteLength;
}
capture.truncatedBytes += Math.max(0, buffer.byteLength - remaining);
return;
}
if (buffer.byteLength >= maxBytes) {
capture.chunks = [Buffer.from(buffer.subarray(buffer.byteLength - maxBytes))];
capture.truncatedBytes += capture.bytes + buffer.byteLength - maxBytes;
capture.bytes = maxBytes;
return;
}
capture.chunks.push(buffer);
capture.bytes += buffer.byteLength;
while (capture.bytes > maxBytes && capture.chunks.length > 0) {
const first = expectDefined(capture.chunks[0], "chunks entry at 0");
const overflow = capture.bytes - maxBytes;
if (first.byteLength <= overflow) {
capture.chunks.shift();
capture.bytes -= first.byteLength;
capture.truncatedBytes += first.byteLength;
} else {
capture.chunks[0] = Buffer.from(first.subarray(overflow));
capture.bytes -= overflow;
capture.truncatedBytes += overflow;
}
}
}
function trimTruncatedUtf8Boundary(buffer, mode, truncatedBytes, forceUtf8) {
if (truncatedBytes === 0 || buffer.length === 0 || process.platform === "win32" && !forceUtf8) return buffer;
if (mode === "tail") {
let start = 0;
while (start < buffer.length && (expectDefined(buffer[start], "buffer byte") & 192) === 128) start += 1;
return buffer.subarray(start);
}
for (let removed = 0; removed <= 3 && removed <= buffer.length; removed += 1) {
const end = buffer.length - removed;
if (isUtf8(buffer.subarray(0, end))) return buffer.subarray(0, end);
}
return buffer;
}
function finalizeCapturedOutput(capture, mode, forceUtf8 = false) {
const buffered = Buffer.concat(capture.chunks, capture.bytes);
const trimmed = trimTruncatedUtf8Boundary(buffered, mode, capture.truncatedBytes, forceUtf8);
capture.truncatedBytes += buffered.byteLength - trimmed.byteLength;
return trimmed;
}
function appendPreservedOutputLines(params) {
const { capture, maxPreservedOutputLines, preserveOutputLine } = params;
if (!preserveOutputLine || capture.preservedLines.length >= maxPreservedOutputLines) return;
const text = Buffer.isBuffer(params.chunk) ? capture.decoder.write(params.chunk) : params.chunk;
if (!text) return;
const lines = (capture.pendingLine + text).split(/\r?\n/);
capture.pendingLine = truncateUtf8Suffix(lines.pop() ?? "", params.maxPendingLineBytes);
for (const line of lines) if (capture.preservedLines.length < maxPreservedOutputLines && preserveOutputLine(line, params.stream)) capture.preservedLines.push(line);
}
function flushPreservedOutputLine(params) {
const { capture, maxPreservedOutputLines, preserveOutputLine } = params;
if (!preserveOutputLine || maxPreservedOutputLines <= 0) return;
const trailing = truncateUtf8Suffix(capture.pendingLine + capture.decoder.end(), params.maxPendingLineBytes);
capture.pendingLine = "";
if (trailing && capture.preservedLines.length < maxPreservedOutputLines && preserveOutputLine(trailing, params.stream)) capture.preservedLines.push(trailing);
}
//#endregion
//#region src/process/exec-termination.ts
const WINDOWS_TASKKILL_TIMEOUT_MS = 5e3;
function createCommandTerminationController(params) {
let processTreeSettlement;
let cleanup = "normal";
const readOriginalStart = () => params.processTree && params.child.pid && process.platform !== "win32" ? getFileLockProcessStartTime(params.child.pid) : null;
let awaitingSpawn = params.spawned !== void 0;
let terminateWhenSpawned = false;
let originalStart = awaitingSpawn ? null : readOriginalStart();
let windowsTerminationPromise;
const isDirectChildAlive = () => !params.isChildExited() && params.child.exitCode == null && params.child.signalCode == null;
const spawnTaskkill = async (args) => {
try {
await runOutsideCommandProcessScope(() => spawnCommand([getWindowsSystem32ExePath("taskkill.exe"), ...args], {
baseEnv: params.baseEnv,
env: params.env,
forceKillAfterDelay: 300,
reject: false,
stdio: "ignore",
timeout: WINDOWS_TASKKILL_TIMEOUT_MS
}));
} catch {}
};
const startWindowsTermination = (childPid, graceful) => {
const taskkills = [];
windowsTerminationPromise = (async () => {
if (graceful) {
taskkills.push(spawnTaskkill([
"/PID",
String(childPid),
"/T"
]));
await sleep(params.killGraceMs);
if (isDirectChildAlive()) taskkills.push(spawnTaskkill([
"/PID",
String(childPid),
"/T",
"/F"
]));
} else taskkills.push(spawnTaskkill([
"/PID",
String(childPid),
"/T",
"/F"
]));
await Promise.allSettled(taskkills);
if (!params.isCommandSettled()) params.cancelController.abort();
})();
};
const terminate = () => {
if (awaitingSpawn) {
terminateWhenSpawned = true;
return false;
}
const childPid = params.child.pid;
const directChildAlive = isDirectChildAlive();
if (process.platform === "win32" && !directChildAlive) return false;
if (params.processTree && typeof childPid === "number") {
if (process.platform === "win32") {
startWindowsTermination(childPid, params.processTree.mode !== "force");
return true;
}
const force = params.processTree.mode === "force" || params.killSignal === "SIGKILL" || params.killSignal === constants.signals.SIGKILL;
if (processTreeSettlement) return !force;
const groupAlive = () => isChildProcessTreeAlive({ pid: childPid });
const forceAndObserve = async () => {
const start = getFileLockProcessStartTime(childPid);
if (start !== null && start !== originalStart) {
cleanup = "uncertain";
if (isDirectChildAlive()) params.cancelController.abort();
return;
}
cleanup = "forced";
killProcessTree(childPid, {
force: true,
detached: true
});
scheduleAdoptedChildZombieReapAfterExit(params.child, true);
cleanup = await waitForCommandProcessGroupExit(childPid, () => {
const current = getFileLockProcessStartTime(childPid);
return current === null || current === originalStart;
}) === "exited" ? "forced" : "uncertain";
};
if (!directChildAlive && !groupAlive()) return false;
if (force) {
processTreeSettlement = forceAndObserve();
return false;
}
cleanup = "cooperative";
try {
process.kill(-childPid, params.killSignal ?? "SIGTERM");
} catch (error) {
if (error.code !== "ESRCH") cleanup = "uncertain";
}
scheduleAdoptedChildZombieReapAfterExit(params.child, true, params.killGraceMs + COMMAND_PROCESS_GROUP_EXIT_CONFIRMATION_MS);
processTreeSettlement = new Promise((resolve) => {
const deadline = Date.now() + params.killGraceMs;
const check = () => {
if (!groupAlive()) {
cleanup = "cooperative";
resolve();
return;
}
if (Date.now() < deadline) {
setTimeout(check, Math.min(25, deadline - Date.now()));
return;
}
forceAndObserve().then(resolve);
};
check();
});
return true;
}
if (!directChildAlive) return false;
if (process.platform === "win32" && typeof childPid === "number") {
startWindowsTermination(childPid, false);
return true;
}
return false;
};
const settle = async () => {
if (windowsTerminationPromise) {
await windowsTerminationPromise;
return "forced";
}
await processTreeSettlement;
return cleanup;
};
if (params.spawned) params.spawned.then(() => {
originalStart = readOriginalStart();
awaitingSpawn = false;
if (terminateWhenSpawned && !terminate()) params.cancelController.abort();
}, () => {
awaitingSpawn = false;
});
return {
terminate,
settle
};
}
//#endregion
//#region src/process/exec-runner.ts
const WINDOWS_CLOSE_STATE_SETTLE_TIMEOUT_MS = 250;
const WINDOWS_CLOSE_STATE_POLL_MS = 10;
async function runCommandWithTimeout(argv, optionsOrTimeout) {
return await runCommandWithOutputEncoding(argv, optionsOrTimeout, false);
}
/** Run a command whose stdout and stderr are defined to be UTF-8 on every platform. */
async function runUtf8CommandWithTimeout(argv, optionsOrTimeout) {
return await runCommandWithOutputEncoding(argv, optionsOrTimeout, true);
}
/** Preserve the ordinary process lifecycle while deferring decoding to its consumer. */
async function runCommandBuffersWithTimeout(argv, optionsOrTimeout) {
return await runCommandWithOutputEncoding(argv, optionsOrTimeout, false, true);
}
async function runCommandWithOutputEncoding(argv, optionsOrTimeout, forceUtf8, raw = false) {
const options = typeof optionsOrTimeout === "number" ? { timeoutMs: optionsOrTimeout } : optionsOrTimeout;
const { timeoutMs, cwd, input, stdinFileDescriptor, baseEnv, env, noOutputTimeoutMs, killProcessTree, killSignal, killGraceMs } = options;
const signal = resolveCommandProcessSignal(options.signal);
const resolvedTimeoutMs = typeof timeoutMs === "number" ? resolveTimerTimeoutMs(timeoutMs, 1) : void 0;
if (options.requireProcessTreeExtinction && !killProcessTree) throw new Error("Process-tree extinction requires process-tree ownership");
const hasInput = input !== void 0;
if (stdinFileDescriptor !== void 0) {
if (!Number.isInteger(stdinFileDescriptor) || stdinFileDescriptor < 0) throw new Error("stdinFileDescriptor must be a nonnegative integer");
if (hasInput) throw new Error("Command accepts either input or stdinFileDescriptor, not both");
}
if (options.beforeInput && !hasInput) throw new Error("Child input admission requires explicit input");
const resolvedKillGraceMs = resolveTimerTimeoutMs(killGraceMs, 300, 0);
if (signal?.aborted) {
const interrupted = {
code: null,
signal: null,
killed: false,
termination: "signal",
cleanup: "normal",
noOutputTimedOut: false
};
return raw ? {
...interrupted,
stdout: Buffer.alloc(0),
stderr: Buffer.alloc(0),
windowsEncoding: null
} : {
...interrupted,
stdout: "",
stderr: ""
};
}
const stdoutCapture = createCapturedOutputBuffers();
const stderrCapture = createCapturedOutputBuffers();
const maxStdoutBytes = resolveMaxOutputBytes(options.maxOutputBytes, "stdout");
const maxStderrBytes = resolveMaxOutputBytes(options.maxOutputBytes, "stderr");
const maxCombinedOutputBytes = resolveOptionalIntegerOption(asPositiveFiniteNumber(options.maxCombinedOutputBytes), { min: 1 });
const stdoutCaptureMode = resolveOutputCapture(options.outputCapture, "stdout");
const stderrCaptureMode = resolveOutputCapture(options.outputCapture, "stderr");
if (maxCombinedOutputBytes !== void 0 && stdoutCaptureMode !== stderrCaptureMode) throw new Error("maxCombinedOutputBytes requires matching stdout and stderr capture modes");
const usesCombinedTailCapture = maxCombinedOutputBytes !== void 0 && stdoutCaptureMode === "tail" && stderrCaptureMode === "tail";
const maxPreservedPendingLineBytes = Math.min(Math.max(maxStdoutBytes, maxStderrBytes), MAX_PRESERVED_PENDING_LINE_BYTES);
const maxPreservedOutputLines = Math.max(0, Math.floor(options.maxPreservedOutputLines ?? 16));
const windowsEncoding = forceUtf8 ? null : resolveWindowsConsoleEncoding();
const cancelController = new AbortController();
let termination;
let childExitState;
let commandSettled = false;
let combinedOutputBytes = 0;
let combinedCapturedBytes = 0;
const outputBytesByStream = {
stdout: 0,
stderr: 0
};
const combinedCapturedBytesByStream = {
stdout: 0,
stderr: 0
};
const combinedTailChunks = [];
let noOutputTimer;
let eofGraceTimer;
let outputObserverError;
let outputErrorStream;
let terminatingOutputError;
const { child, invocation } = spawnCommandWithInvocation(argv, {
buffer: false,
cancelSignal: cancelController.signal,
inheritScopeCancellation: false,
cwd,
detached: Boolean(killProcessTree && process.platform !== "win32"),
encoding: "buffer",
executionTimeoutMs: resolvedTimeoutMs,
baseEnv,
env,
forceKillAfterDelay: resolvedKillGraceMs,
killSignal,
...hasInput && !options.beforeInput ? { input } : {},
reject: false,
stdio: [
options.stdinFileDescriptor ?? (hasInput ? "pipe" : "inherit"),
"pipe",
"pipe"
],
stripFinalNewline: false,
windowsVerbatimArguments: options.windowsVerbatimArguments
});
const startupReady = child.pid === void 0 ? waitForCommandSpawn(child) : void 0;
let waitingForSpawn = startupReady !== void 0;
const startupCanceled = createDeferredCore();
const nodeChild = child.nodeChildProcess;
let inputReleased = options.beforeInput ? false : void 0;
const failedProcess = (error, cleanup = "uncertain") => {
const failure = recordCommandProcessFailure(error, {
pid: nodeChild.pid,
code: childExitState?.code ?? nodeChild.exitCode ?? null,
cleanup,
inputReleased,
termination: termination === "output-limit" ? "signal" : termination ?? (nodeChild.signalCode ? "signal" : "exit")
});
return Object.assign(failure, { cleanup });
};
const ownsExitedProcessTree = Boolean(killProcessTree && process.platform !== "win32");
const resolvedNoOutputTimeoutMs = clampPositiveTimerTimeoutMs(noOutputTimeoutMs);
const ownsOutputDeadline = ownsExitedProcessTree && (resolvedTimeoutMs !== void 0 || resolvedNoOutputTimeoutMs !== void 0);
let releaseOutput;
const terminationController = createCommandTerminationController({
child: nodeChild,
spawned: startupReady,
cancelController,
baseEnv,
env,
processTree: killProcessTree ? { mode: "graceful" } : void 0,
isChildExited: () => childExitState !== void 0,
isCommandSettled: () => commandSettled,
killGraceMs: resolvedKillGraceMs,
killSignal
});
const processCleanup = (async () => {
await child.then(() => void 0, () => void 0);
commandSettled = true;
await startupReady?.catch(() => {});
return await terminationController.settle();
})();
retainCommandProcessCleanup(processCleanup);
processCleanup.catch(() => {});
nodeChild.once("exit", (code, signalValue) => {
childExitState = {
code,
signal: signalValue
};
if (!ownsOutputDeadline || code !== 0 || termination) releaseOutput = releaseChildProcessOutputAfterExit(nodeChild);
if (killProcessTree && !termination && (code !== 0 || options.requireProcessTreeExtinction)) terminationController.terminate();
});
const cancel = (reason, eofGraceElapsed = false) => {
if (termination || commandSettled || childExitState && reason !== "output-limit" && (!ownsExitedProcessTree || childExitState.code !== 0)) return;
if ((reason === "timeout" || reason === "no-output-timeout") && (childExitState || hasChildProcessExited(nodeChild))) {
if (!ownsOutputDeadline || (childExitState?.code ?? nodeChild.exitCode) !== 0) return;
if ((!nodeChild.stdout || nodeChild.stdout.readableEnded) && (!nodeChild.stderr || nodeChild.stderr.readableEnded)) return;
if (!eofGraceElapsed) {
eofGraceTimer ??= setProcessTimeout(() => cancel(reason, true), 100);
return;
}
}
eofGraceTimer?.clear();
termination = reason;
if (waitingForSpawn) startupCanceled.resolve(reason);
if (childExitState) releaseOutput ??= releaseChildProcessOutputAfterExit(nodeChild);
if (!terminationController.terminate()) cancelController.abort();
};
const armNoOutputTimer = () => {
if (resolvedNoOutputTimeoutMs === void 0 || commandSettled || termination || childExitState && !ownsExitedProcessTree) return;
if (noOutputTimer) noOutputTimer.refresh();
else noOutputTimer = setProcessTimeout(() => cancel("no-output-timeout"), resolvedNoOutputTimeoutMs);
};
const timeoutTimer = resolvedTimeoutMs === void 0 ? void 0 : setProcessTimeout(() => cancel("timeout"), resolvedTimeoutMs);
const onAbort = () => cancel("signal");
signal?.addEventListener("abort", onAbort, { once: true });
armNoOutputTimer();
const clearTimers = () => {
timeoutTimer?.clear();
noOutputTimer?.clear();
eofGraceTimer?.clear();
noOutputTimer = void 0;
signal?.removeEventListener("abort", onAbort);
};
if (startupReady) {
let interrupted;
try {
interrupted = await Promise.race([startupReady.then(() => void 0), startupCanceled.promise]);
} catch (error) {
clearTimers();
throw failedProcess(error, nodeChild instanceof BrokerChild && nodeChild.notStarted ? "normal" : "uncertain");
}
if (interrupted) {
clearTimers();
processCleanup.finally(() => releaseOutput?.()).catch(() => {});
const stopped = {
pid: nodeChild.pid,
code: interrupted === "timeout" || interrupted === "no-output-timeout" ? 124 : null,
signal: null,
killed: nodeChild.killed,
cleanup: "uncertain",
termination: interrupted === "output-limit" ? "signal" : interrupted,
noOutputTimedOut: interrupted === "no-output-timeout",
outputLimitExceeded: interrupted === "output-limit" || void 0
};
return raw ? {
...stopped,
stdout: Buffer.alloc(0),
stderr: Buffer.alloc(0),
windowsEncoding
} : {
...stopped,
stdout: "",
stderr: ""
};
}
waitingForSpawn = false;
}
const captureOutput = (capture, buffer, maxBytes, stream, captureMode) => {
outputBytesByStream[stream] += buffer.byteLength;
const streamLimitExceeded = outputBytesByStream[stream] > maxBytes;
if (maxCombinedOutputBytes === void 0) {
appendCapturedOutput(capture, buffer, maxBytes, captureMode);
if (streamLimitExceeded && shouldTerminateOnOutput(options.terminateOnOutputLimit, stream)) cancel("output-limit");
return;
}
const combinedBytesBeforeChunk = combinedOutputBytes;
combinedOutputBytes += buffer.byteLength;
const combinedLimitExceeded = combinedOutputBytes > maxCombinedOutputBytes;
if (usesCombinedTailCapture) {
combinedTailChunks.push({
stream,
buffer
});
combinedCapturedBytes += buffer.byteLength;
combinedCapturedBytesByStream[stream] += buffer.byteLength;
const removeCapturedBytes = (index, requestedBytes) => {
const entry = expectDefined(combinedTailChunks[index], "combined tail chunk");
const removedBytes = Math.min(requestedBytes, entry.buffer.byteLength);
if (removedBytes === entry.buffer.byteLength) combinedTailChunks.splice(index, 1);
else entry.buffer = Buffer.from(entry.buffer.subarray(removedBytes));
combinedCapturedBytes -= removedBytes;
combinedCapturedBytesByStream[entry.stream] -= removedBytes;
(entry.stream === "stdout" ? stdoutCapture : stderrCapture).truncatedBytes += removedBytes;
};
while (combinedCapturedBytesByStream[stream] > maxBytes) {
const index = combinedTailChunks.findIndex((entry) => entry.stream === stream);
if (index < 0) break;
removeCapturedBytes(index, combinedCapturedBytesByStream[stream] - maxBytes);
}
let combinedOverflow = combinedCapturedBytes - maxCombinedOutputBytes;
while (combinedOverflow > 0) {
removeCapturedBytes(0, combinedOverflow);
combinedOverflow = combinedCapturedBytes - maxCombinedOutputBytes;
}
} else {
const remaining = Math.max(0, maxCombinedOutputBytes - combinedBytesBeforeChunk);
appendCapturedOutput(capture, buffer, Math.min(maxBytes, capture.bytes + remaining), captureMode);
}
if (combinedLimitExceeded && shouldTerminateOnOutput(options.terminateOnOutputLimit, "combined") || streamLimitExceeded && shouldTerminateOnOutput(options.terminateOnOutputLimit, stream)) cancel("output-limit");
};
const observeOutputChunk = (chunk, stream) => {
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
if (termination || !options.onOutputChunk) return buffer;
try {
if (options.onOutputChunk(buffer, stream) === false) cancel("output-limit");
} catch (error) {
outputObserverError = error;
cancel("output-limit");
}
return buffer;
};
const onOutputError = (error, stream) => {
outputErrorStream ??= stream;
if (termination || options.tolerateOutputError?.[stream] === true || !shouldTerminateOnOutput(options.terminateOnOutputError, stream)) return;
terminatingOutputError = toErrorObject(error, `Command ${stream} stream failed`);
Object.assign(terminatingOutputError, { outputErrorStream: stream });
cancel("signal");
};
child.stdout?.once("error", (error) => onOutputError(error, "stdout"));
child.stderr?.once("error", (error) => onOutputError(error, "stderr"));
const outputStreams = [{
stream: "stdout",
capture: stdoutCapture,
maxBytes: maxStdoutBytes,
mode: stdoutCaptureMode
}, {
stream: "stderr",
capture: stderrCapture,
maxBytes: maxStderrBytes,
mode: stderrCaptureMode
}];
for (const { stream, capture, maxBytes, mode } of outputStreams) child[stream]?.on("data", (chunk) => {
const buffer = observeOutputChunk(chunk, stream);
appendPreservedOutputLines({
capture,
chunk: buffer,
stream,
preserveOutputLine: options.preserveOutputLine,
maxPreservedOutputLines,
maxPendingLineBytes: maxPreservedPendingLineBytes
});
captureOutput(capture, buffer, maxBytes, stream, mode);
armNoOutputTimer();
});
let inputAdmissionError;
if (options.beforeInput) {
nodeChild.stdin?.once("error", (cause) => {
if (hasErrnoCode(cause, "EPIPE")) return;
inputAdmissionError ??= toErrorObject(cause, "Command input failed");
cancel("signal");
});
try {
if (nodeChild.pid === void 0 || !nodeChild.stdin) throw new Error("Child input admission has no spawned process");
const admitted = options.beforeInput(nodeChild.pid, nodeChild.spawnargs);
if (admitted !== void 0) {
if (isPromiseLike(admitted)) Promise.resolve(admitted).catch(() => void 0);
throw new TypeError("Child input admission must complete synchronously");
}
inputReleased = true;
nodeChild.stdin.end(input);
} catch (cause) {
inputAdmissionError = toErrorObject(cause, "Child input admission failed");
cancel("signal");
}
}
const result = await child.finally(() => {
commandSettled = true;
clearTimers();
releaseOutput?.();
}).catch((error) => {
throw failedProcess(error);
});
if (result.timedOut) termination ??= "timeout";
let cleanup = await processCleanup.catch((error) => {
throw failedProcess(error);
});
const resolvedSignal = result.signal ?? childExitState?.signal ?? nodeChild.signalCode ?? null;
if (cleanup === "normal" && resolvedSignal) cleanup = "uncertain";
if (inputAdmissionError) throw failedProcess(inputAdmissionError, cleanup);
if (terminatingOutputError) throw failedProcess(terminatingOutputError, cleanup);
if (outputObserverError !== void 0) throw failedProcess(toErrorObject(outputObserverError, "Command output observer failed"), cleanup);
const isCauseLessWindowsShimResult = !termination && invocation.usesWindowsExitCodeShim && typeof nodeChild.pid === "number" && result.code === void 0 && result.cause === void 0 && !result.timedOut && !result.isCanceled && !result.isMaxBuffer && !result.isTerminated;
if (isCauseLessWindowsShimResult) for (let elapsedMs = 0; elapsedMs < WINDOWS_CLOSE_STATE_SETTLE_TIMEOUT_MS; elapsedMs += WINDOWS_CLOSE_STATE_POLL_MS) {
if (childExitState?.code != null || childExitState?.signal != null || nodeChild.exitCode != null || nodeChild.signalCode != null) break;
await sleep(WINDOWS_CLOSE_STATE_POLL_MS);
}
if (result.failed && !termination && !isPlainCommandExitFailure(result) && !isPlainCommandSignalFailure(result) && !isCauseLessWindowsShimResult && !(result.exitCode === 0 && outputErrorStream !== void 0 && options.tolerateOutputError?.[outputErrorStream] === true)) {
const error = createSanitizedCommandError(result);
const failedCleanup = typeof nodeChild.pid === "number" ? cleanup === "normal" ? "uncertain" : cleanup : "normal";
if (outputErrorStream) Object.assign(error, { outputErrorStream });
throw failedProcess(error, failedCleanup);
}
const killIssuedByAbort = termination === "signal" || termination === "output-limit";
const resolvedCode = resolveProcessExitCode({
explicitCode: result.exitCode ?? childExitState?.code,
childExitCode: nodeChild.exitCode,
resolvedSignal,
usesWindowsExitCodeShim: invocation.usesWindowsExitCodeShim,
timedOut: termination === "timeout",
noOutputTimedOut: termination === "no-output-timeout",
killIssuedByTimeout: termination === "timeout" || termination === "no-output-timeout",
killIssuedByAbort
});
termination ??= resolvedSignal != null || result.isTerminated ? "signal" : "exit";
const normalizedCode = termination === "timeout" || termination === "no-output-timeout" ? resolvedCode == null || resolvedCode === 0 ? 124 : resolvedCode : resolvedCode;
for (const { stream, capture } of outputStreams) flushPreservedOutputLine({
capture,
stream,
preserveOutputLine: options.preserveOutputLine,
maxPreservedOutputLines,
maxPendingLineBytes: maxPreservedPendingLineBytes
});
if (usesCombinedTailCapture) for (const entry of combinedTailChunks) {
const capture = entry.stream === "stdout" ? stdoutCapture : stderrCapture;
capture.chunks.push(entry.buffer);
capture.bytes += entry.buffer.byteLength;
}
const stdout = finalizeCapturedOutput(stdoutCapture, stdoutCaptureMode, forceUtf8);
const stderr = finalizeCapturedOutput(stderrCapture, stderrCaptureMode, forceUtf8);
const settled = {
pid: nodeChild.pid,
stdoutTruncatedBytes: stdoutCapture.truncatedBytes || void 0,
stderrTruncatedBytes: stderrCapture.truncatedBytes || void 0,
preservedStdoutLines: stdoutCapture.preservedLines.length > 0 ? stdoutCapture.preservedLines : void 0,
preservedStderrLines: stderrCapture.preservedLines.length > 0 ? stderrCapture.preservedLines : void 0,
code: normalizedCode,
signal: resolvedSignal,
killed: nodeChild.killed,
killIssuedByAbort: killIssuedByAbort || void 0,
cleanup,
termination: termination === "output-limit" ? "signal" : termination,
noOutputTimedOut: termination === "no-output-timeout",
outputLimitExceeded: termination === "output-limit" || void 0,
...outputErrorStream ? { outputErrorStream } : {}
};
return raw ? {
...settled,
stdout,
stderr,
windowsEncoding
} : {
...settled,
stdout: forceUtf8 ? stdout.toString("utf8") : decodeWindowsOutputBuffer({
buffer: stdout,
windowsEncoding
}),
stderr: forceUtf8 ? stderr.toString("utf8") : decodeWindowsOutputBuffer({
buffer: stderr,
windowsEncoding
})
};
}
//#endregion
//#region src/process/exec.ts
const DEFAULT_EXEC_MAX_BUFFER_BYTES = 1048576;
function decodeExecOutput(buffer) {
return decodeWindowsOutputBuffer({ buffer: Buffer.from(buffer.buffer, buffer.byteOffset, buffer.byteLength) });
}
async function runExec(command, args, opts = 1e4) {
const resolvedOptions = typeof opts === "number" ? { timeoutMs: opts } : opts;
const timeout = typeof resolvedOptions.timeoutMs === "number" ? resolveTimerTimeoutMs(resolvedOptions.timeoutMs, 1) : void 0;
const maxBuffer = resolvedOptions.maxBuffer ?? DEFAULT_EXEC_MAX_BUFFER_BYTES;
if (resolvedOptions.input !== void 0 && resolvedOptions.stdinFileDescriptor !== void 0) throw new Error("runExec accepts either input or stdinFileDescriptor, not both");
let acceptingOutput = true;
let awaitingStartup = true;
let deadlineExpired = false;
let releaseCancellation = () => {};
try {
const subprocess = spawnCommand([command, ...args], {
baseEnv: resolvedOptions.baseEnv,
cancelSignal: resolvedOptions.signal,
cwd: resolvedOptions.cwd,
encoding: "buffer",
executionTimeoutMs: timeout,
env: resolvedOptions.env,
forceKillAfterDelay: 300,
...resolvedOptions.input !== void 0 ? { input: resolvedOptions.input } : {},
maxBuffer,
reject: true,
...resolvedOptions.stdinFileDescriptor === void 0 ? { stdin: resolvedOptions.input === void 0 ? "ignore" : void 0 } : { stdin: resolvedOptions.stdinFileDescriptor },
stripFinalNewline: false
});
const startupCanceled = subprocess.nodeChildProcess instanceof BrokerChild && subprocess.pid === void 0 ? createDeferredCore() : void 0;
const signal = resolveCommandProcessSignal(resolvedOptions.signal);
let cancellationOpen = true;
let deadline;
const stopCommand = (reason) => {
if (!cancellationOpen) return;
releaseCancellation();
if (reason === "timeout") {
if (hasChildProcessExited(subprocess.nodeChildProcess)) return;
deadlineExpired = true;
subprocess.kill();
}
if (!awaitingStartup || !startupCanceled) return;
acceptingOutput = false;
const flags = {
failed: true,
timedOut: reason === "timeout",
isCanceled: reason === "signal",
isMaxBuffer: false,
isTerminated: false
};
const error = createSanitizedCommandError(flags);
startupCanceled.reject(Object.assign(error, flags, {
shortMessage: error.message,
stdout: "",
stderr: "",
cleanup: "uncertain"
}));
};
const onAbort = () => stopCommand("signal");
releaseCancellation = () => {
cancellationOpen = false;
deadline?.clear();
signal?.removeEventListener("abort", onAbort);
};
signal?.addEventListener("abort", onAbort, { once: true });
if (timeout !== void 0) deadline = setProcessTimeout(() => stopCommand("timeout"), timeout);
if (signal?.aborted) onAbort();
const completion = (async () => {
if (subprocess.pid === void 0) await waitForCommandSpawn(subprocess);
awaitingStartup = false;
const releaseOutput = releaseChildProcessOutputAfterExit(subprocess.nodeChildProcess);
let observer = acceptingOutput ? resolvedOptions.onOutputChunk : void 0;
const observe = (chunk, stream) => {
try {
observer?.(chunk, stream);
} catch {
observer = void 0;
}
};
const onStdout = (chunk) => observe(chunk, "stdout");
const onStderr = (chunk) => observe(chunk, "stderr");
if (observer) {
subprocess.nodeChildProcess.stdout?.on("data", onStdout);
subprocess.nodeChildProcess.stderr?.on("data", onStderr);
}
const result = await subprocess.finally(() => {
releaseCancellation();
releaseOutput();
subprocess.nodeChildProcess.stdout?.off("data", onStdout);
subprocess.nodeChildProcess.stderr?.off("data", onStderr);
});
if (deadlineExpired) {
const error = createSanitizedCommandError({ timedOut: true });
throw Object.assign(error, result, {
failed: true,
timedOut: true,
shortMessage: error.message
});
}
const { stdout, stderr } = result;
const decodedStdout = decodeExecOutput(stdout);
const decodedStderr = decodeExecOutput(stderr);
if (acceptingOutput && resolvedOptions.logOutput !== false) {
const [{ shouldLogVerbose }, { logDebug, logError }] = await Promise.all([import("./globals-IXPVChnW.mjs"), import("./logger-BGbiGFDP.mjs")]);
if (shouldLogVerbose()) {
if (decodedStdout.trim()) logDebug(decodedStdout.trim());
if (decodedStderr.trim()) logError(decodedStderr.trim());
}
}
return {
stdout: decodedStdout,
stderr: decodedStderr
};
})();
retainCommandProcessCleanup(completion.then(() => void 0, () => void 0));
return await (startupCanceled ? Promise.race([completion, startupCanceled.promise]) : completion);
} catch (err) {
releaseCancellation();
if (err && typeof err === "object") {
const errorWithOutput = err;
if (deadlineExpired || errorWithOutput.timedOut) {
const message = createSanitizedCommandError({ timedOut: true }).message;
if (err instanceof Error) err.stack = err.stack?.replace(err.message, message);
Object.assign(err, {
failed: true,
timedOut: true,
message,
shortMessage: message
});
}
if (errorWithOutput.code === void 0 && typeof errorWithOutput.exitCode === "number") errorWithOutput.code = errorWithOutput.exitCode;
if (errorWithOutput.stdout instanceof Uint8Array) errorWithOutput.stdout = decodeExecOutput(errorWithOutput.stdout);
if (errorWithOutput.stderr instanceof Uint8Array) errorWithOutput.stderr = decodeExecOutput(errorWithOutput.stderr);
}
if (resolvedOptions.logOutput !== false) {
const logging = await Promise.all([import("./globals-IXPVChnW.mjs"), import("./logger-BGbiGFDP.mjs")]).catch(() => void 0);
if (logging) {
const [{ danger, shouldLogVerbose }, { logError }] = logging;
if (shouldLogVerbose()) logError(danger(`Command failed: ${command}`));
}
}
throw err;
} finally {
acceptingOutput = false;
releaseCancellation();
}
}
/** Run a one-shot command with raw, independently capped stdout and stderr buffers. */
async function runCommandBuffered(argv, options = {}) {
if (options.signal?.aborted) return {
stdout: Buffer.alloc(0),
stderr: Buffer.alloc(0),
code: null,
signal: null,
killed: false,
termination: "signal",
...options.signal.reason instanceof Error ? { error: options.signal.reason } : {}
};
const chunks = {
stdout: [],
stderr: []
};
const capturedBytes = {
stdout: 0,
stderr: 0
};
const maxCombinedOutputBytes = resolveOptionalIntegerOption(asPositiveFiniteNumber(options.maxCombinedOutputBytes), { min: 1 });
let outputLimitStream;
const appendChunk = (chunk, stream) => {
if (options.discardOutput?.[stream]) return true;
const maxBytes = resolveMaxOutputBytes(options.maxOutputBytes, stream);
const combinedBytes = capturedBytes.stdout + capturedBytes.stderr;
const combinedRemaining = maxCombinedOutputBytes === void 0 ? Number.POSITIVE_INFINITY : Math.max(0, maxCombinedOutputBytes - combinedBytes);
const remaining = Math.max(0, Math.min(maxBytes - capturedBytes[stream], combinedRemaining));
if (remaining > 0) {
const captured = Buffer.from(chunk.subarray(0, remaining));
chunks[stream].push(captured);
capturedBytes[stream] += captured.byteLength;
}
if (chunk.byteLength > remaining) {
outputLimitStream ??= stream;
return false;
}
return true;
};
const capturedOutput = (stream) => Buffer.concat(chunks[stream], capturedBytes[stream]);
try {
const result = await runCommandWithTimeout(argv, {
baseEnv: options.baseEnv,
cwd: options.cwd,
env: options.env,
input: options.input,
killProcessTree: options.killProcessTree ?? true,
killGraceMs: options.killGraceMs,
onOutputChunk: appendChunk,
outputCapture: "discard",
signal: options.signal,
timeoutMs: options.timeoutMs,
tolerateOutputError: {
stdout: options.discardOutput?.stdout || options.tolerateOutputError?.stdout,
stderr: options.discardOutput?.stderr || options.tolerateOutputError?.stderr
},
terminateOnOutputError: options.terminateOnOutputError
});
const termination = result.outputLimitExceeded ? "output-limit" : result.termination === "no-output-timeout" ? "timeout" : result.termination;
return {
stdout: capturedOutput("stdout"),
stderr: capturedOutput("stderr"),
code: termination === "exit" ? result.code : null,
signal: result.signal,
killed: result.killed,
termination,
...outputLimitStream ? { outputLimitStream } : {},
...result.outputErrorStream ? { errorStream: result.outputErrorStream } : {},
...result.cleanup === "uncertain" ? { error: new CommandProcessCleanupError() } : {}
};
} catch (error) {
const commandError = error instanceof Error ? error : /* @__PURE__ */ new Error("Command execution failed");
const metadata = commandError;
const errorStream = metadata.outputErrorStream === "stdout" || metadata.outputErrorStream === "stderr" ? metadata.outputErrorStream : void 0;
return {
stdout: capturedOutput("stdout"),
stderr: capturedOutput("stderr"),
code: typeof metadata.exitCode === "number" ? metadata.exitCode : null,
signal: null,
killed: false,
termination: "error",
...errorStream ? { errorStream } : {},
error: readCommandProcessFailure(commandError)?.cleanup === "uncertain" && !hasCommandProcessCleanupError(commandError) ? new CommandProcessCleanupError({ cause: commandError }) : commandError
};
}
}
//#endregion
export { runUtf8CommandWithTimeout as a, createCapturedOutputBuffers as c, runCommandWithTimeout as i, finalizeCapturedOutput as l, runExec as n, createCommandTerminationController as o, runCommandBuffersWithTimeout as r, appendCapturedOutput as s, runCommandBuffered as t, releaseChildProcessOutputAfterExit as u };