UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

420 lines (419 loc) • 25.2 kB
import { isShellFaceToolName } from "./tool-registry.js"; export const ASK_ORIGINS = Object.freeze(["content_question", "plan_review", "ancestor_marked", "org_unavailable", "org_rule", "rule_store_unavailable", "hook", "ask_rule", "denial_limit_fallback", "shell_gate_tighten", "safety_tighten", "policy"]); const ASK_ORIGIN_SET = new Set(ASK_ORIGINS); export function isAskOrigin(v) { return ASK_ORIGIN_SET.has(v); } export function askOriginOf(decision, facts) { const origin = originLadder(decision, facts); if (ORIGIN_IMPLIES_REAL_APPROVAL[origin] === "at_derivation" && decision.requiresRealApproval !== true) { throw new Error(`ask origin "${origin}" requires the requiresRealApproval bit on the decision it was derived from, and the decision carries none — an engine mint site dropped the bit`); } return origin; } const TIGHTEN_ONLY_ORIGINS = new Set(["shell_gate_tighten", "safety_tighten"]); export function stampAskOrigin(decision, facts, classification) { const origin = askOriginOf(decision, facts); const classed = askClassMembersOf(classification); const { mandate: _selfDeclared, ...unstamped } = decision; const clearance = ruleClearanceOf(unstamped, facts.mandateMarks); const mandated = { ...unstamped, ...(clearance.mandated ? { mandated: true } : {}), ...(clearance.mandate !== undefined ? { mandate: clearance.mandate } : {}), ...(facts.mandateMarks.requiresUserInteraction === true ? { requiresRealApproval: true } : {}) }; if (mandated.probeReadRootCandidate === undefined || TIGHTEN_ONLY_ORIGINS.has(origin)) return { ...mandated, origin, ...classed }; const { probeReadRootCandidate: _withdrawn, ...kept } = mandated; return { ...kept, origin, ...classed }; } export const RULE_OFFERS_ABSENCES = Object.freeze(["mandated", "allow_outranked", "ask_rule_matched", "lane_cannot_speak"]); export function ruleClearanceOf(ask, marks) { const marksWord = persistedRuleMandateOf({ ...marks, probeMandated: ask?.probeMandated === true }); const foldedDoor = (ask?.foldedAskClasses ?? []).some((c) => !MODE_ARBITRATION[c].ruleMayClear); const doorOutsideTable = ask?.requiresRealApproval === true || ask?.decisionReason === "hook" || ask?.decisionReason === "mode" || foldedDoor || ask?.authoredBy === "deployment" || ask?.inheritedUnresolved === true || ask?.ancestorResolved === true; const mandated = ask?.mandated === true || doorOutsideTable || marksWord !== undefined; const stampedWord = ask?.mandated === true && isPersistedRuleMandate(ask.mandate) ? ask.mandate : undefined; const mandate = doorOutsideTable || (ask?.mandated === true && stampedWord === undefined) ? undefined : (stampedWord ?? marksWord); const word = (() => { if (ask?.requiresRealApproval === true) return "mandated"; if (Array.isArray(ask?.allowRuleShadowed) && ask.allowRuleShadowed.length > 0) return "allow_outranked"; if (ask?.decisionReason === "hook") return "mandated"; if (ask?.decisionReason === "mode" || foldedDoor) return "mandated"; if (ask?.authoredBy === "deployment") return "mandated"; if (ask?.matchedAskRule !== undefined) return "ask_rule_matched"; if (ask?.inheritedUnresolved === true) return "mandated"; if (ask?.ancestorResolved === true) return "mandated"; return mandated ? "mandated" : undefined; })(); return { ...(word !== undefined ? { word } : {}), mandated, ruleMayClear: !mandated && ask?.matchedAskRule === undefined, ...(mandate !== undefined ? { mandate } : {}) }; } export function askMandateMember(ask) { const { mandated, mandate } = ruleClearanceOf(ask, {}); const authored = ask?.authoredBy === "deployment" ? { authoredBy: "deployment" } : {}; return mandated ? { mandated: true, ...(mandate !== undefined ? { mandate } : {}), ...authored } : authored; } export function ownedClearanceFacts(d) { const { mandated, requiresRealApproval, decisionReason, matchedAskRule, probeMandated, authoredBy, foldedAskClasses } = d; return { ...(mandated === true ? { mandated } : {}), ...(Array.isArray(foldedAskClasses) && foldedAskClasses.length > 0 ? { foldedAskClasses: [...foldedAskClasses] } : {}), ...(authoredBy === "deployment" ? { authoredBy } : {}), ...(requiresRealApproval === true ? { requiresRealApproval } : {}), ...(decisionReason !== undefined ? { decisionReason } : {}), ...(typeof matchedAskRule === "string" ? { matchedAskRule } : {}), ...(probeMandated === true ? { probeMandated } : {}), }; } function originLadder(decision, facts) { if (facts.contentQuestion) return "content_question"; if (facts.markedUnresolvable) return "ancestor_marked"; if (facts.org === "unavailable") return "org_unavailable"; if (facts.org === "rule") return "org_rule"; if (facts.ruleCallUnreadable === true || facts.ruleStoreUnreadable === true) return "rule_store_unavailable"; if (decision.decisionReason === "hook") return "hook"; if (decision.matchedAskRule !== undefined) return "ask_rule"; if (decision.denialLimitFallback !== undefined) return "denial_limit_fallback"; if (facts.tightened === "shell_gate") return "shell_gate_tighten"; if (facts.tightened === "safety") return "safety_tighten"; return "policy"; } export const ORIGIN_IMPLIES_REAL_APPROVAL = { content_question: false, plan_review: false, ancestor_marked: false, org_unavailable: "at_derivation", org_rule: "at_derivation", rule_store_unavailable: "at_derivation", hook: false, ask_rule: false, denial_limit_fallback: "at_mint", shell_gate_tighten: false, safety_tighten: false, policy: false, }; export const PERSISTED_RULE_MANDATES = Object.freeze(["operator_always", "tool_marks", "probe_mandate", "probe_unanswered", "write_protection", "target_unresolved"]); const PERSISTED_RULE_MANDATE_SET = new Set(PERSISTED_RULE_MANDATES); export function isPersistedRuleMandate(v) { return PERSISTED_RULE_MANDATE_SET.has(v); } const MANDATE_DISCLOSURE = { probe_mandate: "the reversibility check declared this call structurally gated (the built-in shell check raises this for a read outside the directories allowed for this session) — it is cleared by confirming this call, never by a standing allow rule", probe_unanswered: "the reversibility check did not answer for this call (it timed out or failed), so whether the call reads outside the directories allowed for this session is unknown — it is cleared by confirming this call, never by a standing allow rule", operator_always: "this deployment mandates per-call confirmation for shell commands (shellGate: always)", tool_marks: "this tool carries egress/irreversibility/interaction marks (a mandated confirmation a rule cannot clear)", write_protection: "this write's target is on the deployment's write-protection table (the agent's own configuration is guarded one look per write) — it is cleared by confirming this call, never by a standing allow rule", target_unresolved: "where this write leads on disk could not be determined (a link on the way could not be examined, or the environment could not resolve the target), so whether it lands on a guarded path is unknown — it is cleared by confirming this call: no standing rule and no automatic decider answers for it, though a wired approver seat can", }; export function mandateDisclosureOf(mandate) { return mandate === undefined ? "an explicit ask rule matched this call (a person's or the deployment's ask-me-each-time outranks a standing allow rule)" : MANDATE_DISCLOSURE[mandate]; } export function persistedRuleMandateOf(marks) { if (marks.targetUnresolved === true) return "target_unresolved"; const word = irreversibilityMandateOf(marks); if (word === "probe_mandate" || word === "probe_unanswered") return word; if (marks.writeProtected === true) return "write_protection"; return marks.egress === true || marks.requiresUserInteraction === true ? "tool_marks" : word; } function irreversibilityMandateOf(marks) { return marks.probeMandated === true ? "probe_mandate" : marks.probeUnanswered === true ? "probe_unanswered" : marks.shellGated === true ? marks.irreversibility === "always" ? "operator_always" : undefined : marks.irreversibility === "always" || marks.irreversibility === "maybe" ? "tool_marks" : undefined; } export function safetyAxisOf(marks) { const egress = marks.egress === true; const raised = irreversibilityMandateOf(marks) !== undefined || (marks.shellGated === true && marks.irreversibility === "maybe" && marks.tightened === "shell_gate"); const shell = raised && marks.shellGated === true; return egress || raised ? { egress, irreversible: raised && !shell, ...(shell ? { shell: true } : {}) } : undefined; } export const PERMISSION_MODES = Object.freeze(["default", "acceptEdits", "plan", "auto", "bypassPermissions", "dontAsk"]); const PERMISSION_MODE_SET = new Set(PERMISSION_MODES); export function isPermissionMode(v) { return PERMISSION_MODE_SET.has(v); } export const PERMISSION_MODE_RANK = { plan: 0, default: 1, dontAsk: 1, acceptEdits: 2, auto: 3, bypassPermissions: 4 }; export function clampPermissionMode(declared, ceiling) { if (declared === undefined) return { effective: ceiling, clamped: false }; if (declared === ceiling) return { effective: declared, clamped: false }; if (declared === "bypassPermissions") return { effective: ceiling, clamped: true }; if ((declared === "acceptEdits" && ceiling === "auto") || (declared === "auto" && ceiling === "acceptEdits")) return { effective: ceiling, clamped: true }; return PERMISSION_MODE_RANK[declared] <= PERMISSION_MODE_RANK[ceiling] ? { effective: declared, clamped: false } : { effective: ceiling, clamped: true }; } export const ASK_CLASSES = Object.freeze([ "content_question", "hook", "ancestor", "dangerous_delete", "unverifiable_delete", "unverifiable_shell_text", "transcript_write", "org_rule", "rule_unreadable", "ask_rule", "denial_limit_fallback", "classifier_no_ruling", "requires_user_interaction", "operator_forced", "shell_floor", "target_unresolved", "probe", "write_protection", "tool_marks_mcp", "tool_marks_a2a", "policy_rra", "artifact_shared_read", "mode", "unstated", "ordinary", ]); const ASK_CLASS_SET = new Set(ASK_CLASSES); export function isAskClass(v) { return ASK_CLASS_SET.has(v); } const REMOVAL_BREAKER_ROW = Object.freeze({ bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: true }); export const MODE_ARBITRATION = { content_question: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: true }, hook: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false }, ancestor: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false }, dangerous_delete: REMOVAL_BREAKER_ROW, unverifiable_delete: REMOVAL_BREAKER_ROW, unverifiable_shell_text: REMOVAL_BREAKER_ROW, transcript_write: { bypassImmune: false, classifierEligible: true, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: true }, org_rule: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false }, rule_unreadable: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false }, ask_rule: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: true }, denial_limit_fallback: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false }, classifier_no_ruling: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false }, requires_user_interaction: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: true }, operator_forced: { bypassImmune: true, classifierEligible: true, sandboxAdmission: true, ruleMayClear: false, hookAskFloor: false }, shell_floor: { bypassImmune: true, classifierEligible: true, sandboxAdmission: true, ruleMayClear: false, hookAskFloor: false }, target_unresolved: { bypassImmune: true, classifierEligible: false, sandboxAdmission: true, ruleMayClear: false, hookAskFloor: true }, probe: { bypassImmune: false, classifierEligible: true, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false }, write_protection: { bypassImmune: false, classifierEligible: true, sandboxAdmission: true, ruleMayClear: false, hookAskFloor: true }, tool_marks_mcp: { bypassImmune: false, classifierEligible: true, sandboxAdmission: true, ruleMayClear: false, hookAskFloor: false }, tool_marks_a2a: { bypassImmune: false, classifierEligible: true, sandboxAdmission: true, ruleMayClear: false, hookAskFloor: false }, policy_rra: { bypassImmune: true, classifierEligible: true, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false }, artifact_shared_read: { bypassImmune: false, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: true }, mode: { bypassImmune: false, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false }, unstated: { bypassImmune: false, classifierEligible: true, sandboxAdmission: false, ruleMayClear: true, hookAskFloor: false }, ordinary: { bypassImmune: false, classifierEligible: true, sandboxAdmission: true, ruleMayClear: true, hookAskFloor: false }, }; export const ASK_FACES = Object.freeze(["shell", "other"]); export function askFaceOf(toolName) { return isShellFaceToolName(toolName) ? "shell" : "other"; } const RELEASED_ON_SHELL_FACE = new Set(["target_unresolved"]); function bypassImmuneOn(c, face) { return MODE_ARBITRATION[c].bypassImmune && !(face === "shell" && RELEASED_ON_SHELL_FACE.has(c)); } const CLASSIFIER_WITHHELD_ON_ROOTS = new Set(["probe"]); export const RRA_EXPLAINERS = new Set(["dangerous_delete", "unverifiable_delete", "unverifiable_shell_text", "transcript_write", "org_rule", "rule_unreadable", "denial_limit_fallback", "classifier_no_ruling", "ancestor", "artifact_shared_read", "requires_user_interaction"]); const RELEASABLE_BY_FACE = Object.freeze({ shell: Object.freeze(ASK_CLASSES.filter((c) => !bypassImmuneOn(c, "shell"))), other: Object.freeze(ASK_CLASSES.filter((c) => !bypassImmuneOn(c, "other"))), }); export function releasableAskClassesFor(face) { if (!ASK_FACES.includes(face)) throw new TypeError(`releasableAskClassesFor: "${String(face)}" is not an ask face (${ASK_FACES.join(", ")})`); return RELEASABLE_BY_FACE[face]; } export const MODE_RELEASE_REASONS = Object.freeze(["target_unresolved"]); const MODE_RELEASE_REASON_SET = new Set(MODE_RELEASE_REASONS); export function isModeReleaseReason(v) { return MODE_RELEASE_REASON_SET.has(v); } export function askClassMembersOf(c) { return { askClass: c.askClass, classes: Object.freeze(ASK_CLASSES.filter((k) => c.classes.has(k))) }; } export function screenAskClassMembers(askClass, classes) { if (!isAskClass(askClass) || !Array.isArray(classes) || classes.length === 0 || !classes.every(isAskClass)) return undefined; const held = new Set(classes); const ordered = ASK_CLASSES.filter((k) => held.has(k)); if (ordered.length !== classes.length || ordered[0] !== askClass) return undefined; return { askClass, classes: Object.freeze(ordered) }; } export function decisionAskClassesOf(d) { const out = new Set(); for (const guard of d.guards ?? []) out.add(guard); if (d.ruleUnreadable !== undefined) out.add("rule_unreadable"); if (d.matchedAskRule !== undefined) out.add("ask_rule"); if (d.denialLimitFallback !== undefined) out.add("denial_limit_fallback"); if (d.classifierNoRuling !== undefined) out.add("classifier_no_ruling"); if (d.authoredBy === "deployment") out.add("operator_forced"); if (d.probeMandated === true) out.add("probe"); if (d.decisionReason === "hook") out.add("hook"); if (d.decisionReason === "mode") out.add("mode"); if (d.requiresRealApproval === true && ![...out].some((c) => RRA_EXPLAINERS.has(c))) out.add("policy_rra"); if (out.size === 0 && d.mandated === true) out.add("operator_forced"); for (const c of d.foldedAskClasses ?? []) out.add(c); return out; } export function foldArbitrationRow(classes, readFace, askFace) { if (classes.size === 0) return MODE_ARBITRATION.ordinary; const withheld = readFace === "roots" && [...classes].some((c) => CLASSIFIER_WITHHELD_ON_ROOTS.has(c)); let bypassImmune = false, classifierEligible = !withheld, sandboxAdmission = true, ruleMayClear = true, hookAskFloor = false; for (const c of classes) { const row = MODE_ARBITRATION[c]; bypassImmune ||= bypassImmuneOn(c, askFace); classifierEligible &&= row.classifierEligible; sandboxAdmission &&= row.sandboxAdmission; ruleMayClear &&= row.ruleMayClear; hookAskFloor ||= row.hookAskFloor; } return { bypassImmune, classifierEligible, sandboxAdmission, ruleMayClear, hookAskFloor }; } export function classifyAsk(d, facts) { const classes = decisionAskClassesOf({ ...d, foldedAskClasses: undefined }); classes.delete("policy_rra"); if (facts.contentQuestion) classes.add("content_question"); if (facts.markedUnresolvable) classes.add("ancestor"); if (facts.org === "rule") classes.add("org_rule"); if (facts.org === "unavailable" || facts.ruleStoreUnreadable === true || facts.ruleCallUnreadable === true) classes.add("rule_unreadable"); const marks = facts.mandateMarks; if (marks.requiresUserInteraction === true) classes.add("requires_user_interaction"); if (marks.shellGated === true && marks.irreversibility === "always") classes.add("shell_floor"); const toolMarked = marks.shellGated !== true && (marks.egress === true || marks.irreversibility === "always" || marks.irreversibility === "maybe"); if (toolMarked) { const sources = facts.toolMarkSources; if (sources === undefined || sources.size === 0 || sources.has("deployment")) classes.add("operator_forced"); if (sources?.has("mcp_annotation")) classes.add("tool_marks_mcp"); if (sources?.has("a2a_default")) classes.add(facts.a2aIsolatePeers === true ? "operator_forced" : "tool_marks_a2a"); } if (marks.probeUnanswered === true) classes.add("probe"); if (marks.targetUnresolved === true) classes.add("target_unresolved"); if (marks.writeProtected === true) classes.add("write_protection"); if (facts.tightened === "unstated") classes.add("unstated"); for (const c of facts.ancestorClasses ?? []) classes.add(c); if (d.requiresRealApproval === true && ![...classes].some((c) => RRA_EXPLAINERS.has(c))) classes.add("policy_rra"); for (const c of d.foldedAskClasses ?? []) classes.add(c); if (classes.size === 0 && d.mandated === true) classes.add("operator_forced"); if (classes.size === 0) classes.add("ordinary"); const askClass = ASK_CLASSES.find((c) => classes.has(c)); return { classes, askClass, row: foldArbitrationRow(classes, facts.readFace, facts.askFace) }; } export function headlessDenialMessage(toolName, askMessage) { return `Permission for this tool use was denied: it requires interactive approval, and permission prompts are not available in this session (nobody can be asked to approve "${toolName}" in this run — headless auto-deny). The action was NOT performed. Do not claim it succeeded, and do not retry it in this session — report the limitation to the user, or suggest an alternative. What was requested: ${askMessage}`; } export function headlessApprovalRemedy(input) { const approver = input.approverSeat === "unavailable" ? "make a person reachable through the approver that is wired (it answered `unavailable` for this ask)" : input.approverSeat === "declared_none" ? 'run it where a person can be asked — this run declares `approvalReach: "none"` (nobody on it can be asked) — or arm a durable approval park so the ask waits for a person' : "wire an approver (`onAsk`) to put it to a person"; const c = input.classification; if (input.mode === "acceptEdits" && input.unplacedOptions !== undefined && (c === undefined || (c.classes.size === 1 && c.classes.has("ordinary")))) return `${fileCommandOptionsSentence(input.unplacedOptions.program)} Spell the ${input.unplacedOptions.program} without option words for the mode to answer it, or ${approver}`; if (c?.row.bypassImmune === true) return `no permission mode releases this ask (${c.askClass}): ${approver}, or change the call`; if (input.mode === "plan" && c?.classes.has("mode") === true) return `the plan permission mode asks before every write and every call to a tool the engine does not define that is not read-only — leave plan mode (\`setPermissionMode\` to another mode, or approve the plan) to run it, or ${approver}`; if (c !== undefined && c.classes.size === 1 && c.classes.has("unstated")) return `no rule, policy or hook granted this tool, and it states no permission of its own — grant it with an allow rule (a permission rule \`allow\` for its name, or an allow-list entry), run with \`permissionMode: "bypassPermissions"\`, or ${approver}`; const onlyOrdinary = c === undefined || (c.classes.size === 1 && c.classes.has("ordinary")); if (input.coveredWrite && onlyOrdinary) { if (input.mode === "acceptEdits") return `under \`permissionMode: "acceptEdits"\` the mode asks only for a write outside the working directories — if this target is outside them, add its directory to \`additionalDirectories\`; otherwise (or besides) ${approver}`; if (input.mode === undefined || input.mode === "default" || input.mode === "auto") return `the mode's write gate asks every write under \`default\`: \`permissionMode: "acceptEdits"\` lets writes inside the working directories (the task root and \`additionalDirectories\`) through without asking (a question another policy raised still stands), or ${approver}`; } return `run with \`permissionMode: "bypassPermissions"\` to release this ask (the release is disclosed on the call), or ${approver}`; } export function fileCommandOptionsSentence(program) { return `${program} with flags requires manual approval to ensure path safety: the mode cannot automatically validate ${program} commands that use flags, as some flags like --target-directory=PATH can bypass path validation.`; } export function arbitrateByMode(mode, c, disclosure, call) { const has = (k) => c.classes.has(k); const classes = ASK_CLASSES.filter((k) => c.classes.has(k)); switch (mode) { case "dontAsk": return has("hook") && !c.row.hookAskFloor ? { kind: "ask" } : { kind: "denied_by_mode", mode, askClass: c.askClass, classes }; case "bypassPermissions": if (has("target_unresolved") && c.row.bypassImmune) return { kind: "denied_by_mode", mode, askClass: c.askClass, classes }; if (c.row.bypassImmune) return { kind: "ask" }; const reason = [...MODE_RELEASE_REASON_SET].find((r) => has(r)); return { kind: "released", mode, askClass: c.askClass, classes, ...(disclosure?.origin !== undefined ? { origin: disclosure.origin } : {}), ...(disclosure?.mandate !== undefined ? { mandate: disclosure.mandate } : {}), ...(reason !== undefined ? { reason } : {}), }; case "acceptEdits": return call?.fileCommand === true && c.classes.size === 1 && c.classes.has("ordinary") ? { kind: "accepted", mode } : { kind: "ask" }; case "plan": case "default": case "auto": return { kind: "ask" }; default: { const _exhaustive = mode; return _exhaustive; } } }