@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
330 lines • 16.7 kB
TypeScript
import type { Accessor } from '../../accessor/base.ts';
import type { FileCacheMixin } from '../../cache/file/mixin.ts';
import type { Evicted, IndexConfig } from '../../cache/index/config.ts';
import { IOConfig } from '../../io/config.ts';
import type { MemoryBudget } from '../../io/memory.ts';
import { type NetworkRule, type NetworkSetting } from '../../io/network.ts';
import type { Observer } from '../../observe/observer.ts';
import { type Decisions, Policies } from '../../policy/index.ts';
import type { ProcessView } from '../../process/view.ts';
import type { DiffComputer } from '../../rs/diff_types.ts';
import type { Runtime } from '../../runtime/base.ts';
import type { DispatchFn } from '../../runtime/types.ts';
import { Limit, MountMode, PathSpec, type ReadSpec, WritePolicy } from '../../types.ts';
import { type Clock } from '../../utils/clock.ts';
import { type IdSource } from '../../utils/ids.ts';
import type { BaseVFS } from '../../vfs/base.ts';
import type { SessionAccess } from '../../view/types.ts';
import { CLIRegistry } from '../cli/registry.ts';
import type { SessionState } from '../session/session.ts';
import type { MountRow } from '../types.ts';
import { MountEntry } from './mount.ts';
interface ReadReconciler {
mayServeListing(mount: MountEntry, folder: string, version: string | null): Promise<boolean>;
onGone(gone: readonly Evicted[], excluded?: readonly string[]): Promise<void>;
}
export declare const DEV_PREFIX = "/dev/";
export declare const logger: {
debug?: (message: string) => void;
};
export declare class MountCommandUnsupported extends Error {
readonly cmdName: string;
readonly backend: string;
readonly operand: string;
constructor(cmdName: string, backend: string, operand: string);
}
/** The placements of the mounts a registry is constructed with, keyed by raw prefix. */
export interface RegistryPlacements {
index?: IndexConfig;
indexes?: Record<string, IndexConfig>;
refs?: Record<string, string>;
/** The write policy a mount takes when it names none. */
defaultWrite?: WritePolicy;
/** Each mount's own write policy, keyed by raw prefix. */
writes?: Record<string, WritePolicy>;
/** Scopes the redis index stores this registry builds. */
workspaceId?: string;
}
export declare class MountRegistry {
readonly io: IOConfig;
readonly clock: Clock;
readonly ids: IdSource;
diff?: DiffComputer;
private readonly indexConfig;
private readonly workspaceId;
processView?: (session: SessionState) => ProcessView;
fillEnv?: (session: SessionState, names: ReadonlySet<string>) => Promise<void>;
observer: Observer | null;
private readonly mountList;
private rowCache;
readonly retiringMounts: Map<BaseVFS<Accessor>, Promise<void>>;
readonly retiredMounts: WeakSet<BaseVFS<Accessor>>;
private rootRef;
private defaultRead;
private defaultWrite;
private cacheStore;
private reconciler;
private dispatchFn;
private dispatchAsFn;
runtimeBindings: Readonly<Record<string, Runtime | undefined>>;
runtimeEntries: readonly Runtime[];
commandLimits: Readonly<Record<string, Limit>>;
memory: MemoryBudget | null;
network: NetworkRule | null;
systemClock: Clock | null;
private readonly narrowed;
private watch;
readonly policies: Policies;
/** The approval ledger an Ask is taken to, which the policies hold
* (`Policies.decisions`) so a session write reaches it too. */
get decisions(): Decisions;
readonly clis: CLIRegistry;
private clocksBound;
/**
* Drop what a write past the dispatcher may have made stale.
*
* Native code (an external program, a remote runtime line) or an account
* CLI changed its service with no path the dispatcher saw, so per-path
* invalidation has nothing to aim at: after `gws sheets spreadsheets
* create` the new file has no cache entry to expire, which is exactly the
* case that matters. Every mount the write may have reached drops what it
* keeps (`MountEntry.dropCaches`). A write verb is rare next to reads, and
* the cost is one cold listing on a mount's next read, never a wrong
* answer. Mirrors Python's `MountRegistry.drop_caches`.
*
* @param by the client that wrote, whose `reaches` names the mounts it may
* have changed; null for every mount.
*/
dropCaches(by?: Accessor | null): Promise<void>;
/**
* Land a runtime's writes when its line ends, however it ended, and drop
* the caches when they went past the dispatcher.
*
* A runtime serving the workspace over SFTP sent each write through the
* dispatcher, which invalidated that path as it went
* (`fileEffectsDispatched`). Any other may have written anywhere in its
* view of the workspace, so every mount drops its caches and the next
* command refetches instead of serving pre-line state: `cat /data/x`
* caches "old", `python3 job.py` writes "new" straight to S3 through the
* sandbox's own FUSE mount, and without the drop the next `cat /data/x`
* would serve the stale "old". Mirrors Python's `MountRegistry.settle`.
*/
settle(runtime: Runtime): Promise<void>;
/**
* The rule a session's lines are held to: the workspace's, narrowed by the
* `network` its profile states, its routes over the workspace's, built once
* per setting. Mirrors Python's `MountRegistry.network_of`.
*/
networkOf(setting: NetworkSetting | null): NetworkRule | null;
/** Set what starts judging the process's connections, once a rule needs it. */
setNetworkWatch(watch: () => void): void;
/** The rules `networkOf` built, whose connections close with the workspace. */
narrowedNetworks(): NetworkRule[];
setReconciler(reconciler: ReadReconciler): void;
/**
* The workspace's dispatcher, which a shell glob lists and stats through;
* null until the workspace wires it in, and for a bare registry with no
* workspace behind it. Mirrors Python's `MountRegistry.dispatch`.
*/
get dispatch(): DispatchFn | null;
/**
* The workspace's dispatcher bound to `owner`, the ambient one without. On
* the browser's fallback storage the ambient session is whichever line ran
* last, so a glob lists as its own line's session only through a dispatch
* bound to it. Python's task context keeps each line's session, so it has no
* twin.
*/
dispatchFor(owner?: SessionAccess): DispatchFn | null;
/**
* Wire in the workspace's dispatcher, and how to bind it to a session.
* Mirrors Python's `set_dispatch`.
*/
setDispatch(dispatch: DispatchFn, dispatchAs?: (owner: SessionAccess) => DispatchFn): void;
/**
* Attach the workspace file cache and build per-mount CacheManagers.
* Called once by Workspace after the cache store exists; mounts
* added later get their manager in `mount()`. The cache is a hidden
* store, never a mount.
*/
attachFileCache(cache: FileCacheMixin | null): void;
/**
* Run the shared listing verdict for one mount's cached listing.
*
* The reconciler is read at call time because `attachFileCache` runs
* before `setReconciler`, and a manager with none trusts its listings. A
* retiring mount answers false without asking. Python also answers false
* for EBUSY from a mount that began retiring mid-check; this side's probe
* never enters `mount.use()`, so the `retiring` check is the whole guard.
*/
private mayServeListing;
private attachManager;
constructor(mounts: Record<string, BaseVFS>, defaultMode: MountMode, modeOverrides?: Record<string, MountMode>, defaultRead?: ReadSpec, readOverrides?: Record<string, ReadSpec>, placements?: RegistryPlacements, io?: IOConfig, clock?: Clock, ids?: IdSource);
/**
* Build one mount with everything the tree needs to run its driver:
* the driver's command and op tables registered on the entry, the
* general commands beside them. The constructor and `mount()`
* both place through here, so a mount is the same whichever entry point
* built it. Mirrors the Python registry, whose constructor mounts
* through `mount()` as well.
*/
private static place;
/**
* The store a driver runs under: a new one from the mount's or the
* workspace's index config, else a RAM store at the driver's own TTL.
*/
private indexFor;
/**
* Refuse a mount that would place one VFS object at a second prefix, or a
* disk on a folder a mounted disk already has.
* Mirrors Python's `check_storage_unshared`.
*
* @throws SharedStorageError naming the mount already there.
*/
checkStorageUnshared(prefix: string, vfs: BaseVFS, mounts: readonly MountEntry[]): void;
/** A removed VFS instance cannot start a second lifecycle. */
checkVfsAvailable(vfs: BaseVFS): void;
/**
* Add a mount dynamically. Mirrors Python's `registry.mount(...)`.
* Registers the VFS's commands and ops on the new mount and
* re-sorts mounts by prefix length (longest first). Once the workspace is
* built, the VFS takes the workspace clock before anything is placed.
*
* @throws Error when the prefix is taken, or another workspace holds the
* VFS on another clock.
*/
mount(prefix: string, vfs: BaseVFS, mode?: MountMode, read?: ReadSpec, placement?: {
index?: IndexConfig;
vfsRef?: string | null;
/** The mount's write policy; undefined takes the workspace default. */
write?: WritePolicy;
}): MountEntry;
/**
* Hand every mount's and CLI install's accessor the workspace clock, once
* the workspace is built; a mount placed later takes it as it is placed. Mirrors Python's
* `MountRegistry.bind_clocks`.
*
* @throws Error when another workspace holds one of the VFSes on another
* clock; nothing stays bound.
*/
bindClocks(): void;
/**
* Let go of the workspace clock on `vfs`, as an unmounted mount finishes its
* last call. Mirrors Python's `MountRegistry.release_clock`.
*/
releaseClock(vfs: BaseVFS): void;
/**
* Let go of the workspace clock on every mount and CLI install, as the
* workspace closes or fails to come to be, so another workspace may mount
* its VFSes on its own. Mirrors Python's `MountRegistry.release_clocks`.
*/
releaseClocks(): void;
/**
* Remove a mount by exact prefix. Mirrors Python's `registry.unmount(...)`.
* Per-mount commands and ops live on the Mount instance and die with it.
* The /dev/ mount is reserved and cannot be removed.
*/
unmount(prefix: string): MountEntry;
/**
* The mount at exactly this prefix; throws noMount for none. Callers
* that expect the miss branch on `tryMountForPrefix` returning null.
*/
mountForPrefix(prefix: string): MountEntry;
/** The mount at exactly this prefix, or null when none matches. */
tryMountForPrefix(prefix: string, includeHidden?: boolean): MountEntry | null;
/**
* One mount's configured cap for a command or op name. The lookup a
* command's output guard resolves with; tolerant of a prefix that matches
* no mount (unmounted between stamp and boundary) by answering null.
*/
/**
* Set the mount's `walkLimit`, which decides how its backend searches (its
* own search API under a cap, every file without): the mount's `@all` entry,
* else the workspace's, else the backend's default, a null one lifting the
* cap. A command's own `walkLimit` changes only how far that command walks.
* Mirrors Python's `settle_walk_limit`.
*/
settleWalkLimit(entry: MountEntry): void;
/**
* The mount a command's output guard resolves a stamped prefix to; null
* for a prefix that matches no mount (unmounted between stamp and
* boundary). Mirrors Python's `limit_mount`.
*/
limitMount(prefix: string): MountEntry | null;
isMountRoot(path: string): boolean;
descendantMounts(path: string): MountEntry[];
mountPrefixes(): string[];
get rootMount(): MountEntry | null;
get fileCache(): FileCacheMixin | null;
resolve(path: string): [BaseVFS, PathSpec, MountMode];
/**
* The mount that handles this path; throws noMount for none.
*
* The lookup contract pair: `mountFor` is for callers whose path must
* be mounted (a miss is a broken invariant and propagates as a typed
* noMount error), `tryMountFor` is for callers with a real fallback
* for the miss. Never catch around this method — call the try variant
* instead.
*/
mountFor(path: string): MountEntry;
/** The mount that handles this path, or null when none does. */
tryMountFor(path: string): MountEntry | null;
/**
* One row per registered mount, for the namespace reader the dispatcher
* hands out; built once per change to the mount list. The functions a VFS
* answers are the ones its mount read when it was mounted
* (`MountEntry.calls`); who is told of a mount is read off the mount when
* asked, since a session's documents mount is narrowed after it is made.
* Mirrors Python's `mount_rows`.
*/
mountRows(): MountRow[];
/** The mounts the session a call through a facade of `sessionId` runs as
* (null: the default facade) is told of at all, hides aside. */
visibleMounts(sessionId?: string | null): MountEntry[];
allMounts(): readonly MountEntry[];
/**
* The bytes the workspace's RAM mounts hold, each store counted once, as a
* remount's retiring entry shares its store. Mirrors Python's
* `held_in_memory`.
*/
heldInMemory(): number;
isExecAllowed(access?: SessionAccess): boolean;
/**
* Whether code may be loaded from this path: the per-script form of
* `isExecAllowed`, read by an interpreter running a file operand
* (`python3 path.py`, `js app.js`, or a `./script` whose shebang names
* one). Shell scripts (`bash script.sh`, `source`, a `./script` with no
* shebang or an sh one) never ask: they run in the shell, which checks
* each of their commands like a typed one.
*/
execAllowedAt: (virtual: string, access?: SessionAccess) => boolean;
private fallbackCandidates;
mountForCommand(cmdName: string, access?: SessionAccess): MountEntry | null;
/**
* How many leading words form a registered command name. Command names
* may span several words ("gws docs documents get"), git-style. A nested
* name resolves from anywhere its owning mount is reachable, so this
* scans every mount (mirroring mountForCommand) and returns the longest
* prefix any mount recognises, or 1 (bare first token) when none does.
*/
matchCommandPrefix(words: string[]): number;
/**
* Candidates for discovery and admission, without readying a backend.
* A path or working directory that owns the command selects it; fallback
* retains root-first, then longest-prefix registration order. A bound
* mount stays authoritative so the dispatcher owns visibility refusals.
*/
commandCandidates(cmdName: string, pathScopes: readonly PathSpec[], cwd: string, access?: SessionAccess): MountEntry[];
/** The first discovery candidate; selecting execution may refuse ambiguity. */
commandMount(cmdName: string, pathScopes: readonly PathSpec[], cwd: string, access?: SessionAccess): MountEntry | null;
/** The ordered execution candidates and grammar for the same invocation. */
private invocationChoices;
/** The targets this invocation can run, before fetching its environment. */
invocationCandidates(cmdName: string, pathScopes: readonly PathSpec[], cwd: string, argv?: readonly string[], access?: SessionAccess): MountEntry[];
/** The grammar used to classify this invocation, without refusing ambiguity. */
invocationMount(cmdName: string, pathScopes: readonly PathSpec[], cwd: string, argv?: readonly string[], access?: SessionAccess): MountEntry | null;
/** Select an unambiguous write target without starting a backend. */
executionMount(cmdName: string, pathScopes: readonly PathSpec[], cwd: string, argv?: readonly string[], access?: SessionAccess): MountEntry | null;
resolveMount(cmdName: string, pathScopes: readonly PathSpec[], cwd: string, argv?: readonly string[], access?: SessionAccess): Promise<MountEntry | null>;
}
export {};
//# sourceMappingURL=registry.d.ts.map