@opengeni/worker-bundle 0.28.3-canary.0 → 0.28.3
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/sandbox-lease.d.ts +4 -4
- package/dist/{activities-control-JUBMDBDZ.js → activities-control-UXGPMDL3.js} +70 -55
- package/dist/activities-control-UXGPMDL3.js.map +1 -0
- package/dist/{activities-turn-I2ZKGZE6.js → activities-turn-NOYDE7PX.js} +12 -12
- package/dist/activities-turn-NOYDE7PX.js.map +1 -0
- package/dist/{chunk-A4XQWVZK.js → chunk-BARTLVBK.js} +25 -25
- package/dist/chunk-BARTLVBK.js.map +1 -0
- package/dist/{chunk-ZNIKZQMN.js → chunk-NNXLW6V7.js} +111 -144
- package/dist/chunk-NNXLW6V7.js.map +1 -0
- package/dist/index.js +2 -2
- package/dist/observability-metrics.d.ts +4 -12
- package/dist/sandbox-archive-storage.d.ts +3 -15
- package/dist/sandbox-resume.d.ts +1 -1
- package/package.json +17 -17
- package/src/activities/agent-turn/claim.ts +3 -3
- package/src/activities/agent-turn/finalization.ts +5 -10
- package/src/activities/agent-turn/stream-attempt.ts +1 -1
- package/src/activities/sandbox-lease.ts +71 -64
- package/src/observability-metrics.ts +24 -28
- package/src/sandbox-archive-storage.ts +18 -82
- package/src/sandbox-resume.ts +63 -79
- package/dist/activities-control-JUBMDBDZ.js.map +0 -1
- package/dist/activities-turn-I2ZKGZE6.js.map +0 -1
- package/dist/chunk-A4XQWVZK.js.map +0 -1
- package/dist/chunk-ZNIKZQMN.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
startContextCompactionPendingMonitor,
|
|
20
20
|
startSessionRecoveryMonitor,
|
|
21
21
|
startTurnCapacityMonitor
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-BARTLVBK.js";
|
|
23
23
|
|
|
24
24
|
// src/index.ts
|
|
25
25
|
import {
|
|
@@ -781,7 +781,7 @@ function temporalWorkflowExecutionNotFound(error) {
|
|
|
781
781
|
return isGrpcServiceError(error) && error.code === 5;
|
|
782
782
|
}
|
|
783
783
|
async function createDefaultWorkerActivities(role, dependencies = {}) {
|
|
784
|
-
return role === "control" ? (await import("./activities-control-
|
|
784
|
+
return role === "control" ? (await import("./activities-control-UXGPMDL3.js")).createControlActivities(dependencies) : (await import("./activities-turn-NOYDE7PX.js")).createTurnActivities(dependencies);
|
|
785
785
|
}
|
|
786
786
|
function resolveOpenGeniWorkflowDefinition(moduleUrl = import.meta.url) {
|
|
787
787
|
const modulePath = fileURLToPath(moduleUrl).replaceAll("\\", "/");
|
|
@@ -91,23 +91,15 @@ export declare function recordWorkerDeathRecoveryMetrics(observability: Observab
|
|
|
91
91
|
export declare class TurnLifecycleMetrics {
|
|
92
92
|
private readonly observability;
|
|
93
93
|
private readonly options;
|
|
94
|
-
private readonly
|
|
94
|
+
private readonly turns;
|
|
95
95
|
private timer;
|
|
96
96
|
constructor(observability: Observability, options?: {
|
|
97
97
|
now?: () => number;
|
|
98
98
|
refreshIntervalMs?: number;
|
|
99
99
|
});
|
|
100
|
-
start(
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
progress(input: {
|
|
104
|
-
attemptId: string;
|
|
105
|
-
}): void;
|
|
106
|
-
finish(input: {
|
|
107
|
-
attemptId: string;
|
|
108
|
-
outcome: TurnOutcome | null;
|
|
109
|
-
durationSeconds?: number;
|
|
110
|
-
}): void;
|
|
100
|
+
start(turnId: string): void;
|
|
101
|
+
progress(turnId: string): void;
|
|
102
|
+
finish(turnId: string, outcome: TurnOutcome | null, durationSeconds?: number): void;
|
|
111
103
|
refreshGauges(): void;
|
|
112
104
|
stop(): void;
|
|
113
105
|
private oldestInflightAgeSeconds;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { VerifiedHostWorkspaceArchive } from "@opengeni/runtime/sandbox";
|
|
1
|
+
import type { ObjectStorage } from "@opengeni/storage";
|
|
3
2
|
import { type WorkspaceArchiveDescriptor, type WorkspaceArchiveObjectRef } from "@opengeni/contracts";
|
|
4
3
|
export declare class WorkspaceArchiveObjectStorageRequiredError extends Error {
|
|
5
4
|
readonly code: "workspace_archive_object_storage_required";
|
|
@@ -14,26 +13,15 @@ export declare function putTarWorkspaceArchiveObject(input: {
|
|
|
14
13
|
archive: {
|
|
15
14
|
bytes: Uint8Array;
|
|
16
15
|
descriptor: WorkspaceArchiveDescriptor;
|
|
17
|
-
}
|
|
16
|
+
};
|
|
18
17
|
}): Promise<WorkspaceArchiveObjectRef>;
|
|
19
|
-
/** The caller owns a fresh candidate returned by this module. A thrown or lost
|
|
20
|
-
* database response is UNKNOWN: it may have committed, so never delete on catch.
|
|
21
|
-
* Only the transaction's exact candidate disposition can authorize cleanup. */
|
|
22
|
-
export declare function persistWorkspaceArchiveCandidate<T extends {
|
|
23
|
-
wrote: boolean;
|
|
24
|
-
candidateDisposition?: "adopted" | "already_referenced" | "unused";
|
|
25
|
-
}>(input: {
|
|
26
|
-
objectStorage?: ObjectStorage | null;
|
|
27
|
-
ref?: WorkspaceArchiveObjectRef;
|
|
28
|
-
persist: () => Promise<T>;
|
|
29
|
-
}): Promise<T>;
|
|
30
18
|
export declare function putVersion1TarArchiveOrInline(input: {
|
|
31
19
|
backend: string;
|
|
32
20
|
objectStorage?: ObjectStorage | null | undefined;
|
|
33
21
|
accountId: string;
|
|
34
22
|
workspaceId: string;
|
|
35
23
|
sandboxGroupId: string;
|
|
36
|
-
archive:
|
|
24
|
+
archive: {
|
|
37
25
|
bytes: Uint8Array;
|
|
38
26
|
descriptor: WorkspaceArchiveDescriptor;
|
|
39
27
|
base64: string;
|
package/dist/sandbox-resume.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type Settings } from "@opengeni/config";
|
|
2
2
|
import { readLease, reinstateTurnLeaseHolder, SandboxLeaseSupersededError, type Database, type LeaseHolderKind } from "@opengeni/db";
|
|
3
3
|
import { establishSandboxSessionFromEnvelope, type EstablishedSandboxSession, type RuntimeMetricsHooks } from "@opengeni/runtime";
|
|
4
|
-
import {
|
|
4
|
+
import type { ObjectStorage } from "@opengeni/storage";
|
|
5
5
|
import type { Observability } from "@opengeni/observability";
|
|
6
6
|
export type ResumeHolderKind = LeaseHolderKind;
|
|
7
7
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeni/worker-bundle",
|
|
3
|
-
"version": "0.28.3
|
|
3
|
+
"version": "0.28.3",
|
|
4
4
|
"description": "OpenGeni worker entry and reusable embedded lifecycle, shipped with a release-coherent pre-bundled Temporal workflow artifact.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -54,22 +54,22 @@
|
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"@llamaindex/liteparse": "2.14.2",
|
|
57
|
-
"@opengeni/agent-proto": "^0.5.1
|
|
58
|
-
"@opengeni/capabilities": "^0.3.3
|
|
59
|
-
"@opengeni/codemode": "^0.5.5
|
|
60
|
-
"@opengeni/codex": "^0.2.22
|
|
61
|
-
"@opengeni/config": "^1.1.1
|
|
62
|
-
"@opengeni/contracts": "^3.0.1
|
|
63
|
-
"@opengeni/core": "^2.9.3
|
|
64
|
-
"@opengeni/db": "^4.3.2
|
|
65
|
-
"@opengeni/documents": "^0.8.26
|
|
66
|
-
"@opengeni/events": "^0.4.24
|
|
67
|
-
"@opengeni/github": "^0.7.9
|
|
68
|
-
"@opengeni/network": "^0.3.1
|
|
69
|
-
"@opengeni/observability": "^0.8.25
|
|
70
|
-
"@opengeni/runtime": "^2.5.2
|
|
71
|
-
"@opengeni/storage": "^0.2.126
|
|
72
|
-
"@opengeni/xai-subscription": "^0.1.4
|
|
57
|
+
"@opengeni/agent-proto": "^0.5.1",
|
|
58
|
+
"@opengeni/capabilities": "^0.3.3",
|
|
59
|
+
"@opengeni/codemode": "^0.5.5",
|
|
60
|
+
"@opengeni/codex": "^0.2.22",
|
|
61
|
+
"@opengeni/config": "^1.1.1",
|
|
62
|
+
"@opengeni/contracts": "^3.0.1",
|
|
63
|
+
"@opengeni/core": "^2.9.3",
|
|
64
|
+
"@opengeni/db": "^4.3.2",
|
|
65
|
+
"@opengeni/documents": "^0.8.26",
|
|
66
|
+
"@opengeni/events": "^0.4.24",
|
|
67
|
+
"@opengeni/github": "^0.7.9",
|
|
68
|
+
"@opengeni/network": "^0.3.1",
|
|
69
|
+
"@opengeni/observability": "^0.8.25",
|
|
70
|
+
"@opengeni/runtime": "^2.5.2",
|
|
71
|
+
"@opengeni/storage": "^0.2.126",
|
|
72
|
+
"@opengeni/xai-subscription": "^0.1.4",
|
|
73
73
|
"@temporalio/activity": "^1.17.0",
|
|
74
74
|
"@temporalio/client": "^1.17.0",
|
|
75
75
|
"@temporalio/worker": "^1.17.0",
|
|
@@ -347,7 +347,7 @@ export async function claimTurnAttempt(deps: ClaimTurnDeps): Promise<ClaimTurnOu
|
|
|
347
347
|
});
|
|
348
348
|
return attached.action === "claimed" && attached.turn.id === turn.id;
|
|
349
349
|
};
|
|
350
|
-
turnLifecycleMetricsFor(observability).start(
|
|
350
|
+
turnLifecycleMetricsFor(observability).start(attempt.turnId);
|
|
351
351
|
// §7.5 P3 — pass the accepted billing attribution (externally funded turns
|
|
352
352
|
// bypass OpenGeni credit/token gates)
|
|
353
353
|
// AND the optional host `entitlements` port (when bound, its admitRun replaces
|
|
@@ -451,7 +451,7 @@ export async function claimTurnAttempt(deps: ClaimTurnDeps): Promise<ClaimTurnOu
|
|
|
451
451
|
}
|
|
452
452
|
recordCanonicalStartupMilestones(appended.canonicalStartupMilestones);
|
|
453
453
|
if (inputs.length > 0) {
|
|
454
|
-
turnLifecycleMetricsFor(observability).progress(
|
|
454
|
+
turnLifecycleMetricsFor(observability).progress(attempt.turnId!);
|
|
455
455
|
}
|
|
456
456
|
activityContext?.heartbeat({
|
|
457
457
|
...heartbeatDetails,
|
|
@@ -515,7 +515,7 @@ export async function claimTurnAttempt(deps: ClaimTurnDeps): Promise<ClaimTurnOu
|
|
|
515
515
|
return false;
|
|
516
516
|
}
|
|
517
517
|
recordCanonicalStartupMilestones(result.canonicalStartupMilestones);
|
|
518
|
-
turnLifecycleMetricsFor(observability).progress(
|
|
518
|
+
turnLifecycleMetricsFor(observability).progress(attempt.turnId!);
|
|
519
519
|
await publishDurableSessionEvents(bus, input.workspaceId, input.sessionId, result.events);
|
|
520
520
|
if (inputSettlement.turnStatus === "requires_action") {
|
|
521
521
|
// The settlement transaction committed the parent's child_requires_action
|
|
@@ -745,17 +745,12 @@ export async function finalizeTurnAttempt(deps: TurnFinalizationDeps): Promise<v
|
|
|
745
745
|
status: finalizationError ? "cleanup_failed" : control.activityStatus,
|
|
746
746
|
durationSeconds,
|
|
747
747
|
});
|
|
748
|
-
if (attempt.turnId) {
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
// a recovered successor can make progress while the predecessor leaves a
|
|
753
|
-
// process-local false stuck-turn alert behind.
|
|
754
|
-
turnLifecycleMetricsFor(observability).finish({
|
|
755
|
-
attemptId: input.attemptId,
|
|
756
|
-
outcome: control.turnMetricOutcome,
|
|
748
|
+
if (attempt.turnId && control.activityStatus !== "unknown") {
|
|
749
|
+
turnLifecycleMetricsFor(observability).finish(
|
|
750
|
+
attempt.turnId,
|
|
751
|
+
control.turnMetricOutcome,
|
|
757
752
|
durationSeconds,
|
|
758
|
-
|
|
753
|
+
);
|
|
759
754
|
}
|
|
760
755
|
activitySpan.end({
|
|
761
756
|
attributes: {
|
|
@@ -1062,7 +1062,7 @@ export async function runTurnStreamAttempt(
|
|
|
1062
1062
|
currentToolBatchCallIds = new Set<string>();
|
|
1063
1063
|
currentToolBatchCompletedCallIds = new Set<string>();
|
|
1064
1064
|
await historySink.reconcileConversationTruth();
|
|
1065
|
-
turnLifecycleMetricsFor(observability).progress(
|
|
1065
|
+
turnLifecycleMetricsFor(observability).progress(attempt.turnId!);
|
|
1066
1066
|
modelCheckpointMemoryCollector.schedule(observability);
|
|
1067
1067
|
try {
|
|
1068
1068
|
await ensureRunAllowed(
|
|
@@ -82,10 +82,7 @@ import {
|
|
|
82
82
|
// box is typed as provider loss. Stale capture reconciliation separately uses
|
|
83
83
|
// establishSandboxSessionFromEnvelope in strict resume-only mode so it can
|
|
84
84
|
// reuse the runtime's provider-ready proof without ever cold-restoring.
|
|
85
|
-
|
|
86
|
-
disposeWorkspaceArchive,
|
|
87
|
-
type VerifiedWorkspaceArchivePayload,
|
|
88
|
-
type VerifiedHostWorkspaceArchive,
|
|
85
|
+
captureVerifiedWorkspaceArchive,
|
|
89
86
|
cancelSelfhostedOp,
|
|
90
87
|
assertConsistentSandboxProviderIdentity,
|
|
91
88
|
createSandboxClientForBackend,
|
|
@@ -163,7 +160,9 @@ import {
|
|
|
163
160
|
} from "../opensandbox-kubernetes-inventory";
|
|
164
161
|
import type { ObjectStorage } from "@opengeni/storage";
|
|
165
162
|
import {
|
|
166
|
-
|
|
163
|
+
collectWorkspaceArchiveObjectKeys,
|
|
164
|
+
deleteUnpublishedWorkspaceArchiveObject,
|
|
165
|
+
deleteWorkspaceArchiveObjectKeys,
|
|
167
166
|
putVersion1TarArchiveOrInline,
|
|
168
167
|
} from "../sandbox-archive-storage";
|
|
169
168
|
|
|
@@ -211,7 +210,7 @@ type DrainSandboxClient = {
|
|
|
211
210
|
* aborts the terminate before client.delete()).
|
|
212
211
|
*/
|
|
213
212
|
export type PersistArchiveFn = (
|
|
214
|
-
archiveBase64: string |
|
|
213
|
+
archiveBase64: string | null,
|
|
215
214
|
archiveMetadata?: WorkspaceArchiveDescriptor,
|
|
216
215
|
providerSession?: unknown,
|
|
217
216
|
providerBinding?: Awaited<
|
|
@@ -248,7 +247,6 @@ export type TerminateBoxFn = (
|
|
|
248
247
|
providerCaptureRequestId?: string,
|
|
249
248
|
captureDisposition?: DrainCaptureDisposition,
|
|
250
249
|
capturePolicy?: ProviderWorkspaceCapturePolicy | null,
|
|
251
|
-
diskBackedArchives?: boolean,
|
|
252
250
|
) => Promise<boolean | ProviderTerminationOutcome>;
|
|
253
251
|
|
|
254
252
|
export type SweepModalOrphansFn = (
|
|
@@ -481,7 +479,6 @@ export function createSandboxLeaseActivities(
|
|
|
481
479
|
providerCaptureRequestId,
|
|
482
480
|
captureDisposition,
|
|
483
481
|
capturePolicy,
|
|
484
|
-
diskBackedArchives,
|
|
485
482
|
) =>
|
|
486
483
|
await terminateProviderBox(
|
|
487
484
|
settings,
|
|
@@ -493,7 +490,6 @@ export function createSandboxLeaseActivities(
|
|
|
493
490
|
providerCaptureRequestId,
|
|
494
491
|
captureDisposition,
|
|
495
492
|
capturePolicy,
|
|
496
|
-
diskBackedArchives,
|
|
497
493
|
));
|
|
498
494
|
const sweepModalOrphans: SweepModalOrphansFn =
|
|
499
495
|
options.sweepModalOrphans ?? sweepModalOrphansForConfiguredBackend;
|
|
@@ -2679,7 +2675,7 @@ async function terminateDrainableBox(
|
|
|
2679
2675
|
? (lease.recovery.archive.current?.revision ?? null)
|
|
2680
2676
|
: null;
|
|
2681
2677
|
const persistArchive: PersistArchiveFn = async (
|
|
2682
|
-
archiveBase64: string |
|
|
2678
|
+
archiveBase64: string | null,
|
|
2683
2679
|
archiveMetadata?: WorkspaceArchiveDescriptor,
|
|
2684
2680
|
_providerSession?: unknown,
|
|
2685
2681
|
providerBinding?: Awaited<
|
|
@@ -2695,7 +2691,6 @@ async function terminateDrainableBox(
|
|
|
2695
2691
|
const archiveMetrics = runtimeMetricsHooksForObservability(observability);
|
|
2696
2692
|
let checkpointArtifactId: string | null = null;
|
|
2697
2693
|
if (
|
|
2698
|
-
typeof archiveBase64 === "string" &&
|
|
2699
2694
|
archiveBase64 &&
|
|
2700
2695
|
archiveMetadata?.version === 2 &&
|
|
2701
2696
|
(archiveMetadata.provider === "modal_snapshot_filesystem" ||
|
|
@@ -2740,33 +2735,59 @@ async function terminateDrainableBox(
|
|
|
2740
2735
|
if (!archiveMetadata) {
|
|
2741
2736
|
throw new Error("Sandbox snapshot publication requires a verified archive descriptor");
|
|
2742
2737
|
}
|
|
2743
|
-
const
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
}),
|
|
2769
|
-
|
|
2738
|
+
const priorKeys = collectWorkspaceArchiveObjectKeys(
|
|
2739
|
+
(lease.resumeState as Record<string, unknown> | null | undefined) ?? null,
|
|
2740
|
+
);
|
|
2741
|
+
let workspaceArchiveRef:
|
|
2742
|
+
| Awaited<ReturnType<typeof putVersion1TarArchiveOrInline>>["workspaceArchiveRef"]
|
|
2743
|
+
| undefined;
|
|
2744
|
+
try {
|
|
2745
|
+
const published = await putVersion1TarArchiveOrInline({
|
|
2746
|
+
backend: lease.backend,
|
|
2747
|
+
objectStorage,
|
|
2748
|
+
accountId,
|
|
2749
|
+
workspaceId: row.workspaceId,
|
|
2750
|
+
sandboxGroupId: row.sandboxGroupId,
|
|
2751
|
+
archive: {
|
|
2752
|
+
bytes: Buffer.from(archiveBase64, "base64"),
|
|
2753
|
+
descriptor: archiveMetadata,
|
|
2754
|
+
base64: archiveBase64,
|
|
2755
|
+
},
|
|
2756
|
+
metrics: archiveMetrics,
|
|
2757
|
+
});
|
|
2758
|
+
workspaceArchiveRef = published.workspaceArchiveRef;
|
|
2759
|
+
result = await persistDrainSnapshot(db, {
|
|
2760
|
+
...baseInput,
|
|
2761
|
+
workspaceArchiveMeta: archiveMetadata,
|
|
2762
|
+
...published,
|
|
2763
|
+
...(checkpointArtifactId ? { checkpointArtifactId } : {}),
|
|
2764
|
+
});
|
|
2765
|
+
if (published.workspaceArchiveRef && objectStorage) {
|
|
2766
|
+
if (!result.wrote) {
|
|
2767
|
+
await deleteUnpublishedWorkspaceArchiveObject(
|
|
2768
|
+
objectStorage,
|
|
2769
|
+
published.workspaceArchiveRef,
|
|
2770
|
+
archiveMetrics,
|
|
2771
|
+
);
|
|
2772
|
+
} else {
|
|
2773
|
+
const afterLease = await readLease(db, row.workspaceId, row.sandboxGroupId);
|
|
2774
|
+
const afterKeys = collectWorkspaceArchiveObjectKeys(
|
|
2775
|
+
(afterLease?.resumeState as Record<string, unknown> | null | undefined) ?? null,
|
|
2776
|
+
);
|
|
2777
|
+
await deleteWorkspaceArchiveObjectKeys(
|
|
2778
|
+
objectStorage,
|
|
2779
|
+
[...priorKeys].filter((key) => !afterKeys.has(key)),
|
|
2780
|
+
).catch(() => undefined);
|
|
2781
|
+
}
|
|
2782
|
+
}
|
|
2783
|
+
} catch (error) {
|
|
2784
|
+
await deleteUnpublishedWorkspaceArchiveObject(
|
|
2785
|
+
objectStorage,
|
|
2786
|
+
workspaceArchiveRef,
|
|
2787
|
+
archiveMetrics,
|
|
2788
|
+
);
|
|
2789
|
+
throw error;
|
|
2790
|
+
}
|
|
2770
2791
|
}
|
|
2771
2792
|
if (!result.wrote && checkpointArtifactId) {
|
|
2772
2793
|
await markSandboxCheckpointArtifactDeletePending(db, {
|
|
@@ -2802,7 +2823,6 @@ async function terminateDrainableBox(
|
|
|
2802
2823
|
captureClaim?.providerRequestId ?? attempt.operationId,
|
|
2803
2824
|
captureDisposition,
|
|
2804
2825
|
capturePolicy,
|
|
2805
|
-
Boolean(objectStorage),
|
|
2806
2826
|
);
|
|
2807
2827
|
const terminated = typeof termination === "boolean" ? termination : termination.terminated;
|
|
2808
2828
|
if (!terminated) {
|
|
@@ -2935,7 +2955,6 @@ export async function terminateProviderBox(
|
|
|
2935
2955
|
providerCaptureRequestId?: string,
|
|
2936
2956
|
captureDisposition: DrainCaptureDisposition = "capture_required",
|
|
2937
2957
|
claimedCapturePolicy?: ProviderWorkspaceCapturePolicy | null,
|
|
2938
|
-
diskBackedArchives = false,
|
|
2939
2958
|
): Promise<ProviderTerminationOutcome> {
|
|
2940
2959
|
const durableBackendId = (lease.resumeBackendId ?? lease.backend) as string;
|
|
2941
2960
|
const backend = sandboxBackendForSdkBackendId(durableBackendId) ?? durableBackendId;
|
|
@@ -3128,7 +3147,7 @@ export async function terminateProviderBox(
|
|
|
3128
3147
|
// snapshot must re-throw BEFORE any terminate so files are never lost — the next
|
|
3129
3148
|
// sweep retries, the provider idle-timeout is the backstop. A NotFound here (the
|
|
3130
3149
|
// box raced gone between resume and persist) is success: nothing to persist.
|
|
3131
|
-
let verifiedArchive:
|
|
3150
|
+
let verifiedArchive: Awaited<ReturnType<typeof captureVerifiedWorkspaceArchive>> | undefined;
|
|
3132
3151
|
const sessionWorkspacePersistence = (
|
|
3133
3152
|
session as { state?: { workspacePersistence?: unknown } } | undefined
|
|
3134
3153
|
)?.state?.workspacePersistence;
|
|
@@ -3143,15 +3162,10 @@ export async function terminateProviderBox(
|
|
|
3143
3162
|
: null;
|
|
3144
3163
|
try {
|
|
3145
3164
|
if (captureDisposition !== "archive_published" && session?.persistWorkspace) {
|
|
3146
|
-
const capture =
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
requestId: providerCaptureRequestId ?? randomUUID(),
|
|
3151
|
-
strategy: liveCapturePolicy.strategy,
|
|
3152
|
-
},
|
|
3153
|
-
diskBackedArchives,
|
|
3154
|
-
);
|
|
3165
|
+
const capture = captureVerifiedWorkspaceArchive(session, Date.now(), {
|
|
3166
|
+
requestId: providerCaptureRequestId ?? randomUUID(),
|
|
3167
|
+
strategy: liveCapturePolicy.strategy,
|
|
3168
|
+
});
|
|
3155
3169
|
verifiedArchive = await awaitProviderCaptureWithLatePublication({
|
|
3156
3170
|
capture,
|
|
3157
3171
|
timeoutMs: settings.sandboxSnapshotTimeoutMs,
|
|
@@ -3165,7 +3179,7 @@ export async function terminateProviderBox(
|
|
|
3165
3179
|
publishLate: async (archive) => {
|
|
3166
3180
|
try {
|
|
3167
3181
|
const result = await persistArchive(
|
|
3168
|
-
archive.
|
|
3182
|
+
archive.base64,
|
|
3169
3183
|
archive.descriptor,
|
|
3170
3184
|
session,
|
|
3171
3185
|
checkpointBinding,
|
|
@@ -3183,8 +3197,6 @@ export async function terminateProviderBox(
|
|
|
3183
3197
|
...logIdentity,
|
|
3184
3198
|
error: error instanceof Error ? error.message : String(error),
|
|
3185
3199
|
});
|
|
3186
|
-
} finally {
|
|
3187
|
-
await disposeWorkspaceArchive(archive);
|
|
3188
3200
|
}
|
|
3189
3201
|
},
|
|
3190
3202
|
observeLateFailure: (error) => {
|
|
@@ -3225,17 +3237,12 @@ export async function terminateProviderBox(
|
|
|
3225
3237
|
...logIdentity,
|
|
3226
3238
|
});
|
|
3227
3239
|
} else if (verifiedArchive) {
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
checkpointBinding,
|
|
3235
|
-
));
|
|
3236
|
-
} finally {
|
|
3237
|
-
await disposeWorkspaceArchive(verifiedArchive);
|
|
3238
|
-
}
|
|
3240
|
+
const { wrote } = await persistArchive(
|
|
3241
|
+
verifiedArchive.base64,
|
|
3242
|
+
verifiedArchive.descriptor,
|
|
3243
|
+
session,
|
|
3244
|
+
checkpointBinding,
|
|
3245
|
+
);
|
|
3239
3246
|
if (!wrote) {
|
|
3240
3247
|
observability.info(
|
|
3241
3248
|
"sandbox reaper: lease re-armed during persist — leaving box RUNNING (no terminate)",
|
|
@@ -413,7 +413,7 @@ export function recordWorkerDeathRecoveryMetrics(
|
|
|
413
413
|
}
|
|
414
414
|
|
|
415
415
|
export class TurnLifecycleMetrics {
|
|
416
|
-
private readonly
|
|
416
|
+
private readonly turns = new Map<string, { startedAt: number; lastProgressAt: number }>();
|
|
417
417
|
private timer: ReturnType<typeof setInterval> | null = null;
|
|
418
418
|
|
|
419
419
|
constructor(
|
|
@@ -424,46 +424,42 @@ export class TurnLifecycleMetrics {
|
|
|
424
424
|
} = {},
|
|
425
425
|
) {}
|
|
426
426
|
|
|
427
|
-
start(
|
|
427
|
+
start(turnId: string): void {
|
|
428
428
|
const now = this.now();
|
|
429
|
-
this.
|
|
429
|
+
this.turns.set(turnId, { startedAt: now, lastProgressAt: now });
|
|
430
430
|
this.ensureTimer();
|
|
431
431
|
this.refreshGauges();
|
|
432
432
|
}
|
|
433
433
|
|
|
434
|
-
progress(
|
|
435
|
-
const
|
|
436
|
-
if (!
|
|
437
|
-
|
|
434
|
+
progress(turnId: string): void {
|
|
435
|
+
const turn = this.turns.get(turnId);
|
|
436
|
+
if (!turn) return;
|
|
437
|
+
turn.lastProgressAt = this.now();
|
|
438
438
|
}
|
|
439
439
|
|
|
440
|
-
finish(
|
|
441
|
-
|
|
442
|
-
outcome: TurnOutcome | null;
|
|
443
|
-
durationSeconds?: number;
|
|
444
|
-
}): void {
|
|
445
|
-
const startedAt = this.attempts.get(input.attemptId)?.startedAt;
|
|
440
|
+
finish(turnId: string, outcome: TurnOutcome | null, durationSeconds?: number): void {
|
|
441
|
+
const startedAt = this.turns.get(turnId)?.startedAt;
|
|
446
442
|
if (startedAt !== undefined) {
|
|
447
|
-
this.
|
|
443
|
+
this.turns.delete(turnId);
|
|
448
444
|
}
|
|
449
|
-
if (
|
|
445
|
+
if (outcome) {
|
|
450
446
|
const observedDuration =
|
|
451
|
-
|
|
447
|
+
durationSeconds ??
|
|
452
448
|
(startedAt === undefined ? 0 : Math.max(0, (this.now() - startedAt) / 1000));
|
|
453
449
|
this.observability.incrementCounter({
|
|
454
450
|
name: "opengeni_turns_total",
|
|
455
451
|
help: "Total agent turns by terminal outcome.",
|
|
456
|
-
labels: { outcome
|
|
452
|
+
labels: { outcome },
|
|
457
453
|
});
|
|
458
454
|
this.observability.observeHistogram({
|
|
459
455
|
name: "opengeni_turn_duration_seconds",
|
|
460
456
|
help: "Agent turn duration in seconds by terminal outcome.",
|
|
461
|
-
labels: { outcome
|
|
457
|
+
labels: { outcome },
|
|
462
458
|
value: observedDuration,
|
|
463
459
|
});
|
|
464
460
|
}
|
|
465
461
|
this.refreshGauges();
|
|
466
|
-
if (this.
|
|
462
|
+
if (this.turns.size === 0) {
|
|
467
463
|
this.stopTimer();
|
|
468
464
|
}
|
|
469
465
|
}
|
|
@@ -471,42 +467,42 @@ export class TurnLifecycleMetrics {
|
|
|
471
467
|
refreshGauges(): void {
|
|
472
468
|
this.observability.setGauge({
|
|
473
469
|
name: "opengeni_turns_inflight",
|
|
474
|
-
help: "Current number of in-flight
|
|
475
|
-
value: this.
|
|
470
|
+
help: "Current number of in-flight agent turns in this worker process.",
|
|
471
|
+
value: this.turns.size,
|
|
476
472
|
});
|
|
477
473
|
this.observability.setGauge({
|
|
478
474
|
name: "opengeni_turn_oldest_inflight_age_seconds",
|
|
479
|
-
help: "Age in seconds of the oldest in-flight
|
|
475
|
+
help: "Age in seconds of the oldest in-flight agent turn in this worker process.",
|
|
480
476
|
value: this.oldestInflightAgeSeconds(),
|
|
481
477
|
});
|
|
482
478
|
this.observability.setGauge({
|
|
483
479
|
name: "opengeni_turn_oldest_no_progress_age_seconds",
|
|
484
|
-
help: "Seconds since durable progress for the least recently progressing in-flight
|
|
480
|
+
help: "Seconds since durable progress for the least recently progressing in-flight turn.",
|
|
485
481
|
value: this.oldestNoProgressAgeSeconds(),
|
|
486
482
|
});
|
|
487
483
|
}
|
|
488
484
|
|
|
489
485
|
stop(): void {
|
|
490
|
-
this.
|
|
486
|
+
this.turns.clear();
|
|
491
487
|
this.refreshGauges();
|
|
492
488
|
this.stopTimer();
|
|
493
489
|
}
|
|
494
490
|
|
|
495
491
|
private oldestInflightAgeSeconds(): number {
|
|
496
|
-
if (this.
|
|
492
|
+
if (this.turns.size === 0) {
|
|
497
493
|
return 0;
|
|
498
494
|
}
|
|
499
495
|
let oldest = Number.POSITIVE_INFINITY;
|
|
500
|
-
for (const { startedAt } of this.
|
|
496
|
+
for (const { startedAt } of this.turns.values()) {
|
|
501
497
|
oldest = Math.min(oldest, startedAt);
|
|
502
498
|
}
|
|
503
499
|
return Math.max(0, (this.now() - oldest) / 1000);
|
|
504
500
|
}
|
|
505
501
|
|
|
506
502
|
private oldestNoProgressAgeSeconds(): number {
|
|
507
|
-
if (this.
|
|
503
|
+
if (this.turns.size === 0) return 0;
|
|
508
504
|
let leastRecentProgress = Number.POSITIVE_INFINITY;
|
|
509
|
-
for (const { lastProgressAt } of this.
|
|
505
|
+
for (const { lastProgressAt } of this.turns.values()) {
|
|
510
506
|
leastRecentProgress = Math.min(leastRecentProgress, lastProgressAt);
|
|
511
507
|
}
|
|
512
508
|
return Math.max(0, (this.now() - leastRecentProgress) / 1000);
|