UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

239 lines (238 loc) 6.84 kB
import { t as isTruthyEnvValue } from "./env-Di49gJwo.js"; import { j as resolveTimerTimeoutMs, y as parseStrictNonNegativeInteger } from "./number-coercion-CJQ8TR--.js"; import "./parse-finite-number-Z7n6tXLk.js"; import { i as formatErrorMessage } from "./errors-BXgSefBE.js"; import { s as sanitizeHostExecEnv } from "./host-env-security-RnaFYKhk.js"; import fs from "node:fs"; import path from "node:path"; import os from "node:os"; import { execFileSync } from "node:child_process"; //#region src/infra/shell-env.ts const DEFAULT_TIMEOUT_MS = 15e3; const DEFAULT_MAX_BUFFER_BYTES = 2 * 1024 * 1024; const DEFAULT_SHELL = "/bin/sh"; let lastAppliedKeys = []; let cachedShellPath; let cachedEtcShells; let nextExecCacheId = 1; const loginShellEnvProbeCache = /* @__PURE__ */ new Map(); const execCacheIds = /* @__PURE__ */ new WeakMap(); function resolveShellExecEnv(env) { const execEnv = sanitizeHostExecEnv({ baseEnv: env }); const home = os.homedir().trim(); if (home) execEnv.HOME = home; else delete execEnv.HOME; delete execEnv.ZDOTDIR; return execEnv; } function resolveTimeoutMs(timeoutMs) { return resolveTimerTimeoutMs(timeoutMs, DEFAULT_TIMEOUT_MS, 0); } function readEtcShells() { if (cachedEtcShells !== void 0) return cachedEtcShells; try { const entries = fs.readFileSync("/etc/shells", "utf8").split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#") && path.isAbsolute(line)); cachedEtcShells = new Set(entries); } catch { cachedEtcShells = null; } return cachedEtcShells; } function isTrustedShellPath(shell) { if (!path.isAbsolute(shell)) return false; if (path.normalize(shell) !== shell) return false; return readEtcShells()?.has(shell) === true; } function resolveShell(env) { const shell = env.SHELL?.trim(); if (shell && isTrustedShellPath(shell)) return shell; return DEFAULT_SHELL; } function execLoginShellEnvZero(params) { return params.exec(params.shell, [ "-l", "-c", "env -0" ], { encoding: "buffer", timeout: params.timeoutMs, maxBuffer: DEFAULT_MAX_BUFFER_BYTES, env: params.env, windowsHide: true, stdio: [ "ignore", "pipe", "pipe" ] }); } function parseShellEnv(stdout) { const shellEnv = /* @__PURE__ */ new Map(); const parts = stdout.toString("utf8").split("\0"); for (const part of parts) { if (!part) continue; const eq = part.indexOf("="); if (eq <= 0) continue; const key = part.slice(0, eq); const value = part.slice(eq + 1); if (!key) continue; shellEnv.set(key, value); } return shellEnv; } function resolveExecCacheId(exec) { if (!exec) return "default"; const key = exec; let id = execCacheIds.get(key); if (!id) { id = nextExecCacheId; nextExecCacheId += 1; execCacheIds.set(key, id); } return `exec:${id}`; } function createLoginShellEnvCacheKey(params) { const startupEnvEntries = Object.entries(params.execEnv).filter(([key]) => { if (key === "HOME" || key === "PATH" || key === "TERM" || key === "LANG" || key === "LC_ALL" || key === "LC_CTYPE" || key === "USER" || key === "LOGNAME" || key === "TMPDIR") return true; return key.startsWith("XDG_") || key.startsWith("OPENCLAW_"); }).toSorted(([left], [right]) => left.localeCompare(right)); return JSON.stringify([ params.shell, params.timeoutMs, resolveExecCacheId(params.exec), startupEnvEntries ]); } function probeLoginShellEnv(params) { if ((params.platform ?? process.platform) === "win32") return { ok: true, shellEnv: /* @__PURE__ */ new Map() }; const exec = params.exec ?? execFileSync; const timeoutMs = resolveTimeoutMs(params.timeoutMs); const shell = resolveShell(params.env); const execEnv = resolveShellExecEnv(params.env); const cacheKey = createLoginShellEnvCacheKey({ shell, timeoutMs, exec: params.exec, execEnv }); const cached = loginShellEnvProbeCache.get(cacheKey); if (cached) return cached.ok ? { ok: true, shellEnv: new Map(cached.entries) } : cached; try { const shellEnv = parseShellEnv(execLoginShellEnvZero({ shell, env: execEnv, exec, timeoutMs })); loginShellEnvProbeCache.set(cacheKey, { ok: true, entries: [...shellEnv.entries()] }); return { ok: true, shellEnv }; } catch (err) { const result = { ok: false, error: formatErrorMessage(err) }; loginShellEnvProbeCache.set(cacheKey, result); return result; } } function hasExplicitEnvBinding(env, key) { return Object.hasOwn(env, key); } function loadShellEnvFallback(opts) { const logger = opts.logger ?? console; if (!opts.enabled) { lastAppliedKeys = []; return { ok: true, applied: [], skippedReason: "disabled" }; } const missingExpectedKeys = opts.expectedKeys.filter((key) => !hasExplicitEnvBinding(opts.env, key)); if (missingExpectedKeys.length === 0) { lastAppliedKeys = []; return { ok: true, applied: [], skippedReason: "already-has-keys" }; } const probe = probeLoginShellEnv({ env: opts.env, timeoutMs: opts.timeoutMs, exec: opts.exec, platform: opts.platform }); if (!probe.ok) { logger.warn(`[openclaw] shell env fallback failed: ${probe.error}`); lastAppliedKeys = []; return { ok: false, error: probe.error, applied: [] }; } const applied = []; for (const key of missingExpectedKeys) { const value = probe.shellEnv.get(key); if (!value?.trim()) continue; opts.env[key] = value; applied.push(key); } lastAppliedKeys = applied; return { ok: true, applied }; } function shouldEnableShellEnvFallback(env) { return isTruthyEnvValue(env.OPENCLAW_LOAD_SHELL_ENV); } function shouldDeferShellEnvFallback(env) { return isTruthyEnvValue(env.OPENCLAW_DEFER_SHELL_ENV_FALLBACK); } function resolveShellEnvFallbackTimeoutMs(env) { const raw = env.OPENCLAW_SHELL_ENV_TIMEOUT_MS?.trim(); if (!raw) return DEFAULT_TIMEOUT_MS; const parsed = parseStrictNonNegativeInteger(raw); if (parsed === void 0) return DEFAULT_TIMEOUT_MS; return resolveTimeoutMs(parsed); } function getShellPathFromLoginShell(opts) { if (cachedShellPath !== void 0) return cachedShellPath; const platform = opts.platform ?? process.platform; if (platform === "win32") { cachedShellPath = null; return cachedShellPath; } const probe = probeLoginShellEnv({ env: opts.env, timeoutMs: opts.timeoutMs, exec: opts.exec, platform }); if (!probe.ok) { cachedShellPath = null; return cachedShellPath; } const shellPath = probe.shellEnv.get("PATH")?.trim(); cachedShellPath = shellPath && shellPath.length > 0 ? shellPath : null; return cachedShellPath; } function getShellEnvAppliedKeys() { return [...lastAppliedKeys]; } //#endregion export { shouldDeferShellEnvFallback as a, resolveShellEnvFallbackTimeoutMs as i, getShellPathFromLoginShell as n, shouldEnableShellEnvFallback as o, loadShellEnvFallback as r, getShellEnvAppliedKeys as t };