UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

130 lines • 5.61 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 { 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