UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

145 lines (144 loc) • 10.8 kB
/** * The ONE reader of a segment's PROGRAM POSITION: which argv word names the program bash runs, and what stands in * front of it — bare control words with their declared options, environment assignments, a launcher chain. * * Every face that judges a command by its program (the compound walk, the single-command boundary face, the * refused-command whitelist, the poll loop's base-move check) reads this ONE function. A face that needs the * answer it cannot give reads {@link ProgramPosition.refusal}: a declarative record of why the program is not * readable, minted into one sentence by {@link programPositionRefusalSentence}. Reading less than this reader * does (an option skipped, a chain member missed) is how one face used to vouch for a program another refused. */ import { SHELL_CONTROL_PREFIXES, type LexedWord } from "../../shell/lex.js"; /** The verdict every unresolvable / out-of-root / unbounded finding ends on. The findings differ in what * they found; the consequence is one consequence, and a copy that drifts reads as a second rule. */ export declare const NOT_AUTO_ALLOWED = "\u2014 not auto-allowed"; /** argv[0] as the program bash will resolve it: `/usr/bin/cat` and `cat` are one name. */ export declare function programBasename(word: string): string; /** * The `NAME=` / `NAME+=` prefix as WRITTEN in a whitespace-split token. This is NOT the position reader's grammar * — the reader reads the lexer's {@link LexedWord.assignment} fact — but the one reader that still walks the * command TEXT rather than its words (the activity summary) reads it here. It retires with it. */ export declare const ASSIGNMENT_PREFIX: RegExp; /** The prefix a whitespace-split token BEGINS with, or `undefined` — the text readers' view of * {@link ASSIGNMENT_PREFIX}; `name` is the variable before any subscript, `settlesWholeName` false for an * append and for a subscript. */ export declare function writtenAssignmentPrefix(token: string): { readonly prefix: string; readonly name: string; readonly appends: boolean; readonly settlesWholeName: boolean; } | undefined; /** The names a segment's COMMAND ENVIRONMENT sets for the program it starts: the assignment words in front of the * program (the lexer's {@link LexedWord.assignment} fact, a launcher's `NAME=value` operand included) — or `all` where * a launcher in front of it may set, clear or replace the environment (`env -u` / `-i`, `sudo`, `su`, …). */ export declare function commandEnvironmentNames(argv: readonly LexedWord[]): ReadonlySet<string> | "all"; export { SHELL_CONTROL_PREFIXES }; /** A control word is a keyword only when bash sees it BARE: `'if'` / `"do"` / `\\!` are command names (a program * looked up on PATH, whose failure changes nothing), so a quoted or escaped spelling never counts as one. */ export declare function isBareControlWord(words: readonly LexedWord[], i: number, set: ReadonlySet<string>): boolean; /** Does the shell read this word as syntax — a reserved word, a control word, `{`, `coproc` — of `set`? Only a word * written BARE where bash reads a command's first word (the lexer's {@link LexedWord.atCommandPosition}): a quoted or * escaped spelling is a command name, and a word an expansion produced (a value, a split field) is never syntax — * bash parses the line before it expands it. The one reading every syntax reader of a command's words asks. */ export declare function isShellSyntaxWord(w: LexedWord | undefined, set: ReadonlySet<string>): boolean; /** The words that head a loop over a list (`for NAME in …`, `select NAME in …`). */ export declare const LOOP_LIST_HEADS: ReadonlySet<string>; /** * A word bash strips as an ASSIGNMENT before it chooses the program: the lexer's assignment fact, written BARE * (the name, the brackets and the operator unquoted — a quoted VALUE still assigns, a quoted operator is a * program name), read where bash accepts one (the lexer's own command-position state, which this reader's walk * over control words and earlier assignments re-traces). One fact, two readers that must agree. */ export declare function isLeadingAssignmentWord(words: readonly LexedWord[], i: number): boolean; export declare const PROGRAM_POSITION_REFUSAL_KINDS: readonly ["option_at_program_position", "control_syntax_at_program_position"]; export type ProgramPositionRefusalKind = (typeof PROGRAM_POSITION_REFUSAL_KINDS)[number]; /** Why the program could not be read — a closed set, each member minted into one sentence. `owner` is the word * that took the offending token: the control word or the first launcher of the chain. */ export type ProgramPositionRefusal = { readonly kind: "option_at_program_position"; readonly owner: string; readonly option: string; } | { readonly kind: "control_syntax_at_program_position"; readonly owner: string; readonly word: string; /** Not a DECLINE ({@link programPositionRunsShell}): the row WAS read and a SHELL — or a program the launcher * names by an option of its own (`su -s PROGRAM`) — stands at the program position, run by the launcher at argv * index `launcher` and handed the words from index `from` on (none where `from` is the argv's end: `sudo -s`, * `chroot DIR`), as that launcher's row composes them (`launchedCommands`, shell-reentry-text.ts). Absent ⇒ * genuine control syntax, which IS one. */ readonly runsShell?: { readonly launcher: number; readonly from: number; }; }; export interface ProgramPosition { /** Indices of the bare control words stripped in front (`!`, `if`, `time`, …), in order; their declared options * are skipped with them. */ readonly controlWords: readonly number[]; /** The first index past the control words and their options — the WRITTEN command word: an assignment, a * launcher, or the program itself. `undefined` when nothing stands there. */ readonly written: number | undefined; /** The leading `NAME=value` words at `written`. */ readonly assignments: readonly number[]; /** The launcher chain past the assignments, in order (basenames); empty when the written word is not a launcher. */ readonly launchers: readonly { readonly index: number; readonly name: string; }[]; /** Some chain member is an argv re-entry program: the program's operands arrive on a stream. */ readonly argvFromStdin: boolean; /** The chain ends ON the re-entry program (nothing follows it), so it runs its default program. */ readonly runsDefaultProgram: boolean; /** The program the stream-fed chain member runs when the chain names none (`xargs` runs `echo`). */ readonly defaultProgram?: string; /** The program bash RUNS: the first word past the chain. `name` is the folded word as written, `basename` what * bash resolves (`/usr/bin/cat` is `cat`). `undefined` when the chain names none (a lookup, `command -v git`, runs * nothing) or the read was refused. */ readonly program: { readonly index: number; readonly name: string; readonly basename: string; } | undefined; /** Why no program could be read; `undefined` beside `program: undefined` means the chain simply names none. */ readonly refusal: ProgramPositionRefusal | undefined; /** A launcher of the chain moves the directory or the root the program runs in, or where its word is looked up * (`env -C <dir>`, `sudo -R <dir>`, `env -P <dirs>`): the program is still the word it names, but where its path * operands resolve is not the command line's working directory — each consumer reads that as it must. */ readonly launcherMovesBase: boolean; } /** * Read the program position of one segment's argv words. * * In order: bare control words and the options their launcher rows declare; the written word; its leading * assignments; then either the program itself or a launcher chain — whose first member's declared options are read * too (it is the launcher the line names), and inside which an assignment, a bare number, a nested launcher and an * argv re-entry program are stepped over, an option-shaped word (a lone `-` included — `env - <program>` clears * the environment and the table does not say so) refuses, and shell control syntax refuses. An option-shaped * WRITTEN word (judged on its basename, as the program it names) refuses the same way: it is where the program * would be, and the word in front took an option no row explains. */ export declare function readProgramPosition(words: readonly LexedWord[]): ProgramPosition; /** Does this program position hand a program operands the walk cannot see? True when the chain re-enters through * stdin AND the program it runs is not one the caller accepts as a stream target. ONE reading, shared by every * consumer; WHICH targets are accepted is the caller's declaration, not this reader's. */ export declare function argvReentryUnjudged(pos: ProgramPosition, acceptsTarget: (basename: string) => boolean): boolean; /** * The two ways this reader DECLINES rather than judges — a CLOSED set (`docs/CLOSED-SETS.md`), because what a * seat does with a declined segment is a ROW of the shell read decision table (`shell-read-judge.ts`, the * `declined` row: the one row the seats disagree on), not a boolean buried in a caller. * `unreadable_program_position`: the walk cannot name the program bash will run (`env -- ls`). * `stream_fed_operands`: the operands arrive on a STREAM it never saw (`… | xargs cat`). Neither names a WORD. */ export declare const SHELL_SCAN_DECLINES: readonly ["unreadable_program_position", "stream_fed_operands"]; export type ShellScanDecline = (typeof SHELL_SCAN_DECLINES)[number]; /** Does a SHELL stand where this segment's program would be — as the program itself (`sh -c`), or as a launcher * ROW handing its operands to one (`watch "…"`)? ONE predicate for the re-entry class, so the two spellings * cannot be answered differently; which names are shells is the caller's declaration. Not a decline. */ export declare function programPositionRunsShell(pos: ProgramPosition, runsShell: (basename: string) => boolean): boolean; /** Which decline, if any, this program position raises — ONE reading of the two facts above, so no consumer * re-derives either. A refusal that says a SHELL runs is NOT one: it names what runs. */ export declare function shellScanDeclineOf(pos: ProgramPosition, acceptsStreamTarget: (basename: string) => boolean): ShellScanDecline | undefined; /** ONE sentence per refusal kind, minted here and nowhere else. */ export declare function programPositionRefusalSentence(refusal: ProgramPositionRefusal): string;