@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
404 lines • 17.7 kB
TypeScript
import { VersionHistory } from '../../cache/versions.ts';
import { type SharedInput } from '../../io/async_line_iterator.ts';
import type { NetworkSetting } from '../../io/network.ts';
import type { AdmissionRules, Decision, HideReason, ProfileScripts } from '../../policy/types.ts';
import { type ProcessPermissions } from '../../process/config.ts';
import { type EnvEntries } from '../../secrets/config.ts';
import type { ShellArray } from '../../shell/array.ts';
import { Terminal, type JobOutput } from '../../shell/console/index.ts';
import type { Descriptor, StreamOwner } from '../../shell/descriptors.ts';
import type { JobTable, JobWaits } from '../../shell/job_table/index.ts';
import type { AliasExpansion } from '../../shell/types.ts';
import type { ShellVar } from '../../shell/variable.ts';
import type { Limit, MountMode } from '../../types.ts';
import { type Visibility } from '../../types.ts';
import { type IdSource } from '../../utils/ids.ts';
import type { StatusWriter } from '../types.ts';
import { type FunctionSite } from './functions.ts';
import { commandsToDict, type DecisionJSON, type ScriptJSON } from './serialize.ts';
/**
* Read one entry of a session record, ignoring anything inherited from
* `Object.prototype`. Shell names are script-controlled, so a plain
* `record[name]` lookup would hand back `Object.prototype` (or one of
* its methods) for a name like `__proto__` or `toString`; Python's dicts
* have no such shadow, so this guard is the TypeScript side only.
*/
export declare function sessionEntry<T>(record: Record<string, T>, name: string): T | undefined;
/**
* Write one entry of a session record as an own property. A plain
* `record[name] = value` on the name `__proto__` runs the inherited
* setter instead: it silently drops a string value and rewrites the
* record's prototype for an array one.
*/
export declare function setSessionEntry<T>(record: Record<string, T>, name: string, value: T): void;
/**
* A copy of `record` with a null prototype. Session records (env,
* functions, arrays) hold script-controlled names, so they must not
* inherit from `Object.prototype`: on a plain object, reading a name
* like `toString` hands back an inherited function and assigning
* `__proto__` runs the inherited setter instead of storing the value.
* With no prototype, every name is an ordinary key. Python's dicts
* need no equivalent.
*/
export declare function ownRecord<T>(record?: Record<string, T>): Record<string, T>;
export interface SessionInit {
sessionId: string;
cwd?: string;
logicalCwd?: string | undefined;
vars?: Record<string, ShellVar>;
createdAt?: number;
functions?: Record<string, string>;
readonlyFunctions?: Set<string>;
exportedFunctions?: Set<string>;
lastExitCode?: number;
positionalArgs?: string[];
scriptName?: string | null;
shellOptions?: Record<string, boolean>;
/**
* Per-mount mode caps for this session. `null` (the default) means
* no restriction: every mount in the workspace is reachable at its own
* mode. When provided, a mount absent from the map is invisible
* (dispatch / handle_command / Files reject it with a capability error)
* and a present mount is narrowed to the weaker of its own mode and
* the session's mode. The workspace always implicitly grants its own
* infrastructure mounts (implicit scratch root, observer, /dev).
*/
mountModes?: ReadonlyMap<string, MountMode> | null;
/**
* What exists for this session: the profile's hides, shows, hidden
* variables, process scope and allow list, compiled once. The views
* and the op boundary read it; fork carries it, toJSON serializes.
* A field left out hides nothing, as `DEFAULT_VISIBILITY` says.
*/
visibility?: Partial<Visibility>;
/**
* The operator's reasons for grouped hides: never rendered to the
* agent (a reason on ENOENT would confirm the path exists),
* persisted so the host's read-back entry points survive a restart.
*/
hideReasons?: readonly HideReason[];
/**
* The session's own command tier (`profiles.<n>.commands` tightened
* by the inline document): allow patterns, ask and deny rules. A
* durable restriction like visibility, so it persists.
*/
commands?: AdmissionRules | null;
/**
* The profile's policy programs by method, run by ScriptPolicy at
* that method. A durable restriction like commands, so it persists.
*/
scripts?: ProfileScripts;
/**
* The name of the profile the session runs under, null for an
* unrestricted session. What an owner-rendering command prints as the
* group. Stamped by the profile like script, so it persists.
*/
profile?: string | null;
commandLimits?: Readonly<Record<string, Limit>>;
/**
* Where the session's lines may connect besides the workspace's
* network, from its profile. A durable restriction like commands.
*/
network?: NetworkSetting | null;
processes?: ProcessPermissions;
processId?: number | null;
shellPid?: number | null;
processDepth?: number;
shellNesting?: number;
/**
* The host's standing answers to asked lines (design 3.9): session
* state like functions and cwd, persisted, read and written through
* the manager by id so a fork shares them, never another session's.
*/
decisions?: readonly Decision[];
generation?: number;
lastBgJobId?: number | null;
}
/**
* Variable records for a plain name/value map. The one conversion from
* the shape an embedder speaks (a process environment) to the shape the
* session stores.
*
* Every seeded name is exported, because a process environment is by
* definition the exported set: these are the names the embedder means a
* child runtime to inherit, and `envSnapshot` hands on only what carries
* the attribute. Seeding them plain would leave them visible to `$X` and
* invisible to every runtime, which is not what an embedder passing an
* env record is asking for.
*/
export declare function varsFromEnv(env: Record<string, string>): Record<string, ShellVar>;
/**
* Variable records for a stored session's two halves, the restore side
* of `toJSON`. `env` carries every scalar and `attrs` the letter cluster
* for the names that have one, so a name in `attrs` alone is bash's
* declared-but-unset third state (`export Z`) and restores with no value.
*
* Not `varsFromEnv`: that one reads a bare record as a *process*
* environment and exports all of it, which is right for an embedder
* handing over an env record and wrong here, where the attributes were
* recorded. Restoring through it promoted every plain `X=hello` to an
* exported one on the first reload.
*/
export declare function varsFromDict(env: Record<string, string>, attrs: Record<string, string>): Record<string, ShellVar>;
/**
* Variable records for a workspace env block.
*
* The declaration side of the env plane: a bare string is the literal
* short form (exported, like `varsFromEnv`), a mapping is coerced
* through `EnvVar`, and a managed entry becomes bash's third
* state -- exported, unset -- carrying the pointer as `ManagedRef`.
* After this translation the session vars are the only truth the fill
* step reads.
*/
export declare function varsFromEntries(entries: EnvEntries): Record<string, ShellVar>;
/** The wire shape `varsToFields` writes and `varsFromFields` reads. */
export interface VarFields {
env?: Record<string, string>;
var_attrs?: Record<string, string>;
managed?: Record<string, {
from: string;
ref: string;
key: string;
fetch?: string;
}>;
}
/**
* The stored shape of a bare variable table.
*
* The three keys a stored session writes (`toJSON`): `env` holds the
* plain scalars, `var_attrs` the letter clusters, `managed` the
* pointers -- and a managed name serializes as its pointer, never its
* value, the same rule the session codec states. This exists for the
* workspace env template, a variable table with no session around it,
* so a snapshot or copy can carry the declaration.
*/
export declare function varsToFields(table: Record<string, ShellVar>): VarFields;
/**
* The variable table a `varsToFields` payload restores.
*
* `varsFromDict` reads the two plain halves; each managed name then
* restores declared-but-unfetched, its value forced back to null so a
* payload that smuggles one in is discarded rather than trusted --
* exactly how `fromJSON` restores a stored session's vars.
*/
export declare function varsFromFields(data: VarFields): Record<string, ShellVar>;
export declare class SessionState {
versions: VersionHistory;
sessionId: string;
cwd: string;
logicalCwd: string | undefined;
vars: Record<string, ShellVar>;
createdAt: number;
functions: Record<string, string>;
readonlyFunctions: Set<string>;
exportedFunctions: Set<string>;
lastExitCode: number;
pipeStatus: readonly number[];
functionNames: readonly string[] | null;
pipeStatusPending: readonly number[] | null;
statusWriter: StatusWriter | null;
ids: IdSource;
randomState: number | null;
randomSeed: string | null;
randomLast: number;
positionalArgs: string[];
scriptName: string | null;
exitTrap: string | null;
exitTrapInherited: boolean;
errTrap: string | null;
returnTrap: string | null;
errTrapHidden: boolean;
returnTrapHidden: boolean;
errTrapRunning: boolean;
returnTrapRunning: boolean;
trapStatus: number | null;
tty: Terminal;
jobOutput: JobOutput | null;
jobWaits: JobWaits | null;
jobTable: JobTable | null;
shellOptions: Record<string, boolean>;
errexitImmune: boolean;
errexitIgnored: boolean;
errexitExiting: boolean;
localVars: Map<string, ShellVar | null> | null;
getoptsPos: number;
getoptsOptind: number | null;
shopts: Record<string, boolean>;
aliases: Record<string, string>;
aliasMarks: Map<string, Map<string, string | null>>;
expandAliasesMarks: Map<string, boolean>;
aliasExpansion: AliasExpansion | null;
aliasView: Readonly<Record<string, string>> | null;
parseSeq: number;
parseCurrent: number;
parseRow: number;
functionSites: Map<string, FunctionSite>;
readonly terminal: StreamOwner;
lineOpen: boolean;
umask: number;
descriptors: Map<number, Descriptor>;
execStdout: string | null;
execStdoutAppend: boolean;
execStdoutInput: SharedInput | null;
execStderr: string | null;
execStderrAppend: boolean;
execStderrInput: SharedInput | null;
execStdin: SharedInput | null;
execStdinUnreadable: boolean;
execStdinIdentity: string | null;
borrowed: SharedInput[];
localFrames: Map<string, ShellVar | null>[];
reached: [string, Map<string, ShellVar | null>, ShellVar | null][];
localRandom: (string | null)[];
mountModes: ReadonlyMap<string, MountMode> | null;
visibility: Visibility;
hideReasons: readonly HideReason[];
commands: AdmissionRules | null;
scripts: ProfileScripts;
profile: string | null;
commandLimits: Readonly<Record<string, Limit>>;
network: NetworkSetting | null;
terminalOutput: boolean;
processes: ProcessPermissions;
processId: number | null;
shellPid: number | null;
processDepth: number;
shellNesting: number;
decisions: readonly Decision[];
generation: number;
lastBgJobId: number | null;
constructor(init: SessionInit);
/**
* Return a copy of this session with `overrides` applied. Mutable
* containers (env, functions, readonlyVars, arrays, positionalArgs)
* are shallow-copied so mutations on the fork do not leak back into
* the source. Every field — including capability fields like
* `mountModes` — is propagated, so callers cannot accidentally
* forget one when adding new fields.
*
* A caller that moves the fork with `cwd` supplies a physical path
* with no typed spelling behind it, so the source's logical name is
* dropped rather than left describing where the fork is not — the same
* reasoning as `shell_dirs.setCwd`. Deciding it here rather than at
* each call site is what keeps `execute({cwd})` from reporting the
* persistent session's old directory from `pwd`. `??` cannot express
* this, since the value being chosen is `undefined`.
*/
fork(overrides?: Partial<SessionInit>): SessionState;
/**
* A child shell of this session: a fork that reads on from here. `fork`
* copies what a session keeps; a child shell (a command substitution, a
* subshell) also inherits the reader's position, the aliases being
* expanded and the local frames, and reseeds `$RANDOM` on its first draw
* instead of replaying this session's seed. Mirrors Python.
*/
subshell(): SessionState;
/**
* Start `$RANDOM` on a new sequence at its next draw, rather than seed it
* from the value the variable holds now.
*/
private drawAfresh;
/**
* A new shell started from this session, as a nested `bash` is. bash runs
* a nested shell as a program of its own, which inherits the working
* directory, the umask, the open files and the environment: the exported
* variables, as plain exported strings (no array, no other attribute), and
* the functions `export -f` marked. The rest starts as a fresh shell's
* does: the other variables and functions, the aliases, the `set` and
* `shopt` options, `$?`, `$!`, `$RANDOM`'s sequence, `getopts`'s place,
* the call stack, any test its caller is in (`if bash -ec 'false; ...'`
* still ends at `false`) and the startup variables, which bash never reads
* from its environment: IFS is dropped and `STARTUP_VALUES` restart. A
* managed variable not yet fetched crosses as its pointer, which the
* nested shell fetches through. Mirrors Python.
*/
newShell(): SessionState;
/** Remove a function with its definition site and export mark. */
removeFunction(name: string): void;
/**
* What `$0` expands to. Null is the shell itself; a nested `bash`/`sh`
* sets it to the script it is running, or to the name given after
* `-c`. An empty name is a name, so it is not folded into the default:
* GNU `bash -c 'echo "[$0]"' ""` prints `[]`.
*/
get argv0(): string;
/**
* The scalar variables, by name.
*
* A frozen read-only projection of `vars`, not a container: a writer
* goes through `SessionView.set` (or `seedVar` when seeding a session
* before it is narrowed), so a `pre_session` policy sees every write.
* `state.ts` has always documented that rule; freezing the projection
* is what stops it being walked around by assigning into storage, and
* an assignment into a plain object would land in a throwaway and
* vanish -- silent loss is the exact failure this store removes.
*/
get env(): Readonly<Record<string, string>>;
/** The indexed arrays, by name. Read-only, like `env`. */
get arrays(): Readonly<Record<string, ShellArray>>;
/** The associative arrays, by name. Read-only, like `env`. */
get assocs(): Readonly<Record<string, Record<string, string>>>;
/** The names `readonly` has marked. Read-only, like `env`. */
get readonlyVars(): ReadonlySet<string>;
openInputs(): SharedInput[];
/** Close the readers among `inputs` that this shell no longer names and did not inherit. */
releaseInputs(inputs: SharedInput[]): Promise<void>;
/**
* Close the readers this child shell opened, as it ends; the ones it
* inherited stay with the shell that opened them. Mirrors Python's
* close_inputs.
*/
closeInputs(): Promise<void>;
/**
* The durable-field payload the session table and snapshots persist.
* Keys are snake_case, byte-identical to Python's `SessionState.to_dict`,
* so both languages can share one store (a py daemon creates the
* session, a node kernel tier binds it).
*/
toJSON(): Record<string, unknown>;
toDict(): Record<string, unknown>;
static fromDict(data: {
session_id: string;
cwd?: string;
env?: Record<string, string>;
var_attrs?: Record<string, string>;
managed?: Record<string, {
from: string;
ref: string;
key: string;
fetch?: string;
}> | null;
created_at?: number;
mount_modes?: Record<string, MountMode> | null;
hidden_paths?: {
paths?: string[];
patterns?: string[];
} | null;
shown_paths?: {
entries?: {
path: string;
mode?: MountMode;
}[];
} | null;
hide_reasons?: {
patterns?: string[];
reason?: string;
}[] | null;
hidden_vars?: {
names?: string[];
patterns?: string[];
} | null;
commands?: ReturnType<typeof commandsToDict> | null;
scripts?: Record<string, ScriptJSON[]> | null;
profile?: string | null;
command_limits?: unknown;
network?: NetworkSetting | null;
processes?: ProcessPermissions;
decisions?: DecisionJSON[] | null;
generation?: number;
functions?: Record<string, string>;
readonly_functions?: string[];
exported_functions?: string[];
}): SessionState;
}
//# sourceMappingURL=session.d.ts.map