@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
134 lines • 5.94 kB
JavaScript
// ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
import { KeyLock } from "../../cache/lock.js";
import { runWithSession } from "../../context/session_context.js";
import { DEFAULT_READ_SPEC, FileType, MountMode, PathSpec, WritePolicy } from "../../types.js";
import { eexist, enoent, enotdir, isEnoent } from "../../errors/fs.js";
import { pathVisible } from "../../utils/hidden.js";
import { norm, parent } from "../../utils/path.js";
import { DocumentVFS } from "../../vfs/document/document.js";
import { applyProfile } from "../session/resolve.js";
import { SessionState } from "../session/session.js";
import * as render from "./render.js";
export class Documents {
registry;
files;
manager;
session;
profile;
ensureLoaded;
unmount;
followParent;
views = new Map();
lock = new KeyLock();
constructor(registry, files, manager, session, profile, ensureLoaded, unmount, followParent) {
this.registry = registry;
this.files = files;
this.manager = manager;
this.session = session;
this.profile = profile;
this.ensureLoaded = ensureLoaded;
this.unmount = unmount;
this.followParent = followParent;
}
render(kind) {
return (kind === 'vfs' ? render.vfsMd : render.skillMd)(this.registry, this.session(null));
}
/** Drop every binding; a snapshot load restores none. */
async clear() {
await this.lock.withLock('documents', async () => {
for (const path of [...this.views.keys()])
await this.unmount(path);
});
}
async releaseSession(sessionId) {
await this.lock.withLock('documents', async () => {
for (const [path, view] of this.views) {
view.sessions.delete(sessionId);
if (!view.globalView && view.sessions.size === 0)
await this.unmount(path);
}
});
}
async get(kind, path, profile, sessionId) {
if (profile !== undefined && (path !== undefined || sessionId !== undefined)) {
throw new Error('profile is only valid for generation without a path or session');
}
await this.ensureLoaded();
let session;
if (profile !== undefined) {
session = new SessionState({ sessionId: '' });
applyProfile(session, this.profile(profile));
}
else
session = sessionId === undefined ? this.session(null) : this.manager.get(sessionId);
return runWithSession(session, async () => {
if (path !== undefined) {
const virtual = path instanceof PathSpec ? path.virtual : path;
if (norm(virtual) !== virtual ||
virtual.includes('\0') ||
virtual
.slice(1)
.split('/')
.some((part) => ['', '.', '..'].includes(part))) {
throw new Error('document path must be an absolute, normalized file path');
}
// Bound where a later read lands: every link above the name is
// followed, as the read's own walk follows it.
const bound = this.followParent(virtual);
if (![virtual, bound].every((p) => pathVisible(session.visibility, p)))
throw enoent(virtual);
await this.lock.withLock('documents', () => this.expose(kind, bound, sessionId === undefined ? null : session));
}
return this.render(kind);
}, { owner: this.manager });
}
async expose(kind, path, session) {
const directory = await this.files.stat(parent(path));
if (directory.type !== FileType.DIRECTORY)
throw enotdir(parent(path));
let view = this.views.get(path);
if (view !== undefined && view.kind !== kind)
throw eexist(path);
if (view === undefined) {
// Collision checks are host-side: a hidden backend entry must not be
// overwritten by a new view either.
await runWithSession(new SessionState({ sessionId: '' }), async () => {
try {
await this.files.stat(path, undefined, { nofollow: true });
}
catch (error) {
if (isEnoent(error))
return;
throw error;
}
throw eexist(path);
}, { owner: this.manager });
view = new DocumentVFS(path.slice(path.lastIndexOf('/') + 1), () => this.render(kind), kind);
const document = view;
const mount = this.registry.mount(path, view, MountMode.READ, DEFAULT_READ_SPEC, {
write: WritePolicy.UNCONDITIONAL,
});
mount.visible = (sessionId) => document.globalView ||
document.sessions.get(this.session(sessionId).sessionId) ===
this.session(sessionId).createdAt;
this.views.set(path, view);
}
if (session === null)
view.globalView = true;
else
view.sessions.set(session.sessionId, session.createdAt);
}
}
//# sourceMappingURL=documents.js.map