UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

126 lines • 5.27 kB
// ========= 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 { FileType, PathSpec } from "../../../../types.js"; import { isFsError } from "../../../../errors/fs.js"; import { respellOne } from "../../../../utils/path.js"; import { rstripSlash, stripSlash } from "../../../../utils/slash.js"; import { isStdin } from "../../utils/stream.js"; import { mountView } from "../../utils/links.js"; /** Lazily partition an operand into maximal scopes with one owner. * Only directories containing mount boundaries are expanded. Listings * come through the dispatcher so hidden and shadowed entries stay hidden. */ export async function* ownedScopes(path, dispatch, vfs, admit, walked = false) { if (path.walkError !== null || isStdin(path)) { yield { path, walked }; return; } let entries; try { const [info] = await dispatch('stat', path, [], { nofollow: true }); const stat = info; if (walked && !admit(path, stat)) return; const boundaries = mountView(vfs)?.descendants(path.virtual) ?? []; if (stat.type !== FileType.DIRECTORY || boundaries.length === 0) { yield { path, walked, stat }; return; } entries = (await dispatch('readdir', path))[0]; } catch (error) { if (!isFsError(error)) throw error; yield { path, walked, error: error }; return; } for (const entry of entries) { const virtual = path.virtual.replace(/\/$/, '') + '/' + (entry.replace(/\/$/, '').split('/').at(-1) ?? ''); const child = new PathSpec({ virtual, directory: virtual, vfsPath: virtual.replace(/^\/+|\/+$/g, ''), rawPath: respellOne(virtual, path.virtual, path.rawPath), }); yield* ownedScopes(child, dispatch, vfs, admit, true); } } /** * The operand, then each visible mount root below it, as start points. * * Each start is one mount's own part of the operand's tree: the operand's * mount answers for everything but the mounts inside it, and every mount * below answers from its root, spelled as the operand was typed. A hidden * mount is never a start, though it still shadows the parent backend's keys. */ export function mountStarts(path, vfs) { const mounts = mountView(vfs); if (path.walkError !== null || mounts === undefined) return [path]; return [ path, ...mounts.visibleDescendants(path.virtual).map((root) => new PathSpec({ virtual: root, directory: root, vfsPath: root.replace(/^\/+|\/+$/g, ''), rawPath: respellOne(root, path.virtual, path.rawPath), })), ]; } /** Which start points a walk from their operand reaches. * A walk that cannot read a directory never sees what lies inside it, * mounts included. A start below a part whose run failed therefore counts * only when every directory from that part down to it lists through the * dispatcher; every other start counts as it is. */ export async function reached(paths, starts, failed, dispatch) { const key = (index, virtual) => `${String(index)}\0${rstripSlash(virtual) || '/'}`; const broken = new Set(starts.filter((_, i) => failed[i] === true).map(([index, start]) => key(index, start.virtual))); const listed = new Map(); const lists = async (virtual) => { let known = listed.get(virtual); if (known === undefined) { try { await dispatch('readdir', PathSpec.fromStrPath(virtual)); known = true; } catch (err) { console.warn(`cannot list ${virtual}: ${String(err)}`); known = false; } listed.set(virtual, known); } return known; }; const found = []; for (const [index, start] of starts) { const path = paths[index]; const base = rstripSlash(path?.virtual ?? ''); const parts = stripSlash(start.virtual.slice(base.length)).split('/').slice(0, -1); const trail = [ base || '/', ...parts.map((_, end) => `${base}/${parts.slice(0, end + 1).join('/')}`), ]; const hit = trail.findIndex((d) => broken.has(key(index, d))); let reach = true; if (start !== path && hit >= 0) { for (const virtual of trail.slice(hit)) { reach = await lists(virtual); if (!reach) break; } } found.push(reach); } return found; } //# sourceMappingURL=scopes.js.map