UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

192 lines (191 loc) • 9.89 kB
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; } } }