UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

139 lines (138 loc) • 8.83 kB
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 } }; }