openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
440 lines (439 loc) • 17.1 kB
JavaScript
import { n as resolveDiagnosticProcessEnv, r as resolveEnvironmentValue } from "./process-env-DlZFJzq6.mjs";
import { createRequire } from "node:module";
import fs from "node:fs";
import path from "node:path";
import { endianness } from "node:os";
import childProcess, { spawnSync } from "node:child_process";
//#region src/infra/windows-process-start.ts
const DEFAULT_TIMEOUT_MS = 5e3;
const DEFAULT_PROCESS_START_TIMEOUT_MS = 1e4;
const DEFAULT_WINDOWS_SYSTEM_ROOT = "C:\\Windows";
let nativeProcessStartTime;
function loadNativeProcessStartTime() {
const kernel32 = createRequire(import.meta.url)("koffi").load("kernel32.dll");
const openProcess = kernel32.func("void * __stdcall OpenProcess(uint32_t access, int32_t inheritHandle, uint32_t processId)");
const getProcessTimes = kernel32.func("int32_t __stdcall GetProcessTimes(void *process, void *creation, void *exit, void *kernel, void *user)");
const closeHandle = kernel32.func("int32_t __stdcall CloseHandle(void *handle)");
return (pid) => {
const handle = openProcess(4096, 0, pid);
if (handle === null) return null;
try {
const creation = Buffer.alloc(8);
if (!getProcessTimes(handle, creation, Buffer.alloc(8), Buffer.alloc(8), Buffer.alloc(8))) return null;
const ticks = creation.readBigUInt64LE();
if (ticks === 0n) return null;
return Number(ticks / 10000n - 11644473600000n);
} finally {
closeHandle(handle);
}
};
}
function readNativeProcessStartTime(pid) {
if (process.platform !== "win32" || typeof SEALED_RUNTIME_BUILD === "boolean" && SEALED_RUNTIME_BUILD) return null;
try {
nativeProcessStartTime ??= loadNativeProcessStartTime();
return nativeProcessStartTime(pid);
} catch {
return null;
}
}
function windowsSystemRoot(env) {
const configured = resolveEnvironmentValue(env, "SystemRoot", "win32") ?? resolveEnvironmentValue(env, "WINDIR", "win32");
if (!configured) return DEFAULT_WINDOWS_SYSTEM_ROOT;
const normalized = path.win32.normalize(configured);
return /^[A-Za-z]:\\/.test(normalized) && !normalized.startsWith("\\\\") ? normalized : DEFAULT_WINDOWS_SYSTEM_ROOT;
}
function windowsPowerShellPath(env) {
return path.win32.join(windowsSystemRoot(env), "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
}
function windowsWmicPath(env) {
return path.win32.join(windowsSystemRoot(env), "System32", "wbem", "wmic.exe");
}
function decodeWindowsProcessOutput(output) {
if (!Buffer.isBuffer(output)) return output;
return output.length >= 2 && output[0] === 255 && output[1] === 254 ? output.toString("utf16le") : output.toString("utf8");
}
function parseWindowsProcessStartTime(raw) {
const lines = decodeWindowsProcessOutput(raw).split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
const value = lines.find((line) => line.toLowerCase().startsWith("creationdate="))?.slice(13).trim() ?? lines.find((line) => line.toLowerCase() !== "creationdate") ?? "";
const parsedIso = Date.parse(value);
if (Number.isFinite(parsedIso)) return parsedIso;
const dmtf = value.match(/^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})\.(\d{6})([+-])(\d{3})$/);
if (!dmtf) return null;
const [, year, month, day, hour, minute, second, microseconds, offsetSign, offset] = dmtf;
return Date.UTC(Number(year), Number(month) - 1, Number(day), Number(hour), Number(minute), Number(second), Math.floor(Number(microseconds) / 1e3)) - Number(offset) * 6e4 * (offsetSign === "+" ? 1 : -1);
}
/** Read a stable Windows process creation time for lock-owner identity checks. */
function readWindowsProcessStartTimeSync(pid, timeoutMs = DEFAULT_PROCESS_START_TIMEOUT_MS, env = process.env) {
if (!Number.isInteger(pid) || pid <= 0 || pid > 4294967295) return null;
const deadline = Date.now() + timeoutMs;
const nativeStartTime = readNativeProcessStartTime(pid);
if (nativeStartTime !== null) return nativeStartTime;
const powershellBudgetMs = deadline - Date.now();
if (powershellBudgetMs <= 0) return null;
const powershell = spawnSync(windowsPowerShellPath(env), [
"-NoProfile",
"-NonInteractive",
"-Command",
`$process = [System.Diagnostics.Process]::GetProcessById(${pid}); try { [Console]::Out.Write($process.StartTime.ToUniversalTime().ToString("o")) } finally { $process.Dispose() }`
], {
encoding: "utf8",
env: resolveDiagnosticProcessEnv(env, "win32"),
timeout: Math.min(powershellBudgetMs, DEFAULT_TIMEOUT_MS),
windowsHide: true
});
if (!powershell.error && powershell.status === 0) {
const startTime = parseWindowsProcessStartTime(powershell.stdout);
if (startTime !== null) return startTime;
}
const remainingMs = deadline - Date.now();
if (remainingMs <= 0) return null;
const wmic = spawnSync(windowsWmicPath(env), [
"process",
"where",
`ProcessId=${pid}`,
"get",
"CreationDate",
"/value"
], {
env: resolveDiagnosticProcessEnv(env, "win32"),
timeout: remainingMs,
windowsHide: true,
stdio: [
"ignore",
"pipe",
"ignore"
]
});
return !wmic.error && wmic.status === 0 ? parseWindowsProcessStartTime(wmic.stdout) : null;
}
/** Read one process snapshot; a reused parent PID must not extend the ancestry. */
function readWindowsProcessAncestorsSync(pid, maxDepth, timeoutMs, env = process.env) {
try {
const result = spawnSync(windowsPowerShellPath(env), [
"-NoProfile",
"-NonInteractive",
"-Command",
[
"ConvertTo-Json -Compress -InputObject @(Get-CimInstance Win32_Process",
"-Property ProcessId,ParentProcessId,CreationDate -ErrorAction Stop | ForEach-Object {",
"if ($null -ne $_.CreationDate) { [pscustomobject]@{ pid = $_.ProcessId; parentPid = $_.ParentProcessId;",
"startedAt = $_.CreationDate.ToUniversalTime().Ticks.ToString() } } })"
].join(" ")
], {
encoding: "utf8",
env: resolveDiagnosticProcessEnv(env, "win32"),
timeout: timeoutMs,
maxBuffer: 1048576,
windowsHide: true
});
if (result.error || result.status !== 0) return {
pids: [],
complete: false
};
const parsed = JSON.parse(result.stdout);
if (!Array.isArray(parsed)) return {
pids: [],
complete: false
};
const rows = parsed;
const processes = /* @__PURE__ */ new Map();
for (const row of rows) {
if (!row || typeof row !== "object" || !("pid" in row) || typeof row.pid !== "number" || !Number.isSafeInteger(row.pid) || row.pid <= 0 || !("parentPid" in row) || typeof row.parentPid !== "number" || !Number.isSafeInteger(row.parentPid) || row.parentPid < 0 || !("startedAt" in row) || typeof row.startedAt !== "string" || !/^[1-9]\d{0,18}$/.test(row.startedAt)) continue;
if (processes.has(row.pid)) return {
pids: [],
complete: false
};
processes.set(row.pid, {
parentPid: row.parentPid,
startedAt: BigInt(row.startedAt)
});
}
const ancestors = /* @__PURE__ */ new Set();
let current = processes.get(pid);
for (let depth = 0; current && depth < maxDepth; depth++) {
const parentPid = current.parentPid;
const parent = processes.get(parentPid);
if (!parent || parentPid === pid || ancestors.has(parentPid) || parent.startedAt > current.startedAt) break;
ancestors.add(parentPid);
current = parent;
}
return {
pids: [...ancestors],
complete: current?.parentPid === 0
};
} catch {
return {
pids: [],
complete: false
};
}
}
//#endregion
//#region src/shared/freebsd-process-identity-native.ts
/** Normal installations use the dependency's public loader. */
function loadFreeBsdProcessIdentityNative() {
if (typeof SEALED_RUNTIME_BUILD === "boolean" && SEALED_RUNTIME_BUILD) throw new Error("FreeBSD process identity is unavailable in this sealed runtime");
return createRequire(import.meta.url)("koffi/indirect");
}
//#endregion
//#region src/shared/freebsd-process-identity.ts
let native;
function getSysctl() {
if (!native) {
const library = loadFreeBsdProcessIdentityNative().load(null);
native = {
library,
sysctl: library.func("int sysctl(const int *name, unsigned int namelen, _Out_ void *oldp, _Inout_ size_t *oldlenp, const void *newp, size_t newlen)")
};
}
return native.sysctl;
}
function readTimeval(bytes, offset) {
const seconds = bytes.readBigInt64LE(offset);
const microseconds = bytes.readBigInt64LE(offset + 8);
if (seconds < 0n || microseconds < 0n || microseconds >= 1000000n) throw new Error("Invalid FreeBSD process identity timeval");
return seconds * 1000000n + microseconds;
}
/** Read the kernel's monotonic process start time in microseconds. */
function readFreeBsdProcessStartTime(pid) {
if (process.platform !== "freebsd" || process.arch !== "x64" && process.arch !== "arm64" || endianness() !== "LE" || !Number.isInteger(pid) || pid <= 0 || pid > 2147483647) return null;
try {
const sysctl = getSysctl();
const bytes = Buffer.alloc(1120);
for (const [mib, offset, length] of [
[
[1, 21],
0,
16
],
[
[
1,
14,
1,
pid
],
16,
1088
],
[
[1, 21],
1104,
16
]
]) {
const actual = [length];
if (sysctl(mib, mib.length, bytes.subarray(offset, offset + length), actual, null, 0) !== 0 || actual[0] !== length) return null;
}
if (bytes.readInt32LE(16) !== 1088 || bytes.readInt32LE(20) !== 0 || bytes.readInt32LE(88) !== pid || !bytes.subarray(0, 16).equals(bytes.subarray(1104))) return null;
const start = readTimeval(bytes, 352) - readTimeval(bytes, 0);
return start >= 0n && start <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(start) : null;
} catch {
return null;
}
}
//#endregion
//#region src/shared/pid-alive.ts
const PROCESS_START_TIMEOUT_MS = 1e3;
let darwinNative;
function readDarwinNativeIdentity(pid) {
if (process.platform !== "darwin" || process.arch !== "arm64" || pid > 2147483647 || typeof SEALED_RUNTIME_BUILD === "boolean" && SEALED_RUNTIME_BUILD) return null;
try {
if (!darwinNative) {
const library = createRequire(import.meta.url)("koffi").load("/usr/lib/libproc.dylib");
darwinNative = {
library,
query: library.func("int proc_pidinfo(int pid, int flavor, uint64_t arg, _Out_ void *buffer, int buffersize)")
};
}
const bytes = Buffer.alloc(136);
if (darwinNative.query(pid, 3, 0, bytes, bytes.length) !== bytes.length) return null;
const parentPid = bytes.readUInt32LE(16);
const seconds = bytes.readBigUInt64LE(120);
const microseconds = bytes.readBigUInt64LE(128);
if (bytes.readUInt32LE(12) !== pid || parentPid > 2147483647 || seconds === 0n || seconds > BigInt(Number.MAX_SAFE_INTEGER) || microseconds >= 1000000n) return null;
return {
parentPid,
startedAt: Number(seconds),
microseconds: Number(microseconds)
};
} catch {
return null;
}
}
/** Project a best-effort ancestor chain without deciding liveness or authority. */
function collectProcessAncestorPids(immediateParent, readParentPid, throughPid) {
const pids = /* @__PURE__ */ new Set([process.pid]);
if (!Number.isFinite(immediateParent) || immediateParent <= 0) return pids;
pids.add(immediateParent);
let current = immediateParent;
for (let depth = 0; depth < 32 && current !== throughPid; depth++) {
const parent = readParentPid(current);
if (parent == null || parent <= 0 || pids.has(parent)) break;
pids.add(parent);
current = parent;
}
return pids;
}
let selfStartTime = null;
function isValidPid(pid) {
return Number.isInteger(pid) && pid > 0;
}
/**
* Check whether a Linux task is terminal and has no live sibling threads.
* Missing or reaped procfs snapshots require a fresh existence probe.
*/
function isExitedLinuxProcess(pid) {
if (process.platform !== "linux") return false;
try {
const status = fs.readFileSync(`/proc/${pid}/status`, "utf8");
const state = status.match(/^State:\s+(\S)/m)?.[1];
const threads = status.match(/^Threads:[ \t]+(\d+)[ \t]*$/m)?.[1];
if (threads !== "0") return (state === "Z" || state === "X") && threads === "1";
} catch {}
try {
process.kill(pid, 0);
} catch (error) {
return error.code === "ESRCH";
}
return false;
}
/** Returns true only when a positive PID exists and is not a known terminal Linux task. */
function isPidAlive(pid) {
if (!isValidPid(pid)) return false;
try {
process.kill(pid, 0);
} catch (err) {
if (err.code !== "EPERM") return false;
}
return !isExitedLinuxProcess(pid);
}
/** Returns true only when the PID is invalid, missing, or a known terminal Linux task. */
function isPidDefinitelyDead(pid) {
if (!isValidPid(pid)) return true;
try {
process.kill(pid, 0);
} catch (err) {
return err.code === "ESRCH";
}
return isExitedLinuxProcess(pid);
}
function getDarwinProcessStartTime(pid, env, timeoutMs) {
const started = performance.now();
const native = readDarwinNativeIdentity(pid);
if (native) return native.startedAt;
const remainingMs = timeoutMs === void 0 ? PROCESS_START_TIMEOUT_MS : Math.ceil(timeoutMs - (performance.now() - started));
if (remainingMs <= 0) return null;
try {
const startedAt = childProcess.execFileSync("/bin/ps", [
"-o",
"lstart=",
"-p",
String(pid)
], {
encoding: "utf8",
env: {
...resolveDiagnosticProcessEnv(env),
LC_ALL: "C",
TZ: "UTC"
},
stdio: [
"ignore",
"pipe",
"ignore"
],
timeout: remainingMs,
killSignal: "SIGKILL"
}).trim();
const startedAtMs = Date.parse(`${startedAt} UTC`);
return Number.isFinite(startedAtMs) ? Math.floor(startedAtMs / 1e3) : null;
} catch {
return null;
}
}
/** Read one Darwin PID's parent and birth together, without enumerating unrelated processes. */
function readDarwinProcessIdentity(pid, env = process.env, timeoutMs) {
if (process.platform !== "darwin" || !isValidPid(pid)) return null;
const started = performance.now();
const native = readDarwinNativeIdentity(pid);
if (native) return {
parentPid: native.parentPid,
startedAt: native.startedAt
};
const remainingMs = timeoutMs === void 0 ? PROCESS_START_TIMEOUT_MS : Math.ceil(timeoutMs - (performance.now() - started));
if (remainingMs <= 0) return null;
try {
const stdout = childProcess.execFileSync("/bin/ps", [
"-o",
"pid=,ppid=,lstart=",
"-p",
String(pid)
], {
encoding: "utf8",
env: {
...resolveDiagnosticProcessEnv(env),
LC_ALL: "C",
TZ: "UTC"
},
stdio: [
"ignore",
"pipe",
"ignore"
],
timeout: remainingMs,
killSignal: "SIGKILL",
maxBuffer: 4096
});
if (!stdout.endsWith("\n") || /[\r\n]/.test(stdout.slice(0, -1))) return null;
const match = /^[ \t]*(\d+)[ \t]+(\d+)[ \t]+(Sun|Mon|Tue|Wed|Thu|Fri|Sat) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) +(\d{1,2}) (\d{2}:\d{2}:\d{2}) (\d{4})[ \t]*$/.exec(stdout.slice(0, -1));
if (!match || Number(match[1]) !== pid || match[5] === void 0) return null;
const parentPid = Number(match[2]);
const date = `${match[3]}, ${match[5].padStart(2, "0")} ${match[4]} ${match[7]} ${match[6]} GMT`;
const startedAtMs = Date.parse(date);
if (!Number.isSafeInteger(parentPid) || parentPid < 0 || !Number.isFinite(startedAtMs) || new Date(startedAtMs).toUTCString() !== date) return null;
return {
parentPid,
startedAt: Math.floor(startedAtMs / 1e3)
};
} catch {
return null;
}
}
/** Read the Linux procfs start identity used by Linux-owned runtime state. */
function getProcessStartTime(pid) {
if (!isValidPid(pid) || process.platform !== "linux") return null;
try {
const stat = fs.readFileSync(`/proc/${pid}/stat`, "utf8");
const commEndIndex = stat.lastIndexOf(")");
if (commEndIndex < 0) return null;
const fields = stat.slice(commEndIndex + 1).trimStart().split(/\s+/);
const starttime = Number(fields[19]);
return Number.isInteger(starttime) && starttime >= 0 ? starttime : null;
} catch {
return null;
}
}
/** Custody recovery needs native birth precision rather than the shipped lease timestamp format. */
function getProcessInstanceStartTime(pid) {
if (!isValidPid(pid)) return null;
if (process.platform === "linux") {
const startedAt = getProcessStartTime(pid);
return Number.isSafeInteger(startedAt) ? startedAt : null;
}
const native = readDarwinNativeIdentity(pid);
if (!native) return null;
const startedAt = native.startedAt * 1e6 + native.microseconds;
return Number.isSafeInteger(startedAt) ? startedAt : null;
}
/** Read a cross-platform process identity for filesystem lock ownership. */
function getFileLockProcessStartTime(pid, env = process.env, timeoutMs) {
if (!isValidPid(pid)) return null;
const isSelf = pid === process.pid;
if (isSelf && selfStartTime !== null) return selfStartTime;
const startTime = process.platform === "darwin" ? getDarwinProcessStartTime(pid, env, timeoutMs) : process.platform === "win32" ? readWindowsProcessStartTimeSync(pid, timeoutMs, env) : process.platform === "freebsd" ? readFreeBsdProcessStartTime(pid) : getProcessStartTime(pid);
if (isSelf && startTime !== null) selfStartTime = startTime;
return startTime;
}
//#endregion
export { isPidAlive as a, decodeWindowsProcessOutput as c, getProcessStartTime as i, readWindowsProcessAncestorsSync as l, getFileLockProcessStartTime as n, isPidDefinitelyDead as o, getProcessInstanceStartTime as r, readDarwinProcessIdentity as s, collectProcessAncestorPids as t, readWindowsProcessStartTimeSync as u };