@sema-agent/core 7.5.2 → 7.6.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.
Files changed (138) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/dist/agents/agent-transcript-tool.d.ts +2 -2
  3. package/dist/agents/cascade.d.ts +4 -5
  4. package/dist/agents/cascade.js +12 -10
  5. package/dist/agents/repair-loop.d.ts +7 -5
  6. package/dist/agents/repair-loop.js +13 -15
  7. package/dist/agents/retain-ledger.d.ts +2 -3
  8. package/dist/agents/send-message-tool.d.ts +2 -2
  9. package/dist/agents/session-util.d.ts +2 -2
  10. package/dist/agents/subagent.d.ts +27 -46
  11. package/dist/agents/subagent.js +119 -105
  12. package/dist/agents/suspend-guard.d.ts +31 -19
  13. package/dist/agents/suspend-guard.js +14 -8
  14. package/dist/agents/teacher.d.ts +2 -2
  15. package/dist/agents/teacher.js +9 -9
  16. package/dist/agents/team.d.ts +6 -5
  17. package/dist/agents/team.js +10 -8
  18. package/dist/agents/verify.d.ts +8 -9
  19. package/dist/agents/verify.js +17 -17
  20. package/dist/core/a2a.js +2 -1
  21. package/dist/core/agent-definition.d.ts +172 -0
  22. package/dist/core/agent-definition.js +1 -0
  23. package/dist/core/ask-origin.d.ts +60 -7
  24. package/dist/core/ask-origin.js +26 -1
  25. package/dist/core/checkpoint-store.d.ts +78 -76
  26. package/dist/core/checkpoint-store.js +17 -1
  27. package/dist/core/delegation-frames.d.ts +298 -0
  28. package/dist/core/delegation-frames.js +21 -0
  29. package/dist/core/engine-notice.d.ts +555 -0
  30. package/dist/core/engine-notice.js +55 -0
  31. package/dist/core/gate-fold.d.ts +12 -0
  32. package/dist/core/gate-fold.js +158 -0
  33. package/dist/core/gate-lanes.d.ts +93 -0
  34. package/dist/core/gate-lanes.js +626 -0
  35. package/dist/core/gate-outcome.d.ts +189 -0
  36. package/dist/core/gate-outcome.js +70 -0
  37. package/dist/core/hands-band.d.ts +134 -0
  38. package/dist/core/hands-band.js +1 -0
  39. package/dist/core/hooks.d.ts +22 -177
  40. package/dist/core/hooks.js +53 -851
  41. package/dist/core/mcp-failure.d.ts +142 -0
  42. package/dist/core/mcp-failure.js +145 -0
  43. package/dist/core/mcp-server-spec.d.ts +217 -0
  44. package/dist/core/mcp-server-spec.js +1 -0
  45. package/dist/core/mcp.d.ts +21 -77
  46. package/dist/core/mcp.js +76 -150
  47. package/dist/core/model-seat.d.ts +99 -0
  48. package/dist/core/model-seat.js +1 -0
  49. package/dist/core/pause-registry.d.ts +131 -0
  50. package/dist/core/pause-registry.js +27 -0
  51. package/dist/core/reminder-mint.d.ts +10 -0
  52. package/dist/core/reminder-mint.js +3 -0
  53. package/dist/core/runner/assemble-result.d.ts +32 -41
  54. package/dist/core/runner/assemble-result.js +55 -74
  55. package/dist/core/runner/contracts.d.ts +427 -69
  56. package/dist/core/runner/denial-limit-arms.d.ts +1 -1
  57. package/dist/core/runner/denial-limit-arms.js +3 -3
  58. package/dist/core/runner/gate-exit.d.ts +242 -0
  59. package/dist/core/runner/gate-exit.js +124 -0
  60. package/dist/core/runner/park-commit.d.ts +17 -23
  61. package/dist/core/runner/park-commit.js +14 -15
  62. package/dist/core/runner/prepare-ask-lane.d.ts +0 -3
  63. package/dist/core/runner/prepare-ask-lane.js +3 -5
  64. package/dist/core/runner/prepare-boundary-parks.d.ts +3 -6
  65. package/dist/core/runner/prepare-boundary-parks.js +3 -3
  66. package/dist/core/runner/prepare-caps-and-workflow.d.ts +2 -7
  67. package/dist/core/runner/prepare-caps-and-workflow.js +1 -1
  68. package/dist/core/runner/prepare-delegation-surface.d.ts +2 -7
  69. package/dist/core/runner/prepare-gate-stations.d.ts +4 -7
  70. package/dist/core/runner/prepare-gate-stations.js +29 -54
  71. package/dist/core/runner/prepare-inherited-gate.js +1 -1
  72. package/dist/core/runner/prepare-memory.d.ts +44 -26
  73. package/dist/core/runner/prepare-park-ask.d.ts +2 -4
  74. package/dist/core/runner/prepare-park-ask.js +5 -5
  75. package/dist/core/runner/prepare-task.d.ts +2 -2
  76. package/dist/core/runner/prepare-task.js +8 -9
  77. package/dist/core/runner/prepare-wiring-manifest.d.ts +7 -15
  78. package/dist/core/runner/prepare-wiring-manifest.js +9 -10
  79. package/dist/core/runner/runtask.d.ts +20 -102
  80. package/dist/core/runner/runtask.js +119 -121
  81. package/dist/core/runner/terminal-projection.d.ts +22 -0
  82. package/dist/core/runner/terminal-projection.js +28 -0
  83. package/dist/core/runner-deps.d.ts +1416 -0
  84. package/dist/core/runner-deps.js +1 -0
  85. package/dist/core/runtime-caps.d.ts +164 -0
  86. package/dist/core/runtime-caps.js +1 -0
  87. package/dist/core/store-contracts/checkpoint-store-contract.d.ts +4 -1
  88. package/dist/core/store-contracts/checkpoint-store-contract.js +8 -2
  89. package/dist/core/task-event.d.ts +910 -0
  90. package/dist/core/task-event.js +1 -0
  91. package/dist/core/task-limits.d.ts +110 -0
  92. package/dist/core/task-limits.js +1 -0
  93. package/dist/core/task-result.d.ts +809 -0
  94. package/dist/core/task-result.js +1 -0
  95. package/dist/core/task-spec.d.ts +1370 -0
  96. package/dist/core/task-spec.js +1 -0
  97. package/dist/core/task-stream.d.ts +382 -0
  98. package/dist/core/task-stream.js +1 -0
  99. package/dist/core/terminal-cause.d.ts +137 -0
  100. package/dist/core/terminal-cause.js +9 -0
  101. package/dist/core/tool-policy.d.ts +43 -139
  102. package/dist/core/tool-policy.js +79 -112
  103. package/dist/core/tool-spec.d.ts +1174 -0
  104. package/dist/core/tool-spec.js +1 -0
  105. package/dist/core/types.d.ts +27 -7789
  106. package/dist/core/types.js +2 -76
  107. package/dist/core/warm-resume.d.ts +2 -2
  108. package/dist/core/wiring-manifest.d.ts +6 -3
  109. package/dist/core/workflow-journal-store.js +3 -4
  110. package/dist/engine/harness/agent-harness.d.ts +1 -1
  111. package/dist/index.d.ts +12 -8
  112. package/dist/index.js +9 -6
  113. package/dist/orchestration/builtin-workflows.d.ts +2 -2
  114. package/dist/orchestration/builtin-workflows.js +1 -1
  115. package/dist/orchestration/goal.d.ts +2 -2
  116. package/dist/orchestration/goal.js +8 -7
  117. package/dist/orchestration/run-spec.d.ts +2 -2
  118. package/dist/orchestration/run-spec.js +5 -3
  119. package/dist/orchestration/run-workflow-tool.d.ts +4 -4
  120. package/dist/orchestration/run-workflow-tool.js +4 -4
  121. package/dist/orchestration/workflow-governance.d.ts +4 -4
  122. package/dist/orchestration/workflow-governance.js +4 -2
  123. package/dist/orchestration/workflow-primitives.d.ts +1 -1
  124. package/dist/orchestration/workflow-primitives.js +1 -1
  125. package/dist/orchestration/workflow.d.ts +15 -4
  126. package/dist/orchestration/workflow.js +64 -39
  127. package/dist/prompts/supervisor.d.ts +1 -1
  128. package/dist/prompts/supervisor.js +3 -3
  129. package/dist/scenarios/scenario-registry.d.ts +3 -3
  130. package/dist/scenarios/scenario-registry.js +1 -1
  131. package/dist/scenarios/teacher-quickstart.d.ts +2 -2
  132. package/dist/server/http.d.ts +2 -2
  133. package/dist/stores/file/fs-atomic.d.ts +88 -12
  134. package/dist/stores/file/fs-atomic.js +184 -55
  135. package/dist/stores/file/index.d.ts +1 -0
  136. package/dist/stores/file/index.js +1 -0
  137. package/package.json +3 -1
  138. package/test/export-surface.snapshot.json +82 -22
