@sema-agent/core 7.5.1 → 7.6.0

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 (132) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/dist/agents/cascade.d.ts +2 -2
  3. package/dist/agents/cascade.js +12 -10
  4. package/dist/agents/repair-loop.d.ts +5 -3
  5. package/dist/agents/repair-loop.js +13 -15
  6. package/dist/agents/subagent.d.ts +24 -42
  7. package/dist/agents/subagent.js +119 -105
  8. package/dist/agents/suspend-guard.d.ts +31 -19
  9. package/dist/agents/suspend-guard.js +14 -8
  10. package/dist/agents/teacher.js +9 -9
  11. package/dist/agents/team.d.ts +4 -3
  12. package/dist/agents/team.js +10 -8
  13. package/dist/agents/verify.d.ts +3 -3
  14. package/dist/agents/verify.js +17 -17
  15. package/dist/core/a2a.js +2 -1
  16. package/dist/core/ask-origin.d.ts +60 -7
  17. package/dist/core/ask-origin.js +26 -1
  18. package/dist/core/checkpoint-store.d.ts +78 -76
  19. package/dist/core/checkpoint-store.js +17 -1
  20. package/dist/core/gate-outcome.d.ts +189 -0
  21. package/dist/core/gate-outcome.js +70 -0
  22. package/dist/core/hooks.d.ts +18 -92
  23. package/dist/core/hooks.js +88 -85
  24. package/dist/core/mcp-failure.d.ts +104 -0
  25. package/dist/core/mcp-failure.js +128 -0
  26. package/dist/core/mcp.d.ts +21 -77
  27. package/dist/core/mcp.js +76 -150
  28. package/dist/core/pause-registry.d.ts +131 -0
  29. package/dist/core/pause-registry.js +27 -0
  30. package/dist/core/protocol-table.d.ts +5 -0
  31. package/dist/core/protocol-table.js +1 -0
  32. package/dist/core/runner/abort-race.d.ts +41 -0
  33. package/dist/core/runner/abort-race.js +38 -0
  34. package/dist/core/runner/assemble-result.d.ts +32 -41
  35. package/dist/core/runner/assemble-result.js +55 -74
  36. package/dist/core/runner/checkpoint-scope.d.ts +15 -3
  37. package/dist/core/runner/checkpoint-scope.js +3 -0
  38. package/dist/core/runner/compaction-call-options.d.ts +1 -1
  39. package/dist/core/runner/content-ask-bindings.d.ts +27 -0
  40. package/dist/core/runner/content-ask-bindings.js +1 -0
  41. package/dist/core/runner/contracts.d.ts +144 -50
  42. package/dist/core/runner/denial-limit-arms.d.ts +24 -1
  43. package/dist/core/runner/denial-limit-arms.js +24 -3
  44. package/dist/core/runner/gate-exit.d.ts +74 -0
  45. package/dist/core/runner/gate-exit.js +55 -0
  46. package/dist/core/runner/inherited-ask-grants.d.ts +46 -0
  47. package/dist/core/runner/inherited-ask-grants.js +29 -0
  48. package/dist/core/runner/park-commit.d.ts +102 -0
  49. package/dist/core/runner/park-commit.js +31 -0
  50. package/dist/core/runner/{prepare-permission-rules.d.ts → permission-rule-lanes.d.ts} +109 -3
  51. package/dist/core/runner/{prepare-permission-rules.js → permission-rule-lanes.js} +47 -1
  52. package/dist/core/runner/prepare-ask-lane.d.ts +107 -0
  53. package/dist/core/runner/prepare-ask-lane.js +131 -0
  54. package/dist/core/runner/prepare-boundary-parks.d.ts +102 -0
  55. package/dist/core/runner/prepare-boundary-parks.js +169 -0
  56. package/dist/core/runner/prepare-caps-and-workflow.js +1 -1
  57. package/dist/core/runner/prepare-context-lane.d.ts +119 -0
  58. package/dist/core/runner/prepare-context-lane.js +230 -0
  59. package/dist/core/runner/prepare-gate-stations.d.ts +174 -0
  60. package/dist/core/runner/prepare-gate-stations.js +265 -0
  61. package/dist/core/runner/prepare-hands-readface.d.ts +4 -4
  62. package/dist/core/runner/prepare-inherited-gate.d.ts +4 -4
  63. package/dist/core/runner/prepare-inherited-gate.js +1 -1
  64. package/dist/core/runner/prepare-memory-engine-session.d.ts +84 -0
  65. package/dist/core/runner/prepare-memory-engine-session.js +233 -0
  66. package/dist/core/runner/prepare-memory.d.ts +44 -26
  67. package/dist/core/runner/prepare-park-ask.d.ts +162 -0
  68. package/dist/core/runner/prepare-park-ask.js +377 -0
  69. package/dist/core/runner/prepare-policy-chain.d.ts +208 -0
  70. package/dist/core/runner/prepare-policy-chain.js +584 -0
  71. package/dist/core/runner/prepare-project-context.d.ts +1 -13
  72. package/dist/core/runner/prepare-project-context.js +1 -3
  73. package/dist/core/runner/prepare-prompt-assembly.d.ts +95 -0
  74. package/dist/core/runner/prepare-prompt-assembly.js +162 -0
  75. package/dist/core/runner/prepare-prompt-inputs.d.ts +1 -20
  76. package/dist/core/runner/prepare-protocol-tools.d.ts +3 -3
  77. package/dist/core/runner/prepare-protocol-tools.js +0 -3
  78. package/dist/core/runner/prepare-question-face.d.ts +3 -21
  79. package/dist/core/runner/prepare-question-face.js +2 -1
  80. package/dist/core/runner/prepare-safety-scan.d.ts +0 -5
  81. package/dist/core/runner/prepare-safety-scan.js +1 -2
  82. package/dist/core/runner/prepare-suspend-saga.d.ts +170 -0
  83. package/dist/core/runner/prepare-suspend-saga.js +308 -0
  84. package/dist/core/runner/prepare-task.d.ts +9 -136
  85. package/dist/core/runner/prepare-task.js +47 -2745
  86. package/dist/core/runner/prepare-turn-wiring.d.ts +154 -0
  87. package/dist/core/runner/prepare-turn-wiring.js +201 -0
  88. package/dist/core/runner/prepare-wiring-manifest.d.ts +17 -17
  89. package/dist/core/runner/prepare-wiring-manifest.js +16 -10
  90. package/dist/core/runner/prepare-workspace-restore.d.ts +2 -29
  91. package/dist/core/runner/prepare-workspace-restore.js +3 -16
  92. package/dist/core/runner/prompt-hash-salt.d.ts +1 -0
  93. package/dist/core/runner/prompt-hash-salt.js +2 -0
  94. package/dist/core/runner/remote-env-retry.d.ts +29 -0
  95. package/dist/core/runner/remote-env-retry.js +16 -0
  96. package/dist/core/runner/runtask.d.ts +16 -31
  97. package/dist/core/runner/runtask.js +110 -121
  98. package/dist/core/runner/terminal-projection.d.ts +22 -0
  99. package/dist/core/runner/terminal-projection.js +28 -0
  100. package/dist/core/session.d.ts +12 -0
  101. package/dist/core/session.js +3 -0
  102. package/dist/core/store-contracts/checkpoint-store-contract.d.ts +4 -1
  103. package/dist/core/store-contracts/checkpoint-store-contract.js +8 -2
  104. package/dist/core/terminal-cause.d.ts +137 -0
  105. package/dist/core/terminal-cause.js +9 -0
  106. package/dist/core/tool-policy.d.ts +43 -139
  107. package/dist/core/tool-policy.js +79 -112
  108. package/dist/core/types.d.ts +67 -164
  109. package/dist/core/wiring-manifest.d.ts +6 -3
  110. package/dist/core/workflow-journal-store.js +3 -4
  111. package/dist/engine/harness/agent-harness.d.ts +1 -1
  112. package/dist/index.d.ts +10 -7
  113. package/dist/index.js +8 -5
  114. package/dist/orchestration/builtin-workflows.d.ts +2 -2
  115. package/dist/orchestration/builtin-workflows.js +1 -1
  116. package/dist/orchestration/goal.js +8 -7
  117. package/dist/orchestration/run-spec.js +5 -3
  118. package/dist/orchestration/run-workflow-tool.d.ts +1 -1
  119. package/dist/orchestration/run-workflow-tool.js +4 -4
  120. package/dist/orchestration/workflow-governance.d.ts +4 -4
  121. package/dist/orchestration/workflow-governance.js +4 -2
  122. package/dist/orchestration/workflow-primitives.d.ts +1 -1
  123. package/dist/orchestration/workflow-primitives.js +1 -1
  124. package/dist/orchestration/workflow.d.ts +11 -0
  125. package/dist/orchestration/workflow.js +64 -39
  126. package/dist/prompts/supervisor.d.ts +1 -1
  127. package/dist/prompts/supervisor.js +3 -3
  128. package/dist/scenarios/scenario-registry.js +1 -1
  129. package/package.json +3 -1
  130. package/test/export-surface.snapshot.json +74 -22
  131. /package/dist/core/runner/{prepare-announce-once.d.ts → announce-once-ledger.d.ts} +0 -0
  132. /package/dist/core/runner/{prepare-announce-once.js → announce-once-ledger.js} +0 -0
