@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
151 lines • 7.5 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 { 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