@opengeni/worker-bundle 2.3.1 → 2.3.2-canary.37090756480001

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 (66) hide show
  1. package/dist/activities/agent-turn/claude-usage-observer.d.ts +30 -6
  2. package/dist/activities/agent-turn/credential-leases.d.ts +5 -32
  3. package/dist/activities/agent-turn/errors.d.ts +3 -0
  4. package/dist/activities/agent-turn/finalization-monitor.d.ts +1 -1
  5. package/dist/activities/agent-turn/finalization.d.ts +2 -0
  6. package/dist/activities/agent-turn/quiescence.d.ts +3 -4
  7. package/dist/activities/agent-turn/subscription-lease.d.ts +38 -0
  8. package/dist/activities/agent-turn/turn-context.d.ts +1 -1
  9. package/dist/activities/model-call-fact-reconciler.d.ts +34 -0
  10. package/dist/activities/session-state.d.ts +2 -2
  11. package/dist/activities/types.d.ts +7 -0
  12. package/dist/{activities-control-3HAH4FMG.js → activities-control-3BY4IJKL.js} +166 -35
  13. package/dist/activities-control-3BY4IJKL.js.map +1 -0
  14. package/dist/activities-control.d.ts +6 -4
  15. package/dist/{activities-turn-KU25J3VQ.js → activities-turn-TTNAVGKN.js} +263 -308
  16. package/dist/activities-turn-TTNAVGKN.js.map +1 -0
  17. package/dist/activities.d.ts +6 -4
  18. package/dist/{chunk-3K3ULWEE.js → chunk-PA7Z55P4.js} +242 -17
  19. package/dist/chunk-PA7Z55P4.js.map +1 -0
  20. package/dist/{chunk-YG27NEMX.js → chunk-YOI3Z3GF.js} +9 -1
  21. package/dist/chunk-YOI3Z3GF.js.map +1 -0
  22. package/dist/http.d.ts +1 -1
  23. package/dist/index.d.ts +6 -2
  24. package/dist/index.js +82 -16
  25. package/dist/index.js.map +1 -1
  26. package/dist/observability-metrics.d.ts +6 -1
  27. package/dist/sandbox-resume.d.ts +3 -0
  28. package/dist/session-event-fanout.d.ts +6 -0
  29. package/dist/worker-service-lifecycle.d.ts +12 -0
  30. package/dist/workflow-bundle.js +3 -2
  31. package/package.json +21 -21
  32. package/src/activities/agent-turn/claim.ts +13 -0
  33. package/src/activities/agent-turn/claude-usage-observer.ts +99 -8
  34. package/src/activities/agent-turn/credential-leases.ts +86 -213
  35. package/src/activities/agent-turn/errors.ts +61 -1
  36. package/src/activities/agent-turn/failure-settlement.ts +59 -5
  37. package/src/activities/agent-turn/finalization-monitor.ts +3 -4
  38. package/src/activities/agent-turn/finalization.ts +8 -3
  39. package/src/activities/agent-turn/quiescence.ts +3 -4
  40. package/src/activities/agent-turn/run.ts +24 -20
  41. package/src/activities/agent-turn/sandbox-establish.ts +2 -0
  42. package/src/activities/agent-turn/sandbox-runtime.ts +5 -1
  43. package/src/activities/agent-turn/skill-selection.ts +2 -0
  44. package/src/activities/agent-turn/subscription-lease.ts +115 -0
  45. package/src/activities/agent-turn/turn-context.ts +1 -4
  46. package/src/activities/codex-rotation.ts +2 -12
  47. package/src/activities/model-call-fact-reconciler.ts +112 -0
  48. package/src/activities/sandbox-lease.ts +59 -8
  49. package/src/activities/session-state.ts +29 -4
  50. package/src/activities/types.ts +7 -0
  51. package/src/activities-control.ts +2 -0
  52. package/src/activities-turn.ts +5 -0
  53. package/src/activities.ts +7 -0
  54. package/src/http.ts +4 -2
  55. package/src/index.ts +39 -9
  56. package/src/observability-metrics.ts +27 -0
  57. package/src/sandbox-resume.ts +9 -0
  58. package/src/sandbox-routing.ts +77 -0
  59. package/src/session-event-fanout.ts +23 -0
  60. package/src/worker-service-lifecycle.ts +53 -0
  61. package/src/workflows/file-upload-reaper.ts +2 -0
  62. package/src/workflows/session.ts +6 -1
  63. package/dist/activities-control-3HAH4FMG.js.map +0 -1
  64. package/dist/activities-turn-KU25J3VQ.js.map +0 -1
  65. package/dist/chunk-3K3ULWEE.js.map +0 -1
  66. package/dist/chunk-YG27NEMX.js.map +0 -1
