UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

178 lines • 8.4 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 { DISPATCH_BUILDERS } from "../../commands/builtin/generic/crossmount/constants.js"; import { runDu } from "../../commands/builtin/generic/crossmount/du.js"; import { runFind } from "../../commands/builtin/generic/crossmount/find.js"; import { handleCrossMount } from "../../commands/builtin/generic/crossmount/route.js"; import { runDispatch } from "../../commands/builtin/generic_bind/dispatch.js"; import { UsageError } from "../../commands/errors.js"; import { CommandTimeoutError } from "../../errors/types.js"; import { readFailExitCode } from "../../commands/spec/usage.js"; import { IOResult } from "../../io/types.js"; import { encodeText } from "../../shell/bytes.js"; import { compareCodePoints } from "../../utils/sort.js"; import { stripSlash } from "../../utils/slash.js"; import { formatFsError } from "../../errors/render.js"; import { MountCommandUnsupported } from "../mount/registry.js"; import { ExecutionNode } from "../types.js"; import { readerOf } from "../files.js"; import { lineFacts } from "../lookup/index.js"; const TRAVERSAL_CMDS = new Set(['find', 'du']); // The walks the fan-out composes over nested mounts: find and du from each // mount's rows, ls and the searches from each mount's own part. tree walks // them through the dispatcher's listings. const COMPOSED = new Set([...TRAVERSAL_CMDS, 'ls', 'grep', 'rg']); /** * One mount's part of a find or du, on the command that mount serves. A mount * that registers no find or du of its own still answers its part through the * generic walk over the dispatcher, since both read only the metadata every * mount serves. */ function ownPart(runSingle, registry, dispatch, cwd, vfs, follow, find) { return async (cmdName, paths, texts, bag, opts) => { try { await registry.resolveMount(cmdName, paths, cwd); } catch (err) { if (!(err instanceof MountCommandUnsupported)) throw err; const builder = DISPATCH_BUILDERS.get(cmdName); if (builder === undefined) throw new Error(`No dispatch builder for ${cmdName}`); return runDispatch(builder, paths, texts, bag, dispatch, cwd, vfs, opts?.stdin ?? null, opts?.signal, [], follow, registry.diff, registry.ids, opts?.find ?? find); } return runSingle(cmdName, paths, texts, bag, opts ?? {}); }; } // Whether a walk over these operands fans out over the mounts nested in // them: `walks` is read off the line's words, so the facts the admission // gate kept for the line answer. export function shouldFanOut(cmdName, paths, words, cwd, registry) { // Use the raw mount table: hidden descendants still shadow backend keys. // A refused operand names nothing; every valid operand may own a subtree. if (!paths.some((p) => p.walkError === null && registry.descendantMounts(p.virtual).length > 0)) return false; return COMPOSED.has(cmdName) && lineFacts(cmdName, words, cwd).walks; } /** * Compose a traversal over the mounts inside its operands. Each mount's own * command answers for its part: find and du from every mount's structured * rows and measurements, a search from its owned scopes. No output is * inspected to recover paths or repair depth and totals. */ export async function fanOutTraversal(cmdName, paths, texts, flagKwargs, registry, primaryMount, cwd, cmdStr, stdin, vfs, sessionView, signal, dispatch, native, // The line's operand resolution, which a walk through the dispatcher's // globs take. follow, // find's expression, parsed once for the line. find) { signal?.throwIfAborted(); if (dispatch === undefined || native === undefined) throw new Error('traversal requires dispatcher and native execution'); let stdout; let io; const part = ownPart(native, registry, dispatch, cwd, vfs, follow, find); try { if (cmdName === 'find') { ; [stdout, io] = await runFind(paths, texts, flagKwargs, find, dispatch, part, cwd, vfs, stdin, signal, follow); } else if (cmdName === 'du') { ; [stdout, io] = await runDu(paths, texts, flagKwargs, dispatch, part, cwd, vfs, stdin, signal, true, follow); } else { ; [stdout, io] = await handleCrossMount(cmdName, [...paths], [...texts], flagKwargs, dispatch, native, stdin, undefined, vfs, sessionView, cwd, [], null, undefined, follow, registry.diff, registry.ids, signal); } } catch (err) { if (err instanceof CommandTimeoutError || (err instanceof Error && err.name === 'AbortError')) throw err; stdout = null; if (err instanceof UsageError) { io = new IOResult({ exitCode: err.exitCode, stderr: encodeText(`${err.message}\n`), }); } else { // A backend failure anywhere in the walk (a 5xx from a nested mount) // is this command's result, in its voice, as the single-mount entry point // reports it; the rest of the line still runs. io = new IOResult({ exitCode: readFailExitCode(cmdName, err), stderr: formatFsError(cmdName, err, paths), }); } } // Only the mounts the walk can reach bound its output: a hidden one never // contributes a row, so its stricter limit must not apply. const prefixes = new Set([primaryMount.prefix]); const reader = vfs === undefined ? null : readerOf(vfs); for (const path of paths) { if (path.walkError !== null) continue; for (const mount of registry.descendantMounts(path.virtual)) if (reader === null || reader.visible('/' + stripSlash(mount.prefix))) prefixes.add(mount.prefix); } io.producer = { command: cmdName, prefixes: [...prefixes].sort(compareCodePoints), declared: null, }; return [ stdout, io, new ExecutionNode({ command: cmdStr, exitCode: io.exitCode, stderr: await io.materializeStderr(), }), ]; } export function runWithFanout(runSingle, registry, cwd, vfs, sessionView, signal, dispatch, // The line's operand resolution, which a walk through the dispatcher's // globs take. follow, // find's expression, parsed once for the line. find, // The line's words, name first, whose facts say whether it walks. words = []) { return async (cmdName, paths, texts, flagKwargs, opts) => { const stdin = opts?.stdin ?? null; if (!shouldFanOut(cmdName, paths, words, cwd, registry)) { const run = TRAVERSAL_CMDS.has(cmdName) && dispatch !== undefined ? ownPart(runSingle, registry, dispatch, cwd, vfs, follow, find) : runSingle; return run(cmdName, paths, texts, flagKwargs, opts ?? {}); } let mount = null; try { mount = await registry.resolveMount(cmdName, paths, cwd); } catch (err) { // The single-mount runner owns the wording for a command this mount // does not serve, so let it report rather than re-throwing. if (!(err instanceof MountCommandUnsupported)) throw err; } if (mount === null) return runSingle(cmdName, paths, texts, flagKwargs, opts ?? {}); const [stdout, io] = await fanOutTraversal(cmdName, paths, texts, flagKwargs, registry, mount, cwd, cmdName, stdin, vfs, sessionView, signal, dispatch, runSingle, follow, opts?.find ?? find); return [stdout, io]; }; } //# sourceMappingURL=fanout.js.map