@opengeni/worker-bundle 1.0.0-canary.1 → 1.0.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 (244) hide show
  1. package/dist/activities/agent-run-admission.d.ts +3 -1
  2. package/dist/activities/agent-turn/admission.d.ts +24 -3
  3. package/dist/activities/agent-turn/agent-build.d.ts +4 -0
  4. package/dist/activities/agent-turn/claude-usage-observer.d.ts +58 -0
  5. package/dist/activities/agent-turn/code-search.d.ts +60 -0
  6. package/dist/activities/agent-turn/codex-capacity.d.ts +13 -0
  7. package/dist/activities/agent-turn/codex-plan-entitlement.d.ts +105 -0
  8. package/dist/activities/agent-turn/codex.d.ts +3 -2
  9. package/dist/activities/agent-turn/compaction-prep.d.ts +21 -5
  10. package/dist/activities/agent-turn/credential-leases.d.ts +19 -33
  11. package/dist/activities/agent-turn/credential-restriction.d.ts +3 -0
  12. package/dist/activities/agent-turn/errors.d.ts +82 -11
  13. package/dist/activities/agent-turn/failure-settlement.d.ts +6 -3
  14. package/dist/activities/agent-turn/final-reply.d.ts +18 -0
  15. package/dist/activities/agent-turn/finalization-monitor.d.ts +17 -0
  16. package/dist/activities/agent-turn/finalization.d.ts +4 -2
  17. package/dist/activities/agent-turn/github-repository-bindings.d.ts +2 -0
  18. package/dist/activities/agent-turn/governance-model.d.ts +9 -2
  19. package/dist/activities/agent-turn/history-sink.d.ts +2 -0
  20. package/dist/activities/agent-turn/history.d.ts +2 -0
  21. package/dist/activities/agent-turn/input-wait-reply.d.ts +41 -0
  22. package/dist/activities/agent-turn/media-artifacts.d.ts +5 -3
  23. package/dist/activities/agent-turn/model-call-admission.d.ts +15 -0
  24. package/dist/activities/agent-turn/model-usage.d.ts +6 -0
  25. package/dist/activities/agent-turn/model-wire-dispatch.d.ts +15 -0
  26. package/dist/activities/agent-turn/optional-repositories.d.ts +59 -0
  27. package/dist/activities/agent-turn/provider-dispatch-barrier.d.ts +12 -1
  28. package/dist/activities/agent-turn/quiescence.d.ts +7 -7
  29. package/dist/activities/agent-turn/recovery-warning.d.ts +9 -0
  30. package/dist/activities/agent-turn/refresh-credentials.d.ts +6 -0
  31. package/dist/activities/agent-turn/run-credentials.d.ts +8 -3
  32. package/dist/activities/agent-turn/sandbox-establish.d.ts +2 -1
  33. package/dist/activities/agent-turn/sandbox-provision.d.ts +3 -1
  34. package/dist/activities/agent-turn/sandbox-route.d.ts +3 -20
  35. package/dist/activities/agent-turn/sandbox-runtime.d.ts +2 -2
  36. package/dist/activities/agent-turn/session-title.d.ts +34 -2
  37. package/dist/activities/agent-turn/skill-checkout.d.ts +25 -6
  38. package/dist/activities/agent-turn/skill-read.d.ts +70 -2
  39. package/dist/activities/agent-turn/skill-remove.d.ts +14 -0
  40. package/dist/activities/agent-turn/skill-search.d.ts +3 -1
  41. package/dist/activities/agent-turn/skill-tools.d.ts +15 -2
  42. package/dist/activities/agent-turn/skill-transfer.d.ts +18 -6
  43. package/dist/activities/agent-turn/stream-attempt.d.ts +7 -3
  44. package/dist/activities/agent-turn/subscription-lease.d.ts +38 -0
  45. package/dist/activities/agent-turn/tool-environment.d.ts +56 -6
  46. package/dist/activities/agent-turn/tool-policy.d.ts +43 -0
  47. package/dist/activities/agent-turn/turn-context.d.ts +29 -0
  48. package/dist/activities/agent-turn/turn-sandbox-access.d.ts +3 -0
  49. package/dist/activities/agent-turn/xai-capacity.d.ts +2 -1
  50. package/dist/activities/automations.d.ts +3 -1
  51. package/dist/activities/browser-deadline-checkpoint.d.ts +29 -0
  52. package/dist/activities/codemode-dispatcher.d.ts +9 -2
  53. package/dist/activities/codex-capacity.d.ts +18 -2
  54. package/dist/activities/codex-rotation.d.ts +7 -1
  55. package/dist/activities/context-compaction.d.ts +16 -2
  56. package/dist/activities/gmail-files.d.ts +32 -0
  57. package/dist/activities/goals.d.ts +8 -0
  58. package/dist/activities/knowledge-indexing.d.ts +19 -0
  59. package/dist/activities/mcp-account-routes.d.ts +3 -0
  60. package/dist/activities/mcp-credentials.d.ts +12 -13
  61. package/dist/activities/mcp-operation-authority.d.ts +0 -5
  62. package/dist/activities/model-call-fact-reconciler.d.ts +34 -0
  63. package/dist/activities/open-suffix-resume.d.ts +13 -0
  64. package/dist/activities/parent-wake.d.ts +6 -12
  65. package/dist/activities/retained-screenshots.d.ts +7 -2
  66. package/dist/activities/rig-verification.d.ts +2 -3
  67. package/dist/activities/run-credential-renewal.d.ts +1 -0
  68. package/dist/activities/run-credentials.d.ts +15 -3
  69. package/dist/activities/sandbox-images.d.ts +1 -1
  70. package/dist/activities/sandbox-lease.d.ts +47 -9
  71. package/dist/activities/scheduled-tasks.d.ts +4 -35
  72. package/dist/activities/session-state.d.ts +70 -3
  73. package/dist/activities/types.d.ts +55 -6
  74. package/dist/activities/workspace-capture.d.ts +2 -0
  75. package/dist/activities/workspace-credential-provider.d.ts +46 -0
  76. package/dist/{activities-control-HMFGSKC5.js → activities-control-BNPTRTOE.js} +1896 -418
  77. package/dist/activities-control-BNPTRTOE.js.map +1 -0
  78. package/dist/activities-control.d.ts +182 -4
  79. package/dist/{activities-turn-AP67JADS.js → activities-turn-5LGZOSVU.js} +4476 -1327
  80. package/dist/activities-turn-5LGZOSVU.js.map +1 -0
  81. package/dist/activities.d.ts +182 -4
  82. package/dist/artifact-materializer-entry.js +5 -2
  83. package/dist/artifact-materializer-entry.js.map +1 -1
  84. package/dist/artifact-outbox-entry.js +5 -2
  85. package/dist/artifact-outbox-entry.js.map +1 -1
  86. package/dist/browser-deadline-checkpoint-contract.d.ts +2 -0
  87. package/dist/{chunk-A4XQWVZK.js → chunk-3ZTRWDMN.js} +307 -4
  88. package/dist/chunk-3ZTRWDMN.js.map +1 -0
  89. package/dist/{chunk-RAMNYSAW.js → chunk-4ER3YXOT.js} +22 -4
  90. package/dist/chunk-4ER3YXOT.js.map +1 -0
  91. package/dist/{chunk-CYITU5UH.js → chunk-LJX7Z2KP.js} +1445 -343
  92. package/dist/chunk-LJX7Z2KP.js.map +1 -0
  93. package/dist/editable-artifact-materializer-service.d.ts +2 -2
  94. package/dist/editable-artifact-outbox-service.d.ts +2 -2
  95. package/dist/host-export-pump.d.ts +3 -1
  96. package/dist/host-export.d.ts +1 -1
  97. package/dist/host-export.js +1 -1
  98. package/dist/http.d.ts +1 -1
  99. package/dist/index.d.ts +95 -3
  100. package/dist/index.js +137 -43
  101. package/dist/index.js.map +1 -1
  102. package/dist/observability-metrics.d.ts +118 -8
  103. package/dist/retained-process-diagnostics.d.ts +4 -0
  104. package/dist/retained-process-retry.d.ts +7 -0
  105. package/dist/sandbox-resume.d.ts +55 -13
  106. package/dist/sandbox-routing.d.ts +5 -3
  107. package/dist/sandbox-snapshot-diagnostics.d.ts +20 -0
  108. package/dist/session-event-fanout.d.ts +6 -0
  109. package/dist/warm-capture-holder.d.ts +3 -0
  110. package/dist/worker-service-lifecycle.d.ts +12 -0
  111. package/dist/workflow-bundle.js +273 -41
  112. package/dist/workflows/activities.d.ts +1 -1
  113. package/dist/workflows/browser-deadline-checkpoint.d.ts +9 -0
  114. package/dist/workflows/scheduled-tasks.d.ts +4 -0
  115. package/dist/workflows/session.d.ts +36 -0
  116. package/dist/workflows.d.ts +1 -0
  117. package/package.json +21 -19
  118. package/src/activities/agent-run-admission.ts +17 -1
  119. package/src/activities/agent-turn/admission.ts +66 -3
  120. package/src/activities/agent-turn/agent-build.ts +84 -10
  121. package/src/activities/agent-turn/claim.ts +186 -43
  122. package/src/activities/agent-turn/claude-usage-observer.ts +221 -0
  123. package/src/activities/agent-turn/code-search.ts +275 -0
  124. package/src/activities/agent-turn/codex-capacity.ts +172 -8
  125. package/src/activities/agent-turn/codex-plan-entitlement.ts +254 -0
  126. package/src/activities/agent-turn/codex.ts +13 -5
  127. package/src/activities/agent-turn/compaction-prep.ts +170 -102
  128. package/src/activities/agent-turn/credential-leases.ts +122 -216
  129. package/src/activities/agent-turn/credential-restriction.ts +14 -0
  130. package/src/activities/agent-turn/errors.ts +647 -48
  131. package/src/activities/agent-turn/failure-settlement.ts +682 -130
  132. package/src/activities/agent-turn/final-reply.ts +46 -0
  133. package/src/activities/agent-turn/finalization-monitor.ts +120 -0
  134. package/src/activities/agent-turn/finalization.ts +146 -43
  135. package/src/activities/agent-turn/github-repository-bindings.ts +3 -1
  136. package/src/activities/agent-turn/governance-model.ts +69 -28
  137. package/src/activities/agent-turn/history-sink.ts +16 -2
  138. package/src/activities/agent-turn/history.ts +38 -11
  139. package/src/activities/agent-turn/input-wait-reply.ts +103 -0
  140. package/src/activities/agent-turn/knowledge-source-tools.ts +2 -0
  141. package/src/activities/agent-turn/media-artifacts.ts +2 -1
  142. package/src/activities/agent-turn/model-call-admission.ts +94 -0
  143. package/src/activities/agent-turn/model-usage.ts +64 -3
  144. package/src/activities/agent-turn/model-wire-dispatch.ts +27 -0
  145. package/src/activities/agent-turn/optional-repositories.ts +224 -0
  146. package/src/activities/agent-turn/provider-dispatch-barrier.ts +22 -0
  147. package/src/activities/agent-turn/quiescence.ts +10 -7
  148. package/src/activities/agent-turn/recovery-warning.ts +16 -0
  149. package/src/activities/agent-turn/refresh-credentials.ts +44 -0
  150. package/src/activities/agent-turn/run-credentials.ts +116 -55
  151. package/src/activities/agent-turn/run.ts +320 -93
  152. package/src/activities/agent-turn/sandbox-establish.ts +84 -8
  153. package/src/activities/agent-turn/sandbox-provision.ts +3 -1
  154. package/src/activities/agent-turn/sandbox-route.ts +4 -21
  155. package/src/activities/agent-turn/sandbox-runtime.ts +343 -18
  156. package/src/activities/agent-turn/session-title.ts +81 -5
  157. package/src/activities/agent-turn/skill-checkout.ts +111 -24
  158. package/src/activities/agent-turn/skill-install.ts +1 -1
  159. package/src/activities/agent-turn/skill-read.ts +374 -28
  160. package/src/activities/agent-turn/skill-remove.ts +46 -0
  161. package/src/activities/agent-turn/skill-save.ts +1 -1
  162. package/src/activities/agent-turn/skill-search.ts +10 -1
  163. package/src/activities/agent-turn/skill-selection.ts +4 -0
  164. package/src/activities/agent-turn/skill-tools.ts +112 -18
  165. package/src/activities/agent-turn/skill-transfer.ts +68 -23
  166. package/src/activities/agent-turn/stream-attempt.ts +313 -77
  167. package/src/activities/agent-turn/subscription-lease.ts +115 -0
  168. package/src/activities/agent-turn/tool-environment.ts +316 -167
  169. package/src/activities/agent-turn/tool-policy.ts +80 -1
  170. package/src/activities/agent-turn/turn-context.ts +35 -0
  171. package/src/activities/agent-turn/turn-sandbox-access.ts +7 -1
  172. package/src/activities/agent-turn/xai-capacity.ts +94 -51
  173. package/src/activities/automations.ts +76 -4
  174. package/src/activities/browser-deadline-checkpoint.ts +279 -0
  175. package/src/activities/capabilities.ts +108 -11
  176. package/src/activities/codemode-dispatcher.ts +36 -8
  177. package/src/activities/codex-capacity.ts +60 -25
  178. package/src/activities/codex-rotation.ts +39 -17
  179. package/src/activities/context-compaction.ts +75 -18
  180. package/src/activities/gmail-files.ts +88 -0
  181. package/src/activities/goals.ts +116 -121
  182. package/src/activities/google-drive-publication.ts +1 -3
  183. package/src/activities/image-generation-operation.ts +1 -1
  184. package/src/activities/knowledge-indexing.ts +269 -15
  185. package/src/activities/knowledge-source-sync.ts +10 -0
  186. package/src/activities/mcp-account-routes.ts +17 -0
  187. package/src/activities/mcp-credentials.ts +83 -194
  188. package/src/activities/mcp-operation-authority.ts +16 -41
  189. package/src/activities/model-call-fact-reconciler.ts +112 -0
  190. package/src/activities/open-suffix-resume.ts +15 -2
  191. package/src/activities/parent-wake.ts +46 -21
  192. package/src/activities/retained-screenshots.ts +82 -11
  193. package/src/activities/rig-verification.ts +38 -37
  194. package/src/activities/run-credential-renewal.ts +13 -1
  195. package/src/activities/run-credentials.ts +59 -17
  196. package/src/activities/run-input.ts +25 -16
  197. package/src/activities/sandbox-images.ts +0 -1
  198. package/src/activities/sandbox-lease.ts +798 -82
  199. package/src/activities/scheduled-tasks.ts +550 -178
  200. package/src/activities/session-state.ts +245 -6
  201. package/src/activities/site-auth-maintenance.ts +3 -0
  202. package/src/activities/types.ts +72 -5
  203. package/src/activities/workflow-wake.ts +2 -17
  204. package/src/activities/workspace-capture.ts +13 -2
  205. package/src/activities/workspace-credential-provider.ts +283 -0
  206. package/src/activities-control.ts +4 -0
  207. package/src/activities-turn.ts +5 -0
  208. package/src/activities.ts +13 -0
  209. package/src/activity-services.ts +16 -4
  210. package/src/browser-deadline-checkpoint-contract.ts +5 -0
  211. package/src/editable-artifact-hint-broker.ts +3 -0
  212. package/src/editable-artifact-materializer-service.ts +7 -3
  213. package/src/editable-artifact-outbox-service.ts +4 -4
  214. package/src/github-rest-mcp.ts +1 -2
  215. package/src/host-export-pump.ts +26 -4
  216. package/src/host-export.ts +3 -0
  217. package/src/http.ts +4 -2
  218. package/src/index.ts +99 -37
  219. package/src/observability-metrics.ts +510 -5
  220. package/src/personal-github-git-credentials.ts +2 -1
  221. package/src/pr-review-credentials.ts +1 -3
  222. package/src/retained-process-diagnostics.ts +50 -0
  223. package/src/retained-process-retry.ts +36 -0
  224. package/src/sandbox-resume.ts +388 -101
  225. package/src/sandbox-routing.ts +150 -25
  226. package/src/sandbox-snapshot-diagnostics.ts +112 -0
  227. package/src/session-event-fanout.ts +23 -0
  228. package/src/warm-capture-holder.ts +15 -0
  229. package/src/worker-service-lifecycle.ts +53 -0
  230. package/src/workflows/activities.ts +16 -1
  231. package/src/workflows/browser-deadline-checkpoint.ts +50 -0
  232. package/src/workflows/file-upload-reaper.ts +2 -0
  233. package/src/workflows/scheduled-tasks.ts +26 -13
  234. package/src/workflows/session.ts +242 -22
  235. package/src/workflows.ts +4 -0
  236. package/dist/activities/agent-turn/knowledge-sources.d.ts +0 -20
  237. package/dist/activities/packs.d.ts +0 -55
  238. package/dist/activities-control-HMFGSKC5.js.map +0 -1
  239. package/dist/activities-turn-AP67JADS.js.map +0 -1
  240. package/dist/chunk-A4XQWVZK.js.map +0 -1
  241. package/dist/chunk-CYITU5UH.js.map +0 -1
  242. package/dist/chunk-RAMNYSAW.js.map +0 -1
  243. package/src/activities/agent-turn/knowledge-sources.ts +0 -86
  244. package/src/activities/packs.ts +0 -166
