openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
269 lines (268 loc) • 15.7 kB
JavaScript
import { r as resolveRuntimeScriptPosition, t as isBunRuntime } from "./runtime-binary-BczyKE0p.mjs";
import { o as getRootOptionAwareCommandPath } from "./cli-root-options-DGcy1qRg.mjs";
import { o as isPidDefinitelyDead } from "./pid-alive-CPS_shDt.mjs";
import { t as escapeRegExp } from "./regexp-BZyMFTlj.mjs";
import { n as runtimeProcessEntrypoints } from "./runtime-process-entrypoints-De68tE_W.mjs";
import { t as isContainerEnvironment } from "./container-environment-e2wGNmXf.mjs";
import { t as isRosettaTranslatedProcess } from "./rosetta-translation-DvPrY0r4.mjs";
import { t as readDarwinProcessCommand } from "./darwin-process-command-D1hawpX3.mjs";
import { a as readProcessWorkingDirectories, i as readProcessPackageIdentity, n as classifyOpenClawEntrypointPath, o as referencesRetainedArtifact, t as classifyOpenClawArgv } from "./gateway-process-argv-DbHTzJ4P.mjs";
import { t as parseWindowsNativeCommandLine } from "./windows-command-line-BrVAurm9.mjs";
import { r as readProcessGroupMembers } from "./service-child-group-ownership-B2ab04H6.mjs";
import { createRequire } from "node:module";
import { fileURLToPath, pathToFileURL } from "node:url";
import path from "node:path";
//#region src/infra/windows-process-census.ts
let native;
function loadNative() {
if (process.arch !== "x64" && process.arch !== "arm64") throw new Error("Windows process census requires a 64-bit runtime.");
const koffi = createRequire(import.meta.url)("koffi");
const kernel = koffi.load("kernel32.dll");
const security = koffi.load("advapi32.dll");
const nt = koffi.load("ntdll.dll");
const enumerate = kernel.func("int32_t __stdcall K32EnumProcesses(void *, uint32_t, void *)");
const open = kernel.func("void * __stdcall OpenProcess(uint32_t, int32_t, uint32_t)");
const close = kernel.func("int32_t __stdcall CloseHandle(void *)");
const error = kernel.func("uint32_t __stdcall GetLastError()");
const exitCode = kernel.func("int32_t __stdcall GetExitCodeProcess(void *, void *)");
const times = kernel.func("int32_t __stdcall GetProcessTimes(void *, void *, void *, void *, void *)");
const read = kernel.func("int32_t __stdcall ReadProcessMemory(void *, uintptr_t, void *, size_t, void *)");
const query = nt.func("int32_t __stdcall NtQueryInformationProcess(void *, uint32_t, void *, uint32_t, void *)");
const equalSid = security.func("int32_t __stdcall EqualSid(void *, void *)");
const sessions = koffi.load("wtsapi32.dll");
const owners = sessions.func("int32_t __stdcall WTSEnumerateProcessesW(void *, uint32_t, uint32_t, void *, void *)");
const free = sessions.func("void __stdcall WTSFreeMemory(void *)");
const memory = (handle, address, length) => {
const bytes = Buffer.alloc(length);
const returned = Buffer.alloc(8);
if (!address || !read(handle, address, bytes, length, returned) || returned.readBigUInt64LE() !== BigInt(length)) throw new Error("Process memory is unreadable.");
return bytes;
};
const parameters = (handle, observation) => {
const basic = Buffer.alloc(48);
const wow64 = Buffer.alloc(8);
const returned = Buffer.alloc(4);
if (query(handle, 0, basic, basic.length, returned) < 0 || returned.readUInt32LE() !== basic.length || basic.readBigUInt64LE(32) !== BigInt(observation.pid) || query(handle, 26, wow64, wow64.length, returned) < 0 || returned.readUInt32LE() !== wow64.length) throw new Error("Process identity or architecture is unavailable.");
const narrow = wow64.readBigUInt64LE() !== 0n;
const pointer = (bytes, offset) => narrow ? BigInt(bytes.readUInt32LE(offset)) : bytes.readBigUInt64LE(offset);
const peb = narrow ? wow64.readBigUInt64LE() : basic.readBigUInt64LE(8);
const address = pointer(memory(handle, peb, narrow ? 20 : 40), narrow ? 16 : 32);
const header = memory(handle, address, narrow ? 72 : 128);
const string = (offset) => {
try {
const length = header.readUInt16LE(offset);
if (!length || length % 2 || length > header.readUInt16LE(offset + 2)) return;
const value = pointer(header, offset + (narrow ? 4 : 8));
const location = header.readUInt32LE(8) & 1 ? value : address + value;
return memory(handle, location, length).toString("utf16le");
} catch {
return;
}
};
observation.parentPid = Number(basic.readBigUInt64LE(40));
observation.commandLine = string(narrow ? 64 : 112);
observation.cwd = string(narrow ? 36 : 56);
};
return (deadline) => {
const bytes = Buffer.alloc(262144);
const returned = Buffer.alloc(4);
if (!enumerate(bytes, bytes.length, returned) || returned.readUInt32LE() >= bytes.length || returned.readUInt32LE() % 4) throw new Error("Windows process enumeration is incomplete.");
const pids = new Uint32Array(bytes.buffer, bytes.byteOffset, returned.readUInt32LE() / 4);
const snapshot = new Map(Array.from(pids, (pid) => [pid, 0n]));
const allocation = Buffer.alloc(8);
try {
try {
if (owners(null, 0, 1, allocation, returned) && returned.readUInt32LE() <= 65536) {
const records = Buffer.from(koffi.decode(allocation.readBigUInt64LE(), "uint8_t", returned.readUInt32LE() * 24));
for (let offset = 0; offset < records.length; offset += 24) snapshot.set(records.readUInt32LE(offset + 4), records.readBigUInt64LE(offset + 16));
}
} catch {
snapshot.forEach((_sid, pid) => snapshot.set(pid, 0n));
}
const self = snapshot.get(process.pid);
const observations = Array.from(snapshot, ([pid, owner]) => {
if (Date.now() >= deadline) throw new Error("Windows process census exceeded its deadline.");
if (pid === 0 || pid === 4) return;
const observation = { pid };
if (self && owner && !equalSid(self, owner)) observation.foreignOwner = true;
const handle = open(1040, 0, pid) ?? (error() === 87 ? null : open(4096, 0, pid));
if (!handle) return error() === 87 ? void 0 : observation;
try {
const state = Buffer.alloc(4);
const known = exitCode(handle, state);
if (!known || state.readUInt32LE() !== 259) return known ? void 0 : observation;
const created = Buffer.alloc(8);
if (times(handle, created, Buffer.alloc(8), Buffer.alloc(8), Buffer.alloc(8)) && created.readBigUInt64LE()) observation.startIdentity = String(Number(created.readBigUInt64LE() / 10000n - 11644473600000n));
try {
parameters(handle, observation);
} catch {}
} finally {
close(handle);
}
return observation;
});
if (Date.now() >= deadline) throw new Error("Windows process census exceeded its deadline.");
return observations.filter((observation) => observation !== void 0);
} finally {
if (allocation.readBigUInt64LE()) free(allocation.readBigUInt64LE());
}
};
}
/** Missing fields never establish absence for same-user or unknown-owner work. */
function readWindowsProcessCensus(timeoutMs) {
const deadline = Date.now() + timeoutMs;
return (native ??= loadNative())(deadline);
}
//#endregion
//#region src/infra/openclaw-process-census.ts
const workerEntrypoints = Object.values(runtimeProcessEntrypoints).flatMap((entry) => [path.posix.normalize(`src/infra/${entry.sourceWorkerName}.ts`), `dist/${entry.distWorkerPath}`]);
function isStandardSystemdUserManager(command) {
return command !== void 0 && "argv" in command && command.argv.length === 2 && command.argv[1] === "--user" && (command.argv[0] === "/usr/lib/systemd/systemd" || command.argv[0] === "/lib/systemd/systemd");
}
function isStandardSystemdLoginProcess(command, parentCommand, inspectorUid) {
if (process.platform !== "linux" || inspectorUid === void 0 || command?.uid !== inspectorUid || !("argv" in command)) return false;
return isStandardSystemdUserManager(command) || command.argv.length === 1 && command.argv[0] === "(sd-pam)" && parentCommand?.uid === command.uid && isStandardSystemdUserManager(parentCommand);
}
function classifyProcessArtifactCustody(command, pid, cwd, parentCommand) {
try {
const argv = command && "argv" in command ? command.argv : [];
if ([...argv, cwd ?? ""].some(referencesRetainedArtifact)) return { kind: "holder" };
if (!command) throw new Error("process command is unavailable");
if ("argvUnavailable" in command || command.argv.length === 0) return { kind: "non-holder" };
const { serviceMarker } = command;
const options = {
pid,
cwd: cwd ?? "",
serviceMarker,
additionalEntrypoints: workerEntrypoints,
inspectPackage: true
};
const identity = classifyOpenClawArgv(argv, options);
if (identity.kind === "openclaw") return { kind: "holder" };
if (identity.kind === "unclassified" && identity.cause !== "runtime-syntax") throw new Error(identity.reason);
const { position, operands } = resolveRuntimeScriptPosition(argv);
if (!cwd || !path.isAbsolute(cwd)) {
const inspectorUid = process.getuid?.();
if (cwd === void 0 && identity.kind === "other" && typeof position !== "number" && position.kind === "not-runtime" && command.uid !== void 0 && inspectorUid !== void 0 && (command.uid !== inspectorUid || isStandardSystemdLoginProcess(command, parentCommand, inspectorUid))) return { kind: "non-holder" };
throw new Error("working directory is unavailable");
}
if (typeof position !== "number" && position.kind === "not-runtime") return { kind: "non-holder" };
let scripts = {};
if (typeof position !== "number") {
const pkg = readProcessPackageIdentity(cwd, true);
if (pkg.name === "openclaw") return { kind: "holder" };
scripts = pkg.scripts;
}
for (const { value, module } of operands) {
let candidate = value;
if (module) {
if (value.startsWith("file:")) candidate = fileURLToPath(value);
else if (!path.isAbsolute(value) && !value.startsWith("./") && !value.startsWith("../")) throw new Error("runtime module package identity is unavailable");
} else if (isBunRuntime(argv[0] ?? "") && !/[\\/]/u.test(value) && !path.extname(value) && typeof scripts[value] === "string" && scripts[value].trim()) continue;
const evidence = classifyOpenClawEntrypointPath(candidate, options);
if (evidence.kind === "unclassified") throw new Error(evidence.reason);
if (evidence.kind === "openclaw") return { kind: "holder" };
}
return { kind: "non-holder" };
} catch (error) {
return {
kind: "unresolved",
reason: error instanceof Error ? error.message : String(error)
};
}
}
function inspectOtherOpenClawProcesses(handoff) {
const result = {
matchingPids: [],
unverifiedPids: []
};
try {
if (process.platform === "linux" && isContainerEnvironment()) throw new Error("Host process visibility cannot be established from this container. Run Doctor on the host after stopping OpenClaw containers that share its temporary directory.");
const windows = process.platform === "win32";
const deadline = Date.now() + (handoff ? 15e3 : 1e3);
const native = windows && handoff ? [...readWindowsProcessCensus(15e3)] : void 0;
const nativeByPid = new Map(native?.map((entry) => [entry.pid, entry]));
const processes = native?.map(({ pid, parentPid, commandLine }) => {
const argv = commandLine ? parseWindowsNativeCommandLine(commandLine) : null;
return {
pid,
state: "",
command: argv?.length && parentPid !== void 0 ? {
ppid: parentPid,
argv
} : void 0
};
}) ?? [...readProcessGroupMembers(handoff ? 15e3 : 1e3, { readDarwinCommand: readDarwinProcessCommand })];
const byPid = new Map(processes.map((entry) => [entry.pid, entry]));
const current = byPid.get(process.pid);
if (!current || !handoff && processes.some((entry) => !entry.command || process.platform === "linux" && !("argv" in entry.command))) throw new Error("OpenClaw process census is incomplete.");
const directories = native ? new Map(native.map(({ pid, cwd }) => [pid, cwd])) : readProcessWorkingDirectories(processes.map(({ pid }) => pid));
const launchers = /* @__PURE__ */ new Set();
const ancestors = /* @__PURE__ */ new Set([process.pid]);
let child = current;
let parentPid = current.command?.ppid ?? 0;
while (parentPid > 0) {
const parent = byPid.get(parentPid);
if (!parent?.command || ancestors.has(parentPid)) {
if (handoff) break;
throw new Error("OpenClaw process ancestry is incomplete.");
}
const parentStart = nativeByPid.get(parentPid)?.startIdentity;
const childStart = nativeByPid.get(child.pid)?.startIdentity;
if (windows && (!parentStart || !childStart || BigInt(parentStart) >= BigInt(childStart))) break;
ancestors.add(parentPid);
if ("argv" in parent.command) {
const { argv, serviceMarker } = parent.command;
const identity = classifyOpenClawArgv(argv, {
pid: parentPid,
serviceMarker,
cwd: directories.get(parentPid) ?? "",
additionalEntrypoints: workerEntrypoints
});
if (identity.kind === "openclaw" && identity.entryIndex !== void 0 && getRootOptionAwareCommandPath(["node", ...argv.slice(identity.entryIndex)], 1)[0] === (handoff ? "update" : "doctor")) launchers.add(parentPid);
}
child = parent;
parentPid = parent.command.ppid;
}
const normalize = (value) => windows ? value.replaceAll("\\", "/").replace(/\/{2,}\?\/UNC\//gi, "//").replace(/\/{2,}\?\//g, "").replace(/\/{2,}/g, "/").toLowerCase() : value;
const paths = handoff?.artifactPaths.flatMap((value) => [value.replace(/[\\/]+$/, ""), pathToFileURL(value, { windows }).href]) ?? [];
const literal = (value) => escapeRegExp(normalize(value));
const matches = new RegExp(`(?:^|[^\\w-])(?:${literal(handoff?.runId ?? "")}(?=$|[^\\w-])|(?:${paths.map(literal).join("|") || "(?!)"})(?=$|[/\\s"'\x60);,\\]]))`);
for (const { pid, state, command } of processes) {
if (handoff && Date.now() >= deadline) throw new Error("Process census deadline exceeded");
if (pid === process.pid || launchers.has(pid)) continue;
if ((state?.startsWith("Z") || handoff) && isPidDefinitelyDead(pid)) continue;
const argv = command && "argv" in command ? command.argv : void 0;
if (handoff) {
if (!windows && argv?.length === 0) continue;
const observation = nativeByPid.get(pid);
const cwd = directories.get(pid);
if ([
...argv ?? [],
observation?.commandLine ?? "",
cwd ?? ""
].some((value) => matches.test(normalize(value)))) {
result.matchingPids.push(pid);
continue;
}
const inspectorUid = process.getuid?.();
const foreign = observation?.foreignOwner || command?.uid !== void 0 && inspectorUid !== void 0 && command.uid !== inspectorUid;
if ((!argv || cwd === void 0) && !foreign && !isStandardSystemdLoginProcess(command, byPid.get(command?.ppid ?? 0)?.command, inspectorUid)) {
result.unverifiedPids.push(pid);
if (windows) result.error = "Retry update repair as Administrator using the same Windows account.";
}
continue;
}
const custody = classifyProcessArtifactCustody(command, pid, directories.get(pid), byPid.get(command?.ppid ?? 0)?.command);
if (custody.kind === "unresolved") throw new Error(`Could not classify PID ${pid}: ${custody.reason}`);
if (custody.kind === "holder") result.matchingPids.push(pid);
}
} catch (error) {
const pid = /Could not classify PID (\d+):/.exec(error instanceof Error ? error.message : "");
if (handoff && pid) result.unverifiedPids.push(Number(pid[1]));
result.error = !handoff ? `Could not inspect OpenClaw processes: ${String(error)}` : isRosettaTranslatedProcess() ? "Host process census is unavailable under Rosetta; run openclaw update repair with native arm64 Node.js." : "Host process census is incomplete; verify process-inspection permissions and retry update repair.";
}
return handoff ? result : result.error ? { error: result.error } : { pids: result.matchingPids };
}
//#endregion
export { inspectOtherOpenClawProcesses as t };