UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

89 lines (88 loc) • 5.26 kB
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 }); }