@@ -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, 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,6 +35,7 @@ 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
41
  import { effectiveDelegationFacts, gatedCallIdOf, placementValueOrAbsent, prepareTask, resolveCheckpointStore } from "./prepare-task.js";
@@ -47,11 +48,15 @@ import { hasBackgroundShell, sweepBackgroundShells } from "../background-shell.j
47
48
  import { cloneObserverInput, formatHookFeedback, hookSeatExpiredError, MAX_HOOK_TIMEOUT_MS, mintHookInvocationIdentity, resolveHookTimeoutMs, runHookSeat } from "../hooks.js";
48
49
  import { buildHumanInputEvent, frameMidTurnUserInput, projectHumanInput } from "../human-input-projection.js";
49
50
  import { delimitUntrusted, inlineUntrusted, REVIEWER_NOTE_MAX_BODY, sanitizeUntrustedText, SHELLED_BODY_ENVELOPE_TAGS } from "../untrusted-text.js";
50
- import { droppedToolDigest, withAnnounceDigests } from "./prepare-announce-once.js";
51
+ import { droppedToolDigest, withAnnounceDigests } from "./announce-once-ledger.js";
51
52
  import { appendInterruptionMarker, reconcileInterruptedSession } from "../session-reconcile.js";
52
53
  import { RunnerSharedToolResultStore } from "../tool-result-store.js";
53
54
  import { formatDiagnosticsBlock } from "../lsp-diagnostics.js";
54
- import { checkToolPolicyProjection, constraintChainDigest, constraintChainEntryOfSuppliedLayer, isApprovalSettledBy, refuseOutOfContractDecision, screenApproverAttribution, toolPolicyNameSets } from "../tool-policy.js";
55
+ import { isAskOrigin } from "../ask-origin.js";
56
+ import { isGateKind, PAUSE_REGISTRY, resumeGateMatches } from "../pause-registry.js";
57
+ import { checkToolPolicyProjection, constraintChainDigest, constraintChainEntryOfSuppliedLayer, refuseOutOfContractDecision, screenApproverAttribution, toolPolicyNameSets } from "../tool-policy.js";
58
+ import { mintGateOutcome } from "./gate-exit.js";
59
+ import { MCP_DELIVERY_VERDICTS } from "../mcp-failure.js";
55
60
  import { defaultTaskRegistry } from "../task-registry.js";
