@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
162 lines • 7.9 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 { OPERAND_CONCURRENCY } from "../../constants.js";
import { parseFindExpression } from "../../../../rs/find.js";
import { DISPATCH_BUILDERS } from "./constants.js";
import { mountStarts, reached } from "./scopes.js";
import { runDispatch } from "../../generic_bind/dispatch.js";
import { specOf } from "../../../spec/builtins.js";
import { FlagView } from "../../../spec/flag_view.js";
import { boundedMap } from "../../../../concurrency/limiter.js";
import { IOResult, materialize } from "../../../../io/types.js";
import { PathSpec } from "../../../../types.js";
import { respellOne } from "../../../../utils/path.js";
import { compareCodePoints } from "../../../../utils/sort.js";
import { mountView } from "../../utils/links.js";
// The tests whose verdict on one entry decides what the walk reads below it,
// or that read a listing a mount boundary splits: no mount's own find can
// answer them for the whole tree, so such a line keeps the one walk.
export const WALK_WIDE = new Set(['-prune', '-empty', '-xdev', '-mount']);
/**
* The expression for a start point `depth` levels below the operand.
*
* Its depth limits count from that start point; `alone` matches the start
* point only, not what lies below. Null when the limits leave it nothing to
* print.
*/
export function shifted(expr, depth, alone = false) {
const { maxDepth, minDepth } = expr;
if (maxDepth !== null && depth > maxDepth)
return null;
if (alone && minDepth !== null && depth < minDepth)
return null;
const top = alone ? 0 : maxDepth === null ? null : maxDepth - depth;
const bottom = minDepth === null ? null : Math.max(0, minDepth - depth);
return { ...expr, maxDepth: top, minDepth: bottom };
}
/**
* The directories between an operand and the mounts below it.
*
* No mount's own find can answer for them: the parent's backend may hold none
* of them (a mount at `/usr/bin` implies `/usr`), so each is matched alone,
* through the dispatcher.
*/
export function joints(path, starts) {
const base = path.virtual.replace(/\/+$/, '');
const roots = new Set(starts.slice(1).map((s) => s.virtual));
const between = new Set();
for (const root of roots) {
const parts = root
.slice(base.length)
.replace(/^\/+|\/+$/g, '')
.split('/')
.slice(0, -1);
for (let end = 1; end <= parts.length; end++) {
between.add(`${base}/${parts.slice(0, end).join('/')}`);
}
}
return [...between]
.filter((virtual) => !roots.has(virtual))
.sort(compareCodePoints)
.map((virtual) => new PathSpec({
virtual,
directory: virtual,
vfsPath: virtual.replace(/^\/+|\/+$/g, ''),
rawPath: respellOne(virtual, path.virtual, path.rawPath),
}));
}
/**
* Compose find over operands holding mounts from each mount's own find.
*
* Every operand, and every visible mount below it, runs the find its mount
* registered, a few at a time, with the depth limits counted from its own
* start point; the directories between them (`joints`) are matched once each
* through the dispatcher. Each run answers for the rows its mount owns (the
* parent's shadowed keys stay out), and the rows merge in the walk's path
* order, so the actions still run once over every mount's `records`; a
* mount whose find fails adds its diagnostic and status, as GNU's walk past
* an unreadable directory does, and the mounts below it count only where the
* walk could still reach them (`reached`). An expression that looks across a
* boundary (`WALK_WIDE`, `-L`), or a find that succeeds without structured
* rows, keeps the one dispatcher walk.
*/
export async function runFind(paths, texts, bag, find, dispatch, runSingle, cwd, vfs, stdin = null, signal,
// The line's operand resolution, which the one walk's globs take.
follow) {
const builder = DISPATCH_BUILDERS.get('find');
if (builder === undefined)
throw new Error('No dispatch builder for find');
const expr = find ?? parseFindExpression(texts);
const walk = () => runDispatch(builder, paths, texts, bag, dispatch, cwd, vfs, stdin, signal, [], follow, undefined, undefined, expr);
const mounts = mountView(vfs);
if (mounts === undefined ||
expr.predicates.some((word) => WALK_WIDE.has(word)) ||
new FlagView(bag, specOf('find')).asBool('L'))
return walk();
const plan = [];
paths.forEach((path, index) => {
const base = path.virtual.replace(/\/+$/, '');
const starts = mountStarts(path, vfs);
const steps = [
...starts.map((start) => [start, false]),
...joints(path, starts).map((joint) => [joint, true]),
];
for (const [start, alone] of steps) {
const depth = start === path ? 0 : (start.virtual.slice(base.length).match(/\//g) ?? []).length;
const limited = shifted(expr, depth, alone);
if (limited !== null)
plan.push({ index, start, alone, limited });
}
});
const ios = await boundedMap(plan, async (step) => {
const [out, io] = step.alone
? await runDispatch(builder, [step.start], texts, bag, dispatch, cwd, vfs, stdin, signal, [], follow, undefined, undefined, step.limited)
: await runSingle('find', [step.start], [...texts], bag, {
find: step.limited,
...(signal === undefined ? {} : { signal }),
});
await materialize(out);
return io;
}, OPERAND_CONCURRENCY);
// A run that failed found no rows and says why; one that succeeded without
// structured rows is a find this line cannot compose.
if (ios.some((io) => io.records === null && io.exitCode === 0))
return walk();
const between = paths.map((_, index) => new Set(plan.filter((s) => s.index === index && s.alone).map((s) => s.start.virtual)));
const kept = await reached(paths, plan.map((step) => [step.index, step.start]), ios.map((io) => io.exitCode !== 0), dispatch);
const done = plan.flatMap((step, i) => {
const io = ios[i];
return kept[i] === true && io !== undefined ? [{ step, io }] : [];
});
const runs = paths.map(() => []);
let merged = new IOResult();
for (const { step, io } of done) {
const owner = mounts.rootOf(step.start.virtual);
for (const { path: row } of io.records ?? []) {
const kept = step.alone
? row.virtual === step.start.virtual
: mounts.rootOf(row.virtual) === owner && !(between[step.index]?.has(row.virtual) ?? false);
if (kept)
runs[step.index]?.push(row);
}
merged = await merged.merge(io);
}
for (const rows of runs)
rows.sort((a, b) => compareCodePoints(a.virtual, b.virtual));
merged.exitCode = Math.max(0, ...done.map(({ io }) => io.exitCode));
merged.records = runs.flatMap((rows, run) => rows.map((path) => ({ path, run })));
const body = new TextEncoder().encode(merged.records.map((record) => `${record.path.rawPath}\n`).join(''));
return [body, merged];
}
//# sourceMappingURL=find.js.map