@@ -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).
@@ -47,6 +48,7 @@ import { observeSessionBackgroundCommandCompletion } from "@opengeni/db/session-
47
48
  import { appendSessionCommandOutput } from "@opengeni/db/session-command-output";
48
49
  import type { OpStreamOutputFrame } from "@opengeni/runtime/sandbox";
49
50
  import type { EventBus } from "@opengeni/events";
51
+ import { publishDurableSessionEvents } from "./session-event-fanout";
50
52
  import {
51
53
  buildSelfhostedBackendSession,
52
54
  ActiveBackendUnresolvableError,
@@ -401,6 +403,11 @@ async function resolveCurrentHomeBackend(
401
403
  diagnostic: "provider_not_found_during_home_route_rebind",
402
404
  });
403
405
  if (marked.status === "marked") {
406
+ await publishDurableSessionEvents(
407
+ services.bus,
408
+ ids.workspaceId,
409
+ marked.backgroundCommandEvents,
410
+ );
404
411
  await services.onHomeSandboxLost?.({
405
412
  sandboxGroupId: ids.sandboxGroupId,
406
413
  instanceId: lease.instanceId,
@@ -785,6 +792,62 @@ function settleRetainedProcessForTurn(
785
792
  };
786
793
  }
787
794
 
795
+ /** Cleanup can observe the independently settled row even when this worker's
796
+ * pending output receipt or original provider transport can no longer advance. */
797
+ function isRetainedProcessSettledForTurn(services: RoutingWiringServices, ids: RoutingWiringIds) {
798
+ const fence = ids.workspaceMutationFence;
799
+ if (!fence) return undefined;
800
+ return async ({
801
+ backend,
802
+ process,
803
+ }: {
804
+ backend: ResolvedActiveBackend;
805
+ process: RoutingRetainedProcess;
806
+ }): Promise<boolean> => {
807
+ const durable = await getRetainedProcess(services.db, {
808
+ workspaceId: ids.workspaceId,
809
+ sessionId: ids.sessionId,
810
+ processId: process.id,
811
+ });
812
+ if (
813
+ !durable ||
814
+ durable.state === "active" ||
815
+ durable.providerSessionId !== process.providerSessionId ||
816
+ durable.providerBackend !== (sandboxBackendForSdkBackendId(backend.kind) ?? backend.kind) ||
817
+ durable.providerInstanceId !== backend.providerInstanceId ||
818
+ durable.leaseEpoch !== backend.leaseEpoch ||
819
+ durable.routeKind !== (backend.sandboxId === null ? "home" : "active") ||
820
+ durable.routeTargetId !== backend.sandboxId ||
821
+ durable.routeEpoch !== backend.activeEpoch
822
+ )
823
+ return false;
824
+ if (process.providerCommand) {
825
+ // The general process projection deliberately omits the provider locator.
826
+ // Load its immutable identity through the exact protected persistence seam.
827
+ const command = await retainedProviderCommandPersistence(services.db, {
828
+ accountId: fence.accountId,
829
+ workspaceId: ids.workspaceId,
830
+ sessionId: ids.sessionId,
831
+ processId: process.id,
832
+ }).load();
833
+ if (
834
+ !command ||
835
+ command.kind !== process.providerCommand.kind ||
836
+ command.execId !== process.providerCommand.execId ||
837
+ command.taskId !== process.providerCommand.taskId ||
838
+ command.sandboxId !== process.providerCommand.sandboxId ||
839
+ Boolean(command.pty) !== Boolean(process.providerCommand.pty) ||
840
+ (command.kind === "modal-router-v1" &&
841
+ process.providerCommand.kind === "modal-router-v1" &&
842
+ !isDeepStrictEqual(command.supervision, process.providerCommand.supervision))
843
+ )
844
+ return false;
845
+ }
846
+ // Terminal rows are written only through proof-gated physical settlement.
847
+ return true;
848
+ };
849
+ }
850
+
788
851
  function adoptRetainedProcessAsBackgroundCommandForTurn(
789
852
  services: RoutingWiringServices,
790
853
  ids: RoutingWiringIds,
@@ -867,6 +930,7 @@ export function wrapTurnBoxWithRouting(
867
930
  const beforeProcessMutation = beforeRetainedProcessMutation(services, ids);
868
931
  const afterProcessMutation = afterRetainedProcessMutation(services, ids);
869
932
  const settleProcess = settleRetainedProcessForTurn(services, ids);
933
+ const isProcessSettled = isRetainedProcessSettledForTurn(services, ids);
870
934
  const adoptProcessAsBackgroundCommand = adoptRetainedProcessAsBackgroundCommandForTurn(
871
935
  services,
872
936
  ids,
@@ -1063,6 +1127,7 @@ export function wrapTurnBoxWithRouting(
1063
1127
  ...(beforeProcessMutation ? { beforeProcessMutation } : {}),
1064
1128
  ...(afterProcessMutation ? { afterProcessMutation } : {}),
1065
1129
  ...(settleProcess ? { settleProcess } : {}),
1130
+ ...(isProcessSettled ? { isProcessSettled } : {}),
1066
1131
  ...(ids.workspaceMutationFence
1067
1132
  ? { observeProcessTerminal: observeRetainedProcessTerminalForTurn(services, ids)! }
1068
1133
  : {}),
@@ -1093,6 +1158,11 @@ export function wrapTurnBoxWithRouting(
1093
1158
  diagnostic: "provider_not_found_during_routed_operation",
1094
1159
  });
1095
1160
  if (marked.status === "marked") {
1161
+ await publishDurableSessionEvents(
1162
+ services.bus,
1163
+ ids.workspaceId,
1164
+ marked.backgroundCommandEvents,
1165
+ );
1096
1166
  await services.onHomeSandboxLost?.({
1097
1167
  sandboxGroupId: home.sandboxGroupId,
1098
1168
  instanceId: expectedInstanceId,
@@ -1149,6 +1219,7 @@ export function wrapLazyTurnBoxWithRouting(
1149
1219
  const beforeProcessMutation = beforeRetainedProcessMutation(services, ids);
1150
1220
  const afterProcessMutation = afterRetainedProcessMutation(services, ids);
1151
1221
  const settleProcess = settleRetainedProcessForTurn(services, ids);
1222
+ const isProcessSettled = isRetainedProcessSettledForTurn(services, ids);
1152
1223
  const adoptProcessAsBackgroundCommand = adoptRetainedProcessAsBackgroundCommandForTurn(
1153
1224
  services,
1154
1225
  ids,
@@ -1324,6 +1395,7 @@ export function wrapLazyTurnBoxWithRouting(
1324
1395
  ...(beforeProcessMutation ? { beforeProcessMutation } : {}),
1325
1396
  ...(afterProcessMutation ? { afterProcessMutation } : {}),
1326
1397
  ...(settleProcess ? { settleProcess } : {}),
1398
+ ...(isProcessSettled ? { isProcessSettled } : {}),
1327
1399
  ...(ids.workspaceMutationFence
1328
1400
  ? { observeProcessTerminal: observeRetainedProcessTerminalForTurn(services, ids)! }
1329
1401
  : {}),
@@ -1355,6 +1427,11 @@ export function wrapLazyTurnBoxWithRouting(
1355
1427
  diagnostic: "provider_not_found_during_routed_operation",
1356
1428
  });
1357
1429
  if (marked.status === "marked") {
1430
+ await publishDurableSessionEvents(
1431
+ services.bus,
1432
+ ids.workspaceId,
1433
+ marked.backgroundCommandEvents,
1434
+ );
1358
1435
  await services.onHomeSandboxLost?.({
1359
1436
  sandboxGroupId: home.sandboxGroupId,
1360
1437
  instanceId: backend.providerInstanceId,
@@ -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
+ }
@@ -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
+ }
@@ -8,6 +8,7 @@ const reaperActivity = proxyActivities<
8
8
  | "maintainRetainedScreenshots"
9
9
  | "maintainBrowserStateArtifacts"
10
10
  | "recoverVideoGenerationWorkflows"
11
+ | "reconcileRecentModelCallFacts"
11
12
  >
12
13
  >({
13
14
  startToCloseTimeout: "5 minutes",
@@ -20,4 +21,5 @@ export async function fileUploadReaperWorkflow(): Promise<void> {
20
21
  await reaperActivity.maintainRetainedScreenshots();
21
22
  await reaperActivity.maintainBrowserStateArtifacts();
22
23
  await reaperActivity.recoverVideoGenerationWorkflows();
24
+ await reaperActivity.reconcileRecentModelCallFacts();
23
25
  }
@@ -367,7 +367,12 @@ export function postClaimDatabaseRecoveryDetail(
367
367
  if (
368
368
  (detail.sandboxSetupOutcomeUnknown !== undefined &&
369
369
  detail.sandboxSetupOutcomeUnknown !== true) ||
370
- (detail.sandboxSetupOutcomeUnknown === true && hasProviderRecoveryCount) ||
370
+ (detail.sandboxSetupRecoveryExhausted !== undefined &&
371
+ detail.sandboxSetupRecoveryExhausted !== true) ||
372
+ (detail.sandboxSetupOutcomeUnknown === true && detail.sandboxSetupRecoveryExhausted === true) ||
373
+ ((detail.sandboxSetupOutcomeUnknown === true ||
374
+ detail.sandboxSetupRecoveryExhausted === true) &&
375
+ hasProviderRecoveryCount) ||
371
376
  hasProviderRecoveryCount !== hasProviderFailureCode ||
372
377
  (hasProviderRecoveryCount &&
373
378
  (!Number.isSafeInteger(detail.providerRecoveryCount) ||