UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

193 lines • 10.8 kB
import type { ExecutionFrame } from '../frame.ts'; import type { SharedInput } from '../../io/async_line_iterator.ts'; import { type ByteSource, IOResult } from '../../io/types.ts'; import type { ExecutionNode, StatusWriter } from '../types.ts'; import { type Recorder, type StreamOwner } from '../../shell/descriptors.ts'; import { Channel, type JobConsole } from '../../shell/console/index.ts'; import { type TSNodeLike } from '../../shell/types.ts'; import type { SessionState } from '../session/session.ts'; /** * Run a test, the left of `&&`/`||` or a negated command where bash ignores * `set -e`: nothing it runs exits for a failure, a function body or a * subshell included. Mirrors Python's ignoring_errexit. */ export declare function ignoringErrexit<T>(session: SessionState, fn: () => Promise<T>): Promise<T>; /** Whether `set -e` ends the shell after this statement, marking the shell * as ending when it does. */ export declare function errexitActs(node: TSNodeLike, status: number, session: SessionState): boolean; /** * Record a finished statement's exit status: `$?` and `${PIPESTATUS[@]}` * together. * * The one function every status write goes through, so the two can never * disagree. `handlePipe` parks its per-segment statuses on the session, * and the boundary that closes the pipeline claims them here; a boundary * with nothing parked stamps its own one-element status, which is what a * simple command, a function call or a subshell leaves in bash. A * *transparent* statement (a group, a loop, a negation, a redirected * pipeline: see `pipelineTransparent`) claims what was parked but never * overwrites, because bash reports the last pipeline that ran *inside* it * (`{ false | true; }` keeps `1 0`). */ export declare function recordStatus(session: SessionState, code: number, transparent?: boolean): void; /** * The status a line found, taken before its first statement runs and * put back if the caller aborts the line. * * An aborted invocation is the caller's outcome, not the shell's, so it * must leave `$?` where it was. But the abort lands on one await inside * the line, and every statement before that await has already stamped * through `recordStatus`. The status write refuses a statement that * settles after the caller was released; this is for the ones that * landed before it, and only a copy taken before the line can undo them. * * The three fields travel together because they are one shell fact: * `$?`, `${PIPESTATUS[@]}`, and the per-segment statuses a pipeline * parked for its boundary to claim. Restoring one without the others * would leave a state no bash line produces. */ export interface StatusSnapshot { lastExitCode: number; pipeStatus: readonly number[]; pipeStatusPending: readonly number[] | null; } /** Capture `$?` and `${PIPESTATUS[@]}` before a line runs. */ export declare function snapshotStatus(session: SessionState): StatusSnapshot; /** * Put back the status a line found, for a line the caller aborted. * Statements inside the line may already have stamped their own status * before the abort landed, and an aborted invocation is the caller's * outcome, not the shell's. * * Only what this line overwrote, though. Two `execute()` calls can * share a session, and a snapshot taken before a concurrent line * finished is older than that line's result: putting it back would * resurrect a value the shell had already moved past. So the restore * happens only while the last stamp is still this line's. When nobody * has stamped since the snapshot the status already equals it and * declining is the same thing; when someone else did, declining is the * point. */ export declare function restoreStatus(session: SessionState, snapshot: StatusSnapshot, writer: StatusWriter | null): void; /** * Park the status just recorded again, for the boundary that closes the * enclosing statement to claim rather than stamp over. A conditional * list that short-circuits has closed its left pipeline and runs * nothing else, and bash reports the list as that pipeline: * `true | false && true` keeps `0 1`. The list is not a pipeline of its * own, so without this its boundary would stamp the aggregate `1`. */ export declare function carryStatus(session: SessionState): void; /** * Whether bash forks to run a command: a subshell or a program, whose * redirects expand in the child and whose closed pipe ends it alone; a * builtin, a function or a compound command runs in the shell itself. * `command -v` is the builtin, `command X` masks functions. The expanded * `words` decide once there are any; before, a name only an expansion spells * (`$cmd`) counts as a program. Mirrors Python's forks. */ export declare function forks(command: TSNodeLike | null, session: SessionState, words?: readonly string[] | null): boolean; /** * Whether what a statement wrote came from a child bash forks for it, so a * closed pipe ends that writer alone: a program, a subshell or a pipeline, * redirected or negated too. A command is judged by the producer its output * names, what its head ran as (`$cmd`). Mirrors Python's writer_forks. */ export declare function writerForks(statement: TSNodeLike | null, session: SessionState, io: IOResult): boolean; /** * Deliver a writer's own answer to its stdout's reader going away (`tee -p`) * in place of SIGPIPE, if it has one. Mirrors Python's answer_closed. */ export declare function answerClosed(io: IOResult, emit: (channel: Channel, data: Uint8Array) => Promise<unknown>): Promise<boolean>; /** * Finalize a completed statement and seed $? for the next one. * * Every statement boundary must do the same dance: apply a VALUE * barrier so lazily finalized exit codes (grep's) are * concrete, then record the status the next statement's $? expands * to. Statement-list loops (program, subshell, brace group, if/loop/ * case bodies, function bodies, && / || / ; lists) call this instead * of hand-rolling the triple, so a new construct cannot forget it. The * node, when the caller has it, decides whether the statement stamps * `PIPESTATUS` itself; without one it stamps. */ export declare function finishStatement(stdout: ByteSource | null, io: IOResult, session: SessionState, node?: TSNodeLike | null, execNode?: ExecutionNode | null): Promise<ByteSource | null>; /** * A read the statement's output stream failed, as its own failure: `cat: -: * Bad file descriptor` on its stderr and status. Anything but a filesystem * error is rethrown. Mirrors Python's failed_read. */ export declare function failedRead(io: IOResult, err: unknown, execNode: ExecutionNode | null): Promise<void>; /** * Exit status of an assignment-only statement. * * Bash: an assignment statement exits 0 unless expanding it ran * command substitutions, in which case the status of the last * substitution performed becomes the statement's own. */ /** * What the shell's fd 0 is bound to: the descriptor `exec <` opened, and * whether an `exec` left it unreadable (`exec <&-`). A construct takes * this as it starts, so `statementStdin` can tell an `exec` made inside * it from one made before it. */ export declare function fd0Binding(session: SessionState): readonly [SharedInput | null, boolean]; /** * The stdin one statement of a construct reads. * * bash's fd 0 is one descriptor, so an `exec <` replaces whatever a * construct was handed: `printf z | { exec < f; read a; }` reads `f`. * Every statement-list loop asks this before each statement. The * construct's own stdin stands while fd 0 is still what it was when the * construct started (`bound`); a construct handed none reads fd 0, the * descriptor `exec <` opened or EBADF after `exec <&-`. */ export declare function statementStdin(session: SessionState, stdin: ByteSource | null, bound: readonly [SharedInput | null, boolean]): ByteSource | null; export declare function assignmentStatus(frame: ExecutionFrame, seqBefore: number): number; /** * One piece of what a statement wrote, in order: its channel, its bytes, and * whether it went to the terminal through a copy, which an `exec` diversion of * the shell's own output leaves where it is. Mirrors Python's Written. */ export type Written = readonly [Channel, ByteSource, boolean]; /** * Output written as it is, stdout then stderr, none of it through a copy of * the terminal. Mirrors Python's as_written. */ export declare function asWritten(stdout: Uint8Array | null, stderr: Uint8Array | null): Written[]; /** * Run a statement into `recorder`: what it writes to an enclosing level's * stream lands among what it writes. Mirrors Python's recording. */ export declare function recording<T>(recorder: Recorder, run: () => Promise<T>): Promise<T>; /** * Send what the jobs this shell started write into `recorder`, among what its * statement writes, until the returned call ends it. The recording lasts past * the statement, until `landLate` has landed what was recorded, so a job * writing in between cannot reach the shell's own output ahead of the * statement. Ended with `after`, what jobs write next waits for it. Mirrors * Python's record_jobs. */ export declare function recordJobs(session: SessionState, recorder: Recorder): (after?: Promise<void>) => void; /** * Land what jobs wrote into `recorder` once the statement's output was taken, * and end the recording (`recordJobs`). What jobs write from the end on waits * until what was recorded has landed, so nothing written later overtakes it, * and nothing is left in `recorder` unread. Mirrors Python's land_late. */ export declare function landLate(recorder: Recorder, leave: (after?: Promise<void>) => void, own: StreamOwner | null, sink: JobConsole | null, allStdout: (ByteSource | null)[], mergedIo: IOResult): Promise<IOResult>; /** * What a statement wrote that stays with the shell running it, taken off * `recorder`. Bytes written to the shell's terminal through a copy (`exec * 3>&1`, whose owner is `own`) stay, flagged; bytes written to an enclosing * level's stream go on there. What the statement returned rather than wrote * comes last, its stderr taken off `io`. Mirrors Python's statement_output. */ export declare function statementOutput(recorder: Recorder, stdout: ByteSource | null, io: IOResult, own: StreamOwner | null, sink: JobConsole | null): Promise<Written[]>; /** * Put a statement's output where its shell's goes: the sink, in order, or the * stdout and stderr the shell returns. The result keeps its status. Mirrors * Python's land. */ export declare function land(written: readonly Written[], sink: JobConsole | null, allStdout: (ByteSource | null)[], mergedIo: IOResult): Promise<IOResult>; //# sourceMappingURL=statement.d.ts.map