@sema-agent/core
Version:
Stateless, task-oriented AI agent core
192 lines (191 loc) • 9.89 kB
JavaScript
import { SHELL_CONTROL_PREFIXES } from "../../shell/lex.js";
import { shellWordsOf } from "../../shell/shell-words.js";
import { BLOCK_WORD_NAMES, LAUNCHER_NAMES, feedsArgvFromStdin, launcherOptionMovesBase, launcherOptionQueries, launcherOptionStep, launcherRunsShell, programRow, selectedOptions, streamDefaultProgram } from "./program-read-model.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 };
}
const ENVIRONMENT_KEEPING_LAUNCHERS = new Set(["nice", "nohup", "time", "timeout", "stdbuf", "ionice", "chrt", "taskset", "command", "builtin", "caffeinate"]);
export function commandEnvironmentNames(argv) {
const pos = readProgramPosition(argv);
if (pos.launchers.some((l) => !ENVIRONMENT_KEEPING_LAUNCHERS.has(l.name)))
return "all";
return new Set(argv.slice(0, pos.program?.index ?? argv.length).flatMap((w) => (w.assignment?.bare === true ? [w.assignment.name] : [])));
}
export { SHELL_CONTROL_PREFIXES };
export function isBareControlWord(words, i, set) {
return isShellSyntaxWord(words[i], set);
}
export function isShellSyntaxWord(w, set) {
return w !== undefined && w.atCommandPosition && w.raw === w.folded && set.has(w.folded);
}
export const LOOP_LIST_HEADS = shellWordsOf("loop_list_head");
export function isLeadingAssignmentWord(words, i) {
const w = words[i];
return w !== undefined && w.assignment !== undefined && w.assignment.bare && w.atCommandPosition;
}
function launcherRowOf(name) {
const row = programRow(name);
return row?.launcher !== undefined ? row : undefined;
}
function pastLauncherOptions(launcher, folded, i, moved, queried) {
const row = launcherRowOf(launcher);
if (row === undefined)
return i;
for (;;) {
const tok = folded[i];
if (tok === undefined)
return i;
if (row.launcher?.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;
if (launcherOptionMovesBase(row, tok))
moved?.();
if (launcherOptionQueries(row, tok))
queried?.();
i += step;
if (tok === "--")
return i;
}
}
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, launcherMovesBase: false });
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 (!LAUNCHER_NAMES.has(basename)) {
return { controlWords, written, assignments, launchers: [], argvFromStdin: false, runsDefaultProgram: false, program: { index: i, name: head, basename }, refusal: undefined, launcherMovesBase: false };
}
const launchers = [{ index: i, name: basename }];
const owner = head;
const shellRefusal = (launcher, from) => ({ kind: "control_syntax_at_program_position", owner, word: "sh", runsShell: { launcher, from } });
let lastRow = launcherRowOf(basename)?.launcher;
let owedOperands = lastRow?.requiredOperands ?? 0;
const chain0 = (program, refusal) => ({ controlWords, written, assignments, launchers, argvFromStdin: false, runsDefaultProgram: false, program, refusal, launcherMovesBase: false });
if (launcherRunsShell(launcherRowOf(basename), folded.slice(i + 1)))
return chain0(undefined, shellRefusal(i, i + 1));
let argvFromStdin = feedsArgvFromStdin(basename);
let lastReentry = argvFromStdin ? i : -1;
let reentryDefault = argvFromStdin ? streamDefaultProgram(basename) : undefined;
let launcherMovesBase = false;
const moved = () => { launcherMovesBase = true; };
let looksUp = false;
const queried = () => { looksUp = true; };
const chain = (program, refusal) => {
const runsDefaultProgram = lastReentry === folded.length - 1;
return { controlWords, written, assignments, launchers, argvFromStdin, runsDefaultProgram, ...(runsDefaultProgram && reentryDefault !== undefined ? { defaultProgram: reentryDefault } : {}), program, refusal, launcherMovesBase };
};
const selectsShell = (l, end) => selectedOptions(launcherRowOf(l.name), folded.slice(l.index + 1, end)).some((o) => o.mode === "interactive");
const first = pastLauncherOptions(basename, folded, i + 1, moved, queried);
if (looksUp)
return chain(undefined);
for (let j = first; 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;
if (lastRow?.shellCommand !== undefined && !t.startsWith("-") && selectsShell(launchers[launchers.length - 1], j))
return chain(undefined, shellRefusal(launchers[launchers.length - 1].index, j));
const wordBasename = programBasename(t);
if (feedsArgvFromStdin(wordBasename)) {
argvFromStdin = true;
lastReentry = j;
reentryDefault = streamDefaultProgram(wordBasename);
lastRow = undefined;
owedOperands = 0;
continue;
}
if (LAUNCHER_NAMES.has(wordBasename)) {
launchers.push({ index: j, name: wordBasename });
lastRow = launcherRowOf(wordBasename)?.launcher;
if (launcherRunsShell(launcherRowOf(wordBasename), folded.slice(j + 1)))
return chain(undefined, shellRefusal(j, j + 1));
owedOperands = lastRow?.requiredOperands ?? 0;
j = pastLauncherOptions(wordBasename, folded, j + 1, moved, queried) - 1;
if (looksUp)
return chain(undefined);
continue;
}
if (t.startsWith("-"))
return chain(undefined, { kind: "option_at_program_position", owner, option: t });
if (SHELL_CONTROL_PREFIXES.has(wordBasename) || BLOCK_WORD_NAMES.has(wordBasename)) {
return chain(undefined, { kind: "control_syntax_at_program_position", owner, word: wordBasename });
}
return chain({ index: j, name: t, basename: wordBasename });
}
const last = launchers[launchers.length - 1];
if (lastRow?.whenNoProgram === "shell" || selectsShell(last, folded.length))
return chain(undefined, shellRefusal(last.index, folded.length));
return chain(undefined);
}
export function argvReentryUnjudged(pos, acceptsTarget) {
if (!pos.argvFromStdin)
return false;
const runs = pos.program?.basename ?? pos.defaultProgram;
return runs === undefined || !acceptsTarget(runs);
}
export const SHELL_SCAN_DECLINES = Object.freeze(["unreadable_program_position", "stream_fed_operands"]);
export function programPositionRunsShell(pos, runsShell) {
if (pos.refusal !== undefined)
return pos.refusal.kind === "control_syntax_at_program_position" && pos.refusal.runsShell !== undefined;
return pos.program !== undefined && runsShell(pos.program.basename);
}
export function shellScanDeclineOf(pos, acceptsStreamTarget) {
if (pos.refusal !== undefined) {
return pos.refusal.kind === "control_syntax_at_program_position" && pos.refusal.runsShell !== undefined ? undefined : "unreadable_program_position";
}
return argvReentryUnjudged(pos, acceptsStreamTarget) ? "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;
}
}
}