@opengeni/worker-bundle 0.28.1-canary.0 → 0.28.3-canary.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-turn/model-usage.d.ts +1 -1
- package/dist/activities/sandbox-lease.d.ts +4 -4
- package/dist/{activities-control-VVK7RA7F.js → activities-control-JUBMDBDZ.js} +55 -70
- package/dist/activities-control-JUBMDBDZ.js.map +1 -0
- package/dist/{activities-turn-FOIZYY56.js → activities-turn-I2ZKGZE6.js} +20 -15
- package/dist/activities-turn-I2ZKGZE6.js.map +1 -0
- package/dist/{chunk-BARTLVBK.js → chunk-A4XQWVZK.js} +25 -25
- package/dist/chunk-A4XQWVZK.js.map +1 -0
- package/dist/{chunk-AKP6BKM2.js → chunk-ZNIKZQMN.js} +146 -111
- package/dist/chunk-ZNIKZQMN.js.map +1 -0
- package/dist/index.js +2 -2
- package/dist/observability-metrics.d.ts +12 -4
- package/dist/sandbox-archive-storage.d.ts +15 -3
- 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 +10 -5
- package/src/activities/agent-turn/model-usage.ts +10 -1
- package/src/activities/agent-turn/stream-attempt.ts +3 -2
- package/src/activities/goals.ts +2 -0
- package/src/activities/sandbox-lease.ts +64 -71
- package/src/observability-metrics.ts +28 -24
- package/src/sandbox-archive-storage.ts +82 -18
- package/src/sandbox-resume.ts +79 -63
- package/dist/activities-control-VVK7RA7F.js.map +0 -1
- package/dist/activities-turn-FOIZYY56.js.map +0 -1
- package/dist/chunk-AKP6BKM2.js.map +0 -1
- package/dist/chunk-BARTLVBK.js.map +0 -1
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { uploadWorkspaceArchiveSpool, type ObjectStorage } from "@opengeni/storage";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import type { VerifiedHostWorkspaceArchive } from "@opengeni/runtime/sandbox";
|
|
2
4
|
import {
|
|
3
5
|
parseWorkspaceArchiveObjectRef,
|
|
4
6
|
workspaceArchiveObjectKey,
|
|
@@ -35,43 +37,102 @@ export async function putTarWorkspaceArchiveObject(input: {
|
|
|
35
37
|
accountId: string;
|
|
36
38
|
workspaceId: string;
|
|
37
39
|
sandboxGroupId: string;
|
|
38
|
-
archive:
|
|
40
|
+
archive:
|
|
41
|
+
| { bytes: Uint8Array; descriptor: WorkspaceArchiveDescriptor }
|
|
42
|
+
| VerifiedHostWorkspaceArchive;
|
|
39
43
|
}): Promise<WorkspaceArchiveObjectRef> {
|
|
40
|
-
|
|
44
|
+
// Snapshot caller-owned metadata before any provider callback can mutate it.
|
|
45
|
+
const descriptor = structuredClone(input.archive.descriptor);
|
|
46
|
+
if (descriptor.version !== 1) {
|
|
41
47
|
throw new Error("Object-storage workspace archives are portable tar only");
|
|
42
48
|
}
|
|
43
49
|
const key = workspaceArchiveObjectKey({
|
|
44
50
|
accountId: input.accountId,
|
|
45
51
|
workspaceId: input.workspaceId,
|
|
46
52
|
sandboxGroupId: input.sandboxGroupId,
|
|
47
|
-
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,
|
|
53
|
+
revision: descriptor.revision,
|
|
54
|
+
uploadId: randomUUID(),
|
|
54
55
|
});
|
|
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
|
+
}
|
|
55
92
|
return {
|
|
56
93
|
schema: "sandbox_archive_object_v1",
|
|
57
94
|
key,
|
|
58
|
-
sha256:
|
|
59
|
-
bytes:
|
|
95
|
+
sha256: descriptor.archiveSha256,
|
|
96
|
+
bytes: descriptor.archiveBytes,
|
|
60
97
|
backend: input.objectStorage.backend,
|
|
61
98
|
};
|
|
62
99
|
}
|
|
63
100
|
|
|
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
|
+
|
|
64
123
|
export async function putVersion1TarArchiveOrInline(input: {
|
|
65
124
|
backend: string;
|
|
66
125
|
objectStorage?: ObjectStorage | null | undefined;
|
|
67
126
|
accountId: string;
|
|
68
127
|
workspaceId: string;
|
|
69
128
|
sandboxGroupId: string;
|
|
70
|
-
archive:
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
129
|
+
archive:
|
|
130
|
+
| VerifiedHostWorkspaceArchive
|
|
131
|
+
| {
|
|
132
|
+
bytes: Uint8Array;
|
|
133
|
+
descriptor: WorkspaceArchiveDescriptor;
|
|
134
|
+
base64: string;
|
|
135
|
+
};
|
|
75
136
|
metrics?: {
|
|
76
137
|
onWorkspaceArchiveObject?: (input: {
|
|
77
138
|
outcome: "put" | "put_failed" | "deleted_unpublished";
|
|
@@ -83,12 +144,15 @@ export async function putVersion1TarArchiveOrInline(input: {
|
|
|
83
144
|
workspaceArchiveRef?: WorkspaceArchiveObjectRef;
|
|
84
145
|
}> {
|
|
85
146
|
if (input.archive.descriptor.version !== 1) {
|
|
147
|
+
if ("spool" in input.archive) throw new Error("Native snapshots cannot use a portable spool");
|
|
86
148
|
return { workspaceArchive: input.archive.base64 };
|
|
87
149
|
}
|
|
88
150
|
if (input.backend === "opensandbox" && !input.objectStorage) {
|
|
89
151
|
throw new WorkspaceArchiveObjectStorageRequiredError("opensandbox");
|
|
90
152
|
}
|
|
91
153
|
if (!input.objectStorage) {
|
|
154
|
+
if ("spool" in input.archive)
|
|
155
|
+
throw new WorkspaceArchiveObjectStorageRequiredError(input.backend);
|
|
92
156
|
return { workspaceArchive: input.archive.base64 };
|
|
93
157
|
}
|
|
94
158
|
try {
|
|
@@ -97,7 +161,7 @@ export async function putVersion1TarArchiveOrInline(input: {
|
|
|
97
161
|
accountId: input.accountId,
|
|
98
162
|
workspaceId: input.workspaceId,
|
|
99
163
|
sandboxGroupId: input.sandboxGroupId,
|
|
100
|
-
archive:
|
|
164
|
+
archive: input.archive,
|
|
101
165
|
});
|
|
102
166
|
try {
|
|
103
167
|
input.metrics?.onWorkspaceArchiveObject?.({
|
package/src/sandbox-resume.ts
CHANGED
|
@@ -55,7 +55,9 @@ import {
|
|
|
55
55
|
type LeaseHolderKind,
|
|
56
56
|
} from "@opengeni/db";
|
|
57
57
|
import {
|
|
58
|
-
|
|
58
|
+
captureWorkspaceArchiveForStorage,
|
|
59
|
+
disposeWorkspaceArchive,
|
|
60
|
+
type VerifiedWorkspaceArchivePayload,
|
|
59
61
|
describeLegacyNativeSnapshotArchive,
|
|
60
62
|
inlineWorkspaceArchiveForRestore,
|
|
61
63
|
MODAL_EXEC_READINESS_TIMEOUT_MS,
|
|
@@ -79,13 +81,15 @@ import {
|
|
|
79
81
|
type RuntimeMetricsHooks,
|
|
80
82
|
type WorkspaceArchiveDescriptor,
|
|
81
83
|
} from "@opengeni/runtime";
|
|
82
|
-
import
|
|
84
|
+
import {
|
|
85
|
+
downloadWorkspaceArchiveSpool,
|
|
86
|
+
WorkspaceArchiveStorageError,
|
|
87
|
+
type ObjectStorage,
|
|
88
|
+
} from "@opengeni/storage";
|
|
83
89
|
import type { Observability } from "@opengeni/observability";
|
|
84
90
|
import { parseWorkspaceArchiveObjectRef } from "@opengeni/contracts";
|
|
85
91
|
import {
|
|
86
|
-
|
|
87
|
-
deleteUnpublishedWorkspaceArchiveObject,
|
|
88
|
-
deleteWorkspaceArchiveObjectKeys,
|
|
92
|
+
persistWorkspaceArchiveCandidate,
|
|
89
93
|
putVersion1TarArchiveOrInline,
|
|
90
94
|
} from "./sandbox-archive-storage";
|
|
91
95
|
|
|
@@ -582,6 +586,14 @@ async function materializeSpawnEnvelopeArchive(
|
|
|
582
586
|
"workspace archive object storage is not configured",
|
|
583
587
|
);
|
|
584
588
|
}
|
|
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
|
+
}
|
|
585
597
|
return {
|
|
586
598
|
...record,
|
|
587
599
|
sessionState: await inlineWorkspaceArchiveForRestore(sessionState, (key) =>
|
|
@@ -898,9 +910,10 @@ async function persistWarmWorkspaceSnapshot(
|
|
|
898
910
|
// first (the bounded-wait race Bugbot flagged).
|
|
899
911
|
const capturedAtMs = Date.now();
|
|
900
912
|
const registerCandidate = async (
|
|
901
|
-
archive:
|
|
913
|
+
archive: VerifiedWorkspaceArchivePayload,
|
|
902
914
|
): Promise<{ id: string } | null> => {
|
|
903
915
|
if (
|
|
916
|
+
archive.kind === "host_spool" ||
|
|
904
917
|
archive.descriptor.version !== 2 ||
|
|
905
918
|
(archive.descriptor.provider !== "modal_snapshot_filesystem" &&
|
|
906
919
|
archive.descriptor.provider !== "modal_snapshot_directory")
|
|
@@ -938,82 +951,65 @@ async function persistWarmWorkspaceSnapshot(
|
|
|
938
951
|
// turn signal resolves first. Its finally block is the only normal release
|
|
939
952
|
// of the exact admission gate; a late callback cannot release a successor.
|
|
940
953
|
const captureAndPublish = (async (): Promise<boolean> => {
|
|
954
|
+
let archive: VerifiedWorkspaceArchivePayload | undefined;
|
|
941
955
|
let candidate: { id: string } | null = null;
|
|
942
|
-
let
|
|
943
|
-
ReturnType<typeof putVersion1TarArchiveOrInline>
|
|
944
|
-
>["workspaceArchiveRef"];
|
|
956
|
+
let publicationAttempted = false;
|
|
945
957
|
try {
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
(
|
|
958
|
+
archive = await captureWorkspaceArchiveForStorage(
|
|
959
|
+
session,
|
|
960
|
+
capturedAtMs,
|
|
961
|
+
{
|
|
962
|
+
requestId: claimed.claim.providerRequestId,
|
|
963
|
+
strategy: capturePolicy.strategy,
|
|
964
|
+
},
|
|
965
|
+
Boolean(services.objectStorage),
|
|
954
966
|
);
|
|
967
|
+
candidate = await registerCandidate(archive);
|
|
968
|
+
const archiveDescriptor = archive.descriptor;
|
|
955
969
|
const published = await putVersion1TarArchiveOrInline({
|
|
956
970
|
backend: lease.backend,
|
|
957
971
|
objectStorage: services.objectStorage,
|
|
958
972
|
accountId: ids.accountId,
|
|
959
973
|
workspaceId: ids.workspaceId,
|
|
960
974
|
sandboxGroupId: ids.sandboxGroupId,
|
|
961
|
-
archive
|
|
962
|
-
bytes: archive.bytes,
|
|
963
|
-
descriptor: archive.descriptor,
|
|
964
|
-
base64: archive.base64,
|
|
965
|
-
},
|
|
975
|
+
archive,
|
|
966
976
|
...(services.sandboxMetrics ? { metrics: services.sandboxMetrics } : {}),
|
|
967
977
|
});
|
|
968
|
-
|
|
969
|
-
const { wrote } = await
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
978
|
+
publicationAttempted = true;
|
|
979
|
+
const { wrote } = await persistWorkspaceArchiveCandidate({
|
|
980
|
+
...(services.objectStorage ? { objectStorage: services.objectStorage } : {}),
|
|
981
|
+
...(published.workspaceArchiveRef ? { ref: published.workspaceArchiveRef } : {}),
|
|
982
|
+
persist: () =>
|
|
983
|
+
persistWarmSnapshot(db, {
|
|
984
|
+
accountId: ids.accountId,
|
|
985
|
+
workspaceId: ids.workspaceId,
|
|
986
|
+
sessionId: ids.sessionId,
|
|
987
|
+
turnId: ids.turnId,
|
|
988
|
+
attemptId: ids.attemptId,
|
|
989
|
+
sandboxGroupId: ids.sandboxGroupId,
|
|
990
|
+
expectedEpoch: leaseEpoch,
|
|
991
|
+
expectedInstanceId: instanceId,
|
|
992
|
+
expectedWorkspaceGeneration: claimed.claim.workspaceGeneration,
|
|
993
|
+
captureId,
|
|
994
|
+
workspaceArchiveMeta: archiveDescriptor,
|
|
995
|
+
...published,
|
|
996
|
+
checkpointArtifactId: candidate?.id ?? null,
|
|
997
|
+
minIntervalMs: force ? 0 : intervalMs,
|
|
998
|
+
capturedAtMs,
|
|
999
|
+
}),
|
|
985
1000
|
});
|
|
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
|
-
}
|
|
1004
1001
|
if (!wrote && candidate) {
|
|
1005
1002
|
await abandonCandidate(candidate.id, "snapshot_publication_fenced");
|
|
1006
1003
|
}
|
|
1007
1004
|
return wrote;
|
|
1008
1005
|
} catch (error) {
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
workspaceArchiveRef,
|
|
1012
|
-
services.sandboxMetrics,
|
|
1013
|
-
);
|
|
1014
|
-
if (candidate) await abandonCandidate(candidate.id, "snapshot_capture_failed");
|
|
1006
|
+
if (candidate && !publicationAttempted)
|
|
1007
|
+
await abandonCandidate(candidate.id, "snapshot_capture_failed");
|
|
1015
1008
|
throw error;
|
|
1016
1009
|
} finally {
|
|
1010
|
+
await disposeWorkspaceArchive(archive).catch(() => {
|
|
1011
|
+
console.warn("workspace archive spool cleanup failed");
|
|
1012
|
+
});
|
|
1017
1013
|
await releaseWorkspaceArchiveCapture(db, {
|
|
1018
1014
|
accountId: ids.accountId,
|
|
1019
1015
|
workspaceId: ids.workspaceId,
|
|
@@ -1504,6 +1500,26 @@ export async function resumeBoxForTurn(
|
|
|
1504
1500
|
const established = await establishSandboxSessionFromEnvelope(settings, hydrateEnvelope, {
|
|
1505
1501
|
sessionId: ids.sessionId,
|
|
1506
1502
|
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
|
+
: {}),
|
|
1507
1523
|
backendOverride: ids.backend as never,
|
|
1508
1524
|
...(ids.environment ? { environment: ids.environment } : {}),
|
|
1509
1525
|
...(services.sandboxMetrics ? { metrics: services.sandboxMetrics } : {}),
|