56
61
  import { discloseDroppedPending, isDelegatedAgentTerminal, isSystemInjectionPriority, isTerminalTaskNotification, PendingSessionNotifications, renderTaskNotificationXml, SYSTEM_INJECTION_PRIORITIES, SystemInjectionQueue, taskNotificationDedupKey } from "../task-notification.js";
57
62
  import { ToolDetachHub } from "../tool-detach.js";
@@ -147,23 +152,23 @@ function resumeDecisionWasNegative(resume) {
147
152
  }
148
153
  const DEFERRED_REISSUE = "[DEFERRED] This tool call shared a batch with a call that suspended for durable approval, so it was " +
149
154
  "NOT executed on resume. If you still need it, issue it again now.";
150
- function toolEndBodyFrom(result, isError, settledBy, approver, resolution, gatedCallIdOfRun, autoDenied) {
155
+ function toolEndBodyFrom(result, isError, gate, gatedCallIdOfRun) {
151
156
  const o = toolOutputFrom(result);
152
157
  const st = structuredFrom(result);
153
158
  const det = isError ? result?.details : undefined;
154
159
  const codeRaw = det?.code;
155
160
  const kindRaw = det?.errorKind;
156
161
  const code = typeof codeRaw === "string" ? codeRaw : typeof kindRaw === "string" ? kindRaw : undefined;
162
+ const deliveredRaw = det?.delivered;
163
+ const delivered = typeof deliveredRaw === "string" && MCP_DELIVERY_VERDICTS.includes(deliveredRaw) ? deliveredRaw : undefined;
157
164
  const gatedCallId = code === "gate.parked" ? gatedCallIdOfRun : undefined;
158
165
  return {
159
166
  ...(o !== undefined ? { output: o.output, ...(o.truncated ? { truncated: true } : {}), ...(o.totalChars !== undefined ? { totalChars: o.totalChars } : {}) } : {}),
160
167
  ...(st !== undefined ? { structured: st } : {}),
161
168
  ...(typeof code === "string" ? { errorCode: code } : {}),
169
+ ...(delivered !== undefined ? { delivered } : {}),
162
170
  ...(gatedCallId !== undefined ? { gatedCallId } : {}),
163
- ...(settledBy !== undefined ? { settledBy } : {}),
164
- ...(approver !== undefined ? { approver } : {}),
165
- ...(resolution !== undefined ? { resolution } : {}),
166
- ...(autoDenied !== undefined ? { autoDenied } : {}),
171
+ ...(gate !== undefined ? { gate } : {}),
167
172
  };
168
173
  }
169
174
  export function reconciledToolEndBody(orphan) {
@@ -393,7 +398,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
393
398
  let platformCause;
394
399
  let platformRetryAfterMs;
395
400
  const preemptWillSuspend = preemptHit && prepared.suspendForResource !== undefined;
396
- if (!preemptWillSuspend && prepared.suspendRef.token === undefined && !prepared.abortController.signal.aborted) {
401
+ if (!preemptWillSuspend && prepared.pausedRef.current === undefined && !prepared.abortController.signal.aborted) {
397
402
  const ledgerReadDeadline = prepared.envLifetimeSuspendAt !== undefined ? prepared.envLifetimeSuspendAt + ENV_DUE_GOVERNANCE_READ_BUDGET_MS : undefined;
398
403
  if (prepared.usageGovernance !== undefined) {
399
404
  const governance = prepared.usageGovernance;
@@ -461,7 +466,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
461
466
  }
462
467
  if (prepared.suspendForResource &&
463
468
  (rs.limits.turnsExceeded || rs.limits.budgetHit !== undefined || walltimeHit || preemptHit) &&
464
- prepared.suspendRef.token === undefined) {
469
+ prepared.pausedRef.current === undefined) {
465
470
  const reason = preemptHit
466
471
  ? "preempt"
467
472
  : rs.limits.budgetHit !== undefined
@@ -540,8 +545,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
540
545
  prepared.reviewRequestRef.pending = undefined;
541
546
  const honorable = prepared.suspendForReview !== undefined &&
542
547
  !prepared.abortController.signal.aborted &&
543
- prepared.suspendRef.token === undefined &&
544
- prepared.reviewRef.token === undefined;
548
+ prepared.pausedRef.current === undefined;
545
549
  if (honorable && prepared.suspendForReview) {
546
550
  await prepared.suspendForReview(reason);
547
551
  return undefined;
@@ -838,8 +842,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
838
842
  const forceManual = manualCompactRef.requested;
839
843
  if ((!withinTaskCompaction && !forceManual) ||
840
844
  prepared.abortController.signal.aborted ||
841
- prepared.suspendRef.token !== undefined ||
842
- prepared.reviewRef.token !== undefined) {
845
+ prepared.pausedRef.current !== undefined) {
843
846
  if (forceManual) {
844
847
  emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.mooted", version: 1, taskId: rs.telemetry.taskId, reason: "task_ending", ts: Date.now() }));
845
848
  queue.push({ type: "compaction_outcome", outcome: "mooted", trigger: "manual", reason: "task_ending", ...ident() });
@@ -1386,16 +1389,16 @@ function makeHarnessHandlers(prepared, stats, rs, deps) {
1386
1389
  prepared.lspDiagnostics.nudge(p);
1387
1390
  }
1388
1391
  }
1389
- const settlement = prepared.approvalSettlement.get(event.toolCallId);
1390
- if (settlement !== undefined)
1391
- prepared.approvalSettlement.delete(event.toolCallId);
1392
+ const gate = prepared.gateOutcomes.get(event.toolCallId);
1393
+ if (gate !== undefined)
1394
+ prepared.gateOutcomes.delete(event.toolCallId);
1392
1395
  pushContent({
1393
1396
  type: "tool_end",
1394
1397
  toolCallId: event.toolCallId,
1395
1398
  toolName: event.toolName,
1396
1399
  ...(toolLabels.get(event.toolName) !== undefined ? { label: toolLabels.get(event.toolName) } : {}),
1397
1400
  isError: event.isError,
1398
- ...toolEndBodyFrom(event.result, event.isError, settlement?.settledBy, settlement?.approver, settlement?.resolution, prepared.abortController.signal.aborted ? gatedCallIdOf(prepared) : undefined, settlement?.autoDenied),
1401
+ ...toolEndBodyFrom(event.result, event.isError, gate, prepared.abortController.signal.aborted ? gatedCallIdOf(prepared) : undefined),
1399
1402
  ...ident(),
1400
1403
  });
1401
1404
  announceWorkspaceMove();
@@ -1698,17 +1701,18 @@ export class Runner {
1698
1701
  const pcs = internals?.inheritedGate?.parentConstraints;
1699
1702
  if (resume !== undefined)
1700
1703
  this.locallyClaimedTokens.delete(resume.cp.token);
1701
- if (resume !== undefined && r.checkpointToken !== resume.cp.token) {
1704
+ const rToken = r.terminal.kind === "paused" ? r.terminal.token : undefined;
1705
+ if (resume !== undefined && rToken !== resume.cp.token) {
1702
1706
  this.parentConstraintRegistry.delete(resume.cp.token);
1703
1707
  this.suspendedEnvReaps.delete(resume.cp.token);
1704
1708
  }
1705
- if (r.status === "suspended" && r.checkpointToken !== undefined && pcs !== undefined && pcs.length > 0) {
1709
+ if (r.terminal.kind === "paused" && PAUSE_REGISTRY[r.terminal.gate.kind].taskStatus === "suspended" && pcs !== undefined && pcs.length > 0) {
1706
1710
  if (this.parentConstraintRegistry.size >= Runner.PARENT_CONSTRAINT_REGISTRY_CAP) {
1707
1711
  const oldest = this.parentConstraintRegistry.keys().next().value;
1708
1712
  if (oldest !== undefined)
1709
1713
  this.parentConstraintRegistry.delete(oldest);
1710
1714
  }
1711
- this.parentConstraintRegistry.set(r.checkpointToken, pcs);
1715
+ this.parentConstraintRegistry.set(r.terminal.token, pcs);
1712
1716
  }
1713
1717
  }, (p) => {
1714
1718
  suggestionsDone = p;
@@ -1790,10 +1794,8 @@ export class Runner {
1790
1794
  taskId: taskIdRef.current ?? "unknown",
1791
1795
  sessionId: taskIdRef.sessionId ?? "unknown",
1792
1796
  ...(taskIdRef.runId !== undefined ? { runId: taskIdRef.runId } : {}),
1793
- status: "failed",
1797
+ terminal: { kind: "failed", ...(code !== undefined ? { code } : {}), message: err instanceof Error ? err.message : String(err) },
1794
1798
  result: "",
1795
- errorMessage: err instanceof Error ? err.message : String(err),
1796
- errorCode: code,
1797
1799
  ...(remoteEnvFailure !== undefined ? { remoteEnvFailures: remoteEnvFailure } : {}),
1798
1800
  ...(taskIdRef.effectiveMemoryScopes !== undefined ? { effectiveMemoryScopes: taskIdRef.effectiveMemoryScopes } : {}),
1799
1801
  ...(taskIdRef.effectiveReasoning !== undefined ? { effectiveReasoning: taskIdRef.effectiveReasoning } : {}),
@@ -2404,7 +2406,7 @@ export class Runner {
2404
2406
  prepared.harness.engineInjectionsHeld = () => prepared.batchHaltRef.current !== undefined;
2405
2407
  let undrainedUserAtEnd;
2406
2408
  prepared.harness.onUndrainedUserInputs = (counts) => {
2407
- if (prepared.suspendRef.token !== undefined || prepared.reviewRef.token !== undefined) {
2409
+ if (prepared.pausedRef.current !== undefined) {
2408
2410
  undrainedUserAtEnd = counts;
2409
2411
  return;
2410
2412
  }
@@ -3059,7 +3061,7 @@ export class Runner {
3059
3061
  if (stopHook || finalVerificationOn) {
3060
3062
  let consecutiveBlocks = 0;
3061
3063
  prepared.harness.setStopGate(async () => {
3062
- if (prepared.abortController.signal.aborted || prepared.suspendRef.token !== undefined || prepared.batchHaltRef.current !== undefined)
3064
+ if (prepared.abortController.signal.aborted || prepared.pausedRef.current !== undefined || prepared.batchHaltRef.current !== undefined)
3063
3065
  return [];
3064
3066
  if (finalVerificationOn &&
3065
3067
  (rs.counters.finalVerifyInjections === 0 || (rs.counters.finalVerifyInjections === 1 && rs.counters.groundingSignalPreR9 && !rs.counters.groundingSignalPostR9)) &&
@@ -3300,7 +3302,7 @@ export class Runner {
3300
3302
  degenerateOutput: { detect: (m) => isDegenerateCutMessage(m) },
3301
3303
  promptTooLong: {
3302
3304
  recover: async (attempt, turnSignal) => {
3303
- if (prepared.abortController.signal.aborted || prepared.suspendRef.token !== undefined)
3305
+ if (prepared.abortController.signal.aborted || prepared.pausedRef.current !== undefined)
3304
3306
  return false;
3305
3307
  if (turnSignal?.aborted === true)
3306
3308
  return false;
@@ -3345,7 +3347,7 @@ export class Runner {
3345
3347
  },
3346
3348
  });
3347
3349
  prepared.microCompact.inTurnCompactionRef.current = async (turnSignal, anchoredEstimate) => {
3348
- if (prepared.abortController.signal.aborted || prepared.suspendRef.token !== undefined)
3350
+ if (prepared.abortController.signal.aborted || prepared.pausedRef.current !== undefined)
3349
3351
  return false;
3350
3352
  if (turnSignal?.aborted === true)
3351
3353
  return false;
@@ -3926,9 +3928,8 @@ export class Runner {
3926
3928
  this.deps.onError?.(flushErr, { phase: "config", sessionId: prepared.sessionId });
3927
3929
  }
3928
3930
  }
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;
3931
+ const committedPause = prepared.pausedRef.current;
3932
+ const durablyPaused = committedPause !== undefined;
3932
3933
  const interrupted = !durablyPaused &&
3933
3934
  (threw !== undefined || timeout.fired || rs.limits.turnsExceeded || rs.limits.budgetHit !== undefined || abortedLive || final?.stopReason === "aborted");
3934
3935
  let orphansClosed = 0;
@@ -3972,9 +3973,9 @@ export class Runner {
3972
3973
  }
3973
3974
  }
3974
3975
  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) {
3976
+ if (committedPause !== undefined && this.deps.checkpointStore !== undefined) {
3977
+ const parkToken = committedPause.cause.token;
3978
+ const parkScope = committedPause.scope;
3978
3979
  const store = this.deps.checkpointStore;
3979
3980
  const carried = [];
3980
3981
  for (const record of prepared.harness.readParkableUserInputs()) {
@@ -4067,7 +4068,7 @@ export class Runner {
4067
4068
  }
4068
4069
  }
4069
4070
  if (prepared.resourceLedger &&
4070
- (resume?.cp.gate.kind === "resource_limit" || prepared.suspendRef.gate?.kind === "resource_limit")) {
4071
+ (resume?.cp.gate.kind === "resource_limit" || prepared.pausedRef.current?.cause.gate.kind === "resource_limit")) {
4071
4072
  stats.costMicroUsd += prepared.resourceLedger.spentMicroUsd;
4072
4073
  stats.tokens += prepared.resourceLedger.spentTokens;
4073
4074
  stats.turns += prepared.resourceLedger.spentTurns;
@@ -4118,46 +4119,26 @@ export class Runner {
4118
4119
  gitCoreOverBudget: prepared.gitStatusRef.terminalCode === "irreducible_core_over_budget",
4119
4120
  outputInvalid: rs.degrade.outputInvalid,
4120
4121
  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,
4122
+ ...(prepared.pausedRef.current !== undefined ? { paused: prepared.pausedRef.current.cause } : {}),
4137
4123
  });
4138
4124
  if (resume !== undefined &&
4139
4125
  resume.pendingActionStarted !== true &&
4140
- result.status !== "completed" &&
4141
- result.status !== "suspended" &&
4142
- result.status !== "needs_review" &&
4126
+ result.terminal.kind !== "completed" &&
4127
+ result.terminal.kind !== "paused" &&
4143
4128
  !(resumeDecisionWasNegative(resume) && resume.decisionDelivered === true) &&
4144
4129
  resume.onEnvRestoreFailed !== undefined) {
4145
4130
  try {
4146
4131
  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`;
4132
+ 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
4133
  }
4151
4134
  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`;
4135
+ 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
4136
  }
4156
4137
  }
4157
4138
  prepared.sealReadStateSeat?.(ownCommittedTailRef.current);
4158
4139
  {
4159
4140
  const wss = prepared.workspaceStateSettle;
4160
- if (wss !== undefined && result.status !== "suspended" && result.status !== "needs_review") {
4141
+ if (wss !== undefined && result.terminal.kind !== "paused") {
4161
4142
  const curCwd = prepared.cwdRef?.current !== undefined && prepared.cwdRef.current !== wss.rootCanonical ? prepared.cwdRef.current : undefined;
4162
4143
  const curWt = prepared.worktreeSessionRef?.current;
4163
4144
  const changed = curCwd !== wss.restoredHandsCwd || curWt?.worktreeDir !== wss.restoredWorktreeDir;
@@ -4172,7 +4153,7 @@ export class Runner {
4172
4153
  }
4173
4154
  const stopFailureHook = (spec.hooks ?? this.deps.hooks)?.stopFailure;
4174
4155
  if (stopFailureHook !== undefined &&
4175
- result.status === "failed" &&
4156
+ result.terminal.kind === "failed" &&
4176
4157
  final?.stopReason === "error" &&
4177
4158
  !isDegenerateCutMessage(final) &&
4178
4159
  threw === undefined &&
@@ -4180,12 +4161,13 @@ export class Runner {
4180
4161
  !rs.degrade.outputInvalid &&
4181
4162
  !prepared.suspendLoopRef.hit &&
4182
4163
  !abortedLive &&
4183
- result.errorCode !== "conflict") {
4164
+ result.terminal.code !== "conflict") {
4165
+ const failedCause = result.terminal;
4184
4166
  try {
4185
4167
  const failureSeat = await runHookSeat("stopFailure", { timeoutMs: prepared.hookTimeoutMs }, (sig) => stopFailureHook({
4186
4168
  identity: prepared.hookIdentity,
4187
- error: result.errorMessage ?? "model error",
4188
- ...(result.errorCode !== undefined ? { errorKind: result.errorCode } : {}),
4169
+ error: failedCause.message ?? "model error",
4170
+ ...(failedCause.code !== undefined ? { errorKind: failedCause.code } : {}),
4189
4171
  turns: stats.turns,
4190
4172
  signal: sig,
4191
4173
  }));
@@ -4205,12 +4187,13 @@ export class Runner {
4205
4187
  }
4206
4188
  }
4207
4189
  }
4190
+ const terminalFace = terminalProjection(result.terminal);
4208
4191
  emitDelegationLifecycle({
4209
4192
  phase: "terminal",
4210
4193
  identity: prepared.hookIdentity,
4211
- status: result.status,
4194
+ status: terminalFace.status,
4212
4195
  turns: stats.turns,
4213
- ...(result.errorCode !== undefined ? { errorCode: result.errorCode } : {}),
4196
+ ...(terminalFace.errorCode !== undefined ? { errorCode: terminalFace.errorCode } : {}),
4214
4197
  });
4215
4198
  if (taskIdRef !== undefined)
4216
4199
  taskIdRef.delegationTerminalOwed = undefined;
@@ -4218,8 +4201,8 @@ export class Runner {
4218
4201
  result.degraded = rs.degrade.degraded;
4219
4202
  if (prepared.outputRef.set)
4220
4203
  result.structuredOutput = prepared.outputRef.value;
4221
- const committedToken = prepared.suspendRef.token ?? prepared.reviewRef.token;
4222
- const committedScope = prepared.suspendRef.scope ?? prepared.reviewRef.scope;
4204
+ const committedToken = prepared.pausedRef.current?.cause.token;
4205
+ const committedScope = prepared.pausedRef.current?.scope;
4223
4206
  if (committedToken !== undefined) {
4224
4207
  const store = resolveCheckpointStore(spec, this.deps);
4225
4208
  if (store && committedScope !== undefined) {
@@ -4244,8 +4227,8 @@ export class Runner {
4244
4227
  version: 1,
4245
4228
  taskId: rs.telemetry.taskId,
4246
4229
  runId: rs.telemetry.runId,
4247
- status: result.status,
4248
- errorCode: result.errorCode,
4230
+ status: terminalFace.status,
4231
+ errorCode: terminalFace.errorCode,
4249
4232
  turns: stats.turns,
4250
4233
  tokens: stats.tokens,
4251
4234
  ...(rs.telemetry.unpricedSpend ? {} : { costMicroUsd: stats.costMicroUsd }),
@@ -4261,7 +4244,7 @@ export class Runner {
4261
4244
  ts: Date.now(),
4262
4245
  }));
4263
4246
  setResult(result);
4264
- if (parentToolCallId !== undefined && (result.status === "completed" || result.status === "failed" || result.status === "blocked")) {
4247
+ if (parentToolCallId !== undefined && result.terminal.kind !== "paused") {
4265
4248
  const terminalTick = {
4266
4249
  type: "task_progress",
4267
4250
  taskId: rs.telemetry.taskId,
@@ -4271,7 +4254,7 @@ export class Runner {
4271
4254
  ...(subagentName ? { name: subagentName } : {}),
4272
4255
  model: prepared.model.id,
4273
4256
  usage: { totalTokens: stats.tokens, toolUses: stats.toolCalls, durationMs: Date.now() - rs.telemetry.taskStart },
4274
- status: result.status === "completed" ? "completed" : "failed",
4257
+ status: result.terminal.kind === "completed" ? "completed" : "failed",
4275
4258
  ...ident(),
4276
4259
  };
4277
4260
  queue.push(terminalTick);
@@ -4289,7 +4272,7 @@ export class Runner {
4289
4272
  queue.push({ type: "done", result });
4290
4273
  queue.close();
4291
4274
  await prepared.fileHistoryBoundary?.settle();
4292
- if (spec.suggestNextPrompts && result.status === "completed") {
4275
+ if (spec.suggestNextPrompts && result.terminal.kind === "completed") {
4293
4276
  onSuggestions(this.suggestNextPrompts(spec, prepared, result));
4294
4277
  }
4295
4278
  }
@@ -4297,15 +4280,13 @@ export class Runner {
4297
4280
  const ownedEnvDying = prepared.ownedEnv !== undefined &&
4298
4281
  hasDestroy(prepared.ownedEnv) &&
4299
4282
  isIsolated(prepared.ownedEnv) &&
4300
- prepared.suspendRef.token === undefined &&
4301
- prepared.reviewRef.token === undefined;
4283
+ prepared.pausedRef.current === undefined;
4302
4284
  await settleTeardownLeg(() => defaultTaskRegistry.settleKilledForOwner({ owner: spec.taskId ?? prepared.sessionId, scope: spec.principal ?? "default", sessionId: prepared.sessionId }, {
4303
4285
  source: abortedLive &&
4304
4286
  !timeout.fired &&
4305
4287
  !rs.limits.turnsExceeded &&
4306
4288
  rs.limits.budgetHit === undefined &&
4307
- prepared.suspendRef.token === undefined &&
4308
- prepared.reviewRef.token === undefined
4289
+ prepared.pausedRef.current === undefined
4309
4290
  ? "user"
4310
4291
  : "parent",
4311
4292
  skipSessionScoped: true,
@@ -4485,6 +4466,7 @@ export class Runner {
4485
4466
  break;
4486
4467
  case "tool_approval":
4487
4468
  case "resource_limit":
4469
+ case "review":
4488
4470
  case "plan_review": {
4489
4471
  throw new CheckpointError("wake.gate_pending", `cannot wake: this checkpoint still carries a pending gate-typed action (pendingAction "${pa.kind}" ` +
4490
4472
  `on a "${gk}" gate — inconsistent row) — wake is not a decision channel; resolve it through its ` +
@@ -4505,14 +4487,12 @@ export class Runner {
4505
4487
  "wake would burn the checkpoint on a blank continuation; supply `message` or park a steer first");
4506
4488
  }
4507
4489
  }
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");
4490
+ const gateMatch = isGateKind(cp.gate.kind) && resumeGateMatches(cp.gate.kind, outcomeGate);
4514
4491
  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`);
4492
+ 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)
4493
+ .filter(([, row]) => !row.terminal)
4494
+ .map(([k, row]) => `${k}/${row.resume}`)
4495
+ .join(", ")}`);
4516
4496
  }
4517
4497
  let plainReviewOutcome;
4518
4498
  if (cp.gate.kind === "plan_review") {
@@ -4619,25 +4599,27 @@ export class Runner {
4619
4599
  if (decision !== "allow" && decision !== "deny") {
4620
4600
  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
4601
  }
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" });
4602
+ const hostDecisionRaw = decide.hostDecision;
4603
+ if (typeof hostDecisionRaw !== "object" || hostDecisionRaw === null) {
4604
+ 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" });
4605
+ }
4606
+ const decidedBy = hostDecisionRaw.decidedBy;
4607
+ if (decidedBy !== "person" && decidedBy !== "sla_timeout") {
4608
+ 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
4609
  }
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" });
4610
+ if (decidedBy === "sla_timeout" && decision === "allow") {
4611
+ 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
4612
  }
4630
- const approver = decide.approver;
4631
- const attribution = screenApproverAttribution(approver);
4613
+ if (decide.gateOutcome !== undefined) {
4614
+ throw new CheckpointError("checkpoint.invalid_outcome", "resume carries a gateOutcome — the settlement record is minted by core from the host's decision facts and the row's origin, never supplied; refusing pre-CAS, the checkpoint stays pending", { field: "hostDecision" });
4615
+ }
4616
+ const attribution = screenApproverAttribution(hostDecisionRaw.approver);
4632
4617
  if (attribution.defect !== undefined) {
4633
4618
  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
4619
  }
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
4620
  const reason = decide.reason;
4640
4621
  assertOutcomeText(reason, "reason");
4622
+ const approverCell = attribution.approver !== undefined ? { approver: attribution.approver } : {};
4641
4623
  plainPolicyOutcome = {
4642
4624
  gate: "policy_ask",
4643
4625
  boundCallId: decide.boundCallId,
@@ -4646,8 +4628,7 @@ export class Runner {
4646
4628
  ...(updatedInput !== undefined ? { updatedInput } : {}),
4647
4629
  ...(reason !== undefined ? { reason } : {}),
4648
4630
  ...(redeemedAnswer !== undefined ? { answer: redeemedAnswer } : {}),
4649
- ...(settledBy !== undefined ? { settledBy } : {}),
4650
- ...(attribution.approver !== undefined ? { approver: attribution.approver } : {}),
4631
+ hostDecision: { decidedBy, ...approverCell },
4651
4632
  };
4652
4633
  if (plainPolicyOutcome.decision === "deny" && plainPolicyOutcome.reason && sanitizeUntrustedText(plainPolicyOutcome.reason) !== plainPolicyOutcome.reason) {
4653
4634
  throw new CheckpointError("checkpoint.invalid_outcome", "resume deny/reject reason must not contain a </system-reminder> tag");
@@ -4701,11 +4682,7 @@ export class Runner {
4701
4682
  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
4683
  }
4703
4684
  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");
4685
+ const preCasBitWellFormed = preCasGateBit !== undefined && typeof preCasGateBit === "object" && isAskOrigin(preCasGateBit.origin);
4709
4686
  const faceSaysGoverned = (cp.state.readFace?.realApproval) === true;
4710
4687
  const preCasBitViolation = checkpointVersionOf(cp) === REAL_APPROVAL_CHECKPOINT_VERSION
4711
4688
  ? !preCasBitWellFormed
@@ -4716,7 +4693,7 @@ export class Runner {
4716
4693
  : preCasGateBit !== undefined && !preCasBitWellFormed;
4717
4694
  if (preCasBitViolation) {
4718
4695
  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`
4696
+ ? `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
4697
  : `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
4698
  }
4722
4699
  {
@@ -4736,9 +4713,8 @@ export class Runner {
4736
4713
  : `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
4714
  }
4738
4715
  }
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" });
4716
+ if (realApprovalOrgFact(preCasGateBit) !== undefined && this.deps.permissionRuleStore?.partitions.org !== true) {
4717
+ 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
4718
  }
4743
4719
  const retiredWalltimeTotal = cp.resourceLedger?.totalWalltimeSec;
4744
4720
  if (retiredWalltimeTotal !== undefined) {
@@ -4995,6 +4971,18 @@ export class Runner {
4995
4971
  }
4996
4972
  }
4997
4973
  }
4974
+ if (plainPolicyOutcome !== undefined) {
4975
+ const parkedOrigin = cp.pendingAction.kind === "tool_approval" ? cp.pendingAction.origin : undefined;
4976
+ if (!isAskOrigin(parkedOrigin)) {
4977
+ 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");
4978
+ }
4979
+ const facts = plainPolicyOutcome.hostDecision;
4980
+ const approverCell = facts.approver !== undefined ? { approver: facts.approver } : {};
4981
+ const settlement = facts.decidedBy === "sla_timeout"
4982
+ ? { kind: "park_sla_expired", who: { party: "host", ...approverCell }, when: Date.now() }
4983
+ : { kind: plainPolicyOutcome.decision === "allow" ? "human_allowed" : "human_refused", who: { party: "person", ...approverCell }, when: Date.now(), ...(plainPolicyOutcome.decision === "deny" && plainPolicyOutcome.reason !== undefined ? { note: plainPolicyOutcome.reason } : {}) };
4984
+ plainPolicyOutcome = { ...plainPolicyOutcome, gateOutcome: mintGateOutcome({ ...(plainPolicyOutcome.decision === "deny" ? { deniedBy: "ask_resolution" } : {}), settled: { settlement, origin: parkedOrigin } }) };
4985
+ }
4998
4986
  const outcomeForStore = plainPolicyOutcome ?? plainReviewOutcome ?? plainParkOutcome;
4999
4987
  if (outcomeForStore === undefined) {
5000
4988
  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 +5225,15 @@ export class Runner {
5237
5225
  const resolvedArgs = pendingAction.toolName === ASK_USER_QUESTION_TOOL_NAME ? pendingAction.args : (outcome.updatedInput !== undefined ? outcome.updatedInput : pendingAction.args);
5238
5226
  const pendingLabel = (() => { const l = prepared.tools.find((t) => t.name === pendingAction.toolName)?.label; return l !== undefined && l !== pendingAction.toolName ? { label: l } : {}; })();
5239
5227
  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) });
5228
+ const emitEnd = (isError, result, gate = outcome.gateOutcome) => emit({ type: "tool_end", toolCallId: pendingAction.toolCallId, toolName: pendingAction.toolName, ...pendingLabel, isError, ...toolEndBodyFrom(result, isError, gate) });
5229
+ const vetoed = (layer) => mintGateOutcome({
5230
+ deniedBy: layer,
5231
+ ...(outcome.gateOutcome?.settlement !== undefined && outcome.gateOutcome.origin !== undefined ? { settled: { settlement: outcome.gateOutcome.settlement, origin: outcome.gateOutcome.origin } } : {}),
5232
+ });
5241
5233
  if (outcome.decision === "deny") {
5242
- const defaultDenial = outcome.settledBy === "timeout"
5234
+ const defaultDenial = outcome.gateOutcome?.settlement?.kind === "park_sla_expired"
5243
5235
  ? `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.`;
5236
+ : `The pending tool call "${pendingAction.toolName}" was denied by an approver.`;
5247
5237
  const reason = outcome.reason ? delimitUntrusted("reviewer note", outcome.reason, REVIEWER_NOTE_MAX_BODY) : defaultDenial;
5248
5238
  emitEnd(true, { content: formatHookFeedback(reason, prepared.reminderMark) });
5249
5239
  const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, formatHookFeedback(reason, prepared.reminderMark), true));
@@ -5270,17 +5260,17 @@ export class Runner {
5270
5260
  (rechecked.updatedInput !== undefined && !deepJsonEqual(rechecked.updatedInput, resolvedArgs));
5271
5261
  if (beltUnsafe) {
5272
5262
  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 });
5263
+ emitEnd(true, { content: editedDenial }, vetoed("policy"));
5274
5264
  const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, editedDenial, true));
5275
5265
  emitCommitted(eid, "toolResult", pendingAction.toolCallId);
5276
5266
  return;
5277
5267
  }
5278
5268
  }
5279
5269
  const gateRealApproval = resume.cp.gate.kind === "irreversible_ask" ? resume.cp.gate.realApproval : undefined;
5280
- const gateOrgGoverned = gateRealApproval?.origin === "org_rule" || gateRealApproval?.origin === "org_unavailable";
5270
+ const gateOrgGoverned = realApprovalOrgFact(gateRealApproval) !== undefined;
5281
5271
  if (gateOrgGoverned && prepared.permissionRuleOrg === undefined) {
5282
5272
  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 });
5273
+ emitEnd(true, { content: unwiredDenial }, vetoed("org"));
5284
5274
  const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, unwiredDenial, true));
5285
5275
  emitCommitted(eid, "toolResult", pendingAction.toolCallId);
5286
5276
  return;
@@ -5298,7 +5288,7 @@ export class Runner {
5298
5288
  },
5299
5289
  });
5300
5290
  const blocked = org.status === "unavailable"
5301
- ? gateRealApproval?.origin === "org_unavailable"
5291
+ ? realApprovalOrgFact(gateRealApproval) === "unavailable"
5302
5292
  ? undefined
5303
5293
  : orgWaitCancelled
5304
5294
  ? "this deployment is org-governed and the task was cancelled before the organization policy snapshot could be adjudicated"
@@ -5311,7 +5301,7 @@ export class Runner {
5311
5301
  ? ` Governance disclosures: ${inlineUntrusted(org.disclosures.join("; "), ORG_DISCLOSURE_MAX_CHARS)}.`
5312
5302
  : "";
5313
5303
  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 });
5304
+ emitEnd(true, { content: orgDenial }, vetoed("org"));
5315
5305
  const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, orgDenial, true));
5316
5306
  emitCommitted(eid, "toolResult", pendingAction.toolCallId);
5317
5307
  return;
@@ -5327,7 +5317,7 @@ export class Runner {
5327
5317
  }, prepared.abortController.signal));
5328
5318
  if (narrowed.action === "deny") {
5329
5319
  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 });
5320
+ emitEnd(true, { content: narrowedDenial }, vetoed("policy"));
5331
5321
  const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, narrowedDenial, true));
5332
5322
  emitCommitted(eid, "toolResult", pendingAction.toolCallId);
5333
5323
  return;
@@ -5535,8 +5525,7 @@ export class Runner {
5535
5525
  async teardownOwnedEnv(prepared) {
5536
5526
  if (prepared.ownedEnv &&
5537
5527
  hasDestroy(prepared.ownedEnv) &&
5538
- prepared.suspendRef.token === undefined &&
5539
- prepared.reviewRef.token === undefined) {
5528
+ prepared.pausedRef.current === undefined) {
5540
5529
  try {
5541
5530
  await prepared.ownedEnv.destroy();
5542
5531
  }