@sema-agent/core
Version:
Stateless, task-oriented AI agent core
168 lines (167 loc) • 9.97 kB
JavaScript
import { markTightenMessage } from "../safety-axis-vocab.js";
import { classifierParseFailureDenyMessage, createAutoModeDenialTracker, denialLimitFallbackMessage, denialLimitSentence, unarmedWindow } from "../auto-mode.js";
import { sanitizeAutoModeArmingRecipe, tightenDenialLimit } from "../auto-mode-arming.js";
import { classifierUnavailableArm } from "../classifier-unavailable-arm.js";
import { attestClassifierDenyCause, attestClassifierRound, carryEngineAttestations } from "../tool-policy.js";
import { SETTLEMENT_NOBODY_JUDGED } from "../gate-outcome.js";
import { deliverEngineNotice } from "../types.js";
import { inlineUntrusted } from "../untrusted-text.js";
import { askClassMembersOf, askOriginOf, classifyAsk } from "../ask-origin.js";
export function attachRebuiltDenialTrackers(entries, denialLimit) {
if (entries === undefined)
return undefined;
const needsTracker = (pc) => pc.autoMode !== undefined && pc.autoMode.denialTracking === undefined;
const boundsFor = new Map();
const liveTrackers = new Map();
for (const pc of entries) {
if (pc.autoMode?.denialTracking !== undefined)
liveTrackers.set(pc.autoMode.decider, pc.autoMode.denialTracking);
}
for (const pc of entries) {
if (!needsTracker(pc) || liveTrackers.has(pc.autoMode.decider))
continue;
const recorded = sanitizeAutoModeArmingRecipe(pc.autoMode.arming);
if (recorded === undefined)
continue;
const merged = tightenDenialLimit(recorded.denialLimit, denialLimit);
const seen = boundsFor.get(pc.autoMode.decider);
boundsFor.set(pc.autoMode.decider, seen === undefined ? merged : tightenDenialLimit(seen, merged));
}
const rebuiltTrackers = new Map(liveTrackers);
const trackerForRebuilt = (decider) => {
let t = rebuiltTrackers.get(decider);
if (t === undefined) {
t = createAutoModeDenialTracker(boundsFor.get(decider) ?? denialLimit);
rebuiltTrackers.set(decider, t);
}
return t;
};
return entries.map((pc) => (needsTracker(pc) ? { ...pc, autoMode: { ...pc.autoMode, denialTracking: trackerForRebuilt(pc.autoMode.decider) } } : pc));
}
export function createDenialLimitStop(opts) {
const gateStopRef = {};
const stopForDenialLimit = (info) => {
if (gateStopRef.terminal !== undefined)
return;
const lane = info.settlement === undefined
? `no approver is wired to fall back to`
: `the durable approval it parked was settled with nobody judging it ("${info.settlement}")`;
const message = `too many classifier denials in headless mode — ${denialLimitSentence(info.fallback)} The run was stopped: ` +
`the auto-mode classifier's denial limit falls back to a person, and ${lane} ` +
`(latest blocked action: "${info.toolName}").`;
const terminal = Object.assign(new Error(message), { code: "classifier.denial_limit" });
gateStopRef.terminal = terminal;
deliverEngineNotice(opts.onNotice, {
code: "classifier.denial_limit",
message,
detail: { sessionId: opts.sessionId, runId: opts.runId, toolName: info.toolName, toolCallId: info.toolCallId, consecutive: info.fallback.consecutive, total: info.fallback.total, limit: info.fallback.limit },
});
opts.abort();
};
return { gateStopRef, stopForDenialLimit };
}
export const INHERITED_STATION_FACTS = { contentQuestion: false, markedUnresolvable: false, org: undefined, tightened: undefined, mandateMarks: {} };
function fallbackCarry(fallback, liveApprover, tracker) {
if (fallback === undefined)
return {};
return { denialLimitFallback: liveApprover && tracker !== undefined ? tracker.armTimedWindow(fallback) : unarmedWindow(fallback) };
}
export function gateAskCarry(decision, liveApprover, tracker) {
return {
...(decision.origin !== undefined ? { origin: decision.origin } : {}),
...(decision.askClass !== undefined && decision.classes !== undefined ? { askClass: decision.askClass, classes: decision.classes } : {}),
...fallbackCarry(decision.denialLimitFallback, liveApprover, tracker),
};
}
export function inheritedAskCarry(judged, liveApprover, tracker) {
return {
origin: judged.origin,
...askClassMembersOf(judged.classification),
...fallbackCarry(judged.fallback, liveApprover, tracker),
};
}
export const inheritedAskFallbackMessage = (toolName) => `approval required for "${toolName}" (inherited parent policy)`;
export async function judgeInheritedClassifier(opts) {
const { autoMode, req } = opts;
const facts = opts.facts ?? INHERITED_STATION_FACTS, m = facts.mandateMarks;
const frames = [...(m.egress === true ? [markTightenMessage(req.toolName, "egress")] : []), ...(m.irreversibility === "always" ? [markTightenMessage(req.toolName, "irreversible")] : [])];
const ask = (frames.length > 0 ? carryEngineAttestations(opts.ask, { ...opts.ask, message: [opts.ask.message ?? inheritedAskFallbackMessage(req.toolName), ...frames].join(" — ") }) : opts.ask);
const origin = askOriginOf(ask, facts);
const classification = classifyAsk(ask, facts);
const classifierStandsDown = () => {
const live = opts.liveMode?.();
return live === "dontAsk" || live === "bypassPermissions" || live === "plan" || (autoMode?.liveMode !== undefined && autoMode.liveMode.current !== "auto");
};
if (autoMode === undefined || classifierStandsDown() || !classification.row.classifierEligible) {
return { kind: "resolve", ask, fallback: ask.denialLimitFallback, mintedHere: false, origin, classification };
}
const verdict = await autoMode.decider
.decide({ req, ...(ask.message !== undefined ? { askMessage: ask.message } : {}) }, opts.signal)
.catch(() => ({ kind: "unavailable", cause: "error" }));
if (verdict.kind === "allow" && classifierStandsDown())
return { kind: "resolve", ask, fallback: ask.denialLimitFallback, mintedHere: false, origin, classification };
const attributed = (d) => (verdict.classifier !== undefined ? attestClassifierRound(d, verdict.classifier, req) : d);
if (verdict.kind === "allow") {
autoMode.denialTracking?.recordAllow();
return { kind: "allow", result: attributed({ action: "allow", decisionReason: "classifier", ...(opts.allowRewrite !== undefined ? { updatedInput: opts.allowRewrite } : {}) }) };
}
if (verdict.kind === "unavailable") {
const arm = classifierUnavailableArm({ ask, req, cause: verdict.cause, seat: opts.unavailableSeat, aborted: opts.signal.aborted });
if (arm.kind === "asked")
return { kind: "resolve", ask: arm.ask, fallback: ask.denialLimitFallback, mintedHere: false, origin, classification: classifyAsk(arm.ask, facts) };
return { kind: "deny", result: attributed(arm.deny) };
}
const reason = verdict.kind === "block" ? (verdict.reason ? inlineUntrusted(verdict.reason) : "") : classifierParseFailureDenyMessage(verdict.classifier);
const category = verdict.kind === "block" && verdict.category ? inlineUntrusted(verdict.category) : "";
const tracked = autoMode.denialTracking?.recordBlock();
if (tracked?.limitReached !== true) {
const denied = {
action: "deny",
message: verdict.kind === "block" ? `auto-mode classifier blocked ${opts.subject} at an inherited ancestor layer${reason ? `: ${reason}` : category ? `: [${category}]` : ""}` : `${reason} (${opts.subject}, at an inherited ancestor layer)`,
decisionReason: "classifier",
};
return { kind: "deny", result: attributed(verdict.kind === "parse_error" ? attestClassifierDenyCause(denied, "parse_error", req) : denied) };
}
const fallback = tracked.fallback;
const minted = carryEngineAttestations(ask, { ...ask, message: denialLimitFallbackMessage(fallback, reason || category || req.toolName), decisionReason: "classifier", requiresRealApproval: true, denialLimitFallback: fallback });
return { kind: "resolve", ask: minted, fallback, mintedHere: true, origin: askOriginOf(minted, facts), classification: classifyAsk(minted, facts) };
}
export function headlessDenyAtFold(r) {
return r.settlement !== undefined && SETTLEMENT_NOBODY_JUDGED[r.settlement.kind];
}
export function headlessDenyAtRecheck(r) {
return headlessDenyAtFold(r) || r.settlement?.kind === "approver_unavailable" || r.approverUnavailable === true;
}
export function settleDenialLimitFallback(opts) {
const { fallback, resolved } = opts;
if (fallback === undefined)
return;
if (resolved.action === "allow") {
if (opts.mintedHere)
opts.tracker?.recordAllow();
}
else if (resolved.action === "deny" && opts.headless(resolved)) {
opts.stop({ toolName: opts.toolName, toolCallId: opts.toolCallId, fallback });
}
}
export function lateAskSettlementObserver(args) {
return (late) => {
if (late.kind === "approve") {
deliverEngineNotice(args.onNotice, {
code: "task.late_approval",
message: `an approval for "${args.toolName}" was not consumed: a run or turn interrupt released the ask wait, ` +
`so the tool did NOT run and the approval was not honored (an unconsumed answer is the approver ` +
`releasing its wait, never a verdict).`,
detail: {
toolName: args.toolName,
toolCallId: args.toolCallId,
sessionId: args.sessionId,
runId: args.runId,
...(args.taskId !== undefined ? { taskId: args.taskId } : {}),
},
});
return;
}
args.onError?.(late.error, { phase: "hook", sessionId: args.sessionId });
};
}