Files
clawdbot/src/agents/shell-utils.ts
2026-01-20 11:59:36 +00:00

102 lines
2.9 KiB
TypeScript

import { spawn } from "node:child_process";
import fs from "node:fs";
import path from "node:path";
function resolvePowerShellPath(): string {
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";
}
export function getShellConfig(): { shell: string; args: string[] } {
if (process.platform === "win32") {
// Use PowerShell instead of cmd.exe on Windows.
// Problem: Many Windows system utilities (ipconfig, systeminfo, etc.) write
// directly to the console via WriteConsole API, bypassing stdout pipes.
// When Node.js spawns cmd.exe with piped stdio, these utilities produce no output.
// PowerShell properly captures and redirects their output to stdout.
return {
shell: resolvePowerShellPath(),
args: ["-NoProfile", "-NonInteractive", "-Command"],
};
}
const envShell = process.env.SHELL?.trim();
const shellName = envShell ? path.basename(envShell) : "";
// Fish rejects common bashisms used by tools, so prefer bash when detected.
if (shellName === "fish") {
const bash = resolveShellFromPath("bash");
if (bash) return { shell: bash, args: ["-c"] };
const sh = resolveShellFromPath("sh");
if (sh) return { shell: sh, args: ["-c"] };
}
const shell = envShell && envShell.length > 0 ? envShell : "sh";
return { shell, args: ["-c"] };
}
function resolveShellFromPath(name: string): string | undefined {
const envPath = process.env.PATH ?? "";
if (!envPath) return undefined;
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 {
// ignore missing or non-executable entries
}
}
return undefined;
}
export function sanitizeBinaryOutput(text: string): string {
const scrubbed = text.replace(/[\p{Format}\p{Surrogate}]/gu, "");
if (!scrubbed) return scrubbed;
const chunks: string[] = [];
for (const char of scrubbed) {
const code = char.codePointAt(0);
if (code == null) continue;
if (code === 0x09 || code === 0x0a || code === 0x0d) {
chunks.push(char);
continue;
}
if (code < 0x20) continue;
chunks.push(char);
}
return chunks.join("");
}
export function killProcessTree(pid: number): void {
if (process.platform === "win32") {
try {
spawn("taskkill", ["/F", "/T", "/PID", String(pid)], {
stdio: "ignore",
detached: true,
});
} catch {
// ignore errors if taskkill fails
}
return;
}
try {
process.kill(-pid, "SIGKILL");
} catch {
try {
process.kill(pid, "SIGKILL");
} catch {
// process already dead
}
}
}