UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

299 lines (298 loc) • 12.5 kB
import { c as extractErrorCode } from "./errno-jiy5vtf-.mjs"; import { _ as normalizeLowercaseStringOrEmpty, k as isRecord } from "./number-coercion-PJEgwXYS.mjs"; import { n as resolveDiagnosticProcessEnv } from "./process-env-DlZFJzq6.mjs"; import { n as isNodeRuntime, r as resolveRuntimeScriptPosition, t as isBunRuntime } from "./runtime-binary-BczyKE0p.mjs"; import { n as isLegacyPluginSourceCaptureName } from "./plugin-source-capture-path-piKSCzPY.mjs"; import { spawnSync } from "node:child_process"; import fsSync from "node:fs"; import path from "node:path"; //#region src/process/windows-command-line.ts /** Parse an Arguments field; it does not contain the executable's special first token. */ function parseWindowsNativeArguments(value) { const args = []; let index = 0; while (index < value.length) { while (/[ \t]/.test(value.charAt(index))) index++; if (index === value.length) break; let argument = ""; let quoted = false; while (index < value.length) { let backslashes = 0; while (value[index] === "\\") { backslashes++; index++; } if (value[index] === "\"") { argument += "\\".repeat(Math.floor(backslashes / 2)); if (backslashes % 2 === 1) { argument += "\""; index++; } else if (quoted && value[index + 1] === "\"") { argument += "\""; index += 2; } else { quoted = !quoted; index++; } continue; } argument += "\\".repeat(backslashes); if (index === value.length || !quoted && /[ \t]/.test(value.charAt(index))) break; argument += value.charAt(index++); } args.push(argument); } return args; } /** Parse a native process command line; argv[0] preserves backslashes before quotes. */ function parseWindowsNativeCommandLine(value) { if (value.includes("\0")) return null; let index = 0; while (/[ \t]/.test(value.charAt(index))) index++; if (index === value.length) return []; let executable = ""; let quoted = false; while (index < value.length && (quoted || !/[ \t]/.test(value.charAt(index)))) { const character = value.charAt(index++); if (character === "\"") quoted = !quoted; else executable += character; } if (quoted || !executable) return null; return [executable, ...parseWindowsNativeArguments(value.slice(index))]; } const ROOT_BOOLEAN_FLAGS = /* @__PURE__ */ new Set(["--dev", "--no-color"]); const ROOT_VALUE_FLAGS = /* @__PURE__ */ new Set([ "--profile", "--log-level", "--container" ]); /** Returns whether a token can be consumed as a root option value. */ function isValueToken(arg) { if (!arg || arg === "--") return false; if (!arg.startsWith("-")) return true; return /^-\d+(?:\.\d+)?$/.test(arg); } /** Consume required root values by their Commander role before startup policy is selected. */ function consumeRootCommandOptionToken(args, index) { return consumeKnownOptionToken(args, index, ROOT_BOOLEAN_FLAGS, ROOT_VALUE_FLAGS, "command-path"); } /** Read positional command tokens while accepting root options at any pre-terminator position. */ function getRootOptionAwareCommandPath(argv, depth) { const args = argv.slice(2); const path = []; let literal = false; for (let index = 0; index < args.length; index += 1) { const arg = args[index]; if (!arg) break; if (!literal && arg === "--") { if (path.length > 0) break; literal = true; continue; } const consumed = literal ? 0 : consumeRootCommandOptionToken(args, index); if (consumed > 0) { index += consumed - 1; continue; } if (!literal && arg.startsWith("-")) continue; path.push(arg); if (path.length >= depth) break; } return path; } function consumeKnownOptionToken(args, index, booleanFlags, valueFlags, mode) { const arg = args[index]; if (!arg || arg === "--" || !arg.startsWith("-")) return 0; const equalsIndex = arg.indexOf("="); const flag = equalsIndex === -1 ? arg : arg.slice(0, equalsIndex); if (booleanFlags.has(flag)) return equalsIndex === -1 ? 1 : 0; if (!valueFlags.has(flag)) return 0; if (equalsIndex !== -1) return mode === "command-path" || arg.slice(equalsIndex + 1).trim() ? 1 : 0; if (mode === "command-path") return args[index + 1] !== void 0 ? 2 : 0; return isValueToken(args[index + 1]) ? 2 : 0; } //#endregion //#region src/infra/gateway-process-argv.ts function normalizeProcArg(arg) { return normalizeLowercaseStringOrEmpty(arg.replaceAll("\\", "/")); } const ENTRY_CANDIDATES = [ "openclaw.mjs", "dist/index.js", "dist/entry.js", "scripts/run-node.mjs", "src/entry.ts", "src/index.ts" ]; function referencesRetainedArtifact(value) { return value.split(/[\\/=]/u).some((part) => isLegacyPluginSourceCaptureName(part) || /^openclaw-update-runtime-[A-Za-z0-9]{6}$/u.test(part)); } /** Only absent nested manifests permit ancestor lookup; unreadable identities stay unresolved. */ function readProcessPackageIdentity(directory, searchParents = false) { for (let current = directory;;) { let manifest; try { manifest = JSON.parse(fsSync.readFileSync(path.join(current, "package.json"), "utf8")); } catch (error) { const parent = path.dirname(current); if (searchParents && extractErrorCode(error) === "ENOENT" && parent !== current && path.basename(current) !== "node_modules") { current = parent; continue; } throw new Error("could not read package identity", { cause: error }); } if (!isRecord(manifest) || typeof manifest.name !== "string" || !manifest.name.trim() || manifest.name !== manifest.name.trim()) throw new Error("package identity has no valid name"); return { name: manifest.name, scripts: isRecord(manifest.scripts) ? manifest.scripts : {} }; } } function readProcessWorkingDirectories(pids) { const requested = new Set(pids.filter((pid) => Number.isSafeInteger(pid) && pid > 0)); const directories = /* @__PURE__ */ new Map(); if (requested.size === 0) return directories; if (process.platform === "linux") { for (const pid of requested) try { directories.set(pid, fsSync.readlinkSync(`/proc/${pid}/cwd`)); } catch {} return directories; } try { if (process.platform === "darwin") { const result = spawnSync("/usr/sbin/lsof", [ "-a", "-p", [...requested].join(","), "-d", "cwd", "-F0pn" ], { encoding: "utf8", timeout: 5e3, maxBuffer: 4194304, env: resolveDiagnosticProcessEnv() }); if (result.error || result.status !== 0 && result.status !== 1) return directories; const fields = result.stdout.split("\0"); fields.pop(); const ambiguous = /* @__PURE__ */ new Set(); let pid; for (const raw of fields) { const field = raw.replace(/^\n/, ""); if (field.startsWith("p")) { const candidate = /^p(\d+)$/.exec(field); pid = candidate && requested.has(Number(candidate[1])) ? Number(candidate[1]) : void 0; } else if (pid !== void 0 && field.startsWith("n") && path.isAbsolute(field.slice(1))) { if (directories.has(pid)) { directories.delete(pid); ambiguous.add(pid); } else if (!ambiguous.has(pid)) directories.set(pid, field.slice(1)); } } } } catch {} return directories; } /** Generic script names identify OpenClaw only inside a verified package root. */ function classifyEntrypoint(args, opts = {}) { const exe = normalizeProcArg(args[0] ?? "").replace(/\.(bat|cmd|exe)$/i, ""); if (!opts.requirePackageIdentity && (exe.endsWith("/openclaw") || exe === "openclaw")) return { kind: "openclaw", entryIndex: 0 }; const entryIndex = /(?:^|\/)openclaw\.mjs$/.test(exe) ? 0 : resolveRuntimeScriptPosition(args).position; if (typeof entryIndex !== "number") return entryIndex.kind === "unclassified" ? { kind: "unclassified", cause: "runtime-syntax", reason: entryIndex.reason } : { kind: "other" }; const script = args[entryIndex]; if (opts.inspectPackage && !opts.requirePackageIdentity && opts.cwd && path.isAbsolute(opts.cwd) && isBunRuntime(args[0] ?? "") && !/[\\/]/u.test(script) && !path.extname(script)) { const pkg = readProcessPackageIdentity(opts.cwd, true); const task = pkg.scripts[script]; if (typeof task === "string" && task.trim()) return pkg.name === "openclaw" ? { kind: "openclaw" } : { kind: "other" }; } const identity = classifyOpenClawEntrypointPath(script, opts); return identity.kind === "openclaw" ? { ...identity, entryIndex } : identity; } /** Path evidence is shared with cleanup even when launcher syntax is unfamiliar. */ function classifyOpenClawEntrypointPath(script, opts = {}) { const normalized = normalizeProcArg(script); if (!opts.requirePackageIdentity && /(?:^|\/)openclaw\.mjs$/.test(normalized)) return { kind: "openclaw" }; const entrypoints = [...ENTRY_CANDIDATES, ...opts.additionalEntrypoints ?? []]; let scriptPath = script; if (!path.isAbsolute(script)) { const cwd = opts.cwd ?? (opts.pid === void 0 ? void 0 : readProcessWorkingDirectories([opts.pid]).get(opts.pid)); if (!cwd || !path.isAbsolute(cwd)) return { kind: "unclassified", cause: "cwd", reason: "working directory is unavailable" }; scriptPath = path.resolve(cwd, script); } let resolved; let directory; try { resolved = fsSync.realpathSync(scriptPath); if (opts.inspectPackage && !opts.requirePackageIdentity && referencesRetainedArtifact(resolved)) return { kind: "openclaw" }; const stat = opts.requirePackageIdentity || opts.inspectPackage ? fsSync.statSync(resolved) : void 0; if (opts.requirePackageIdentity && !stat?.isFile()) return { kind: "unclassified", cause: "script", reason: `entrypoint is not a regular file: ${script}` }; directory = stat?.isDirectory() ?? false; } catch { return { kind: "unclassified", cause: "script", reason: "could not resolve script" }; } const resolvedNormalized = normalizeProcArg(resolved); const entry = directory ? void 0 : entrypoints.find((candidate) => resolvedNormalized.endsWith(`/${candidate}`)); if (!entry && (!opts.inspectPackage || opts.requirePackageIdentity)) return { kind: "other" }; const root = entry ? resolved.slice(0, -entry.length) : directory ? resolved : path.dirname(resolved); try { return readProcessPackageIdentity(root, !entry && !directory).name === "openclaw" ? { kind: "openclaw", ...opts.requirePackageIdentity ? { packageIdentity: { root: path.resolve(root), entrypoint: resolved } } : {} } : { kind: "other" }; } catch (error) { return { kind: "unclassified", cause: "package-identity", reason: error instanceof Error ? error.message : String(error) }; } } /** One classification for process owners, command consumers, and diagnostics. */ function classifyOpenClawArgv(args, opts = {}) { const { command, owner, pid, port } = opts; if (!opts.requirePackageIdentity && command === "gateway" && owner?.pid === pid && owner?.state === "live" && (port === void 0 || owner.port === port)) return { kind: "openclaw" }; const executable = normalizeProcArg(args[0] ?? "").split("/").at(-1)?.replace(/\.(exe|cmd|bat)$/, "") ?? ""; if (!opts.requirePackageIdentity && /^openclaw-[a-z0-9-]+$/.test(executable)) return !command || executable === `openclaw-${command}` ? { kind: "openclaw" } : { kind: "other" }; const identity = classifyEntrypoint(args, opts); if (command) return identity.kind === "openclaw" && normalizeProcArg(getRootOptionAwareCommandPath(["node", ...args.slice(identity.entryIndex)], 1)[0] ?? "") !== command ? { kind: "other" } : identity; if (identity.kind === "openclaw" || !opts.requirePackageIdentity && args.some((arg) => arg.replaceAll("\\", "/").split("/").some(isLegacyPluginSourceCaptureName))) return identity.kind === "openclaw" ? identity : { kind: "openclaw" }; let marker = opts.serviceMarker; if (pid !== void 0 && process.platform === "linux" && (isNodeRuntime(executable) || isBunRuntime(executable) || executable === "tsx")) try { marker = fsSync.readFileSync(`/proc/${pid}/environ`, "utf8").split("\0").find((entry) => entry.startsWith("OPENCLAW_SERVICE_MARKER="))?.slice(24); } catch (error) { return { kind: "unclassified", cause: "service-marker", reason: `process identity inspection failed (${extractErrorCode(error) ?? "unavailable"})` }; } return !opts.requirePackageIdentity && marker === "openclaw" ? { kind: "openclaw" } : identity; } //#endregion export { referencesRetainedArtifact as a, readProcessWorkingDirectories as i, classifyOpenClawEntrypointPath as n, getRootOptionAwareCommandPath as o, readProcessPackageIdentity as r, parseWindowsNativeCommandLine as s, classifyOpenClawArgv as t };