@sema-agent/core
Version:
Stateless, task-oriented AI agent core
567 lines (566 loc) • 34.8 kB
JavaScript
import { deliverEngineNotice } from "../types.js";
import { withDeploymentAllowedTools } from "../allow-entry.js";
import { uuidv7 } from "../../internal/harness.js";
import { snapshotActorAssertion } from "../../internal/llm.js";
import { CheckpointError } from "../checkpoint-store.js";
import { createRapidRefillState } from "../auto-compaction.js";
import { boundInputHashOf } from "../canonical-json.js";
import { emitTrace } from "../trace.js";
import { emitTaskOutcome } from "../task-outcome.js";
import { runWithReasoningWireFacts } from "../../brain/status-sink.js";
import { expandTiers } from "../roles.js";
import { announceReadDenySwap, screenSwappableDeps } from "../swappable-deps.js";
import { runSideQuery } from "../side-query.js";
import { PushQueue } from "../push-queue.js";
import { TtlSessionStore } from "../session-store.js";
import { errorCodeOf } from "./terminal-account.js";
import { effectiveDelegationFacts, prepareTask } from "./prepare-task.js";
import { SessionReadFileStates } from "./session-read-file-states.js";
import { SessionModeSeats } from "./session-mode-seats.js";
import { RunnerSharedToolResultStore } from "../tool-result-store.js";
import { PAUSE_REGISTRY } from "../pause-registry.js";
import { wrapToolPolicy } from "../tool-policy.js";
import { PendingSessionNotifications } from "../task-notification.js";
import { ToolDetachHub } from "../tool-detach.js";
import { assertContextSettingsValue, assertContextSettingsWords, contextOverlayFor } from "../context-settings.js";
import { createRunState } from "./initial-run-state.js";
import { emitSettled, reconciledEnd } from "./tool-end-body.js";
import { answerFaceForRedeemedCall } from "./decide-continuation.js";
import { createTurnBoundary } from "./run-turn-boundary.js";
import { createHarnessHandlers } from "./run-harness-handlers.js";
import { resumeAdmission } from "./resume-admission.js";
import { resumeReviewOutcome } from "./resume-review-outcome.js";
import { resumePolicyOutcome } from "./resume-policy-outcome.js";
import { resumeCheckpointScreen } from "./resume-checkpoint-screen.js";
import { resumeInternalsAndConfig } from "./resume-internals-and-config.js";
import { resumeApply } from "./resume-apply.js";
import { resumePreflight } from "./resume-preflight.js";
import { resumeClaim } from "./resume-claim.js";
import { streamSettleBackstop } from "./stream-settle-backstop.js";
import { streamReap } from "./stream-reap.js";
import { streamSteerVerb } from "./stream-steer-verb.js";
import { steeringError } from "./steer-admission.js";
import { streamLifecycleVerbs } from "./stream-lifecycle-verbs.js";
import { streamHaltVerbs } from "./stream-halt-verbs.js";
import { streamModeVerb } from "./stream-mode-verb.js";
import { runNotificationLane } from "./run-notification-lane.js";
import { retainRunnerDeps } from "./construction-doors.js";
import { runLeg } from "./run-leg.js";
import { runTerminalAdoption } from "./run-terminal-adoption.js";
import { runSettleAndTeardown } from "./run-settle-and-teardown.js";
import { runIdentityWiring } from "./run-identity-wiring.js";
import { runTelemetryAndBudgetSeats } from "./run-telemetry-and-budget-seats.js";
import { runAttachmentSeats } from "./run-attachment-seats.js";
import { runToolMountFacts } from "./run-tool-mount-facts.js";
import { runReasoningSeat } from "./run-reasoning-seat.js";
import { runClockAndContent } from "./run-clock-and-content.js";
import { createToolProgressLane } from "./tool-progress-lane.js";
import { createForwardSinkScope } from "./forward-sink.js";
import { runBrainSinks } from "./run-brain-sinks.js";
import { runCompactionMachinery } from "./run-compaction-machinery.js";
import { runStopAndFinalVerify } from "./run-stop-and-final-verify.js";
import { runRecoveryLanes } from "./run-recovery-lanes.js";
import { runGitLane } from "./run-git-lane.js";
import { deploymentCanRerootChild } from "./child-tree.js";
import { thrownText } from "../thrown-value.js";
export class Runner {
deps;
sessions;
sessionLocks = new Map();
sessionReadStates = new SessionReadFileStates();
sessionModeSeats = new SessionModeSeats();
pendingSessionNotifications = new PendingSessionNotifications();
parentConstraintRegistry = new Map();
suspendedEnvReaps = new Map();
locallyClaimedTokens = new Set();
static PARENT_CONSTRAINT_REGISTRY_CAP = 1024;
depsSeat;
constructor(deps) {
const retained = retainRunnerDeps(deps);
const runner = this;
this.depsSeat = { get deps() { return runner.deps; } };
this.deps = withDeploymentAllowedTools(retained);
if (this.deps.tiers) {
this.deps = { ...this.deps, models: expandTiers(this.deps.models, this.deps.tiers) };
}
if (this.deps.contextSettings !== undefined) {
this.deps = { ...this.deps, contextSettings: assertContextSettingsWords(assertContextSettingsValue(this.deps.contextSettings, "RunnerDeps"), this.deps.models, "RunnerDeps") };
}
if (this.deps.onError) {
const rawOnError = this.deps.onError;
const reportSinkFailure = (sinkErr, context) => {
try {
emitTrace(this.deps.tracer, () => ({
kind: "task.onerror_sink_failed",
version: 1,
sessionId: context.sessionId,
phase: context.phase,
reason: (thrownText(sinkErr)).slice(0, 256),
ts: Date.now(),
}));
}
catch {
}
};
this.deps = {
...this.deps,
onError: (err, context) => {
try {
const ret = rawOnError(err, context);
if (ret !== null && ret !== undefined && typeof ret.then === "function") {
Promise.resolve(ret).then(undefined, (sinkErr) => reportSinkFailure(sinkErr, context));
}
}
catch (sinkErr) {
reportSinkFailure(sinkErr, context);
}
},
};
}
if (!this.deps.toolResultStore) {
this.deps = { ...this.deps, toolResultStore: new RunnerSharedToolResultStore({ maxTotalChars: 64_000_000 }) };
}
this.sessions = this.deps.sessionStore ?? new TtlSessionStore();
}
emitTaskOutcome(outcome) {
emitTaskOutcome(this.deps, outcome);
}
canRerootChild() { return deploymentCanRerootChild(this.deps); }
get agentCatalog() {
return {
...(this.deps.agents ? { agents: [...this.deps.agents] } : {}),
...(this.deps.builtinAgents !== undefined ? { builtinAgents: this.deps.builtinAgents } : {}),
...(this.deps.models ? { models: { ...this.deps.models } } : {}), ...(this.deps.roles ? { roles: { ...this.deps.roles } } : {}),
};
}
get contextSettings() {
return this.deps.contextSettings;
}
contextOverlayOf(ref) {
return contextOverlayFor(this.deps.contextSettings, ref, this.deps);
}
get gateBaseline() {
const deps = this.deps;
return {
...(deps.toolPolicy !== undefined
? {
toolPolicy: wrapToolPolicy(deps.toolPolicy, (req, signal) => deps.toolPolicy.check(req, signal)),
}
: {}),
};
}
async acquireSessionLock(sessionId) {
const prev = this.sessionLocks.get(sessionId) ?? Promise.resolve();
let release;
const mine = new Promise((r) => {
release = r;
});
const chain = prev.then(() => mine);
this.sessionLocks.set(sessionId, chain);
await prev.catch(() => { });
return () => {
release();
void chain.then(() => {
if (this.sessionLocks.get(sessionId) === chain) {
this.sessionLocks.delete(sessionId);
}
});
};
}
swapDeps(next) {
const screened = screenSwappableDeps(next);
const { seats, candidate } = screened;
const catalogSwap = seats.includes("models");
let expanded;
let tiers;
if (catalogSwap) {
const models = candidate.models;
tiers = Object.hasOwn(candidate, "tiers") ? candidate.tiers : this.deps.tiers;
expanded = tiers && Object.keys(tiers).length > 0 ? expandTiers({ ...models }, tiers) : { ...models };
}
const faceSeats = ["readFace", "writeFace"].filter((seat) => seats.includes(seat));
const priorFaces = {
readFace: this.deps.readFace,
writeFace: this.deps.writeFace,
};
const written = {
...this.deps,
...(expanded !== undefined ? { models: expanded } : {}),
...(catalogSwap && tiers !== undefined ? { tiers } : {}),
};
if (catalogSwap && tiers === undefined)
delete written.tiers;
const priorContextSettings = this.deps.contextSettings;
for (const seat of seats) {
if (seat === "models" || seat === "tiers")
continue;
if (candidate[seat] !== undefined)
Object.assign(written, { [seat]: candidate[seat] });
else
Reflect.deleteProperty(written, seat);
}
assertContextSettingsWords(written.contextSettings, written.models, "swapDeps");
this.deps = written;
if (catalogSwap) {
const modelCount = Object.keys(candidate.models).length;
deliverEngineNotice(this.deps.onNotice, {
code: "config.models_swapped",
message: `model catalog swapped: ${modelCount} model(s), ${tiers ? Object.keys(tiers).length : 0} tier binding(s); in-flight tasks finish on their resolved models, new tasks resolve against the new catalog`,
detail: { models: modelCount, tiers: tiers ? Object.keys(tiers).length : 0, occurrenceId: uuidv7() },
});
}
if (seats.includes("contextSettings")) {
const to = candidate.contextSettings;
const words = to !== undefined ? Object.keys(to) : [];
deliverEngineNotice(this.deps.onNotice, {
code: "config.context_settings_swapped",
message: `deployment context-window settings swapped: ${words.length} model word(s) declared` +
`${priorContextSettings !== undefined ? ` (was ${Object.keys(priorContextSettings).length})` : ""}` +
"; unlike every other swappable seat this one is read LIVE — running legs measure their next boundary against the new windows, while their routing stays on the generation they prepared under",
detail: { words: words.slice(0, 8), declared: words.length, previousDeclared: priorContextSettings !== undefined ? Object.keys(priorContextSettings).length : 0, occurrenceId: uuidv7() },
});
}
for (const seat of faceSeats) {
const to = candidate[seat] ?? null;
const from = priorFaces[seat] ?? null;
const message = `deployment ${seat === "readFace" ? "read" : "write"} face swapped: ${from ?? "(no declaration)"} → ${to ?? "(no declaration)"}; in-flight legs keep the face they prepared under, new legs resolve against the new declaration`;
const detail = { from, to, occurrenceId: uuidv7() };
if (seat === "readFace")
deliverEngineNotice(this.deps.onNotice, { code: "config.read_face_swapped", message, detail });
else
deliverEngineNotice(this.deps.onNotice, { code: "config.write_face_swapped", message, detail });
}
announceReadDenySwap(this.deps.onNotice, seats, this.deps);
}
sideQuery(spec) {
return runWithReasoningWireFacts(() => { }, () => runSideQuery(spec, { brain: this.deps.brain, models: this.deps.models, roles: this.deps.roles }));
}
runTaskStream(spec, resume, internals) {
if (resume !== undefined && (typeof resume !== "object" || resume.outcome === undefined)) {
throw new TypeError("runTaskStream: `resume` must be a ResumeRun carrying `outcome` — got a value without one. " +
"(Note the parameter order: runTaskStream(spec, resume?, internals?) — internals is the THIRD parameter.)");
}
const entryActor = spec.actor === undefined ? undefined : snapshotActorAssertion(spec.actor);
const entryTracer = { tracer: spec.tracer ?? this.deps.tracer };
const queue = new PushQueue();
const detachHub = new ToolDetachHub();
let resultValue;
let suggestionsDone = Promise.resolve([]);
let handle;
let publishReady;
const ready = new Promise((res) => {
publishReady = res;
});
const READY_TIMEOUT_MS = 30_000;
const orTimeout = (p) => {
let t;
const timeout = new Promise((res2) => {
t = setTimeout(() => res2(undefined), READY_TIMEOUT_MS);
});
return Promise.race([p, timeout]).finally(() => clearTimeout(t));
};
let reapHandle;
const acceptedSteerInputs = new Map();
const notifyRef = {};
const captureOptOutRef = {};
const mcpRedialRef = {};
const manualCompactRef = { requested: false, waiters: [] };
const drainManualCompactWaiters = (outcome) => {
manualCompactRef.requested = false;
for (const w of manualCompactRef.waiters.splice(0))
w.resolve(outcome);
};
const runStartedAt = Date.now();
const taskIdRef = { current: spec.taskId, sessionId: spec.sessionId };
let held;
let releaseLock;
const holdSessionLock = (sessionId) => {
if (held === undefined)
held = { sessionId, taken: this.acquireSessionLock(sessionId).then((release) => void (releaseLock = release)) };
else if (held.sessionId !== sessionId)
return Promise.reject(Object.assign(new Error(`internal invariant: a leg holds one session lock — it holds "${held.sessionId}" and was asked for "${sessionId}"`), { code: "internal.session_lock_second_id" }));
return held.taken;
};
const namedSessionId = spec.sessionId;
const run = (async () => {
if (namedSessionId)
await holdSessionLock(namedSessionId);
try {
await this.runLocked({
spec,
queue,
holdSessionLock: namedSessionId ? undefined : holdSessionLock,
setResult: (r) => {
resultValue = r;
const pcs = internals?.inheritedGate?.parentConstraints;
if (resume !== undefined)
this.locallyClaimedTokens.delete(resume.cp.token);
const rToken = r.terminal.kind === "paused" ? r.terminal.token : undefined;
if (resume !== undefined && rToken !== resume.cp.token) {
this.parentConstraintRegistry.delete(resume.cp.token);
this.suspendedEnvReaps.delete(resume.cp.token);
}
if (r.terminal.kind === "paused" && PAUSE_REGISTRY[r.terminal.gate.kind].taskStatus === "suspended" && pcs !== undefined && pcs.length > 0) {
if (this.parentConstraintRegistry.size >= Runner.PARENT_CONSTRAINT_REGISTRY_CAP) {
const oldest = this.parentConstraintRegistry.keys().next().value;
if (oldest !== undefined)
this.parentConstraintRegistry.delete(oldest);
}
this.parentConstraintRegistry.set(r.terminal.token, pcs);
}
},
onSuggestions: (p) => {
suggestionsDone = p;
},
onReady: (h) => {
handle = h;
publishReady(h);
},
onSuspend: (s) => {
reapHandle = s;
this.suspendedEnvReaps.set(s.token, s);
},
manualCompactRef,
taskIdRef,
resume,
internals: { ...(internals ?? {}), detachHub, forwardScope: createForwardSinkScope(this.deps.onNotice) },
notifyRef,
captureOptOutRef,
mcpRedialRef,
entryActor,
entryTracer,
});
}
finally {
releaseLock?.();
publishReady(handle);
manualCompactRef.closed = true;
if (manualCompactRef.waiters.length > 0)
manualCompactRef.emitMooted?.("task_ending");
manualCompactRef.emitMooted = undefined;
drainManualCompactWaiters("mooted");
}
})();
const live = {
get resultValue() { return resultValue; },
set resultValue(v) { resultValue = v; },
get handle() { return handle; },
get reapHandle() { return reapHandle; },
};
const settled = run.catch(streamSettleBackstop({
spec, resume, queue, taskIdRef, entryTracer, runStartedAt, live, runner: this.depsSeat, sessions: this.sessions,
locallyClaimedTokens: this.locallyClaimedTokens, parentConstraintRegistry: this.parentConstraintRegistry, suspendedEnvReaps: this.suspendedEnvReaps,
}).onRunRejected);
void settled.then(() => acceptedSteerInputs.clear(), () => acceptedSteerInputs.clear());
const { reapSuspended } = streamReap({
live, runner: this.depsSeat,
locallyClaimedTokens: this.locallyClaimedTokens, parentConstraintRegistry: this.parentConstraintRegistry, suspendedEnvReaps: this.suspendedEnvReaps,
});
const { steer } = streamSteerVerb({ spec, internals, queue, acceptedSteerInputs, live, ready, orTimeout, readyTimeoutMs: READY_TIMEOUT_MS, steeringError, runner: this.depsSeat });
const { notify, optOutMemoryCapture, reconnectMcpServer, compact, detach, interrupt } = streamLifecycleVerbs({ live, ready, orTimeout, steeringError, notifyRef, captureOptOutRef, mcpRedialRef, manualCompactRef, detachHub });
const { halt, destroy } = streamHaltVerbs({ spec, live, ready, orTimeout, readyTimeoutMs: READY_TIMEOUT_MS, run, reapSuspended, runner: this.depsSeat });
const { setPermissionMode } = streamModeVerb({ live, ready, orTimeout, steeringError });
return {
[Symbol.asyncIterator]: () => queue[Symbol.asyncIterator](),
result: async () => {
await settled;
return resultValue;
},
suggestions: async () => {
await run.catch(() => { });
return suggestionsDone.catch(() => []);
},
steer,
notify,
optOutMemoryCapture,
setPermissionMode,
reconnectMcpServer,
compact,
detach,
interrupt,
halt,
destroy,
};
}
runLocked(input) {
const { spec, queue, setResult, onSuggestions, onReady, onSuspend, manualCompactRef, taskIdRef, holdSessionLock, resume, internals, notifyRef, captureOptOutRef, mcpRedialRef, entryActor, entryTracer } = input;
return runNotificationLane({
spec, queue, resume, internals, taskIdRef, holdSessionLock,
runner: this.depsSeat, self: this, sessions: this.sessions, sessionReadStates: this.sessionReadStates, sessionModeSeats: this.sessionModeSeats, pendingSessionNotifications: this.pendingSessionNotifications,
prepareTask,
next: ({ prepared, injectTaskNotification, deliveredAtTurnOpen, peerInboundChainRef, modelCatalog, notificationLane, unsubscribeTaskNotifications }) => {
const { runSourceTaskId, parentToolCallId, ident, emitDelegationLifecycle, loopLatch, stats, internalsNotifier, undrainedUserAtEnd, userInputLostAtEnd } = runIdentityWiring({
spec, queue, prepared, internals, taskIdRef, onReady, manualCompactRef, notifyRef, captureOptOutRef, mcpRedialRef, injectTaskNotification, deliveredAtTurnOpen, peerInboundChainRef,
notificationLane, pendingSessionNotifications: this.pendingSessionNotifications, runner: this.depsSeat,
});
const rs = createRunState();
const { noteUnevaluablePriceTable } = runTelemetryAndBudgetSeats({ spec, prepared, resume, stats, taskIdRef, entryTracer, modelCatalog, runSourceTaskId, rs, runner: this.depsSeat, sessions: this.sessions });
return runAttachmentSeats({
spec, prepared, resume, rs, runner: this.depsSeat,
next: () => {
const { todoToolMounted, taskToolsMounted, writeFamilyOf, toolStartAt, startedToolCallIds } = runToolMountFacts({ spec, prepared, rs });
const { observeReasoningWireFacts, reasoningResolution } = runReasoningSeat({ prepared, rs, taskIdRef });
const { effectiveTimeoutMs, walltimeMonotonicDeadline, timeout, pushContent, ownCommittedTailRef, emitCommitted } = runClockAndContent({ spec, prepared, queue, internals, rs, ident, parentToolCallId, startedToolCallIds });
const toolProgress = createToolProgressLane(pushContent, ident, prepared.toolOccurrences);
prepared.toolProgressRef.current = toolProgress;
const { withBrainSinks, withLegRequestSinks } = runBrainSinks({ queue, internals, rs, ident, observeReasoningWireFacts, notice: { onNotice: this.deps.onNotice, sessionId: prepared.sessionId, runId: prepared.runId } });
const toolLabels = new Map(prepared.tools.flatMap((t) => (t.label !== undefined && t.label !== t.name ? [[t.name, t.label]] : [])));
for (const orphan of prepared.wakeRecovered) {
emitSettled(prepared.toolOccurrences, { toolCallId: orphan.toolCallId, spelledName: orphan.toolName, ...(toolLabels.has(orphan.toolName) ? { label: toolLabels.get(orphan.toolName) } : {}), args: orphan.args }, reconciledEnd(orphan), { push: (f) => queue.push({ ...f, ...ident() }), structured: prepared.structuredProjector, mediaByteCapBytes: this.deps.mediaByteCapBytes });
emitCommitted(orphan.entryId, "toolResult", orphan.toolCallId);
}
const postToolBatchArmed = prepared.modBus.hasHandlers("classic.PostToolBatch");
const batchArgs = postToolBatchArmed ? new Map() : undefined;
rs.turn.toolBatch = [];
const { onMessageUpdate, onMessageEnd, onToolStart, onToolEnd, onTurnEnd, announceWorkspaceMove } = createHarnessHandlers({
prepared, stats, rs,
deps: {
spec, queue, internals, ident, mediaByteCapBytes: this.deps.mediaByteCapBytes,
pushContent, emitCommitted, startedToolCallIds, toolStartAt, toolProgress, writeFamilyOf,
toolLabels, postToolBatchArmed, batchArgs, internalsNotifier,
runnerHooks: { onError: (e, info) => this.deps.onError?.(e, info), onNotice: this.deps.onNotice },
},
});
const { compactionBrain, withinTaskCompaction, compactionBreaker, windowSafetyOptions } = runCompactionMachinery({ spec, prepared, rs, stats, noteUnevaluablePriceTable, modelCatalog, runner: this.depsSeat });
runStopAndFinalVerify({ spec, prepared, queue, rs, stats, ident, walltimeMonotonicDeadline, runner: this.depsSeat });
runRecoveryLanes({ spec, prepared, queue, rs, ident, compactionBrain, compactionBreaker, windowSafetyOptions, callerStop: loopLatch.callerStop, runner: this.depsSeat });
const rapidRefill = createRapidRefillState();
const drainManualCompact = (outcome) => {
manualCompactRef.requested = false;
for (const w of manualCompactRef.waiters.splice(0))
w.resolve(outcome);
};
const onTurnBoundary = createTurnBoundary({
prepared, stats, rs,
deps: {
spec, queue, loopLatch, manualCompactRef, todoToolMounted, taskToolsMounted,
walltimeMonotonicDeadline,
timeout, ident, postToolBatchArmed, batchArgs, compactionBrain, withinTaskCompaction,
compactionBreaker, windowSafetyOptions, rapidRefill, drainManualCompact,
runnerHooks: { onError: this.deps.onError },
runner: this.depsSeat,
},
});
const { flushGitMirror, unsubGitRetry, unsubBoundary, unsub } = runGitLane({ prepared, queue, rs, ident, onMessageUpdate, onMessageEnd, onToolStart, onToolEnd, onTurnEnd, onTurnBoundary, runner: this.depsSeat });
return withLegRequestSinks(() => runLeg({
spec, queue, prepared, resume, rs, entryActor, effectiveTimeoutMs, timeout, pushContent, ident, emitCommitted, writeFamilyOf, startedToolCallIds,
announceWorkspaceMove, withBrainSinks, flushGitMirror, loopLatch, notificationLane, unsubscribeTaskNotifications, unsubGitRetry, unsubBoundary, unsub,
runner: this.depsSeat,
next: ({ final, threw, abortedLive, userInterruptedLive, strandedHumanAnswers }) => runTerminalAdoption({
spec, queue, prepared, rs, stats, ident, emitCommitted, startedToolCallIds, toolLabels, timeout, compactionBrain, compactionBreaker, windowSafetyOptions,
undrainedUserAtEnd, userInputLostAtEnd, final, threw, abortedLive, userInterruptedLive, callerStop: loopLatch.callerStop, runner: this.depsSeat, sessions: this.sessions,
next: ({ threw, comp, answerSettled, owedUserInput }) => runSettleAndTeardown({
spec, queue, prepared, resume, internals, rs, stats, ident, loopLatch, timeout, ownCommittedTailRef,
emitDelegationLifecycle, taskIdRef, manualCompactRef, drainManualCompact, final, threw, abortedLive, userInterruptedLive, answerSettled, strandedHumanAnswers, reasoningResolution, comp, compactionBreaker, owedUserInput,
setResult, onSuggestions, onSuspend, runner: this.depsSeat, sessions: this.sessions,
}),
}),
}));
},
});
},
});
}
async runTask(spec, internals) {
const stream = this.runTaskStream(spec, undefined, internals);
for await (const _ of stream) {
void _;
}
return stream.result();
}
async resume(token, outcome, taskConfig, internals) {
const stream = await this.resumeStream(token, outcome, taskConfig, internals);
for await (const _ of stream) {
void _;
}
return stream.result();
}
async resumeStream(token, outcome, taskConfig, internals) {
return await resumeAdmission({
token,
outcome,
taskConfig,
internals,
runner: this.depsSeat, sessions: this.sessions,
parentConstraintRegistry: this.parentConstraintRegistry,
next: (admitted) => this.resumeLadder(token, outcome, admitted),
});
}
async resumeLadder(token, outcome, admitted) {
const { config, internals, store, cp, outcomeGate, resumeSignal, wakeMessage } = admitted;
const { plainReviewOutcome, plainParkOutcome } = resumeReviewOutcome({ cp, outcome, outcomeGate, wakeMessage });
const { plainPolicyOutcome: capturedPolicyOutcome, redeemedAnswer } = resumePolicyOutcome({ cp, outcome, config, runner: this.depsSeat });
const { resumeInternals } = resumeCheckpointScreen({ cp, resumeSignal, internals, tools: config.tools, runner: this.depsSeat });
const { recheckGovernanceWindow, windowEntrance } = resumeInternalsAndConfig({ cp, config, internals, outcomeGate, runner: this.depsSeat });
if (windowEntrance !== undefined)
await windowEntrance;
return await resumeApply({
cp, config, internals, outcomeGate, wakeMessage, resumeSignal, recheckGovernanceWindow,
plainPolicyOutcome: capturedPolicyOutcome, plainReviewOutcome, plainParkOutcome,
runner: this.depsSeat, effectiveDelegationFacts,
next: ({ wakeHookContext, legMods, plainPolicyOutcome, outcomeForStore }) => resumePreflight({
token, cp, resumeSignal, recheckGovernanceWindow, store, runner: this.depsSeat,
locallyClaimedTokens: this.locallyClaimedTokens, parentConstraintRegistry: this.parentConstraintRegistry, suspendedEnvReaps: this.suspendedEnvReaps,
next: () => resumeClaim({
token, cp, store, outcomeForStore,
locallyClaimedTokens: this.locallyClaimedTokens, parentConstraintRegistry: this.parentConstraintRegistry,
next: () => this.resumeHandoff({ token, cp, store, config, internals, resumeInternals, plainPolicyOutcome, redeemedAnswer, outcomeForStore, wakeMessage, wakeHookContext, legMods, resumeSignal }),
}),
}),
});
}
async resumeHandoff(claimed) {
const { token, cp, store, config, internals, resumeInternals, plainPolicyOutcome, redeemedAnswer, outcomeForStore, wakeMessage, wakeHookContext, legMods, resumeSignal } = claimed;
let consumeFlipDone = false;
if (internals?.afterCheckpointResolve !== undefined) {
await internals.afterCheckpointResolve();
consumeFlipDone = true;
}
const answerFace = plainPolicyOutcome !== undefined && redeemedAnswer !== undefined
? answerFaceForRedeemedCall(redeemedAnswer, plainPolicyOutcome.boundCallId, config.onQuestion ?? this.deps.onQuestion, boundInputHashOf(cp.pendingAction.args?.questions))
: undefined;
const spec = {
...config,
...((config.principal || undefined) === undefined && (cp.principal || undefined) !== undefined ? { principal: cp.principal } : {}),
objective: "",
sessionId: cp.sessionId,
requireExistingSession: true,
preemptSignal: config.preemptSignal?.aborted ? undefined : config.preemptSignal,
...(answerFace !== undefined ? { onQuestion: answerFace } : {}),
};
const reopenFn = store.reopen?.bind(store);
const recordExecutionOutcome = (gate) => store.recordExecutionOutcome(token, cp.scope, gate);
const onEnvRestoreFailed = reopenFn
? async (reason) => {
const reopened = await reopenFn(token, cp.scope, reason);
if (!reopened) {
throw new CheckpointError("checkpoint.reopen_failed", "checkpoint could not be reopened for a retry (missing, expired, or changed concurrently) — the resume failure is terminal");
}
}
: undefined;
if (resumeSignal?.aborted === true && consumeFlipDone) {
throw new CheckpointError("checkpoint.resume_aborted", "the resume was aborted after its row was already claimed — the checkpoint stays consumed and the caller's rollback settles the outcome-unknown terminal (reopening here would strand a pending approval no redemption path can reach)");
}
if (resumeSignal?.aborted === true) {
let reopenOutcome = "refused";
if (onEnvRestoreFailed !== undefined) {
try {
await onEnvRestoreFailed("env_failed");
reopenOutcome = "reopened";
}
catch (err) {
reopenOutcome = errorCodeOf(err) === "checkpoint.reopen_failed" ? "refused" : "unknown";
}
}
throw reopenOutcome === "reopened"
? new CheckpointError("checkpoint.resume_aborted", "the resume was aborted before the approved action could run — the checkpoint was reopened, so the same approval can be resumed with a live signal")
: reopenOutcome === "refused"
? new CheckpointError("checkpoint.reopen_failed", "the resume was aborted before the approved action could run AND the store refused to reopen the checkpoint — the approval is terminally consumed and the suspended work was not executed; a retry needs a fresh approval")
: new CheckpointError("checkpoint.reopen_failed", "the resume was aborted before the approved action could run and the reopen attempt FAILED IN FLIGHT — the checkpoint's state is unprovable from here: it may already be pending again. Re-read it before deciding; do NOT issue a fresh approval on the assumption the old one is dead (the approved action did NOT run either way)");
}
return this.runTaskStream(spec, { cp, outcome: outcomeForStore, onEnvRestoreFailed, recordExecutionOutcome, ...(wakeMessage !== undefined ? { wakeMessage } : {}), ...(wakeHookContext !== undefined ? { wakeMessageHookContext: wakeHookContext } : {}), ...(legMods !== undefined ? { legMods } : {}) }, resumeInternals);
}
}
export function runTask(spec, deps) {
return new Runner(deps).runTask(spec);
}