@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.
- package/dist/activities/agent-turn/claude-usage-observer.d.ts +30 -6
- package/dist/activities/agent-turn/credential-leases.d.ts +5 -32
- package/dist/activities/agent-turn/errors.d.ts +3 -0
- package/dist/activities/agent-turn/finalization-monitor.d.ts +1 -1
- package/dist/activities/agent-turn/finalization.d.ts +2 -0
- package/dist/activities/agent-turn/quiescence.d.ts +3 -4
- package/dist/activities/agent-turn/subscription-lease.d.ts +38 -0
- package/dist/activities/agent-turn/turn-context.d.ts +1 -1
- package/dist/activities/model-call-fact-reconciler.d.ts +34 -0
- package/dist/activities/session-state.d.ts +2 -2
- package/dist/activities/types.d.ts +7 -0
- package/dist/{activities-control-3HAH4FMG.js → activities-control-3BY4IJKL.js} +166 -35
- package/dist/activities-control-3BY4IJKL.js.map +1 -0
- package/dist/activities-control.d.ts +6 -4
- package/dist/{activities-turn-KU25J3VQ.js → activities-turn-TTNAVGKN.js} +263 -308
- package/dist/activities-turn-TTNAVGKN.js.map +1 -0
- package/dist/activities.d.ts +6 -4
- package/dist/{chunk-3K3ULWEE.js → chunk-PA7Z55P4.js} +242 -17
- package/dist/chunk-PA7Z55P4.js.map +1 -0
- package/dist/{chunk-YG27NEMX.js → chunk-YOI3Z3GF.js} +9 -1
- package/dist/chunk-YOI3Z3GF.js.map +1 -0
- package/dist/http.d.ts +1 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.js +82 -16
- package/dist/index.js.map +1 -1
- package/dist/observability-metrics.d.ts +6 -1
- package/dist/sandbox-resume.d.ts +3 -0
- package/dist/session-event-fanout.d.ts +6 -0
- package/dist/worker-service-lifecycle.d.ts +12 -0
- package/dist/workflow-bundle.js +3 -2
- package/package.json +21 -21
- package/src/activities/agent-turn/claim.ts +13 -0
- package/src/activities/agent-turn/claude-usage-observer.ts +99 -8
- package/src/activities/agent-turn/credential-leases.ts +86 -213
- package/src/activities/agent-turn/errors.ts +61 -1
- package/src/activities/agent-turn/failure-settlement.ts +59 -5
- package/src/activities/agent-turn/finalization-monitor.ts +3 -4
- package/src/activities/agent-turn/finalization.ts +8 -3
- package/src/activities/agent-turn/quiescence.ts +3 -4
- package/src/activities/agent-turn/run.ts +24 -20
- package/src/activities/agent-turn/sandbox-establish.ts +2 -0
- package/src/activities/agent-turn/sandbox-runtime.ts +5 -1
- package/src/activities/agent-turn/skill-selection.ts +2 -0
- package/src/activities/agent-turn/subscription-lease.ts +115 -0
- package/src/activities/agent-turn/turn-context.ts +1 -4
- package/src/activities/codex-rotation.ts +2 -12
- package/src/activities/model-call-fact-reconciler.ts +112 -0
- package/src/activities/sandbox-lease.ts +59 -8
- package/src/activities/session-state.ts +29 -4
- package/src/activities/types.ts +7 -0
- package/src/activities-control.ts +2 -0
- package/src/activities-turn.ts +5 -0
- package/src/activities.ts +7 -0
- package/src/http.ts +4 -2
- package/src/index.ts +39 -9
- package/src/observability-metrics.ts +27 -0
- package/src/sandbox-resume.ts +9 -0
- package/src/sandbox-routing.ts +77 -0
- package/src/session-event-fanout.ts +23 -0
- package/src/worker-service-lifecycle.ts +53 -0
- package/src/workflows/file-upload-reaper.ts +2 -0
- package/src/workflows/session.ts +6 -1
- package/dist/activities-control-3HAH4FMG.js.map +0 -1
- package/dist/activities-turn-KU25J3VQ.js.map +0 -1
- package/dist/chunk-3K3ULWEE.js.map +0 -1
- package/dist/chunk-YG27NEMX.js.map +0 -1
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).
|
|
@@ -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
|
}
|
package/src/workflows/session.ts
CHANGED
|
@@ -367,7 +367,12 @@ export function postClaimDatabaseRecoveryDetail(
|
|
|
367
367
|
if (
|
|
368
368
|
(detail.sandboxSetupOutcomeUnknown !== undefined &&
|
|
369
369
|
detail.sandboxSetupOutcomeUnknown !== true) ||
|
|
370
|
-
(detail.
|
|
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) ||
|