@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,3 +1,4 @@
1
+ import { isDeepStrictEqual } from "node:util";
1
2
  // apps/worker/src/sandbox-routing.ts — wire the agent-loop-free routing proxy
2
3
  // (`@opengeni/runtime` RoutingSandboxSession + makeActiveBackendResolver) to the
3
4
  // real DB pointer + the live NATS control plane for the WORKER TURN path (M7).
@@ -17,12 +18,13 @@
17
18
  import { sandboxLifecycleTransitionWaitMs, type Settings } from "@opengeni/config";
18
19
  import {
19
20
  createProviderCommandRetainer,
20
- retainedProviderCommandPersistence,
21
+ supervisedCommandProtocolReady,
21
22
  } from "@opengeni/db/retained-provider-commands";
22
23
  import {
23
24
  adoptConnectedMachineSessionBackgroundCommand,
24
25
  adoptManagedSessionBackgroundCommand,
25
26
  advanceWorkspaceGenerationForRetainedProcess,
27
+ retainedProviderCommandPersistence,
26
28
  advanceWorkspaceGeneration,
27
29
  getRetainedProcess,
28
30
  retainWorkspaceMutationProcess,
@@ -42,10 +44,14 @@ import {
42
44
  type EnrollmentRecord,
43
45
  type SandboxWorkspaceMutationAdmission,
44
46
  } from "@opengeni/db";
45
- import { observeSessionBackgroundCommandCompletion } from "@opengeni/db/session-background-commands";
47
+ import {
48
+ observeSessionBackgroundCommandCompletion,
49
+ recordConnectedCommandOutputConsumption,
50
+ } from "@opengeni/db/session-background-commands";
46
51
  import { appendSessionCommandOutput } from "@opengeni/db/session-command-output";
47
52
  import type { OpStreamOutputFrame } from "@opengeni/runtime/sandbox";
48
53
  import type { EventBus } from "@opengeni/events";
54
+ import { publishDurableSessionEvents } from "./session-event-fanout";
49
55
  import {
50
56
  buildSelfhostedBackendSession,
51
57
  ActiveBackendUnresolvableError,
@@ -72,6 +78,7 @@ import {
72
78
  type RoutingMutationSettlementResult,
73
79
  type RoutingSandboxFirstOperationObserver,
74
80
  type RoutingSandboxOperationObserver,
81
+ type RoutingSandboxCaptureWaitObserver,
75
82
  type RoutingSandboxWaitObservation,
76
83
  type RoutingRetainedProcess,
77
84
  type RoutingRetainedProcessTerminalProof,
@@ -112,6 +119,7 @@ export type RoutingWiringServices = {
112
119
  onOp?: SelfhostedOpObserver;
113
120
  /** Every physical routed provider call, across cloud and selfhosted homes. */
114
121
  onSandboxOperation?: RoutingSandboxOperationObserver;
122
+ onSandboxCaptureWait?: RoutingSandboxCaptureWaitObserver;
115
123
  /** The op-stream durable-resume journal (the Temporal adaptation from
116
124
  * op-journal.ts): attach generation + settled-frontier persistence. Absent ⇒
117
125
  * the runtime defaults (generation "1", no persistence) — tests / non-turn
@@ -124,11 +132,12 @@ export type RoutingWiringServices = {
124
132
  instanceId: string;
125
133
  leaseEpoch: number;
126
134
  }) => Promise<void>;
127
- /** Called when a route repair replaces the worker's original home handle. */
135
+ /** Prepare and publish the worker's home handle. Rejection leaves the route
136
+ * unpublished; replacement preparation must settle before the first tool op. */
128
137
  onHomeSandboxRebound?: (input: {
129
138
  established: EstablishedSandboxSession;
130
139
  leaseEpoch: number;
131
- }) => void;
140
+ }) => void | Promise<void>;
132
141
  /** Cancel bounded capture waits with the owning turn activity. */
133
142
  waitSignal?: AbortSignal;
134
143
  };
@@ -342,7 +351,7 @@ async function resolveCurrentHomeBackend(
342
351
  // A route epoch can advance without a provider replacement. Reuse the exact
343
352
  // established handle only when its identity still equals the durable one.
344
353
  if (lease.instanceId === established.instanceId) {
345
- services.onHomeSandboxRebound?.({
354
+ await services.onHomeSandboxRebound?.({
346
355
  established,
347
356
  leaseEpoch: lease.leaseEpoch,
348
357
  });
@@ -393,9 +402,15 @@ async function resolveCurrentHomeBackend(
393
402
  sandboxGroupId: ids.sandboxGroupId,
394
403
  expectedEpoch: lease.leaseEpoch,
395
404
  expectedInstanceId: lease.instanceId,
405
+ expectedBackend: lease.backend,
396
406
  diagnostic: "provider_not_found_during_home_route_rebind",
397
407
  });
398
408
  if (marked.status === "marked") {
409
+ await publishDurableSessionEvents(
410
+ services.bus,
411
+ ids.workspaceId,
412
+ marked.backgroundCommandEvents,
413
+ );
399
414
  await services.onHomeSandboxLost?.({
400
415
  sandboxGroupId: ids.sandboxGroupId,
401
416
  instanceId: lease.instanceId,
@@ -416,7 +431,7 @@ async function resolveCurrentHomeBackend(
416
431
  if (rebound.instanceId !== lease.instanceId || reboundBackend !== resumeBackend) {
417
432
  throw homeRouteRecoveryError(lease, lease.leaseEpoch);
418
433
  }
419
- services.onHomeSandboxRebound?.({
434
+ await services.onHomeSandboxRebound?.({
420
435
  established: rebound,
421
436
  leaseEpoch: lease.leaseEpoch,
422
437
  });
@@ -491,7 +506,7 @@ function afterPersistableHomeMutation(
491
506
  op: string;
492
507
  backend: ResolvedActiveBackend;
493
508
  admission: unknown;
494
- outcome: "resolved" | "rejected";
509
+ outcome: "resolved" | "rejected" | "outcome_unknown";
495
510
  result?: unknown;
496
511
  retainedProcess?: RoutingRetainedProcess;
497
512
  }) => Promise<void | RoutingMutationSettlementResult>)
@@ -518,7 +533,7 @@ function afterPersistableHomeMutation(
518
533
  ) {
519
534
  throw new Error("Persistable home mutation settlement lacked its bound admission");
520
535
  }
521
- if (outcome === "resolved" && retainedProcess) {
536
+ if ((outcome === "resolved" || outcome === "outcome_unknown") && retainedProcess) {
522
537
  try {
523
538
  await retainWorkspaceProviderCommand(services.db, {
524
539
  accountId: fence.accountId,
@@ -577,6 +592,8 @@ function afterPersistableHomeMutation(
577
592
  }
578
593
  return;
579
594
  }
595
+ if (outcome === "outcome_unknown")
596
+ throw new Error("Outcome-unknown command settlement requires its exact retained invocation");
580
597
  await verifyWorkspaceMutationSettlement(services.db, {
581
598
  accountId: fence.accountId,
582
599
  workspaceId: ids.workspaceId,
@@ -747,7 +764,7 @@ function settleRetainedProcessForTurn(
747
764
  if (
748
765
  !durable ||
749
766
  durable.providerSessionId !== process.providerSessionId ||
750
- durable.providerBackend !== backend.kind ||
767
+ durable.providerBackend !== (sandboxBackendForSdkBackendId(backend.kind) ?? backend.kind) ||
751
768
  durable.providerInstanceId !== backend.providerInstanceId ||
752
769
  durable.leaseEpoch !== backend.leaseEpoch ||
753
770
  durable.routeKind !== (backend.sandboxId === null ? "home" : "active") ||
@@ -778,15 +795,75 @@ function settleRetainedProcessForTurn(
778
795
  };
779
796
  }
780
797
 
798
+ /** Cleanup can observe the independently settled row even when this worker's
799
+ * pending output receipt or original provider transport can no longer advance. */
800
+ function isRetainedProcessSettledForTurn(services: RoutingWiringServices, ids: RoutingWiringIds) {
801
+ const fence = ids.workspaceMutationFence;
802
+ if (!fence) return undefined;
803
+ return async ({
804
+ backend,
805
+ process,
806
+ }: {
807
+ backend: ResolvedActiveBackend;
808
+ process: RoutingRetainedProcess;
809
+ }): Promise<boolean> => {
810
+ const durable = await getRetainedProcess(services.db, {
811
+ workspaceId: ids.workspaceId,
812
+ sessionId: ids.sessionId,
813
+ processId: process.id,
814
+ });
815
+ if (
816
+ !durable ||
817
+ durable.state === "active" ||
818
+ durable.providerSessionId !== process.providerSessionId ||
819
+ durable.providerBackend !== (sandboxBackendForSdkBackendId(backend.kind) ?? backend.kind) ||
820
+ durable.providerInstanceId !== backend.providerInstanceId ||
821
+ durable.leaseEpoch !== backend.leaseEpoch ||
822
+ durable.routeKind !== (backend.sandboxId === null ? "home" : "active") ||
823
+ durable.routeTargetId !== backend.sandboxId ||
824
+ durable.routeEpoch !== backend.activeEpoch
825
+ )
826
+ return false;
827
+ if (process.providerCommand) {
828
+ // The general process projection deliberately omits the provider locator.
829
+ // Load its immutable identity through the exact protected persistence seam.
830
+ const command = await retainedProviderCommandPersistence(services.db, {
831
+ accountId: fence.accountId,
832
+ workspaceId: ids.workspaceId,
833
+ sessionId: ids.sessionId,
834
+ processId: process.id,
835
+ }).load();
836
+ if (
837
+ !command ||
838
+ command.kind !== process.providerCommand.kind ||
839
+ command.execId !== process.providerCommand.execId ||
840
+ command.taskId !== process.providerCommand.taskId ||
841
+ command.sandboxId !== process.providerCommand.sandboxId ||
842
+ Boolean(command.pty) !== Boolean(process.providerCommand.pty) ||
843
+ (command.kind === "modal-router-v1" &&
844
+ process.providerCommand.kind === "modal-router-v1" &&
845
+ !isDeepStrictEqual(command.supervision, process.providerCommand.supervision))
846
+ )
847
+ return false;
848
+ }
849
+ // Terminal rows are written only through proof-gated physical settlement.
850
+ return true;
851
+ };
852
+ }
853
+
781
854
  function adoptRetainedProcessAsBackgroundCommandForTurn(
782
855
  services: RoutingWiringServices,
783
856
  ids: RoutingWiringIds,
784
857
  ):
785
- | ((input: { backend: ResolvedActiveBackend; process: RoutingRetainedProcess }) => Promise<void>)
858
+ | ((input: {
859
+ backend: ResolvedActiveBackend;
860
+ process: RoutingRetainedProcess;
861
+ command?: string | undefined;
862
+ }) => Promise<void>)
786
863
  | undefined {
787
864
  const fence = ids.workspaceMutationFence;
788
865
  if (!fence) return undefined;
789
- return async ({ backend, process }) => {
866
+ return async ({ backend, process, command }) => {
790
867
  const durable = await getRetainedProcess(services.db, {
791
868
  workspaceId: ids.workspaceId,
792
869
  sessionId: ids.sessionId,
@@ -795,7 +872,7 @@ function adoptRetainedProcessAsBackgroundCommandForTurn(
795
872
  if (
796
873
  !durable ||
797
874
  durable.providerSessionId !== process.providerSessionId ||
798
- durable.providerBackend !== backend.kind ||
875
+ durable.providerBackend !== (sandboxBackendForSdkBackendId(backend.kind) ?? backend.kind) ||
799
876
  durable.providerInstanceId !== backend.providerInstanceId ||
800
877
  durable.leaseEpoch !== backend.leaseEpoch ||
801
878
  durable.routeKind !== (backend.sandboxId === null ? "home" : "active") ||
@@ -813,7 +890,7 @@ function adoptRetainedProcessAsBackgroundCommandForTurn(
813
890
  attemptId: fence.attemptId,
814
891
  processId: process.id,
815
892
  expected: retainedProcessSettlementIdentity(durable),
816
- command: "execCommand",
893
+ command: command ?? "Background command",
817
894
  });
818
895
  };
819
896
  }
@@ -856,6 +933,7 @@ export function wrapTurnBoxWithRouting(
856
933
  const beforeProcessMutation = beforeRetainedProcessMutation(services, ids);
857
934
  const afterProcessMutation = afterRetainedProcessMutation(services, ids);
858
935
  const settleProcess = settleRetainedProcessForTurn(services, ids);
936
+ const isProcessSettled = isRetainedProcessSettledForTurn(services, ids);
859
937
  const adoptProcessAsBackgroundCommand = adoptRetainedProcessAsBackgroundCommandForTurn(
860
938
  services,
861
939
  ids,
@@ -1026,16 +1104,25 @@ export function wrapTurnBoxWithRouting(
1026
1104
  },
1027
1105
  resolveActiveBackend: resolver,
1028
1106
  ...(services.onSandboxOperation ? { onOperation: services.onSandboxOperation } : {}),
1107
+ ...(services.onSandboxCaptureWait ? { onCaptureWait: services.onSandboxCaptureWait } : {}),
1029
1108
  providerCommandHandle: admittedCommandHandle,
1109
+ providerSupervisionReady: async () =>
1110
+ settings.modalCommandSupervisionEnabled && (await supervisedCommandProtocolReady(db)),
1030
1111
  ...(ids.workspaceMutationFence
1031
1112
  ? {
1032
1113
  providerCommandPersistence: (process: RoutingRetainedProcess) =>
1033
- retainedProviderCommandPersistence(db, {
1034
- accountId: ids.workspaceMutationFence!.accountId,
1035
- workspaceId: ids.workspaceId,
1036
- sessionId: ids.sessionId,
1037
- processId: process.id,
1038
- }),
1114
+ retainedProviderCommandPersistence(
1115
+ db,
1116
+ {
1117
+ accountId: ids.workspaceMutationFence!.accountId,
1118
+ workspaceId: ids.workspaceId,
1119
+ sessionId: ids.sessionId,
1120
+ processId: process.id,
1121
+ },
1122
+ services.bus
1123
+ ? (events) => services.bus!.publish(ids.workspaceId, ids.sessionId, events)
1124
+ : undefined,
1125
+ ),
1039
1126
  }
1040
1127
  : {}),
1041
1128
  ...(beforeMutation ? { beforeMutation } : {}),
@@ -1043,6 +1130,7 @@ export function wrapTurnBoxWithRouting(
1043
1130
  ...(beforeProcessMutation ? { beforeProcessMutation } : {}),
1044
1131
  ...(afterProcessMutation ? { afterProcessMutation } : {}),
1045
1132
  ...(settleProcess ? { settleProcess } : {}),
1133
+ ...(isProcessSettled ? { isProcessSettled } : {}),
1046
1134
  ...(ids.workspaceMutationFence
1047
1135
  ? { observeProcessTerminal: observeRetainedProcessTerminalForTurn(services, ids)! }
1048
1136
  : {}),
@@ -1069,9 +1157,15 @@ export function wrapTurnBoxWithRouting(
1069
1157
  sandboxGroupId: home.sandboxGroupId,
1070
1158
  expectedEpoch,
1071
1159
  expectedInstanceId,
1160
+ expectedBackend: home.backend,
1072
1161
  diagnostic: "provider_not_found_during_routed_operation",
1073
1162
  });
1074
1163
  if (marked.status === "marked") {
1164
+ await publishDurableSessionEvents(
1165
+ services.bus,
1166
+ ids.workspaceId,
1167
+ marked.backgroundCommandEvents,
1168
+ );
1075
1169
  await services.onHomeSandboxLost?.({
1076
1170
  sandboxGroupId: home.sandboxGroupId,
1077
1171
  instanceId: expectedInstanceId,
@@ -1128,6 +1222,7 @@ export function wrapLazyTurnBoxWithRouting(
1128
1222
  const beforeProcessMutation = beforeRetainedProcessMutation(services, ids);
1129
1223
  const afterProcessMutation = afterRetainedProcessMutation(services, ids);
1130
1224
  const settleProcess = settleRetainedProcessForTurn(services, ids);
1225
+ const isProcessSettled = isRetainedProcessSettledForTurn(services, ids);
1131
1226
  const adoptProcessAsBackgroundCommand = adoptRetainedProcessAsBackgroundCommandForTurn(
1132
1227
  services,
1133
1228
  ids,
@@ -1276,17 +1371,26 @@ export function wrapLazyTurnBoxWithRouting(
1276
1371
  return routedResolver(pointer);
1277
1372
  },
1278
1373
  ...(services.onSandboxOperation ? { onOperation: services.onSandboxOperation } : {}),
1374
+ ...(services.onSandboxCaptureWait ? { onCaptureWait: services.onSandboxCaptureWait } : {}),
1279
1375
  ...(args.onFirstOperation ? { onFirstOperation: args.onFirstOperation } : {}),
1280
1376
  providerCommandHandle: admittedCommandHandle,
1377
+ providerSupervisionReady: async () =>
1378
+ settings.modalCommandSupervisionEnabled && (await supervisedCommandProtocolReady(db)),
1281
1379
  ...(ids.workspaceMutationFence
1282
1380
  ? {
1283
1381
  providerCommandPersistence: (process: RoutingRetainedProcess) =>
1284
- retainedProviderCommandPersistence(db, {
1285
- accountId: ids.workspaceMutationFence!.accountId,
1286
- workspaceId: ids.workspaceId,
1287
- sessionId: ids.sessionId,
1288
- processId: process.id,
1289
- }),
1382
+ retainedProviderCommandPersistence(
1383
+ db,
1384
+ {
1385
+ accountId: ids.workspaceMutationFence!.accountId,
1386
+ workspaceId: ids.workspaceId,
1387
+ sessionId: ids.sessionId,
1388
+ processId: process.id,
1389
+ },
1390
+ services.bus
1391
+ ? (events) => services.bus!.publish(ids.workspaceId, ids.sessionId, events)
1392
+ : undefined,
1393
+ ),
1290
1394
  }
1291
1395
  : {}),
1292
1396
  ...(beforeMutation ? { beforeMutation } : {}),
@@ -1294,6 +1398,7 @@ export function wrapLazyTurnBoxWithRouting(
1294
1398
  ...(beforeProcessMutation ? { beforeProcessMutation } : {}),
1295
1399
  ...(afterProcessMutation ? { afterProcessMutation } : {}),
1296
1400
  ...(settleProcess ? { settleProcess } : {}),
1401
+ ...(isProcessSettled ? { isProcessSettled } : {}),
1297
1402
  ...(ids.workspaceMutationFence
1298
1403
  ? { observeProcessTerminal: observeRetainedProcessTerminalForTurn(services, ids)! }
1299
1404
  : {}),
@@ -1321,9 +1426,15 @@ export function wrapLazyTurnBoxWithRouting(
1321
1426
  sandboxGroupId: home.sandboxGroupId,
1322
1427
  expectedEpoch: backend.leaseEpoch,
1323
1428
  expectedInstanceId: backend.providerInstanceId,
1429
+ expectedBackend: home.backend,
1324
1430
  diagnostic: "provider_not_found_during_routed_operation",
1325
1431
  });
1326
1432
  if (marked.status === "marked") {
1433
+ await publishDurableSessionEvents(
1434
+ services.bus,
1435
+ ids.workspaceId,
1436
+ marked.backgroundCommandEvents,
1437
+ );
1327
1438
  await services.onHomeSandboxLost?.({
1328
1439
  sandboxGroupId: home.sandboxGroupId,
1329
1440
  instanceId: backend.providerInstanceId,
@@ -1606,6 +1717,20 @@ export async function establishSelfhostedTurnSession(
1606
1717
  reason: command.reason,
1607
1718
  ...(command.failure ? { failure: command.failure } : {}),
1608
1719
  });
1720
+ if (command.outputReceipt && command.outcome === "exited" && command.exitCode !== null) {
1721
+ await recordConnectedCommandOutputConsumption(db, {
1722
+ accountId: args.accountId,
1723
+ workspaceId: args.workspaceId,
1724
+ sessionId: args.sessionId,
1725
+ commandId: command.commandId,
1726
+ controlWorkspaceId: command.controlWorkspaceId,
1727
+ enrollmentId: command.enrollmentId,
1728
+ connectionInstanceId: command.connectionInstanceId,
1729
+ opId: command.opId,
1730
+ receipt: command.outputReceipt,
1731
+ exitCode: command.exitCode,
1732
+ });
1733
+ }
1609
1734
  if (settlement && bus) {
1610
1735
  await bus
1611
1736
  .publish(args.workspaceId, args.sessionId, settlement.events)
@@ -0,0 +1,112 @@
1
+ import { sandboxLeaseTelemetryKey, type Observability } from "@opengeni/observability";
2
+
3
+ type SafeSnapshotError = {
4
+ errorClass: "SnapshotOperationError";
5
+ errorCode: "snapshot_operation_failed";
6
+ status?: number;
7
+ origin: "sandbox-resume" | "worker-lifecycle";
8
+ causeName?: string;
9
+ integrityCode?: string;
10
+ providerErrorName?: string;
11
+ providerGrpcCode?: number;
12
+ providerHttpStatus?: number;
13
+ providerRetryable?: boolean;
14
+ };
15
+
16
+ export function safeSnapshotError(
17
+ error: unknown,
18
+ origin: SafeSnapshotError["origin"] = "sandbox-resume",
19
+ ): SafeSnapshotError {
20
+ const fields: SafeSnapshotError = {
21
+ errorClass: "SnapshotOperationError",
22
+ errorCode: "snapshot_operation_failed",
23
+ origin,
24
+ };
25
+ try {
26
+ if (error && typeof error === "object") {
27
+ const candidate = error as {
28
+ name?: unknown;
29
+ code?: unknown;
30
+ status?: unknown;
31
+ statusCode?: unknown;
32
+ };
33
+ if (
34
+ typeof candidate.name === "string" &&
35
+ /^[A-Z][A-Za-z0-9]{2,62}Error$/u.test(candidate.name)
36
+ ) {
37
+ fields.causeName = candidate.name;
38
+ }
39
+ if (typeof candidate.code === "string" && /^[a-z0-9_]{1,64}$/u.test(candidate.code)) {
40
+ fields.integrityCode = candidate.code;
41
+ }
42
+ const status = Number(candidate.status ?? candidate.statusCode);
43
+ if (Number.isInteger(status) && status >= 100 && status <= 599) fields.status = status;
44
+ }
45
+ } catch {
46
+ // Diagnostics must never replace the exact internal snapshot failure.
47
+ }
48
+ // The SDK wraps provider classification in details, not Error.cause. Never
49
+ // project free-form messages, causes, request IDs or payloads. Read fields
50
+ // independently so a hostile getter cannot hide other safe classifications.
51
+ const read = (value: unknown, key: string): unknown => {
52
+ try {
53
+ return value && typeof value === "object" ? Reflect.get(value, key) : undefined;
54
+ } catch {
55
+ return undefined;
56
+ }
57
+ };
58
+ const details = read(error, "details");
59
+ const providerName = read(details, "errorName");
60
+ if (
61
+ typeof providerName === "string" &&
62
+ [
63
+ "ClientError",
64
+ "TimeoutError",
65
+ "ConnectionError",
66
+ "AuthError",
67
+ "NotFoundError",
68
+ "InvalidError",
69
+ "RemoteError",
70
+ "AbortError",
71
+ ].includes(providerName)
72
+ )
73
+ fields.providerErrorName = providerName;
74
+ const grpcCode = read(details, "errorCode");
75
+ if (
76
+ providerName === "ClientError" &&
77
+ typeof grpcCode === "number" &&
78
+ Number.isInteger(grpcCode) &&
79
+ grpcCode >= 0 &&
80
+ grpcCode <= 16
81
+ )
82
+ fields.providerGrpcCode = grpcCode;
83
+ for (const key of ["status", "httpStatus", "responseStatus"]) {
84
+ const status = read(details, key);
85
+ if (typeof status === "number" && Number.isInteger(status) && status >= 100 && status <= 599) {
86
+ fields.providerHttpStatus = status;
87
+ break;
88
+ }
89
+ }
90
+ const retryable = read(error, "retryable");
91
+ if (typeof retryable === "boolean") fields.providerRetryable = retryable;
92
+ return fields;
93
+ }
94
+
95
+ export function warnDrainSnapshotFailure(
96
+ observability: Pick<Observability, "warn">,
97
+ message: string,
98
+ error: unknown,
99
+ lease: { workspaceId?: string; sandboxGroupId: string; leaseEpoch: number },
100
+ ): void {
101
+ try {
102
+ observability.warn(message, {
103
+ ...safeSnapshotError(error, "worker-lifecycle"),
104
+ ...(lease.workspaceId
105
+ ? { sandboxLeaseKey: sandboxLeaseTelemetryKey(lease.workspaceId, lease.sandboxGroupId) }
106
+ : {}),
107
+ leaseEpoch: lease.leaseEpoch,
108
+ });
109
+ } catch {
110
+ // Logging cannot replace the provider failure or prevent claim release.
111
+ }
112
+ }
@@ -0,0 +1,23 @@
1
+ import type { SessionEvent } from "@opengeni/contracts";
2
+ import type { EventBus } from "@opengeni/events";
3
+
4
+ /** Best-effort live fanout of events that are already durable, grouped by
5
+ * session. Durable catch-up remains the source of truth, so failures are only
6
+ * reported. */
7
+ export async function publishDurableSessionEvents(
8
+ bus: EventBus | null | undefined,
9
+ workspaceId: string,
10
+ events: readonly SessionEvent[] | undefined,
11
+ onError: (error: unknown) => void = () => undefined,
12
+ ): Promise<void> {
13
+ if (!bus || !events?.length) return;
14
+ for (const sessionId of new Set(events.map((event) => event.sessionId))) {
15
+ await bus
16
+ .publish(
17
+ workspaceId,
18
+ sessionId,
19
+ events.filter((event) => event.sessionId === sessionId),
20
+ )
21
+ .catch(onError);
22
+ }
23
+ }
@@ -0,0 +1,15 @@
1
+ import type { LeaseSnapshot } from "@opengeni/db";
2
+
3
+ /** Physical capture retention is not permission to heartbeat or execute a closed turn. */
4
+ export function retainsHolderForWarmCapture(
5
+ lease: Pick<LeaseSnapshot, "liveness" | "leaseEpoch" | "archiveCapture"> | null,
6
+ expectedEpoch: number,
7
+ now = Date.now(),
8
+ ): boolean {
9
+ return Boolean(
10
+ lease?.liveness === "warm" &&
11
+ lease.leaseEpoch === expectedEpoch &&
12
+ lease.archiveCapture &&
13
+ lease.archiveCapture.deadlineAt.getTime() > now,
14
+ );
15
+ }
@@ -172,3 +172,56 @@ function safeLifecycleLog(log: () => void): void {
172
172
  // Telemetry cannot change worker lifecycle state or shutdown delivery.
173
173
  }
174
174
  }
175
+
176
+ /** A cleanup stall first drains peers through WORKER_SHUTDOWN. The standalone
177
+ * process owns the final exit, because Temporal cannot kill JavaScript writers.
178
+ * Embedded hosts may supply their own termination policy at this same edge. */
179
+ export function createWorkerCleanupContainment(input: {
180
+ drain: () => boolean;
181
+ terminate?: () => void;
182
+ observability: Observability;
183
+ timeoutMs?: number;
184
+ }) {
185
+ let requested = false;
186
+ let finished = false;
187
+ let timer: ReturnType<typeof setTimeout> | undefined;
188
+ return {
189
+ request() {
190
+ if (requested || finished) return;
191
+ requested = true;
192
+ if (input.terminate) {
193
+ timer = setTimeout(() => {
194
+ safeLifecycleLog(() =>
195
+ input.observability.error("worker cleanup drain exhausted; terminating host", {
196
+ errorClass: "WorkerLifecycleOperationError",
197
+ errorCode: "worker_cleanup_drain_exhausted",
198
+ origin: "worker-lifecycle",
199
+ }),
200
+ );
201
+ input.terminate?.();
202
+ }, input.timeoutMs ?? 100_000);
203
+ timer.unref?.();
204
+ }
205
+ let accepted = false;
206
+ try {
207
+ accepted = input.drain();
208
+ } catch {
209
+ /* The host backstop remains armed. */
210
+ }
211
+ if (!accepted)
212
+ safeLifecycleLog(() =>
213
+ input.observability.error("worker cleanup drain request failed", {
214
+ errorClass: "WorkerLifecycleOperationError",
215
+ errorCode: "worker_shutdown_request_failed",
216
+ origin: "worker-lifecycle",
217
+ }),
218
+ );
219
+ },
220
+ finished() {
221
+ // Call only after Worker.run resolves. ForceShutdownError does not prove
222
+ // its activity promises stopped, so rejection must retain the backstop.
223
+ finished = true;
224
+ clearTimeout(timer);
225
+ },
226
+ };
227
+ }
@@ -1,4 +1,9 @@
1
- import { ActivityCancellationType, proxyActivities } from "@temporalio/workflow";
1
+ import {
2
+ ActivityCancellationType,
3
+ ActivityFailure,
4
+ ApplicationFailure,
5
+ proxyActivities,
6
+ } from "@temporalio/workflow";
2
7
  import type * as activities from "../activities";
3
8
  import {
4
9
  KNOWLEDGE_SOURCE_SYNC_ACTIVITY_HEARTBEAT_TIMEOUT_MS,
@@ -10,12 +15,14 @@ type WorkflowControlActivities = Pick<
10
15
  | "enqueueGoalRetryWake"
11
16
  | "expireSessionHumanInput"
12
17
  | "expireSessionInteractionIntervention"
18
+ | "expireScheduledRunHumanWait"
13
19
  | "failSessionAttempt"
14
20
  | "getCodexCapacityWait"
15
21
  | "markSessionIdle"
16
22
  | "peekSessionWork"
17
23
  | "persistSessionAttemptQuiescence"
18
24
  | "reconcileSessionAttemptQuiescence"
25
+ | "reconcileSettledSessionAttempt"
19
26
  | "reconcileCodexCapacityWait"
20
27
  | "recoverDispatch"
21
28
  | "recoverEscapedMcpTimeout"
@@ -160,6 +167,14 @@ export const knowledgeSourceSyncActivity = proxyActivities<
160
167
  });
161
168
 
162
169
  export function workflowFailureMessage(error: unknown): string {
170
+ if (
171
+ error instanceof ActivityFailure &&
172
+ error.activityType === "runAgentTurn" &&
173
+ error.cause instanceof ApplicationFailure &&
174
+ error.cause.type === "TurnExecutionPolicyDefinitionMismatchError"
175
+ ) {
176
+ return error.cause.message;
177
+ }
163
178
  if (error instanceof Error) {
164
179
  return error.message;
165
180
  }
@@ -0,0 +1,50 @@
1
+ import {
2
+ ParentClosePolicy,
3
+ WorkflowIdReusePolicy,
4
+ log,
5
+ proxyActivities,
6
+ startChild,
7
+ workflowInfo,
8
+ } from "@temporalio/workflow";
9
+ import type { BrowserDeadlineCheckpointTarget } from "@opengeni/db";
10
+ import type * as activities from "../activities";
11
+
12
+ const inventory = proxyActivities<Pick<typeof activities, "listDueBrowserCheckpoints">>({
13
+ startToCloseTimeout: "5 minutes",
14
+ heartbeatTimeout: "20 seconds",
15
+ });
16
+ const checkpoint = proxyActivities<Pick<typeof activities, "checkpointBrowserBeforeDeadline">>({
17
+ startToCloseTimeout: "25 minutes",
18
+ heartbeatTimeout: "20 seconds",
19
+ retry: { initialInterval: "1 second", maximumInterval: "30 seconds" },
20
+ });
21
+
22
+ export async function browserDeadlineCheckpointWorkflow(target: BrowserDeadlineCheckpointTarget) {
23
+ return await checkpoint.checkpointBrowserBeforeDeadline(target);
24
+ }
25
+
26
+ /** Runs from the existing lease-sweep dispatch. Inventory never performs provider
27
+ * I/O; each exact browser has an independent durable capture/cleanup child. */
28
+ export async function browserDeadlineCheckpointSweepWorkflow(): Promise<void> {
29
+ const targets = await inventory.listDueBrowserCheckpoints();
30
+ await Promise.all(
31
+ targets.map(async (target) => {
32
+ try {
33
+ await startChild(browserDeadlineCheckpointWorkflow, {
34
+ workflowId: `browser-deadline:${target.browserSessionId}:${target.leaseEpoch}:${target.controllerGeneration}`,
35
+ taskQueue: workflowInfo().taskQueue,
36
+ workflowIdReusePolicy: WorkflowIdReusePolicy.ALLOW_DUPLICATE,
37
+ parentClosePolicy: ParentClosePolicy.ABANDON,
38
+ args: [target],
39
+ });
40
+ } catch (error) {
41
+ if (error instanceof Error && error.name === "WorkflowExecutionAlreadyStartedError") return;
42
+ // Another sweep retries start; it cannot retire the browser's authority.
43
+ log.warn("browser deadline checkpoint child start deferred", {
44
+ browserSessionId: target.browserSessionId,
45
+ error: error instanceof Error ? error.name : "unknown",
46
+ });
47
+ }
48
+ }),
49
+ );
50
+ }