@sema-agent/core
Version:
Stateless, task-oriented AI agent core
141 lines (140 loc) • 10.9 kB
TypeScript
/**
* 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[];