UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

567 lines (566 loc) • 34.8 kB
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); }