UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

462 lines (461 loc) 19.9 kB
import { a as normalizeLowercaseStringOrEmpty, c as normalizeOptionalString } from "./string-coerce-mnp54Vah.js"; import { t as resolveNodeStartupTlsEnvironment } from "./node-startup-env-Chdn0e25.js"; import { _ as resolveNodeSystemdServiceName, c as NODE_WINDOWS_TASK_SCRIPT_NAME, g as resolveNodeLaunchAgentLabel, m as resolveGatewayWindowsTaskName, n as GATEWAY_SERVICE_KIND, o as NODE_SERVICE_KIND, p as resolveGatewaySystemdServiceName, r as GATEWAY_SERVICE_MARKER, s as NODE_SERVICE_MARKER, u as resolveGatewayLaunchAgentLabel, v as resolveNodeWindowsTaskName } from "./constants-e049XCW0.js"; import { t as resolveGatewayStateDir } from "./paths-Qb6rRFUH.js"; import { n as VERSION } from "./version-Crcn9X9T.js"; import { r as isSupportedNodeVersion } from "./runtime-guard-DFX4PxCB.js"; import { t as resolveStableNodePath } from "./stable-node-path-COOLjGh2.js"; import { n as getWindowsProgramFilesRoots } from "./windows-install-roots-jk5k_ejK.js"; import fs from "node:fs"; import path from "node:path"; import fs$1 from "node:fs/promises"; import os from "node:os"; import { execFile } from "node:child_process"; import { promisify } from "node:util"; //#region src/daemon/service-env.ts /** Builds minimal, portable environment blocks for managed daemon services. */ const SERVICE_PROXY_ENV_KEYS = [ "OPENCLAW_PROXY_URL", "HTTP_PROXY", "HTTPS_PROXY", "NO_PROXY", "ALL_PROXY", "http_proxy", "https_proxy", "no_proxy", "all_proxy" ]; function readServiceProxyEnvironment(env) { const proxyUrl = normalizeOptionalString(env.OPENCLAW_PROXY_URL); return proxyUrl ? { OPENCLAW_PROXY_URL: proxyUrl } : {}; } function normalizeServicePathDir(dir) { const trimmed = dir?.trim(); if (!trimmed || !path.posix.isAbsolute(trimmed)) return; return path.posix.normalize(trimmed); } function realpathServicePathDir(dir) { try { return path.posix.normalize(fs.realpathSync.native(dir)); } catch { return; } } function realpathExistingServicePathDir(dir) { const parts = []; let current = dir; while (current && current !== path.posix.dirname(current)) { const realCurrent = realpathServicePathDir(current); if (realCurrent) return path.posix.normalize(path.posix.join(realCurrent, ...parts.toReversed())); parts.push(path.posix.basename(current)); current = path.posix.dirname(current); } const realRoot = realpathServicePathDir(current); return realRoot ? path.posix.normalize(path.posix.join(realRoot, ...parts.toReversed())) : void 0; } function isSameOrChildPath(candidate, parent) { return candidate === parent || candidate.startsWith(`${parent}/`); } function isUnsafeProcPath(candidate) { return candidate === "/proc" || candidate.startsWith("/proc/"); } function isWorkspaceDerivedPath(dir, options) { if (isUnsafeProcPath(dir)) return true; const cwd = normalizeServicePathDir(options.cwd ?? process.cwd()); if (!cwd) return false; const home = normalizeServicePathDir(options.home); if (home && cwd === home) return false; if (isSameOrChildPath(dir, cwd)) return true; const realDir = realpathExistingServicePathDir(dir); const realCwd = realpathServicePathDir(cwd); const realHome = home ? realpathServicePathDir(home) : void 0; return Boolean(realDir && realCwd && realHome !== realCwd && isSameOrChildPath(realDir, realCwd)); } function addEnvConfiguredBinDir(dirs, dir, options) { const normalized = normalizeServicePathDir(dir); if (!normalized || isWorkspaceDerivedPath(normalized, options)) return; dirs.push(normalized); } function appendSubdir(base, subdir) { if (!base) return; return base.endsWith(`/${subdir}`) ? base : path.posix.join(base, subdir); } function addExistingDir(dirs, candidate, existsSync) { if (existsSync(candidate)) dirs.push(candidate); } function addCommonUserBinDirs(dirs, home, existsSync, includeMissingDefaults) { const addDefault = includeMissingDefaults ? (candidate) => dirs.push(candidate) : (candidate) => addExistingDir(dirs, candidate, existsSync); addDefault(`${home}/.local/bin`); addDefault(`${home}/.npm-global/bin`); addDefault(`${home}/bin`); addExistingDir(dirs, `${home}/.volta/bin`, existsSync); addExistingDir(dirs, `${home}/.asdf/shims`, existsSync); addExistingDir(dirs, `${home}/.bun/bin`, existsSync); } function addCommonEnvConfiguredBinDirs(dirs, env, options) { addEnvConfiguredBinDir(dirs, env?.PNPM_HOME, options); addEnvConfiguredBinDir(dirs, appendSubdir(env?.NPM_CONFIG_PREFIX, "bin"), options); addEnvConfiguredBinDir(dirs, appendSubdir(env?.BUN_INSTALL, "bin"), options); addEnvConfiguredBinDir(dirs, appendSubdir(env?.VOLTA_HOME, "bin"), options); addEnvConfiguredBinDir(dirs, appendSubdir(env?.ASDF_DATA_DIR, "shims"), options); } function addNixProfileBinDirs(dirs, home, env, options, includeMissingDefault, existsSync) { const nixProfiles = env?.NIX_PROFILES?.trim(); if (nixProfiles) for (const profile of nixProfiles.split(/\s+/).toReversed()) addEnvConfiguredBinDir(dirs, appendSubdir(profile, "bin"), options); else { const defaultProfileBin = `${home}/.nix-profile/bin`; if (includeMissingDefault) dirs.push(defaultProfileBin); else addExistingDir(dirs, defaultProfileBin, existsSync); } } function resolveSystemPathDirs(platform) { if (platform === "darwin") return [ "/opt/homebrew/bin", "/opt/homebrew/sbin", "/usr/local/bin", "/usr/bin", "/bin", "/usr/sbin", "/sbin" ]; if (platform === "linux") return [ "/usr/local/bin", "/usr/bin", "/bin" ]; return []; } /** * Resolve common user bin directories for macOS. * These are paths where npm global installs and node version managers typically place binaries. * * Key differences from Linux: * - fnm: macOS uses ~/Library/Application Support/fnm (not ~/.local/share/fnm) * - pnpm: macOS uses ~/Library/pnpm (not ~/.local/share/pnpm) */ function resolveDarwinUserBinDirs(home, env, existsSync = fs.existsSync, options = {}) { if (!home) return []; const dirs = []; const pathOptions = { ...options, home }; const includeMissingUserBinDefaults = options.includeMissingUserBinDefaults ?? true; addCommonEnvConfiguredBinDirs(dirs, env, pathOptions); addEnvConfiguredBinDir(dirs, env?.NVM_DIR, pathOptions); addEnvConfiguredBinDir(dirs, appendSubdir(env?.FNM_DIR, "aliases/default/bin"), pathOptions); addCommonUserBinDirs(dirs, home, existsSync, includeMissingUserBinDefaults); addNixProfileBinDirs(dirs, home, env, pathOptions, includeMissingUserBinDefaults, existsSync); addExistingDir(dirs, `${home}/Library/Application Support/fnm/aliases/default/bin`, existsSync); addExistingDir(dirs, `${home}/.fnm/aliases/default/bin`, existsSync); addExistingDir(dirs, `${home}/Library/pnpm`, existsSync); addExistingDir(dirs, `${home}/.local/share/pnpm`, existsSync); return dirs; } /** * Resolve common user bin directories for Linux. * These are paths where npm global installs and node version managers typically place binaries. */ function resolveLinuxUserBinDirs(home, env, existsSync = fs.existsSync, options = {}) { if (!home) return []; const dirs = []; const pathOptions = { ...options, home }; const includeMissingUserBinDefaults = options.includeMissingUserBinDefaults ?? true; addCommonEnvConfiguredBinDirs(dirs, env, pathOptions); addEnvConfiguredBinDir(dirs, appendSubdir(env?.NVM_DIR, "current/bin"), pathOptions); addEnvConfiguredBinDir(dirs, appendSubdir(env?.FNM_DIR, "aliases/default/bin"), pathOptions); addEnvConfiguredBinDir(dirs, appendSubdir(env?.FNM_DIR, "current/bin"), pathOptions); addCommonUserBinDirs(dirs, home, existsSync, includeMissingUserBinDefaults); addNixProfileBinDirs(dirs, home, env, pathOptions, includeMissingUserBinDefaults, existsSync); addExistingDir(dirs, `${home}/.nvm/current/bin`, existsSync); addExistingDir(dirs, `${home}/.local/share/fnm/aliases/default/bin`, existsSync); addExistingDir(dirs, `${home}/.local/share/fnm/current/bin`, existsSync); addExistingDir(dirs, `${home}/.fnm/aliases/default/bin`, existsSync); addExistingDir(dirs, `${home}/.fnm/current/bin`, existsSync); addExistingDir(dirs, `${home}/.local/share/pnpm`, existsSync); return dirs; } function getMinimalServicePathParts(options = {}) { const platform = options.platform ?? process.platform; if (platform === "win32") return []; const parts = []; const extraDirs = options.extraDirs ?? []; const systemDirs = resolveSystemPathDirs(platform); const includeUserDirs = options.includeUserDirs ?? platform !== "darwin"; const existsSync = options.existsSync ?? fs.existsSync; const userDirs = includeUserDirs ? platform === "linux" ? resolveLinuxUserBinDirs(options.home, options.env, existsSync, options) : platform === "darwin" ? resolveDarwinUserBinDirs(options.home, options.env, existsSync, options) : [] : []; const add = (dir) => { if (!dir) return; if (!parts.includes(dir)) parts.push(dir); }; for (const dir of extraDirs) add(dir); for (const dir of userDirs) add(dir); for (const dir of systemDirs) add(dir); return parts; } function getMinimalServicePathPartsFromEnv(options = {}) { const env = options.env ?? process.env; return getMinimalServicePathParts({ ...options, home: options.home ?? env.HOME, env }); } function buildMinimalServicePath(options = {}) { const env = options.env ?? process.env; if ((options.platform ?? process.platform) === "win32") return env.PATH ?? ""; return getMinimalServicePathPartsFromEnv({ ...options, env }).join(path.posix.delimiter); } function resolveGatewaySystemdUnitEnv(env) { const override = normalizeOptionalString(env.OPENCLAW_SYSTEMD_UNIT); if (override) return override.endsWith(".service") ? override : `${override}.service`; return `${resolveGatewaySystemdServiceName(env.OPENCLAW_PROFILE)}.service`; } function buildServiceEnvironment(params) { const { env, port, launchdLabel, extraPathDirs } = params; const platform = params.platform ?? process.platform; const sharedEnv = resolveSharedServiceEnvironmentFields(env, platform, extraPathDirs, params.execPath); const profile = env.OPENCLAW_PROFILE; const wrapperPath = normalizeOptionalString(env.OPENCLAW_WRAPPER); const resolvedLaunchdLabel = launchdLabel || (platform === "darwin" ? resolveGatewayLaunchAgentLabel(profile) : void 0); const systemdUnit = resolveGatewaySystemdUnitEnv(env); return { ...buildCommonServiceEnvironment(env, sharedEnv), OPENCLAW_PROFILE: profile, OPENCLAW_WRAPPER: wrapperPath, OPENCLAW_GATEWAY_PORT: String(port), OPENCLAW_LAUNCHD_LABEL: resolvedLaunchdLabel, OPENCLAW_SYSTEMD_UNIT: systemdUnit, OPENCLAW_WINDOWS_TASK_NAME: resolveGatewayWindowsTaskName(profile), OPENCLAW_SERVICE_MARKER: GATEWAY_SERVICE_MARKER, OPENCLAW_SERVICE_KIND: GATEWAY_SERVICE_KIND, OPENCLAW_SERVICE_VERSION: VERSION }; } function buildNodeServiceEnvironment(params) { const { env, extraPathDirs } = params; const sharedEnv = resolveSharedServiceEnvironmentFields(env, params.platform ?? process.platform, extraPathDirs, params.execPath); const gatewayToken = normalizeOptionalString(env.OPENCLAW_GATEWAY_TOKEN); const allowInsecurePrivateWs = normalizeOptionalString(env.OPENCLAW_ALLOW_INSECURE_PRIVATE_WS); return { ...buildCommonServiceEnvironment(env, sharedEnv), OPENCLAW_GATEWAY_TOKEN: gatewayToken, OPENCLAW_ALLOW_INSECURE_PRIVATE_WS: allowInsecurePrivateWs, OPENCLAW_LAUNCHD_LABEL: resolveNodeLaunchAgentLabel(), OPENCLAW_SYSTEMD_UNIT: resolveNodeSystemdServiceName(), OPENCLAW_WINDOWS_TASK_NAME: resolveNodeWindowsTaskName(), OPENCLAW_WINDOWS_TASK_HIDDEN_LAUNCHER: "1", OPENCLAW_TASK_SCRIPT_NAME: NODE_WINDOWS_TASK_SCRIPT_NAME, OPENCLAW_LOG_PREFIX: "node", OPENCLAW_SERVICE_MARKER: NODE_SERVICE_MARKER, OPENCLAW_SERVICE_KIND: NODE_SERVICE_KIND, OPENCLAW_SERVICE_VERSION: VERSION }; } function buildCommonServiceEnvironment(env, sharedEnv) { const serviceEnv = { HOME: env.HOME, TMPDIR: sharedEnv.tmpDir, NODE_EXTRA_CA_CERTS: sharedEnv.nodeCaCerts, NODE_USE_SYSTEM_CA: sharedEnv.nodeUseSystemCa, OPENCLAW_STATE_DIR: sharedEnv.stateDir, OPENCLAW_CONFIG_PATH: sharedEnv.configPath, ...sharedEnv.proxyEnv }; if (sharedEnv.minimalPath) serviceEnv.PATH = sharedEnv.minimalPath; return serviceEnv; } function resolveServiceTmpDir(env, platform) { if (platform === "darwin") try { return path.join(resolveGatewayStateDir(env), "tmp"); } catch { return env.TMPDIR?.trim() || os.tmpdir(); } return env.TMPDIR?.trim() || os.tmpdir(); } function resolveSharedServiceEnvironmentFields(env, platform, extraPathDirs, execPath) { const stateDir = env.OPENCLAW_STATE_DIR; const configPath = env.OPENCLAW_CONFIG_PATH; const tmpDir = resolveServiceTmpDir(env, platform); const startupTlsEnv = resolveNodeStartupTlsEnvironment({ env, platform, execPath }); return { stateDir, configPath, tmpDir, minimalPath: platform === "win32" ? void 0 : buildMinimalServicePath({ env, platform, extraDirs: extraPathDirs }), proxyEnv: readServiceProxyEnvironment(env), nodeCaCerts: startupTlsEnv.NODE_EXTRA_CA_CERTS, nodeUseSystemCa: startupTlsEnv.NODE_USE_SYSTEM_CA }; } //#endregion //#region src/daemon/service-path-policy.ts /** Classifies service PATH entries that should not be frozen into daemons. */ function getPathModule$1(platform) { return platform === "win32" ? path.win32 : path.posix; } function normalizeServicePathEntry(entry, platform) { const normalized = getPathModule$1(platform).normalize(entry).replaceAll("\\", "/"); if (platform === "win32") return normalizeLowercaseStringOrEmpty(normalized); return normalized; } function isNonMinimalServicePathEntry(entry, platform) { if (platform === "win32") return false; const normalized = normalizeServicePathEntry(entry, platform); return normalized.includes("/.nvm/") || normalized.includes("/.fnm/") || normalized.includes("/.local/share/fnm/") || normalized.includes("/.volta/") || normalized.includes("/.asdf/") || normalized.includes("/.n/") || normalized.includes("/.nodenv/") || normalized.includes("/.nodebrew/") || normalized.includes("/nvs/") || normalized.includes("/.local/share/pnpm/") || normalized.includes("/pnpm/") || normalized.endsWith("/pnpm"); } //#endregion //#region src/daemon/runtime-paths.ts /** Selects stable Node runtime paths for daemon installs across platforms. */ const VERSION_MANAGER_MARKERS = [ "/.nvm/", "/.fnm/", "/.local/share/fnm/", "/library/application support/fnm/", "/.volta/", "/.asdf/", "/.local/share/mise/", "/.n/", "/.nodenv/", "/.nodebrew/", "/nvs/" ]; function getPathModule(platform) { return platform === "win32" ? path.win32 : path.posix; } function isNodeExecPath(execPath, platform) { const base = normalizeLowercaseStringOrEmpty(getPathModule(platform).basename(execPath)); return base === "node" || base === "node.exe"; } function normalizeForCompare(input, platform) { const normalized = getPathModule(platform).normalize(input).replaceAll("\\", "/"); if (platform === "win32") return normalizeLowercaseStringOrEmpty(normalized); return normalized; } function buildSystemNodeCandidates(env, platform) { if (platform === "darwin") return [ "/opt/homebrew/bin/node", "/opt/homebrew/opt/node/bin/node", "/opt/homebrew/opt/node@24/bin/node", "/opt/homebrew/opt/node@22/bin/node", "/usr/local/bin/node", "/usr/local/opt/node/bin/node", "/usr/local/opt/node@24/bin/node", "/usr/local/opt/node@22/bin/node", "/usr/bin/node" ]; if (platform === "linux") return ["/usr/local/bin/node", "/usr/bin/node"]; if (platform === "win32") { const pathModule = getPathModule(platform); return getWindowsProgramFilesRoots(env).map((root) => pathModule.join(root, "nodejs", "node.exe")); } return []; } const execFileAsync = promisify(execFile); async function resolveNodeVersion(nodePath, execFileImpl) { try { const { stdout } = await execFileImpl(nodePath, ["-p", "process.versions.node"], { encoding: "utf8" }); const value = stdout.trim(); return value ? value : null; } catch { return null; } } async function isVersionManagedRealNodePath(nodePath, platform) { try { return isVersionManagedNodePath(await fs$1.realpath(nodePath), platform); } catch { return false; } } /** True when a Node path lives under a known user version-manager root. */ function isVersionManagedNodePath(nodePath, platform = process.platform) { const normalized = normalizeLowercaseStringOrEmpty(normalizeForCompare(nodePath, platform)); return VERSION_MANAGER_MARKERS.some((marker) => normalized.includes(marker)); } /** True when a Node path matches known system install candidates for the platform. */ function isSystemNodePath(nodePath, env = process.env, platform = process.platform) { const normalized = normalizeForCompare(nodePath, platform); return buildSystemNodeCandidates(env, platform).some((candidate) => { return normalized === normalizeForCompare(candidate, platform); }); } /** Resolves the first available system Node candidate for the platform. */ async function resolveSystemNodePath(env = process.env, platform = process.platform) { const candidates = buildSystemNodeCandidates(env, platform); for (const candidate of candidates) try { await fs$1.access(candidate); return candidate; } catch {} return null; } /** Resolves system Node info, preferring a supported non-version-managed install. */ async function resolveSystemNodeInfo(params) { const env = params.env ?? process.env; const platform = params.platform ?? process.platform; const execFileImpl = params.execFile ?? execFileAsync; let firstAvailable = null; for (const systemNode of buildSystemNodeCandidates(env, platform)) { try { await fs$1.access(systemNode); } catch { continue; } if (await isVersionManagedRealNodePath(systemNode, platform)) continue; const version = await resolveNodeVersion(systemNode, execFileImpl); const info = { path: systemNode, version, supported: isSupportedNodeVersion(version) }; if (info.supported) return info; firstAvailable ??= info; } return firstAvailable; } /** Renders a warning when the system Node exists but is below the supported floor. */ function renderSystemNodeWarning(systemNode, selectedNodePath) { if (!systemNode || systemNode.supported) return null; const versionLabel = systemNode.version ?? "unknown"; const selectedLabel = selectedNodePath ? ` Using ${selectedNodePath} for the daemon.` : ""; return `System Node ${versionLabel} at ${systemNode.path} is below the required Node 22.19+.${selectedLabel} Install Node 24 (recommended) or Node 22 LTS from nodejs.org or Homebrew.`; } /** Resolves the Node binary the daemon should use for a node runtime. */ async function resolvePreferredNodePath(params) { if (params.runtime !== "node") return; const platform = params.platform ?? process.platform; const currentExecPath = params.execPath ?? process.execPath; const execFileImpl = params.execFile ?? execFileAsync; if (currentExecPath && isNodeExecPath(currentExecPath, platform)) { if (isSupportedNodeVersion(await resolveNodeVersion(currentExecPath, execFileImpl))) { const stableCurrentPath = await resolveStableNodePath(currentExecPath); if (!isVersionManagedNodePath(currentExecPath, platform)) return stableCurrentPath; const systemNode = await resolveSystemNodeInfo({ env: params.env, platform, execFile: execFileImpl }); if (systemNode?.supported) return systemNode.path; return stableCurrentPath; } } const systemNode = await resolveSystemNodeInfo(params); if (!systemNode?.supported) return; return systemNode.path; } //#endregion export { resolveSystemNodeInfo as a, normalizeServicePathEntry as c, buildServiceEnvironment as d, getMinimalServicePathPartsFromEnv as f, resolvePreferredNodePath as i, SERVICE_PROXY_ENV_KEYS as l, isVersionManagedNodePath as n, resolveSystemNodePath as o, renderSystemNodeWarning as r, isNonMinimalServicePathEntry as s, isSystemNodePath as t, buildNodeServiceEnvironment as u };