@sema-agent/core
Version:
Stateless, task-oriented AI agent core
179 lines (178 loc) • 14 kB
JavaScript
import { snapshotActorAssertion } from "../../internal/llm.js";
import { ASK_USER_QUESTION_TOOL_NAME } from "../ask-question.js";
import { CheckpointError, remainingBudgetMicroUsd } from "../checkpoint-store.js";
import { chainPolicyOnLeg, chainTopAnchors } from "./child-tree.js";
import { mintHookInvocationIdentity } from "../hooks.js";
import { admitLegMods, classicEventBase, failedHandlerClause, screenUserPrompt } from "../mods/stations.js";
import { checkToolPolicyProjection, policyCannotAnswer, refuseOutOfContractDecision } from "../tool-policy.js";
import { callFaceSeat, parkedCallFaceOf } from "../tool-roster.js";
import { readCallTarget } from "../effective-path-target.js";
import { inlineUntrusted } from "../untrusted-text.js";
import { deepJsonEqual, describeSuppliedValue, mintDecideRecord } from "./decide-continuation.js";
import { orgVerdictBeforeClaim } from "./permission-rule-lanes.js";
import { chainCannotJudge } from "./call-hook-stations.js";
import { thrownText } from "../thrown-value.js";
import { wireOf } from "../tool-name-readings.js";
import { parkedSkillApprovalRefusal } from "../skill-grant.js";
import { SKILL_CONTENT_MAX_CHARS, SKILL_TOOL_NAME, normalizeSkills } from "./synthetic-tools.js";
import { skillLoadReading } from "./skill-base-dir.js";
export async function resumeApply(input) {
const { cp, config, internals, outcomeGate, wakeMessage, resumeSignal, recheckGovernanceWindow, plainReviewOutcome, plainParkOutcome, runner, effectiveDelegationFacts } = input;
let plainPolicyOutcome = input.plainPolicyOutcome;
let wakeHookContext;
let legMods;
if (outcomeGate === "wake" && wakeMessage !== undefined) {
if (resumeSignal?.aborted !== true) {
legMods = await admitLegMods({ spec: config, deps: runner.deps, sessionId: cp.sessionId });
}
if (legMods !== undefined && legMods.bus.hasHandlers("classic.UserPromptSubmit")) {
const screenedMessage = wakeMessage;
const identity = mintHookInvocationIdentity({
sessionId: cp.sessionId,
taskId: config.taskId ?? cp.sessionId,
legKind: "resume",
isDelegatedChild: effectiveDelegationFacts(internals, cp.state.isDelegatedChild).isDelegatedChild,
...(internals?.insideFork === true ? { insideFork: true } : {}),
...(internals?.agentName !== undefined ? { agentName: internals.agentName } : {}),
...(internals?.explicitAgentName !== undefined ? { spawnName: internals.explicitAgentName } : {}),
...(internals?.spawnAgentType !== undefined ? { agentType: internals.spawnAgentType } : {}),
...(internals?.parentToolCallId !== undefined ? { parentToolCallId: internals.parentToolCallId } : {}),
});
const screen = await screenUserPrompt(legMods.bus, {
...classicEventBase(identity),
prompt: screenedMessage.text,
source: "resume_message",
...(screenedMessage.inputId !== undefined ? { inputId: screenedMessage.inputId } : {}),
...(screenedMessage.actor !== undefined ? { actor: snapshotActorAssertion(screenedMessage.actor) } : {}),
}, resumeSignal);
if (screen?.kind === "aborted") {
throw new CheckpointError("checkpoint.resume_aborted", "the resume was aborted while the deployment's userPromptSubmit hook was still screening this wake message — the checkpoint stays pending and is resumable with a live signal");
}
if (screen?.kind === "failed") {
throw new CheckpointError("steering.blocked_by_hook", `the deployment's userPromptSubmit hook ${failedHandlerClause(screen)} while screening this wake message; the resume was refused (fail-closed) and the checkpoint stays pending`);
}
if (screen?.block !== undefined && screen.block !== "") {
throw new CheckpointError("steering.blocked_by_hook", `the deployment's userPromptSubmit hook blocked this wake message: ${inlineUntrusted(screen.block)}`);
}
if (screen?.additionalContext !== undefined && screen.additionalContext !== "") {
wakeHookContext = screen.additionalContext;
}
if (recheckGovernanceWindow !== undefined)
await recheckGovernanceWindow();
}
}
const editSignal = config.signal;
const raceEditAbort = async (p) => {
if (editSignal === undefined)
return p;
if (editSignal.aborted) {
throw new CheckpointError("checkpoint.resume_aborted", "the resume was aborted while re-adjudicating the call before the claim — the checkpoint stays pending and is resumable with a live signal");
}
return new Promise((resolve, reject) => {
const onAbort = () => reject(new CheckpointError("checkpoint.resume_aborted", "the resume was aborted while re-adjudicating the call before the claim — the checkpoint stays pending and is resumable with a live signal"));
editSignal.addEventListener("abort", onAbort, { once: true });
p.then((v) => { editSignal.removeEventListener("abort", onAbort); resolve(v); }, (e) => { editSignal.removeEventListener("abort", onAbort); reject(e); });
});
};
if (plainPolicyOutcome?.decision === "allow" && cp.pendingAction.kind === "tool_approval") {
const servedSkills = normalizeSkills(config.skills).filter((s) => skillLoadReading(s, SKILL_CONTENT_MAX_CHARS).loads);
const skillRefusal = parkedSkillApprovalRefusal(cp.pendingAction, plainPolicyOutcome.updatedInput ?? cp.pendingAction.args, servedSkills, SKILL_TOOL_NAME);
if (skillRefusal !== undefined)
throw new CheckpointError("checkpoint.invalid_outcome", skillRefusal, { field: "boundGrantDigest" });
}
if (plainPolicyOutcome !== undefined &&
plainPolicyOutcome.decision === "allow" &&
plainPolicyOutcome.updatedInput !== undefined &&
cp.pendingAction.kind === "tool_approval" &&
wireOf("resume.editRecheckQuestionSkip", cp.pendingAction) !== ASK_USER_QUESTION_TOOL_NAME) {
const editedArgs = plainPolicyOutcome.updatedInput;
const editBudget = {
resourceRemainingMicroUsd: remainingBudgetMicroUsd(cp.resourceLedger),
resourceSpentMicroUsd: cp.resourceLedger?.spentMicroUsd ?? 0,
suspendCount: cp.suspendCount ?? 0,
};
const editedCall = {
toolName: cp.pendingAction.toolName,
args: editedArgs,
toolCallId: cp.pendingAction.toolCallId,
budget: editBudget,
...callFaceSeat(parkedCallFaceOf(cp.pendingAction)),
...(cp.state.handsCwd !== undefined ? { cwd: cp.state.handsCwd } : {}),
...(cp.state.handsRoot !== undefined ? { root: cp.state.handsRoot } : {}),
};
const editedReq = { ...editedCall, targetReading: await readCallTarget(undefined, editedCall) };
const ig = cp.state.inheritedGate;
if (ig?.requiresParentConstraint === true) {
const chain = ig.constraintChain;
const unprojectable = chainCannotJudge(ig);
if (unprojectable !== undefined) {
throw new CheckpointError("resume.constraint_unprojectable", `the approver EDITED this call's input, but the inherited ancestor chain cannot re-adjudicate the edit statically: ${unprojectable}. ` +
"Refusing the edited resume pre-CAS (the checkpoint stays pending and remains decidable) — approve the shown input as-is, deny it, " +
"or re-issue the edited action as a fresh call so the full live chain adjudicates it");
}
for (const entry of chain) {
const denied = checkToolPolicyProjection(entry, editedReq);
if (denied !== undefined) {
throw new CheckpointError("resume.constraint_rejected", `the approver EDITED this call's input and a FROZEN inherited ancestor constraint denies the edited args${denied.message ? `: ${denied.message}` : ""}. ` +
"Refused pre-CAS (fresh-redecision semantics: the checkpoint stays pending and the same token remains decidable — approve as shown, deny, or edit differently)");
}
}
}
const toolPolicyLocked = runner.deps.lockedConfig?.keys?.includes("toolPolicy") === true;
const editBasePolicy = toolPolicyLocked
? (runner.deps.basePolicyForResumeEdit ?? runner.deps.toolPolicy)
: (config.basePolicyForResumeEdit ?? runner.deps.basePolicyForResumeEdit ?? config.toolPolicy ?? runner.deps.toolPolicy);
const rowLeg = { ...(typeof cp.state.handsRoot === "string" ? { root: cp.state.handsRoot } : {}), ...(typeof cp.state.handsHome === "string" ? { home: cp.state.handsHome } : {}) };
const asRecorded = (p) => p;
const installedEditBase = chainPolicyOnLeg({
leg: rowLeg,
chainTop: chainTopAnchors({ leg: rowLeg, live: internals?.inheritedGate?.pathRuleAnchors, seed: cp.state.inheritedGate?.pathRuleAnchors, placement: undefined, rebase: asRecorded }),
placement: undefined,
chain: (internals?.inheritedGate?.parentConstraints ?? []).map((c) => c.policy),
installations: { live: internals?.inheritedGate?.pathRuleInstallations, seed: cp.state.inheritedGate?.pathRuleInstallations },
rebase: asRecorded,
}).own(editBasePolicy);
if (installedEditBase !== undefined) {
let rechecked;
try {
rechecked = refuseOutOfContractDecision(await raceEditAbort(Promise.resolve(installedEditBase.check(editedReq, editSignal))));
}
catch (err) {
if (err instanceof CheckpointError)
throw err;
rechecked = { action: "deny", message: `the resume-edit policy re-check errored: ${thrownText(err)}` };
}
if (rechecked.action === "deny") {
throw new CheckpointError("resume.constraint_rejected", `the approver EDITED this call's input and the deployment's resume-edit policy denies the edited args${rechecked.message ? `: ${rechecked.message}` : ""}. ` +
"Refused pre-CAS (the checkpoint stays pending and remains decidable)");
}
if (policyCannotAnswer(rechecked, plainPolicyOutcome.hostDecision.decidedBy))
throw new CheckpointError("checkpoint.invalid_outcome", `the approver EDITED this call's input, the edited args raise a question that requires real approval, and the decision is the deployment's policy — a policy's answer is not a person's (decide the edit as "person", or approve the shown input); refusing pre-CAS, the checkpoint stays pending`, { field: "hostDecision" });
if (rechecked.updatedInput !== undefined && !deepJsonEqual(rechecked.updatedInput, editedArgs)) {
throw new CheckpointError("resume.constraint_rejected", `the deployment's resume-edit policy REWROTE the approver's edited args to a different value than was approved — the durable resume cannot execute either the un-adjudicated edit or an unapproved rewrite; ` +
`refused pre-CAS (the checkpoint stays pending); re-submit the edited action so the rewrite is adjudicated and approved synchronously`);
}
}
}
const orgProvider = runner.deps.permissionRuleStore;
if (plainPolicyOutcome?.decision === "allow" && plainPolicyOutcome.hostDecision.decidedBy !== "person" && orgProvider?.partitions.org === true && cp.pendingAction.kind === "tool_approval") {
const call = { toolName: cp.pendingAction.toolName, args: plainPolicyOutcome.updatedInput !== undefined ? plainPolicyOutcome.updatedInput : cp.pendingAction.args, toolCallId: cp.pendingAction.toolCallId };
const orgNow = await raceEditAbort(orgVerdictBeforeClaim({ provider: orgProvider, principal: config.principal || cp.principal || undefined, localOwnerDeclared: runner.deps.localOwnerRules === true, sessionId: cp.sessionId, signal: editSignal }, call));
if (policyCannotAnswer(orgNow, plainPolicyOutcome.hostDecision.decidedBy)) {
const rule = orgNow.status === "available" ? orgNow.verdict?.rule : undefined;
throw new CheckpointError("checkpoint.invalid_outcome", `resume carries decision "allow" decided by "${plainPolicyOutcome.hostDecision.decidedBy}" on a call an organization policy rule (${rule}) requires a person's approval for — a policy's answer is not a person's (decide it as "person", or refuse it as "policy"); refusing pre-CAS, the checkpoint stays pending`, { field: "hostDecision" });
}
}
if (plainPolicyOutcome !== undefined) {
plainPolicyOutcome = { ...plainPolicyOutcome, gateOutcome: mintDecideRecord(cp, plainPolicyOutcome.hostDecision, { allowed: plainPolicyOutcome.decision === "allow", note: plainPolicyOutcome.reason }) };
}
let outcomeForReview = plainReviewOutcome;
if (outcomeForReview?.gate === "plan_review") {
outcomeForReview = { ...outcomeForReview, gateOutcome: mintDecideRecord(cp, outcomeForReview.hostDecision, { allowed: outcomeForReview.decision !== "reject", note: outcomeForReview.reason }) };
}
const outcomeForStore = plainPolicyOutcome ?? outcomeForReview ?? plainParkOutcome;
if (outcomeForStore === undefined) {
throw new CheckpointError("checkpoint.invalid_outcome", `resume outcome (gate "${describeSuppliedValue(outcomeGate)}") matched the checkpoint gate but no lane captured it — refusing pre-CAS rather than passing the caller's live object to the store and the resumed run`);
}
return await input.next({ wakeHookContext, ...(legMods !== undefined ? { legMods } : {}), plainPolicyOutcome, outcomeForStore });
}