@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
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { randomUUID } from "node:crypto";
|
|
3
|
-
import type { VerifiedHostWorkspaceArchive } from "@opengeni/runtime/sandbox";
|
|
1
|
+
import type { ObjectStorage } from "@opengeni/storage";
|
|
4
2
|
import {
|
|
5
3
|
parseWorkspaceArchiveObjectRef,
|
|
6
4
|
workspaceArchiveObjectKey,
|
|
@@ -37,102 +35,43 @@ export async function putTarWorkspaceArchiveObject(input: {
|
|
|
37
35
|
accountId: string;
|
|
38
36
|
workspaceId: string;
|
|
39
37
|
sandboxGroupId: string;
|
|
40
|
-
archive:
|
|
41
|
-
| { bytes: Uint8Array; descriptor: WorkspaceArchiveDescriptor }
|
|
42
|
-
| VerifiedHostWorkspaceArchive;
|
|
38
|
+
archive: { bytes: Uint8Array; descriptor: WorkspaceArchiveDescriptor };
|
|
43
39
|
}): Promise<WorkspaceArchiveObjectRef> {
|
|
44
|
-
|
|
45
|
-
const descriptor = structuredClone(input.archive.descriptor);
|
|
46
|
-
if (descriptor.version !== 1) {
|
|
40
|
+
if (input.archive.descriptor.version !== 1) {
|
|
47
41
|
throw new Error("Object-storage workspace archives are portable tar only");
|
|
48
42
|
}
|
|
49
43
|
const key = workspaceArchiveObjectKey({
|
|
50
44
|
accountId: input.accountId,
|
|
51
45
|
workspaceId: input.workspaceId,
|
|
52
46
|
sandboxGroupId: input.sandboxGroupId,
|
|
53
|
-
revision: descriptor.revision,
|
|
54
|
-
|
|
47
|
+
revision: input.archive.descriptor.revision,
|
|
48
|
+
});
|
|
49
|
+
await input.objectStorage.putObject({
|
|
50
|
+
key,
|
|
51
|
+
contentType: "application/x-tar",
|
|
52
|
+
body: input.archive.bytes,
|
|
53
|
+
sha256: input.archive.descriptor.archiveSha256,
|
|
55
54
|
});
|
|
56
|
-
const archive = input.archive;
|
|
57
|
-
if ("spool" in archive) {
|
|
58
|
-
if (
|
|
59
|
-
archive.spool.byteSize !== descriptor.archiveBytes ||
|
|
60
|
-
archive.spool.sha256 !== descriptor.archiveSha256
|
|
61
|
-
) {
|
|
62
|
-
throw new Error("Workspace archive spool does not match its verified descriptor");
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
const source =
|
|
66
|
-
"spool" in archive
|
|
67
|
-
? archive.spool
|
|
68
|
-
: {
|
|
69
|
-
path: "",
|
|
70
|
-
byteSize: archive.bytes.byteLength,
|
|
71
|
-
sha256: descriptor.archiveSha256,
|
|
72
|
-
async *open() {
|
|
73
|
-
for (let offset = 0; offset < archive.bytes.length; offset += 1024 * 1024)
|
|
74
|
-
yield archive.bytes.subarray(offset, offset + 1024 * 1024);
|
|
75
|
-
},
|
|
76
|
-
async dispose() {},
|
|
77
|
-
};
|
|
78
|
-
try {
|
|
79
|
-
await uploadWorkspaceArchiveSpool(input.objectStorage, key, {
|
|
80
|
-
...source,
|
|
81
|
-
byteSize: descriptor.archiveBytes,
|
|
82
|
-
sha256: descriptor.archiveSha256,
|
|
83
|
-
open: source.open.bind(source),
|
|
84
|
-
});
|
|
85
|
-
} catch (error) {
|
|
86
|
-
// This fresh locator has never been offered to database publication. An
|
|
87
|
-
// ambiguous provider upload can leave an orphan, never overwrite a retained
|
|
88
|
-
// archive or justify deleting another attempt's object.
|
|
89
|
-
await input.objectStorage.deleteObject(key).catch(() => undefined);
|
|
90
|
-
throw error;
|
|
91
|
-
}
|
|
92
55
|
return {
|
|
93
56
|
schema: "sandbox_archive_object_v1",
|
|
94
57
|
key,
|
|
95
|
-
sha256: descriptor.archiveSha256,
|
|
96
|
-
bytes: descriptor.archiveBytes,
|
|
58
|
+
sha256: input.archive.descriptor.archiveSha256,
|
|
59
|
+
bytes: input.archive.descriptor.archiveBytes,
|
|
97
60
|
backend: input.objectStorage.backend,
|
|
98
61
|
};
|
|
99
62
|
}
|
|
100
63
|
|
|
101
|
-
/** The caller owns a fresh candidate returned by this module. A thrown or lost
|
|
102
|
-
* database response is UNKNOWN: it may have committed, so never delete on catch.
|
|
103
|
-
* Only the transaction's exact candidate disposition can authorize cleanup. */
|
|
104
|
-
export async function persistWorkspaceArchiveCandidate<
|
|
105
|
-
T extends {
|
|
106
|
-
wrote: boolean;
|
|
107
|
-
candidateDisposition?: "adopted" | "already_referenced" | "unused";
|
|
108
|
-
},
|
|
109
|
-
>(input: {
|
|
110
|
-
objectStorage?: ObjectStorage | null;
|
|
111
|
-
ref?: WorkspaceArchiveObjectRef;
|
|
112
|
-
persist: () => Promise<T>;
|
|
113
|
-
}): Promise<T> {
|
|
114
|
-
const objectStorage = input.objectStorage;
|
|
115
|
-
const ref = input.ref ? { ...input.ref } : undefined;
|
|
116
|
-
const result = await input.persist();
|
|
117
|
-
if (objectStorage && ref && result.candidateDisposition === "unused") {
|
|
118
|
-
await deleteUnpublishedWorkspaceArchiveObject(objectStorage, ref);
|
|
119
|
-
}
|
|
120
|
-
return result;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
64
|
export async function putVersion1TarArchiveOrInline(input: {
|
|
124
65
|
backend: string;
|
|
125
66
|
objectStorage?: ObjectStorage | null | undefined;
|
|
126
67
|
accountId: string;
|
|
127
68
|
workspaceId: string;
|
|
128
69
|
sandboxGroupId: string;
|
|
129
|
-
archive:
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
base64: string;
|
|
135
|
-
};
|
|
70
|
+
archive: {
|
|
71
|
+
bytes: Uint8Array;
|
|
72
|
+
descriptor: WorkspaceArchiveDescriptor;
|
|
73
|
+
base64: string;
|
|
74
|
+
};
|
|
136
75
|
metrics?: {
|
|
137
76
|
onWorkspaceArchiveObject?: (input: {
|
|
138
77
|
outcome: "put" | "put_failed" | "deleted_unpublished";
|
|
@@ -144,15 +83,12 @@ export async function putVersion1TarArchiveOrInline(input: {
|
|
|
144
83
|
workspaceArchiveRef?: WorkspaceArchiveObjectRef;
|
|
145
84
|
}> {
|
|
146
85
|
if (input.archive.descriptor.version !== 1) {
|
|
147
|
-
if ("spool" in input.archive) throw new Error("Native snapshots cannot use a portable spool");
|
|
148
86
|
return { workspaceArchive: input.archive.base64 };
|
|
149
87
|
}
|
|
150
88
|
if (input.backend === "opensandbox" && !input.objectStorage) {
|
|
151
89
|
throw new WorkspaceArchiveObjectStorageRequiredError("opensandbox");
|
|
152
90
|
}
|
|
153
91
|
if (!input.objectStorage) {
|
|
154
|
-
if ("spool" in input.archive)
|
|
155
|
-
throw new WorkspaceArchiveObjectStorageRequiredError(input.backend);
|
|
156
92
|
return { workspaceArchive: input.archive.base64 };
|
|
157
93
|
}
|
|
158
94
|
try {
|
|
@@ -161,7 +97,7 @@ export async function putVersion1TarArchiveOrInline(input: {
|
|
|
161
97
|
accountId: input.accountId,
|
|
162
98
|
workspaceId: input.workspaceId,
|
|
163
99
|
sandboxGroupId: input.sandboxGroupId,
|
|
164
|
-
archive: input.archive,
|
|
100
|
+
archive: { bytes: input.archive.bytes, descriptor: input.archive.descriptor },
|
|
165
101
|
});
|
|
166
102
|
try {
|
|
167
103
|
input.metrics?.onWorkspaceArchiveObject?.({
|
package/src/sandbox-resume.ts
CHANGED
|
@@ -55,9 +55,7 @@ import {
|
|
|
55
55
|
type LeaseHolderKind,
|
|
56
56
|
} from "@opengeni/db";
|
|
57
57
|
import {
|
|
58
|
-
|
|
59
|
-
disposeWorkspaceArchive,
|
|
60
|
-
type VerifiedWorkspaceArchivePayload,
|
|
58
|
+
captureVerifiedWorkspaceArchive,
|
|
61
59
|
describeLegacyNativeSnapshotArchive,
|
|
62
60
|
inlineWorkspaceArchiveForRestore,
|
|
63
61
|
MODAL_EXEC_READINESS_TIMEOUT_MS,
|
|
@@ -81,15 +79,13 @@ import {
|
|
|
81
79
|
type RuntimeMetricsHooks,
|
|
82
80
|
type WorkspaceArchiveDescriptor,
|
|
83
81
|
} from "@opengeni/runtime";
|
|
84
|
-
import {
|
|
85
|
-
downloadWorkspaceArchiveSpool,
|
|
86
|
-
WorkspaceArchiveStorageError,
|
|
87
|
-
type ObjectStorage,
|
|
88
|
-
} from "@opengeni/storage";
|
|
82
|
+
import type { ObjectStorage } from "@opengeni/storage";
|
|
89
83
|
import type { Observability } from "@opengeni/observability";
|
|
90
84
|
import { parseWorkspaceArchiveObjectRef } from "@opengeni/contracts";
|
|
91
85
|
import {
|
|
92
|
-
|
|
86
|
+
collectWorkspaceArchiveObjectKeys,
|
|
87
|
+
deleteUnpublishedWorkspaceArchiveObject,
|
|
88
|
+
deleteWorkspaceArchiveObjectKeys,
|
|
93
89
|
putVersion1TarArchiveOrInline,
|
|
94
90
|
} from "./sandbox-archive-storage";
|
|
95
91
|
|
|
@@ -586,14 +582,6 @@ async function materializeSpawnEnvelopeArchive(
|
|
|
586
582
|
"workspace archive object storage is not configured",
|
|
587
583
|
);
|
|
588
584
|
}
|
|
589
|
-
const descriptor = parseWorkspaceArchiveDescriptor(sessionState.workspaceArchiveMeta);
|
|
590
|
-
if (
|
|
591
|
-
process.platform === "linux" &&
|
|
592
|
-
descriptor?.version === 1 &&
|
|
593
|
-
descriptor.workspace.projection === "sdk_local_archive_v1"
|
|
594
|
-
) {
|
|
595
|
-
return envelope;
|
|
596
|
-
}
|
|
597
585
|
return {
|
|
598
586
|
...record,
|
|
599
587
|
sessionState: await inlineWorkspaceArchiveForRestore(sessionState, (key) =>
|
|
@@ -910,10 +898,9 @@ async function persistWarmWorkspaceSnapshot(
|
|
|
910
898
|
// first (the bounded-wait race Bugbot flagged).
|
|
911
899
|
const capturedAtMs = Date.now();
|
|
912
900
|
const registerCandidate = async (
|
|
913
|
-
archive:
|
|
901
|
+
archive: Awaited<ReturnType<typeof captureVerifiedWorkspaceArchive>>,
|
|
914
902
|
): Promise<{ id: string } | null> => {
|
|
915
903
|
if (
|
|
916
|
-
archive.kind === "host_spool" ||
|
|
917
904
|
archive.descriptor.version !== 2 ||
|
|
918
905
|
(archive.descriptor.provider !== "modal_snapshot_filesystem" &&
|
|
919
906
|
archive.descriptor.provider !== "modal_snapshot_directory")
|
|
@@ -951,65 +938,82 @@ async function persistWarmWorkspaceSnapshot(
|
|
|
951
938
|
// turn signal resolves first. Its finally block is the only normal release
|
|
952
939
|
// of the exact admission gate; a late callback cannot release a successor.
|
|
953
940
|
const captureAndPublish = (async (): Promise<boolean> => {
|
|
954
|
-
let archive: VerifiedWorkspaceArchivePayload | undefined;
|
|
955
941
|
let candidate: { id: string } | null = null;
|
|
956
|
-
let
|
|
942
|
+
let workspaceArchiveRef: Awaited<
|
|
943
|
+
ReturnType<typeof putVersion1TarArchiveOrInline>
|
|
944
|
+
>["workspaceArchiveRef"];
|
|
957
945
|
try {
|
|
958
|
-
archive = await
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
requestId: claimed.claim.providerRequestId,
|
|
963
|
-
strategy: capturePolicy.strategy,
|
|
964
|
-
},
|
|
965
|
-
Boolean(services.objectStorage),
|
|
966
|
-
);
|
|
946
|
+
const archive = await captureVerifiedWorkspaceArchive(session, capturedAtMs, {
|
|
947
|
+
requestId: claimed.claim.providerRequestId,
|
|
948
|
+
strategy: capturePolicy.strategy,
|
|
949
|
+
});
|
|
967
950
|
candidate = await registerCandidate(archive);
|
|
968
|
-
const
|
|
951
|
+
const priorLease = await readLease(db, ids.workspaceId, ids.sandboxGroupId);
|
|
952
|
+
const priorKeys = collectWorkspaceArchiveObjectKeys(
|
|
953
|
+
(priorLease?.resumeState as Record<string, unknown> | null | undefined) ?? null,
|
|
954
|
+
);
|
|
969
955
|
const published = await putVersion1TarArchiveOrInline({
|
|
970
956
|
backend: lease.backend,
|
|
971
957
|
objectStorage: services.objectStorage,
|
|
972
958
|
accountId: ids.accountId,
|
|
973
959
|
workspaceId: ids.workspaceId,
|
|
974
960
|
sandboxGroupId: ids.sandboxGroupId,
|
|
975
|
-
archive
|
|
961
|
+
archive: {
|
|
962
|
+
bytes: archive.bytes,
|
|
963
|
+
descriptor: archive.descriptor,
|
|
964
|
+
base64: archive.base64,
|
|
965
|
+
},
|
|
976
966
|
...(services.sandboxMetrics ? { metrics: services.sandboxMetrics } : {}),
|
|
977
967
|
});
|
|
978
|
-
|
|
979
|
-
const { wrote } = await
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
...published,
|
|
996
|
-
checkpointArtifactId: candidate?.id ?? null,
|
|
997
|
-
minIntervalMs: force ? 0 : intervalMs,
|
|
998
|
-
capturedAtMs,
|
|
999
|
-
}),
|
|
968
|
+
workspaceArchiveRef = published.workspaceArchiveRef;
|
|
969
|
+
const { wrote } = await persistWarmSnapshot(db, {
|
|
970
|
+
accountId: ids.accountId,
|
|
971
|
+
workspaceId: ids.workspaceId,
|
|
972
|
+
sessionId: ids.sessionId,
|
|
973
|
+
turnId: ids.turnId,
|
|
974
|
+
attemptId: ids.attemptId,
|
|
975
|
+
sandboxGroupId: ids.sandboxGroupId,
|
|
976
|
+
expectedEpoch: leaseEpoch,
|
|
977
|
+
expectedInstanceId: instanceId,
|
|
978
|
+
expectedWorkspaceGeneration: claimed.claim.workspaceGeneration,
|
|
979
|
+
captureId,
|
|
980
|
+
workspaceArchiveMeta: archive.descriptor,
|
|
981
|
+
...published,
|
|
982
|
+
checkpointArtifactId: candidate?.id ?? null,
|
|
983
|
+
minIntervalMs: force ? 0 : intervalMs,
|
|
984
|
+
capturedAtMs,
|
|
1000
985
|
});
|
|
986
|
+
if (published.workspaceArchiveRef && services.objectStorage) {
|
|
987
|
+
if (!wrote) {
|
|
988
|
+
await deleteUnpublishedWorkspaceArchiveObject(
|
|
989
|
+
services.objectStorage,
|
|
990
|
+
published.workspaceArchiveRef,
|
|
991
|
+
services.sandboxMetrics,
|
|
992
|
+
);
|
|
993
|
+
} else {
|
|
994
|
+
const afterLease = await readLease(db, ids.workspaceId, ids.sandboxGroupId);
|
|
995
|
+
const afterKeys = collectWorkspaceArchiveObjectKeys(
|
|
996
|
+
(afterLease?.resumeState as Record<string, unknown> | null | undefined) ?? null,
|
|
997
|
+
);
|
|
998
|
+
await deleteWorkspaceArchiveObjectKeys(
|
|
999
|
+
services.objectStorage,
|
|
1000
|
+
[...priorKeys].filter((key) => !afterKeys.has(key)),
|
|
1001
|
+
).catch(() => undefined);
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1001
1004
|
if (!wrote && candidate) {
|
|
1002
1005
|
await abandonCandidate(candidate.id, "snapshot_publication_fenced");
|
|
1003
1006
|
}
|
|
1004
1007
|
return wrote;
|
|
1005
1008
|
} catch (error) {
|
|
1006
|
-
|
|
1007
|
-
|
|
1009
|
+
await deleteUnpublishedWorkspaceArchiveObject(
|
|
1010
|
+
services.objectStorage,
|
|
1011
|
+
workspaceArchiveRef,
|
|
1012
|
+
services.sandboxMetrics,
|
|
1013
|
+
);
|
|
1014
|
+
if (candidate) await abandonCandidate(candidate.id, "snapshot_capture_failed");
|
|
1008
1015
|
throw error;
|
|
1009
1016
|
} finally {
|
|
1010
|
-
await disposeWorkspaceArchive(archive).catch(() => {
|
|
1011
|
-
console.warn("workspace archive spool cleanup failed");
|
|
1012
|
-
});
|
|
1013
1017
|
await releaseWorkspaceArchiveCapture(db, {
|
|
1014
1018
|
accountId: ids.accountId,
|
|
1015
1019
|
workspaceId: ids.workspaceId,
|
|
@@ -1500,26 +1504,6 @@ export async function resumeBoxForTurn(
|
|
|
1500
1504
|
const established = await establishSandboxSessionFromEnvelope(settings, hydrateEnvelope, {
|
|
1501
1505
|
sessionId: ids.sessionId,
|
|
1502
1506
|
recovery: "create-or-restore",
|
|
1503
|
-
...(services.objectStorage
|
|
1504
|
-
? {
|
|
1505
|
-
loadHostWorkspaceArchive: async (ref) => {
|
|
1506
|
-
try {
|
|
1507
|
-
return await downloadWorkspaceArchiveSpool(services.objectStorage!, ref.key, {
|
|
1508
|
-
bytes: ref.bytes,
|
|
1509
|
-
sha256: ref.sha256,
|
|
1510
|
-
});
|
|
1511
|
-
} catch (error) {
|
|
1512
|
-
if (error instanceof WorkspaceArchiveStorageError) {
|
|
1513
|
-
throw new WorkspaceArchiveIntegrityError(error.code, error.message, {
|
|
1514
|
-
retryable: error.retryable,
|
|
1515
|
-
cause: error,
|
|
1516
|
-
});
|
|
1517
|
-
}
|
|
1518
|
-
throw error;
|
|
1519
|
-
}
|
|
1520
|
-
},
|
|
1521
|
-
}
|
|
1522
|
-
: {}),
|
|
1523
1507
|
backendOverride: ids.backend as never,
|
|
1524
1508
|
...(ids.environment ? { environment: ids.environment } : {}),
|
|
1525
1509
|
...(services.sandboxMetrics ? { metrics: services.sandboxMetrics } : {}),
|