@sema-agent/core
Version:
Stateless, task-oriented AI agent core
139 lines (138 loc) • 8.83 kB
JavaScript
import { cardDisclosureOf } from "../allow-entry.js";
import { primaryActivityArg } from "../arg-summary.js";
import { classicEventBase, observeClassic } from "../mods/stations.js";
import { advertisedWritableRootsSeat, exemptRootsSeat, attest, attestationOf, isLiveApproverSeat, resolveAsk } from "../tool-policy.js";
import { boundInputHashOf } from "../canonical-json.js";
import { composeCallSignal, raceSettlementAgainstSignal } from "./abort-race.js";
import { failedJudgmentDeny, policyCheckAbortedDeny } from "./policy-throw.js";
import { askMandateMember } from "../ask-origin.js";
import { approvalCardMemberOf } from "../tool-roster.js";
import { gateAskCarry, lateAskSettlementObserver } from "./denial-limit-arms.js";
import { consumeInheritedAskGrant } from "./inherited-ask-grants.js";
import { settleReadRootGrant } from "./read-root-grant.js";
function createDenyObserverNotifier(bus, signal) {
if (bus === undefined || !bus.hasHandlers("classic.PermissionDenied"))
return undefined;
return (payload) => observeClassic(bus, "classic.PermissionDenied", { ...payload, ...classicEventBase(payload.identity, payload.permissionMode) }, signal);
}
const sanitizePreview = (node, depth = 0) => {
if (depth > 6)
return undefined;
if (typeof node === "string") {
return node.replace(/[\u0000-\u0008\u000b-\u001f\u007f]/g, "\u2400");
}
if (node === null || typeof node !== "object")
return node;
if (Array.isArray(node))
return node.map((v) => sanitizePreview(v, depth + 1));
const out = {};
for (const [k, v] of Object.entries(node)) {
out[sanitizePreview(k, depth + 1)] = sanitizePreview(v, depth + 1);
}
return out;
};
function resolveApprovalPreview(tools, toolName, args) {
const t = tools.find((x) => x.name === toolName || (x.aliases?.includes(toolName) ?? false));
if (t?.approvalPreview === undefined)
return {};
try {
const raw = t.approvalPreview(args);
if (raw === undefined)
return {};
const bytes = JSON.stringify(raw);
if (bytes === undefined)
return { withheld: "unavailable" };
if (bytes.length > 16_384) {
return { preview: { truncated: true, note: `approval preview exceeded 16KiB (${bytes.length} chars serialized)` }, withheld: "oversize" };
}
return { preview: sanitizePreview(raw) };
}
catch {
return { withheld: "unavailable" };
}
}
export function prepareAskLane(input) {
const { gateMachineryActive, abortController, effectivePolicy, budgetSnapshot, handsCwdRef, handsRoot, stampCallTarget, advertisedWritableDirs, exemptRoots, tools, inheritedUnavailableAsks, inheritedAskGrants, onAsk, humanReviewRef, now, ruleOffersOf, grantBindingOf, askSourceIdentity, riskAxesOf, autoModeDenialTracking, spec, deps, sessionId, runId, grantReadRoot, modBus, notifyOwnHookCrash } = input;
if (!gateMachineryActive)
return { askLane: undefined };
const composedCallSignal = (callSignal) => composeCallSignal(abortController.signal, callSignal);
const adjudicate = effectivePolicy
? async (req, callSignal) => {
const signal = composedCallSignal(callSignal);
const stampedReq = () => ({ ...req, budget: budgetSnapshot, ...(handsCwdRef !== undefined ? { cwd: handsCwdRef.current } : {}), ...(handsRoot !== undefined ? { root: handsRoot } : {}), ...advertisedWritableRootsSeat(advertisedWritableDirs), ...exemptRootsSeat(exemptRoots) });
const judged = (async () => {
const withReading = stampCallTarget?.(stampedReq(), signal);
return withReading === undefined ? effectivePolicy.check(stampedReq(), signal) : effectivePolicy.check(await withReading, signal);
})();
const raced = await raceSettlementAgainstSignal(judged, signal);
if (raced.tag === "value")
return raced.value;
if (raced.tag === "aborted")
return policyCheckAbortedDeny();
return failedJudgmentDeny(raced.error, undefined, notifyOwnHookCrash, "the tool policy chain", req);
}
: undefined;
const approvalPreviewOf = (toolName, args) => resolveApprovalPreview(tools, toolName, args);
const resolveAskBound = async (decision, req, callSignal) => {
if (inheritedUnavailableAsks.delete(req.toolCallId)) {
const intercepted = {
action: "deny",
message: `approval for "${req.toolName}" requires an ancestor task's approval that cannot be resolved here ` +
`(no live approver reachable, or a durable-park mandate applies), and no durable approval park is ` +
`available — denied fail-closed (the inherited constraint stands).`,
decisionReason: "mode",
approverUnavailable: true,
};
const settlement = { kind: "approver_unavailable", who: { party: "none" }, when: now() };
return decision.action === "ask"
? attest(intercepted, req, { settlement, ...(decision.origin !== undefined ? { origin: decision.origin } : {}), ...(decision.classes !== undefined ? { classes: decision.classes } : {}), inputDigest: boundInputHashOf(req.args) })
: attest(intercepted, req, { settlement, inputDigest: boundInputHashOf(req.args) });
}
const grantReuse = consumeInheritedAskGrant(inheritedAskGrants, onAsk, humanReviewRef, decision, req);
if (grantReuse !== undefined)
return grantReuse;
const t0 = now();
const resolved = await resolveAsk({
toolName: req.toolName,
toolCallId: req.toolCallId,
args: req.args,
...(() => {
const p = approvalPreviewOf(req.toolName, req.args);
return { ...(p.preview !== undefined ? { preview: p.preview } : {}), ...(p.withheld !== undefined ? { previewWithheld: p.withheld } : {}) };
})(),
...ruleOffersOf(req, req.args, decision.action === "ask" ? decision : undefined),
...grantBindingOf(req, req.args),
...cardDisclosureOf(decision, `approval required for "${req.toolName}"`),
...askSourceIdentity(),
...riskAxesOf(req),
...approvalCardMemberOf(req),
...(decision.action === "ask" && decision.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
...(decision.action === "ask" ? askMandateMember(decision) : {}),
...(decision.action === "ask" ? gateAskCarry(decision, isLiveApproverSeat(onAsk), autoModeDenialTracking.current) : {}),
...(decision.action === "ask" && decision.probeReason !== undefined ? { probeReason: decision.probeReason } : {}),
...(decision.action === "ask" && decision.probeCause !== undefined ? { probeCause: decision.probeCause } : {}),
...(decision.action === "ask" && decision.probeReadRootCandidate !== undefined ? { readRootCandidate: decision.probeReadRootCandidate } : {}),
...(decision.action === "ask" && decision.ruleEvidence !== undefined ? { ruleEvidence: decision.ruleEvidence } : {}),
}, onAsk, composedCallSignal(callSignal), lateAskSettlementObserver({ toolName: req.toolName, toolCallId: req.toolCallId, sessionId, runId, ...(spec.taskId !== undefined ? { taskId: spec.taskId } : {}), onNotice: deps.onNotice, onError: deps.onError }));
const waitMs = Math.max(0, now() - t0);
const granting = settleReadRootGrant(resolved.readRootGrant, grantReadRoot, { toolName: req.toolName, toolCallId: req.toolCallId, sessionId, runId, ...(spec.taskId !== undefined ? { taskId: spec.taskId } : {}), onNotice: deps.onNotice, signal: composedCallSignal(callSignal) });
if (granting !== undefined)
await granting;
if (resolved.approverUnavailable !== true && attestationOf(resolved, req)?.settlement?.kind !== "task_aborted") {
humanReviewRef.count += 1;
humanReviewRef.totalWaitMs += waitMs;
const toolArg = primaryActivityArg(req.args);
humanReviewRef.gates.push({
kind: "human",
waitMs,
decision: resolved.action === "allow" ? "allow" : "deny",
toolName: req.toolName,
toolCallId: req.toolCallId,
...(toolArg !== undefined ? { toolArg } : {}),
});
}
return resolved;
};
const notifyPermissionDenied = createDenyObserverNotifier(modBus, abortController.signal);
return { askLane: { composedCallSignal, adjudicate, approvalPreviewOf, resolveAskBound, notifyPermissionDenied, notifyHookError: notifyOwnHookCrash } };
}