@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.
- package/dist/activities/agent-run-admission.d.ts +3 -1
- package/dist/activities/agent-turn/admission.d.ts +24 -3
- package/dist/activities/agent-turn/agent-build.d.ts +4 -0
- package/dist/activities/agent-turn/claude-usage-observer.d.ts +58 -0
- package/dist/activities/agent-turn/code-search.d.ts +60 -0
- package/dist/activities/agent-turn/codex-capacity.d.ts +13 -0
- package/dist/activities/agent-turn/codex-plan-entitlement.d.ts +105 -0
- package/dist/activities/agent-turn/codex.d.ts +3 -2
- package/dist/activities/agent-turn/compaction-prep.d.ts +21 -5
- package/dist/activities/agent-turn/credential-leases.d.ts +19 -33
- package/dist/activities/agent-turn/credential-restriction.d.ts +3 -0
- package/dist/activities/agent-turn/errors.d.ts +82 -11
- package/dist/activities/agent-turn/failure-settlement.d.ts +6 -3
- package/dist/activities/agent-turn/final-reply.d.ts +18 -0
- package/dist/activities/agent-turn/finalization-monitor.d.ts +17 -0
- package/dist/activities/agent-turn/finalization.d.ts +4 -2
- package/dist/activities/agent-turn/github-repository-bindings.d.ts +2 -0
- package/dist/activities/agent-turn/governance-model.d.ts +9 -2
- package/dist/activities/agent-turn/history-sink.d.ts +2 -0
- package/dist/activities/agent-turn/history.d.ts +2 -0
- package/dist/activities/agent-turn/input-wait-reply.d.ts +41 -0
- package/dist/activities/agent-turn/media-artifacts.d.ts +5 -3
- package/dist/activities/agent-turn/model-call-admission.d.ts +15 -0
- package/dist/activities/agent-turn/model-usage.d.ts +6 -0
- package/dist/activities/agent-turn/model-wire-dispatch.d.ts +15 -0
- package/dist/activities/agent-turn/optional-repositories.d.ts +59 -0
- package/dist/activities/agent-turn/provider-dispatch-barrier.d.ts +12 -1
- package/dist/activities/agent-turn/quiescence.d.ts +7 -7
- package/dist/activities/agent-turn/recovery-warning.d.ts +9 -0
- package/dist/activities/agent-turn/refresh-credentials.d.ts +6 -0
- package/dist/activities/agent-turn/run-credentials.d.ts +8 -3
- package/dist/activities/agent-turn/sandbox-establish.d.ts +2 -1
- package/dist/activities/agent-turn/sandbox-provision.d.ts +3 -1
- package/dist/activities/agent-turn/sandbox-route.d.ts +3 -20
- package/dist/activities/agent-turn/sandbox-runtime.d.ts +2 -2
- package/dist/activities/agent-turn/session-title.d.ts +34 -2
- package/dist/activities/agent-turn/skill-checkout.d.ts +25 -6
- package/dist/activities/agent-turn/skill-read.d.ts +70 -2
- package/dist/activities/agent-turn/skill-remove.d.ts +14 -0
- package/dist/activities/agent-turn/skill-search.d.ts +3 -1
- package/dist/activities/agent-turn/skill-tools.d.ts +15 -2
- package/dist/activities/agent-turn/skill-transfer.d.ts +18 -6
- package/dist/activities/agent-turn/stream-attempt.d.ts +7 -3
- package/dist/activities/agent-turn/subscription-lease.d.ts +38 -0
- package/dist/activities/agent-turn/tool-environment.d.ts +56 -6
- package/dist/activities/agent-turn/tool-policy.d.ts +43 -0
- package/dist/activities/agent-turn/turn-context.d.ts +29 -0
- package/dist/activities/agent-turn/turn-sandbox-access.d.ts +3 -0
- package/dist/activities/agent-turn/xai-capacity.d.ts +2 -1
- package/dist/activities/automations.d.ts +3 -1
- package/dist/activities/browser-deadline-checkpoint.d.ts +29 -0
- package/dist/activities/codemode-dispatcher.d.ts +9 -2
- package/dist/activities/codex-capacity.d.ts +18 -2
- package/dist/activities/codex-rotation.d.ts +7 -1
- package/dist/activities/context-compaction.d.ts +16 -2
- package/dist/activities/gmail-files.d.ts +32 -0
- package/dist/activities/goals.d.ts +8 -0
- package/dist/activities/knowledge-indexing.d.ts +19 -0
- package/dist/activities/mcp-account-routes.d.ts +3 -0
- package/dist/activities/mcp-credentials.d.ts +12 -13
- package/dist/activities/mcp-operation-authority.d.ts +0 -5
- package/dist/activities/model-call-fact-reconciler.d.ts +34 -0
- package/dist/activities/open-suffix-resume.d.ts +13 -0
- package/dist/activities/parent-wake.d.ts +6 -12
- package/dist/activities/retained-screenshots.d.ts +7 -2
- package/dist/activities/rig-verification.d.ts +2 -3
- package/dist/activities/run-credential-renewal.d.ts +1 -0
- package/dist/activities/run-credentials.d.ts +15 -3
- package/dist/activities/sandbox-images.d.ts +1 -1
- package/dist/activities/sandbox-lease.d.ts +47 -9
- package/dist/activities/scheduled-tasks.d.ts +4 -35
- package/dist/activities/session-state.d.ts +70 -3
- package/dist/activities/types.d.ts +55 -6
- package/dist/activities/workspace-capture.d.ts +2 -0
- package/dist/activities/workspace-credential-provider.d.ts +46 -0
- package/dist/{activities-control-HMFGSKC5.js → activities-control-BNPTRTOE.js} +1896 -418
- package/dist/activities-control-BNPTRTOE.js.map +1 -0
- package/dist/activities-control.d.ts +182 -4
- package/dist/{activities-turn-AP67JADS.js → activities-turn-5LGZOSVU.js} +4476 -1327
- package/dist/activities-turn-5LGZOSVU.js.map +1 -0
- package/dist/activities.d.ts +182 -4
- package/dist/artifact-materializer-entry.js +5 -2
- package/dist/artifact-materializer-entry.js.map +1 -1
- package/dist/artifact-outbox-entry.js +5 -2
- package/dist/artifact-outbox-entry.js.map +1 -1
- package/dist/browser-deadline-checkpoint-contract.d.ts +2 -0
- package/dist/{chunk-A4XQWVZK.js → chunk-3ZTRWDMN.js} +307 -4
- package/dist/chunk-3ZTRWDMN.js.map +1 -0
- package/dist/{chunk-RAMNYSAW.js → chunk-4ER3YXOT.js} +22 -4
- package/dist/chunk-4ER3YXOT.js.map +1 -0
- package/dist/{chunk-CYITU5UH.js → chunk-LJX7Z2KP.js} +1445 -343
- package/dist/chunk-LJX7Z2KP.js.map +1 -0
- package/dist/editable-artifact-materializer-service.d.ts +2 -2
- package/dist/editable-artifact-outbox-service.d.ts +2 -2
- package/dist/host-export-pump.d.ts +3 -1
- package/dist/host-export.d.ts +1 -1
- package/dist/host-export.js +1 -1
- package/dist/http.d.ts +1 -1
- package/dist/index.d.ts +95 -3
- package/dist/index.js +137 -43
- package/dist/index.js.map +1 -1
- package/dist/observability-metrics.d.ts +118 -8
- package/dist/retained-process-diagnostics.d.ts +4 -0
- package/dist/retained-process-retry.d.ts +7 -0
- package/dist/sandbox-resume.d.ts +55 -13
- package/dist/sandbox-routing.d.ts +5 -3
- package/dist/sandbox-snapshot-diagnostics.d.ts +20 -0
- package/dist/session-event-fanout.d.ts +6 -0
- package/dist/warm-capture-holder.d.ts +3 -0
- package/dist/worker-service-lifecycle.d.ts +12 -0
- package/dist/workflow-bundle.js +273 -41
- package/dist/workflows/activities.d.ts +1 -1
- package/dist/workflows/browser-deadline-checkpoint.d.ts +9 -0
- package/dist/workflows/scheduled-tasks.d.ts +4 -0
- package/dist/workflows/session.d.ts +36 -0
- package/dist/workflows.d.ts +1 -0
- package/package.json +21 -19
- package/src/activities/agent-run-admission.ts +17 -1
- package/src/activities/agent-turn/admission.ts +66 -3
- package/src/activities/agent-turn/agent-build.ts +84 -10
- package/src/activities/agent-turn/claim.ts +186 -43
- package/src/activities/agent-turn/claude-usage-observer.ts +221 -0
- package/src/activities/agent-turn/code-search.ts +275 -0
- package/src/activities/agent-turn/codex-capacity.ts +172 -8
- package/src/activities/agent-turn/codex-plan-entitlement.ts +254 -0
- package/src/activities/agent-turn/codex.ts +13 -5
- package/src/activities/agent-turn/compaction-prep.ts +170 -102
- package/src/activities/agent-turn/credential-leases.ts +122 -216
- package/src/activities/agent-turn/credential-restriction.ts +14 -0
- package/src/activities/agent-turn/errors.ts +647 -48
- package/src/activities/agent-turn/failure-settlement.ts +682 -130
- package/src/activities/agent-turn/final-reply.ts +46 -0
- package/src/activities/agent-turn/finalization-monitor.ts +120 -0
- package/src/activities/agent-turn/finalization.ts +146 -43
- package/src/activities/agent-turn/github-repository-bindings.ts +3 -1
- package/src/activities/agent-turn/governance-model.ts +69 -28
- package/src/activities/agent-turn/history-sink.ts +16 -2
- package/src/activities/agent-turn/history.ts +38 -11
- package/src/activities/agent-turn/input-wait-reply.ts +103 -0
- package/src/activities/agent-turn/knowledge-source-tools.ts +2 -0
- package/src/activities/agent-turn/media-artifacts.ts +2 -1
- package/src/activities/agent-turn/model-call-admission.ts +94 -0
- package/src/activities/agent-turn/model-usage.ts +64 -3
- package/src/activities/agent-turn/model-wire-dispatch.ts +27 -0
- package/src/activities/agent-turn/optional-repositories.ts +224 -0
- package/src/activities/agent-turn/provider-dispatch-barrier.ts +22 -0
- package/src/activities/agent-turn/quiescence.ts +10 -7
- package/src/activities/agent-turn/recovery-warning.ts +16 -0
- package/src/activities/agent-turn/refresh-credentials.ts +44 -0
- package/src/activities/agent-turn/run-credentials.ts +116 -55
- package/src/activities/agent-turn/run.ts +320 -93
- package/src/activities/agent-turn/sandbox-establish.ts +84 -8
- package/src/activities/agent-turn/sandbox-provision.ts +3 -1
- package/src/activities/agent-turn/sandbox-route.ts +4 -21
- package/src/activities/agent-turn/sandbox-runtime.ts +343 -18
- package/src/activities/agent-turn/session-title.ts +81 -5
- package/src/activities/agent-turn/skill-checkout.ts +111 -24
- package/src/activities/agent-turn/skill-install.ts +1 -1
- package/src/activities/agent-turn/skill-read.ts +374 -28
- package/src/activities/agent-turn/skill-remove.ts +46 -0
- package/src/activities/agent-turn/skill-save.ts +1 -1
- package/src/activities/agent-turn/skill-search.ts +10 -1
- package/src/activities/agent-turn/skill-selection.ts +4 -0
- package/src/activities/agent-turn/skill-tools.ts +112 -18
- package/src/activities/agent-turn/skill-transfer.ts +68 -23
- package/src/activities/agent-turn/stream-attempt.ts +313 -77
- package/src/activities/agent-turn/subscription-lease.ts +115 -0
- package/src/activities/agent-turn/tool-environment.ts +316 -167
- package/src/activities/agent-turn/tool-policy.ts +80 -1
- package/src/activities/agent-turn/turn-context.ts +35 -0
- package/src/activities/agent-turn/turn-sandbox-access.ts +7 -1
- package/src/activities/agent-turn/xai-capacity.ts +94 -51
- package/src/activities/automations.ts +76 -4
- package/src/activities/browser-deadline-checkpoint.ts +279 -0
- package/src/activities/capabilities.ts +108 -11
- package/src/activities/codemode-dispatcher.ts +36 -8
- package/src/activities/codex-capacity.ts +60 -25
- package/src/activities/codex-rotation.ts +39 -17
- package/src/activities/context-compaction.ts +75 -18
- package/src/activities/gmail-files.ts +88 -0
- package/src/activities/goals.ts +116 -121
- package/src/activities/google-drive-publication.ts +1 -3
- package/src/activities/image-generation-operation.ts +1 -1
- package/src/activities/knowledge-indexing.ts +269 -15
- package/src/activities/knowledge-source-sync.ts +10 -0
- package/src/activities/mcp-account-routes.ts +17 -0
- package/src/activities/mcp-credentials.ts +83 -194
- package/src/activities/mcp-operation-authority.ts +16 -41
- package/src/activities/model-call-fact-reconciler.ts +112 -0
- package/src/activities/open-suffix-resume.ts +15 -2
- package/src/activities/parent-wake.ts +46 -21
- package/src/activities/retained-screenshots.ts +82 -11
- package/src/activities/rig-verification.ts +38 -37
- package/src/activities/run-credential-renewal.ts +13 -1
- package/src/activities/run-credentials.ts +59 -17
- package/src/activities/run-input.ts +25 -16
- package/src/activities/sandbox-images.ts +0 -1
- package/src/activities/sandbox-lease.ts +798 -82
- package/src/activities/scheduled-tasks.ts +550 -178
- package/src/activities/session-state.ts +245 -6
- package/src/activities/site-auth-maintenance.ts +3 -0
- package/src/activities/types.ts +72 -5
- package/src/activities/workflow-wake.ts +2 -17
- package/src/activities/workspace-capture.ts +13 -2
- package/src/activities/workspace-credential-provider.ts +283 -0
- package/src/activities-control.ts +4 -0
- package/src/activities-turn.ts +5 -0
- package/src/activities.ts +13 -0
- package/src/activity-services.ts +16 -4
- package/src/browser-deadline-checkpoint-contract.ts +5 -0
- package/src/editable-artifact-hint-broker.ts +3 -0
- package/src/editable-artifact-materializer-service.ts +7 -3
- package/src/editable-artifact-outbox-service.ts +4 -4
- package/src/github-rest-mcp.ts +1 -2
- package/src/host-export-pump.ts +26 -4
- package/src/host-export.ts +3 -0
- package/src/http.ts +4 -2
- package/src/index.ts +99 -37
- package/src/observability-metrics.ts +510 -5
- package/src/personal-github-git-credentials.ts +2 -1
- package/src/pr-review-credentials.ts +1 -3
- package/src/retained-process-diagnostics.ts +50 -0
- package/src/retained-process-retry.ts +36 -0
- package/src/sandbox-resume.ts +388 -101
- package/src/sandbox-routing.ts +150 -25
- package/src/sandbox-snapshot-diagnostics.ts +112 -0
- package/src/session-event-fanout.ts +23 -0
- package/src/warm-capture-holder.ts +15 -0
- package/src/worker-service-lifecycle.ts +53 -0
- package/src/workflows/activities.ts +16 -1
- package/src/workflows/browser-deadline-checkpoint.ts +50 -0
- package/src/workflows/file-upload-reaper.ts +2 -0
- package/src/workflows/scheduled-tasks.ts +26 -13
- package/src/workflows/session.ts +242 -22
- package/src/workflows.ts +4 -0
- package/dist/activities/agent-turn/knowledge-sources.d.ts +0 -20
- package/dist/activities/packs.d.ts +0 -55
- package/dist/activities-control-HMFGSKC5.js.map +0 -1
- package/dist/activities-turn-AP67JADS.js.map +0 -1
- package/dist/chunk-A4XQWVZK.js.map +0 -1
- package/dist/chunk-CYITU5UH.js.map +0 -1
- package/dist/chunk-RAMNYSAW.js.map +0 -1
- package/src/activities/agent-turn/knowledge-sources.ts +0 -86
- package/src/activities/packs.ts +0 -166
package/src/sandbox-routing.ts
CHANGED
|
@@ -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
|
-
|
|
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 {
|
|
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
|
-
/**
|
|
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: {
|
|
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: "
|
|
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(
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
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(
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
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 {
|
|
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
|
+
}
|