@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
390 lines • 20.7 kB
TypeScript
import type { SessionView } from '../../view/types.ts';
import { type Policies } from '../../policy/index.ts';
import type { CallStack } from '../../shell/call_stack.ts';
import { type ShellArray } from '../../shell/array.ts';
import type { ArithResult, ElementOps } from '../../shell/types.ts';
import type { ShellValue, ShellVar } from '../../shell/variable.ts';
import { VarAttr } from '../../shell/variable.ts';
import type { SessionState } from './session.ts';
/**
* The one copy-out of a session's environment.
*
* Every tier that hands the env onward as a process view (a command's or
* CLI's or a runtime's `ctx.env`, the `env` builtin) copies
* through here, so the hidden-vars filter lands on all of them by
* construction rather than on however many hand-rolled copies someone
* remembers. The copy keeps the null prototype session records carry.
*
* *Exported* names only, which is what makes this the process view
* rather than a second spelling of `visibleEnv`. bash puts a variable
* in a child's environment when it carries the export attribute, not
* when it happens to hold a string: `X=hello` is absent from `env` and
* `export Y=world` is present. An unset name carrying the attribute
* (`export Z`) is absent too, which falls out of the value check
* rather than needing its own arm.
*
* Diverges from bash on one point: bash also carries each function
* `export -f` marked, as a `BASH_FUNC_NAME%%` entry. mirage hands those to
* a nested shell directly (`SessionState.newShell`), so neither `env` nor a
* runtime lists them.
*/
export declare function envSnapshot(session: SessionState): Record<string, string>;
/**
* The name a `declare -n` reference points at, null otherwise. Null
* also for a reference declared but not yet aimed (`declare -n r`
* before `r=v`): bash treats the first assignment as naming the target,
* so until then it stands for nothing.
*/
export declare function namerefTarget(session: SessionState, name: string): string | null;
/**
* The variable a name stands for, following `declare -n` chains. A name
* that is not a reference is its own answer. A chain that comes back to
* itself (`declare -n a=b; declare -n b=a`) is bash's circular name
* reference, read as unset: it resolves to the empty name, which no
* record has, so a reader sees unset and a writer falls back to the
* reference's own record. The warning line is the one part not
* reproduced.
*/
export declare function deref(session: SessionState, name: string): string;
/** The variable's value, null when unset or hidden. Sync on purpose:
* `$X` expansion is the hot path, so a read stays a record lookup plus
* the hidden check. A name reference reads its target. */
export declare function envGet(session: SessionState, name: string): string | null;
/**
* Whether `readonly` has marked the name.
*
* A hidden name answers false: isReadonly speaks about the session's
* visible world, and calling a name that reads as unset "readonly"
* would leak it. `followRef` asks about what a `declare -n` reference
* points at; a write to the reference itself (`declare -n r=w`,
* `unset -n r`) asks about the reference.
*/
export declare function envIsReadonly(session: SessionState, name: string, followRef?: boolean): boolean;
/**
* The env mapping a reader tier should resolve names against.
*
* Always a filtered copy, never `session.env`: that getter is itself a
* projection built fresh per access, so handing it out would copy the
* store anyway and freeze the answer at that moment. TS diverges from
* python's lazy mapping view deliberately: expansion sites read records
* with plain property access, so a copy is the shape they already
* consume, and env sizes make the copy cost noise.
*
* The *shell* view, and no longer a synonym for `envSnapshot`: this is
* what `$X`, arithmetic, `[[ ]]`, `IFS` and the bare `set` listing
* resolve against, and they see every variable, exported or not. The
* two were the same function while the process view was also "every
* string", and reusing it once the process view narrowed would have
* stopped `$X` resolving a plain assignment. python kept them separate
* all along (`_VisibleEnv` beside `env_snapshot`); this is TS catching
* up to it.
*/
export declare function visibleEnv(session: SessionState): Record<string, string>;
/**
* The arrays mapping a reader tier should resolve names against.
*
* The arrays twin of `visibleEnv`: the embedder can seed
* `session.arrays` before narrowing, so a hidden name can hold an
* array and array reads need the same filter env reads get.
*/
export declare function visibleArrays(session: SessionState): Record<string, ShellArray>;
/**
* The associative arrays a reader tier should resolve names against.
*
* The third sibling beside `visibleEnv` and `visibleArrays`, for the
* same reason both exist: the embedder can seed a hidden name with any
* value shape, so every reader tier filters the same way.
*/
export declare function visibleAssocs(session: SessionState): Record<string, Record<string, string>>;
/**
* Write one variable through the session plane's gate.
*
* General over variable shapes: a string stores a scalar, a ShellArray
* stores a whole array, and the two storages stay exclusive. Semantics
* live here once — the hidden refusal, readonly refusal, the
* `preSession` policy gate (whose context value renders an array as
* its present elements joined by spaces), then the store — so every
* writer states them the same way whichever tier or spelling asked.
* Writers with richer mechanics (subscripts, appends, holes) compute
* the resulting value on a copy and hand it here, so a denial never
* leaves a half-applied write. Null policies gate nothing (a writer
* outside a workspace). Throws PolicyDenied when the name is hidden
* for this session (a landed write would clobber the real value the
* host's wiring still reads; a swallowed one would gaslight the
* writer — the vars twin of EACCES on a create into hidden path
* space), ReadonlyVariableError when the name is readonly, and
* PolicyDenied when a preSession policy refuses the write.
*/
/**
* Refuse a write that names a hidden variable.
*
* The sync half of `setVar`'s hidden gate, shared with the
* expansion-time writers that land on the raw env (`${X:=d}`,
* `$((X=5))`, `printf -v`): a landed write would clobber the real
* value the host's wiring still reads, and a swallowed one would
* gaslight the writer; refuse loudly instead, the vars twin of EACCES
* on a create into hidden path space.
*/
/**
* Remove one surrounding quote pair from an associative subscript.
*
* An arithmetic reference carries its subscript verbatim, so `m["x"]`
* arrives with the quotes bash would have removed; one layer comes off
* and anything else is the key itself.
*/
export declare function stripKeyQuotes(text: string): string;
/** Element callbacks bound to one session, for `evaluateArith`. */
export declare function sessionElements(session: SessionState): ElementOps;
/**
* An indexed subscript resolved outside an arithmetic expression:
* `${a[i]}`, `a[i]=v`, `unset 'a[i]'`, `[[ -v a[i] ]]`.
*
* The subscript is arithmetic, so it may assign (`a[x=3]`) and seed
* (`a[RANDOM=42]`), and bash binds those as it evaluates them. Each
* lands through the session view once the index is known, then the `RANDOM`
* reader replays the draws made after the seed. A subscript that fails
* to evaluate lands what it assigned before failing and then throws, the
* subscript text leading the message, since bash aborts the line on it
* (`${a[1/0]}` is `1/0: division by 0`) rather than reading element 0.
* `view` is the gated session view; null lands the writes ungated, outside a
* workspace. Throws what the session view throws too: a PolicyDenied, or an
* ArithError from a `-i` name refusing the value; a readonly name ends the
* shell wherever a subscript is (`${a[R=3]}`): ExitSignal.
*/
export declare function subscriptIndex(session: SessionState, subscript: string, view?: SessionView | null): Promise<number>;
/**
* The `-i` coercion: evaluate the incoming text as arithmetic.
*
* Reads resolve against the visible env, so `n=x+1` sees `x`, and
* element references resolve through the session's resolver, so
* `n=a[1]+1` and `n=m[k]+1` see the element; an unresolvable name is 0
* (`n=abc` stores `0`), which is the arithmetic rule, not a refusal. A
* malformed expression throws ArithError with the offending text led,
* which is how every caller voices it (`bash: 1+: syntax error: operand
* expected`), so it is spelled once here rather than at each of the
* sites that catch it.
*/
/** Evaluate a host-supplied seed; invalid arithmetic propagates. Read
* without the generator on offer: a host word naming `RANDOM` would
* otherwise draw, and the draw reseed, without end. */
export declare function seedFrom(word: string, session: SessionState): number;
/** Draw from the session generator, or null after RANDOM is unset.
* Shell assignments validate and seed at the session view. A host-seeded
* variable is consumed here on its first read. Reseeding resets repeat
* suppression to zero independently of the stored word. */
export declare function nextRandom(session: SessionState, stored: string | undefined): number | null;
/**
* Arithmetic's reads of `$RANDOM`, bound to one session.
*
* A read before the expression assigns `RANDOM` draws from the session
* generator. bash seeds at the instant of an assignment and every later
* read draws from the new seed (`$((RANDOM=42, RANDOM))` is the first
* draw after seeding with 42). Here the assignment is still pending at
* the session view, which lands it gated after evaluation, so the
* evaluator tells the reader of each assignment as it is made (`wrote`),
* the reader seeds a scratch generator the way the session view will and draws
* from that, and `settle` replays the draws on the session once the
* session view has seeded it: the session ends where bash's does, seeded and
* advanced by every read since the last assignment, and the write still
* reaches the gate as the assignment it is. Each assignment restarts
* the scratch generator and the count, since the session view lands only the
* last value written, and the draws are replayed only if the session view did
* land it: an assignment the caller never applied leaves the session as
* it was.
*
* Lives beside the session view rather than with the generator because the
* session view needs it too: `RANDOM=RANDOM` draws once while the seed is
* evaluated, then seeds with the draw, as bash does: it reads an
* assigned seed as an arithmetic expression.
*/
export declare class RandomReader {
private readonly session;
private seeded;
private state;
private last;
private draws;
constructor(session: SessionState);
private special;
/** The dynamic value of a name, null for a name that has none. */
readonly read: (name: string) => string | null;
/** Note an assignment the expression made: the name and its value, an
* integer's text. */
readonly wrote: (name: string, value: string) => void;
/** Replay the scratch draws on the session generator, once the session view
* has seeded it with the value the expression assigned. */
settle(): void;
}
/**
* End `RANDOM`'s special meaning when a non-string lands on it.
*
* Once bash turns `RANDOM` into an array it neither draws nor seeds,
* so `RANDOM=(1 2)`, `declare -a RANDOM`, `RANDOM[1]=5` and
* `RANDOM+=(3)` all leave an ordinary array that `$RANDOM` reads element
* 0 of, for good, as `unset RANDOM` does. Every store entry point calls this,
* gated or not, since a host seeding an array onto the name means the
* same thing.
*/
export declare function noteRandomKind(session: SessionState, name: string, value: ShellValue): void;
/**
* The scalar an array conversion keeps as element 0.
*
* When bash turns a variable into an array, its current value becomes
* element 0, and for a live `RANDOM` looking the name up is what draws:
* `RANDOM[1]=5` leaves `[0]` holding one draw and `declare -a RANDOM` one
* alone, after which the array is ordinary.
*/
export declare function conversionScalar(session: SessionState, name: string): string | undefined;
/** Bind arithmetic `$RANDOM` reads to a session. */
export declare function randomReader(session: SessionState): RandomReader;
/**
* Evaluate `text` as every arithmetic context of the shell does: against
* the visible env and the session's elements, drawing through `reader`, and
* stopping at a write to a readonly name, as bash's evaluation does
* (`(( X=5, R=3 ))` binds X and refuses R). Throws ArithError when the text
* does not evaluate, and ReadonlyVariableError, carrying the writes made before it,
* for a readonly name. `added` is an integer `+=`'s added text, read after
* `text` in the same evaluation and added to it.
*/
export declare function sessionArith(session: SessionState, text: string, reader: RandomReader, nounset?: boolean, added?: string | null): ArithResult;
/**
* Evaluate `text` as an `-i` write coerces it, land what it assigns through
* `view`, and give back the value: a `declare -ni r=M` value, which bash
* evaluates before refusing the reference (`M='X=5'` sets X), and its
* `r+=M` form, whose `added` text evaluates after `text` in the same
* evaluation and adds to it. Inside a `declare -g` it reads the
* function's scope, as the coercion does. A hidden name throws
* PolicyDenied and a readonly one ExitSignal, which ends the shell as
* bash's does, the assignments before it landed; a malformed text throws
* ArithError once the ones before the error land.
*/
export declare function evaluateInteger(session: SessionState, view: SessionView, text: string, added?: string | null): Promise<string>;
/**
* What a `+=` hands `setVar`: the held text then the added one, or on an
* integer the held text with the added one as `added`, the two evaluating
* there in turn and summing behind the store's refusals. The held value
* evaluates too, so `n='x=5'; declare -i n; n+=x` stores 10, and an empty
* side counts as 0. An array extends element 0 and a map key `"0"`
* (`S=x; declare -a S+=y` gives `([0]="xy")`), and `held` is null when unset.
*/
export declare function appended(held: ShellValue | null, added: string, integer: boolean): [string, string | null];
export declare function ensureVarVisible(session: SessionState, name: string): void;
/**
* Put each name's global record in place for a `declare -g`, and return the
* call that puts the running locals back. Outside a function, or for a name
* no frame on the call path shadows, the global record is already in place.
* Otherwise the running local lives in `session.vars` and the global is
* what the *outermost* shadowing frame saved, so the two swap for the
* declaration: its writes, marks and kind checks reach the global, and the
* local comes back untouched, which is what GNU shows (`local G=5; declare
* -gr G=1` leaves `$G` at 5 and writable in the function, 1 and frozen
* outside, and a nested `declare -g` reaches past the caller's local too).
* Arithmetic it runs still reads the locals (`stepBack`).
*/
export declare function reachGlobal(session: SessionState, names: readonly string[]): () => void;
/**
* Let a temporary-environment variable outlive its call.
*
* bash keeps a name that `x=1 f` put in front of a function once
* something inside runs `export x` or `readonly x`: x still holds its
* value after f returns, where otherwise the caller's comes back.
*/
export declare function outliveCall(session: SessionState, name: string): void;
/**
* Whether the running function's call assigned `name` in front.
*
* `x=1 f` puts `x` in f's temporary environment, which sits right under
* f's own frame of locals.
*/
export declare function inCallEnv(session: SessionState, name: string): boolean;
/**
* The positional parameters in scope.
*
* Inside a function they are the function's own, even when it was called
* with none: bash's `f` run bare sees `$#` as 0, never its caller's
* count. Outside every function they are the shell's.
*/
export declare function positionalParams(session: SessionState, callStack: CallStack | null): string[];
/**
* Replace the positional parameters in scope.
*
* `set --` and `shift` inside a function change the function's own and
* leave the caller's alone, as bash's do.
*/
export declare function setPositionalParams(session: SessionState, callStack: CallStack | null, values: string[]): void;
/**
* Record the caller's record before a `local` shadows it, once per frame.
*
* `RANDOM` parks its generator marker too: a local `RANDOM` is an ordinary
* variable for the function's extent (`local RANDOM=5; echo $RANDOM`
* prints 5, and `local RANDOM=(7)` leaves the caller's generator alone),
* and `restoreLocals` hands the marker back.
*/
export declare function shadowLocal(session: SessionState, locals: Map<string, ShellVar | null>, name: string): void;
/**
* Put a returning function's shadowed records back.
*
* Deliberate divergence: bash reseeds the global generator when a local
* `RANDOM` is popped (`RANDOM=42; f(){ local RANDOM; }; f; echo $RANDOM`
* prints 11074 where 17772 was next); mirage resumes the caller's
* sequence where it left off.
*/
export declare function restoreLocals(session: SessionState, locals: Map<string, ShellVar | null>): void;
/**
* The session plane's view: five facts bound to one session.
*
* The one constructor every tier uses — builtins, the command
* dispatcher, a bare unit test — so the gate cannot be skipped by
* picking a different entry point. The view is the whole capability: it
* carries no handle back to the raw session.
*/
/**
* Write a variable without consulting the gate.
*
* For seeding a session before it is handed out -- the embedder
* populating an environment, a test arranging state. `visibleArrays`
* already names this case ("the embedder can seed session.arrays before
* narrowing"). Anything reached from a command line goes through
* `SessionView.set` instead, which is the whole point of the store being
* read-only from outside.
*/
export declare function seedVar(session: SessionState, name: string, value: ShellValue): void;
/**
* Turn one attribute on or off, creating the name if needed.
*
* bash's `readonly NAME` / `export NAME` on a name that does not exist
* yet marks it anyway, and the name stays *unset*: GNU prints
* `declare -r ONLY` with no value and `${ONLY-d}` still expands to `d`.
* So the record is created with no value, not with an empty string.
*
* A null attribute changes no attribute and only ensures the name
* exists, which is what a bare `local L` / `declare D` does: GNU answers
* `declare -- L` and `${L-d}` still expands to `d`, so those two cannot
* route through a value writer either.
*/
export declare function setAttr(session: SessionState, name: string, attr: VarAttr | null, on?: boolean): void;
export declare function sessionView(session: SessionState, policies?: Policies | null, diagnostics?: (string | Uint8Array)[]): SessionView;
export declare const SHELL_BOOKKEEPING: ReadonlySet<string>;
/**
* The value a `preSession` method is shown for one variable: a scalar as
* itself, an indexed array as its present elements joined by spaces, an
* associative one in sorted-key order, and null for a variable that is
* declared but unset. One rendering, so a rule reads the same text whether
* the write came from a typed line or a restore. Mirrors Python
* `gate_rendering`.
*/
export declare function gateRendering(value: ShellValue | null): string | null;
/**
* Vet a variable table a snapshot restores through the session gate.
*
* A snapshot is the one env input the deployment did not author, so the
* `preSession` rule that refuses a name on a typed line has to see the
* restore too. Every restored variable fires the gate as a `set` of its
* rendered value before any of them lands, and a refusal aborts the load
* with the `PolicyDenied` a live `export` of that name reports, rather
* than dropping the one variable: a partial restore is a workspace whose
* state matches no snapshot. The shell's own bookkeeping
* (`SHELL_BOOKKEEPING`) is exempt here as it is live. Null policies gate
* nothing. Mirrors Python `gate_restored_vars`.
*/
export declare function gateRestoredVars(policies: Policies | null, sessionId: string, table: Record<string, ShellVar>): Promise<void>;
//# sourceMappingURL=state.d.ts.map