@@ -1,35 +1,58 @@
1
+ import { environmentsEncryptionKeyBytes } from "@opengeni/config";
1
2
  import {
2
3
  requestSessionTurnRecovery,
3
4
  getSessionGoal,
4
5
  armXaiCapacityWait,
6
+ armClaudeCapacityWait,
7
+ reconcileClaudeCapacityWait,
8
+ recordClaudeAccountUsage,
9
+ resolveClaudeAccountCredential,
10
+ loadClaudeAccountCredential,
11
+ ClaudeSubscriptionConnectionChanged,
5
12
  reconcileXaiCapacityWait,
6
13
  listCodexAccountStatuses,
7
14
  quarantineCodexCredentialForLease,
8
15
  recordUsageEvent,
9
16
  getActiveSessionHistoryItemsPaged,
17
+ recheckCodexCredentialPlan,
10
18
  settleCodexCredentialLeaseLoss,
11
19
  settleCodexCredentialFailover,
12
20
  readLease,
13
21
  SandboxLeaseSupersededError,
14
22
  isSessionEventPersistenceError,
23
+ SANDBOX_SETUP_RECOVERY_LIMIT,
15
24
  type CodexLeaseAccountStatus,
16
25
  } from "@opengeni/db";
17
26
  import { publishDurableSessionEvents } from "@opengeni/events";
18
- import { maxTurnsExceededRunState } from "@opengeni/runtime";
19
- import { CancelledFailure } from "@temporalio/activity";
27
+ import {
28
+ maxTurnsExceededRunState,
29
+ isModalTaskExecStartPreDispatchUnavailableError,
30
+ isModalCommandStartOutcomeUnknownError,
31
+ isProviderCommandObservationUnavailableError,
32
+ } from "@opengeni/runtime";
33
+ import { ApplicationFailure, CancelledFailure } from "@temporalio/activity";
20
34
  import {
21
35
  authoritativeCodexCapacityResetAt,
22
36
  classifyCodexPin,
23
37
  selectCodexCredentialLeaseForTurn,
24
38
  type CodexRotationStrategy,
25
39
  } from "../codex-rotation";
