@sema-agent/core
Version:
Stateless, task-oriented AI agent core
199 lines (198 loc) • 10.9 kB
JavaScript
import { SHELL_CONTROL_PREFIXES } from "../../shell/lex.js";
export const NOT_AUTO_ALLOWED = "— not auto-allowed";
export function programBasename(word) {
return word.includes("/") ? (word.split("/").pop() ?? word) : word;
}
export const ASSIGNMENT_PREFIX = /^[A-Za-z_][A-Za-z0-9_]*(?:\[[^\]\s]*\])?\+?=/;
export function writtenAssignmentPrefix(token) {
const prefix = ASSIGNMENT_PREFIX.exec(token)?.[0];
if (prefix === undefined)
return undefined;
const appends = prefix.endsWith("+=");
const written = prefix.slice(0, prefix.length - (appends ? 2 : 1));
const bracket = written.indexOf("[");
return { prefix, name: bracket < 0 ? written : written.slice(0, bracket), appends, settlesWholeName: !appends && bracket < 0 };
}
export { SHELL_CONTROL_PREFIXES };
export const SHELL_BLOCK_WORDS = new Set(["for", "select", "case", "esac", "fi", "done", "function", "coproc", "[[", "]]", "in"]);
export function isBareControlWord(words, i, set) {
const w = words[i];
return w !== undefined && w.raw === w.folded && set.has(w.folded);
}
export function isLeadingAssignmentWord(words, i) {
const w = words[i];
return w !== undefined && w.assignment !== undefined && w.assignment.bare && w.atCommandPosition;
}
export const ARGV_REENTRY_PROGRAM_RE = /^xargs$/;
export const ARGV_REENTRY_DEFAULT_PROGRAM = "echo";
const NO_DECLARED_OPTIONS = { optionArity: new Map() };
const launcherRow = (pairs, extra = {}) => ({ optionArity: new Map(pairs), ...extra });
export const LAUNCHER_TABLE = new Map([
["env", launcherRow([["-i", 0], ["-0", 0], ["-v", 0], ["--ignore-environment", 0], ["--null", 0], ["--debug", 0], ["-u", 1], ["--unset", 1]], { assignmentOperands: true, commandString: ["-S", "--split-string"] })],
["command", launcherRow([["-p", 0]])],
["builtin", NO_DECLARED_OPTIONS], ["exec", NO_DECLARED_OPTIONS],
["noglob", NO_DECLARED_OPTIONS], ["xargs", NO_DECLARED_OPTIONS], ["busybox", NO_DECLARED_OPTIONS],
["sudo", { optionArity: new Map(), assignmentOperands: true }], ["doas", NO_DECLARED_OPTIONS], ["pkexec", { optionArity: new Map(), whenNoProgram: "shell" }],
["su", { optionArity: new Map(), shellText: true }], ["runuser", { optionArity: new Map(), shellText: true }],
["chroot", { optionArity: new Map(), requiredOperands: 1, whenNoProgram: "shell" }], ["setpriv", NO_DECLARED_OPTIONS],
["nohup", NO_DECLARED_OPTIONS],
["nice", launcherRow([["-n", 1], ["--adjustment", 1]], { negativeIntegerIsOwnArgument: true })],
["ionice", NO_DECLARED_OPTIONS], ["chrt", { optionArity: new Map(), requiredOperands: 1 }], ["taskset", { optionArity: new Map(), requiredOperands: 1 }],
["stdbuf", launcherRow([["-i", 1], ["-o", 1], ["-e", 1], ["--input", 1], ["--output", 1], ["--error", 1]])],
["timeout", launcherRow([
["--foreground", 0], ["--preserve-status", 0], ["--verbose", 0], ["-v", 0],
["-k", 1], ["-s", 1], ["--kill-after", 1], ["--signal", 1],
], { requiredOperands: 1 })],
["time", {
optionArity: new Map([
["-p", 0], ["-a", 0], ["-v", 0], ["-q", 0], ["--portability", 0], ["--append", 0], ["--verbose", 0], ["--quiet", 0], ["--help", 0], ["--version", 0], ["--", 0],
["-f", 1], ["-o", 1], ["--format", 1], ["--output", 1],
]),
}],
["watch", { optionArity: new Map(), shellText: true }], ["setsid", NO_DECLARED_OPTIONS], ["flock", { optionArity: new Map(), requiredOperands: 1 }], ["unshare", { optionArity: new Map(), whenNoProgram: "shell" }],
["nsenter", { optionArity: new Map(), whenNoProgram: "shell" }], ["script", { optionArity: new Map(), shellText: true }], ["numactl", NO_DECLARED_OPTIONS], ["prlimit", NO_DECLARED_OPTIONS],
["systemd-run", NO_DECLARED_OPTIONS],
["strace", NO_DECLARED_OPTIONS], ["ltrace", NO_DECLARED_OPTIONS], ["valgrind", NO_DECLARED_OPTIONS], ["firejail", { optionArity: new Map(), whenNoProgram: "shell" }],
["bwrap", NO_DECLARED_OPTIONS],
]);
export const COMMAND_LAUNCHERS = new Set(LAUNCHER_TABLE.keys());
function pastLauncherOptions(launcher, folded, i) {
const row = LAUNCHER_TABLE.get(launcher);
if (row === undefined)
return i;
for (;;) {
const tok = folded[i];
if (tok === undefined)
return i;
if (row.negativeIntegerIsOwnArgument === true && /^-\d+$/.test(tok)) {
i += 1;
continue;
}
const step = launcherOptionStep(row, tok);
if (step === undefined)
return i;
if (step === 2 && folded[i + 1] === undefined)
return i;
i += step;
}
}
function launcherOptionStep(row, tok) {
const exact = row.optionArity.get(tok);
if (exact !== undefined)
return exact === 1 ? 2 : 1;
const eq = tok.startsWith("--") ? tok.indexOf("=") : -1;
if (eq > 2 && row.optionArity.get(tok.slice(0, eq)) === 1)
return 1;
if (/^-[A-Za-z]./.test(tok) && row.optionArity.get(tok.slice(0, 2)) === 1)
return 1;
if (/^-[A-Za-z]+$/.test(tok) && [...tok.slice(1)].every((ch) => row.optionArity.get(`-${ch}`) === 0))
return 1;
return undefined;
}
export const PROGRAM_POSITION_REFUSAL_KINDS = Object.freeze(["option_at_program_position", "control_syntax_at_program_position"]);
export function readProgramPosition(words) {
const folded = words.map((w) => w.folded);
const controlWords = [];
let i = 0;
while (i < folded.length && isBareControlWord(words, i, SHELL_CONTROL_PREFIXES)) {
const controlWord = folded[i];
controlWords.push(i);
i = pastLauncherOptions(controlWord, folded, i + 1);
}
const none = (written, assignments, refusal) => ({ controlWords, written, assignments, launchers: [], argvFromStdin: false, runsDefaultProgram: false, program: undefined, refusal });
if (i >= folded.length)
return none(undefined, []);
const written = i;
const assignments = [];
while (i < folded.length && isLeadingAssignmentWord(words, i)) {
assignments.push(i);
i++;
}
const head = folded[i];
if (head === undefined)
return none(written, assignments);
const basename = programBasename(head);
if (basename.startsWith("-") && basename !== "-") {
const owner = controlWords.length > 0 ? folded[controlWords[controlWords.length - 1]] : head;
return none(written, assignments, { kind: "option_at_program_position", owner, option: head });
}
if (!COMMAND_LAUNCHERS.has(basename)) {
return { controlWords, written, assignments, launchers: [], argvFromStdin: false, runsDefaultProgram: false, program: { index: i, name: head, basename }, refusal: undefined };
}
const launchers = [{ index: i, name: basename }];
const owner = head;
const shellRefusal = () => ({ kind: "control_syntax_at_program_position", owner, word: "sh", runsShell: true });
let owedOperands = LAUNCHER_TABLE.get(basename)?.requiredOperands ?? 0;
let lastRow = LAUNCHER_TABLE.get(basename);
const chain0 = (program, refusal) => ({ controlWords, written, assignments, launchers, argvFromStdin: false, runsDefaultProgram: false, program, refusal });
if (lastRow?.shellText === true)
return chain0(undefined, shellRefusal());
let argvFromStdin = ARGV_REENTRY_PROGRAM_RE.test(basename);
let lastReentry = argvFromStdin ? i : -1;
const chain = (program, refusal) => ({ controlWords, written, assignments, launchers, argvFromStdin, runsDefaultProgram: lastReentry === folded.length - 1, program, refusal });
for (let j = pastLauncherOptions(basename, folded, i + 1); j < folded.length; j++) {
const t = folded[j];
if (owedOperands > 0 && !t.startsWith("-")) {
owedOperands -= 1;
continue;
}
if (lastRow?.assignmentOperands === true && !t.startsWith("-") && t.includes("="))
continue;
const wordBasename = programBasename(t);
if (ARGV_REENTRY_PROGRAM_RE.test(wordBasename)) {
argvFromStdin = true;
lastReentry = j;
lastRow = undefined;
owedOperands = 0;
continue;
}
if (COMMAND_LAUNCHERS.has(wordBasename)) {
launchers.push({ index: j, name: wordBasename });
lastRow = LAUNCHER_TABLE.get(wordBasename);
if (lastRow?.shellText === true)
return chain(undefined, shellRefusal());
owedOperands = lastRow?.requiredOperands ?? 0;
j = pastLauncherOptions(wordBasename, folded, j + 1) - 1;
continue;
}
if (t.startsWith("-"))
return chain(undefined, { kind: "option_at_program_position", owner, option: t });
if (SHELL_CONTROL_PREFIXES.has(wordBasename) || SHELL_BLOCK_WORDS.has(wordBasename)) {
return chain(undefined, { kind: "control_syntax_at_program_position", owner, word: wordBasename });
}
return chain({ index: j, name: t, basename: wordBasename });
}
if (lastRow?.whenNoProgram === "shell")
return chain(undefined, shellRefusal());
return chain(undefined);
}
export function argvReentryUnjudged(pos, acceptedTargets) {
if (!pos.argvFromStdin)
return false;
const runs = pos.program?.basename ?? (pos.runsDefaultProgram ? ARGV_REENTRY_DEFAULT_PROGRAM : undefined);
return runs === undefined || !acceptedTargets.has(runs);
}
export const SHELL_SCAN_DECLINES = Object.freeze(["unreadable_program_position", "stream_fed_operands"]);
export function programPositionRunsShell(pos, shellProgram) {
if (pos.refusal !== undefined)
return pos.refusal.kind === "control_syntax_at_program_position" && pos.refusal.runsShell === true;
return pos.program !== undefined && shellProgram.test(pos.program.basename);
}
export function shellScanDeclineOf(pos, acceptedStreamTargets) {
if (pos.refusal !== undefined) {
return pos.refusal.kind === "control_syntax_at_program_position" && pos.refusal.runsShell === true ? undefined : "unreadable_program_position";
}
return argvReentryUnjudged(pos, acceptedStreamTargets) ? "stream_fed_operands" : undefined;
}
export function programPositionRefusalSentence(refusal) {
switch (refusal.kind) {
case "option_at_program_position":
return `"${refusal.owner}" is given the option "${refusal.option}" where the program it runs would be — which program runs is not readable without that word's option table ${NOT_AUTO_ALLOWED}`;
case "control_syntax_at_program_position":
return `"${refusal.owner}" is given shell control syntax ("${refusal.word}") where the program it runs would be ${NOT_AUTO_ALLOWED}`;
default: {
const exhaustive = refusal;
return exhaustive;
}
}
}