UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

330 lines • 16.7 kB
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