@sema-agent/core
Version:
Stateless, task-oriented AI agent core
89 lines (88 loc) • 5.26 kB
JavaScript
import { CheckpointError, readPendingSteerQueue, refuseRetiredModeSeed, resolveCheckpointStore, validatePendingSteer } from "../checkpoint-store.js";
import { refuseResumeDoors } from "../mode-policy.js";
import { describeSuppliedValue } from "./decide-continuation.js";
import { detachSkillLists } from "../skill-grant.js";
export async function resumeAdmission(input) {
const { token, outcome, taskConfig, runner, parentConstraintRegistry } = input;
let internals = input.internals;
if (outcome === null || typeof outcome !== "object") {
throw new CheckpointError("checkpoint.invalid_outcome", `resume outcome must be an object naming its gate (got ${outcome === null ? "null" : typeof outcome})`);
}
const config = typeof taskConfig === "object" && taskConfig !== null ? { ...taskConfig, ...(Array.isArray(taskConfig.skills) ? { skills: detachSkillLists(taskConfig.skills) } : {}) } : taskConfig;
internals = internals !== undefined ? { ...internals } : undefined;
const store = resolveCheckpointStore(config, runner.deps);
if (!store) {
throw new CheckpointError("checkpoint.not_found", "no CheckpointStore wired — cannot resume (set RunnerDeps.checkpointStore or taskConfig.checkpointStore)");
}
let cp = await store.get(token);
if (!cp) {
throw new CheckpointError("checkpoint.not_found", "no checkpoint found for the supplied token");
}
if (cp.status !== "pending") {
const live = await store.get(token);
if (live?.status === "pending") {
throw new CheckpointError("checkpoint.reopened_concurrently", "checkpoint changed concurrently (a resolve/reopen cycle landed between this resume's read of the row and its state check) — nothing was validated or executed; re-resume against the current state");
}
throw new CheckpointError("checkpoint.already_resolved", `checkpoint is ${describeSuppliedValue(live?.status ?? cp.status)} — the token is consumed, so this resume was neither validated nor executed (idempotent); a different outcome cannot redeem it`);
}
if ((internals?.inheritedGate?.parentConstraints?.length ?? 0) === 0) {
const parked = parentConstraintRegistry.get(token);
if (parked !== undefined) {
internals = {
...(internals ?? {}),
inheritedGate: { ...(internals?.inheritedGate ?? {}), parentConstraints: parked },
};
}
}
let wakeMessage;
const outcomeGate = outcome.gate;
const resumeSignal = config.signal;
let suppliedMessage;
refuseRetiredModeSeed(cp.state);
refuseResumeDoors(config, cp.state.inheritedGate);
const parkedSteers = readPendingSteerQueue(cp.state);
if (outcomeGate === "wake") {
suppliedMessage = outcome.message;
const gk = cp.gate.kind;
const decideEntry = gk === "human" || gk === "irreversible_ask"
? 'a `policy_ask` outcome (decision allow/deny bound to the pending tool call)'
: gk === "needs_review"
? 'a `dry_run_review` outcome (decision approve/reject)'
: gk === "plan_review"
? 'a `plan_review` outcome (decision approve/edit/reject)'
: gk === "resource_limit"
? 'a `resource_limit` outcome (decision "continue")'
: undefined;
if (gk !== "task_done") {
throw new CheckpointError("wake.gate_pending", `cannot wake: this checkpoint awaits a pending gate decision (gate "${gk}") — wake is not a decision ` +
`channel; resume it with ${decideEntry ?? "its own gate outcome"} instead`);
}
const pa = cp.pendingAction;
switch (pa.kind) {
case "task_done":
break;
case "tool_approval":
case "resource_limit":
case "review":
case "plan_review": {
throw new CheckpointError("wake.gate_pending", `cannot wake: this checkpoint still carries a pending gate-typed action (pendingAction "${pa.kind}" ` +
`on a "${gk}" gate — inconsistent row) — wake is not a decision channel; resolve it through its ` +
`own decide entry instead`);
}
default: {
const _exhaustive = pa;
void _exhaustive;
throw new CheckpointError("wake.gate_pending", `cannot wake: this checkpoint carries an unrecognized pendingAction kind "${pa.kind}" ` +
`(newer-version row?) — fail-closed: wake only serves a pure park (pendingAction "task_done")`);
}
}
if (suppliedMessage !== undefined) {
wakeMessage = validatePendingSteer(suppliedMessage);
}
else if (parkedSteers.length === 0) {
throw new CheckpointError("wake.nothing_to_deliver", "cannot wake: no message was supplied and the checkpoint holds no parked pendingSteer — an empty " +
"wake would burn the checkpoint on a blank continuation; supply `message` or park a steer first");
}
}
return await input.next({ config, internals, store, cp, outcomeGate, resumeSignal, wakeMessage });
}