UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

151 lines • 7.5 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 { guardOutput } from "../../commands/builtin/utils/limit.js"; import { runWithAdmission } from "../../context/session_context.js"; import { CommandTimeoutError } from "../../errors/types.js"; import { IOResult, materialize } from "../../io/types.js"; import { postExecuteGate, refusalOf, renderDeny } from "../../policy/index.js"; import { applyBarrier, BarrierPolicy } from "../../shell/barrier.js"; import { encodeText } from "../../shell/bytes.js"; import { Terminal } from "../../shell/console/index.js"; import { inputSubstitutionRedirect } from "../../shell/helpers.js"; import { PathSpec } from "../../types.js"; import { concat } from "../../utils/bytes.js"; import { toScope } from "../executor/builtins/scope.js"; import { handleRedirect } from "../executor/redirect.js"; import { expandRedirect } from "../expand/redirects.js"; import { sessionView } from "../session/state.js"; import { ExecutionNode } from "../types.js"; import { isControlFlowError } from "../workspace/failure.js"; import { admit, Admitted } from "./admission.js"; import { executeNode } from "./execute_node.js"; import { claimantFor } from "./occurrence.js"; export async function runCommandTree(deps, node, context, stdin = null, commandSubstitution = false, // The frames of the caller the tree runs in place of (`eval`), null for // a line of its own. callStack = null, // What `preExecute` saw of the line, which `postExecute` sees with what // came back; null when no policy reads `postExecute`. route = null) { const session = context.session; const redirect = commandSubstitution ? inputSubstitutionRedirect(node) : null; let result; if (redirect === null) { result = await executeNode(deps, node, context, stdin, callStack); } else { const expanded = await expandRedirect(redirect, context, deps.executeFn, deps.registry, null, sessionView(session, deps.registry.policies, context.frame.diagnostics), false, deps.namespace.sessionLinks(session)); // Bash's implicit read uses cat's policy identity without invoking // a shadowing function/alias or expanding the filename a second time. const target = expanded.target; const paths = target instanceof PathSpec ? [target] : typeof target === 'string' ? [toScope(target)] : []; const verdict = await admit('cat', [], [], context.session, deps.registry, deps.namespace, null, paths, deps.signal, claimantFor(node, deps.handed), true); if (!(verdict instanceof Admitted)) { result = [ null, new IOResult({ exitCode: verdict.exitCode, stderr: verdict.stderr, refusal: verdict.refusal, }), new ExecutionNode({ command: 'cat', exitCode: verdict.exitCode, stderr: verdict.stderr, refused: true, }), ]; } else { result = await runWithAdmission(verdict, () => handleRedirect((inner, current, input, stack) => executeNode(deps, inner, current, input, stack), deps.dispatch, null, [expanded], context, stdin, null, true)); } } const [stdout, io, execNode] = result; let materialized; try { materialized = await applyBarrier(stdout, io, BarrierPolicy.VALUE); } catch (err) { if (isControlFlowError(err) || err instanceof CommandTimeoutError) throw err; // Lazy reads can fail on the first pull (e.g. a backend size guard); // surface that as a failed command, not a crash. const msg = err instanceof Error ? err.message : String(err); const existing = await materialize(io.stderr); const added = encodeText(`${msg}\n`); const merged = new Uint8Array(existing.byteLength + added.byteLength); merged.set(existing, 0); merged.set(added, existing.byteLength); io.stderr = merged; io.exitCode = 1; materialized = null; execNode.exitCode = 1; return [materialized, io, execNode]; } // A line written to a terminal (a typed line's, a substitution's) is // bounded as what reached it, its jobs' output included, and what the // bound leaves goes back ahead of anything later. const screen = deps.sink instanceof Terminal && deps.sink.reader === null ? deps.sink : null; if (screen !== null) { const [out, err] = screen.drain(); materialized = concat([out, await materialize(materialized)]); const stderr = concat([err, await materialize(io.stderr)]); io.stderr = stderr.byteLength > 0 ? stderr : null; } // The boundary consultation: what preExecute saw, the envelope's // producer facts, the output and each command's result become the // postExecute context; the built-in cap and any user policies answer // with Limits (tightest merged), enforced by guardOutput. A Deny or an // Ask the host has not approved drops the output; an Ask is claimed on // the line's hand-off as a preExecute one is. const producer = io.producer ?? { command: '', prefixes: [], declared: null }; const handed = deps.handed; const [refused, bound] = route === null ? [null, null] : await postExecuteGate(deps.registry.policies, { ...route, producer, exitCode: io.exitCode, output: await materialize(materialized), results: execNode.toDict(), }, claimantFor(node, handed), deps.signal); if (refused !== null) { const existingErr = await materialize(io.stderr); const subject = producer.command || 'line'; const [denyBytes, exitCode] = renderDeny(subject, refused); const mergedErr = new Uint8Array(existingErr.byteLength + denyBytes.byteLength); mergedErr.set(existingErr, 0); mergedErr.set(denyBytes, existingErr.byteLength); io.stderr = mergedErr; io.exitCode = exitCode; io.refusal = refusalOf(refused); execNode.exitCode = io.exitCode; if (screen !== null) { screen.putBack(new Uint8Array(), mergedErr); io.stderr = null; } return [null, io, execNode]; } const [guarded, guardedErr, guardedCode] = await guardOutput(materialized, io.stderr, io.exitCode, bound); materialized = guarded !== null ? await materialize(guarded) : null; io.stderr = guardedErr; io.exitCode = guardedCode; if (screen !== null) { screen.putBack(materialized ?? new Uint8Array(), await materialize(io.stderr)); materialized = null; io.stderr = null; } return [materialized, io, execNode]; } //# sourceMappingURL=run_tree.js.map