openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
205 lines (204 loc) • 6.71 kB
JavaScript
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 };