@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,13 +1,17 @@
1
+ import { getSessionParentClaudeProviderAccountAuthority } from "@opengeni/db";
1
2
  import type { Settings } from "@opengeni/config";
2
3
  import type { Session, SessionGoal, SessionSystemUpdatePayload } from "@opengeni/contracts";
3
4
  import {
4
5
  getSession,
5
6
  getSessionGoal,
6
7
  getSessionParentPersonalConnectionDelegations,
8
+ getSessionParentMcpAccountBindings,
7
9
  getSessionParentXaiProviderAccountAuthority,
8
10
  addSessionSystemUpdateWithSourceMutation,
9
11
  claimPendingSessionSystemUpdateOutbox,
10
12
  claimPendingSessionWorkflowWakes,
13
+ repairPendingChildTerminalResultWakes,
14
+ type ChildTerminalWakeRepairCursor,
11
15
  childRequiresActionDedupeKey,
12
16
  getSessionSystemUpdateOutboxByDedupeKey,
13
17
  getOrCreateSessionSystemUpdateOutbox,
@@ -38,8 +42,11 @@ export type ReconcileParentSystemUpdateOverrides = Partial<{
38
42
 
39
43
  export type ReconcileSessionWorkflowWakeOverrides = Partial<{
40
44
  claimPendingSessionWorkflowWakes: typeof claimPendingSessionWorkflowWakes;
45
+ repairPendingChildTerminalResultWakes: typeof repairPendingChildTerminalResultWakes;
41
46
  }>;
42
47
 
48
+ const childTerminalRepairCursors = new WeakMap<Database, ChildTerminalWakeRepairCursor>();
49
+
43
50
  export type ReconcileAutomaticSessionTitleFanoutOverrides = Partial<{
44
51
  claimAutomaticSessionTitleFanout: typeof claimAutomaticSessionTitleFanout;
45
52
  markAutomaticSessionTitleFanoutDelivered: typeof markAutomaticSessionTitleFanoutDelivered;
@@ -63,23 +70,30 @@ export async function notifyParentOfChildIdle(
63
70
  return;
64
71
  }
65
72
  const goal = await getSessionGoal(svc.db, workspaceId, childSessionId);
66
- // Sacred user pause: if the MANAGER's own goal was paused by the user, the
67
- // wake must not tell it to "resume it now" — that instruction is exactly
68
- // what re-arms the loop the user just stopped. Suppress the resume nudge and
69
- // tell the agent to stay paused. Paired with the goal_set reactivation
70
- // guard so a nudge that slips through still cannot revive the goal.
71
73
  const clientEventId = `child-completion:${childSessionId}:${episodeKey}`;
74
+ // The idle settlement committed this row with the child's frozen final
75
+ // answer. This adds the goal facts; the upsert keeps the frozen content.
72
76
  const payload = childCompletionPayload(child, goal);
73
77
  const personalConnectionDelegations = await getSessionParentPersonalConnectionDelegations(
74
78
  svc.db,
75
79
  workspaceId,
76
80
  childSessionId,
77
81
  );
82
+ const mcpAccountBindings = await getSessionParentMcpAccountBindings(
83
+ svc.db,
84
+ workspaceId,
85
+ childSessionId,
86
+ );
78
87
  const xaiAuthority = await getSessionParentXaiProviderAccountAuthority(
79
88
  svc.db,
80
89
  workspaceId,
81
90
  childSessionId,
82
91
  );
92
+ const claudeAuthority = await getSessionParentClaudeProviderAccountAuthority(
93
+ svc.db,
94
+ workspaceId,
95
+ childSessionId,
96
+ );
83
97
  const outbox = await getOrCreateSessionSystemUpdateOutbox(svc.db, {
84
98
  accountId: child.accountId,
85
99
  workspaceId,
@@ -92,12 +106,17 @@ export async function notifyParentOfChildIdle(
92
106
  summary: childCompletionSummary(child, goal, "idle"),
93
107
  payload,
94
108
  lineage: {
109
+ ...(claudeAuthority.subjectId
110
+ ? { claudeAuthoritySubjectId: claudeAuthority.subjectId }
111
+ : {}),
95
112
  childSessionId: child.id,
96
113
  parentSessionId: child.parentSessionId,
97
114
  ...(xaiAuthority.subjectId ? { xaiAuthoritySubjectId: xaiAuthority.subjectId } : {}),
98
115
  },
99
116
  personalConnectionDelegations,
117
+ mcpAccountBindings,
100
118
  xaiProviderAccountAuthoritySnapshot: xaiAuthority.snapshot,
119
+ claudeProviderAccountAuthoritySnapshot: claudeAuthority.snapshot,
101
120
  });
102
121
  if (outbox.status === "delivered") {
103
122
  return;
@@ -214,7 +233,9 @@ async function deliverParentSystemUpdateOutbox(
214
233
  summary: outbox.summary,
215
234
  lineage: outbox.lineage,
216
235
  personalConnectionDelegations: outbox.personalConnectionDelegations,
236
+ mcpAccountBindings: outbox.mcpAccountBindings,
217
237
  xaiProviderAccountAuthoritySnapshot: outbox.xaiProviderAccountAuthoritySnapshot,
238
+ claudeProviderAccountAuthoritySnapshot: outbox.claudeProviderAccountAuthoritySnapshot,
218
239
  },
219
240
  async (tx) => {
220
241
  await markSessionSystemUpdateOutboxDeliveredInTransaction(tx, outbox);
@@ -370,6 +391,22 @@ export async function reconcilePendingSessionWorkflowWakes(
370
391
  pendingAdmissionBlockers: {},
371
392
  };
372
393
  }
394
+ const repairChildren =
395
+ overrides.repairPendingChildTerminalResultWakes ?? repairPendingChildTerminalResultWakes;
396
+ const inventory = await repairChildren(
397
+ svc.db,
398
+ Math.min(100, limit),
399
+ childTerminalRepairCursors.get(svc.db) ?? null,
400
+ );
401
+ if (inventory.cursor) childTerminalRepairCursors.set(svc.db, inventory.cursor);
402
+ else childTerminalRepairCursors.delete(svc.db);
403
+ if (inventory.failed > 0) {
404
+ svc.observability.error("Pending child-result wake repair failed", {
405
+ examined: inventory.examined,
406
+ registered: inventory.registered,
407
+ failed: inventory.failed,
408
+ });
409
+ }
373
410
  const repairs = await claimPendingSessionWorkflowWakesFn(svc.db, limit);
374
411
  let signaled = 0;
375
412
  let pendingAdmission = 0;
@@ -457,11 +494,10 @@ function childCompletionPayload(
457
494
  }
458
495
 
459
496
  /**
460
- * The worker-specific lines for one child (what happened + its goal), WITHOUT
461
- * the trailing "what to do next" instruction. Kept separate so N child
462
- * completions can be coalesced into one internal-update inference: the DB layer
463
- * stores each child's summary and rebuilds a single numbered digest with one
464
- * shared trailing instruction instead of running N model calls.
497
+ * The short status line for one child (what happened + its goal). It is also
498
+ * the timeline preview, so it carries no instructions: the parent reads the
499
+ * child's answer from the typed payload, and the operational contract says how
500
+ * to use it. N completions coalesce into one internal-update inference.
465
501
  */
466
502
  export function childCompletionSummary(
467
503
  child: Session,
@@ -495,14 +531,3 @@ export function childCompletionSummary(
495
531
  }
496
532
  return lines.join("\n");
497
533
  }
498
-
499
- /**
500
- * The single trailing instruction appended to a child-completion (or digest)
501
- * inference. Suppresses the "resume it now" nudge when the manager's own goal
502
- * was paused by the user (paired with the goal_set reactivation guard).
503
- */
504
- export function childCompletionTrailing(parentGoalUserPaused: boolean): string {
505
- return parentGoalUserPaused
506
- ? "Read each worker's session events/notebook output for its result. This session was paused by the user — do NOT resume or replace your goal; summarize the result for the user and remain paused."
507
- : "Read each worker's session events/notebook output for its result, then continue. If your own goal was paused awaiting these workers, resume it now.";
508
- }
@@ -6,13 +6,16 @@ import {
6
6
  COMPUTER_SCREENSHOT_WORKSPACE_QUOTA_BYTES,
7
7
  RetainedArtifactMetadataSchema,
8
8
  retainedScreenshotReferenceFromFile,
9
+ retainedSessionScreenshotKindFromObjectKey,
9
10
  type RetainedArtifactMetadata,
10
11
  type RetainedArtifactReference,
11
12
  type RetainedOutputUnavailableReason,
13
+ type RetainedSessionScreenshotKind,
12
14
  } from "@opengeni/contracts";
13
15
  import {
14
16
  RetainedScreenshotQuotaExceededError,
15
17
  getRetainedScreenshotArtifact,
18
+ getRetainedScreenshotArtifactForToolCall,
16
19
  isDatabasePersistenceFailure,
17
20
  isSessionEventPersistenceError,
18
21
  prepareRetainedScreenshotArtifact,
@@ -209,14 +212,26 @@ export function toolOutputContainsInlineImage(output: unknown): boolean {
209
212
  return (
210
213
  (record.type === "input_image" &&
211
214
  sdkImageSourceContainsInlineImage(record.image ?? record.image_url ?? record.imageUrl)) ||
212
- (record.type === "image" && decodeInlineMcpImage(record.data, record.mimeType) !== null)
215
+ (record.type === "image" && isDeclaredInlineMcpImage(record))
213
216
  );
214
217
  });
215
218
  }
216
219
  if (!output || typeof output !== "object") return false;
217
220
  const record = output as Record<string, unknown>;
218
221
  if (Array.isArray(record.content)) return toolOutputContainsInlineImage(record.content);
219
- return record.type === "image" && sdkImageSourceContainsInlineImage(record.image);
222
+ return (
223
+ record.type === "image" &&
224
+ (sdkImageSourceContainsInlineImage(record.image) || isDeclaredInlineMcpImage(record))
225
+ );
226
+ }
227
+
228
+ /** Detect image-bearing MCP blocks even when decoding rejects oversized or malformed bytes. */
229
+ function isDeclaredInlineMcpImage(record: Record<string, unknown>): boolean {
230
+ return (
231
+ typeof record.data === "string" &&
232
+ typeof record.mimeType === "string" &&
233
+ record.mimeType.toLowerCase().startsWith("image/")
234
+ );
220
235
  }
221
236
 
222
237
  function decodeInlineMcpImage(
@@ -520,9 +535,14 @@ export function retainedScreenshotIdentity(input: {
520
535
  attemptId: string;
521
536
  toolCallId: string;
522
537
  toolOutputId: string;
538
+ kind?: RetainedSessionScreenshotKind | undefined;
523
539
  }): { artifactId: string; settlementKey: string } {
524
540
  const settlementKey = createHash("sha256")
525
- .update("opengeni:computer-screenshot:v1\0")
541
+ .update(
542
+ input.kind === "browser_screenshot"
543
+ ? "opengeni:browser-screenshot:v1\0"
544
+ : "opengeni:computer-screenshot:v1\0",
545
+ )
526
546
  .update(input.sessionId)
527
547
  .update("\0")
528
548
  .update(input.turnId)
@@ -551,12 +571,13 @@ export function unavailableRetainedSessionImage(input: {
551
571
  toolCallId: string;
552
572
  toolOutputId: string;
553
573
  reason: RetainedOutputUnavailableReason;
574
+ kind?: RetainedSessionScreenshotKind | undefined;
554
575
  }): RetainedArtifactMetadata {
555
576
  const identity = retainedScreenshotIdentity(input);
556
577
  return unavailable(identity.artifactId, input.reason);
557
578
  }
558
579
 
559
- export async function retainComputerScreenshot(input: {
580
+ export async function retainSessionScreenshot(input: {
560
581
  db: Database;
561
582
  objectStorage: ObjectStorage | null;
562
583
  accountId: string;
@@ -565,6 +586,7 @@ export async function retainComputerScreenshot(input: {
565
586
  turnId: string;
566
587
  attemptId: string;
567
588
  output: TypedScreenshotToolOutput;
589
+ kind?: RetainedSessionScreenshotKind | undefined;
568
590
  now?: Date;
569
591
  retentionMs?: number;
570
592
  workspaceQuotaBytes?: number;
@@ -575,6 +597,7 @@ export async function retainComputerScreenshot(input: {
575
597
  attemptId: input.attemptId,
576
598
  toolCallId: input.output.callId,
577
599
  toolOutputId: input.output.toolOutputId,
600
+ kind: input.kind,
578
601
  });
579
602
  let screenshot: ValidatedSessionImage;
580
603
  try {
@@ -594,7 +617,8 @@ export async function retainComputerScreenshot(input: {
594
617
  const retentionExpiresAt = new Date(
595
618
  now.getTime() + (input.retentionMs ?? COMPUTER_SCREENSHOT_RETENTION_MS),
596
619
  );
597
- const objectKey = `workspaces/${input.workspaceId}/files/${identity.artifactId}/retained/session-image.${screenshot.extension}`;
620
+ const objectName = input.kind === "browser_screenshot" ? "browser-screenshot" : "session-image";
621
+ const objectKey = `workspaces/${input.workspaceId}/files/${identity.artifactId}/retained/${objectName}.${screenshot.extension}`;
598
622
  let prepared: RetainedScreenshotArtifact;
599
623
  try {
600
624
  prepared = (
@@ -697,6 +721,9 @@ export async function retainComputerScreenshot(input: {
697
721
  }
698
722
  }
699
723
 
724
+ /** Existing computer callers keep their stable entry point. */
725
+ export const retainComputerScreenshot = retainSessionScreenshot;
726
+
700
727
  /** Replace only new screenshot bytes with a compact receipt before first persistence. */
701
728
  export function compactRetainedScreenshotHistory(
702
729
  history: Array<Record<string, unknown>>,
@@ -874,6 +901,26 @@ async function materializeRetainedScreenshotHistoryWithCache(
874
901
  cache: Map<string, string>,
875
902
  ): Promise<Array<Record<string, unknown>>> {
876
903
  const now = input.now ?? new Date();
904
+ const receiptForMarker = async (
905
+ marker: unknown,
906
+ callId: string | null,
907
+ ): Promise<RetainedArtifactMetadata | null> => {
908
+ if (!isRetainedImageMarker(marker)) return null;
909
+ const receipt = retainedReceipt(marker.artifact);
910
+ if (receipt) return receipt;
911
+ // Older canonical history could truncate the artifact UUID and reason as
912
+ // ordinary text. Recover only from this exact session and an unambiguous
913
+ // tool call; otherwise fail before constructing a malformed model image.
914
+ if (!callId) throw new Error("Retained screenshot receipt has no tool call identity");
915
+ const artifact = await getRetainedScreenshotArtifactForToolCall(
916
+ input.db,
917
+ input.workspaceId,
918
+ input.sessionId,
919
+ callId,
920
+ );
921
+ if (!artifact) throw new Error("Retained screenshot receipt cannot be recovered");
922
+ return reference(artifact);
923
+ };
877
924
  const dataUrlForReceipt = async (receipt: RetainedArtifactMetadata): Promise<string> => {
878
925
  let dataUrl = cache.get(receipt.artifactId);
879
926
  if (!dataUrl) {
@@ -921,8 +968,12 @@ async function materializeRetainedScreenshotHistoryWithCache(
921
968
  }
922
969
  return dataUrl;
923
970
  };
924
- const materializeEntry = async (entry: unknown): Promise<unknown> => {
925
- const receipt = retainedReceiptFromImageContent(entry);
971
+ const materializeEntry = async (entry: unknown, callId: string | null): Promise<unknown> => {
972
+ const image =
973
+ entry && typeof entry === "object" && !Array.isArray(entry)
974
+ ? (entry as Record<string, unknown>).image
975
+ : null;
976
+ const receipt = await receiptForMarker(image, callId);
926
977
  if (!receipt) return entry;
927
978
  const dataUrl = await dataUrlForReceipt(receipt);
928
979
  return { ...(entry as Record<string, unknown>), image: dataUrl };
@@ -930,7 +981,8 @@ async function materializeRetainedScreenshotHistoryWithCache(
930
981
 
931
982
  const materialized: Array<Record<string, unknown>> = [];
932
983
  for (const item of input.history) {
933
- const directReceipt = retainedReceiptFromDirectOutput(item.output);
984
+ const callId = historyCallId(item);
985
+ const directReceipt = await receiptForMarker(item.output, callId);
934
986
  if (directReceipt) {
935
987
  materialized.push({
936
988
  ...item,
@@ -954,7 +1006,7 @@ async function materializeRetainedScreenshotHistoryWithCache(
954
1006
  let changed = false;
955
1007
  const output: unknown[] = [];
956
1008
  for (const entry of outputEntries) {
957
- const next = await materializeEntry(entry);
1009
+ const next = await materializeEntry(entry, callId);
958
1010
  changed ||= next !== entry;
959
1011
  output.push(next);
960
1012
  }
@@ -1059,12 +1111,15 @@ function reference(artifact: RetainedScreenshotArtifact): RetainedArtifactRefere
1059
1111
  if (!artifact.sessionId) {
1060
1112
  throw new Error(`Ready retained screenshot is detached: ${artifact.artifactId}`);
1061
1113
  }
1114
+ const kind = retainedSessionScreenshotKindFromObjectKey(artifact.file.objectKey);
1115
+ if (!kind) throw new Error(`Ready retained screenshot has unknown key: ${artifact.artifactId}`);
1062
1116
  const value = retainedScreenshotReferenceFromFile({
1063
1117
  ...artifact.file,
1064
1118
  sessionId: artifact.sessionId,
1065
1119
  width: artifact.width,
1066
1120
  height: artifact.height,
1067
1121
  expiresAt: artifact.retentionExpiresAt.toISOString(),
1122
+ kind,
1068
1123
  });
1069
1124
  if (!value)
1070
1125
  throw new Error(`Ready retained screenshot receipt is invalid: ${artifact.artifactId}`);
@@ -1110,6 +1165,18 @@ function retainedReceiptFromImageContent(entry: unknown): RetainedArtifactMetada
1110
1165
  return retainedReceipt(marker.artifact);
1111
1166
  }
1112
1167
 
1168
+ function isRetainedImageMarker(value: unknown): value is {
1169
+ type: "retained_artifact";
1170
+ artifact: unknown;
1171
+ } {
1172
+ return Boolean(
1173
+ value &&
1174
+ typeof value === "object" &&
1175
+ !Array.isArray(value) &&
1176
+ (value as Record<string, unknown>).type === RETAINED_IMAGE_MARKER,
1177
+ );
1178
+ }
1179
+
1113
1180
  function retainedReceiptFromDirectOutput(output: unknown): RetainedArtifactMetadata | null {
1114
1181
  if (!output || typeof output !== "object" || Array.isArray(output)) return null;
1115
1182
  const marker = output as Record<string, unknown>;
@@ -1120,7 +1187,11 @@ function retainedReceiptFromDirectOutput(output: unknown): RetainedArtifactMetad
1120
1187
  function retainedReceipt(value: unknown): RetainedArtifactMetadata | null {
1121
1188
  const parsed = RetainedArtifactMetadataSchema.safeParse(value);
1122
1189
  if (!parsed.success) return null;
1123
- return parsed.data.available && parsed.data.kind !== "computer_screenshot" ? null : parsed.data;
1190
+ return parsed.data.available &&
1191
+ parsed.data.kind !== "computer_screenshot" &&
1192
+ parsed.data.kind !== "browser_screenshot"
1193
+ ? null
1194
+ : parsed.data;
1124
1195
  }
1125
1196
 
1126
1197
  function isInlineImageContent(entry: unknown): boolean {
@@ -1130,7 +1201,7 @@ function isInlineImageContent(entry: unknown): boolean {
1130
1201
  (record.type === "input_image" &&
1131
1202
  typeof record.image === "string" &&
1132
1203
  record.image.startsWith("data:image/")) ||
1133
- (record.type === "image" && decodeInlineMcpImage(record.data, record.mimeType) !== null)
1204
+ (record.type === "image" && isDeclaredInlineMcpImage(record))
1134
1205
  );
1135
1206
  }
1136
1207
 
@@ -60,7 +60,6 @@ import {
60
60
  import type { Context } from "@temporalio/activity";
61
61
  import type { ControlActivityServices } from "./types";
62
62
  import { rigProviderImageSourceImage } from "./sandbox-images";
63
- import { resolveWorkspacePackRuntime, type WorkspacePackRuntime } from "./packs";
64
63
  import { currentActivityContext } from "./streaming";
65
64
 
66
65
  type RigSetupHook = typeof import("@opengeni/runtime").runRigSetupHook;
@@ -175,7 +174,7 @@ const RIG_VERIFICATION_HEARTBEAT_MIN_INTERVAL_MS = 250;
175
174
  // lease-holder fiction or manual recovery.
176
175
  export const RIG_VERIFICATION_OWNER_TTL_MS = 20 * 60_000;
177
176
  export const RIG_VERIFICATION_OWNERS_DISABLED_MESSAGE =
178
- "Rig verification lease ownership is disabled; refusing to create an unowned verifier sandbox";
177
+ "Sandbox Environment verification lease ownership is disabled; refusing to create an unowned verifier sandbox";
179
178
  const RIG_VERIFICATION_HOLDER_TOUCH_INTERVAL_MS = 10_000;
180
179
  const RIG_VERIFICATION_RELEASE_GRACE_MS = 1;
181
180
 
@@ -187,7 +186,7 @@ export class RigVerificationActivityDeadlineError extends Error {
187
186
  readonly cleanupReserveMs: number,
188
187
  ) {
189
188
  super(
190
- `Rig verification activity-local deadline reached with ${cleanupReserveMs}ms reserved for cleanup before the ${startToCloseTimeoutMs}ms Temporal start-to-close timeout`,
189
+ `Sandbox Environment verification activity-local deadline reached with ${cleanupReserveMs}ms reserved for cleanup before the ${startToCloseTimeoutMs}ms Temporal start-to-close timeout`,
191
190
  );
192
191
  }
193
192
  }
@@ -223,7 +222,9 @@ export function createRigVerificationActivityLifecycle(
223
222
  const temporalSignal = context?.cancellationSignal;
224
223
  const forwardTemporalCancellation = (): void => {
225
224
  if (!controller.signal.aborted) {
226
- controller.abort(temporalSignal?.reason ?? new Error("Rig verification cancelled"));
225
+ controller.abort(
226
+ temporalSignal?.reason ?? new Error("Sandbox Environment verification cancelled"),
227
+ );
227
228
  }
228
229
  };
229
230
  if (temporalSignal?.aborted) {
@@ -308,7 +309,7 @@ export function createRigVerificationActivityLifecycle(
308
309
  }
309
310
 
310
311
  function abortReason(signal: AbortSignal): unknown {
311
- return signal.reason ?? new Error("Rig verification cancelled");
312
+ return signal.reason ?? new Error("Sandbox Environment verification cancelled");
312
313
  }
313
314
 
314
315
  function throwIfAborted(signal: AbortSignal): void {
@@ -433,7 +434,9 @@ export class RigVerificationLeaseUnavailableError extends Error {
433
434
  readonly sandboxGroupId: string,
434
435
  readonly role: "attached" | "blocked" | "rearmed" | "fenced",
435
436
  ) {
436
- super(`Rig verification requires a new clean sandbox, but lease ${sandboxGroupId} was ${role}`);
437
+ super(
438
+ `Sandbox Environment verification requires a new clean sandbox, but lease ${sandboxGroupId} was ${role}`,
439
+ );
437
440
  }
438
441
  }
439
442
 
@@ -490,7 +493,7 @@ export async function runWithOwnedRigVerificationSandbox<T>(
490
493
  let establishmentPromise: Promise<EstablishedSandboxSession> | null = null;
491
494
 
492
495
  const warnCleanupTimeout = (operation: string, instanceId: string | null): void => {
493
- input.observability.warn("rig verifier: ownership cleanup timed out", {
496
+ input.observability.warn("sandbox environment verifier: ownership cleanup timed out", {
494
497
  holderId,
495
498
  instanceId,
496
499
  operation,
@@ -504,7 +507,7 @@ export async function runWithOwnedRigVerificationSandbox<T>(
504
507
  instanceId: string | null,
505
508
  error: unknown,
506
509
  ): void => {
507
- input.observability.warn("rig verifier: ownership cleanup failed", {
510
+ input.observability.warn("sandbox environment verifier: ownership cleanup failed", {
508
511
  holderId,
509
512
  instanceId,
510
513
  operation,
@@ -643,11 +646,14 @@ export async function runWithOwnedRigVerificationSandbox<T>(
643
646
  );
644
647
  } catch (error) {
645
648
  if (!signal.aborted) {
646
- input.observability.warn("rig verifier: Modal ownership tag failed", {
647
- holderId,
648
- instanceId: created.instanceId,
649
- error: error instanceof Error ? error.message : String(error),
650
- });
649
+ input.observability.warn(
650
+ "sandbox environment verifier: Modal ownership tag failed",
651
+ {
652
+ holderId,
653
+ instanceId: created.instanceId,
654
+ error: error instanceof Error ? error.message : String(error),
655
+ },
656
+ );
651
657
  }
652
658
  }
653
659
  }
@@ -913,10 +919,12 @@ function rigProviderImageContentMarker(
913
919
  markerRoot = "/var/opengeni",
914
920
  ): { marker: string; normalizedRoot: string } {
915
921
  if (!/^sha256:[0-9a-f]{64}$/u.test(contentHash)) {
916
- throw new Error("Rig provider image content marker requires a canonical SHA-256 value");
922
+ throw new Error(
923
+ "Sandbox Environment provider image content marker requires a canonical SHA-256 value",
924
+ );
917
925
  }
918
926
  if (!/^\/[A-Za-z0-9._/-]+$/u.test(markerRoot)) {
919
- throw new Error("Rig provider image marker root must be a safe absolute path");
927
+ throw new Error("Sandbox Environment provider image marker root must be a safe absolute path");
920
928
  }
921
929
  const normalizedRoot = markerRoot.replace(/\/+$/u, "") || "/";
922
930
  const marker = `${normalizedRoot === "/" ? "" : normalizedRoot}/rig-setup-content-${contentHash.slice("sha256:".length)}.done`;
@@ -978,7 +986,9 @@ export async function verifyRigProviderImageColdBoot(
978
986
  runContext.commandRunner,
979
987
  );
980
988
  if (markerResult.exitCode !== 0) {
981
- throw new Error("built provider image is missing its exact rig-content marker");
989
+ throw new Error(
990
+ "built provider image is missing its exact sandbox environment-content marker",
991
+ );
982
992
  }
983
993
  for (const check of rigPlatformChecksForSettings(input.settings)) {
984
994
  const result = await runCommand(
@@ -1083,7 +1093,7 @@ export async function buildVerifiedRigProviderImage(input: {
1083
1093
  error: {
1084
1094
  code: "provider_image_content_conflict",
1085
1095
  message:
1086
- "this exact rig version already records a provider image for different effective content; mint and verify a new version",
1096
+ "this exact sandbox environment version already records a provider image for different effective content; mint and verify a new version",
1087
1097
  retryable: false,
1088
1098
  },
1089
1099
  };
@@ -1276,13 +1286,10 @@ export async function buildVerifiedRigProviderImage(input: {
1276
1286
 
1277
1287
  export function settingsForRigVerification(
1278
1288
  settings: ControlActivityServices["settings"],
1279
- packRuntime: WorkspacePackRuntime,
1280
1289
  rigImage: string | null,
1281
1290
  ): ControlActivityServices["settings"] {
1282
1291
  // Rig verification must prove setup and provider-image materialization on
1283
- // the deployment-owned platform base. Legacy Pack and historical Rig image
1284
- // values are intentionally ignored here.
1285
- void packRuntime;
1292
+ // the deployment-owned platform base. Historical Rig image values are ignored.
1286
1293
  void rigImage;
1287
1294
  return settings;
1288
1295
  }
@@ -1294,18 +1301,18 @@ async function loadChangeTarget(
1294
1301
  ): Promise<{ rig: Rig; baseVersion: RigVersion; change: RigChange }> {
1295
1302
  const change = await getRigChange(db, workspaceId, changeId);
1296
1303
  if (!change) {
1297
- throw new Error(`Rig change not found: ${changeId}`);
1304
+ throw new Error(`Sandbox Environment change not found: ${changeId}`);
1298
1305
  }
1299
1306
  const rig = await getRig(db, workspaceId, change.rigId);
1300
1307
  if (!rig) {
1301
- throw new Error(`Rig not found for change: ${change.rigId}`);
1308
+ throw new Error(`Sandbox Environment not found for change: ${change.rigId}`);
1302
1309
  }
1303
1310
  if (!change.baseVersionId) {
1304
- throw new Error(`Rig change ${change.id} has no base version`);
1311
+ throw new Error(`Sandbox Environment change ${change.id} has no base version`);
1305
1312
  }
1306
1313
  const baseVersion = await getRigVersionById(db, workspaceId, change.baseVersionId);
1307
1314
  if (!baseVersion || baseVersion.rigId !== rig.id) {
1308
- throw new Error(`Base rig version not found: ${change.baseVersionId}`);
1315
+ throw new Error(`Base sandbox environment version not found: ${change.baseVersionId}`);
1309
1316
  }
1310
1317
  return { rig, baseVersion, change };
1311
1318
  }
@@ -1317,11 +1324,11 @@ async function loadVersionTarget(
1317
1324
  ): Promise<{ rig: Rig; version: RigVersion }> {
1318
1325
  const version = await getRigVersionById(db, workspaceId, versionId);
1319
1326
  if (!version) {
1320
- throw new Error(`Rig version not found: ${versionId}`);
1327
+ throw new Error(`Sandbox Environment version not found: ${versionId}`);
1321
1328
  }
1322
1329
  const rig = await getRig(db, workspaceId, version.rigId);
1323
1330
  if (!rig) {
1324
- throw new Error(`Rig not found for version: ${version.rigId}`);
1331
+ throw new Error(`Sandbox Environment not found for version: ${version.rigId}`);
1325
1332
  }
1326
1333
  return { rig, version };
1327
1334
  }
@@ -1373,12 +1380,7 @@ export function createRigVerificationActivities(services: () => Promise<ControlA
1373
1380
  change,
1374
1381
  candidateVersion,
1375
1382
  );
1376
- const packRuntime = await resolveWorkspacePackRuntime(db, input.workspaceId);
1377
- const runSettings = settingsForRigVerification(
1378
- settings,
1379
- packRuntime,
1380
- candidateVersion.image,
1381
- );
1383
+ const runSettings = settingsForRigVerification(settings, candidateVersion.image);
1382
1384
  const providerImageContentHash = rigProviderImageContentHash({
1383
1385
  backend: runSettings.sandboxBackend,
1384
1386
  sourceImage: rigProviderImageSourceImage(runSettings, runSettings.sandboxBackend),
@@ -1452,7 +1454,7 @@ export function createRigVerificationActivities(services: () => Promise<ControlA
1452
1454
  );
1453
1455
  if (markerResult.exitCode !== 0) {
1454
1456
  throw new Error(
1455
- `Failed to seal rig provider image content marker: ${markerResult.output.slice(-2000)}`,
1457
+ `Failed to seal sandbox environment provider image content marker: ${markerResult.output.slice(-2000)}`,
1456
1458
  );
1457
1459
  }
1458
1460
  const platformCheckResults = await runRigChecks(
@@ -1572,8 +1574,7 @@ export function createRigVerificationActivities(services: () => Promise<ControlA
1572
1574
  metadata: { versionId: version.id },
1573
1575
  });
1574
1576
  try {
1575
- const packRuntime = await resolveWorkspacePackRuntime(db, input.workspaceId);
1576
- const runSettings = settingsForRigVerification(settings, packRuntime, version.image);
1577
+ const runSettings = settingsForRigVerification(settings, version.image);
1577
1578
  const providerImageContentHash = rigProviderImageContentHash({
1578
1579
  backend: runSettings.sandboxBackend,
1579
1580
  sourceImage: rigProviderImageSourceImage(runSettings, runSettings.sandboxBackend),
@@ -1615,7 +1616,7 @@ export function createRigVerificationActivities(services: () => Promise<ControlA
1615
1616
  );
1616
1617
  if (markerResult.exitCode !== 0) {
1617
1618
  throw new Error(
1618
- `Failed to seal rig provider image content marker: ${markerResult.output.slice(-2000)}`,
1619
+ `Failed to seal sandbox environment provider image content marker: ${markerResult.output.slice(-2000)}`,
1619
1620
  );
1620
1621
  }
1621
1622
  const platformCheckResults = await runRigChecks(
@@ -28,6 +28,18 @@ export type RunCredentialRenewalOptions = {
28
28
  }) => void;
29
29
  };
30
30
 
31
+ export function runCredentialRenewalExpiry(
32
+ material: NormalizedRunCredentialMaterial | null,
33
+ ): Date | null {
34
+ const expiries = [
35
+ ...(material?.expiresAt ? [material.expiresAt.getTime()] : []),
36
+ ...(material?.mcp ?? []).flatMap((entry) =>
37
+ entry.expiresAt ? [Date.parse(entry.expiresAt)] : [],
38
+ ),
39
+ ];
40
+ return expiries.length ? new Date(Math.min(...expiries)) : null;
41
+ }
42
+
31
43
  export function nextRunCredentialRenewalDelay(expiresAt: Date | null, nowMs = Date.now()): number {
32
44
  return nextExpiringMaterialRenewalDelay(
33
45
  expiresAt,
@@ -52,7 +64,7 @@ export function startRunCredentialRenewalLoop(
52
64
  initialExpiresAt: options.initialExpiresAt,
53
65
  resolve: options.resolve,
54
66
  write: options.write,
55
- expiresAt: (material) => material?.expiresAt ?? null,
67
+ expiresAt: runCredentialRenewalExpiry,
56
68
  publicErrorClass: "RunCredentialRenewalOperationError",
57
69
  policy: {
58
70
  defaultRefreshMs: RUN_CREDENTIAL_DEFAULT_REFRESH_MS,