@sema-agent/core
Version:
Stateless, task-oriented AI agent core
145 lines (144 loc) • 10.8 kB
TypeScript
/**
* 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;