@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
193 lines • 10.8 kB
TypeScript
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