@sema-agent/server 7.79.0 → 7.79.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1230 @@
1
+ import { redactErrorMessage, redactHead } from "../observability/run-terminal-log.js";
2
+ import { createHash } from "node:crypto";
3
+ import { uuidv7, resumeWithVerification, CheckpointError, defaultTaskRegistry, validatePendingSteer, GLOBAL_USAGE_KEY, terminalProjection, PAUSE_REGISTRY } from "@sema-agent/core";
4
+ import { decideParkedAgent, denyWorkflowParksUnreadable, findParkOrigin, planWorkflowParkedDecide } from "../parked-decide.js";
5
+ import { HttpError, decodeCheckpointScope, CHECKPOINT_PUBLIC_SCOPE } from "../security.js";
6
+ import { evictIfConflict, parkToolCallId, TurnAnchorCapture, HEARTBEAT_MS, markChildrenStoppedByUserOnAbort } from "../runs.js";
7
+ import { failureCodeOf, nestedPauseOf, stripResumeToken } from "../terminal.js";
8
+ import { mintCancelledResult } from "../run-cancel-context.js";
9
+ import {} from "../host-decision.js";
10
+ import { readTurnActivityMs, recordTurnActivity } from "../turn-activity.js";
11
+ import { recordResumeResourceWindow } from "../resource-window.js";
12
+ import { emitPendingWorkflowCompletions, taskNotificationInboxEntry, taskNotificationStreamKey, NotifiedKeys } from "../orchestration/workflow-completion-inbox.js";
13
+ import { fleetRunPublisher, fleetRunLabels, fleetRunResiduals, isFleetAgentTerminalNotification } from "../fleet/fleet-bus.js";
14
+ import { defaultSubagentTailBus } from "../fleet/subagent-tail-bus.js";
15
+ import { APPROVAL_GATE_KINDS, createApprovalCardEmitter, isApprovalGateKind, resolveApprovalLeg, resolveStreamApprovalGate } from "../tool-approval.js";
16
+ import { buildCheckpointAnchorHash } from "../approval-ask-audit-store.js";
17
+ import { TERMINAL_PARK_GATE_KINDS } from "../plugins/checkpoint-store-sql.js";
18
+ import { withPrincipal } from "../observability/principal-context.js";
19
+ import { modelTextEventData, turnEndEventData, contextUsageEventData, toolStartEventData, toolEndEventData, taskProgressEventData, taskNotificationEventData, compactedEventData, diagnosticsEventData, brainStatusEventData, steeringInjectedEventData, compactionOutcomeEventData, workspaceChangedEventData, wiringManifestEventData, toolRosterDeltaEventData, toolDisclosureEventData, humanInputEventData, appendModelUsageDelta, appendPromptManifest, attachModelUsage } from "../trace/project.js";
20
+ import { redactSecrets } from "../trace/redact.js";
21
+ import { createLedgerSink, createSerialLedgerAppend } from "../trace/ledger-sink.js";
22
+ import { registerEngineNoticeLeg } from "../trace/engine-notice-wire.js";
23
+ import { reasoningDeltaBytes, reasoningSegmentEventId } from "../trace/model-segment.js";
24
+ import { errorText } from "../observability/err-text.js";
25
+ import { recordFailOpen } from "../observability/fail-open.js";
26
+ import { verifyRoundsFromBody } from "./verify-rounds.js";
27
+ import { acceptedResumingReceipt } from "./route-ctx.js";
28
+ import { mintAndDriveDurableRun } from "./durable-run-leg.js";
29
+ import { createSessionsLocal } from "./routes/sessions.js";
30
+ import { createSessionSyncLocal } from "./routes/session-sync.js";
31
+ import { createImagesLocal } from "./routes/images.js";
32
+ import { gatedPrincipal, explicitOperatorOk } from "./principal-gate.js";
33
+ import { buildActiveRunConflict, resumeEntryForGate } from "./active-run-conflict.js";
34
+ import { ASK_USER_QUESTION_TOOL_NAME } from "../approval-content-kind.js";
35
+ function isCheckpointReopenedFailure(r) {
36
+ const code = failureCodeOf(r.terminal);
37
+ return code === "resume.env_failed" || code === "resume.tool_unavailable" || code === "resume.tool_contract_mismatch";
38
+ }
39
+ function nestedParkStatusOf(r) {
40
+ const paused = nestedPauseOf(r.terminal);
41
+ if (paused === undefined)
42
+ return undefined;
43
+ return PAUSE_REGISTRY[paused.gate.kind].taskStatus;
44
+ }
45
+ const WORKER_SWAP_REDEEMABLE = {
46
+ version_newer: true,
47
+ env_factory_missing: true,
48
+ governed_unwired: true,
49
+ origin_missing: true,
50
+ seat_retired: true,
51
+ ledger_event_unknown: true,
52
+ real_approval_damaged: false,
53
+ real_approval_forged: false,
54
+ read_face_damaged: false,
55
+ read_face_forged: false,
56
+ write_face_damaged: false,
57
+ constraint_chain_missing: false,
58
+ };
59
+ function checkpointRowRedeemableElsewhere(reason) {
60
+ return reason !== undefined && WORKER_SWAP_REDEEMABLE[reason] === true;
61
+ }
62
+ export function createResumeLegs(ctx) {
63
+ const { deps, runnerFor, idemCache, inflightRuns, preemptableRuns, cancelledViaVerb, steerableRuns, wakeParkMints, counters, readJson, readRawBody, rateLimited, quotaExceeded, ownerQuotaDenied, leaseDenied, ownerLeaseDenied, usageWindowDenied, safeDecode, isFleetWide, runOwnerOk, sessionOwnerScope, sessionOwnerScopeForWrite, runSessionAcceptOk, prepareSpec, finalizeTaskResult, ownerCostQuotaDenied, ownerFleetLeaseDenied, usageWindowDenialFor } = ctx;
64
+ const resumeFleetScope = (req, auth) => req ? (gatedPrincipal(req, deps.config) ?? "default") : (auth.principal ?? "default");
65
+ async function resumeCheckpoint(sessionId, decision, reason, hostDecision, req, answer, binding, onResumeCommitted, acceptEarly, decider) {
66
+ const cs = deps.checkpointStore;
67
+ const token = (await cs.findPendingTokenBySession(sessionId, undefined, { gateKinds: APPROVAL_GATE_KINDS })) ?? (await cs.findPendingTokenBySession(sessionId));
68
+ if (binding?.checkpointToken && binding.checkpointToken !== token) {
69
+ let seenUnreadable = false;
70
+ const seen = await cs.get(binding.checkpointToken).catch((e) => {
71
+ seenUnreadable = true;
72
+ recordFailOpen("server.approvals.stale-echoed-token-unreadable", `session=${sessionId} err=${e instanceof Error ? e.message : String(e)}`);
73
+ return null;
74
+ });
75
+ const attributable = seen !== null && seen.sessionId === sessionId;
76
+ if (!attributable && seen !== null) {
77
+ deps.logger?.warn?.("approval_stale_token_foreign", { sessionId, tokenSession: seen.sessionId ?? null });
78
+ }
79
+ if (!token && !attributable && !seenUnreadable) {
80
+ deps.logger?.info?.("approval_decide_no_pending", { sessionId, staleTokenEchoed: true });
81
+ return { status: 404, body: { error: "approval not found", errorCode: "not_found.approval" } };
82
+ }
83
+ let currentPending;
84
+ if (token && decider !== undefined) {
85
+ const cur = await cs.get(token).catch((e) => {
86
+ recordFailOpen("server.approvals.stale-current-pending-unreadable", `session=${sessionId} err=${e instanceof Error ? e.message : String(e)}`);
87
+ return null;
88
+ });
89
+ const curPa = cur?.pendingAction;
90
+ const curOwner = cur ? decodeCheckpointScope(cur.scope) : undefined;
91
+ const scopeOk = cur !== null && (decider.explicitOperator || curOwner === undefined || curOwner === decider.principal);
92
+ if (cur && cur.sessionId === sessionId && !scopeOk) {
93
+ deps.logger?.info?.("approval_decide_no_pending", { sessionId, staleTokenEchoed: true, foreignCurrentSuppressed: true });
94
+ return { status: 404, body: { error: "approval not found", errorCode: "not_found.approval" } };
95
+ }
96
+ if (cur && cur.status === "pending" && cur.sessionId === sessionId && scopeOk && curPa?.kind === "tool_approval" && curPa.toolCallId) {
97
+ currentPending = {
98
+ toolName: curPa.toolName,
99
+ boundCallId: curPa.toolCallId,
100
+ ...(curPa.boundInputHash ? { boundInputHash: curPa.boundInputHash } : {}),
101
+ };
102
+ }
103
+ }
104
+ return {
105
+ status: 409,
106
+ body: {
107
+ error: "the approval you are deciding is no longer current (already resolved or superseded since you viewed it)",
108
+ errorCode: "approval_stale",
109
+ ...(attributable ? { terminal: "resolved" } : {}),
110
+ ...(currentPending ? { currentPending } : {}),
111
+ },
112
+ };
113
+ }
114
+ if (!token) {
115
+ deps.logger?.info?.("approval_decide_no_pending", { sessionId });
116
+ return { status: 404, body: { error: "approval not found", errorCode: "not_found.approval" } };
117
+ }
118
+ const cpPromise = cs.get(token);
119
+ const ctxPromise = cs.getCtx(sessionId);
120
+ void ctxPromise.catch(() => { });
121
+ const noApproval = { status: 404, body: { error: "approval not found", errorCode: "not_found.approval" } };
122
+ let cp;
123
+ try {
124
+ cp = await cpPromise;
125
+ }
126
+ catch (e) {
127
+ void ctxPromise.catch(() => { });
128
+ if (!(await mayDiscloseRowError(cs, token, decider))) {
129
+ deps.logger?.warn?.("approval_decide_unreadable_row_withheld", { sessionId, note: "row unreadable and the caller's ownership of THAT token is unproven — folded into the uniform 404" });
130
+ return noApproval;
131
+ }
132
+ return { status: 409, body: { error: e instanceof Error ? e.message : String(e), errorCode: "conflict.checkpoint_unreadable" } };
133
+ }
134
+ if (!cp) {
135
+ if (!(await mayDiscloseRowError(cs, token, decider)))
136
+ return noApproval;
137
+ return { status: 404, body: { error: "checkpoint not found", errorCode: "not_found.checkpoint" } };
138
+ }
139
+ if (decider !== undefined && !decider.explicitOperator) {
140
+ const cpOwner = decodeCheckpointScope(cp.scope);
141
+ if (cpOwner !== undefined && cpOwner !== decider.principal) {
142
+ deps.logger?.warn?.("approval_decide_row_scope_mismatch", { sessionId, note: "loaded pending row is foreign to the verified caller — TOCTOU fold" });
143
+ return { status: 404, body: { error: "approval not found", errorCode: "not_found.approval" } };
144
+ }
145
+ }
146
+ const decideGateKind = cp.gate?.kind;
147
+ if (!isApprovalGateKind(decideGateKind)) {
148
+ const activeForPath = decideGateKind
149
+ ? await deps.runStore?.getActiveTaskId(sessionId).catch(() => undefined)
150
+ : undefined;
151
+ const gnPath = decideGateKind && activeForPath
152
+ ? resumeEntryForGate(decideGateKind, { sessionId, taskId: activeForPath })
153
+ : null;
154
+ return {
155
+ status: 409,
156
+ body: {
157
+ error: `task is suspended on a '${decideGateKind ?? "unknown"}' gate, not a tool approval — a plan_review gate is resolved via POST /v1/assistant/tasks/:id/plan_review, a resource_limit suspension via POST /v1/assistant/tasks/:id/resume`,
158
+ errorCode: "gate_not_tool_approval",
159
+ ...(decideGateKind ? { pendingGate: { kind: decideGateKind, ...(gnPath ? { decidePath: gnPath } : {}) } } : {}),
160
+ },
161
+ };
162
+ }
163
+ if (req !== undefined && !binding?.checkpointToken && !binding?.boundCallId) {
164
+ return {
165
+ status: 400,
166
+ body: {
167
+ error: "decide requires the decision-action binding — echo checkpointToken and/or boundCallId from the pending approval (GET /v1/approvals)",
168
+ errorCode: "decide_requires_binding",
169
+ bindingEnforced: true,
170
+ },
171
+ };
172
+ }
173
+ const s57RowCallId = cp.pendingAction.kind === "tool_approval" ? cp.pendingAction.toolCallId : undefined;
174
+ if (binding?.boundCallId !== undefined && s57RowCallId && binding.boundCallId !== s57RowCallId) {
175
+ let s57CurrentPending;
176
+ if (decider !== undefined && cp.status === "pending" && cp.sessionId === sessionId && cp.pendingAction.kind === "tool_approval") {
177
+ s57CurrentPending = {
178
+ toolName: cp.pendingAction.toolName,
179
+ boundCallId: s57RowCallId,
180
+ ...(cp.pendingAction.boundInputHash ? { boundInputHash: cp.pendingAction.boundInputHash } : {}),
181
+ };
182
+ }
183
+ return {
184
+ status: 409,
185
+ body: {
186
+ error: "the approval you are deciding is no longer current (already resolved or superseded since you viewed it)",
187
+ errorCode: "approval_stale",
188
+ ...(s57CurrentPending ? { currentPending: s57CurrentPending } : {}),
189
+ },
190
+ };
191
+ }
192
+ const paForGrant = cp.pendingAction.kind === "tool_approval" ? cp.pendingAction : undefined;
193
+ const loadedGrantTool = paForGrant !== undefined && paForGrant.toolName && paForGrant.toolName !== ASK_USER_QUESTION_TOOL_NAME
194
+ ? paForGrant.toolName
195
+ : null;
196
+ const boundGrant = onResumeCommitted
197
+ ? (overrideSessionId) => onResumeCommitted({ ...(overrideSessionId !== undefined ? { overrideSessionId } : {}), validatedToolName: loadedGrantTool })
198
+ : undefined;
199
+ const lateSettleSink = deps.approvalLateSettle;
200
+ const lateSettleAnchor = lateSettleSink !== undefined && req !== undefined && token
201
+ ? {
202
+ sessionId: cp.sessionId,
203
+ ...(paForGrant?.toolCallId !== undefined ? { toolCallId: paForGrant.toolCallId } : {}),
204
+ checkpointTokenHash: buildCheckpointAnchorHash(token),
205
+ }
206
+ : undefined;
207
+ const noteLateSettleIfAccepted = (out) => {
208
+ if (lateSettleSink === undefined || lateSettleAnchor === undefined || out.status !== 200)
209
+ return;
210
+ if (out.body?.errorCode === "cancelled")
211
+ return;
212
+ try {
213
+ lateSettleSink.recordLateSettle({ ...lateSettleAnchor, outcome: decision === "approve" ? "allowed" : "denied", decidedBy: hostDecision.decidedBy, ...(hostDecision.approver !== undefined ? { approver: hostDecision.approver } : {}), settledAtMs: Date.now() });
214
+ }
215
+ catch (e) {
216
+ recordFailOpen("server.approval-ask-audit.append-failed", `kind=late_settle err=${e instanceof Error ? e.message : String(e)}`);
217
+ }
218
+ };
219
+ const parkedForkQuotaDenial = req !== undefined ? ownerCostQuotaDenied(decodeCheckpointScope(cp.scope)) : undefined;
220
+ if (parkedForkQuotaDenial)
221
+ return parkedForkQuotaDenial;
222
+ const parkedForkLeaseDenial = req !== undefined ? await ownerFleetLeaseDenied(decodeCheckpointScope(cp.scope)) : undefined;
223
+ if (parkedForkLeaseDenial)
224
+ return parkedForkLeaseDenial;
225
+ const ownerLeaseAdmittedHere = req !== undefined;
226
+ if (req !== undefined) {
227
+ const parkedAgentStore = deps.backgroundAgentStore;
228
+ const parkedTool = deps.parkedReviveTool;
229
+ const parkWarn = (event, fields) => deps.logger?.warn?.(event, fields);
230
+ const origin = await findParkOrigin({
231
+ ...(parkedAgentStore !== undefined && parkedTool !== undefined ? { agentStore: parkedAgentStore } : {}),
232
+ ...(deps.workflowAgentSessionIndex !== undefined && deps.workflowRunStore !== undefined
233
+ ? { workflowIndex: deps.workflowAgentSessionIndex, workflowRunStore: deps.workflowRunStore }
234
+ : {}),
235
+ ...(deps.logger ? { warn: parkWarn } : {}),
236
+ }, { token, scope: cp.scope, sessionId: cp.sessionId });
237
+ const parkedReq = {
238
+ token, scope: cp.scope, sessionId: cp.sessionId, pendingAction: cp.pendingAction,
239
+ decision, hostDecision, ...(reason !== undefined ? { reason } : {}),
240
+ ...(binding !== undefined ? { binding: { ...(binding.boundCallId !== undefined ? { boundCallId: binding.boundCallId } : {}), ...(binding.boundInputHash !== undefined ? { boundInputHash: binding.boundInputHash } : {}), ...(binding.updatedInput !== undefined ? { updatedInput: binding.updatedInput } : {}) } } : {}),
241
+ ...(answer !== undefined ? { answer } : {}),
242
+ ...(boundGrant ? { grantRemember: (rootSessionId) => boundGrant(rootSessionId) } : {}),
243
+ };
244
+ if (origin?.kind === "background" && parkedAgentStore !== undefined && parkedTool !== undefined) {
245
+ const parked = await withPrincipal(decodeCheckpointScope(cp.scope), () => decideParkedAgent({
246
+ agentStore: parkedAgentStore,
247
+ checkpointStore: cs,
248
+ registry: defaultTaskRegistry,
249
+ reviveTool: parkedTool,
250
+ ...(deps.parkedKnownAgentTypes ? { knownAgentTypes: deps.parkedKnownAgentTypes } : {}),
251
+ ...(deps.parkedReviveInheritedGate ? { rebuildInheritedGate: deps.parkedReviveInheritedGate } : {}),
252
+ ...(deps.logger ? { warn: parkWarn } : {}),
253
+ }, parkedReq, { handle: origin.handle, row: origin.row }));
254
+ void ctxPromise.catch(() => { });
255
+ noteLateSettleIfAccepted(parked);
256
+ return parked;
257
+ }
258
+ if (origin?.kind === "workflow_parks_unreadable") {
259
+ void ctxPromise.catch(() => { });
260
+ return denyWorkflowParksUnreadable(origin);
261
+ }
262
+ if (origin?.kind === "workflow") {
263
+ void ctxPromise.catch(() => { });
264
+ const plan = planWorkflowParkedDecide(origin, parkedReq);
265
+ if (!plan.ok)
266
+ return plan.deny;
267
+ const parkedResume = { workflowParkedResume: [{ runId: plan.runId, token, outcome: plan.outcome }] };
268
+ const hostToken = await cs
269
+ .findPendingTokenBySession(plan.hostSessionId, undefined, { gateKinds: TERMINAL_PARK_GATE_KINDS, pick: "oldest" })
270
+ .catch(() => undefined);
271
+ const admittedPayer = decodeCheckpointScope(cp.scope);
272
+ return hostToken
273
+ ? await resumeWake(plan.hostSessionId, plan.message, decider?.principal, req, acceptEarly, parkedResume.workflowParkedResume, admittedPayer)
274
+ : await startFreshHostTurn(plan.hostSessionId, plan.message, admittedPayer, req, parkedResume, plan.runId);
275
+ }
276
+ }
277
+ let ctx;
278
+ try {
279
+ ctx = await ctxPromise;
280
+ }
281
+ catch (e) {
282
+ return { status: 409, body: { error: `resume context load failed: ${e instanceof Error ? e.message : String(e)}`, errorCode: "conflict.resume_context_unavailable" } };
283
+ }
284
+ if (!ctx)
285
+ return { status: 409, body: { error: "resume context missing — cannot rebuild task config", errorCode: "conflict.resume_context_unavailable" } };
286
+ const auth = { sessionId: cp.sessionId, principal: decodeCheckpointScope(cp.scope), memoryScope: ctx.memoryScope };
287
+ let spec;
288
+ let engineNotices = [];
289
+ try {
290
+ ({ spec, engineNotices } = await resolveResumeSpec(ctx.body, req, auth));
291
+ }
292
+ catch (e) {
293
+ if (e instanceof HttpError) {
294
+ return {
295
+ status: 409,
296
+ body: { error: `resume blocked by an admission policy change: ${e.message}`, errorCode: "resume_blocked_by_policy", ...(e.code ? { blockedBy: e.code } : {}), ...(e.extra ?? {}) },
297
+ };
298
+ }
299
+ throw e;
300
+ }
301
+ const pendingTool = cp.pendingAction.toolName;
302
+ if (decision === "approve" && pendingTool === ASK_USER_QUESTION_TOOL_NAME && !answer) {
303
+ return { status: 400, body: { error: "pending action is AskUserQuestion — approve must carry body.answer ({ answers: [{ header, selected: string[], note? }] })", errorCode: "request.field_conflict" } };
304
+ }
305
+ if (answer && (decision !== "approve" || pendingTool !== ASK_USER_QUESTION_TOOL_NAME)) {
306
+ return { status: 400, body: { error: `body.answer is only valid when approving a pending AskUserQuestion (decision: "${decision}", pending: "${pendingTool ?? "unknown"}")`, errorCode: "request.field_conflict" } };
307
+ }
308
+ const { objective: resumeObjective, sessionId: _sessionId, ...taskConfig } = spec;
309
+ const pa = cp.pendingAction;
310
+ const persistedCallId = pa.kind === "tool_approval" ? pa.toolCallId : "";
311
+ const persistedHash = pa.kind === "tool_approval" ? (pa.boundInputHash ?? "") : "";
312
+ const outcome = {
313
+ gate: "policy_ask",
314
+ boundCallId: binding?.boundCallId ?? persistedCallId,
315
+ boundInputHash: binding?.boundInputHash ?? persistedHash,
316
+ decision: decision === "approve" ? "allow" : "deny",
317
+ ...(decision === "approve" && binding?.updatedInput !== undefined ? { updatedInput: binding.updatedInput } : {}),
318
+ ...(reason ? { reason } : {}),
319
+ ...(answer !== undefined ? { answer } : {}),
320
+ hostDecision,
321
+ };
322
+ const verifyRounds = verifyRoundsFromBody(ctx.body);
323
+ const driven = await driveResumeIntoRunLog({ runner: runnerFor(spec), token, sessionId: cp.sessionId, principal: auth.principal, fleetScope: resumeFleetScope(req, auth), taskConfig, resumeObjective, outcome, verifyRounds, ...(engineNotices.length > 0 ? { engineNotices } : {}), ...(boundGrant ? { onResumeCommitted: boundGrant } : {}), ...(acceptEarly === true ? { acceptEarly: true } : {}), ...(req !== undefined ? { callerInitiated: true } : {}), ...(ownerLeaseAdmittedHere ? { ownerLeaseAdmitted: true } : {}) });
324
+ noteLateSettleIfAccepted(driven);
325
+ return driven;
326
+ }
327
+ const ONE_SHOT_HISTORY_KEYS = ["resumeAt", "restoreFiles", "acceptPartialRestore", "rewindFilesTo", "rewindFiles"];
328
+ const ONE_SHOT_HISTORY_STRIP = Object.fromEntries(ONE_SHOT_HISTORY_KEYS.map((k) => [k, undefined]));
329
+ const resolveResumeSpec = async (body, req, auth) => {
330
+ const engineNotices = [];
331
+ const spec = await deps.resolveSpec({ ...body, ...ONE_SHOT_HISTORY_STRIP }, req, auth, { leg: "resume", onNotice: (n) => engineNotices.push(n) });
332
+ return { spec, engineNotices };
333
+ };
334
+ async function driveResumeIntoRunLog(args) {
335
+ if (args.acceptEarly !== true)
336
+ return await driveResumeLeg(args);
337
+ let settleAccepted;
338
+ let wasAccepted = false;
339
+ const accepted = new Promise((resolve) => { settleAccepted = resolve; });
340
+ const leg = driveResumeLeg(args, (receipt) => { wasAccepted = true; settleAccepted?.(receipt); });
341
+ void leg.then((out) => {
342
+ if (wasAccepted && out.status >= 400) {
343
+ deps.logger?.warn?.("resume_leg_failed_after_accept", { sessionId: args.sessionId, status: out.status, body: out.body });
344
+ deps.metrics?.inc("resume_post_accept_failed_total");
345
+ }
346
+ }, (err) => {
347
+ if (!wasAccepted)
348
+ return;
349
+ deps.logger?.error?.("resume_leg_threw_after_accept", { sessionId: args.sessionId, err: redactHead(err instanceof Error ? err.message : String(err), 2000) });
350
+ deps.metrics?.inc("resume_post_accept_failed_total");
351
+ });
352
+ return await Promise.race([accepted, leg]);
353
+ }
354
+ async function driveResumeLeg(args, onAccepted) {
355
+ const resumeLegStartedAt = Date.now();
356
+ const { runner: legRunner, token, sessionId, principal, fleetScope, taskConfig, resumeObjective, outcome, verifyRounds } = args;
357
+ const legKey = createHash("sha256").update(String(token)).digest("hex");
358
+ const streamApprovalOn = resolveStreamApprovalGate({
359
+ toolApprovalEnabled: Boolean(deps.toolApproval),
360
+ streamApprovalEnabled: deps.config.streamApproval?.enabled === true,
361
+ backend: deps.backend,
362
+ parkFacility: deps.checkpointStore !== undefined,
363
+ }).active;
364
+ const approvalLeg = resolveApprovalLeg({
365
+ streamApprovalOn,
366
+ windowMs: deps.config.streamApproval?.windowMs,
367
+ windowMarginMs: deps.config.streamAskWindowMarginMs,
368
+ ...(typeof taskConfig.limits?.maxWalltimeMs === "number" ? { legWalltimeMs: taskConfig.limits.maxWalltimeMs } : {}),
369
+ nowMonotonicMs: performance.now(),
370
+ });
371
+ const legDeadlineMonotonic = approvalLeg.legDeadlineMonotonic;
372
+ const ownerQuotaDenial = args.callerInitiated === true ? ownerCostQuotaDenied(principal) : undefined;
373
+ if (ownerQuotaDenial)
374
+ return ownerQuotaDenial;
375
+ const ownerLeaseDenial = args.callerInitiated === true && args.ownerLeaseAdmitted !== true ? await ownerFleetLeaseDenied(principal) : undefined;
376
+ if (ownerLeaseDenial)
377
+ return ownerLeaseDenial;
378
+ let taskId = await deps.runStore?.getActiveTaskId(sessionId);
379
+ let freshRunClaimed = false;
380
+ if (args.mintFreshRun && taskId && deps.runStore) {
381
+ return { status: 409, body: await buildActiveRunConflict({ runStore: deps.runStore, checkpointStore: deps.checkpointStore, governance: deps.config, runStaleSec: deps.config.runStaleSec, turnActivity: readTurnActivityMs }, sessionId, taskId) };
382
+ }
383
+ if (!taskId && args.mintFreshRun && deps.runStore) {
384
+ const freshId = uuidv7();
385
+ const created = await deps.runStore.createRun(freshId, sessionId, principal ?? null, deps.instanceId ?? "default", {
386
+ source: args.mintFreshRun.source,
387
+ objectivePreview: resumeObjective ? redactSecrets(resumeObjective).slice(0, 120) : null,
388
+ });
389
+ if (!created.ok) {
390
+ return { status: 409, body: await buildActiveRunConflict({ runStore: deps.runStore, checkpointStore: deps.checkpointStore, governance: deps.config, runStaleSec: deps.config.runStaleSec, turnActivity: readTurnActivityMs }, sessionId, created.activeTaskId) };
391
+ }
392
+ taskId = freshId;
393
+ freshRunClaimed = true;
394
+ }
395
+ const fleetPub = taskId && deps.fleetBus
396
+ ? fleetRunPublisher(deps.fleetBus, { runId: taskId, scope: fleetScope, rootTaskId: taskId, ...fleetRunLabels(resumeObjective) })
397
+ : undefined;
398
+ const resumeTaskConfig = taskId ? { ...taskConfig, taskId } : taskConfig;
399
+ if (deps.toolApproval && taskId) {
400
+ const toolApproval = deps.toolApproval;
401
+ resumeTaskConfig.onAsk = approvalLeg.windowZero
402
+ ? (askReq) => Promise.resolve(toolApproval.unattendedAskOutcome({ taskId, owner: principal ?? null, toolName: askReq.toolName }))
403
+ : toolApproval.boundAsk({
404
+ owner: principal ?? null,
405
+ taskId,
406
+ sessionId,
407
+ ...(approvalLeg.active ? { legKey, ...(legDeadlineMonotonic !== undefined ? { legDeadlineMonotonic } : {}) } : {}),
408
+ });
409
+ }
410
+ if (taskId && resumeTaskConfig.resourceSuspend)
411
+ recordResumeResourceWindow(taskId, resumeTaskConfig.resourceSuspend);
412
+ let fleetSettled = false;
413
+ const settleFleet = (status, residuals) => {
414
+ if (fleetSettled)
415
+ return;
416
+ fleetSettled = true;
417
+ fleetPub?.onTerminal(status, residuals);
418
+ };
419
+ let result;
420
+ let reopenConfirmedPark = false;
421
+ const confirmCheckpointReopened = async () => {
422
+ const store = deps.checkpointStore;
423
+ if (!store)
424
+ return true;
425
+ try {
426
+ return (await store.get(token))?.status === "pending";
427
+ }
428
+ catch {
429
+ return true;
430
+ }
431
+ };
432
+ let resumeHeartbeat;
433
+ let resumeManifestLease = 0;
434
+ let resumeStreamRef;
435
+ let resumeLegLive = false;
436
+ const subagentHandleEvictions = [];
437
+ let claimedRow = false;
438
+ const cancelCtrl = new AbortController();
439
+ const preemptCtrl = new AbortController();
440
+ if (taskId && deps.runStore) {
441
+ claimedRow = freshRunClaimed ? true : await deps.runStore.markResuming(taskId, deps.instanceId ?? "default");
442
+ if (!claimedRow)
443
+ return { status: 409, body: { error: "run is not in a resumable (suspended) state (already resumed, decided, or expired)", errorCode: "conflict.not_resumable" } };
444
+ markChildrenStoppedByUserOnAbort(cancelCtrl.signal, taskId, principal);
445
+ fleetPub?.onStart();
446
+ const rs = deps.runStore;
447
+ const owner = principal ?? null;
448
+ inflightRuns.set(taskId, cancelCtrl);
449
+ preemptableRuns.set(taskId, preemptCtrl);
450
+ resumeHeartbeat = setInterval(() => {
451
+ void rs.heartbeat(taskId, owner).catch(() => undefined);
452
+ if (!cancelCtrl.signal.aborted) {
453
+ void rs.isCancelRequested(taskId, owner).then((c) => { if (c)
454
+ cancelCtrl.abort(); }).catch(() => undefined);
455
+ }
456
+ if (!preemptCtrl.signal.aborted) {
457
+ void rs.isPreemptRequested(taskId, owner).then((p) => { if (p)
458
+ preemptCtrl.abort(); }).catch(() => undefined);
459
+ }
460
+ }, HEARTBEAT_MS);
461
+ resumeHeartbeat.unref?.();
462
+ deps.modelUsage?.register(taskId);
463
+ resumeManifestLease = deps.promptManifests?.register(taskId) ?? 0;
464
+ }
465
+ if (args.onResumeCommitted)
466
+ await args.onResumeCommitted();
467
+ let unregisterEngineNotice;
468
+ try {
469
+ result = await withPrincipal(principal, async () => {
470
+ const resumeNotifiedKeys = new NotifiedKeys();
471
+ if (deps.runStore && taskId) {
472
+ const rs0 = deps.runStore;
473
+ let drainSeq = (await rs0.maxSeq(taskId)) + 1;
474
+ await emitPendingWorkflowCompletions(deps.workflowCompletionInbox, sessionId, principal ?? null, (frame) => {
475
+ const { type, ...rest } = frame;
476
+ const f = frame;
477
+ const write = rs0.appendEvent(taskId, drainSeq++, type, rest);
478
+ if (f.type === "task_notification" && f.task_id)
479
+ resumeNotifiedKeys.set(taskNotificationStreamKey({ task_type: f.task_type, task_id: f.task_id, status: String(f.status), seq: f.seq }), write.then(() => true, () => false));
480
+ return write;
481
+ }, { route: "resume-open", connection: "durable-append", log: (m, x) => deps.logger?.info?.(m, x) });
482
+ }
483
+ if (verifyRounds !== undefined) {
484
+ const rsV = deps.runStore;
485
+ const vSeqBase = rsV && taskId ? await rsV.maxSeq(taskId) : 0;
486
+ const verifySink = createLedgerSink({
487
+ appendEvent: (seq, type, data) => (rsV && taskId ? rsV.appendEvent(taskId, vSeqBase + seq, type, data) : Promise.resolve()),
488
+ persistThinking: deps.config.traceThinking,
489
+ notifiedKeys: resumeNotifiedKeys,
490
+ ...(taskId ? { onActivity: () => recordTurnActivity(taskId) } : {}),
491
+ });
492
+ const vAppend = (type, data) => verifySink.append(type, data);
493
+ const verifyTailContentMode = resumeTaskConfig.forwardSubagentEvents === true ? "on" : "progress_only";
494
+ const verifyInternals = {
495
+ ...(taskId ? { onActivity: () => recordTurnActivity(taskId) } : {}),
496
+ onForwardEvent: (e) => {
497
+ fleetPub?.onForwardEvent(e);
498
+ {
499
+ const bg = e.bgAgentId;
500
+ if (bg !== undefined)
501
+ defaultSubagentTailBus.onForwardFrame(bg, verifyTailContentMode, e);
502
+ }
503
+ if (rsV && taskId)
504
+ verifySink.onForwardEvent(e);
505
+ },
506
+ onStatusEvent: (e) => {
507
+ deps.metrics?.inc("brain_retry_total", { phase: String(e.phase) });
508
+ if (rsV && taskId) {
509
+ void verifySink.appendStatus(e).catch((err) => deps.logger?.warn?.("status_event_append_failed", { route: "resume-verify", taskId, err: err instanceof Error ? err.message : String(err) }));
510
+ }
511
+ },
512
+ onTaskNotification: (n, opts) => {
513
+ if (n.task_type === "workflow")
514
+ return;
515
+ defaultSubagentTailBus.forgetHandle(n.task_id);
516
+ if (defaultSubagentTailBus.hasSubscribers(n.task_id)) {
517
+ defaultSubagentTailBus.publish(n.task_id, { type: "task_settled", taskId: n.task_id, status: n.status, ...(typeof n.seq === "number" ? { seq: n.seq } : {}), ...(n.summary ? { summary: redactSecrets(n.summary) } : {}) });
518
+ }
519
+ const hadRow = isFleetAgentTerminalNotification(n) ? (fleetPub?.onChildTerminal(n.sessionId ?? n.task_id, n.status, n.task_id, n.toolUseId) ?? false) : false;
520
+ const parked = !resumeLegLive && Boolean(deps.workflowCompletionInbox && sessionId);
521
+ deps.logger?.info?.("task_notification_observed", { route: "resume-verify", taskId: n.task_id, taskType: n.task_type, status: n.status, hadFleetRow: hadRow, legLive: resumeLegLive, parkedDurable: parked });
522
+ if (parked) {
523
+ void deps.workflowCompletionInbox.enqueue(taskNotificationInboxEntry(sessionId, principal ?? null, n, Date.now(), taskId, opts?.priority)).catch((err) => deps.logger?.warn?.("park_enqueue_failed", { route: "resume-verify", taskId: n.task_id, err: err instanceof Error ? err.message : String(err) }));
524
+ }
525
+ else if (resumeLegLive) {
526
+ const key = taskNotificationStreamKey(n);
527
+ if (resumeNotifiedKeys.get(key) === undefined) {
528
+ resumeNotifiedKeys.set(key, vAppend("task_notification", taskNotificationEventData({ notification: n, ...(opts?.priority !== undefined ? { priority: opts.priority } : {}) })).then(() => true, () => false));
529
+ }
530
+ }
531
+ },
532
+ ...(deps.subagentSteerRegistry && taskId
533
+ ? {
534
+ onSubagentSpawn: (handle) => {
535
+ subagentHandleEvictions.push(deps.subagentSteerRegistry.register(taskId, handle));
536
+ },
537
+ }
538
+ : {}),
539
+ };
540
+ resumeLegLive = true;
541
+ const vr = await resumeWithVerification(legRunner, token, outcome, { ...resumeTaskConfig, signal: cancelCtrl.signal, preemptSignal: preemptCtrl.signal }, resumeObjective, verifyRounds, verifyInternals);
542
+ let safe = stripResumeToken(vr);
543
+ const vrNested = nestedPauseOf(vr.terminal);
544
+ const reParkStatus = nestedParkStatusOf(vr);
545
+ const reSuspended = vrNested !== undefined;
546
+ const vrReopened = isCheckpointReopenedFailure(vr) && (await confirmCheckpointReopened());
547
+ if (vrReopened)
548
+ reopenConfirmedPark = true;
549
+ if (taskId && deps.runStore) {
550
+ const rs2 = deps.runStore;
551
+ try {
552
+ const ap = vAppend;
553
+ await appendPromptManifest(ap, deps.promptManifests, taskId);
554
+ if (reSuspended || vrReopened) {
555
+ await appendModelUsageDelta(ap, deps.modelUsage, taskId);
556
+ const vrParkCallId = vrNested !== undefined ? await parkToolCallId({ terminal: vrNested }, deps.checkpointStore) : undefined;
557
+ await ap(reParkStatus ?? "suspended", { gate: vrNested?.gate ?? null, ...(vrParkCallId !== undefined ? { toolCallId: vrParkCallId } : {}), ...(vrReopened ? { reopened: failureCodeOf(vr.terminal) ?? null } : {}) });
558
+ }
559
+ else {
560
+ safe = await attachModelUsage(safe, { append: ap, getEvents: (id, a) => rs2.getEvents(id, a), modelUsage: deps.modelUsage, taskId });
561
+ await ap("done", { result: safe });
562
+ }
563
+ }
564
+ catch (e) {
565
+ if (!vrReopened)
566
+ throw e;
567
+ }
568
+ }
569
+ if (vrNested !== undefined)
570
+ return { ...safe, terminal: vrNested };
571
+ if (vr.verification?.verdict)
572
+ deps.metrics?.inc("verifications_total", { verdict: vr.verification.verdict });
573
+ return safe;
574
+ }
575
+ const rs = deps.runStore;
576
+ const startSeq = rs && taskId ? await rs.maxSeq(taskId) : 0;
577
+ let text = "";
578
+ let reasoning = "";
579
+ let reasoningEventId;
580
+ const persistThinking = deps.config.traceThinking;
581
+ const serialAppend = createSerialLedgerAppend((s, type, data) => rs.appendEvent(taskId, s, type, data), startSeq);
582
+ const append = (type, data) => (rs && taskId ? serialAppend(type, data) : Promise.resolve());
583
+ unregisterEngineNotice = registerEngineNoticeLeg({
584
+ sessionId,
585
+ ...(args.engineNotices !== undefined && args.engineNotices.length > 0 ? { pending: [...args.engineNotices] } : {}),
586
+ durable: (row) => void append("engine_notice", row).catch((err) => {
587
+ recordFailOpen("server.engine-notice.durable-append-failed", `leg=resume code=${row.code} task=${taskId ?? ""}`);
588
+ deps.logger?.warn?.("engine_notice_append_failed", { taskId, noticeCode: row.code, sessionId: row.sessionId, err: err instanceof Error ? err.message : String(err) });
589
+ }),
590
+ });
591
+ const anchorOwner = principal ?? null;
592
+ const getLeafId = deps.sessionStorage?.getLeafId?.bind(deps.sessionStorage);
593
+ const captureTurnAnchor = deps.resumeAnchorStore && getLeafId
594
+ ? async (eventId) => { const leaf = await getLeafId(sessionId); if (leaf)
595
+ await deps.resumeAnchorStore.put(sessionId, eventId, leaf, anchorOwner); }
596
+ : undefined;
597
+ const captureUserMessageAnchor = deps.resumeAnchorStore && taskId
598
+ ? async (entryId) => { await deps.resumeAnchorStore.put(sessionId, taskId, entryId, anchorOwner); }
599
+ : undefined;
600
+ const anchor = new TurnAnchorCapture(captureTurnAnchor, () => deps.metrics?.inc("resume_anchor_capture_failed"), captureUserMessageAnchor);
601
+ const flush = async () => {
602
+ if (reasoning) {
603
+ await append("reasoning", modelTextEventData({ text: reasoning, eventId: reasoningEventId }));
604
+ reasoning = "";
605
+ reasoningEventId = undefined;
606
+ }
607
+ if (text) {
608
+ await append("text", modelTextEventData({ text, eventId: anchor.firstTextEventId }));
609
+ text = "";
610
+ }
611
+ };
612
+ const driveResume = async () => {
613
+ resumeLegLive = true;
614
+ const tailContentMode = resumeTaskConfig.forwardSubagentEvents === true ? "on" : "progress_only";
615
+ const stream = await legRunner.resumeStream(token, outcome, { ...resumeTaskConfig, signal: cancelCtrl.signal, preemptSignal: preemptCtrl.signal }, {
616
+ ...(args.workflowParkedResume !== undefined ? { workflowParkedResume: args.workflowParkedResume } : {}),
617
+ onForwardEvent: (e) => {
618
+ fleetPub?.onForwardEvent(e);
619
+ {
620
+ const bg = e.bgAgentId;
621
+ if (bg !== undefined)
622
+ defaultSubagentTailBus.onForwardFrame(bg, tailContentMode, e);
623
+ }
624
+ if (!rs || !taskId)
625
+ return;
626
+ const t = e.type;
627
+ const warnAppend = (kind) => (err) => deps.logger?.warn?.("forward_event_append_failed", { type: kind, taskId, err: err instanceof Error ? err.message : String(err) });
628
+ if (t === "task_progress") {
629
+ void append("task_progress", taskProgressEventData(e)).catch(warnAppend(t));
630
+ }
631
+ else if (t === "tool_start") {
632
+ void append("tool_start", toolStartEventData(e)).catch(warnAppend(t));
633
+ }
634
+ else if (t === "tool_end") {
635
+ void append("tool_end", toolEndEventData(e)).catch(warnAppend(t));
636
+ }
637
+ },
638
+ onTaskNotification: (n, opts) => {
639
+ if (n.task_type === "workflow")
640
+ return;
641
+ defaultSubagentTailBus.forgetHandle(n.task_id);
642
+ if (defaultSubagentTailBus.hasSubscribers(n.task_id)) {
643
+ defaultSubagentTailBus.publish(n.task_id, { type: "task_settled", taskId: n.task_id, status: n.status, ...(typeof n.seq === "number" ? { seq: n.seq } : {}), ...(n.summary ? { summary: redactSecrets(n.summary) } : {}) });
644
+ }
645
+ const hadRow = isFleetAgentTerminalNotification(n) ? (fleetPub?.onChildTerminal(n.sessionId ?? n.task_id, n.status, n.task_id, n.toolUseId) ?? false) : false;
646
+ const parked = !resumeLegLive && Boolean(deps.workflowCompletionInbox && sessionId);
647
+ deps.logger?.info?.("task_notification_observed", { route: "resume", taskId: n.task_id, taskType: n.task_type, status: n.status, hadFleetRow: hadRow, legLive: resumeLegLive, parkedDurable: parked });
648
+ if (parked) {
649
+ void deps.workflowCompletionInbox.enqueue(taskNotificationInboxEntry(sessionId, principal ?? null, n, Date.now(), taskId, opts?.priority)).catch((err) => deps.logger?.warn?.("park_enqueue_failed", { route: "resume", taskId: n.task_id, err: err instanceof Error ? err.message : String(err) }));
650
+ }
651
+ else if (resumeLegLive) {
652
+ const key = taskNotificationStreamKey(n);
653
+ if (resumeNotifiedKeys.get(key) === undefined) {
654
+ resumeNotifiedKeys.set(key, append("task_notification", taskNotificationEventData({ notification: n, ...(opts?.priority !== undefined ? { priority: opts.priority } : {}) })).then(() => true, () => false));
655
+ }
656
+ }
657
+ },
658
+ ...(deps.subagentSteerRegistry && taskId
659
+ ? {
660
+ onSubagentSpawn: (handle) => {
661
+ subagentHandleEvictions.push(deps.subagentSteerRegistry.register(taskId, handle));
662
+ },
663
+ }
664
+ : {}),
665
+ });
666
+ resumeStreamRef = stream;
667
+ if (taskId)
668
+ steerableRuns.set(taskId, stream);
669
+ if (!taskId || !rs)
670
+ return await stream.result();
671
+ onAccepted?.(acceptedResumingReceipt(taskId, sessionId));
672
+ for await (const ev of stream) {
673
+ recordTurnActivity(taskId);
674
+ fleetPub?.onEvent(ev);
675
+ await appendPromptManifest(append, deps.promptManifests, taskId);
676
+ switch (ev.type) {
677
+ case "text_delta":
678
+ text += ev.delta;
679
+ anchor.onText(ev.eventId);
680
+ break;
681
+ case "reasoning_delta": {
682
+ const rb = reasoningDeltaBytes(ev);
683
+ if (persistThinking && rb !== undefined) {
684
+ reasoning += rb;
685
+ reasoningEventId ??= reasoningSegmentEventId(ev.eventId);
686
+ }
687
+ break;
688
+ }
689
+ case "tool_start":
690
+ anchor.onTool();
691
+ await flush();
692
+ await append("tool_start", toolStartEventData(ev));
693
+ break;
694
+ case "tool_end":
695
+ await flush();
696
+ await append("tool_end", toolEndEventData(ev));
697
+ break;
698
+ case "status": {
699
+ const st = ev;
700
+ deps.metrics?.inc("brain_retry_total", { phase: String(st.phase) });
701
+ await flush();
702
+ await append("status", brainStatusEventData(st));
703
+ break;
704
+ }
705
+ case "context_usage":
706
+ await flush();
707
+ await append("context_usage", contextUsageEventData(ev));
708
+ break;
709
+ case "compacted":
710
+ await flush();
711
+ await append("compacted", compactedEventData(ev));
712
+ break;
713
+ case "diagnostics":
714
+ await flush();
715
+ await append("diagnostics", diagnosticsEventData(ev));
716
+ break;
717
+ case "steering_injected":
718
+ await flush();
719
+ await append("steering_injected", steeringInjectedEventData(ev));
720
+ break;
721
+ case "compaction_outcome":
722
+ await flush();
723
+ await append("compaction_outcome", compactionOutcomeEventData(ev));
724
+ break;
725
+ case "workspace_changed":
726
+ await flush();
727
+ await append("workspace_changed", workspaceChangedEventData(ev));
728
+ break;
729
+ case "wiring_manifest":
730
+ await flush();
731
+ await append("wiring_manifest", wiringManifestEventData(ev));
732
+ break;
733
+ case "tool_roster_delta": {
734
+ await flush();
735
+ const rosterDelta = toolRosterDeltaEventData(ev);
736
+ if (rosterDelta !== undefined)
737
+ await append("tool_roster_delta", rosterDelta);
738
+ break;
739
+ }
740
+ case "tool_disclosure": {
741
+ await flush();
742
+ const disclosure = toolDisclosureEventData(ev);
743
+ if (disclosure !== undefined)
744
+ await append("tool_disclosure", disclosure);
745
+ break;
746
+ }
747
+ case "human_input":
748
+ await flush();
749
+ await append("human_input", humanInputEventData(ev));
750
+ break;
751
+ case "message_committed":
752
+ await anchor.onMessageCommitted(ev.role, ev.entryId);
753
+ break;
754
+ case "task_progress":
755
+ await append("task_progress", taskProgressEventData(ev));
756
+ break;
757
+ case "task_notification": {
758
+ await flush();
759
+ const tnr = ev.notification;
760
+ const tnrKey = tnr?.task_id ? taskNotificationStreamKey({ task_type: tnr.task_type, task_id: tnr.task_id, status: String(tnr.status), seq: tnr.seq }) : undefined;
761
+ const tnrPrior = tnrKey !== undefined ? resumeNotifiedKeys.get(tnrKey) : undefined;
762
+ if (tnrPrior === undefined || !(await tnrPrior)) {
763
+ const write = append("task_notification", taskNotificationEventData(ev));
764
+ if (tnrKey !== undefined)
765
+ resumeNotifiedKeys.set(tnrKey, write.then(() => true, () => false));
766
+ await write;
767
+ }
768
+ break;
769
+ }
770
+ case "turn_end": {
771
+ await flush();
772
+ await append("turn_end", turnEndEventData(ev));
773
+ await appendModelUsageDelta(append, deps.modelUsage, taskId);
774
+ await anchor.onTurnEnd();
775
+ break;
776
+ }
777
+ }
778
+ }
779
+ await flush();
780
+ const r = await stream.result();
781
+ const rPark = r.terminal.kind === "paused" ? terminalProjection(r.terminal).status : undefined;
782
+ if (rPark === "suspended") {
783
+ const parkCallId = await parkToolCallId(r, deps.checkpointStore);
784
+ await append("suspended", { gate: r.terminal.kind === "paused" ? r.terminal.gate : null, ...(parkCallId !== undefined ? { toolCallId: parkCallId } : {}) });
785
+ await appendModelUsageDelta(append, deps.modelUsage, taskId);
786
+ return r;
787
+ }
788
+ if (rPark === "needs_review") {
789
+ await append("needs_review", { gate: r.terminal.kind === "paused" ? r.terminal.gate : null });
790
+ await appendModelUsageDelta(append, deps.modelUsage, taskId);
791
+ return r;
792
+ }
793
+ if (isCheckpointReopenedFailure(r) && (await confirmCheckpointReopened())) {
794
+ reopenConfirmedPark = true;
795
+ try {
796
+ await append("suspended", { gate: null, reopened: failureCodeOf(r.terminal) ?? null });
797
+ await appendModelUsageDelta(append, deps.modelUsage, taskId);
798
+ }
799
+ catch {
800
+ }
801
+ return r;
802
+ }
803
+ const safe = await attachModelUsage(stripResumeToken(r), { append, getEvents: (id, a) => rs.getEvents(id, a), modelUsage: deps.modelUsage, taskId });
804
+ await append("done", { result: safe });
805
+ if (taskConfig.suggestNextPrompts && safe.terminal.kind === "completed") {
806
+ const suggestions = await stream.suggestions().catch(() => []);
807
+ if (suggestions.length)
808
+ await append("suggestions", { suggestions: suggestions.map((s) => redactSecrets(s)) });
809
+ }
810
+ return safe;
811
+ };
812
+ const approvalEmit = (frame) => {
813
+ const { type, ...rest } = frame;
814
+ return append(type, rest);
815
+ };
816
+ const withApproval = () => deps.toolApproval && taskId && approvalLeg.active && !approvalLeg.windowZero
817
+ ? deps.toolApproval.runWithContext({
818
+ taskId,
819
+ owner: principal ?? null,
820
+ emit: approvalEmit,
821
+ abortSignal: cancelCtrl.signal,
822
+ sessionId,
823
+ emitCard: createApprovalCardEmitter({ appendDurable: (f) => approvalEmit(f) }),
824
+ emitRevoke: (f) => approvalEmit(f),
825
+ legKey,
826
+ ...(legDeadlineMonotonic !== undefined ? { legDeadlineMonotonic } : {}),
827
+ }, driveResume)
828
+ : driveResume();
829
+ const withElicitation = () => deps.elicitation && taskId
830
+ ? deps.elicitation.runWithContext({ taskId, owner: principal ?? null, emit: (frame) => { const { type, ...rest } = frame; return append(type, rest); }, abortSignal: cancelCtrl.signal }, withApproval)
831
+ : withApproval();
832
+ return await (deps.sendUserFile && taskId
833
+ ? deps.sendUserFile.runWithContext({ taskId, emit: async (frame) => { const { type, ...rest } = frame; await append(type, rest); } }, withElicitation)
834
+ : withElicitation());
835
+ });
836
+ }
837
+ catch (e) {
838
+ if (e instanceof CheckpointError) {
839
+ if (e.code === "checkpoint.resume_aborted") {
840
+ const cs2 = deps.checkpointStore;
841
+ if (cs2) {
842
+ try {
843
+ const live = await cs2.get(token);
844
+ if (live?.status === "pending" && !(await cs2.expire(token, live.scope))) {
845
+ deps.logger?.warn?.("resume_cancel_checkpoint_settle_lost", { taskId, sessionId });
846
+ }
847
+ }
848
+ catch (cancelSettleErr) {
849
+ deps.logger?.warn?.("resume_cancel_checkpoint_settle_failed", {
850
+ taskId, sessionId, err: cancelSettleErr instanceof Error ? cancelSettleErr.message : String(cancelSettleErr),
851
+ });
852
+ }
853
+ }
854
+ if (claimedRow && taskId && deps.runStore) {
855
+ const c = mintCancelledResult({ taskId, sessionId, startedAtMs: resumeLegStartedAt, message: e.message });
856
+ await deps.runStore.setTerminal(taskId, "failed", c, e.message).catch(() => undefined);
857
+ }
858
+ settleFleet("failed");
859
+ return { status: 200, body: { taskId, sessionId, status: "failed", errorCode: "cancelled" } };
860
+ }
861
+ const retriable = e.code === "checkpoint.invalid_outcome" ||
862
+ e.code === "checkpoint.reopen_revote" ||
863
+ e.code === "checkpoint.reopened_concurrently" ||
864
+ e.code === "resume.parent_constraint_missing" ||
865
+ e.code === "resume.parent_constraint_mismatch" ||
866
+ e.code === "resume.constraint_rejected" ||
867
+ e.code === "resume.constraint_unprojectable" ||
868
+ e.code === "resume.usage_window_exhausted" ||
869
+ e.code === "resume.principal_mismatch" ||
870
+ e.code === "resume.placement_mismatch" ||
871
+ e.code === "resume.preflight_rejected" ||
872
+ e.code.startsWith("wake.") ||
873
+ (e.code === "checkpoint.unsupported_version" && checkpointRowRedeemableElsewhere(e.detail?.reason));
874
+ if (claimedRow && taskId && deps.runStore) {
875
+ if (retriable && freshRunClaimed) {
876
+ const c = { taskId, sessionId, terminal: { kind: "failed", code: e.code, message: e.message }, stats: { turns: 0, tokens: 0, usageMissing: true } };
877
+ await deps.runStore.setTerminal(taskId, "failed", c, e.message).catch((err) => {
878
+ deps.logger?.warn?.("wake_synthetic_run_release_failed", { taskId, sessionId, errorCode: e.code, error: err instanceof Error ? err.message : String(err), note: "the synthetic run row for this refused fresh-run resume could not be settled — its task_active claim is still held and the next attempt on this session will 409 conflict.session_active_run until reapStale collects it (runStaleSec)" });
879
+ });
880
+ settleFleet("failed");
881
+ }
882
+ else if (retriable) {
883
+ await (outcome.gate === "plan_review" ? deps.runStore.setNeedsReview(taskId) : deps.runStore.setSuspended(taskId)).catch(() => undefined);
884
+ settleFleet(outcome.gate === "plan_review" ? "needs_review" : "suspended");
885
+ }
886
+ else {
887
+ await deps.runStore.setTerminal(taskId, "failed", null, e.message).catch(() => undefined);
888
+ settleFleet("failed");
889
+ }
890
+ }
891
+ const status = e.code === "checkpoint.not_found" ? 404 : 409;
892
+ return {
893
+ status,
894
+ body: {
895
+ ...(taskId !== undefined ? { taskId } : {}),
896
+ error: e.message,
897
+ errorCode: e.code === "checkpoint.invalid_outcome"
898
+ ? (outcome.gate === "policy_ask" ? "approval_binding_mismatch" : "resume_outcome_invalid")
899
+ : e.code,
900
+ ...(e.detail?.field ? { field: e.detail.field } : {}),
901
+ ...((e.code === "resume.usage_window_exhausted" || e.code === "resume.preflight_rejected") && typeof e.detail?.retryAfterMs === "number" && Number.isFinite(e.detail.retryAfterMs) && e.detail.retryAfterMs > 0
902
+ ? { retryAfterSec: Math.max(1, Math.ceil(e.detail.retryAfterMs / 1000)) }
903
+ : {}),
904
+ ...(retriable ? { retriable: true } : {}),
905
+ },
906
+ };
907
+ }
908
+ if (cancelCtrl.signal.aborted) {
909
+ if (taskId && deps.runStore) {
910
+ const c = mintCancelledResult({ taskId, sessionId, startedAtMs: resumeLegStartedAt, message: "cancelled by user" });
911
+ await deps.runStore.setTerminal(taskId, "failed", c, "cancelled by user").catch(() => undefined);
912
+ }
913
+ settleFleet("failed");
914
+ return { status: 200, body: { taskId, sessionId, status: "failed", errorCode: "cancelled" } };
915
+ }
916
+ if (claimedRow && taskId && deps.runStore) {
917
+ await deps.runStore.setTerminal(taskId, "failed", null, errorText(e)).catch(() => undefined);
918
+ }
919
+ settleFleet("failed");
920
+ throw e;
921
+ }
922
+ finally {
923
+ unregisterEngineNotice?.();
924
+ if (taskId) {
925
+ if (inflightRuns.get(taskId) === cancelCtrl)
926
+ inflightRuns.delete(taskId);
927
+ if (preemptableRuns.get(taskId) === preemptCtrl)
928
+ preemptableRuns.delete(taskId);
929
+ if (resumeStreamRef !== undefined && steerableRuns.get(taskId) === resumeStreamRef)
930
+ steerableRuns.delete(taskId);
931
+ for (const evict of subagentHandleEvictions)
932
+ evict();
933
+ deps.modelUsage?.clear(taskId);
934
+ deps.promptManifests?.clear(taskId, resumeManifestLease);
935
+ }
936
+ if (resumeHeartbeat)
937
+ clearInterval(resumeHeartbeat);
938
+ resumeLegLive = false;
939
+ }
940
+ evictIfConflict(deps.runner, sessionId, result);
941
+ const checkpointReopened = reopenConfirmedPark;
942
+ const resumePlane = terminalProjection(result.terminal);
943
+ if (taskId && deps.runStore) {
944
+ if (resumePlane.status === "suspended" || checkpointReopened)
945
+ await deps.runStore.setSuspended(taskId);
946
+ else if (resumePlane.status === "needs_review")
947
+ await deps.runStore.setNeedsReview(taskId);
948
+ else
949
+ await deps.runStore.setTerminal(taskId, resumePlane.status, stripResumeToken(result), resumePlane.errorMessage ?? null);
950
+ }
951
+ settleFleet(checkpointReopened ? "suspended" : resumePlane.status, fleetRunResiduals(result));
952
+ return {
953
+ status: 200,
954
+ body: {
955
+ taskId, sessionId, status: resumePlane.status,
956
+ bindingEnforced: true,
957
+ ...(resumePlane.errorCode ? { errorCode: resumePlane.errorCode } : {}),
958
+ ...(resumePlane.errorMessage ? { errorMessage: redactErrorMessage(resumePlane.errorMessage) ?? "" } : {}),
959
+ ...(checkpointReopened ? { retriable: true } : {}),
960
+ },
961
+ };
962
+ }
963
+ async function mayDiscloseRowError(cs, token, decider) {
964
+ if (decider === undefined || decider.explicitOperator)
965
+ return true;
966
+ let scope;
967
+ try {
968
+ scope = await cs.peekScopeByToken(token);
969
+ }
970
+ catch (e) {
971
+ deps.logger?.warn?.("checkpoint_owner_probe_unreadable", { note: "peekScopeByToken failed — row-error disclosure withheld (fail-closed)", err: e instanceof Error ? e.message : String(e) });
972
+ return false;
973
+ }
974
+ if (scope === undefined)
975
+ return false;
976
+ const owner = decodeCheckpointScope(scope ?? CHECKPOINT_PUBLIC_SCOPE);
977
+ return owner === undefined || owner === decider.principal;
978
+ }
979
+ async function resumePreempted(sessionId, req, acceptEarly, decider) {
980
+ const cs = deps.checkpointStore;
981
+ const RESUMABLE_GATE_KIND = "resource_limit";
982
+ const preemptBinding = { gateKinds: [RESUMABLE_GATE_KIND] };
983
+ const token = (await cs.findPendingTokenBySession(sessionId, undefined, preemptBinding)) ?? (await cs.findPendingTokenBySession(sessionId));
984
+ const noSuspension = { status: 404, body: { error: "no resumable suspension for this task (already running, resumed, or expired)", errorCode: "not_found.suspension" } };
985
+ if (!token)
986
+ return noSuspension;
987
+ if (decider !== undefined && !decider.explicitOperator) {
988
+ const peeked = await cs
989
+ .peekPendingScope(sessionId, preemptBinding)
990
+ .then((sc) => (sc === undefined ? cs.peekPendingScope(sessionId) : sc))
991
+ .catch(() => undefined);
992
+ const peekedOwner = peeked != null ? decodeCheckpointScope(peeked) : undefined;
993
+ if (peekedOwner !== undefined && peekedOwner !== decider.principal) {
994
+ deps.logger?.warn?.("assistant_resume_row_scope_mismatch", { sessionId, note: "pending row is foreign to the verified caller — owner gate ahead of the row load" });
995
+ return noSuspension;
996
+ }
997
+ }
998
+ let cp;
999
+ try {
1000
+ cp = await cs.get(token);
1001
+ }
1002
+ catch (e) {
1003
+ if (!(await mayDiscloseRowError(cs, token, decider))) {
1004
+ deps.logger?.warn?.("assistant_resume_unreadable_row_withheld", { sessionId, note: "row unreadable and the caller's ownership of THAT token is unproven — folded into the uniform 404" });
1005
+ return noSuspension;
1006
+ }
1007
+ return { status: 409, body: { error: e instanceof Error ? e.message : String(e), errorCode: "conflict.checkpoint_unreadable" } };
1008
+ }
1009
+ if (!cp) {
1010
+ if (!(await mayDiscloseRowError(cs, token, decider)))
1011
+ return noSuspension;
1012
+ return { status: 404, body: { error: "checkpoint not found", errorCode: "not_found.checkpoint" } };
1013
+ }
1014
+ if (decider !== undefined && !decider.explicitOperator && cp.scope !== CHECKPOINT_PUBLIC_SCOPE && cp.scope !== decider.principal) {
1015
+ deps.logger?.warn?.("assistant_resume_row_scope_mismatch", { sessionId, note: "loaded pending row is foreign to the verified caller — TOCTOU fold" });
1016
+ return noSuspension;
1017
+ }
1018
+ const gateKind = cp.gate?.kind;
1019
+ if (gateKind !== RESUMABLE_GATE_KIND) {
1020
+ return { status: 409, body: { error: `task is suspended on a '${gateKind ?? "unknown"}' gate, not a resumable resource/preempt suspension — an approval gate must be decided via POST /v1/approvals/:id/decide`, errorCode: "gate_not_resumable" } };
1021
+ }
1022
+ const ctx = await cs.getCtx(sessionId).catch(() => null);
1023
+ if (!ctx)
1024
+ return { status: 409, body: { error: "resume context missing — cannot rebuild task config", errorCode: "conflict.resume_context_unavailable" } };
1025
+ const auth = { sessionId: cp.sessionId, principal: decodeCheckpointScope(cp.scope), memoryScope: ctx.memoryScope };
1026
+ const { spec, engineNotices } = await resolveResumeSpec(ctx.body, req, auth);
1027
+ const { objective: resumeObjective, sessionId: _sessionId, ...taskConfig } = spec;
1028
+ const verifyRounds = verifyRoundsFromBody(ctx.body);
1029
+ return driveResumeIntoRunLog({ runner: runnerFor(spec), token, sessionId: cp.sessionId, principal: auth.principal, fleetScope: resumeFleetScope(req, auth), taskConfig, resumeObjective, outcome: { gate: "resource_limit", decision: "continue" }, verifyRounds, ...(engineNotices.length > 0 ? { engineNotices } : {}), ...(acceptEarly === true ? { acceptEarly: true } : {}), ...(req !== undefined ? { callerInitiated: true } : {}) });
1030
+ }
1031
+ async function startFreshHostTurn(sessionId, objective, principal, req, trustedInternals, runId) {
1032
+ const cs = deps.checkpointStore;
1033
+ if (!deps.runStore) {
1034
+ return { status: 501, body: { error: "delivering a workflow decision on the host's next turn requires a durable run store (DB_BACKEND=mysql|pg|local)", errorCode: "capability.run_store_required" } };
1035
+ }
1036
+ const ctx = await cs.getCtx(sessionId).catch(() => null);
1037
+ if (!ctx) {
1038
+ return {
1039
+ status: 409,
1040
+ body: {
1041
+ error: "the workflow's originating session no longer has the rebuild inputs for a new turn: a session's resume context is garbage-collected once it is stale AND that session itself has no pending checkpoint and no active run (REAP_RUN_STALE_SEC, default 120s) — a workflow CHILD's park does not hold the HOST session's row open. Raise REAP_RUN_STALE_SEC to cover how long approvals may sit before someone decides them, or resume the workflow run yourself with the runId below",
1042
+ errorCode: "conflict.resume_context_unavailable",
1043
+ runId,
1044
+ },
1045
+ };
1046
+ }
1047
+ const windowDenial = await usageWindowDenialFor(principal || GLOBAL_USAGE_KEY);
1048
+ if (windowDenial)
1049
+ return { status: windowDenial.status, body: windowDenial.body };
1050
+ const auth = { sessionId, principal, memoryScope: ctx.memoryScope };
1051
+ let spec;
1052
+ let engineNotices = [];
1053
+ try {
1054
+ ({ spec, engineNotices } = await resolveResumeSpec(ctx.body, req, auth));
1055
+ }
1056
+ catch (e) {
1057
+ if (e instanceof HttpError) {
1058
+ return { status: 409, body: { error: `resume blocked by an admission policy change: ${e.message}`, errorCode: "resume_blocked_by_policy", ...(e.code ? { blockedBy: e.code } : {}), ...(e.extra ?? {}) } };
1059
+ }
1060
+ throw e;
1061
+ }
1062
+ const hostSpec = { ...spec, objective, sessionId };
1063
+ const out = await mintAndDriveDurableRun(routeCtxBase, deps.runStore, {
1064
+ prepared: { spec: hostSpec, runner: runnerFor(hostSpec), auth, body: ctx.body, ...(engineNotices.length > 0 ? { engineNotices } : {}) },
1065
+ fleetScope: resumeFleetScope(req, auth),
1066
+ elicitOwner: principal ?? null,
1067
+ source: "workflow_parked_decide",
1068
+ trustedInternals,
1069
+ });
1070
+ if (out.status !== 202)
1071
+ return { status: out.status, body: out.body };
1072
+ const minted = out.body;
1073
+ return acceptedResumingReceipt(minted.taskId, minted.sessionId);
1074
+ }
1075
+ async function resumeWake(sessionId, message, caller, req, acceptEarly, workflowParkedResume, ownerLeaseAdmittedFor) {
1076
+ const cs = deps.checkpointStore;
1077
+ const wakeBinding = { gateKinds: TERMINAL_PARK_GATE_KINDS, pick: "oldest" };
1078
+ const token = (await cs.findPendingTokenBySession(sessionId, undefined, wakeBinding)) ?? (await cs.findPendingTokenBySession(sessionId));
1079
+ if (!token)
1080
+ return { status: 404, body: { error: "no parked checkpoint for this session (nothing to wake)", errorCode: "not_found.parked_checkpoint" } };
1081
+ const operator = explicitOperatorOk(caller, deps.config.operatorPrincipals);
1082
+ if (!operator) {
1083
+ const scope = await cs.peekPendingScope(sessionId, wakeBinding).then((s) => (s === undefined ? cs.peekPendingScope(sessionId) : s)).catch(() => undefined);
1084
+ const decodedScope = scope != null ? decodeCheckpointScope(scope) : undefined;
1085
+ if (decodedScope !== undefined && decodedScope !== caller) {
1086
+ return { status: 404, body: { error: "no parked checkpoint for this session (nothing to wake)", errorCode: "not_found.parked_checkpoint" } };
1087
+ }
1088
+ }
1089
+ let cp;
1090
+ try {
1091
+ cp = await cs.get(token);
1092
+ }
1093
+ catch (e) {
1094
+ if (!(await mayDiscloseRowError(cs, token, { principal: caller, explicitOperator: operator }))) {
1095
+ deps.logger?.warn?.("wake_unreadable_row_withheld", { sessionId, note: "row unreadable and the caller's ownership of THAT token is unproven — folded into the uniform 404" });
1096
+ return { status: 404, body: { error: "no parked checkpoint for this session (nothing to wake)", errorCode: "not_found.parked_checkpoint" } };
1097
+ }
1098
+ return { status: 409, body: { error: e instanceof Error ? e.message : String(e), errorCode: "conflict.checkpoint_unreadable" } };
1099
+ }
1100
+ if (!cp) {
1101
+ if (!(await mayDiscloseRowError(cs, token, { principal: caller, explicitOperator: operator }))) {
1102
+ return { status: 404, body: { error: "no parked checkpoint for this session (nothing to wake)", errorCode: "not_found.parked_checkpoint" } };
1103
+ }
1104
+ return { status: 404, body: { error: "checkpoint not found", errorCode: "not_found.checkpoint" } };
1105
+ }
1106
+ if (!operator && cp.scope !== CHECKPOINT_PUBLIC_SCOPE && cp.scope !== caller) {
1107
+ return { status: 404, body: { error: "no parked checkpoint for this session (nothing to wake)", errorCode: "not_found.parked_checkpoint" } };
1108
+ }
1109
+ const gateKind = cp.gate?.kind;
1110
+ if (gateKind !== "task_done") {
1111
+ return { status: 409, body: { error: `cannot wake: this checkpoint awaits a pending gate decision (gate "${gateKind ?? "unknown"}") — resolve it through its own decide entry`, errorCode: "wake.gate_pending" } };
1112
+ }
1113
+ const trusted = operator;
1114
+ if (message !== undefined) {
1115
+ try {
1116
+ validatePendingSteer({ text: message, trusted });
1117
+ }
1118
+ catch (e) {
1119
+ if (e instanceof CheckpointError && e.code === "steering.invalid_content") {
1120
+ return { status: 422, body: { errorCode: "steering.invalid_content", error: e.message } };
1121
+ }
1122
+ throw e;
1123
+ }
1124
+ }
1125
+ const ctx = await cs.getCtx(sessionId).catch(() => null);
1126
+ if (!ctx)
1127
+ return { status: 409, body: { error: "resume context missing — cannot rebuild task config", errorCode: "conflict.resume_context_unavailable" } };
1128
+ const auth = { sessionId: cp.sessionId, principal: decodeCheckpointScope(cp.scope), memoryScope: ctx.memoryScope };
1129
+ const { spec, engineNotices } = await resolveResumeSpec(ctx.body, req, auth);
1130
+ const { objective: resumeObjective, sessionId: _sessionId, ...taskConfig } = spec;
1131
+ return driveResumeIntoRunLog({
1132
+ runner: runnerFor(spec),
1133
+ token,
1134
+ sessionId: cp.sessionId,
1135
+ principal: auth.principal,
1136
+ fleetScope: resumeFleetScope(req, auth),
1137
+ taskConfig,
1138
+ resumeObjective,
1139
+ outcome: { gate: "wake", ...(message !== undefined ? { message: { text: message, trusted } } : {}) },
1140
+ verifyRounds: undefined,
1141
+ mintFreshRun: { source: "wake" },
1142
+ ...(engineNotices.length > 0 ? { engineNotices } : {}),
1143
+ ...(acceptEarly === true ? { acceptEarly: true } : {}),
1144
+ ...(req !== undefined ? { callerInitiated: true } : {}),
1145
+ ...(workflowParkedResume !== undefined ? { workflowParkedResume } : {}),
1146
+ ...(ownerLeaseAdmittedFor !== undefined && auth.principal === ownerLeaseAdmittedFor ? { ownerLeaseAdmitted: true } : {}),
1147
+ });
1148
+ }
1149
+ async function resumePlanReview(sessionId, decision, editedPlan, reason, req, log, acceptEarly, decider) {
1150
+ const cs = deps.checkpointStore;
1151
+ const log404 = (r) => deps.logger?.info?.("decide_404", {
1152
+ reason: r,
1153
+ ...(log?.taskId !== undefined ? { taskId: log.taskId } : { sessionId }),
1154
+ ...(log?.principalPresent !== undefined ? { principalPresent: log.principalPresent } : {}),
1155
+ });
1156
+ const PLAN_REVIEW_GATE_KIND = "plan_review";
1157
+ const planReviewBinding = { gateKinds: [PLAN_REVIEW_GATE_KIND] };
1158
+ const token = (await cs.findPendingTokenBySession(sessionId, undefined, planReviewBinding)) ?? (await cs.findPendingTokenBySession(sessionId));
1159
+ const noPending = { status: 404, body: { error: "no pending plan_review for this session (already decided or expired)", errorCode: "not_found.plan_review" } };
1160
+ if (!token) {
1161
+ log404("no-pending-checkpoint");
1162
+ return noPending;
1163
+ }
1164
+ if (decider !== undefined && !decider.explicitOperator) {
1165
+ const peeked = await cs
1166
+ .peekPendingScope(sessionId, planReviewBinding)
1167
+ .then((sc) => (sc === undefined ? cs.peekPendingScope(sessionId) : sc))
1168
+ .catch(() => undefined);
1169
+ const peekedOwner = peeked != null ? decodeCheckpointScope(peeked) : undefined;
1170
+ if (peekedOwner !== undefined && peekedOwner !== decider.principal) {
1171
+ deps.logger?.warn?.("plan_review_row_scope_mismatch", { sessionId, note: "pending row is foreign to the verified caller — owner gate ahead of the row load" });
1172
+ return noPending;
1173
+ }
1174
+ }
1175
+ let cp;
1176
+ try {
1177
+ cp = await cs.get(token);
1178
+ }
1179
+ catch (e) {
1180
+ if (!(await mayDiscloseRowError(cs, token, decider))) {
1181
+ deps.logger?.warn?.("plan_review_unreadable_row_withheld", { sessionId, note: "row unreadable and the caller's ownership of THAT token is unproven — folded into the uniform 404" });
1182
+ return noPending;
1183
+ }
1184
+ return { status: 409, body: { error: e instanceof Error ? e.message : String(e), errorCode: "conflict.checkpoint_unreadable" } };
1185
+ }
1186
+ if (!cp) {
1187
+ log404("checkpoint-missing");
1188
+ if (!(await mayDiscloseRowError(cs, token, decider)))
1189
+ return noPending;
1190
+ return { status: 404, body: { error: "checkpoint not found", errorCode: "not_found.checkpoint" } };
1191
+ }
1192
+ if (decider !== undefined && !decider.explicitOperator && cp.scope !== CHECKPOINT_PUBLIC_SCOPE && cp.scope !== decider.principal) {
1193
+ deps.logger?.warn?.("plan_review_row_scope_mismatch", { sessionId, note: "loaded pending row is foreign to the verified caller — TOCTOU fold" });
1194
+ return noPending;
1195
+ }
1196
+ const gateKind = cp.gate?.kind;
1197
+ if (gateKind !== PLAN_REVIEW_GATE_KIND) {
1198
+ return {
1199
+ status: 409,
1200
+ body: {
1201
+ error: `task is suspended on a '${gateKind ?? "unknown"}' gate, not a plan_review — a tool approval must be decided via POST /v1/approvals/:id/decide`,
1202
+ errorCode: "gate_not_plan_review",
1203
+ },
1204
+ };
1205
+ }
1206
+ const ctx = await cs.getCtx(sessionId).catch(() => null);
1207
+ if (!ctx)
1208
+ return { status: 409, body: { error: "resume context missing — cannot rebuild task config", errorCode: "conflict.resume_context_unavailable" } };
1209
+ const auth = { sessionId: cp.sessionId, principal: decodeCheckpointScope(cp.scope), memoryScope: ctx.memoryScope };
1210
+ const { spec, engineNotices } = await resolveResumeSpec(ctx.body, req, auth);
1211
+ const { objective: resumeObjective, sessionId: _sessionId, ...taskConfig } = spec;
1212
+ const verifyRounds = verifyRoundsFromBody(ctx.body);
1213
+ const outcome = {
1214
+ gate: "plan_review",
1215
+ decision,
1216
+ ...(editedPlan !== undefined ? { editedPlan } : {}),
1217
+ ...(reason ? { reason } : {}),
1218
+ };
1219
+ return driveResumeIntoRunLog({ runner: runnerFor(spec), token, sessionId: cp.sessionId, principal: auth.principal, fleetScope: resumeFleetScope(req, auth), taskConfig, resumeObjective, outcome, verifyRounds, ...(engineNotices.length > 0 ? { engineNotices } : {}), ...(acceptEarly === true ? { acceptEarly: true } : {}), ...(req !== undefined ? { callerInitiated: true } : {}) });
1220
+ }
1221
+ const routeCtxBase = {
1222
+ deps,
1223
+ registry: { idemCache, inflightRuns, preemptableRuns, cancelledViaVerb, steerableRuns, wakeParkMints, counters },
1224
+ helpers: { readJson, readRawBody, rateLimited, quotaExceeded, ownerQuotaDenied, leaseDenied, ownerLeaseDenied, usageWindowDenied, safeDecode, isFleetWide, runOwnerOk, sessionOwnerScope, sessionOwnerScopeForWrite, runSessionAcceptOk },
1225
+ local: { images: createImagesLocal(deps), sessions: createSessionsLocal(), sessionSync: createSessionSyncLocal() },
1226
+ legs: { prepareSpec, finalizeTaskResult, resumeCheckpoint, driveResumeIntoRunLog, resumePreempted, resumeWake, resumePlanReview },
1227
+ };
1228
+ return { routeCtxBase, resumeCheckpoint, driveResumeIntoRunLog, driveResumeLeg, mayDiscloseRowError, resumePreempted, startFreshHostTurn, resumeWake, resumePlanReview };
1229
+ }
1230
+ //# sourceMappingURL=resume-legs.js.map