@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
130 lines • 5.61 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 { linkView } from "../../../../commands/builtin/utils/links.js";
import { isProgramInvocation, runWithAdmission } from "../../../../context/session_context.js";
import { IOResult } from "../../../../io/types.js";
import { encodeText } from "../../../../shell/bytes.js";
import { ExitSignal } from "../../../../shell/errors.js";
import { ShellBuiltin as SB } from "../../../../shell/types.js";
import { wordText } from "../../../../types.js";
import { Files } from "../../../files.js";
import { sessionView } from "../../../session/state.js";
import { ExecutionNode } from "../../../types.js";
import { CondError, CondRefused } from "./errors.js";
import { evalFlat } from "./flat.js";
import { evalCond } from "./tree.js";
/**
* Evaluate test/[ (flat argv) or [[ (condition tree). `name` is the
* invocation spelling used in diagnostics: "test", "[", or "[[".
*
* `[ ]` and `[[ ]]` are the shell's own tests, which no command gate
* admitted, so each is admitted here by its own name on what it tests
* (`admit`), read as every reader of the line reads it (`readsOfTest`):
* `[`'s whole argv before it runs, each `[[` leaf as it is reached. A
* refusal is the test's answer, as it is `test`'s. `admit` is absent where
* the command was admitted already.
*/
export async function handleTest(dispatch, namespace, args, session, name = 'test', view, admit, signal) {
const links = linkView(new Files(dispatch, namespace.readers(() => session.visibility), null, {
clock: namespace.clock,
}));
const ctx = {
dispatch,
namespace,
session,
name,
...(view !== undefined ? { view } : {}),
...(links !== undefined ? { links } : {}),
...(admit !== undefined ? { admit } : {}),
...(signal !== undefined ? { signal } : {}),
};
let code;
try {
if (Array.isArray(args)) {
code = await flatStatus(ctx, args, admit);
}
else {
code = await evalCond(ctx, args);
}
}
catch (exc) {
if (exc instanceof CondRefused) {
return [
null,
new IOResult({ exitCode: exc.exitCode, stderr: exc.stderr, refusal: exc.refusal }),
new ExecutionNode({
command: name,
exitCode: exc.exitCode,
stderr: exc.stderr,
refused: true,
}),
];
}
if (!(exc instanceof CondError))
throw exc;
// The builtin's own diagnostic is in bash's voice; the program
// (`find -exec test`) keeps its bare one.
const own = exc.message.startsWith(`${name}: `) && !isProgramInvocation(session);
const stderr = encodeText(`${own ? 'bash: ' : ''}${exc.message}\n`);
if (name === '[[' && exc.fatal) {
// A bad [[ ]] operator is a bash PARSE error: the whole input
// line dies, not just this command.
throw new ExitSignal(2, stderr, null, 2);
}
return [
null,
new IOResult({ exitCode: exc.exitCode, stderr }),
new ExecutionNode({ command: 'test', exitCode: exc.exitCode, stderr }),
];
}
return [
null,
new IOResult({ exitCode: code }),
new ExecutionNode({ command: 'test', exitCode: code }),
];
}
/**
* The status of a flat test, admitted whole first where it has to be: on
* its words and its closing `]`, as the command `[` is (`admit`, absent
* where the command was admitted already).
*/
async function flatStatus(ctx, args, admit) {
if (admit === undefined)
return (await evalFlat(ctx, args)) ? 0 : 1;
const words = args.map((a) => wordText(a));
const gate = await admit([...words, ']']);
return runWithAdmission(gate, async () => ((await evalFlat(ctx, args)) ? 0 : 1));
}
/** The `test` / `[` / `[[` arm; `[` must close with `]`. */
export async function testBuiltin(call) {
const name = call.argv.name;
let testArgs = [...call.argv.operands];
const testName = name === SB.BRACKET ? '[' : 'test';
if (name === SB.BRACKET) {
const last = testArgs[testArgs.length - 1];
if (last !== undefined && wordText(last) === ']') {
testArgs = testArgs.slice(0, -1);
}
else {
const err = encodeText(isProgramInvocation(call.context.session) ? "[: missing ']'\n" : "bash: [: missing `]'\n");
return [
null,
new IOResult({ exitCode: 2, stderr: err }),
new ExecutionNode({ command: '[', exitCode: 2, stderr: err }),
];
}
}
return handleTest(call.dispatch, call.namespace, testArgs, call.context.session, testName, sessionView(call.context.session, call.registry.policies, call.context.frame.diagnostics));
}
//# sourceMappingURL=condition.js.map