UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

141 lines (140 loc) • 10.9 kB
/** * The shell text a program hands back to the shell: the text a shell program runs INSTEAD of its argv (`sh -c S`, * `eval S`, or its standard input where the argv names no script — {@link reentryText}), and the code bodies a builtin * keeps AMONG its operands to run later ({@link codeBodies}). The plan marks a segment carrying either as a shell * re-entry (`shell-read-plan.ts`); this module is the one reader of which operand — or which input — that text is. */ import { type LexedSegment, type LexedWord } from "../../shell/lex.js"; import { type ProgramPosition } from "./bash-program-position.js"; import { type TargetUser } from "./program-read-model.js"; import type { BoundValue } from "./shell-read-bindings.js"; /** * The code bodies a builtin keeps among its operands, one per body; empty for every other program. The program read * model's row says where the body is: the action of a `trap` (its first operand after the options), the value of an * `alias` definition, the shell-text option of `mapfile` / `readarray` / `compgen` (`-C`; compgen runs it at once, to * list completions). The text is re-entry text as `eval`'s operand is — the segment re-enters the shell, the command's * read set is not established, and each body is its own nested plan whose deny hits still refuse (one unreadable body * cannot hide another's). What a body does when it runs is not read from the text, so a body of any content asks: a * harmless `trap 'echo bye' EXIT` in front of a read asks as `eval 'echo bye'` does. The builtin's own words stay the * candidates they are for any unlisted program. An absent body is no re-entry (`trap - SIG` resets, `trap '' SIG` * ignores, `mapfile` without `-C`); an EMPTY alias value or `-C` value is still one. Over-reading is the direction * taken: a display option (`trap -p SIG`) and a lone operand (`trap SIG`) still count their operand as the body. */ export declare function codeBodies(basename: string, args: readonly string[]): readonly string[]; /** The shell text a re-entry program runs, by its row: the shell-text value its walk reads for a shell (`-c`'s), its * words joined for `eval`, its non-option words for `parallel`; none for `source` / `.` (a script file operand), for a * shell whose first operand (a script file) or `--` comes before any text, or for a program that is no re-entry. Where * the argv names no text and the program runs its standard input ({@link scriptSourceOf}: the row runs it unnamed, or * the script file named is standard input), the text is `stdin` — one text that may arrive there, as the caller spells it. */ export declare function reentryText(basename: string, args: readonly string[], stdin?: string): string | undefined; /** One argument of the argv a launcher hands the SHELL it runs: a word of the command line passed on as it stands (`at`, * its argv index; `from` — only the word's characters from there on, an option value attached to its option word), a word * of the launcher's own (`fixed`: the `-c` in front of a text it builds), or a text it builds from several words * (`joined`: their argv indices, joined by a space; `escaped` — every character of a word's value but a letter, a digit, * `_`, `-` and `$` escaped with a backslash, as `sudo -s` builds its shell's `-c` text). */ export type LaunchedArg = { readonly at: number; readonly from?: number; } | { readonly fixed: string; } | { readonly joined: readonly number[]; readonly escaped?: true; }; /** What a launcher that runs a program named by no word of its own runs: `program` — absent, a SHELL named by no word * (sudo's, chroot's, the user's login shell `su` runs); `at` — the word naming it (`su -s PROGRAM`, from its `from`-th * character where the value is attached to its option word); `spelled` — a program the launcher takes from elsewhere, * as a shell spells it (`su -m`: the caller's `"$SHELL"`) — and the argv it hands that program. */ export interface LaunchedCommand { readonly program?: { readonly at: number; readonly from?: number; } | { readonly spelled: string; }; readonly args: readonly LaunchedArg[]; /** The user the launcher runs it as, where its row says it runs another ({@link targetUserOf}). */ readonly as?: TargetUser; } /** * What a launcher that runs a program named by no word of its own runs ({@link readProgramPosition}'s `runsShell`: the * launcher, and the first word it hands on), one command per reading of the launcher's row where its implementations * read it differently; empty when no launcher of the segment runs one. A shell's argv is read by that shell's row * ({@link reentryText}, {@link scriptFileAt}): its `-c` text, its script file, else its standard input. By the launcher * row (`LauncherSpec`, program-read-model.ts): * · no word handed on (`sudo -s`, `chroot DIR`, a bare `su`): the shell is handed nothing; * · `shellCommand: "escaped"` (`sudo -s CMD…`): `-c` and the words, escaped and joined; * · `shellText: "joined"` (`watch CMD…`): `-c` and the non-option words, joined — and `-c` with each shell-text value * attached to its option word (`script -c'CMD'`); * · `shellText: "user"` (`su [-] [USER [ARG…]]`): per implementation — its first operand past a lone `-` names the user; * one that reads its options anywhere hands `-c TEXT` for each shell-text value of its own (a command each) and its * operands after the user, one that stops at the user hands every word after the user. The program is the value of * its option of role `command` (`-s PROGRAM`), else the program a selected `names-program` switch takes from the * caller's environment (`-m`: `"$SHELL"`), else the user's login shell. */ export declare function launchedCommands(argv: readonly LexedWord[], pos?: ProgramPosition): readonly LaunchedCommand[]; /** How a launched command is read: as a SHELL's argv (`shell`: the shell whose row reads it — a program that runs its * `-c` text, `sh`, `bash`, `fish`, …, or `sh` for the shell named by no word), as the command line it is (`line`: a * program that is no shell, `su -s /bin/rm …`, its words as written, so its expansions are the command line's), or * both — a program the text does not spell (`su -s "$X"`, `su -m`: the caller's `"$SHELL"`) may be any program, a shell * among them. */ export declare function launchedReadings(argv: readonly LexedWord[], command: LaunchedCommand): { readonly shell?: string; readonly line?: string; }; /** What a launcher runs ({@link launchedCommands}, {@link launchedReadings}), as a reader of the command line reads it in * one reading of its names: each shell's argv in two spellings — `texts` (a word's literal value, else `spell.text`) and * `args` (`spell.folded`) — with the argv index each position stands for (`words`), its words' fields read off * `spell.values` ({@link spellLaunched}) and the user it runs as (`as`); the word naming a shell (`su -s /bin/bash`, * `programWords`); and each command line a program that is no shell runs (`lines`, once each). */ export declare function launchedInput(argv: readonly LexedWord[], pos: ProgramPosition, spell: { readonly text: (k: number) => string; readonly folded: (k: number) => string; readonly values: (k: number) => readonly BoundValue[] | undefined; }): { readonly shells: readonly { readonly shell: string; readonly texts: readonly string[]; readonly args: readonly string[]; readonly words: readonly (number | undefined)[]; readonly as?: TargetUser; }[]; readonly programWords: readonly number[]; readonly lines: readonly string[]; }; /** How a reader spells the words of a command line it hands a launched shell ({@link spellLaunched}): `word` — a word as * it stands, from its `from`-th character; `values` — for a word the shell rewrites before the launcher sees it, the * values it may take where the reader knows them (each one field, or a pattern whose matches are the fields — its parts * say which characters the shell expands), `undefined` where it does not; absent — the reader knows none. */ export interface LaunchedSpelling { readonly word: (at: number, from: number) => string; readonly values?: (at: number) => readonly BoundValue[] | undefined; } /** * The argv a launched shell is handed ({@link launchedCommands}), spelled as `spell` spells the command line's words in ONE * reading of their names (the plan reads a segment once per combination of its names' values, `segmentReadingsOf` in * shell-read-word.ts — a word's value here is its value in that reading). A word an escaping launcher joins (`sudo -s * CMD…`) is handed as the launcher hands it: the FIELD the command line's shell gives the launcher, escaped * ({@link sudoEscaped}) — the field a literal word spells, or the field the shell established for a word it rewrites first * (`X='#'; sudo -s rm -r "$X" d` hands `rm -r \# d`: the value is an operand, never a comment), a pattern's literal * characters escaped and its pattern characters bare ({@link launchedSpellingOf}). A word whose field the reader does not * know is handed AS WRITTEN: the launched shell re-reads the expansion with the command line's quoting, so the word stays * one argument (an unknown there) and its neighbours stay theirs (`"#$Y" d` is never `#$Y d`). Any other launcher hands a * word's text as `spell.word` spells it. */ export declare function spellLaunched(argv: readonly LexedWord[], args: readonly LaunchedArg[], spell: LaunchedSpelling): string[]; /** The argv index of the SCRIPT FILE a shell runs where its argv carries no text ({@link scriptSourceOf}). */ export declare function scriptFileAt(basename: string, args: readonly string[]): number | undefined; /** One text that may arrive on a segment's standard input: `from` = its source as written (a here-document's body, * `<<<WORD`, `<FILE`, `<&N`, the segment the pipe comes from); `literal` = the text, where the command spells it — a * here-document or here-string written in the command; never a pipe's text, which a program PRODUCES. */ export interface StdinText { readonly from: string; readonly literal?: string; } /** What may arrive on a segment's standard input: each here-document (its descriptor is not read: every one counts) — * its body, literal when quoted or when nothing in it expands — each here-string (literal when its word is), `<` file * or `<&` descriptor written to no other descriptor, and what it inherits (`fed`: the pipe it is reached through, the * pipe or the input of the group it stands in, the input of the shell running its text) unless one of those three * replaces it. */ export declare function stdinTextsOf(segment: LexedSegment, fed: readonly StdinText[]): StdinText[];