UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

205 lines (204 loc) 6.71 kB
import { t as killProcessTree$1 } from "./kill-tree-kSm0C74g.js"; import { a as getBinDir } from "./config-DSj7k-uT.js"; import fs from "node:fs"; import path from "node:path"; import { spawnSync } from "node:child_process"; //#region src/agents/shell-utils.ts /** * Shell execution helpers. * * Resolves platform shell commands, sanitizes binary output, and exposes process-tree cleanup. */ function resolvePowerShellPath() { const programFiles = process.env.ProgramFiles || process.env.PROGRAMFILES || "C:\\Program Files"; const pwsh7 = path.join(programFiles, "PowerShell", "7", "pwsh.exe"); if (fs.existsSync(pwsh7)) return pwsh7; const programW6432 = process.env.ProgramW6432; if (programW6432 && programW6432 !== programFiles) { const pwsh7Alt = path.join(programW6432, "PowerShell", "7", "pwsh.exe"); if (fs.existsSync(pwsh7Alt)) return pwsh7Alt; } const pwshInPath = resolveShellFromPath("pwsh"); if (pwshInPath) return pwshInPath; const systemRoot = process.env.SystemRoot || process.env.WINDIR; if (systemRoot) { const candidate = path.join(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe"); if (fs.existsSync(candidate)) return candidate; } return "powershell.exe"; } const NON_INTERACTIVE_SHELLS = new Set(["false", "nologin"]); function isNonInteractiveShell(shellPath) { if (!shellPath) return false; return NON_INTERACTIVE_SHELLS.has(path.basename(shellPath)); } function getPosixShellArgs(shellPath) { switch (path.basename(shellPath)) { case "bash": return [ "--noprofile", "--norc", "-c" ]; case "zsh": return ["-f", "-c"]; case "fish": return ["--no-config", "-c"]; default: return ["-c"]; } } function resolveWindowsBashPath(env = process.env) { const candidates = [env.ProgramFiles, env["ProgramFiles(x86)"]].filter((dir) => Boolean(dir?.trim())).map((dir) => path.join(dir, "Git", "bin", "bash.exe")); for (const candidate of candidates) if (fs.existsSync(candidate)) return candidate; return resolveShellFromPath("bash.exe", env) ?? resolveShellFromPath("bash", env); } function getShellConfig(customShellPath) { if (customShellPath) { if (!fs.existsSync(customShellPath)) throw new Error(`Custom shell path not found: ${customShellPath}`); return { shell: customShellPath, args: getPosixShellArgs(customShellPath) }; } if (process.platform === "win32") return { shell: resolvePowerShellPath(), args: [ "-NoProfile", "-NonInteractive", "-Command" ] }; const rawEnvShell = process.env.SHELL?.trim(); const envShell = rawEnvShell && !isNonInteractiveShell(rawEnvShell) ? rawEnvShell : void 0; if ((envShell ? path.basename(envShell) : "") === "fish") { const bash = resolveShellFromPath("bash"); if (bash) return { shell: bash, args: getPosixShellArgs(bash) }; const sh = resolveShellFromPath("sh"); if (sh) return { shell: sh, args: getPosixShellArgs(sh) }; } if (envShell) return { shell: envShell, args: getPosixShellArgs(envShell) }; const shell = resolveShellFromPath("sh") ?? resolveShellFromPath("bash") ?? "sh"; return { shell, args: getPosixShellArgs(shell) }; } function getBashShellConfig(customShellPath) { if (customShellPath) { if (!fs.existsSync(customShellPath)) throw new Error(`Custom shell path not found: ${customShellPath}`); return { shell: customShellPath, args: getPosixShellArgs(customShellPath) }; } if (process.platform === "win32") { const bash = resolveWindowsBashPath(); if (bash) return { shell: bash, args: ["-c"] }; throw new Error("No bash shell found. Install Git for Windows or add bash.exe to PATH."); } if (fs.existsSync("/bin/bash")) return { shell: "/bin/bash", args: getPosixShellArgs("/bin/bash") }; const shell = resolveShellFromPath("bash") ?? resolveShellFromWhich("bash") ?? resolveShellFromPath("sh") ?? "sh"; return { shell, args: getPosixShellArgs(shell) }; } function resolveShellFromPath(name, env = process.env) { const envPath = env.PATH ?? ""; if (!envPath) return; const entries = envPath.split(path.delimiter).filter(Boolean); for (const entry of entries) { const candidate = path.join(entry, name); try { fs.accessSync(candidate, fs.constants.X_OK); return candidate; } catch {} } } function resolveShellFromWhich(name) { if (process.platform === "win32") return; try { const result = spawnSync("which", [name], { encoding: "utf8", timeout: 5e3, windowsHide: true }); if (result.status !== 0 || !result.stdout) return; return result.stdout.trim().split(/\r?\n/)[0]?.trim() || void 0; } catch { return; } } function normalizeShellName(value) { const trimmed = value.trim(); if (!trimmed) return ""; return path.basename(trimmed).replace(/\.(exe|cmd|bat)$/i, "").replace(/[^a-zA-Z0-9_-]/g, ""); } function detectRuntimeShell() { const overrideShell = process.env.OPENCLAW_SHELL?.trim(); if (overrideShell) { const name = normalizeShellName(overrideShell); if (name) return name; } if (process.platform === "win32") { if (process.env.POWERSHELL_DISTRIBUTION_CHANNEL) return "pwsh"; return "powershell"; } const envShell = process.env.SHELL?.trim(); if (envShell && !isNonInteractiveShell(envShell)) { const name = normalizeShellName(envShell); if (name) return name; } if (process.env.POWERSHELL_DISTRIBUTION_CHANNEL) return "pwsh"; if (process.env.BASH_VERSION) return "bash"; if (process.env.ZSH_VERSION) return "zsh"; if (process.env.FISH_VERSION) return "fish"; if (process.env.KSH_VERSION) return "ksh"; if (process.env.NU_VERSION || process.env.NUSHELL_VERSION) return "nu"; } function sanitizeBinaryOutput(text) { const scrubbed = text.replace(/[\p{Format}\p{Surrogate}]/gu, ""); if (!scrubbed) return scrubbed; const chunks = []; for (const char of scrubbed) { const code = char.codePointAt(0); if (code == null) continue; if (code === 9 || code === 10 || code === 13) { chunks.push(char); continue; } if (code < 32) continue; chunks.push(char); } return chunks.join(""); } function getShellEnv() { const binDir = getBinDir(); const pathKey = Object.keys(process.env).find((key) => key.toLowerCase() === "path") ?? "PATH"; const currentPath = process.env[pathKey] ?? ""; const updatedPath = currentPath.split(path.delimiter).filter(Boolean).includes(binDir) ? currentPath : [binDir, currentPath].filter(Boolean).join(path.delimiter); return { ...process.env, [pathKey]: updatedPath }; } function killProcessTree(pid, opts) { killProcessTree$1(pid, { force: true, ...opts }); } //#endregion export { killProcessTree as a, getShellEnv as i, getBashShellConfig as n, sanitizeBinaryOutput as o, getShellConfig as r, detectRuntimeShell as t };