@sema-agent/core
Version:
Stateless, task-oriented AI agent core
255 lines (254 loc) • 14.2 kB
JavaScript
import { inlineUntrusted } from "./untrusted-text.js";
import { markTightenMessage } from "./safety-axis-vocab.js";
import { PROBE_REASON_MAX, normalizeProbeCause } from "./checkpoint-store.js";
import { normalizeReadRootCandidate } from "./read-root-candidate.js";
import {} from "./runner/gate-exit.js";
import { traceHookCrash } from "./gate-crash-trace.js";
import { writeProtectionMandated, writeProtectionTightenDecision, writeProtectionUnresolved } from "./write-protect.js";
import { createGateDeadline, awaitWriteProtectionJudgment, gateWaitFailureOf, withGateTimeout } from "./gate-bounded-await.js";
import { thrownText } from "./thrown-value.js";
import { refuseOutOfContractDecision } from "./tool-policy.js";
import { asksUnstated, permissionSeatAt, unstatedAskMessage } from "./mode-policy.js";
export async function runGateFold(pass) {
const { input, toolName, toolCallId, callSignal, ledger, notifier, adjudicate } = pass;
const boundedAwaitDeadlineMs = createGateDeadline(input, notifier);
pass.req = { toolName, args: pass.currentInput, toolCallId, ...(pass.req.face !== undefined ? { face: pass.req.face } : {}) };
pass.decision = adjudicate ? refuseOutOfContractDecision(await (callSignal !== undefined ? adjudicate(pass.req, callSignal) : adjudicate(pass.req))) : { action: "allow" };
{
const refusedBy = ledger.fromDecision(pass.decision);
if (refusedBy !== undefined)
pass.deniedBy = refusedBy;
if (refusedBy !== undefined)
pass.denyCause = ledger.causeOf(pass.decision);
const attributed = ledger.classifierRoundOf(pass.decision);
if (attributed !== undefined)
pass.classifierRound = attributed;
}
if ((pass.decision.action === "allow" || pass.decision.action === "ask") && pass.decision.updatedInput !== undefined) {
pass.policyRewrite = pass.decision.updatedInput;
}
else if (pass.hookAsk?.updatedInput !== undefined) {
pass.policyRewrite = pass.hookAsk.updatedInput;
}
if (pass.decision.action === "allow" && pass.hookAsk) {
pass.decision = {
action: "ask",
...(pass.hookAsk.message !== undefined ? { message: pass.hookAsk.message } : {}),
decisionReason: "hook",
...(pass.hookAsk.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
...(pass.hookAsk.updatedInput !== undefined ? { updatedInput: pass.hookAsk.updatedInput } : {}),
};
}
else if (pass.decision.action === "ask" && pass.hookAsk) {
const hookMessage = pass.hookAsk.message;
const foldedMessage = hookMessage !== undefined && hookMessage !== pass.decision.message
? `${pass.decision.message !== undefined ? `${pass.decision.message} ` : ""}(a PreToolUse hook also asks: ${hookMessage})`
: pass.decision.message;
pass.decision = {
...pass.decision,
decisionReason: "hook",
...(foldedMessage !== undefined ? { message: foldedMessage } : {}),
...(pass.hookAsk.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
};
}
if ((pass.decision.action === "allow" || pass.decision.action === "ask") && input.shellReadCheck !== undefined) {
const station = input.shellReadCheck;
const judged = station.judge(pass.req, pass.policyRewrite !== undefined ? pass.policyRewrite : pass.currentInput, input.trackedCwd?.(), pass.callSignal ?? input.abortSignal, { toolCallId });
if (judged !== null) {
let verdict;
try {
verdict = await withGateTimeout(judged, boundedAwaitDeadlineMs(), pass.callSignal ?? input.abortSignal, "shellReadStation");
}
catch (err) {
const kind = gateWaitFailureOf(err);
if (kind !== "aborted" && input.abortSignal?.aborted !== true) {
traceHookCrash(input, new Error(`shell read station ${kind === "timeout" ? "timed out" : "threw"} for tool "${toolName}" (toolCallId ${toolCallId}) — where this call reads could not be established: ${thrownText(err)}`), notifier);
}
verdict = station.unanswered(pass.req, kind === "timeout" ? "the read station timed out" : "the read station could not answer");
}
applyShellReadVerdict(pass, verdict, toolName, input.shellGated === true);
}
}
if (input.requiresUserInteraction === true && pass.decision.action === "allow") {
pass.decision = { action: "ask", message: markTightenMessage(toolName, "interaction"), decisionReason: "safety" };
pass.tightenedBy = "safety";
}
if (input.egress && pass.decision.action === "allow") {
pass.decision = {
action: "ask",
message: markTightenMessage(toolName, "egress"),
decisionReason: "safety",
};
pass.tightenedBy = "safety";
}
const alreadyAsking = pass.decision.action === "ask";
if ((pass.decision.action === "allow" || alreadyAsking) && (input.irreversibility === "always" || input.irreversibility === "maybe")) {
let tighten = !alreadyAsking;
const withdrawStationOffer = () => {
if (alreadyAsking && pass.shellReadOffer === true && pass.decision.action === "ask" && pass.decision.probeReadRootCandidate !== undefined) {
const { probeReadRootCandidate: _withdrawn, ...kept } = pass.decision;
pass.decision = kept;
}
};
if (input.irreversibility === "always" || !input.reversibilityProbe)
withdrawStationOffer();
let probeReason;
let probeCause;
let probeMandated;
let probeReadRootCandidate;
if (input.irreversibility === "maybe" && input.reversibilityProbe) {
let reversible = false;
const probeArgs = pass.policyRewrite !== undefined ? pass.policyRewrite : pass.currentInput;
const probeTimeoutMs = boundedAwaitDeadlineMs();
try {
const verdict = await withGateTimeout(Promise.resolve(input.reversibilityProbe(probeArgs)), probeTimeoutMs, input.abortSignal);
reversible = verdict?.reversible === true;
if (!reversible) {
const raw = verdict?.reason;
if (typeof raw === "string" && raw.trim().length > 0)
probeReason = inlineUntrusted(raw, PROBE_REASON_MAX);
probeCause = normalizeProbeCause(verdict?.cause);
if (verdict?.mandated === true)
probeMandated = true;
if (verdict?.boundaryDeclined === true)
pass.probeBoundaryDeclined = true;
probeReadRootCandidate = normalizeReadRootCandidate(verdict?.readRootCandidate);
withdrawStationOffer();
}
}
catch (err) {
reversible = false;
pass.probeUnanswered = true;
withdrawStationOffer();
if (input.abortSignal?.aborted !== true)
traceHookCrash(input, err, notifier);
}
tighten = !alreadyAsking && !reversible;
}
if (tighten) {
pass.decision = {
action: "ask",
message: markTightenMessage(toolName, "irreversible"),
decisionReason: "safety",
...(probeReason !== undefined ? { probeReason } : {}),
...(probeCause !== undefined ? { probeCause } : {}),
...(probeMandated === true ? { probeMandated: true } : {}),
...(probeReadRootCandidate !== undefined ? { probeReadRootCandidate } : {}),
};
pass.tightenedBy = input.shellGated === true ? "shell_gate" : "safety";
}
else if (alreadyAsking && probeMandated === true && pass.decision.action === "ask") {
pass.decision = { ...pass.decision, probeMandated: true };
}
}
if (input.peerMessage === true && pass.decision.action === "allow" && input.autoMode !== undefined) {
pass.decision = {
action: "ask",
message: `tool "${toolName}" sends a message to another agent — routed for classifier review in auto mode`,
decisionReason: "safety",
};
pass.tightenedBy = "safety";
}
const writeProtectionStanding = pass.decision.action;
if ((writeProtectionStanding === "allow" || writeProtectionStanding === "ask") && input.writeProtectionCheck !== undefined) {
const judged = input.writeProtectionCheck(toolName, pass.policyRewrite !== undefined ? pass.policyRewrite : pass.currentInput, pass.req.face, input.trackedCwd?.(), pass.callSignal ?? input.abortSignal);
const verdict = judged === null
? undefined
: await awaitWriteProtectionJudgment(judged, boundedAwaitDeadlineMs(), pass.callSignal ?? input.abortSignal, (err) => {
const kind = gateWaitFailureOf(err);
if (kind === "aborted" || input.abortSignal?.aborted === true)
return;
traceHookCrash(input, new Error(`write-protection judge ${kind === "timeout" ? "timed out" : "threw"} for tool "${toolName}" (toolCallId ${toolCallId}) — this call's write target could not be judged: ${thrownText(err)}`), notifier);
});
if (verdict !== undefined && writeProtectionMandated(verdict))
pass.writeProtected = true;
if (verdict !== undefined && writeProtectionUnresolved(verdict))
pass.writeProtectionUnresolved = true;
const decision = verdict === undefined ? undefined : writeProtectionTightenDecision(toolName, verdict);
if (decision !== undefined && writeProtectionStanding === "allow") {
pass.decision = decision;
if (decision.action === "ask")
pass.tightenedBy = "safety";
else
pass.deniedBy = "write_protection";
}
}
if (asksUnstated(pass.decision, permissionSeatAt(input, pass.policyRewrite !== undefined ? pass.policyRewrite : pass.currentInput)?.seat, input.permissionMode?.())) {
pass.decision = { action: "ask", message: permissionSeatAt(input, pass.policyRewrite !== undefined ? pass.policyRewrite : pass.currentInput)?.question ?? unstatedAskMessage(toolName), ...(pass.policyRewrite !== undefined ? { updatedInput: pass.policyRewrite } : {}) };
pass.tightenedBy = "unstated";
}
return await pass.afterFold(pass);
}
function shellReadAskMessage(toolName, finding) {
return finding === "outside_roots"
? `tool "${toolName}" reads outside the directories allowed for this session — explicit approval required`
: `tool "${toolName}" reads where the session's read boundary could not establish what is read — explicit approval required`;
}
function applyShellReadVerdict(pass, verdict, toolName, shellGated) {
const account = verdict.sentence !== undefined && verdict.sentence.trim().length > 0 ? inlineUntrusted(verdict.sentence, PROBE_REASON_MAX) : undefined;
if (verdict.writesPlaced === true)
pass.shellWritesPlaced = true;
if (verdict.writeProtected === true)
pass.writeProtected = true;
if (verdict.unplacedOptions !== undefined)
pass.shellUnplacedOptions = verdict.unplacedOptions;
if (verdict.finding === "unestablishable" || verdict.finding === "declined" || verdict.unestablished === true)
pass.shellReadUnestablished = true;
switch (verdict.disposition) {
case "vouch":
return;
case "refuse":
pass.decision = { action: "deny", message: verdict.sentence ?? `tool "${toolName}" reads what the session's read boundary refuses` };
pass.deniedBy = "read_boundary";
if (verdict.details !== undefined)
pass.denyDetails = verdict.details;
return;
case "mandated": {
if (verdict.details !== undefined)
pass.shellReadDetails = verdict.details;
if (pass.decision.action !== "allow") {
if (pass.decision.action === "ask")
pass.decision = { ...pass.decision, probeMandated: true };
return;
}
const candidate = normalizeReadRootCandidate(verdict.readRootCandidate);
pass.decision = {
action: "ask",
message: shellReadAskMessage(toolName, verdict.finding),
decisionReason: "safety",
probeMandated: true,
...(account !== undefined ? { probeReason: account } : {}),
...(candidate !== undefined ? { probeReadRootCandidate: candidate } : {}),
};
if (candidate !== undefined)
pass.shellReadOffer = true;
pass.tightenedBy = shellGated ? "shell_gate" : "safety";
return;
}
case "ordinary": {
verdict.admit?.();
if (verdict.details !== undefined)
pass.shellReadDetails = verdict.details;
if (pass.decision.action === "allow") {
const candidate = normalizeReadRootCandidate(verdict.readRootCandidate);
pass.decision = {
action: "ask",
message: shellReadAskMessage(toolName, verdict.finding),
decisionReason: "safety",
...(account !== undefined ? { probeReason: account } : {}),
...(candidate !== undefined ? { probeReadRootCandidate: candidate } : {}),
};
if (candidate !== undefined)
pass.shellReadOffer = true;
pass.tightenedBy = shellGated ? "shell_gate" : "safety";
}
if (verdict.finding === "declined")
pass.probeBoundaryDeclined = true;
return;
}
default: {
const exhaustive = verdict.disposition;
return exhaustive;
}
}
}