openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
758 lines (757 loc) • 27.7 kB
JavaScript
import { a as asOptionalRecord, l as isStringRecord } from "../../record-coerce-DItp3I4t.js";
import { r as resolveEnvironmentValue, t as mergeProcessEnv } from "../../process-env-DlZFJzq6.js";
import { t as getWindowsCmdExePath } from "../../windows-install-roots-BBaNTTOQ.js";
import { t as createWindowsOutputDecoder } from "../../windows-encoding-Cl7_e1FK.js";
import { t as createDeferredCore } from "../../deferred-D0La5CRk.js";
//#region src/process/supervisor/service-child-windows-job-native.ts
const JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 8192;
const PROC_THREAD_ATTRIBUTE_HANDLE_LIST = 131074;
const PROC_THREAD_ATTRIBUTE_JOB_LIST = 131085;
const HANDLE_FLAG_INHERIT = 1;
const GENERIC_READ = 2147483648;
const OPEN_EXISTING = 3;
const FILE_ATTRIBUTE_NORMAL = 128;
function createWindowsJobBindings(koffi) {
if (process.arch !== "x64" && process.arch !== "arm64") throw new Error(`Windows Job command ownership requires x64 or arm64, got ${process.arch}`);
const kernel32 = koffi.load("kernel32.dll");
const HANDLE = koffi.pointer("HANDLE", koffi.opaque());
const VOID_POINTER = koffi.pointer("VOID_POINTER", koffi.opaque());
const SECURITY_ATTRIBUTES = koffi.struct("SECURITY_ATTRIBUTES", {
nLength: "uint32_t",
lpSecurityDescriptor: VOID_POINTER,
bInheritHandle: "int32_t"
});
const BASIC_LIMITS = koffi.struct("JOBOBJECT_BASIC_LIMIT_INFORMATION", {
PerProcessUserTimeLimit: "int64_t",
PerJobUserTimeLimit: "int64_t",
LimitFlags: "uint32_t",
MinimumWorkingSetSize: "uintptr_t",
MaximumWorkingSetSize: "uintptr_t",
ActiveProcessLimit: "uint32_t",
Affinity: "uintptr_t",
PriorityClass: "uint32_t",
SchedulingClass: "uint32_t"
});
const IO_COUNTERS = koffi.struct("IO_COUNTERS", {
ReadOperationCount: "uint64_t",
WriteOperationCount: "uint64_t",
OtherOperationCount: "uint64_t",
ReadTransferCount: "uint64_t",
WriteTransferCount: "uint64_t",
OtherTransferCount: "uint64_t"
});
const EXTENDED_LIMITS = koffi.struct("JOBOBJECT_EXTENDED_LIMIT_INFORMATION", {
BasicLimitInformation: BASIC_LIMITS,
IoInfo: IO_COUNTERS,
ProcessMemoryLimit: "uintptr_t",
JobMemoryLimit: "uintptr_t",
PeakProcessMemoryUsed: "uintptr_t",
PeakJobMemoryUsed: "uintptr_t"
});
const BASIC_ACCOUNTING = koffi.struct("JOBOBJECT_BASIC_ACCOUNTING_INFORMATION", {
TotalUserTime: "int64_t",
TotalKernelTime: "int64_t",
ThisPeriodTotalUserTime: "int64_t",
ThisPeriodTotalKernelTime: "int64_t",
TotalPageFaultCount: "uint32_t",
TotalProcesses: "uint32_t",
ActiveProcesses: "uint32_t",
TotalTerminatedProcesses: "uint32_t"
});
const STARTUPINFO = koffi.struct("STARTUPINFOW", {
cb: "uint32_t",
lpReserved: VOID_POINTER,
lpDesktop: VOID_POINTER,
lpTitle: VOID_POINTER,
dwX: "uint32_t",
dwY: "uint32_t",
dwXSize: "uint32_t",
dwYSize: "uint32_t",
dwXCountChars: "uint32_t",
dwYCountChars: "uint32_t",
dwFillAttribute: "uint32_t",
dwFlags: "uint32_t",
wShowWindow: "uint16_t",
cbReserved2: "uint16_t",
lpReserved2: VOID_POINTER,
hStdInput: HANDLE,
hStdOutput: HANDLE,
hStdError: HANDLE
});
const STARTUPINFOEX = koffi.struct("STARTUPINFOEXW", {
StartupInfo: STARTUPINFO,
lpAttributeList: VOID_POINTER
});
const PROCESS_INFORMATION = koffi.struct("PROCESS_INFORMATION", {
hProcess: HANDLE,
hThread: HANDLE,
dwProcessId: "uint32_t",
dwThreadId: "uint32_t"
});
const getLastErrorCode = kernel32.func("__stdcall", "GetLastError", "uint32_t", []);
const CloseHandle = kernel32.func("__stdcall", "CloseHandle", "int32_t", [HANDLE]);
const CreateJobObjectW = kernel32.func("__stdcall", "CreateJobObjectW", HANDLE, [VOID_POINTER, "str16"]);
const SetExtendedLimits = kernel32.func("__stdcall", "SetInformationJobObject", "int32_t", [
HANDLE,
"int32_t",
koffi.pointer(EXTENDED_LIMITS),
"uint32_t"
]);
const CreatePipe = kernel32.func("__stdcall", "CreatePipe", "int32_t", [
koffi.out(koffi.pointer(HANDLE)),
koffi.out(koffi.pointer(HANDLE)),
koffi.pointer(SECURITY_ATTRIBUTES),
"uint32_t"
]);
const SetHandleInformation = kernel32.func("__stdcall", "SetHandleInformation", "int32_t", [
HANDLE,
"uint32_t",
"uint32_t"
]);
const CreateFileW = kernel32.func("__stdcall", "CreateFileW", HANDLE, [
"str16",
"uint32_t",
"uint32_t",
koffi.pointer(SECURITY_ATTRIBUTES),
"uint32_t",
"uint32_t",
HANDLE
]);
const InitializeProcThreadAttributeList = kernel32.func("__stdcall", "InitializeProcThreadAttributeList", "int32_t", [
VOID_POINTER,
"uint32_t",
"uint32_t",
koffi.inout(koffi.pointer("uintptr_t"))
]);
const UpdateProcThreadAttribute = kernel32.func("__stdcall", "UpdateProcThreadAttribute", "int32_t", [
VOID_POINTER,
"uint32_t",
"uintptr_t",
VOID_POINTER,
"uintptr_t",
VOID_POINTER,
VOID_POINTER
]);
const DeleteProcThreadAttributeList = kernel32.func("__stdcall", "DeleteProcThreadAttributeList", "void", [VOID_POINTER]);
const CreateProcessW = kernel32.func("__stdcall", "CreateProcessW", "int32_t", [
"str16",
koffi.pointer("uint16_t"),
VOID_POINTER,
VOID_POINTER,
"int32_t",
"uint32_t",
VOID_POINTER,
"str16",
koffi.pointer(STARTUPINFOEX),
koffi.out(koffi.pointer(PROCESS_INFORMATION))
]);
const WaitForSingleObject = kernel32.func("__stdcall", "WaitForSingleObject", "uint32_t", [HANDLE, "uint32_t"]);
const GetExitCodeProcess = kernel32.func("__stdcall", "GetExitCodeProcess", "int32_t", [HANDLE, koffi.out(koffi.pointer("uint32_t"))]);
const QueryInformationJobObject = kernel32.func("__stdcall", "QueryInformationJobObject", "int32_t", [
HANDLE,
"int32_t",
koffi.out(koffi.pointer(BASIC_ACCOUNTING)),
"uint32_t",
VOID_POINTER
]);
const PeekNamedPipe = kernel32.func("__stdcall", "PeekNamedPipe", "int32_t", [
HANDLE,
VOID_POINTER,
"uint32_t",
VOID_POINTER,
koffi.out(koffi.pointer("uint32_t")),
VOID_POINTER
]);
const ReadFile = kernel32.func("__stdcall", "ReadFile", "int32_t", [
HANDLE,
koffi.out(koffi.pointer("uint8_t")),
"uint32_t",
koffi.out(koffi.pointer("uint32_t")),
VOID_POINTER
]);
const TerminateJobObject = kernel32.func("__stdcall", "TerminateJobObject", "int32_t", [HANDLE, "uint32_t"]);
const lastError = (operation) => /* @__PURE__ */ new Error(`${operation} failed (Win32 error ${getLastErrorCode()})`);
const requireHandle = (value, operation) => {
if (typeof value !== "bigint" || value === 0n) throw lastError(operation);
return value;
};
const createCommandStdio = () => {
let stdinHandle;
const outputPipes = {
stdout: {},
stderr: {}
};
const securityAttributes = {
nLength: koffi.sizeof(SECURITY_ATTRIBUTES),
lpSecurityDescriptor: null,
bInheritHandle: 1
};
const closeChildHandles = () => {
for (const handle of [
stdinHandle,
outputPipes.stdout.write,
outputPipes.stderr.write
]) if (handle !== void 0) CloseHandle(handle);
stdinHandle = void 0;
delete outputPipes.stdout.write;
delete outputPipes.stderr.write;
};
const closeRawReadHandles = () => {
for (const pipe of Object.values(outputPipes)) {
const handle = pipe.read;
if (handle !== void 0) CloseHandle(handle);
delete pipe.read;
}
};
try {
for (const [streamName, pipe] of Object.entries(outputPipes)) {
const read = [null];
const write = [null];
if (!CreatePipe(read, write, securityAttributes, 0)) throw lastError(`CreatePipe(${streamName})`);
if (typeof write[0] === "bigint" && write[0] !== 0n) pipe.write = write[0];
pipe.read = requireHandle(read[0], `CreatePipe(${streamName} read)`);
pipe.write = requireHandle(write[0], `CreatePipe(${streamName} write)`);
if (!SetHandleInformation(pipe.read, HANDLE_FLAG_INHERIT, 0)) throw lastError(`SetHandleInformation(${streamName} read)`);
}
const openedStdin = CreateFileW("NUL", GENERIC_READ, 3, securityAttributes, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, null);
if (openedStdin === 18446744073709551615n) throw lastError("CreateFileW(NUL)");
const childStdin = requireHandle(openedStdin, "CreateFileW(NUL)");
stdinHandle = childStdin;
const childStdout = outputPipes.stdout.write;
const childStderr = outputPipes.stderr.write;
if (childStdout === void 0 || childStderr === void 0) throw new Error("Windows command output handles were not initialized");
return {
inheritedHandles: [
childStdin,
childStdout,
childStderr
],
stdinHandle: childStdin,
stdoutWriteHandle: childStdout,
stderrWriteHandle: childStderr,
closeChildHandles,
takeOutputReadHandles: () => {
if (outputPipes.stdout.read === void 0 || outputPipes.stderr.read === void 0) throw new Error("Windows command output handles were already transferred");
const output = {
stdoutReadHandle: outputPipes.stdout.read,
stderrReadHandle: outputPipes.stderr.read
};
delete outputPipes.stdout.read;
delete outputPipes.stderr.read;
return output;
},
close: () => {
closeChildHandles();
closeRawReadHandles();
}
};
} catch (error) {
closeChildHandles();
closeRawReadHandles();
throw error;
}
};
const createProcessAttributeList = (handles, job) => {
const size = [0n];
InitializeProcThreadAttributeList(null, 2, 0, size);
const attributeListSize = size[0] ?? 0n;
if (attributeListSize <= 0n) throw lastError("InitializeProcThreadAttributeList(size)");
const attributeList = Buffer.alloc(Number(attributeListSize));
if (!InitializeProcThreadAttributeList(attributeList, 2, 0, size)) throw lastError("InitializeProcThreadAttributeList");
const backingLists = [];
const release = () => {
DeleteProcThreadAttributeList(attributeList);
backingLists.length = 0;
};
try {
for (const { attribute, values, name } of [{
attribute: PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
values: handles,
name: "HANDLE_LIST"
}, {
attribute: PROC_THREAD_ATTRIBUTE_JOB_LIST,
values: [job],
name: "JOB_LIST"
}]) {
const backingList = Buffer.alloc(koffi.sizeof(HANDLE) * values.length);
backingLists.push(backingList);
koffi.encode(backingList, HANDLE, values, values.length);
if (!UpdateProcThreadAttribute(attributeList, 0, attribute, backingList, koffi.sizeof(HANDLE) * values.length, null, null)) throw lastError(`UpdateProcThreadAttribute(${name})`);
}
return {
attributeList,
release
};
} catch (error) {
release();
throw error;
}
};
return {
CreateJobObjectW,
SetExtendedLimits,
CreateProcessW,
WaitForSingleObject,
GetExitCodeProcess,
QueryInformationJobObject,
PeekNamedPipe,
ReadFile,
TerminateJobObject,
CloseHandle,
getLastErrorCode,
lastError,
requireHandle,
createCommandStdio,
createProcessAttributeList,
assertLayouts: () => {
const actual = [
koffi.sizeof(STARTUPINFO),
koffi.sizeof(STARTUPINFOEX),
koffi.sizeof(PROCESS_INFORMATION),
koffi.sizeof(BASIC_LIMITS),
koffi.sizeof(EXTENDED_LIMITS),
koffi.sizeof(BASIC_ACCOUNTING),
koffi.offsetof(BASIC_ACCOUNTING, "ActiveProcesses"),
koffi.sizeof(SECURITY_ATTRIBUTES)
];
const expected = [
104,
112,
24,
64,
144,
48,
40,
24
];
if (actual.some((value, index) => value !== expected[index])) throw new Error(`Koffi Win32 layout mismatch: ${actual.join(",")}`);
},
extendedLimits: { BasicLimitInformation: { LimitFlags: JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE } },
extendedLimitsSize: koffi.sizeof(EXTENDED_LIMITS),
basicAccountingSize: koffi.sizeof(BASIC_ACCOUNTING),
startupInfoExSize: koffi.sizeof(STARTUPINFOEX)
};
}
//#endregion
//#region src/process/supervisor/service-child-windows-job-start.ts
function buildWindowsJobEnvironmentBlock(env) {
const merged = mergeProcessEnv([env], "win32");
for (const [key, value] of Object.entries(merged)) if (key.includes("\0") || value.includes("\0")) throw new Error("owned command environment contains a NUL byte");
const entries = Object.keys(merged).toSorted((left, right) => {
const leftFolded = left.toUpperCase();
const rightFolded = right.toUpperCase();
return leftFolded < rightFolded ? -1 : leftFolded > rightFolded ? 1 : 0;
}).map((key) => `${key}=${merged[key]}`);
return Buffer.from(`${entries.join("\0")}\0\0`, "utf16le");
}
function isWindowsJobServiceStart(value) {
const message = asOptionalRecord(value);
return Boolean(message && message.type === "start" && typeof message.generation === "string" && typeof message.command === "string" && Array.isArray(message.args) && message.args.every((arg) => typeof arg === "string") && (message.argv0 === void 0 || typeof message.argv0 === "string") && (message.cwd === void 0 || typeof message.cwd === "string") && (message.env === void 0 || isStringRecord(message.env)) && (message.stdinMode === "inherit" || message.stdinMode === "pipe-open" || message.stdinMode === "pipe-closed") && (message.secretFd === void 0 || typeof message.secretFd === "number") && (message.controlFd === void 0 || typeof message.controlFd === "number") && (message.windowsShellCommand === void 0 || typeof message.windowsShellCommand === "string"));
}
//#endregion
//#region src/process/supervisor/service-child-windows-job-anchor.ts
const JOB_OBJECT_BASIC_ACCOUNTING_INFORMATION = 1;
const JOB_OBJECT_EXTENDED_LIMIT_INFORMATION = 9;
const STARTF_USESTDHANDLES = 256;
const WAIT_OBJECT_0 = 0;
const WAIT_TIMEOUT = 258;
const WAIT_FAILED = 4294967295;
const ERROR_BROKEN_PIPE = 109;
const IDLE_OBSERVATION_MS = 10;
const OUTPUT_BUFFER_BYTES = 65536;
const OUTPUT_ROUNDS_PER_TURN = 2;
function errorMessage(error) {
return error instanceof Error ? error.message : String(error);
}
function sendProcessMessage(message) {
return new Promise((resolve, reject) => {
if (!process.connected || !process.send) {
reject(/* @__PURE__ */ new Error("Windows Job anchor IPC is closed"));
return;
}
process.send(message, (error) => error ? reject(error) : resolve());
});
}
function runServiceChildWindowsJobAnchor() {
let start;
let state = "starting";
let sequence = 0;
let lastHostSequence = 0;
let outboundQueue = Promise.resolve();
let job;
let processHandle;
let bindings;
let pendingCommandStdio;
let rootObserved = false;
let extinctionProven = false;
let terminationRequested = false;
let closeReason;
let lifecycleTimer;
let lifecycleImmediate;
let lifecycleRunning = false;
let lifecycleRerun = false;
const outputStreams = [];
const outputBuffer = Buffer.allocUnsafe(OUTPUT_BUFFER_BYTES);
const startupErrorAcknowledged = createDeferredCore();
const cleanupFinished = createDeferredCore();
cleanupFinished.promise.catch(() => {});
const send = (payload) => {
if (!start) return Promise.reject(/* @__PURE__ */ new Error("Windows Job anchor has not started"));
sequence += 1;
const message = {
...payload,
generation: start.generation,
sequence
};
const delivery = outboundQueue.then(() => sendProcessMessage(message));
outboundQueue = delivery.catch(() => {});
return delivery;
};
const deliver = async (payload) => {
if (!process.connected) {
if (!closeReason) requestCleanup("parent-lost");
return;
}
try {
await send(payload);
} catch (error) {
if (process.connected) throw error;
if (!closeReason) requestCleanup("parent-lost");
}
};
const stopLifecycle = () => {
clearTimeout(lifecycleTimer);
clearImmediate(lifecycleImmediate);
lifecycleTimer = void 0;
lifecycleImmediate = void 0;
};
const finishAnchor = (exitCode) => {
stopLifecycle();
process.exitCode = exitCode;
if (process.connected) process.disconnect?.();
};
const closeOutputHandle = (stream) => {
const handle = stream.handle;
if (handle === void 0) return;
if (!bindings) throw new Error(`${stream.name} output bindings were not initialized`);
if (!bindings.CloseHandle(handle)) throw bindings.lastError(`CloseHandle(${stream.name} pipe)`);
stream.handle = void 0;
};
const closeNativeHandles = () => {
let closeError;
for (const stream of outputStreams) try {
closeOutputHandle(stream);
} catch (error) {
closeError ??= error instanceof Error ? error : new Error(errorMessage(error));
}
for (const handle of [processHandle, job]) if (handle !== void 0 && bindings && !bindings.CloseHandle(handle)) {
const error = bindings.lastError("CloseHandle");
closeError ??= error;
}
processHandle = void 0;
job = void 0;
if (closeError) throw closeError;
};
const closeAuthority = async (reason) => {
if (state === "closed") return;
state = "closed";
stopLifecycle();
try {
if (process.connected) await send({
type: "closing",
reason
});
closeNativeHandles();
cleanupFinished.resolve();
finishAnchor(0);
} catch (error) {
try {
closeNativeHandles();
} catch {}
cleanupFinished.reject(error);
finishAnchor(1);
}
};
const failAuthority = async (error) => {
if (state === "closed") return;
state = "closed";
stopLifecycle();
try {
if (process.connected && start) await send({
type: "result-error",
error: errorMessage(error)
}).catch(() => {});
closeNativeHandles();
} catch {} finally {
cleanupFinished.reject(error);
finishAnchor(1);
}
};
const finishOutput = async (stream) => {
if (stream.ended) return;
closeOutputHandle(stream);
stream.ended = true;
const tail = stream.decoder?.flush();
if (tail) await deliver({
type: "output",
stream: stream.name,
chunk: tail
});
await deliver({
type: "output-end",
stream: stream.name
});
};
const observeOutput = async (stream) => {
if (stream.ended) return false;
if (!bindings || stream.handle === void 0 || !stream.decoder) throw new Error(`${stream.name} output ownership was not initialized`);
const available = [0];
if (!bindings.PeekNamedPipe(stream.handle, null, 0, null, available, null)) {
const errorCode = bindings.getLastErrorCode();
if (errorCode !== ERROR_BROKEN_PIPE) throw new Error(`PeekNamedPipe(${stream.name}) failed (Win32 error ${errorCode})`);
await finishOutput(stream);
return true;
}
const availableBytes = available[0];
if (typeof availableBytes !== "number" || !Number.isSafeInteger(availableBytes) || availableBytes < 0) throw new Error(`PeekNamedPipe(${stream.name}) returned an invalid byte count`);
if (availableBytes === 0) return false;
const requestedBytes = Math.min(availableBytes, outputBuffer.length);
const bytesRead = [0];
if (!bindings.ReadFile(stream.handle, outputBuffer, requestedBytes, bytesRead, null)) {
const errorCode = bindings.getLastErrorCode();
if (errorCode !== ERROR_BROKEN_PIPE) throw new Error(`ReadFile(${stream.name}) failed (Win32 error ${errorCode})`);
await finishOutput(stream);
return true;
}
const count = bytesRead[0];
if (typeof count !== "number" || !Number.isSafeInteger(count) || count < 0 || count > requestedBytes) throw new Error(`ReadFile(${stream.name}) returned an invalid byte count`);
if (count === 0) throw new Error(`ReadFile(${stream.name}) returned no available bytes`);
const text = stream.decoder.decode(outputBuffer.subarray(0, count));
if (text) await deliver({
type: "output",
stream: stream.name,
chunk: text
});
return true;
};
const observeRoot = async () => {
if (rootObserved) return false;
if (!bindings || !processHandle) throw new Error("Windows root process ownership was not initialized");
const waitResult = bindings.WaitForSingleObject(processHandle, 0);
if (waitResult === WAIT_TIMEOUT) return false;
if (waitResult === WAIT_FAILED) throw bindings.lastError("WaitForSingleObject(root)");
if (waitResult !== WAIT_OBJECT_0) throw new Error(`WaitForSingleObject(root) returned unexpected result ${waitResult}`);
const exitCode = [0];
if (!bindings.GetExitCodeProcess(processHandle, exitCode)) throw bindings.lastError("GetExitCodeProcess");
const code = exitCode[0];
if (typeof code !== "number" || !Number.isSafeInteger(code) || code < 0 || code > 4294967295) throw new Error("GetExitCodeProcess returned an invalid exit code");
rootObserved = true;
await deliver({
type: "root-result",
code,
signal: null
});
return true;
};
const observeJob = () => {
if (extinctionProven) return false;
if (!bindings || !job) throw new Error("Windows Job ownership was not initialized");
const accounting = {};
if (!bindings.QueryInformationJobObject(job, JOB_OBJECT_BASIC_ACCOUNTING_INFORMATION, accounting, bindings.basicAccountingSize, null)) throw bindings.lastError("QueryInformationJobObject");
const count = accounting.ActiveProcesses;
if (typeof count !== "number" || !Number.isSafeInteger(count) || count < 0) throw new Error("QueryInformationJobObject returned an invalid active process count");
extinctionProven = count === 0;
return extinctionProven;
};
const runLifecycle = async () => {
if (lifecycleRunning || state === "closed" || !processHandle) {
lifecycleRerun ||= lifecycleRunning;
return;
}
lifecycleRunning = true;
let advanced = false;
try {
advanced = await observeRoot() || advanced;
advanced = rootObserved && observeJob() || advanced;
for (let round = 0; round < OUTPUT_ROUNDS_PER_TURN; round += 1) {
let outputAdvanced = false;
for (const stream of outputStreams) outputAdvanced = await observeOutput(stream) || outputAdvanced;
advanced ||= outputAdvanced;
if (!outputAdvanced) break;
}
if (extinctionProven && rootObserved && outputStreams.every((stream) => stream.ended)) {
await closeAuthority(closeReason ?? "lineage-closed");
return;
}
} catch (error) {
await failAuthority(error);
return;
} finally {
lifecycleRunning = false;
}
const immediate = advanced || lifecycleRerun;
lifecycleRerun = false;
scheduleLifecycle(immediate);
};
const scheduleLifecycle = (immediate) => {
if (state === "closed" || !processHandle) return;
if (lifecycleRunning) {
lifecycleRerun ||= immediate;
return;
}
if (immediate && lifecycleTimer) {
clearTimeout(lifecycleTimer);
lifecycleTimer = void 0;
}
if (lifecycleTimer || lifecycleImmediate) return;
if (immediate) {
lifecycleImmediate = setImmediate(() => {
lifecycleImmediate = void 0;
runLifecycle();
});
return;
}
lifecycleTimer = setTimeout(() => {
lifecycleTimer = void 0;
runLifecycle();
}, IDLE_OBSERVATION_MS);
if (state === "active" && process.connected) lifecycleTimer.unref();
};
const requestCleanup = (reason) => {
if (state === "closed") return cleanupFinished.promise;
closeReason ??= reason;
state = "closing";
if (!processHandle) {
closeAuthority(reason);
return cleanupFinished.promise;
}
if (!terminationRequested && !extinctionProven) {
terminationRequested = true;
if (!bindings || !job) {
failAuthority(/* @__PURE__ */ new Error("Windows Job cleanup authority was not initialized"));
return cleanupFinished.promise;
}
if (!bindings.TerminateJobObject(job, 1)) {
const error = bindings.lastError("TerminateJobObject");
failAuthority(error);
return cleanupFinished.promise;
}
}
scheduleLifecycle(true);
return cleanupFinished.promise;
};
const reportStartupError = async (error) => {
if (!process.connected) return;
await send({
type: "startup-error",
error: errorMessage(error)
});
await startupErrorAcknowledged.promise;
};
const startCommand = async (next) => {
start = next;
if (typeof next.windowsShellCommand !== "string") {
state = "closed";
finishAnchor(1);
return;
}
try {
const koffi = (await import("koffi")).default;
if (state !== "starting") return;
bindings = createWindowsJobBindings(koffi);
bindings.assertLayouts();
job = bindings.requireHandle(bindings.CreateJobObjectW(null, null), "CreateJobObjectW");
if (!bindings.SetExtendedLimits(job, JOB_OBJECT_EXTENDED_LIMIT_INFORMATION, bindings.extendedLimits, bindings.extendedLimitsSize)) throw bindings.lastError("SetInformationJobObject(KILL_ON_JOB_CLOSE)");
const commandStdio = bindings.createCommandStdio();
pendingCommandStdio = commandStdio;
let processAttributes;
const processInfo = {};
try {
processAttributes = bindings.createProcessAttributeList(commandStdio.inheritedHandles, job);
const shell = resolveEnvironmentValue(next.env, "COMSPEC", "win32") || getWindowsCmdExePath(next.env);
const commandLine = Buffer.from(`"${shell}" /d /s /c "${next.windowsShellCommand}"\0`, "utf16le");
if (!bindings.CreateProcessW(shell, commandLine, null, null, 1, 525824, next.env === void 0 ? null : buildWindowsJobEnvironmentBlock(next.env), next.cwd ?? null, {
StartupInfo: {
cb: bindings.startupInfoExSize,
dwFlags: STARTF_USESTDHANDLES,
hStdInput: commandStdio.stdinHandle,
hStdOutput: commandStdio.stdoutWriteHandle,
hStdError: commandStdio.stderrWriteHandle
},
lpAttributeList: processAttributes.attributeList
}, processInfo)) throw bindings.lastError("CreateProcessW(JOB_LIST)");
processHandle = bindings.requireHandle(processInfo.hProcess, "CreateProcessW process");
const threadHandle = bindings.requireHandle(processInfo.hThread, "CreateProcessW thread");
if (!bindings.CloseHandle(threadHandle)) throw bindings.lastError("CloseHandle(command thread)");
} finally {
processAttributes?.release();
pendingCommandStdio?.closeChildHandles();
}
const commandPid = Number(processInfo.dwProcessId);
const handles = commandStdio.takeOutputReadHandles();
outputStreams.push({
name: "stdout",
handle: handles.stdoutReadHandle,
ended: false
}, {
name: "stderr",
handle: handles.stderrReadHandle,
ended: false
});
for (const stream of outputStreams) stream.decoder = createWindowsOutputDecoder();
commandStdio.close();
pendingCommandStdio = void 0;
state = "active";
const readyDelivery = send({
type: "ready",
commandPid,
anchorPid: process.pid
});
scheduleLifecycle(true);
await readyDelivery;
} catch (error) {
if (state === "closed") return;
if (state === "closing") {
await cleanupFinished.promise.catch(() => {});
return;
}
if (processHandle && outputStreams.some((stream) => !stream.decoder)) for (const stream of outputStreams) {
closeOutputHandle(stream);
stream.ended = true;
}
await reportStartupError(error);
await (processHandle ? requestCleanup("lineage-lost") : closeAuthority("lineage-lost"));
} finally {
pendingCommandStdio?.close();
pendingCommandStdio = void 0;
}
};
process.once("disconnect", () => {
startupErrorAcknowledged.resolve();
if (!start) {
state = "closed";
process.exitCode = 1;
return;
}
requestCleanup("parent-lost");
});
process.once("SIGTERM", () => void requestCleanup("parent-lost"));
process.once("SIGINT", () => void requestCleanup("parent-lost"));
process.on("message", (raw) => {
if (isWindowsJobServiceStart(raw) && start === void 0 && state === "starting") {
startCommand(raw);
return;
}
const message = asOptionalRecord(raw);
if (!start || state === "closed" || !message || message.type !== "cancel" && message.type !== "startup-error-ack" || typeof message.generation !== "string" || typeof message.sequence !== "number" || message.generation !== start.generation || message.sequence <= lastHostSequence) {
if (start && state !== "closed") requestCleanup("lineage-lost");
return;
}
lastHostSequence = message.sequence;
if (message.type === "startup-error-ack") startupErrorAcknowledged.resolve();
else requestCleanup("cancel");
});
}
runServiceChildWindowsJobAnchor();
//#endregion
export { runServiceChildWindowsJobAnchor };