UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

81 lines (80 loc) • 6.82 kB
/** * Where a text the read plan (`shell-read-plan.ts`) plans starts: a nested text's start — its names, the deny floors the * words handing it on carry, the user its shell runs as, its standard input and its working directory — and where each * segment's standard input comes from. A nested plan parsed against the declarations' working directory is re-based onto * the segment it sits in ({@link rebasePlan}), unless it starts in a working directory of its own. */ import type { LexedSegment } from "../../shell/lex.js"; import type { BindingTable, FloorValue, InheritedFunctions, NameReader } from "./shell-read-bindings.js"; import type { TargetUser } from "./program-read-model.js"; import type { StdinText } from "./shell-reentry-text.js"; import type { ReadCandidate, ShellReadPlan } from "./shell-read-plan.js"; /** Where a nested text starts its names: a substitution body inherits the table of the segment it sits in (a subshell * holds every variable of the shell that forks it); a text the shell runs later or elsewhere (a code body, `eval`'s * text, a child shell's `-c`) starts from the text's own start — every name unknown, `HOME` the declared home unless * the enclosing text binds it anywhere. `boundAnywhere` answers for the enclosing texts. */ export interface PlanStart { readonly initial: NameReader; readonly boundAnywhere: (name: string) => boolean; /** `HOME` may already hold something other than the declared home where the nested text starts. */ readonly homeTouched: boolean; /** The attributes, name references and deny floors in force where the nested text starts. */ readonly inherited: BindingTable["inherited"]; /** The working directories it starts in, where they are not the enclosing segment's (a login shell starts in its user's * home; `"unmodelled"` — a home the text does not settle). */ readonly bases?: readonly string[] | "unmodelled"; /** The thread of the shell that runs the text, where the text runs in that shell or a fork of it ({@link HeldThread}). */ readonly thread?: HeldThread; /** What the shell around the text holds where the text runs in that shell or a fork of it — a literal `eval` text, a * substitution body, a text run later (a `trap` action): each function and builtin key as it stands there * ({@link InheritedFunctions}). A text a new process runs (a child shell's `-c`) holds none: every builtin enabled, no function. */ readonly names?: InheritedFunctions; /** What arrives on its own standard input (the input of the shell running it). */ readonly stdin?: readonly StdinText[]; } /** The thread of the shell that runs a text, at the segment that hands it on: a literal `eval` text is not a new plan but the next * segment of the same thread, so it starts in all of it and hands the working directory and the physical mode back; a substitution * body (a fork) and a text run later start in what the shell holds and hand nothing back. `functions`: the names a call may mean * (the shell's definition heads and the ones an `eval` text defines), `unread` those whose bodies the nested plan has not read — an * `eval` text reads the shell's bodies through the names they bind, every other text has none of them in front of it. */ export interface HeldThread { readonly physical: boolean; readonly functions: ReadonlySet<string>; readonly unread: ReadonlySet<string>; } /** What a shell holds at a segment, for a text that runs in it or in a fork of it ({@link PlanStart.names}): each function and builtin key * as it stands there, and the names a call to one of its functions may change — the ones the text reads a call of: an `eval` text names * the shell's functions (`bound`: what a call to one of them changes); a substitution body and a text run later read none of their bodies * (the shell's functions are {@link HeldThread.unread} there: a call to one makes every name unknown), so they name none. */ export declare function heldAt(table: BindingTable, bound?: InheritedFunctions["bound"]): InheritedFunctions; /** What a segment says of the start of a text it hands a shell to run later: the deny floors of the words it holds as * written, the user the shell runs as ({@link TargetUser}), and the shell's standard input. */ export interface NestedStart { readonly floors?: ReadonlyMap<string, readonly FloorValue[]>; readonly as?: TargetUser; readonly stdin?: readonly StdinText[]; } /** Where a text run later starts ({@link NestedStart}): the later start, with the shell's standard input, the floors of the * words that hand it on, and — run as another user — that user's `HOME` (the caller's, the target's, or either) and, for * a login shell, the target's home as its working directory. */ export declare function startedAt(later: PlanStart, at: NestedStart | undefined): PlanStart; /** A planner that plans each text once per start: a segment read in several readings hands most of its nested texts (a * substitution's body, a launched shell's text) on alike in every reading. */ export declare function plannedOnce<K, P>(plan: (text: string, kind: K, at?: NestedStart) => P): (text: string, kind: K, at?: NestedStart) => P; /** * What arrives on each segment's standard input: the pipe it is reached through (`piped`, what the segment before it * prints); else the input of the group (`{ …; }`, `( … )`) it stands in — a group a pipe feeds hands each member that * pipe; else the input the text itself was handed (`start`: a shell's `-c` text reads that shell's standard input). */ export declare function stdinFeeds(segments: readonly LexedSegment[], start: readonly StdinText[], piped: (si: number) => readonly StdinText[]): (si: number) => readonly StdinText[]; /** * A nested plan (a substitution body, a here-document's substitution, a launcher's command string) was parsed * against the declarations' working directory; the segment it sits in may stand elsewhere (`cd sub && cat <<EOF * $(cat secret)`), so its relative candidates are re-based onto the ENCLOSING segment's bases — every base the * nested text derived from the start is carried over by prefix, an unsettled enclosing base leaves every * relative candidate unplaced, and absolute candidates are untouched. Recursive: a nested plan's own nested plans * derive from the same start. A candidate left unplaced is judged where the text stood when it lost track (its floor, * {@link ReadCandidate.floorBases}): the places the nested text was in move with it like its bases, and an enclosing segment that * lost its base itself hands on its own floor (`floor`). */ export declare function rebasePlan(plan: ShellReadPlan, from: string, to: ReadCandidate["bases"], floor?: readonly string[]): ShellReadPlan;