@@ -6,7 +6,7 @@ import { deliverDelegationLifecycle, deliverEngineNotice, undrainedUserInputNoti
6
6
  import { planRejectionClears, resolveTriggerWindow } from "../context-edit.js";
7
7
  import { AgentHarness, DEFAULT_COMPACTION_SETTINGS, isSyntheticApiErrorMessage, uuidv7 } from "../../internal/harness.js";
8
8
  import { snapshotActorAssertion } from "../../internal/llm.js";
9
- import { CheckpointError, BINDING_CHECKPOINT_VERSION, checkpointVersionOf, F012_CHECKPOINT_VERSION, MAX_SUPPORTED_CHECKPOINT_VERSION, REAL_APPROVAL_CHECKPOINT_VERSION, FACE_CHECKPOINT_VERSION, remainingBudgetMicroUsd, readPendingSteerQueue, remainingTokens, LEGACY_PENDING_STEER_INPUT_ID, MAX_STEER_INPUT_ID_CHARS, validatePendingSteer, winnerFromOutcome, } from "../checkpoint-store.js";
9
+ import { CheckpointError, BINDING_CHECKPOINT_VERSION, checkpointVersionOf, F012_CHECKPOINT_VERSION, MAX_SUPPORTED_CHECKPOINT_VERSION, REAL_APPROVAL_CHECKPOINT_VERSION, FACE_CHECKPOINT_VERSION, remainingBudgetMicroUsd, readPendingSteerQueue, remainingTokens, resolveCheckpointStore, LEGACY_PENDING_STEER_INPUT_ID, MAX_STEER_INPUT_ID_CHARS, validatePendingSteer, winnerFromOutcome, realApprovalOrgFact } from "../checkpoint-store.js";
10
10
  import { GIT_STATUS_ECHO_PREVIEW, branchCarriesVisiblePositiveGitFrame, newestEngineGitFrame, stripGitStatusUnits } from "./git-status-frame.js";
11
11
  import { gitFrameContextVisible, normalizeGitAnnouncement } from "../../internal/harness.js";
12
12
  import { engineVersion } from "../version.js";
@@ -35,9 +35,12 @@ import { toImageContent } from "./image.js";
35
35
  import { OUTPUT_TOOL_NAME, SKILLS_LISTING_PROBE_HEADER, resolveOutputRetries } from "./synthetic-tools.js";
36
36
  import { cacheFamilyOf, usageCostMicroUsd } from "./usage-accounting.js";
37
37
  import { assembleResult, errorCodeOf } from "./assemble-result.js";
38
+ import { amendTerminal, terminalProjection } from "./terminal-projection.js";
38
39
  import { ATTACHMENT_BYTE_CAP, CHANGED_FILES_MAX, TOTAL_TOKENS_REMINDER_DEFAULT_MODE, AGENT_LISTING_REMOVED_HEADER, SKILLS_LISTING_DELTA_HEADER, SKILLS_LISTING_REMOVED_HEADER, advanceCadenceClock, agentListingDeltaHeader, attachmentEnvelopeTags, agentListingInitialHeader, replayAnnouncedListing, replayAnnouncedModels, clipToBytes, collectDateChange, collectDueAttachments, collectInstructionsChange, commitAgentListing, commitInstructionsChange, commitSkillsListing, createAttachmentState, rebaseCadenceWindows, reduceToolEnd, renderAgentListingDelta, renderMcpDroppedTools, renderMcpInstructionsDelta, renderOrphanedBackgroundTasks, selectMcpDroppedBatch, renderSkillsListingDelta, renderToolsDelta, stampWriteAnchor } from "./turn-attachments.js";
39
40
  import { buildWorkingFileAttachments, centerAdoptionOption, contextInstructionFilesOption, emitInputTruncated, forkContextOption } from "./compaction-call-options.js";
40
- import { effectiveDelegationFacts, gatedCallIdOf, placementValueOrAbsent, prepareTask, resolveCheckpointStore } from "./prepare-task.js";
41
+ import { effectiveDelegationFacts, prepareTask } from "./prepare-task.js";
42
+ import { gatedCallIdOf } from "./park-commit.js";
43
+ import { placementValueOrAbsent } from "./checkpoint-scope.js";
41
44
  import { SessionReadFileStates } from "./prepare-hands-readface.js";
42
45
  import { settleTeardownLeg } from "./teardown-bounded.js";
43
46
  import { TOOL_SEARCH_NAME } from "./tool-disclosure.js";
@@ -51,7 +54,11 @@ import { droppedToolDigest, withAnnounceDigests } from "./announce-once-ledger.j
51
54
  import { appendInterruptionMarker, reconcileInterruptedSession } from "../session-reconcile.js";
52
55
  import { RunnerSharedToolResultStore } from "../tool-result-store.js";
53
56
  import { formatDiagnosticsBlock } from "../lsp-diagnostics.js";
54
- import { checkToolPolicyProjection, constraintChainDigest, constraintChainEntryOfSuppliedLayer, isApprovalSettledBy, refuseOutOfContractDecision, screenApproverAttribution, toolPolicyNameSets } from "../tool-policy.js";
57
+ import { isAskOrigin } from "../ask-origin.js";
58
+ import { isGateKind, PAUSE_REGISTRY, resumeGateMatches } from "../pause-registry.js";
59
+ import { checkToolPolicyProjection, constraintChainDigest, constraintChainEntryOfSuppliedLayer, refuseOutOfContractDecision, screenApproverAttribution, toolPolicyNameSets } from "../tool-policy.js";
60
+ import { mintGateOutcome } from "./gate-exit.js";
61
+ import { MCP_DELIVERY_VERDICTS } from "../mcp-failure.js";
55
62
  import { defaultTaskRegistry } from "../task-registry.js";
56
63
  import { discloseDroppedPending, isDelegatedAgentTerminal, isSystemInjectionPriority, isTerminalTaskNotification, PendingSessionNotifications, renderTaskNotificationXml, SYSTEM_INJECTION_PRIORITIES, SystemInjectionQueue, taskNotificationDedupKey } from "../task-notification.js";
57
64
  import { ToolDetachHub } from "../tool-detach.js";
@@ -147,23 +154,23 @@ function resumeDecisionWasNegative(resume) {
147
154
  }
148
155
  const DEFERRED_REISSUE = "[DEFERRED] This tool call shared a batch with a call that suspended for durable approval, so it was " +
149
156
  "NOT executed on resume. If you still need it, issue it again now.";
150
- function toolEndBodyFrom(result, isError, settledBy, approver, resolution, gatedCallIdOfRun, autoDenied) {
157
+ function toolEndBodyFrom(result, isError, gate, gatedCallIdOfRun) {
151
158
  const o = toolOutputFrom(result);
152
159
  const st = structuredFrom(result);
153
160
  const det = isError ? result?.details : undefined;
154
161
  const codeRaw = det?.code;
155
162
  const kindRaw = det?.errorKind;
156
163
  const code = typeof codeRaw === "string" ? codeRaw : typeof kindRaw === "string" ? kindRaw : undefined;
164
+ const deliveredRaw = det?.delivered;
165
+ const delivered = typeof deliveredRaw === "string" && MCP_DELIVERY_VERDICTS.includes(deliveredRaw) ? deliveredRaw : undefined;
157
166
  const gatedCallId = code === "gate.parked" ? gatedCallIdOfRun : undefined;
158
167
  return {
159
168
  ...(o !== undefined ? { output: o.output, ...(o.truncated ? { truncated: true } : {}), ...(o.totalChars !== undefined ? { totalChars: o.totalChars } : {}) } : {}),
160
169
  ...(st !== undefined ? { structured: st } : {}),
161
170
  ...(typeof code === "string" ? { errorCode: code } : {}),
171
+ ...(delivered !== undefined ? { delivered } : {}),
162
172
  ...(gatedCallId !== undefined ? { gatedCallId } : {}),
163
- ...(settledBy !== undefined ? { settledBy } : {}),
164
- ...(approver !== undefined ? { approver } : {}),
165
- ...(resolution !== undefined ? { resolution } : {}),
166
- ...(autoDenied !== undefined ? { autoDenied } : {}),
173
+ ...(gate !== undefined ? { gate } : {}),
167
174
  };
168
175
  }
169
176
  export function reconciledToolEndBody(orphan) {
@@ -393,7 +400,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
393
400
  let platformCause;
394
401
  let platformRetryAfterMs;
395
402
  const preemptWillSuspend = preemptHit && prepared.suspendForResource !== undefined;
396
- if (!preemptWillSuspend && prepared.suspendRef.token === undefined && !prepared.abortController.signal.aborted) {
403
+ if (!preemptWillSuspend && prepared.pausedRef.current === undefined && !prepared.abortController.signal.aborted) {
397
404
  const ledgerReadDeadline = prepared.envLifetimeSuspendAt !== undefined ? prepared.envLifetimeSuspendAt + ENV_DUE_GOVERNANCE_READ_BUDGET_MS : undefined;
398
405
  if (prepared.usageGovernance !== undefined) {
399
406
  const governance = prepared.usageGovernance;
@@ -461,7 +468,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
461
468
  }
462
469
  if (prepared.suspendForResource &&
463
470
  (rs.limits.turnsExceeded || rs.limits.budgetHit !== undefined || walltimeHit || preemptHit) &&
464
- prepared.suspendRef.token === undefined) {
471
+ prepared.pausedRef.current === undefined) {
465
472
  const reason = preemptHit
466
473
  ? "preempt"
467
474
  : rs.limits.budgetHit !== undefined
@@ -540,8 +547,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
540
547
  prepared.reviewRequestRef.pending = undefined;
541
548
  const honorable = prepared.suspendForReview !== undefined &&
542
549
  !prepared.abortController.signal.aborted &&
543
- prepared.suspendRef.token === undefined &&
544
- prepared.reviewRef.token === undefined;
550
+ prepared.pausedRef.current === undefined;
545
551
  if (honorable && prepared.suspendForReview) {
546
552
  await prepared.suspendForReview(reason);
547
553
  return undefined;
@@ -838,8 +844,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
838
844
  const forceManual = manualCompactRef.requested;
839
845
  if ((!withinTaskCompaction && !forceManual) ||
840
846
  prepared.abortController.signal.aborted ||
841
- prepared.suspendRef.token !== undefined ||
842
- prepared.reviewRef.token !== undefined) {
847
+ prepared.pausedRef.current !== undefined) {
843
848
  if (forceManual) {
844
849
  emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.mooted", version: 1, taskId: rs.telemetry.taskId, reason: "task_ending", ts: Date.now() }));
845
850
  queue.push({ type: "compaction_outcome", outcome: "mooted", trigger: "manual", reason: "task_ending", ...ident() });
@@ -1386,16 +1391,16 @@ function makeHarnessHandlers(prepared, stats, rs, deps) {
1386
1391
  prepared.lspDiagnostics.nudge(p);
1387
1392
  }
1388
1393
  }
1389
- const settlement = prepared.approvalSettlement.get(event.toolCallId);
1390
- if (settlement !== undefined)
1391
- prepared.approvalSettlement.delete(event.toolCallId);
1394
+ const gate = prepared.gateOutcomes.get(event.toolCallId);
1395
+ if (gate !== undefined)
1396
+ prepared.gateOutcomes.delete(event.toolCallId);
1392
1397
  pushContent({
1393
1398
  type: "tool_end",
1394
1399
  toolCallId: event.toolCallId,
1395
1400
  toolName: event.toolName,
1396
1401
  ...(toolLabels.get(event.toolName) !== undefined ? { label: toolLabels.get(event.toolName) } : {}),
1397
1402
  isError: event.isError,
1398
- ...toolEndBodyFrom(event.result, event.isError, settlement?.settledBy, settlement?.approver, settlement?.resolution, prepared.abortController.signal.aborted ? gatedCallIdOf(prepared) : undefined, settlement?.autoDenied),
1403
+ ...toolEndBodyFrom(event.result, event.isError, gate, prepared.abortController.signal.aborted ? gatedCallIdOf(prepared) : undefined),
1399
1404
  ...ident(),
1400
1405
  });
1401
1406
  announceWorkspaceMove();
@@ -1477,6 +1482,11 @@ function makeHarnessHandlers(prepared, stats, rs, deps) {
1477
1482
  };
1478
1483
  return { onMessageUpdate, onMessageEnd, onToolStart, onToolEnd, onTurnEnd, announceWorkspaceMove };
1479
1484
  }
1485
+ const REFUSED_DECIDE_KEYS = [
1486
+ ["gateOutcome", "the settlement record is minted by core from the host's decision facts and the row's origin, never supplied"],
1487
+ ["settledBy", 'a retired key — the host supplies decision FACTS as hostDecision{decidedBy: "person" | "sla_timeout", approver?} and core mints the settlement word from them'],
1488
+ ["approver", "a retired key — the attribution rides the facts it belongs to, as hostDecision.approver"],
1489
+ ];
1480
1490
  export class Runner {
1481
1491
  deps;
1482
1492
  sessions;
@@ -1698,17 +1708,18 @@ export class Runner {
1698
1708
  const pcs = internals?.inheritedGate?.parentConstraints;
1699
1709
  if (resume !== undefined)
1700
1710
  this.locallyClaimedTokens.delete(resume.cp.token);
1701
- if (resume !== undefined && r.checkpointToken !== resume.cp.token) {
1711
+ const rToken = r.terminal.kind === "paused" ? r.terminal.token : undefined;
1712
+ if (resume !== undefined && rToken !== resume.cp.token) {
1702
1713
  this.parentConstraintRegistry.delete(resume.cp.token);
1703
1714
  this.suspendedEnvReaps.delete(resume.cp.token);
1704
1715
  }
1705
- if (r.status === "suspended" && r.checkpointToken !== undefined && pcs !== undefined && pcs.length > 0) {
1716
+ if (r.terminal.kind === "paused" && PAUSE_REGISTRY[r.terminal.gate.kind].taskStatus === "suspended" && pcs !== undefined && pcs.length > 0) {
1706
1717
  if (this.parentConstraintRegistry.size >= Runner.PARENT_CONSTRAINT_REGISTRY_CAP) {
1707
1718
  const oldest = this.parentConstraintRegistry.keys().next().value;
1708
1719
  if (oldest !== undefined)
1709
1720
  this.parentConstraintRegistry.delete(oldest);
1710
1721
  }
1711
- this.parentConstraintRegistry.set(r.checkpointToken, pcs);
1722
+ this.parentConstraintRegistry.set(r.terminal.token, pcs);
1712
1723
  }
1713
1724
  }, (p) => {
1714
1725
  suggestionsDone = p;
@@ -1790,10 +1801,8 @@ export class Runner {
1790
1801
  taskId: taskIdRef.current ?? "unknown",
1791
1802
  sessionId: taskIdRef.sessionId ?? "unknown",
1792
1803
  ...(taskIdRef.runId !== undefined ? { runId: taskIdRef.runId } : {}),
1793
- status: "failed",
1804
+ terminal: { kind: "failed", ...(code !== undefined ? { code } : {}), message: err instanceof Error ? err.message : String(err) },
1794
1805
  result: "",
1795
- errorMessage: err instanceof Error ? err.message : String(err),
1796
- errorCode: code,
1797
1806
  ...(remoteEnvFailure !== undefined ? { remoteEnvFailures: remoteEnvFailure } : {}),
1798
1807
  ...(taskIdRef.effectiveMemoryScopes !== undefined ? { effectiveMemoryScopes: taskIdRef.effectiveMemoryScopes } : {}),
1799
1808
  ...(taskIdRef.effectiveReasoning !== undefined ? { effectiveReasoning: taskIdRef.effectiveReasoning } : {}),
@@ -2404,7 +2413,7 @@ export class Runner {
2404
2413
  prepared.harness.engineInjectionsHeld = () => prepared.batchHaltRef.current !== undefined;
2405
2414
  let undrainedUserAtEnd;
2406
2415
  prepared.harness.onUndrainedUserInputs = (counts) => {
2407
- if (prepared.suspendRef.token !== undefined || prepared.reviewRef.token !== undefined) {
2416
+ if (prepared.pausedRef.current !== undefined) {
2408
2417
  undrainedUserAtEnd = counts;
2409
2418
  return;
2410
2419
  }
@@ -3059,7 +3068,7 @@ export class Runner {
3059
3068
  if (stopHook || finalVerificationOn) {
3060
3069
  let consecutiveBlocks = 0;
3061
3070
  prepared.harness.setStopGate(async () => {
3062
- if (prepared.abortController.signal.aborted || prepared.suspendRef.token !== undefined || prepared.batchHaltRef.current !== undefined)
3071
+ if (prepared.abortController.signal.aborted || prepared.pausedRef.current !== undefined || prepared.batchHaltRef.current !== undefined)
3063
3072
  return [];
3064
3073
  if (finalVerificationOn &&
3065
3074
  (rs.counters.finalVerifyInjections === 0 || (rs.counters.finalVerifyInjections === 1 && rs.counters.groundingSignalPreR9 && !rs.counters.groundingSignalPostR9)) &&
@@ -3300,7 +3309,7 @@ export class Runner {
3300
3309
  degenerateOutput: { detect: (m) => isDegenerateCutMessage(m) },
3301
3310
  promptTooLong: {
3302
3311
  recover: async (attempt, turnSignal) => {
3303
- if (prepared.abortController.signal.aborted || prepared.suspendRef.token !== undefined)
3312
+ if (prepared.abortController.signal.aborted || prepared.pausedRef.current !== undefined)
3304
3313
  return false;
3305
3314
  if (turnSignal?.aborted === true)
3306
3315
  return false;
@@ -3345,7 +3354,7 @@ export class Runner {
3345
3354
  },
3346
3355
  });
3347
3356
  prepared.microCompact.inTurnCompactionRef.current = async (turnSignal, anchoredEstimate) => {
3348
- if (prepared.abortController.signal.aborted || prepared.suspendRef.token !== undefined)
3357
+ if (prepared.abortController.signal.aborted || prepared.pausedRef.current !== undefined)
3349
3358
  return false;
3350
3359
  if (turnSignal?.aborted === true)
3351
3360
  return false;
@@ -3926,9 +3935,8 @@ export class Runner {
3926
3935
  this.deps.onError?.(flushErr, { phase: "config", sessionId: prepared.sessionId });
3927
3936
  }
3928
3937
  }
3929
- const suspended = prepared.suspendRef.token !== undefined && prepared.suspendRef.gate !== undefined;
3930
- const reviewPaused = prepared.reviewRef.token !== undefined && prepared.reviewRef.gate !== undefined;
3931
- const durablyPaused = suspended || reviewPaused;
3938
+ const committedPause = prepared.pausedRef.current;
3939
+ const durablyPaused = committedPause !== undefined;
3932
3940
  const interrupted = !durablyPaused &&
3933
3941
  (threw !== undefined || timeout.fired || rs.limits.turnsExceeded || rs.limits.budgetHit !== undefined || abortedLive || final?.stopReason === "aborted");
3934
3942
  let orphansClosed = 0;
@@ -3972,9 +3980,9 @@ export class Runner {
3972
3980
  }
3973
3981
  }
3974
3982
  let migratedParked = { steer: 0, followUp: 0 };
3975
- const parkToken = prepared.suspendRef.token ?? prepared.reviewRef.token;
3976
- const parkScope = prepared.suspendRef.token !== undefined ? prepared.suspendRef.scope : prepared.reviewRef.scope;
3977
- if (durablyPaused && parkToken !== undefined && parkScope !== undefined && this.deps.checkpointStore !== undefined) {
3983
+ if (committedPause !== undefined && this.deps.checkpointStore !== undefined) {
3984
+ const parkToken = committedPause.cause.token;
3985
+ const parkScope = committedPause.scope;
3978
3986
  const store = this.deps.checkpointStore;
3979
3987
  const carried = [];
3980
3988
  for (const record of prepared.harness.readParkableUserInputs()) {
@@ -4067,7 +4075,7 @@ export class Runner {
4067
4075
  }
4068
4076
  }
4069
4077
  if (prepared.resourceLedger &&
4070
- (resume?.cp.gate.kind === "resource_limit" || prepared.suspendRef.gate?.kind === "resource_limit")) {
4078
+ (resume?.cp.gate.kind === "resource_limit" || prepared.pausedRef.current?.cause.gate.kind === "resource_limit")) {
4071
4079
  stats.costMicroUsd += prepared.resourceLedger.spentMicroUsd;
4072
4080
  stats.tokens += prepared.resourceLedger.spentTokens;
4073
4081
  stats.turns += prepared.resourceLedger.spentTurns;
@@ -4118,46 +4126,26 @@ export class Runner {
4118
4126
  gitCoreOverBudget: prepared.gitStatusRef.terminalCode === "irreducible_core_over_budget",
4119
4127
  outputInvalid: rs.degrade.outputInvalid,
4120
4128
  suspendLoop: prepared.suspendLoopRef.hit,
4121
- suspendRef: prepared.suspendRef.token !== undefined && prepared.suspendRef.gate !== undefined
4122
- ? {
4123
- token: prepared.suspendRef.token,
4124
- ...(prepared.suspendRef.checkpointId !== undefined ? { checkpointId: prepared.suspendRef.checkpointId } : {}),
4125
- gate: prepared.suspendRef.gate,
4126
- ...(prepared.suspendRef.restoreMode !== undefined ? { restoreMode: prepared.suspendRef.restoreMode } : {}),
4127
- }
4128
- : undefined,
4129
- reviewRef: prepared.reviewRef.token !== undefined && prepared.reviewRef.gate !== undefined
4130
- ? {
4131
- token: prepared.reviewRef.token,
4132
- ...(prepared.reviewRef.checkpointId !== undefined ? { checkpointId: prepared.reviewRef.checkpointId } : {}),
4133
- gate: prepared.reviewRef.gate,
4134
- ...(prepared.reviewRef.restoreMode !== undefined ? { restoreMode: prepared.reviewRef.restoreMode } : {}),
4135
- }
4136
- : undefined,
4129
+ ...(prepared.pausedRef.current !== undefined ? { paused: prepared.pausedRef.current.cause } : {}),
4137
4130
  });
4138
4131
  if (resume !== undefined &&
4139
4132
  resume.pendingActionStarted !== true &&
4140
- result.status !== "completed" &&
4141
- result.status !== "suspended" &&
4142
- result.status !== "needs_review" &&
4133
+ result.terminal.kind !== "completed" &&
4134
+ result.terminal.kind !== "paused" &&
4143
4135
  !(resumeDecisionWasNegative(resume) && resume.decisionDelivered === true) &&
4144
4136
  resume.onEnvRestoreFailed !== undefined) {
4145
4137
  try {
4146
4138
  await resume.onEnvRestoreFailed("env_failed");
4147
- const r = result;
4148
- r.errorCode ??= "resume.reopened_unstarted";
4149
- r.errorMessage = `${r.errorMessage ?? "the resume leg did not complete"} — the approved action never started, so the checkpoint was REOPENED (pending): the same approval can be resumed again, do not ask for a fresh one`;
4139
+ amendTerminal(result, "resume.reopened_unstarted", (m) => `${m ?? "the resume leg did not complete"} — the approved action never started, so the checkpoint was REOPENED (pending): the same approval can be resumed again, do not ask for a fresh one`);
4150
4140
  }
4151
4141
  catch {
4152
- const r = result;
4153
- r.errorCode ??= "resume.reopen_failed_unstarted";
4154
- r.errorMessage = `${r.errorMessage ?? "the resume leg did not complete"} — the approved action never started, and reopening the checkpoint FAILED: its state is unprovable from here. Re-read it before deciding; do NOT issue a fresh approval on the assumption the old one is dead`;
4142
+ amendTerminal(result, "resume.reopen_failed_unstarted", (m) => `${m ?? "the resume leg did not complete"} — the approved action never started, and reopening the checkpoint FAILED: its state is unprovable from here. Re-read it before deciding; do NOT issue a fresh approval on the assumption the old one is dead`);
4155
4143
  }
4156
4144
  }
4157
4145
  prepared.sealReadStateSeat?.(ownCommittedTailRef.current);
4158
4146
  {
4159
4147
  const wss = prepared.workspaceStateSettle;
4160
- if (wss !== undefined && result.status !== "suspended" && result.status !== "needs_review") {
4148
+ if (wss !== undefined && result.terminal.kind !== "paused") {
4161
4149
  const curCwd = prepared.cwdRef?.current !== undefined && prepared.cwdRef.current !== wss.rootCanonical ? prepared.cwdRef.current : undefined;
4162
4150
  const curWt = prepared.worktreeSessionRef?.current;
4163
4151
  const changed = curCwd !== wss.restoredHandsCwd || curWt?.worktreeDir !== wss.restoredWorktreeDir;
@@ -4172,7 +4160,7 @@ export class Runner {
4172
4160
  }
4173
4161
  const stopFailureHook = (spec.hooks ?? this.deps.hooks)?.stopFailure;
4174
4162
  if (stopFailureHook !== undefined &&
4175
- result.status === "failed" &&
4163
+ result.terminal.kind === "failed" &&
4176
4164
  final?.stopReason === "error" &&
4177
4165
  !isDegenerateCutMessage(final) &&
4178
4166
  threw === undefined &&
@@ -4180,12 +4168,13 @@ export class Runner {
4180
4168
  !rs.degrade.outputInvalid &&
4181
4169
  !prepared.suspendLoopRef.hit &&
4182
4170
  !abortedLive &&
4183
- result.errorCode !== "conflict") {
4171
+ result.terminal.code !== "conflict") {
4172
+ const failedCause = result.terminal;
4184
4173
  try {
4185
4174
  const failureSeat = await runHookSeat("stopFailure", { timeoutMs: prepared.hookTimeoutMs }, (sig) => stopFailureHook({
4186
4175
  identity: prepared.hookIdentity,
4187
- error: result.errorMessage ?? "model error",
4188
- ...(result.errorCode !== undefined ? { errorKind: result.errorCode } : {}),
4176
+ error: failedCause.message ?? "model error",
4177
+ ...(failedCause.code !== undefined ? { errorKind: failedCause.code } : {}),
4189
4178
  turns: stats.turns,
4190
4179
  signal: sig,
4191
4180
  }));
@@ -4205,12 +4194,13 @@ export class Runner {
4205
4194
  }
4206
4195
  }
4207
4196
  }
4197
+ const terminalFace = terminalProjection(result.terminal);
4208
4198
  emitDelegationLifecycle({
4209
4199
  phase: "terminal",
4210
4200
  identity: prepared.hookIdentity,
4211
- status: result.status,
4201
+ status: terminalFace.status,
4212
4202
  turns: stats.turns,
4213
- ...(result.errorCode !== undefined ? { errorCode: result.errorCode } : {}),
4203
+ ...(terminalFace.errorCode !== undefined ? { errorCode: terminalFace.errorCode } : {}),
4214
4204
  });
4215
4205
  if (taskIdRef !== undefined)
4216
4206
  taskIdRef.delegationTerminalOwed = undefined;
@@ -4218,8 +4208,8 @@ export class Runner {
4218
4208
  result.degraded = rs.degrade.degraded;
4219
4209
  if (prepared.outputRef.set)
4220
4210
  result.structuredOutput = prepared.outputRef.value;
4221
- const committedToken = prepared.suspendRef.token ?? prepared.reviewRef.token;
4222
- const committedScope = prepared.suspendRef.scope ?? prepared.reviewRef.scope;
4211
+ const committedToken = prepared.pausedRef.current?.cause.token;
4212
+ const committedScope = prepared.pausedRef.current?.scope;
4223
4213
  if (committedToken !== undefined) {
4224
4214
  const store = resolveCheckpointStore(spec, this.deps);
4225
4215
  if (store && committedScope !== undefined) {
@@ -4244,8 +4234,8 @@ export class Runner {
4244
4234
  version: 1,
4245
4235
  taskId: rs.telemetry.taskId,
4246
4236
  runId: rs.telemetry.runId,
4247
- status: result.status,
4248
- errorCode: result.errorCode,
4237
+ status: terminalFace.status,
4238
+ errorCode: terminalFace.errorCode,
4249
4239
  turns: stats.turns,
4250
4240
  tokens: stats.tokens,
4251
4241
  ...(rs.telemetry.unpricedSpend ? {} : { costMicroUsd: stats.costMicroUsd }),
@@ -4261,7 +4251,7 @@ export class Runner {
4261
4251
  ts: Date.now(),
4262
4252
  }));
4263
4253
  setResult(result);
4264
- if (parentToolCallId !== undefined && (result.status === "completed" || result.status === "failed" || result.status === "blocked")) {
4254
+ if (parentToolCallId !== undefined && result.terminal.kind !== "paused") {
4265
4255
  const terminalTick = {
4266
4256
  type: "task_progress",
4267
4257
  taskId: rs.telemetry.taskId,
@@ -4271,7 +4261,7 @@ export class Runner {
4271
4261
  ...(subagentName ? { name: subagentName } : {}),
4272
4262
  model: prepared.model.id,
4273
4263
  usage: { totalTokens: stats.tokens, toolUses: stats.toolCalls, durationMs: Date.now() - rs.telemetry.taskStart },
4274
- status: result.status === "completed" ? "completed" : "failed",
4264
+ status: result.terminal.kind === "completed" ? "completed" : "failed",
4275
4265
  ...ident(),
4276
4266
  };
4277
4267
  queue.push(terminalTick);
@@ -4289,7 +4279,7 @@ export class Runner {
4289
4279
  queue.push({ type: "done", result });
4290
4280
  queue.close();
4291
4281
  await prepared.fileHistoryBoundary?.settle();
4292
- if (spec.suggestNextPrompts && result.status === "completed") {
4282
+ if (spec.suggestNextPrompts && result.terminal.kind === "completed") {
4293
4283
  onSuggestions(this.suggestNextPrompts(spec, prepared, result));
4294
4284
  }
4295
4285
  }
@@ -4297,15 +4287,13 @@ export class Runner {
4297
4287
  const ownedEnvDying = prepared.ownedEnv !== undefined &&
4298
4288
  hasDestroy(prepared.ownedEnv) &&
4299
4289
  isIsolated(prepared.ownedEnv) &&
4300
- prepared.suspendRef.token === undefined &&
4301
- prepared.reviewRef.token === undefined;
4290
+ prepared.pausedRef.current === undefined;
4302
4291
  await settleTeardownLeg(() => defaultTaskRegistry.settleKilledForOwner({ owner: spec.taskId ?? prepared.sessionId, scope: spec.principal ?? "default", sessionId: prepared.sessionId }, {
4303
4292
  source: abortedLive &&
4304
4293
  !timeout.fired &&
4305
4294
  !rs.limits.turnsExceeded &&
4306
4295
  rs.limits.budgetHit === undefined &&
4307
- prepared.suspendRef.token === undefined &&
4308
- prepared.reviewRef.token === undefined
4296
+ prepared.pausedRef.current === undefined
4309
4297
  ? "user"
4310
4298
  : "parent",
4311
4299
  skipSessionScoped: true,
@@ -4485,6 +4473,7 @@ export class Runner {
4485
4473
  break;
4486
4474
  case "tool_approval":
4487
4475
  case "resource_limit":
4476
+ case "review":
4488
4477
  case "plan_review": {
4489
4478
  throw new CheckpointError("wake.gate_pending", `cannot wake: this checkpoint still carries a pending gate-typed action (pendingAction "${pa.kind}" ` +
4490
4479
  `on a "${gk}" gate — inconsistent row) — wake is not a decision channel; resolve it through its ` +
@@ -4505,14 +4494,12 @@ export class Runner {
4505
4494
  "wake would burn the checkpoint on a blank continuation; supply `message` or park a steer first");
4506
4495
  }
4507
4496
  }
4508
- const gateMatch = outcomeGate === "wake" ||
4509
- (cp.gate.kind === "human" && outcomeGate === "policy_ask") ||
4510
- (cp.gate.kind === "irreversible_ask" && outcomeGate === "policy_ask") ||
4511
- (cp.gate.kind === "resource_limit" && outcomeGate === "resource_limit") ||
4512
- (cp.gate.kind === "needs_review" && outcomeGate === "dry_run_review") ||
4513
- (cp.gate.kind === "plan_review" && outcomeGate === "plan_review");
4497
+ const gateMatch = isGateKind(cp.gate.kind) && resumeGateMatches(cp.gate.kind, outcomeGate);
4514
4498
  if (!gateMatch) {
4515
- throw new CheckpointError("checkpoint.gate_mismatch", `resume outcome (gate "${describeSuppliedValue(outcomeGate)}") does not match checkpoint gate "${cp.gate.kind}" — resume serves human/policy_ask, irreversible_ask/policy_ask, resource_limit/resource_limit, needs_review/dry_run_review, and plan_review/plan_review`);
4499
+ throw new CheckpointError("checkpoint.gate_mismatch", `resume outcome (gate "${describeSuppliedValue(outcomeGate)}") does not match checkpoint gate "${cp.gate.kind}" — resume serves ${Object.entries(PAUSE_REGISTRY)
4500
+ .filter(([, row]) => !row.terminal)
4501
+ .map(([k, row]) => `${k}/${row.resume}`)
4502
+ .join(", ")}`);
4516
4503
  }
4517
4504
  let plainReviewOutcome;
4518
4505
  if (cp.gate.kind === "plan_review") {
@@ -4619,25 +4606,29 @@ export class Runner {
4619
4606
  if (decision !== "allow" && decision !== "deny") {
4620
4607
  throw new CheckpointError("checkpoint.invalid_outcome", `resume decision "${describeSuppliedValue(decision)}" is outside the policy_ask domain — a decide is exactly "allow" or "deny"; refusing pre-CAS, the checkpoint stays pending`);
4621
4608
  }
4622
- const settledBy = decide.settledBy;
4623
- if (settledBy !== undefined && !isApprovalSettledBy(settledBy)) {
4624
- throw new CheckpointError("checkpoint.invalid_outcome", `resume settledBy "${describeSuppliedValue(settledBy)}" is outside the settlement vocabulary it is exactly "human", "timeout" or "aborted" (or omitted); refusing pre-CAS, the checkpoint stays pending`, { field: "settledBy" });
4609
+ for (const [key, why] of REFUSED_DECIDE_KEYS) {
4610
+ if (Reflect.get(decide, key) !== undefined) {
4611
+ throw new CheckpointError("checkpoint.invalid_outcome", `resume carries \`${key}\`${why}; refusing pre-CAS, the checkpoint stays pending`, { field: "hostDecision" });
4612
+ }
4613
+ }
4614
+ const hostDecisionRaw = decide.hostDecision;
4615
+ if (typeof hostDecisionRaw !== "object" || hostDecisionRaw === null) {
4616
+ throw new CheckpointError("checkpoint.invalid_outcome", `resume carries no hostDecision — a policy_ask decide names who decided it ({ decidedBy: "person" | "sla_timeout", approver? }); refusing pre-CAS, the checkpoint stays pending`, { field: "hostDecision" });
4617
+ }
4618
+ const decidedBy = hostDecisionRaw.decidedBy;
4619
+ if (decidedBy !== "person" && decidedBy !== "sla_timeout") {
4620
+ throw new CheckpointError("checkpoint.invalid_outcome", `resume hostDecision.decidedBy "${describeSuppliedValue(decidedBy)}" is outside the closed set — it is exactly "person" or "sla_timeout"; refusing pre-CAS, the checkpoint stays pending`, { field: "hostDecision" });
4625
4621
  }
4626
- if (settledBy !== undefined && settledBy !== "human" && decision === "allow") {
4627
- throw new CheckpointError("checkpoint.invalid_outcome", `resume carries decision "allow" with settledBy "${settledBy}" — a non-human settlement is a fail-closed end (nobody answered), so it cannot be the source of an approval that EXECUTES; ` +
4628
- "supply \"human\", or omit the field if the allow came from configuration rather than a person; refusing pre-CAS, the checkpoint stays pending", { field: "settledBy" });
4622
+ if (decidedBy === "sla_timeout" && decision === "allow") {
4623
+ throw new CheckpointError("checkpoint.invalid_outcome", `resume carries decision "allow" decided by "sla_timeout" — an elapsed SLA window cannot be what approved an action that EXECUTES; refusing pre-CAS, the checkpoint stays pending`, { field: "hostDecision" });
4629
4624
  }
4630
- const approver = decide.approver;
4631
- const attribution = screenApproverAttribution(approver);
4625
+ const attribution = screenApproverAttribution(hostDecisionRaw.approver);
4632
4626
  if (attribution.defect !== undefined) {
4633
4627
  throw new CheckpointError("checkpoint.invalid_outcome", `resume carries an approver attribution this engine refuses: ${attribution.defect}; refusing pre-CAS, the checkpoint stays pending`, { field: "approver" });
4634
4628
  }
4635
- if (attribution.approver !== undefined && settledBy === undefined) {
4636
- throw new CheckpointError("checkpoint.invalid_outcome", `resume names an approver but no settledBy — an attribution says WHOSE settlement it was and needs the word that says what KIND of end it was beside it; ` +
4637
- `supply settledBy ("human" / "timeout" / "aborted"), or omit the approver; refusing pre-CAS, the checkpoint stays pending`, { field: "approver" });
4638
- }
4639
4629
  const reason = decide.reason;
4640
4630
  assertOutcomeText(reason, "reason");
4631
+ const approverCell = attribution.approver !== undefined ? { approver: attribution.approver } : {};
4641
4632
  plainPolicyOutcome = {
4642
4633
  gate: "policy_ask",
4643
4634
  boundCallId: decide.boundCallId,
@@ -4646,8 +4637,7 @@ export class Runner {
4646
4637
  ...(updatedInput !== undefined ? { updatedInput } : {}),
4647
4638
  ...(reason !== undefined ? { reason } : {}),
4648
4639
  ...(redeemedAnswer !== undefined ? { answer: redeemedAnswer } : {}),
4649
- ...(settledBy !== undefined ? { settledBy } : {}),
4650
- ...(attribution.approver !== undefined ? { approver: attribution.approver } : {}),
4640
+ hostDecision: { decidedBy, ...approverCell },
4651
4641
  };
4652
4642
  if (plainPolicyOutcome.decision === "deny" && plainPolicyOutcome.reason && sanitizeUntrustedText(plainPolicyOutcome.reason) !== plainPolicyOutcome.reason) {
4653
4643
  throw new CheckpointError("checkpoint.invalid_outcome", "resume deny/reject reason must not contain a </system-reminder> tag");
@@ -4701,11 +4691,7 @@ export class Runner {
4701
4691
  throw new CheckpointError("checkpoint.unsupported_version", `checkpoint version ${checkpointVersionOf(cp)} is newer than this worker supports (max ${MAX_SUPPORTED_CHECKPOINT_VERSION})`, { reason: "version_newer" });
4702
4692
  }
4703
4693
  const preCasGateBit = cp.gate.kind === "irreversible_ask" ? cp.gate.realApproval : undefined;
4704
- const preCasBitWellFormed = preCasGateBit !== undefined &&
4705
- typeof preCasGateBit === "object" &&
4706
- (preCasGateBit.origin === "org_rule" ||
4707
- preCasGateBit.origin === "org_unavailable" ||
4708
- preCasGateBit.origin === "policy");
4694
+ const preCasBitWellFormed = preCasGateBit !== undefined && typeof preCasGateBit === "object" && isAskOrigin(preCasGateBit.origin);
4709
4695
  const faceSaysGoverned = (cp.state.readFace?.realApproval) === true;
4710
4696
  const preCasBitViolation = checkpointVersionOf(cp) === REAL_APPROVAL_CHECKPOINT_VERSION
4711
4697
  ? !preCasBitWellFormed
@@ -4716,7 +4702,7 @@ export class Runner {
4716
4702
  : preCasGateBit !== undefined && !preCasBitWellFormed;
4717
4703
  if (preCasBitViolation) {
4718
4704
  throw new CheckpointError("checkpoint.invalid_outcome", checkpointVersionOf(cp) >= REAL_APPROVAL_CHECKPOINT_VERSION
4719
- ? `a v${checkpointVersionOf(cp)} checkpoint's realApproval gate bit must be well-formed (origin org_rule/org_unavailable/policy)${checkpointVersionOf(cp) === REAL_APPROVAL_CHECKPOINT_VERSION ? " and present on an irreversible_ask gate" : ""} — this row's is not; refusing to resume a damaged real-approval row (corruption / downgrade guard), the checkpoint stays pending`
4705
+ ? `a v${checkpointVersionOf(cp)} checkpoint's realApproval gate bit must be well-formed (origin a member of the closed ask-origin set)${checkpointVersionOf(cp) === REAL_APPROVAL_CHECKPOINT_VERSION ? " and present on an irreversible_ask gate" : ""} — this row's is not; refusing to resume a damaged real-approval row (corruption / downgrade guard), the checkpoint stays pending`
4720
4706
  : `a v${checkpointVersionOf(cp)} checkpoint carries a realApproval gate bit no release of that version ever minted — refusing to honor a fabricated origin (corruption / forgery guard), the checkpoint stays pending`, { reason: checkpointVersionOf(cp) >= REAL_APPROVAL_CHECKPOINT_VERSION ? "real_approval_damaged" : "real_approval_forged" });
4721
4707
  }
4722
4708
  {
@@ -4736,9 +4722,8 @@ export class Runner {
4736
4722
  : `a v${checkpointVersionOf(cp)} checkpoint carries a read-face section no release of that version ever minted — refusing to honor a fabricated read-face state (corruption / forgery guard), the checkpoint stays pending`, { reason: checkpointVersionOf(cp) >= FACE_CHECKPOINT_VERSION ? "read_face_damaged" : "read_face_forged" });
4737
4723
  }
4738
4724
  }
4739
- if ((preCasGateBit?.origin === "org_rule" || preCasGateBit?.origin === "org_unavailable") &&
4740
- this.deps.permissionRuleStore?.partitions.org !== true) {
4741
- throw new CheckpointError("checkpoint.unsupported_version", `this checkpoint's approval was minted under organization governance (${preCasGateBit.origin}) and this worker has no org adjudication wiring (its permission-rule store carries no org partition) — a governed approval may only be redeemed where governance can be enforced; the checkpoint stays pending, resume it on an org-wired worker`, { reason: "governed_unwired" });
4725
+ if (realApprovalOrgFact(preCasGateBit) !== undefined && this.deps.permissionRuleStore?.partitions.org !== true) {
4726
+ throw new CheckpointError("checkpoint.unsupported_version", `this checkpoint's approval was minted under organization governance (org ${String(realApprovalOrgFact(preCasGateBit))}, routed as ${String(preCasGateBit?.origin)}) and this worker has no org adjudication wiring (its permission-rule store carries no org partition) — a governed approval may only be redeemed where governance can be enforced; the checkpoint stays pending, resume it on an org-wired worker`, { reason: "governed_unwired" });
4742
4727
  }
4743
4728
  const retiredWalltimeTotal = cp.resourceLedger?.totalWalltimeSec;
4744
4729
  if (retiredWalltimeTotal !== undefined) {
@@ -4995,6 +4980,18 @@ export class Runner {
4995
4980
  }
4996
4981
  }
4997
4982
  }
4983
+ if (plainPolicyOutcome !== undefined) {
4984
+ const parkedOrigin = cp.pendingAction.kind === "tool_approval" ? cp.pendingAction.origin : undefined;
4985
+ if (!isAskOrigin(parkedOrigin)) {
4986
+ throw new CheckpointError("checkpoint.unsupported_version", "this checkpoint's pending approval carries no origin word (a row minted before the ask's origin was persisted on the park) — its settlement cannot be recorded; the checkpoint stays pending");
4987
+ }
4988
+ const facts = plainPolicyOutcome.hostDecision;
4989
+ const approverCell = facts.approver !== undefined ? { approver: facts.approver } : {};
4990
+ const settlement = facts.decidedBy === "sla_timeout"
4991
+ ? { kind: "park_sla_expired", who: { party: "host", ...approverCell }, when: Date.now() }
4992
+ : { kind: plainPolicyOutcome.decision === "allow" ? "human_allowed" : "human_refused", who: { party: "person", ...approverCell }, when: Date.now(), ...(plainPolicyOutcome.decision === "deny" && plainPolicyOutcome.reason !== undefined ? { note: plainPolicyOutcome.reason } : {}) };
4993
+ plainPolicyOutcome = { ...plainPolicyOutcome, gateOutcome: mintGateOutcome({ ...(plainPolicyOutcome.decision === "deny" ? { deniedBy: "ask_resolution" } : {}), settled: { settlement, origin: parkedOrigin } }) };
4994
+ }
4998
4995
  const outcomeForStore = plainPolicyOutcome ?? plainReviewOutcome ?? plainParkOutcome;
4999
4996
  if (outcomeForStore === undefined) {
5000
4997
  throw new CheckpointError("checkpoint.invalid_outcome", `resume outcome (gate "${describeSuppliedValue(outcomeGate)}") matched the checkpoint gate but no lane captured it — refusing pre-CAS rather than passing the caller's live object to the store and the resumed run`);
@@ -5237,13 +5234,15 @@ export class Runner {
5237
5234
  const resolvedArgs = pendingAction.toolName === ASK_USER_QUESTION_TOOL_NAME ? pendingAction.args : (outcome.updatedInput !== undefined ? outcome.updatedInput : pendingAction.args);
5238
5235
  const pendingLabel = (() => { const l = prepared.tools.find((t) => t.name === pendingAction.toolName)?.label; return l !== undefined && l !== pendingAction.toolName ? { label: l } : {}; })();
5239
5236
  emit({ type: "tool_start", toolCallId: pendingAction.toolCallId, toolName: pendingAction.toolName, ...pendingLabel, args: resolvedArgs });
5240
- const emitEnd = (isError, result) => emit({ type: "tool_end", toolCallId: pendingAction.toolCallId, toolName: pendingAction.toolName, ...pendingLabel, isError, ...toolEndBodyFrom(result, isError, outcome.settledBy, outcome.approver) });
5237
+ const emitEnd = (isError, result, gate = outcome.gateOutcome) => emit({ type: "tool_end", toolCallId: pendingAction.toolCallId, toolName: pendingAction.toolName, ...pendingLabel, isError, ...toolEndBodyFrom(result, isError, gate) });
5238
+ const vetoed = (layer) => mintGateOutcome({
5239
+ deniedBy: layer,
5240
+ ...(outcome.gateOutcome?.settlement !== undefined && outcome.gateOutcome.origin !== undefined ? { settled: { settlement: outcome.gateOutcome.settlement, origin: outcome.gateOutcome.origin } } : {}),
5241
+ });
5241
5242
  if (outcome.decision === "deny") {
5242
- const defaultDenial = outcome.settledBy === "timeout"
5243
+ const defaultDenial = outcome.gateOutcome?.settlement?.kind === "park_sla_expired"
5243
5244
  ? `No one answered the approval request for the pending tool call "${pendingAction.toolName}" — the approval window elapsed with no answer, so it was not executed.`
5244
- : outcome.settledBy === "aborted"
5245
- ? `The approval for the pending tool call "${pendingAction.toolName}" ended without anyone deciding it (it was cancelled or could not be delivered), so it was not executed.`
5246
- : `The pending tool call "${pendingAction.toolName}" was denied by an approver.`;
5245
+ : `The pending tool call "${pendingAction.toolName}" was denied by an approver.`;
5247
5246
  const reason = outcome.reason ? delimitUntrusted("reviewer note", outcome.reason, REVIEWER_NOTE_MAX_BODY) : defaultDenial;
5248
5247
  emitEnd(true, { content: formatHookFeedback(reason, prepared.reminderMark) });
5249
5248
  const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, formatHookFeedback(reason, prepared.reminderMark), true));
@@ -5270,17 +5269,17 @@ export class Runner {
5270
5269
  (rechecked.updatedInput !== undefined && !deepJsonEqual(rechecked.updatedInput, resolvedArgs));
5271
5270
  if (beltUnsafe) {
5272
5271
  const editedDenial = formatHookFeedback(`The approver EDITED this call's input; the edited call is refused by the deployment's tool policy and was not executed${rechecked.action === "deny" && rechecked.message ? `: ${rechecked.message}` : " (the policy rewrote the edit to a value that was not approved)"}.`, prepared.reminderMark);
5273
- emitEnd(true, { content: editedDenial });
5272
+ emitEnd(true, { content: editedDenial }, vetoed("policy"));
5274
5273
  const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, editedDenial, true));
5275
5274
  emitCommitted(eid, "toolResult", pendingAction.toolCallId);
5276
5275
  return;
5277
5276
  }
5278
5277
  }
5279
5278
  const gateRealApproval = resume.cp.gate.kind === "irreversible_ask" ? resume.cp.gate.realApproval : undefined;
5280
- const gateOrgGoverned = gateRealApproval?.origin === "org_rule" || gateRealApproval?.origin === "org_unavailable";
5279
+ const gateOrgGoverned = realApprovalOrgFact(gateRealApproval) !== undefined;
5281
5280
  if (gateOrgGoverned && prepared.permissionRuleOrg === undefined) {
5282
5281
  const unwiredDenial = formatHookFeedback(`The approved tool call "${pendingAction.toolName}" was not executed: its approval was minted under organization governance (${gateRealApproval.origin}), and this worker has no org adjudication wiring — a governed approval may only be redeemed where governance can be enforced. This approval is spent; re-issue the call on an org-wired worker.`, prepared.reminderMark);
5283
- emitEnd(true, { content: unwiredDenial });
5282
+ emitEnd(true, { content: unwiredDenial }, vetoed("org"));
5284
5283
  const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, unwiredDenial, true));
5285
5284
  emitCommitted(eid, "toolResult", pendingAction.toolCallId);
5286
5285
  return;
@@ -5298,7 +5297,7 @@ export class Runner {
5298
5297
  },
5299
5298
  });
5300
5299
  const blocked = org.status === "unavailable"
5301
- ? gateRealApproval?.origin === "org_unavailable"
5300
+ ? realApprovalOrgFact(gateRealApproval) === "unavailable"
5302
5301
  ? undefined
5303
5302
  : orgWaitCancelled
5304
5303
  ? "this deployment is org-governed and the task was cancelled before the organization policy snapshot could be adjudicated"
@@ -5311,7 +5310,7 @@ export class Runner {
5311
5310
  ? ` Governance disclosures: ${inlineUntrusted(org.disclosures.join("; "), ORG_DISCLOSURE_MAX_CHARS)}.`
5312
5311
  : "";
5313
5312
  const orgDenial = formatHookFeedback(`The approved tool call "${pendingAction.toolName}" was not executed: ${blocked}.${orgDisclosures} This approval is spent — the call has to be re-issued and approved again once organization policy permits it.`, prepared.reminderMark);
5314
- emitEnd(true, { content: orgDenial });
5313
+ emitEnd(true, { content: orgDenial }, vetoed("org"));
5315
5314
  const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, orgDenial, true));
5316
5315
  emitCommitted(eid, "toolResult", pendingAction.toolCallId);
5317
5316
  return;
@@ -5327,7 +5326,7 @@ export class Runner {
5327
5326
  }, prepared.abortController.signal));
5328
5327
  if (narrowed.action === "deny") {
5329
5328
  const narrowedDenial = formatHookFeedback(`The approved tool call "${pendingAction.toolName}" is now denied by a session rule and was not executed${narrowed.message ? `: ${narrowed.message}` : ""}.`, prepared.reminderMark);
5330
- emitEnd(true, { content: narrowedDenial });
5329
+ emitEnd(true, { content: narrowedDenial }, vetoed("policy"));
5331
5330
  const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, narrowedDenial, true));
5332
5331
  emitCommitted(eid, "toolResult", pendingAction.toolCallId);
5333
5332
  return;
@@ -5535,8 +5534,7 @@ export class Runner {
5535
5534
  async teardownOwnedEnv(prepared) {
5536
5535
  if (prepared.ownedEnv &&
5537
5536
  hasDestroy(prepared.ownedEnv) &&
5538
- prepared.suspendRef.token === undefined &&
5539
- prepared.reviewRef.token === undefined) {
5537
+ prepared.pausedRef.current === undefined) {
5540
5538
  try {
5541
5539
  await prepared.ownedEnv.destroy();
5542
5540
  }