Files
llmx/codex-cli/src/utils/agent/exec.ts
Michael Bolin 033d379eca fix: remove unused _writableRoots arg to exec() function (#762)
I suspect this was done originally so that `execForSandbox()` had a
consistent signature for both the `SandboxType.NONE` and
`SandboxType.MACOS_SEATBELT` cases, but that is not really necessary and
turns out to make the upcoming Landlock support a bit more complicated
to implement, so I had Codex remove it and clean up the call sites.
2025-04-30 14:08:27 -07:00

114 lines
3.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { ExecInput, ExecResult } from "./sandbox/interface.js";
import type { SpawnOptions } from "child_process";
import type { ParseEntry } from "shell-quote";
import { process_patch } from "./apply-patch.js";
import { SandboxType } from "./sandbox/interface.js";
import { execWithSeatbelt } from "./sandbox/macos-seatbelt.js";
import { exec as rawExec } from "./sandbox/raw-exec.js";
import { formatCommandForDisplay } from "../../format-command.js";
import fs from "fs";
import os from "os";
import path from "path";
import { parse } from "shell-quote";
import { resolvePathAgainstWorkdir } from "src/approvals.js";
const DEFAULT_TIMEOUT_MS = 10_000; // 10 seconds
function requiresShell(cmd: Array<string>): boolean {
// If the command is a single string that contains shell operators,
// it needs to be run with shell: true
if (cmd.length === 1 && cmd[0] !== undefined) {
const tokens = parse(cmd[0]) as Array<ParseEntry>;
return tokens.some((token) => typeof token === "object" && "op" in token);
}
// If the command is split into multiple arguments, we don't need shell: true
// even if one of the arguments is a shell operator like '|'
return false;
}
/**
* This function should never return a rejected promise: errors should be
* mapped to a non-zero exit code and the error message should be in stderr.
*/
export function exec(
{
cmd,
workdir,
timeoutInMillis,
additionalWritableRoots,
}: ExecInput & { additionalWritableRoots: ReadonlyArray<string> },
sandbox: SandboxType,
abortSignal?: AbortSignal,
): Promise<ExecResult> {
// This is a temporary measure to understand what are the common base commands
// until we start persisting and uploading rollouts
const opts: SpawnOptions = {
timeout: timeoutInMillis || DEFAULT_TIMEOUT_MS,
...(requiresShell(cmd) ? { shell: true } : {}),
...(workdir ? { cwd: workdir } : {}),
};
// Merge default writable roots with any user-specified ones.
const writableRoots = [
process.cwd(),
os.tmpdir(),
...additionalWritableRoots,
];
if (sandbox === SandboxType.MACOS_SEATBELT) {
return execWithSeatbelt(cmd, opts, writableRoots, abortSignal);
}
// SandboxType.NONE (or any other) falls back to the raw exec implementation
return rawExec(cmd, opts, abortSignal);
}
export function execApplyPatch(
patchText: string,
workdir: string | undefined = undefined,
): ExecResult {
// This is a temporary measure to understand what are the common base commands
// until we start persisting and uploading rollouts
try {
const result = process_patch(
patchText,
(p) => fs.readFileSync(resolvePathAgainstWorkdir(p, workdir), "utf8"),
(p, c) => {
const resolvedPath = resolvePathAgainstWorkdir(p, workdir);
// Ensure the parent directory exists before writing the file. This
// mirrors the behaviour of the standalone apply_patch CLI (see
// write_file() in apply-patch.ts) and prevents errors when adding a
// new file in a notyetcreated subdirectory.
const dir = path.dirname(resolvedPath);
if (dir !== ".") {
fs.mkdirSync(dir, { recursive: true });
}
fs.writeFileSync(resolvedPath, c, "utf8");
},
(p) => fs.unlinkSync(resolvePathAgainstWorkdir(p, workdir)),
);
return {
stdout: result,
stderr: "",
exitCode: 0,
};
} catch (error: unknown) {
// @ts-expect-error error might not be an object or have a message property.
const stderr = String(error.message ?? error);
return {
stdout: "",
stderr: stderr,
exitCode: 1,
};
}
}
export function getBaseCmd(cmd: Array<string>): string {
const formattedCommand = formatCommandForDisplay(cmd);
return formattedCommand.split(" ")[0] || cmd[0] || "<unknown>";
}