26
- import type { Settings } from "@opengeni/config";
40
+ import { TurnExecutionPolicyDefinitionMismatchError, type Settings } from "@opengeni/config";
27
41
  import {
28
42
  classifyCodexEncryptedArtifactRejection,
43
+ classifyCodexEntitlementRejection,
29
44
  classifyCodexUsageLimitError,
30
45
  isCodexTransportError,
31
46
  type CodexUsageHeaderSnapshot,
32
47
  } from "@opengeni/codex";
48
+ import {
49
+ assessCodexPlanEntitlement,
50
+ codexAccountDisplayLabel,
51
+ codexPlanEntitlementFailurePayload,
52
+ codexRequestRejectedFailurePayload,
53
+ type CodexPlanEntitlementFailurePayload,
54
+ type CodexRequestRejectedFailurePayload,
55
+ } from "./codex-plan-entitlement";
33
56
  import { TurnAttemptFencedError } from "../turn-attempt-fenced";
34
57
  import { deliverFailedChildTurnToParent } from "../parent-wake";
35
58
  import type {
@@ -53,10 +76,13 @@ import {
53
76
  sandboxRouteTransitionCode,
54
77
  safeErrorDiagnostic,
55
78
  classifyXaiCredentialFailure,
79
+ classifyClaudeCredentialFailure,
56
80
  agentRunFailurePayload,
81
+ agentRunRecoveryFailurePayload,
57
82
  codexCredentialCooldownUntil,
58
83
  classifyCodexCredentialFailure,
59
84
  codexUsageLimitFailurePayload,
85
+ type CodexCredentialFailure,
60
86
  } from "./errors";
61
87
  import { selectRejectedProviderArtifactHistoryIds } from "./history";
62
88
  import { waitForTurnFinalizerStep, turnFinalizerCancellationSignal } from "./quiescence";
@@ -122,7 +148,7 @@ type CodexCapacityWaitFailurePayload = {
122
148
  * recover the same durable turn immediately.
123
149
  */
124
150
  export function codexDefinitiveFailureDisposition(input: {
125
- failureKind: "auth" | "forbidden" | "rate_limit" | "quota";
151
+ failureKind: CodexCredentialFailure["kind"];
126
152
  rotationEnabled: boolean;
127
153
  pinDisposition: "manual" | "sharded" | "clearStale" | "unpinned";
128
154
  decisionKind: "active" | "allCapped" | "none";
@@ -136,6 +162,17 @@ export function codexDefinitiveFailureDisposition(input: {
136
162
  input.decisionCredentialId !== null &&
137
163
  input.decisionCredentialId !== input.servingCredentialId;
138
164
  if (alternateAvailable) return "failover";
165
+ // A plan that no longer includes the model does not recover by itself, and
166
+ // a manual pin or rotation-off pointer names exactly that account. Only a
167
+ // rotation-on pool whose OTHER accounts are temporarily capped is worth a
168
+ // durable wait; everything else fails the turn with typed copy.
169
+ if (input.failureKind === "plan_entitlement") {
170
+ return input.decisionKind === "allCapped" &&
171
+ input.rotationEnabled &&
172
+ input.pinDisposition !== "manual"
173
+ ? "wait"
174
+ : "terminal";
175
+ }
139
176
  if (
140
177
  input.failureKind === "quota" ||
141
178
  input.failureKind === "rate_limit" ||
@@ -167,12 +204,23 @@ export function codexCredentialFailoverLimit(
167
204
 
168
205
  /** Build the durable waiter payload without collapsing quota refusals into 403. */
169
206
  export function codexCapacityWaitFailurePayload(input: {
170
- failureKind: "auth" | "forbidden" | "rate_limit" | "quota";
207
+ failureKind: CodexCredentialFailure["kind"];
171
208
  usageLimit: { resetsInSeconds: number | null } | null;
172
209
  cooldownSeconds: number | null;
173
210
  detail: string;
174
211
  allAccounts: boolean;
212
+ planEntitlement?: CodexPlanEntitlementFailurePayload | null;
175
213
  }): CodexCapacityWaitFailurePayload {
214
+ if (input.failureKind === "plan_entitlement") {
215
+ return {
216
+ error:
217
+ input.planEntitlement?.error ??
218
+ "The serving ChatGPT account's plan does not include this model. Opengeni is waiting for another connected account to become available.",
219
+ code: "codex_plan_entitlement",
220
+ detail: input.detail,
221
+ retryable: false,
222
+ };
223
+ }
176
224
  if (input.failureKind === "quota") {
177
225
  return codexUsageLimitFailurePayload(
178
226
  input.usageLimit ?? { resetsInSeconds: input.cooldownSeconds },
@@ -225,6 +273,42 @@ function acceptedCodexPolicySnapshot(
225
273
  }
226
274
 
227
275
  export async function settleTurnFailure(deps: TurnFailureDeps): Promise<RunAgentTurnResult> {
276
+ try {
277
+ return await settleTurnFailureInAttempt(deps);
278
+ } catch (error) {
279
+ // Connectivity can disappear while settling an unrelated run error too.
280
+ // Do not overwrite a possibly committed settlement; the control lane
281
+ // re-reads exact ownership and becomes a stale no-op if it already closed.
282
+ if (deps.attempt.turnId && deps.attempt.triggerEventId) {
283
+ const recovery = postClaimDatabaseRecoveryFailure({
284
+ error,
285
+ turnId: deps.attempt.turnId,
286
+ triggerEventId: deps.attempt.triggerEventId,
287
+ executionGeneration: deps.attempt.executionGeneration,
288
+ requireDatabaseProvenance: true,
289
+ });
290
+ if (recovery) {
291
+ deps.control.activityStatus = "recovering";
292
+ deps.control.turnMetricOutcome = "recovering";
293
+ deps.control.activityError = error;
294
+ throw recovery;
295
+ }
296
+ }
297
+ throw error;
298
+ }
299
+ }
300
+
301
+ async function settleTurnFailureInAttempt(deps: TurnFailureDeps): Promise<RunAgentTurnResult> {
302
+ if (deps.settings.environment === "local" && deps.error instanceof Error) {
303
+ // Keep local startup failures diagnosable without logging error messages,
304
+ // absolute host paths, prompts, credentials, or provider response bodies.
305
+ const locations = (deps.error.stack ?? "")
306
+ .split("\n")
307
+ .slice(1)
308
+ .flatMap((line) => line.match(/(?:apps|packages)\/[A-Za-z0-9_./-]+:\d+:\d+/g) ?? [])
309
+ .slice(0, 8);
310
+ console.error(JSON.stringify({ message: "Local turn failure source locations", locations }));
311
+ }
228
312
  const {
229
313
  error,
230
314
  input,
@@ -249,6 +333,36 @@ export async function settleTurnFailure(deps: TurnFailureDeps): Promise<RunAgent
249
333
  acknowledgeLostAttemptOwnership,
250
334
  acknowledgeRecoveryQuiescence,
251
335
  } = deps;
336
+ // Capture before any recovery/checkpoint DB operation can fail again.
337
+ if (isSessionEventPersistenceError(error)) {
338
+ try {
339
+ const diagnosticId = observability.recordFailureDiagnostic({
340
+ code: error.details.code,
341
+ stage:
342
+ error.details.stage === "session_events.append_generic"
343
+ ? "session_events.append_generic"
344
+ : error.details.stage === "session_events.append_for_turn_attempt"
345
+ ? "session_events.append_for_turn_attempt"
346
+ : error.details.stage === "session_attempts.claim"
347
+ ? "session_attempts.claim"
348
+ : "failure_settlement",
349
+ retryDecision: error.details.retryOutcome,
350
+ error,
351
+ sessionId: input.sessionId,
352
+ ...(attempt.turnId ? { turnId: attempt.turnId } : {}),
353
+ attemptId: input.attemptId,
354
+ attempts: error.details.attempts,
355
+ eventTypes: error.details.eventTypes,
356
+ sqlState: error.details.sqlState,
357
+ ...(error.details.database.constraint
358
+ ? { constraint: error.details.database.constraint }
359
+ : {}),
360
+ });
361
+ observability.error("session event persistence failed", { correlationId: diagnosticId });
362
+ } catch {
363
+ // Failure settlement must not depend on telemetry availability.
364
+ }
365
+ }
252
366
  // Graceful worker shutdown (deploy / rollout restart): checkpoint the
253
367
  // same current inference for a new fenced attempt instead of failing the
254
368
  // session. Conversation truth is already persisted per model response;
@@ -262,6 +376,69 @@ export async function settleTurnFailure(deps: TurnFailureDeps): Promise<RunAgent
262
376
  // back to the workflow-claimed turn when the local lookup had not
263
377
  // finished yet.
264
378
  const recoveryTurnId = attempt.turnId;
379
+ // Unlike proven pre-dispatch failure, a genuine SDK Start/Wait uncertainty
380
+ // cannot reconstruct setup on a replacement attempt. The exact command and
381
+ // writer remain retained by sandbox-runtime; the logical turn is parked as
382
+ // recovering with a durable no-replay marker, not failed or completed.
383
+ const observationUnavailable = isProviderCommandObservationUnavailableError(error);
384
+ if (
385
+ (isModalCommandStartOutcomeUnknownError(error) || observationUnavailable) &&
386
+ recoveryTurnId &&
387
+ attempt.triggerEventId &&
388
+ attempt.executionGeneration > 0
389
+ ) {
390
+ let recovery: Awaited<ReturnType<typeof requestSessionTurnRecovery>>;
391
+ try {
392
+ if (eventing.turnStartedPublished) {
393
+ await flushRuntimeBatcher();
394
+ await historySink.reconcileConversationTruth({ requireDurable: true });
395
+ }
396
+ recovery = await requestSessionTurnRecovery(db, input.workspaceId, {
397
+ sessionId: input.sessionId,
398
+ turnId: recoveryTurnId,
399
+ triggerEventId: attempt.triggerEventId,
400
+ attemptId: input.attemptId,
401
+ reason: observationUnavailable
402
+ ? "sandbox_command_observation_unavailable"
403
+ : "sandbox_command_start_outcome_unknown",
404
+ sandboxSetupOutcomeUnknown: true,
405
+ detail: {
406
+ code: observationUnavailable
407
+ ? "sandbox_command_observation_unavailable"
408
+ : "sandbox_command_start_outcome_unknown",
409
+ retryable: false,
410
+ setupOutcome: "unknown",
411
+ replay: "blocked",
412
+ providerRecoveryCount: attempt.providerRecoveryCount,
413
+ },
414
+ });
415
+ } catch (checkpointError) {
416
+ const databaseRecovery = postClaimDatabaseRecoveryFailure({
417
+ error: checkpointError,
418
+ turnId: recoveryTurnId,
419
+ triggerEventId: attempt.triggerEventId,
420
+ executionGeneration: attempt.executionGeneration,
421
+ sandboxSetupOutcomeUnknown: true,
422
+ });
423
+ if (!databaseRecovery) throw checkpointError;
424
+ control.activityStatus = "recovering";
425
+ control.turnMetricOutcome = "recovering";
426
+ control.activityError = error;
427
+ throw databaseRecovery;
428
+ }
429
+ if (recovery.action === "stale") {
430
+ acknowledgeLostAttemptOwnership();
431
+ control.activityStatus = "cancelled";
432
+ control.turnMetricOutcome = "cancelled";
433
+ return claimedResult({ status: "cancelled" });
434
+ }
435
+ acknowledgeRecoveryQuiescence();
436
+ await publishDurableSessionEvents(bus, input.workspaceId, input.sessionId, recovery.events);
437
+ control.activityStatus = "recovering";
438
+ control.turnMetricOutcome = "recovering";
439
+ control.activityError = error;
440
+ return claimedResult({ status: "recovering", deferredUntilWake: true });
441
+ }
265
442
  // A true epoch supersession and a provider lifecycle transition are both
266
443
  // recoverable control-plane states, never session failures. A rotation
267
444
  // persists an exact group/epoch wait marker so the workflow parks before
@@ -340,13 +517,11 @@ export async function settleTurnFailure(deps: TurnFailureDeps): Promise<RunAgent
340
517
  throw recoveryError;
341
518
  }
342
519
  }
343
- // A managed-home session can start directly on a Connected Machine without
344
- // creating or leasing its cloud home. If sandbox_attach/sandbox_swap then
345
- // clears the durable pointer to home, the commit is valid but this exact
346
- // attempt cannot serve a later home operation. Preserve the completed attach
520
+ // A route change can require a different home, filesystem root, or native
521
+ // capability set than this attempt established. Preserve the completed attach
347
522
  // and every preceding model/tool receipt, close only the unresolved suffix,
348
523
  // and continue the SAME logical turn in a fresh attempt. That next attempt
349
- // starts from the now-null pointer and establishes home normally.
524
+ // starts from the committed pointer and establishes its route normally.
350
525
  const routeTransitionCode = sandboxRouteTransitionCode(error);
351
526
  if (routeTransitionCode && recoveryTurnId && eventing.publish && eventing.turnStartedPublished) {
352
527
  try {
@@ -370,7 +545,7 @@ export async function settleTurnFailure(deps: TurnFailureDeps): Promise<RunAgent
370
545
  return claimedResult({ status: "cancelled" });
371
546
  }
372
547
  if (recovery.action !== "recovering") {
373
- throw new Error("Home sandbox route transition could not recover the current turn");
548
+ throw new Error("Sandbox route transition could not recover the current turn");
374
549
  }
375
550
  acknowledgeRecoveryQuiescence();
376
551
  await publishDurableSessionEvents(bus, input.workspaceId, input.sessionId, recovery.events);
@@ -504,6 +679,27 @@ export async function settleTurnFailure(deps: TurnFailureDeps): Promise<RunAgent
504
679
  control.turnMetricOutcome = "cancelled";
505
680
  throw cancellationFailure;
506
681
  }
682
+ if (attempt.turnId && attempt.triggerEventId && attempt.executionGeneration > 0) {
683
+ const recoveryFailure = postClaimDatabaseRecoveryFailure({
684
+ error,
685
+ turnId: attempt.turnId,
686
+ triggerEventId: attempt.triggerEventId,
687
+ executionGeneration: attempt.executionGeneration,
688
+ requireDatabaseProvenance: eventing.turnStartedPublished || attempt.modelRequestStarted,
689
+ });
690
+ if (recoveryFailure) {
691
+ // Stop this non-retryable activity without terminal logical settlement.
692
+ // Do not retry a delta/tool-ledger mutation with an unknown commit, nor
693
+ // claim that DB-down cleanup proved writer exit. Finally still drains
694
+ // the physical writers; the existing DB-only workflow lane closes this
695
+ // exact owner after connectivity returns and admission waits for its
696
+ // ordinary durable quiescence/writer fences.
697
+ control.activityStatus = "recovering";
698
+ control.turnMetricOutcome = "recovering";
699
+ control.activityError = error;
700
+ throw recoveryFailure;
701
+ }
702
+ }
507
703
  // The SDK's per-segment turn cap is a pacing valve, not a failure: end
508
704
  // the turn gracefully and idle the session so an active goal continues
509
705
  // via a synthesized continuation turn (or a user message resumes work).
@@ -642,13 +838,13 @@ export async function settleTurnFailure(deps: TurnFailureDeps): Promise<RunAgent
642
838
  leases.codex.generation,
643
839
  );
644
840
  }
645
- if (
646
- leases.xai.lost &&
647
- billingState.isXaiTurn &&
648
- eventing.publish &&
649
- attempt.turnId &&
650
- eventing.turnStartedPublished
651
- ) {
841
+ const scopedLeaseLost =
842
+ billingState.isClaudeTurn && leases.claude.lost
843
+ ? "claude"
844
+ : billingState.isXaiTurn && leases.xai.lost
845
+ ? "xai"
846
+ : null;
847
+ if (scopedLeaseLost && eventing.publish && attempt.turnId && eventing.turnStartedPublished) {
652
848
  await flushRuntimeBatcher();
653
849
  await historySink.reconcileConversationTruth({ requireDurable: true });
654
850
  const recovery = await requestSessionTurnRecovery(db, input.workspaceId, {
@@ -656,8 +852,10 @@ export async function settleTurnFailure(deps: TurnFailureDeps): Promise<RunAgent
656
852
  turnId: attempt.turnId,
657
853
  triggerEventId: attempt.triggerEventId!,
658
854
  attemptId: input.attemptId,
659
- reason: "xai_lease_lost",
660
- detail: { provider: "supergrok-subscription" },
855
+ reason: scopedLeaseLost + "_lease_lost",
856
+ detail: {
857
+ provider: scopedLeaseLost === "claude" ? "claude-subscription" : "supergrok-subscription",
858
+ },
661
859
  });
662
860
  if (recovery.action === "stale") {
663
861
  acknowledgeLostAttemptOwnership();
@@ -678,10 +876,107 @@ export async function settleTurnFailure(deps: TurnFailureDeps): Promise<RunAgent
678
876
  // 5xx does not classify here and therefore cannot consume another
679
877
  // subscription or duplicate a side effect.
680
878
  const usageLimit = isCodexTransportError(error) ? classifyCodexUsageLimitError(error) : null;
681
- const codexCredentialFailure =
879
+ let codexCredentialFailure: CodexCredentialFailure | null =
682
880
  billingState.isCodexTurn && providerTurn.effectiveCodexCredentialId
683
881
  ? classifyCodexCredentialFailure(error)
684
882
  : null;
883
+ // Plan entitlement evidence (an explicit plan refusal, or an HTTP 400 with no
884
+ // body) is ambiguous until the serving account's CURRENT plan is re-read. A
885
+ // proven loss becomes a definitive `plan_entitlement` refusal for this model
886
+ // only and walks the pool through the same checkpointed failover below; an
887
+ // unexplained rejection stays terminal with typed copy. Nothing retries the
888
+ // rejected request itself.
889
+ let codexTerminalFailure:
890
+ | CodexPlanEntitlementFailurePayload
891
+ | CodexRequestRejectedFailurePayload
892
+ | null = null;
893
+ let codexPlanEntitlement: {
894
+ modelId: string;
895
+ planType: string | null;
896
+ planObserved: boolean;
897
+ credentialVersion: number | null;
898
+ waitPayload: CodexPlanEntitlementFailurePayload;
899
+ } | null = null;
900
+ const codexEntitlementRejection =
901
+ billingState.isCodexTurn && providerTurn.effectiveCodexCredentialId && !codexCredentialFailure
902
+ ? classifyCodexEntitlementRejection(error)
903
+ : null;
904
+ if (
905
+ codexEntitlementRejection &&
906
+ providerTurn.effectiveCodexCredentialId &&
907
+ eventing.publish &&
908
+ attempt.turnId &&
909
+ eventing.turnStartedPublished &&
910
+ leases.codex.holderId &&
911
+ leases.codex.generation !== null
912
+ ) {
913
+ const servingCredentialId = providerTurn.effectiveCodexCredentialId;
914
+ const servingAccount = (
915
+ await listCodexAccountStatuses(db, input.workspaceId, attempt.turnId).catch(() => [])
916
+ ).find((account) => account.id === servingCredentialId);
917
+ const accountLabel = codexAccountDisplayLabel(servingAccount);
918
+ const recheck = await recheckCodexCredentialPlan(
919
+ db,
920
+ settings,
921
+ input.workspaceId,
922
+ servingCredentialId,
923
+ {
924
+ turnId: attempt.turnId,
925
+ holderId: leases.codex.holderId,
926
+ generation: leases.codex.generation,
927
+ },
928
+ ).catch(() => null);
929
+ const modelId = providerTurn.codexProductModelId ?? null;
930
+ const assessment = recheck
931
+ ? assessCodexPlanEntitlement(codexEntitlementRejection, recheck, modelId)
932
+ : codexEntitlementRejection.evidence === "plan_entitlement"
933
+ ? {
934
+ kind: "entitlement_lost" as const,
935
+ planType: servingAccount?.planType ?? null,
936
+ planObserved: false,
937
+ planChanged: false,
938
+ credentialVersion: null,
939
+ }
940
+ : { kind: "unexplained" as const, planType: null };
941
+ observability.incrementCounter({
942
+ name: "opengeni_codex_plan_rechecks_total",
943
+ help: "Codex plan re-checks after an entitlement-shaped rejection, by outcome.",
944
+ labels: {
945
+ workspace_key: codexWorkspaceKey,
946
+ evidence: codexEntitlementRejection.evidence,
947
+ outcome: assessment.kind,
948
+ source: recheck?.source ?? "none",
949
+ },
950
+ });
951
+ if (assessment.kind === "entitlement_lost") {
952
+ const payloadInput = {
953
+ accountLabel,
954
+ // Name a plan only when the provider just reported it; a recorded
955
+ // plan may be the very one that changed.
956
+ planType: assessment.planObserved ? assessment.planType : null,
957
+ planChanged: assessment.planChanged,
958
+ modelId,
959
+ rejection: codexEntitlementRejection,
960
+ };
961
+ codexTerminalFailure = codexPlanEntitlementFailurePayload(payloadInput);
962
+ if (modelId) {
963
+ codexCredentialFailure = { kind: "plan_entitlement", cooldownSeconds: null };
964
+ codexPlanEntitlement = {
965
+ modelId,
966
+ planType: assessment.planType,
967
+ planObserved: assessment.planObserved,
968
+ credentialVersion: assessment.credentialVersion,
969
+ waitPayload: codexPlanEntitlementFailurePayload({ ...payloadInput, waiting: true }),
970
+ };
971
+ }
972
+ } else {
973
+ codexTerminalFailure = codexRequestRejectedFailurePayload({
974
+ accountLabel,
975
+ planType: recheck?.planType ?? null,
976
+ rejection: codexEntitlementRejection,
977
+ });
978
+ }
979
+ }
685
980
  if (
686
981
  codexCredentialFailure &&
687
982
  providerTurn.effectiveCodexCredentialId &&
@@ -724,7 +1019,9 @@ export async function settleTurnFailure(deps: TurnFailureDeps): Promise<RunAgent
724
1019
  labels: { workspace_key: codexWorkspaceKey, outcome: "completed" },
725
1020
  });
726
1021
  const now = new Date();
727
- const before = await listCodexAccountStatuses(db, input.workspaceId).catch(() => []);
1022
+ const before = await listCodexAccountStatuses(db, input.workspaceId, attempt.turnId).catch(
1023
+ () => [],
1024
+ );
728
1025
  const servingCached = before.find(
729
1026
  (account) => account.id === providerTurn.effectiveCodexCredentialId,
730
1027
  );
@@ -743,10 +1040,14 @@ export async function settleTurnFailure(deps: TurnFailureDeps): Promise<RunAgent
743
1040
  }
744
1041
  : null;
745
1042
  const cooldownUntil = codexCredentialCooldownUntil(codexCredentialFailure, serving, now);
1043
+ // A plan re-check may itself have rotated tokens (same family, version
1044
+ // CAS-advanced by this holder); fence the quarantine on that version.
1045
+ const quarantineCredentialVersion =
1046
+ codexPlanEntitlement?.credentialVersion ?? providerTurn.effectiveCodexCredentialVersion;
746
1047
  const quarantineResult =
747
1048
  leases.codex.holderId &&
748
1049
  leases.codex.generation !== null &&
749
- providerTurn.effectiveCodexCredentialVersion !== null
1050
+ quarantineCredentialVersion !== null
750
1051
  ? await quarantineCodexCredentialForLease(db, {
751
1052
  accountId: input.accountId,
752
1053
  workspaceId: input.workspaceId,
@@ -759,7 +1060,7 @@ export async function settleTurnFailure(deps: TurnFailureDeps): Promise<RunAgent
759
1060
  dispatchId: attempt.dispatchId,
760
1061
  expectedRedispatches: attempt.redispatchesAtDispatch,
761
1062
  credentialId: providerTurn.effectiveCodexCredentialId,
762
- credentialVersion: providerTurn.effectiveCodexCredentialVersion,
1063
+ credentialVersion: quarantineCredentialVersion,
763
1064
  holderId: leases.codex.holderId,
764
1065
  generation: leases.codex.generation,
765
1066
  maxFailovers: providerTurn.codexCredentialFailoverLimit,
@@ -776,11 +1077,18 @@ export async function settleTurnFailure(deps: TurnFailureDeps): Promise<RunAgent
776
1077
  status: "error",
777
1078
  lastError: "model request was forbidden for this credential",
778
1079
  }
779
- : {
780
- kind: "cooldown",
781
- until: cooldownUntil!,
782
- cooldownKind: codexCredentialFailure.kind,
783
- },
1080
+ : codexCredentialFailure.kind === "plan_entitlement"
1081
+ ? {
1082
+ kind: "plan_entitlement",
1083
+ modelId: codexPlanEntitlement!.modelId,
1084
+ planType: codexPlanEntitlement!.planType,
1085
+ planObserved: codexPlanEntitlement!.planObserved,
1086
+ }
1087
+ : {
1088
+ kind: "cooldown",
1089
+ until: cooldownUntil!,
1090
+ cooldownKind: codexCredentialFailure.kind,
1091
+ },
784
1092
  })
785
1093
  : null;
786
1094
  if (
@@ -892,7 +1200,7 @@ export async function settleTurnFailure(deps: TurnFailureDeps): Promise<RunAgent
892
1200
  }
893
1201
  let accounts: Awaited<ReturnType<typeof listCodexAccountStatuses>>;
894
1202
  try {
895
- accounts = await listCodexAccountStatuses(db, input.workspaceId);
1203
+ accounts = await listCodexAccountStatuses(db, input.workspaceId, attempt.turnId);
896
1204
  } catch (metadataError) {
897
1205
  // Current account health/cooldown metadata is still required after
898
1206
  // quarantine. Operational database failures re-enter the existing
@@ -921,6 +1229,9 @@ export async function settleTurnFailure(deps: TurnFailureDeps): Promise<RunAgent
921
1229
  rotationStrategy: acceptedPolicy.rotationStrategy,
922
1230
  existingCredentialId: null,
923
1231
  failedCredentialIds: [providerTurn.effectiveCodexCredentialId],
1232
+ ...(providerTurn.codexProductModelId
1233
+ ? { modelId: providerTurn.codexProductModelId }
1234
+ : {}),
924
1235
  policyScope: null,
925
1236
  unavailableDiagnostics: [],
926
1237
  },
@@ -1079,9 +1390,10 @@ export async function settleTurnFailure(deps: TurnFailureDeps): Promise<RunAgent
1079
1390
  const capacityAccounts = policyCredentialId
1080
1391
  ? accounts.filter((account) => account.id === policyCredentialId)
1081
1392
  : accounts;
1082
- const authoritativeResetAt = exactProviderReset
1083
- ? (authoritativeCodexCapacityResetAt(capacityAccounts, now) ?? cooldownUntil)
1084
- : null;
1393
+ const authoritativeResetAt =
1394
+ exactProviderReset || codexCredentialFailure.kind === "plan_entitlement"
1395
+ ? (authoritativeCodexCapacityResetAt(capacityAccounts, now) ?? cooldownUntil)
1396
+ : null;
1085
1397
  const allAccounts =
1086
1398
  acceptedPolicy.rotationEnabled &&
1087
1399
  pinDisposition !== "manual" &&
@@ -1097,6 +1409,7 @@ export async function settleTurnFailure(deps: TurnFailureDeps): Promise<RunAgent
1097
1409
  : String(error)
1098
1410
  : "the same accepted turn is waiting for eligible credential capacity",
1099
1411
  allAccounts,
1412
+ planEntitlement: codexPlanEntitlement?.waitPayload ?? null,
1100
1413
  });
1101
1414
  const evaluated = await armAndReconcileCodexCapacityWait(
1102
1415
  { db, bus },
@@ -1110,6 +1423,8 @@ export async function settleTurnFailure(deps: TurnFailureDeps): Promise<RunAgent
1110
1423
  goalId: activeGoal?.id ?? null,
1111
1424
  goalVersion: activeGoal?.version ?? null,
1112
1425
  earliestResetAt: authoritativeResetAt,
1426
+ // plan_entitlement waits only on OTHER capped accounts, so it keeps
1427
+ // their reset/bounded-refresh cadence rather than a mutation-only wait.
1113
1428
  resetKind: authoritativeResetAt
1114
1429
  ? "authoritative"
1115
1430
  : codexCredentialFailure.kind === "auth" ||
@@ -1126,6 +1441,11 @@ export async function settleTurnFailure(deps: TurnFailureDeps): Promise<RunAgent
1126
1441
  { onArmed: () => (leases.codex.held = false) },
1127
1442
  );
1128
1443
  control.activityError = error;
1444
+ if (evaluated.action === "stopped") {
1445
+ control.activityStatus = evaluated.sessionStatus === "queued" ? "idle" : "failed";
1446
+ control.turnMetricOutcome = "failed";
1447
+ return claimedResult({ status: control.activityStatus });
1448
+ }
1129
1449
  if (evaluated.action === "resumed") {
1130
1450
  control.activityStatus = "recovering";
1131
1451
  control.turnMetricOutcome = "recovering";
@@ -1152,83 +1472,265 @@ export async function settleTurnFailure(deps: TurnFailureDeps): Promise<RunAgent
1152
1472
  }
1153
1473
  }
1154
1474
  }
1155
- const xaiCredentialFailure =
1475
+ const xaiFailure =
1156
1476
  billingState.isXaiTurn && providerTurn.effectiveXaiCredentialId
1157
1477
  ? classifyXaiCredentialFailure(error)
1158
1478
  : null;
1479
+ const claudeFailure =
1480
+ billingState.isClaudeTurn && providerTurn.effectiveClaudeCredentialId
1481
+ ? classifyClaudeCredentialFailure(error)
1482
+ : null;
1483
+ const scopedFailure = claudeFailure ?? xaiFailure;
1484
+ const scopedProvider = claudeFailure ? ("claude" as const) : ("xai" as const);
1485
+ const scopedName = claudeFailure ? "Claude" : "SuperGrok";
1486
+ const scopedCredentialId = claudeFailure
1487
+ ? providerTurn.effectiveClaudeCredentialId
1488
+ : providerTurn.effectiveXaiCredentialId;
1489
+ const scopedAuthority = claudeFailure
1490
+ ? providerTurn.claudeAuthoritySnapshot
1491
+ : providerTurn.xaiAuthoritySnapshot;
1492
+ const scopedLease = claudeFailure ? leases.claude : leases.xai;
1493
+ const armScopedWait = claudeFailure ? armClaudeCapacityWait : armXaiCapacityWait;
1494
+ const reconcileScopedWait = claudeFailure
1495
+ ? reconcileClaudeCapacityWait
1496
+ : reconcileXaiCapacityWait;
1159
1497
  if (
1160
- xaiCredentialFailure &&
1161
- providerTurn.effectiveXaiCredentialId &&
1162
- providerTurn.xaiAuthoritySnapshot &&
1163
- leases.xai.subjectId &&
1164
- leases.xai.holderId &&
1165
- leases.xai.generation !== null &&
1498
+ scopedFailure &&
1499
+ scopedCredentialId &&
1500
+ scopedAuthority &&
1501
+ scopedLease.subjectId &&
1502
+ scopedLease.holderId &&
1503
+ scopedLease.generation !== null &&
1166
1504
  eventing.publish &&
1167
1505
  attempt.turnId &&
1168
1506
  eventing.turnStartedPublished
1169
1507
  ) {
1170
1508
  await flushRuntimeBatcher();
1171
1509
  await historySink.reconcileConversationTruth({ requireDurable: true });
1510
+ const recoverChangedAccount = async () => {
1511
+ const recovery = await requestSessionTurnRecovery(db, input.workspaceId, {
1512
+ sessionId: input.sessionId,
1513
+ turnId: attempt.turnId!,
1514
+ triggerEventId: attempt.triggerEventId!,
1515
+ attemptId: input.attemptId,
1516
+ reason: "claude_credential_changed",
1517
+ detail: { code: "claude_credential_changed", retryable: true },
1518
+ });
1519
+ if (recovery.action === "stale") {
1520
+ acknowledgeLostAttemptOwnership();
1521
+ control.activityStatus = "cancelled";
1522
+ control.turnMetricOutcome = "cancelled";
1523
+ return claimedResult({ status: "cancelled" });
1524
+ }
1525
+ acknowledgeRecoveryQuiescence();
1526
+ await publishDurableSessionEvents(bus, input.workspaceId, input.sessionId, recovery.events);
1527
+ control.activityStatus = "recovering";
1528
+ control.turnMetricOutcome = "recovering";
1529
+ control.activityError = error;
1530
+ return claimedResult({ status: "recovering" });
1531
+ };
1172
1532
  const goal = await getSessionGoal(db, input.workspaceId, input.sessionId).catch(() => null);
1173
1533
  const activeGoal = goal?.status === "active" ? goal : null;
1174
1534
  const now = new Date();
1535
+ let claudeTokenFence: { encryptionKey: Uint8Array; observedAccessToken: string } | undefined;
1175
1536
  const cooldownUntil =
1176
- xaiCredentialFailure.kind === "rate_limit"
1177
- ? new Date(now.getTime() + Math.max(1, xaiCredentialFailure.cooldownMs ?? 60_000))
1537
+ scopedFailure.kind === "rate_limit"
1538
+ ? new Date(now.getTime() + Math.max(1, scopedFailure.cooldownMs ?? 60_000))
1178
1539
  : null;
1540
+ if (claudeFailure) {
1541
+ const key = environmentsEncryptionKeyBytes(settings);
1542
+ const matchingReceipts = [...providerTurn.latestClaudeUsage.values()].filter(
1543
+ (value) =>
1544
+ value.expectedConnectionId === scopedCredentialId &&
1545
+ value.expectedCredentialVersion === providerTurn.effectiveClaudeCredentialVersion &&
1546
+ value.upstreamModelId === providerTurn.claudeUpstreamModelId &&
1547
+ (value.responseStatus === (scopedFailure.kind === "rate_limit" ? 429 : 401) ||
1548
+ (value.responseStatus === 200 &&
1549
+ !!claudeFailure.requestId &&
1550
+ value.requestId === claudeFailure.requestId)) &&
1551
+ (!claudeFailure.requestId || value.requestId === claudeFailure.requestId),
1552
+ );
1553
+ const receipt = matchingReceipts.length === 1 ? matchingReceipts[0] : undefined;
1554
+ if (
1555
+ !key ||
1556
+ !providerTurn.claudeUpstreamModelId ||
1557
+ providerTurn.effectiveClaudeCredentialVersion === null
1558
+ )
1559
+ throw new Error("Claude refused request has no exact serving account");
1560
+ if (!receipt) {
1561
+ // Refresh may discover a revoked grant before any physical model call.
1562
+ // Verify durable reconnect evidence instead of inventing a response receipt.
1563
+ const current = await loadClaudeAccountCredential(
1564
+ db,
1565
+ {
1566
+ accountId: input.accountId,
1567
+ workspaceId: input.workspaceId,
1568
+ subjectId: scopedLease.subjectId,
1569
+ credentialId: scopedCredentialId,
1570
+ authoritySnapshot: scopedAuthority,
1571
+ },
1572
+ key,
1573
+ );
1574
+ if (current.version !== providerTurn.effectiveClaudeCredentialVersion)
1575
+ return await recoverChangedAccount();
1576
+ if (scopedFailure.kind !== "auth" || current.usage.refreshStatus !== "reconnect")
1577
+ throw new Error("Claude refused request has no exact account receipt");
1578
+ claudeTokenFence = { encryptionKey: key, observedAccessToken: current.secret.token };
1579
+ }
1580
+ if (receipt) claudeTokenFence = { encryptionKey: key, observedAccessToken: receipt.token };
1581
+ if (
1582
+ receipt &&
1583
+ scopedFailure.kind === "auth" &&
1584
+ !(
1585
+ attempt.claudeAuthRecovery?.credentialId === scopedCredentialId &&
1586
+ attempt.claudeAuthRecovery.credentialVersion === receipt.expectedCredentialVersion
1587
+ )
1588
+ ) {
1589
+ const credential = await resolveClaudeAccountCredential(
1590
+ db,
1591
+ settings,
1592
+ {
1593
+ accountId: input.accountId,
1594
+ workspaceId: input.workspaceId,
1595
+ subjectId: scopedLease.subjectId,
1596
+ credentialId: scopedCredentialId,
1597
+ authoritySnapshot: scopedAuthority,
1598
+ },
1599
+ {
1600
+ expectedCredentialVersion: receipt.expectedCredentialVersion,
1601
+ forceRefresh: true,
1602
+ observedAccessToken: receipt.token,
1603
+ },
1604
+ ).catch((refreshError) => {
1605
+ if (refreshError instanceof ClaudeSubscriptionConnectionChanged) return null;
1606
+ throw refreshError;
1607
+ });
1608
+ if (!credential) return await recoverChangedAccount();
1609
+ if (!("reconnectRequired" in credential) && credential.secret.token !== receipt.token) {
1610
+ const recovery = await requestSessionTurnRecovery(db, input.workspaceId, {
1611
+ sessionId: input.sessionId,
1612
+ turnId: attempt.turnId,
1613
+ triggerEventId: attempt.triggerEventId!,
1614
+ attemptId: input.attemptId,
1615
+ reason: "claude_token_renewed",
1616
+ claudeAuthRecovery: {
1617
+ credentialId: scopedCredentialId,
1618
+ credentialVersion: receipt.expectedCredentialVersion,
1619
+ },
1620
+ detail: { code: "claude_token_renewed", retryable: true },
1621
+ });
1622
+ if (recovery.action === "stale") {
1623
+ acknowledgeLostAttemptOwnership();
1624
+ control.activityStatus = "cancelled";
1625
+ control.turnMetricOutcome = "cancelled";
1626
+ return claimedResult({ status: "cancelled" });
1627
+ }
1628
+ acknowledgeRecoveryQuiescence();
1629
+ await publishDurableSessionEvents(
1630
+ bus,
1631
+ input.workspaceId,
1632
+ input.sessionId,
1633
+ recovery.events,
1634
+ );
1635
+ control.activityStatus = "recovering";
1636
+ control.turnMetricOutcome = "recovering";
1637
+ control.activityError = error;
1638
+ return claimedResult({ status: "recovering" });
1639
+ }
1640
+ }
1641
+ const recorded = receipt
1642
+ ? await recordClaudeAccountUsage(
1643
+ db,
1644
+ {
1645
+ accountId: input.accountId,
1646
+ workspaceId: input.workspaceId,
1647
+ subjectId: scopedLease.subjectId,
1648
+ credentialId: scopedCredentialId,
1649
+ authoritySnapshot: scopedAuthority,
1650
+ },
1651
+ {
1652
+ encryptionKey: key,
1653
+ token: receipt.token,
1654
+ expectedCredentialVersion: receipt.expectedCredentialVersion,
1655
+ ...(receipt.observation ? { observation: receipt.observation } : {}),
1656
+ ...(receipt.refresh ? { refresh: receipt.refresh } : {}),
1657
+ ...(cooldownUntil
1658
+ ? {
1659
+ modelCooldown: {
1660
+ upstreamModelId:
1661
+ receipt.upstreamModelId ?? providerTurn.claudeUpstreamModelId,
1662
+ until: cooldownUntil,
1663
+ },
1664
+ }
1665
+ : {}),
1666
+ },
1667
+ )
1668
+ : true;
1669
+ if (!recorded) return await recoverChangedAccount();
1670
+ }
1179
1671
  const failurePayload = {
1180
1672
  error:
1181
- xaiCredentialFailure.kind === "auth"
1182
- ? "The serving SuperGrok account requires reconnection"
1183
- : xaiCredentialFailure.kind === "forbidden"
1184
- ? "The serving SuperGrok account is not authorized for this request"
1185
- : "The serving SuperGrok account is temporarily rate limited",
1673
+ scopedFailure.kind === "auth"
1674
+ ? "The serving " + scopedName + " account requires reconnection"
1675
+ : scopedFailure.kind === "forbidden"
1676
+ ? "The serving " + scopedName + " account is not authorized for this request"
1677
+ : "The serving " + scopedName + " account is temporarily rate limited",
1186
1678
  code:
1187
- xaiCredentialFailure.kind === "auth"
1188
- ? "xai_relogin_required"
1189
- : xaiCredentialFailure.kind === "forbidden"
1190
- ? "xai_account_forbidden"
1191
- : "xai_account_rate_limited",
1679
+ scopedFailure.kind === "auth"
1680
+ ? scopedProvider + "_relogin_required"
1681
+ : scopedFailure.kind === "forbidden"
1682
+ ? scopedProvider + "_account_forbidden"
1683
+ : scopedProvider + "_account_rate_limited",
1192
1684
  detail: "the same accepted turn is waiting for another eligible account",
1193
1685
  };
1194
- const armed = await armXaiCapacityWait(db, {
1686
+ const armed = await armScopedWait(db, {
1195
1687
  accountId: input.accountId,
1196
1688
  workspaceId: input.workspaceId,
1197
- subjectId: leases.xai.subjectId,
1689
+ subjectId: scopedLease.subjectId,
1198
1690
  sessionId: input.sessionId,
1199
1691
  turnId: attempt.turnId,
1200
1692
  attemptId: input.attemptId,
1201
1693
  workflowId: input.workflowId,
1202
- authoritySnapshot: providerTurn.xaiAuthoritySnapshot,
1694
+ authoritySnapshot: scopedAuthority,
1203
1695
  goalId: activeGoal?.id ?? null,
1204
1696
  goalVersion: activeGoal?.version ?? null,
1205
1697
  earliestResetAt: cooldownUntil,
1206
1698
  failurePayload,
1207
1699
  leaseFence: {
1208
- holderId: leases.xai.holderId,
1209
- generation: leases.xai.generation,
1700
+ holderId: scopedLease.holderId,
1701
+ generation: scopedLease.generation,
1210
1702
  },
1211
- credentialQuarantine:
1212
- xaiCredentialFailure.kind === "auth"
1213
- ? {
1214
- kind: "status",
1215
- status: "needs_relogin",
1216
- lastError: "model request remained unauthorized after refresh",
1217
- }
1218
- : xaiCredentialFailure.kind === "forbidden"
1219
- ? {
1220
- kind: "status",
1221
- status: "error",
1222
- lastError: "model request was forbidden for this credential",
1223
- }
1224
- : { kind: "cooldown", until: cooldownUntil! },
1703
+ ...(claudeFailure
1704
+ ? {
1705
+ expectedCredentialVersion: providerTurn.effectiveClaudeCredentialVersion!,
1706
+ ...(claudeTokenFence ? { credentialTokenFence: claudeTokenFence } : {}),
1707
+ }
1708
+ : {}),
1709
+ ...(!claudeFailure || scopedFailure.kind !== "rate_limit"
1710
+ ? {
1711
+ credentialQuarantine:
1712
+ scopedFailure.kind === "auth"
1713
+ ? {
1714
+ kind: "status",
1715
+ status: "needs_relogin",
1716
+ lastError: "model request remained unauthorized after refresh",
1717
+ }
1718
+ : scopedFailure.kind === "forbidden"
1719
+ ? {
1720
+ kind: "status",
1721
+ status: "error",
1722
+ lastError: "model request was forbidden for this credential",
1723
+ }
1724
+ : { kind: "cooldown", until: cooldownUntil! },
1725
+ }
1726
+ : {}),
1225
1727
  now,
1226
1728
  });
1227
1729
  if (armed.action === "waiting") {
1228
- leases.xai.held = false;
1229
- providerTurn.xaiCredentialQuarantined = true;
1730
+ scopedLease.held = false;
1731
+ if (!claudeFailure) providerTurn.xaiCredentialQuarantined = true;
1230
1732
  await publishDurableSessionEvents(bus, input.workspaceId, input.sessionId, armed.events);
1231
- const evaluated = await reconcileXaiCapacityWait(db, {
1733
+ const evaluated = await reconcileScopedWait(db, {
1232
1734
  accountId: input.accountId,
1233
1735
  workspaceId: input.workspaceId,
1234
1736
  sessionId: input.sessionId,
@@ -1256,7 +1758,7 @@ export async function settleTurnFailure(deps: TurnFailureDeps): Promise<RunAgent
1256
1758
  return claimedResult({
1257
1759
  status: "waiting_capacity",
1258
1760
  capacityWait: {
1259
- provider: "xai",
1761
+ provider: scopedProvider,
1260
1762
  waiterId: evaluated.waiter.id,
1261
1763
  generation: evaluated.waiter.generation,
1262
1764
  nextCheckAt: evaluated.waiter.nextCheckAt.toISOString(),
@@ -1275,7 +1777,7 @@ export async function settleTurnFailure(deps: TurnFailureDeps): Promise<RunAgent
1275
1777
  turnId: attempt.turnId,
1276
1778
  triggerEventId: attempt.triggerEventId!,
1277
1779
  attemptId: input.attemptId,
1278
- reason: "xai_credential_recheck",
1780
+ reason: scopedProvider + "_credential_recheck",
1279
1781
  detail: failurePayload,
1280
1782
  });
1281
1783
  if (recovery.action === "stale") {
@@ -1343,12 +1845,16 @@ export async function settleTurnFailure(deps: TurnFailureDeps): Promise<RunAgent
1343
1845
  if (
1344
1846
  !(await eventing.settle!({
1345
1847
  events: [
1848
+ ...(error.allowance
1849
+ ? [{ type: "usage.exhausted" as const, payload: error.allowance }]
1850
+ : []),
1346
1851
  {
1347
1852
  type: "turn.completed",
1348
1853
  payload: {
1349
1854
  output: "",
1350
1855
  segmentLimit: "budget_exhausted",
1351
1856
  detail: error.message,
1857
+ ...(error.allowance ?? {}),
1352
1858
  },
1353
1859
  },
1354
1860
  { type: "session.status.changed", payload: { status: "idle" } },
@@ -1356,6 +1862,7 @@ export async function settleTurnFailure(deps: TurnFailureDeps): Promise<RunAgent
1356
1862
  turnStatus: "completed",
1357
1863
  sessionStatus: "idle",
1358
1864
  activeTurnId: null,
1865
+ ...(error.allowance ? { allowanceGoalPause: { rationale: error.allowance.message } } : {}),
1359
1866
  }))
1360
1867
  ) {
1361
1868
  return claimedResult({ status: "cancelled" });
@@ -1452,46 +1959,87 @@ export async function settleTurnFailure(deps: TurnFailureDeps): Promise<RunAgent
1452
1959
  // truth, recover this SAME accepted turn, then let the workflow re-claim
1453
1960
  // it after a pacing delay. This is independent of goal state and never
1454
1961
  // relies on a synthetic continuation prompt.
1455
- let failure = agentRunFailurePayload(error, {
1456
- isCodexTurn: billingState.isCodexTurn,
1457
- }) as ReturnType<typeof agentRunFailurePayload>;
1458
- if (isSessionEventPersistenceError(error)) {
1459
- // Preserve the exact source message in the internal runtime diagnostic;
1460
- // SQLSTATE/catalog facts remain separate classification attributes.
1461
- observability.error("session event persistence failed", {
1462
- accountId: input.accountId,
1463
- workspaceId: input.workspaceId,
1464
- sessionId: input.sessionId,
1465
- turnId: attempt.turnId,
1466
- attemptId: input.attemptId,
1467
- code: error.details.code,
1468
- sqlState: error.details.sqlState ?? "unknown",
1469
- stage: error.details.stage,
1470
- eventTypes: error.details.eventTypes.join(","),
1471
- correlationId: error.details.correlationId,
1472
- attempts: error.details.attempts,
1473
- retryOutcome: error.details.retryOutcome,
1474
- dbSeverity: error.details.database.severity,
1475
- dbSchema: error.details.database.schema,
1476
- dbTable: error.details.database.table,
1477
- dbColumn: error.details.database.column,
1478
- dbDataType: error.details.database.dataType,
1479
- dbConstraint: error.details.database.constraint,
1480
- dbRoutine: error.details.database.routine,
1481
- error: error.message,
1482
- });
1483
- }
1484
- if (failure.retryable && eventing.publish && attempt.turnId && eventing.turnStartedPublished) {
1962
+ // A rolling-deployment definition mismatch is a separate configuration
1963
+ // class: only the exact typed setup error can use this checkpoint before
1964
+ // eventing exists. No generic setup/credential failure gains retry authority.
1965
+ const earlyDefinitionMismatch =
1966
+ error instanceof TurnExecutionPolicyDefinitionMismatchError &&
1967
+ !attempt.modelRequestStarted &&
1968
+ !eventing.turnStartedPublished &&
1969
+ !!attempt.turnId &&
1970
+ !!attempt.triggerEventId &&
1971
+ attempt.executionGeneration > 0;
1972
+ const earlyCommandStartUnavailable =
1973
+ isModalTaskExecStartPreDispatchUnavailableError(error) &&
1974
+ !attempt.modelRequestStarted &&
1975
+ !eventing.turnStartedPublished &&
1976
+ !!attempt.turnId &&
1977
+ !!attempt.triggerEventId &&
1978
+ attempt.executionGeneration > 0;
1979
+ const earlyRecoverableSetup = earlyDefinitionMismatch || earlyCommandStartUnavailable;
1980
+ let failure = (
1981
+ earlyDefinitionMismatch
1982
+ ? { error: error.message, code: error.code, retryable: true }
1983
+ : (codexTerminalFailure ??
1984
+ agentRunFailurePayload(error, {
1985
+ isCodexTurn: billingState.isCodexTurn,
1986
+ }))
1987
+ ) as ReturnType<typeof agentRunFailurePayload>;
1988
+ if (
1989
+ attempt.turnId &&
1990
+ (earlyRecoverableSetup ||
1991
+ (failure.retryable && eventing.publish && eventing.turnStartedPublished))
1992
+ ) {
1485
1993
  const nextProviderRecoveryCount = attempt.providerRecoveryCount + 1;
1486
1994
  const recoveryResult = providerRecoveryResult({
1487
1995
  failureCode: failure.code,
1488
1996
  attemptNumber: nextProviderRecoveryCount,
1489
1997
  retryAfterMs: providerRetryAfterMs(error),
1490
1998
  });
1999
+ const setupRecoveryExhausted =
2000
+ earlyCommandStartUnavailable &&
2001
+ recoveryResult.status === "exhausted" &&
2002
+ attempt.providerRecoveryCount === SANDBOX_SETUP_RECOVERY_LIMIT;
1491
2003
  try {
2004
+ if (setupRecoveryExhausted) {
2005
+ // This is positive pre-dispatch proof, not an ambiguous command. Park
2006
+ // the SAME accepted turn without resetting or advancing its budget.
2007
+ const recovery = await requestSessionTurnRecovery(db, input.workspaceId, {
2008
+ sessionId: input.sessionId,
2009
+ turnId: attempt.turnId,
2010
+ triggerEventId: attempt.triggerEventId!,
2011
+ attemptId: input.attemptId,
2012
+ reason: "sandbox_command_start_recovery_exhausted",
2013
+ sandboxSetupRecoveryExhausted: true,
2014
+ detail: {
2015
+ code: failure.code,
2016
+ error:
2017
+ "Automatic sandbox setup recovery exhausted after five retries; the accepted turn remains parked without starting a command.",
2018
+ retryable: false,
2019
+ setupOutcome: "not_started",
2020
+ replay: "blocked",
2021
+ recoveryExhausted: true,
2022
+ providerRecoveryCount: SANDBOX_SETUP_RECOVERY_LIMIT,
2023
+ },
2024
+ });
2025
+ if (recovery.action === "stale") {
2026
+ acknowledgeLostAttemptOwnership();
2027
+ control.activityStatus = "cancelled";
2028
+ control.turnMetricOutcome = "cancelled";
2029
+ return claimedResult({ status: "cancelled" });
2030
+ }
2031
+ acknowledgeRecoveryQuiescence();
2032
+ await publishDurableSessionEvents(bus, input.workspaceId, input.sessionId, recovery.events);
2033
+ control.turnMetricOutcome = "recovering";
2034
+ control.activityStatus = "recovering";
2035
+ control.activityError = error;
2036
+ return claimedResult({ status: "recovering" });
2037
+ }
1492
2038
  if (recoveryResult.status === "recovering") {
1493
- await flushRuntimeBatcher();
1494
- await historySink.reconcileConversationTruth({ requireDurable: true });
2039
+ if (!earlyRecoverableSetup) {
2040
+ await flushRuntimeBatcher();
2041
+ await historySink.reconcileConversationTruth({ requireDurable: true });
2042
+ }
1495
2043
  const recovery = await requestSessionTurnRecovery(db, input.workspaceId, {
1496
2044
  sessionId: input.sessionId,
1497
2045
  turnId: attempt.turnId,
@@ -1500,7 +2048,7 @@ export async function settleTurnFailure(deps: TurnFailureDeps): Promise<RunAgent
1500
2048
  reason: failure.code ?? "provider_unavailable",
1501
2049
  providerRecoveryCount: nextProviderRecoveryCount,
1502
2050
  detail: {
1503
- ...failure,
2051
+ ...agentRunRecoveryFailurePayload(error, failure),
1504
2052
  continueDelayMs: recoveryResult.continueDelayMs,
1505
2053
  providerRecoveryCount: nextProviderRecoveryCount,
1506
2054
  },
@@ -1519,6 +2067,22 @@ export async function settleTurnFailure(deps: TurnFailureDeps): Promise<RunAgent
1519
2067
  return claimedResult(recoveryResult);
1520
2068
  }
1521
2069
  failure = providerRecoveryExhaustedFailure(failure, recoveryResult);
2070
+ if (earlyRecoverableSetup) {
2071
+ // Setup has no eventing sink yet. Carry only the fixed, safe diagnostic
2072
+ // through Temporal into exact-attempt workflow failure settlement.
2073
+ control.activityStatus = "failed";
2074
+ control.turnMetricOutcome = "failed";
2075
+ control.activityError = error;
2076
+ throw ApplicationFailure.create({
2077
+ message: earlyDefinitionMismatch
2078
+ ? `${error.message}. Automatic same-turn configuration recovery exhausted after ${recoveryResult.providerRecoveryCount} retries.`
2079
+ : failure.error,
2080
+ type: earlyDefinitionMismatch
2081
+ ? "TurnExecutionPolicyDefinitionMismatchError"
2082
+ : "SandboxCommandStartUnavailableError",
2083
+ nonRetryable: true,
2084
+ });
2085
+ }
1522
2086
  } catch (recoveryError) {
1523
2087
  const escaped =
1524
2088
  recoveryResult.status === "recovering"
@@ -1552,7 +2116,15 @@ export async function settleTurnFailure(deps: TurnFailureDeps): Promise<RunAgent
1552
2116
  providerRecoveryCount: nextProviderRecoveryCount,
1553
2117
  },
1554
2118
  })
1555
- : null;
2119
+ : setupRecoveryExhausted
2120
+ ? postClaimDatabaseRecoveryFailure({
2121
+ error: recoveryError,
2122
+ turnId: attempt.turnId,
2123
+ triggerEventId: attempt.triggerEventId!,
2124
+ executionGeneration: attempt.executionGeneration,
2125
+ sandboxSetupRecoveryExhausted: true,
2126
+ })
2127
+ : null;
1556
2128
  if (postClaimRecovery) {
1557
2129
  control.activityStatus = "recovering";
1558
2130
  control.turnMetricOutcome = "recovering";
@@ -1562,26 +2134,6 @@ export async function settleTurnFailure(deps: TurnFailureDeps): Promise<RunAgent
1562
2134
  throw recoveryError;
1563
2135
  }
1564
2136
  }
1565
- if (
1566
- attempt.turnId &&
1567
- attempt.triggerEventId &&
1568
- attempt.executionGeneration > 0 &&
1569
- !eventing.turnStartedPublished &&
1570
- !attempt.modelRequestStarted
1571
- ) {
1572
- const recoveryFailure = postClaimDatabaseRecoveryFailure({
1573
- error,
1574
- turnId: attempt.turnId,
1575
- triggerEventId: attempt.triggerEventId,
1576
- executionGeneration: attempt.executionGeneration,
1577
- });
1578
- if (recoveryFailure) {
1579
- control.activityStatus = "recovering";
1580
- control.turnMetricOutcome = "recovering";
1581
- control.activityError = error;
1582
- throw recoveryFailure;
1583
- }
1584
- }
1585
2137
  control.activityStatus = "failed";
1586
2138
  control.activityError = error;
1587
2139
  if (!attempt.turnId) {