@opengeni/worker-bundle 2.0.1 → 2.0.3-canary.36079201993001
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/failure-settlement.d.ts +1 -1
- package/dist/activities/agent-turn/history.d.ts +2 -0
- package/dist/activities/agent-turn/media-artifacts.d.ts +5 -3
- package/dist/activities/context-compaction.d.ts +10 -2
- package/dist/activities/retained-screenshots.d.ts +7 -2
- package/dist/activities/sandbox-lease.d.ts +3 -4
- package/dist/{activities-control-QA6OLRQY.js → activities-control-IVTL723D.js} +204 -23
- package/dist/activities-control-IVTL723D.js.map +1 -0
- package/dist/{activities-turn-YVVKCGAK.js → activities-turn-AJHDYNZU.js} +180 -58
- package/dist/activities-turn-AJHDYNZU.js.map +1 -0
- package/dist/{chunk-M3NVMF2V.js → chunk-JMO5ZUW3.js} +14 -7
- package/dist/{chunk-M3NVMF2V.js.map → chunk-JMO5ZUW3.js.map} +1 -1
- package/dist/index.js +7 -2
- package/dist/index.js.map +1 -1
- package/dist/workflow-bundle.js +4 -1
- package/package.json +19 -19
- package/src/activities/agent-turn/compaction-prep.ts +52 -16
- package/src/activities/agent-turn/errors.ts +8 -3
- package/src/activities/agent-turn/failure-settlement.ts +40 -8
- package/src/activities/agent-turn/history.ts +14 -1
- package/src/activities/agent-turn/media-artifacts.ts +2 -1
- package/src/activities/agent-turn/sandbox-runtime.ts +25 -9
- package/src/activities/agent-turn/stream-attempt.ts +13 -2
- package/src/activities/context-compaction.ts +49 -14
- package/src/activities/knowledge-indexing.ts +133 -6
- package/src/activities/retained-screenshots.ts +82 -11
- package/src/activities/sandbox-lease.ts +141 -21
- package/src/sandbox-resume.ts +6 -0
- package/src/workflows/activities.ts +14 -1
- package/dist/activities-control-QA6OLRQY.js.map +0 -1
- package/dist/activities-turn-YVVKCGAK.js.map +0 -1
|
@@ -39,10 +39,10 @@ import {
|
|
|
39
39
|
countSandboxLeasesByLiveness,
|
|
40
40
|
deferRetainedProcessReconciliation,
|
|
41
41
|
forceDrainOverLimitViewerOnlyBoxes,
|
|
42
|
-
getBillingBalance,
|
|
43
42
|
listCreditBalancesByAccount,
|
|
44
43
|
listLegacyModalCheckpointSlots,
|
|
45
44
|
listLiveModalSandboxLeaseAttributions,
|
|
45
|
+
markWarmBillingStopCutoff,
|
|
46
46
|
listMeterableWarmLeases,
|
|
47
47
|
listSandboxViewerForceDrainWorkspaceIds,
|
|
48
48
|
markSandboxCheckpointArtifactDeletePending,
|
|
@@ -186,8 +186,7 @@ export type ReapSandboxLeasesResult = {
|
|
|
186
186
|
skipped: number;
|
|
187
187
|
/** Warm viewer-only leases that accrued warm-seconds this tick (P2.1). */
|
|
188
188
|
metered: number;
|
|
189
|
-
/**
|
|
190
|
-
* (0 balance / over the warm cap) — turn-held boxes are never drained (P2.1). */
|
|
189
|
+
/** Holderless boxes accelerated to draining by the optional warm cap. */
|
|
191
190
|
forceDrained: number;
|
|
192
191
|
/** Provider-side Modal orphan sandboxes terminated by the defensive sweep. */
|
|
193
192
|
modalOrphansTerminated: number;
|
|
@@ -256,6 +255,7 @@ export type TerminateBoxFn = (
|
|
|
256
255
|
diskBackedArchives?: boolean,
|
|
257
256
|
releaseFailedCapture?: () => Promise<void>,
|
|
258
257
|
workspaceId?: string,
|
|
258
|
+
beforeProviderStop?: () => Promise<void>,
|
|
259
259
|
) => Promise<boolean | ProviderTerminationOutcome>;
|
|
260
260
|
|
|
261
261
|
export type SweepModalOrphansFn = (
|
|
@@ -482,7 +482,14 @@ export function createSandboxLeaseActivities(
|
|
|
482
482
|
options: SandboxLeaseActivityOptions = {},
|
|
483
483
|
) {
|
|
484
484
|
const terminateBox: TerminateBoxFn =
|
|
485
|
-
options.terminateBox
|
|
485
|
+
(options.terminateBox
|
|
486
|
+
? async (...args: Parameters<TerminateBoxFn>) => {
|
|
487
|
+
// The injected test seam represents the stop itself; the real
|
|
488
|
+
// implementation invokes this only after capture has succeeded.
|
|
489
|
+
await args[10]?.();
|
|
490
|
+
return options.terminateBox!(...args);
|
|
491
|
+
}
|
|
492
|
+
: null) ??
|
|
486
493
|
(async (
|
|
487
494
|
settings,
|
|
488
495
|
lease,
|
|
@@ -494,6 +501,7 @@ export function createSandboxLeaseActivities(
|
|
|
494
501
|
diskBackedArchives,
|
|
495
502
|
releaseFailedCapture,
|
|
496
503
|
workspaceId,
|
|
504
|
+
beforeProviderStop,
|
|
497
505
|
) =>
|
|
498
506
|
await terminateProviderBox(
|
|
499
507
|
settings,
|
|
@@ -508,6 +516,7 @@ export function createSandboxLeaseActivities(
|
|
|
508
516
|
diskBackedArchives,
|
|
509
517
|
releaseFailedCapture,
|
|
510
518
|
workspaceId,
|
|
519
|
+
beforeProviderStop,
|
|
511
520
|
));
|
|
512
521
|
const sweepModalOrphans: SweepModalOrphansFn =
|
|
513
522
|
options.sweepModalOrphans ?? sweepModalOrphansForConfiguredBackend;
|
|
@@ -735,8 +744,7 @@ export function createSandboxLeaseActivities(
|
|
|
735
744
|
metered = meterResult.accrued;
|
|
736
745
|
const forceDrainWorkspaceIds = new Set<string>();
|
|
737
746
|
if (
|
|
738
|
-
settings.
|
|
739
|
-
settings.usageLimitsMode === "managed" ||
|
|
747
|
+
settings.sandboxWarmBillingMode === "credits" ||
|
|
740
748
|
settings.sandboxMaxWarmSecondsPerWorkspace > 0
|
|
741
749
|
) {
|
|
742
750
|
for (const workspaceId of meterResult.workspaceIds) {
|
|
@@ -1507,6 +1515,25 @@ async function reconcileTerminalRetainedProcesses(
|
|
|
1507
1515
|
}
|
|
1508
1516
|
}
|
|
1509
1517
|
|
|
1518
|
+
if (
|
|
1519
|
+
observation?.status === "deferred" &&
|
|
1520
|
+
process.providerBackend === "modal" &&
|
|
1521
|
+
!process.providerBindingKey &&
|
|
1522
|
+
!process.deadlineCancellationRequestedAt
|
|
1523
|
+
) {
|
|
1524
|
+
// An unbound historical command cannot be signalled safely. Still
|
|
1525
|
+
// record deadline intent when its lease matches: a failed observer
|
|
1526
|
+
// must not block capture until the provider expires.
|
|
1527
|
+
try {
|
|
1528
|
+
await requestRetainedProcessDeadlineCancellation(db, processScope);
|
|
1529
|
+
} catch (error) {
|
|
1530
|
+
observability.warn("sandbox reaper: legacy deadline cancellation intent failed", {
|
|
1531
|
+
processId: process.id,
|
|
1532
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1533
|
+
});
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1510
1537
|
if (observation === null) {
|
|
1511
1538
|
const lease = await readLease(db, process.workspaceId, process.sandboxGroupId).catch(
|
|
1512
1539
|
() => null,
|
|
@@ -1583,11 +1610,19 @@ async function reconcileTerminalRetainedProcesses(
|
|
|
1583
1610
|
);
|
|
1584
1611
|
supervisionMetric("cancellation_intent");
|
|
1585
1612
|
}
|
|
1586
|
-
|
|
1613
|
+
const legacyDeadlineStop =
|
|
1614
|
+
lease!.rotationReason === "provider_deadline" &&
|
|
1615
|
+
!supervised &&
|
|
1616
|
+
!process.deadlineCancellationRequestedAt;
|
|
1617
|
+
const probeOutcome = await probe(
|
|
1587
1618
|
settings,
|
|
1588
1619
|
lease!,
|
|
1589
1620
|
process,
|
|
1590
|
-
claim.ownerState === "background_stopping"
|
|
1621
|
+
(claim.ownerState === "background_stopping" &&
|
|
1622
|
+
!process.deadlineCancellationRequestedAt) ||
|
|
1623
|
+
legacyDeadlineStop
|
|
1624
|
+
? "cancel"
|
|
1625
|
+
: "observe",
|
|
1591
1626
|
async (result, chunkId, stream, streamFidelity) => {
|
|
1592
1627
|
if (
|
|
1593
1628
|
typeof result !== "string" ||
|
|
@@ -1610,7 +1645,20 @@ async function reconcileTerminalRetainedProcesses(
|
|
|
1610
1645
|
.catch(() => undefined);
|
|
1611
1646
|
},
|
|
1612
1647
|
commandPersistence,
|
|
1648
|
+
).then(
|
|
1649
|
+
(value) => ({ ok: true as const, value }),
|
|
1650
|
+
(error: unknown) => ({ ok: false as const, error }),
|
|
1613
1651
|
);
|
|
1652
|
+
// Legacy PTY input reserves Ctrl-C before the cancellation fence.
|
|
1653
|
+
// Non-PTY has no command signal in the Modal SDK: probe it without
|
|
1654
|
+
// injecting Ctrl-C as data, then fence and save after the grace.
|
|
1655
|
+
if (
|
|
1656
|
+
legacyDeadlineStop &&
|
|
1657
|
+
!(await requestRetainedProcessDeadlineCancellation(db, processScope))
|
|
1658
|
+
)
|
|
1659
|
+
throw new Error("Deadline cancellation no longer owns its rotating lease");
|
|
1660
|
+
if (!probeOutcome.ok) throw probeOutcome.error;
|
|
1661
|
+
observation = probeOutcome.value;
|
|
1614
1662
|
if (supervised) {
|
|
1615
1663
|
supervisionMetric(
|
|
1616
1664
|
(await commandPersistence.loadSupervisionReceipt())
|
|
@@ -1899,7 +1947,13 @@ export async function probeRetainedProcessAtProvider(
|
|
|
1899
1947
|
if (pending) {
|
|
1900
1948
|
if (!captureOutput) throw new Error("Pending command output requires its persistence callback");
|
|
1901
1949
|
await captureRetainedProbeOutput(process.id, pending.result, captureOutput);
|
|
1902
|
-
|
|
1950
|
+
const pendingObservation = classifyRetainedProcessPollResult(
|
|
1951
|
+
pending.result,
|
|
1952
|
+
process.providerSessionId,
|
|
1953
|
+
);
|
|
1954
|
+
// Capture of a previous page must not consume the only deadline stop
|
|
1955
|
+
// attempt. Terminal proof needs no further provider call.
|
|
1956
|
+
if (mode !== "cancel" || pendingObservation.status === "proved") return pendingObservation;
|
|
1903
1957
|
}
|
|
1904
1958
|
const envelopeBackend = (lease.resumeState as { backendId?: unknown }).backendId;
|
|
1905
1959
|
if (
|
|
@@ -1996,6 +2050,7 @@ export async function probeRetainedProcessAtProvider(
|
|
|
1996
2050
|
if (typeof session.writeStdin !== "function") {
|
|
1997
2051
|
return { status: "deferred", reason: "backend_unsupported" };
|
|
1998
2052
|
}
|
|
2053
|
+
let legacyPty = false;
|
|
1999
2054
|
if (process.providerBackend === "modal") {
|
|
2000
2055
|
let liveBinding: Awaited<ReturnType<typeof resolveModalCheckpointProviderBindingForSession>>;
|
|
2001
2056
|
try {
|
|
@@ -2027,6 +2082,7 @@ export async function probeRetainedProcessAtProvider(
|
|
|
2027
2082
|
if (!command || !providerPersistence || !session.bindProviderCommand)
|
|
2028
2083
|
return { status: "deferred", reason: "process_observation_unavailable" };
|
|
2029
2084
|
session.bindProviderCommand(process.providerSessionId, command, providerPersistence);
|
|
2085
|
+
legacyPty = command.pty === true;
|
|
2030
2086
|
}
|
|
2031
2087
|
const capturePage = async (value: unknown): Promise<void> => {
|
|
2032
2088
|
if (!captureOutput) {
|
|
@@ -2062,7 +2118,14 @@ export async function probeRetainedProcessAtProvider(
|
|
|
2062
2118
|
result = await withRetainedProcessProbeTimeout(
|
|
2063
2119
|
session.writeStdin({
|
|
2064
2120
|
sessionId: process.providerSessionId,
|
|
2065
|
-
|
|
2121
|
+
// Ctrl-C is a signal only through a PTY. For a non-PTY command it is
|
|
2122
|
+
// ordinary stdin data and can corrupt the command's input.
|
|
2123
|
+
chars:
|
|
2124
|
+
mode === "cancel" &&
|
|
2125
|
+
!supervisedCancelled &&
|
|
2126
|
+
(process.providerBackend !== "modal" || legacyPty)
|
|
2127
|
+
? "\u0003"
|
|
2128
|
+
: "",
|
|
2066
2129
|
yieldTimeMs: 1_000,
|
|
2067
2130
|
maxOutputTokens: 2_000,
|
|
2068
2131
|
}),
|
|
@@ -2092,6 +2155,9 @@ export async function probeRetainedProcessAtProvider(
|
|
|
2092
2155
|
observation.status === "deferred" &&
|
|
2093
2156
|
observation.reason === "provider_running" &&
|
|
2094
2157
|
lease.rotationRequestedAt !== null &&
|
|
2158
|
+
mode !== "cancel" &&
|
|
2159
|
+
!process.cancellationRequestedAt &&
|
|
2160
|
+
legacyPty &&
|
|
2095
2161
|
!(await providerPersistence
|
|
2096
2162
|
?.load()
|
|
2097
2163
|
.then((command) => command?.kind === "modal-router-v1" && command.supervision))
|
|
@@ -2197,19 +2263,25 @@ async function accrueWarmTick(
|
|
|
2197
2263
|
}
|
|
2198
2264
|
await forEachWithConcurrency(leases, SANDBOX_MAINTENANCE_ITEM_CONCURRENCY, async (lease) => {
|
|
2199
2265
|
try {
|
|
2200
|
-
const rate =
|
|
2266
|
+
const rate =
|
|
2267
|
+
settings.sandboxWarmBillingMode === "usage_only"
|
|
2268
|
+
? 0
|
|
2269
|
+
: sandboxWarmRateMicrosPerSecond(settings, lease.backend);
|
|
2201
2270
|
const result = await accrueWarmSeconds(db, {
|
|
2202
2271
|
accountId: lease.accountId,
|
|
2203
2272
|
workspaceId: lease.workspaceId,
|
|
2204
2273
|
sandboxGroupId: lease.sandboxGroupId,
|
|
2205
2274
|
expectedEpoch: lease.leaseEpoch,
|
|
2206
2275
|
warmRateMicrosPerSecond: rate,
|
|
2276
|
+
billingMode: settings.sandboxWarmBillingMode,
|
|
2207
2277
|
subjectId: lease.sandboxGroupId,
|
|
2208
2278
|
});
|
|
2209
2279
|
if (result.accrued) {
|
|
2210
2280
|
accrued += 1;
|
|
2211
2281
|
}
|
|
2212
|
-
|
|
2282
|
+
if (result.costMicros > 0 && settings.sandboxWarmBillingMode === "credits") {
|
|
2283
|
+
recordCreditMicros(observability, "usage", result.costMicros);
|
|
2284
|
+
}
|
|
2213
2285
|
} catch (error) {
|
|
2214
2286
|
observability.warn("sandbox reaper: warm-seconds accrual failed for lease", {
|
|
2215
2287
|
workspaceId: lease.workspaceId,
|
|
@@ -2325,8 +2397,8 @@ async function refreshSandboxInventoryGauge(
|
|
|
2325
2397
|
|
|
2326
2398
|
/**
|
|
2327
2399
|
* The per-workspace warm-cap + force-drain pass (P2.1). For each workspace with a
|
|
2328
|
-
* live warm box, under the usage lock: if it is
|
|
2329
|
-
*
|
|
2400
|
+
* live warm box, under the usage lock: if it is over its optional warm cap,
|
|
2401
|
+
* accelerate only holderless boxes to draining
|
|
2330
2402
|
* (guarded turn_holders=0 — a paying turn is never killed). Returns the count of
|
|
2331
2403
|
* viewer-only boxes force-drained. Per-workspace best-effort.
|
|
2332
2404
|
*/
|
|
@@ -2336,8 +2408,7 @@ async function forceDrainOverLimitWorkspaces(
|
|
|
2336
2408
|
workspaceIds: Set<string>,
|
|
2337
2409
|
observability: ActivityServices["observability"],
|
|
2338
2410
|
): Promise<number> {
|
|
2339
|
-
const enforceBalance =
|
|
2340
|
-
settings.billingMode === "stripe" || settings.usageLimitsMode === "managed";
|
|
2411
|
+
const enforceBalance = false;
|
|
2341
2412
|
const cap = settings.sandboxMaxWarmSecondsPerWorkspace;
|
|
2342
2413
|
let forceDrained = 0;
|
|
2343
2414
|
await forEachWithConcurrency(
|
|
@@ -2345,13 +2416,8 @@ async function forceDrainOverLimitWorkspaces(
|
|
|
2345
2416
|
SANDBOX_MAINTENANCE_ITEM_CONCURRENCY,
|
|
2346
2417
|
async (workspaceId) => {
|
|
2347
2418
|
try {
|
|
2348
|
-
const { accountId } = await rlsContextForWorkspace(db, workspaceId);
|
|
2349
|
-
const balance = enforceBalance
|
|
2350
|
-
? await getBillingBalance(db, accountId)
|
|
2351
|
-
: ({ balanceMicros: 1 } as { balanceMicros: number });
|
|
2352
2419
|
const result = await forceDrainOverLimitViewerOnlyBoxes(db, {
|
|
2353
2420
|
workspaceId,
|
|
2354
|
-
balanceMicros: balance.balanceMicros,
|
|
2355
2421
|
enforceBalance,
|
|
2356
2422
|
maxWarmSecondsPerWorkspace: cap,
|
|
2357
2423
|
idleGraceMs: settings.sandboxIdleGraceMs,
|
|
@@ -2939,6 +3005,20 @@ async function terminateDrainableBox(
|
|
|
2939
3005
|
) {
|
|
2940
3006
|
return false;
|
|
2941
3007
|
}
|
|
3008
|
+
// Stamp the charge horizon after verified capture and immediately before
|
|
3009
|
+
// provider stop. Capture can fail and release its claim, allowing re-arm.
|
|
3010
|
+
// A retry after stop success cannot charge post-termination wall time.
|
|
3011
|
+
let stopCutoffMarked = false;
|
|
3012
|
+
const beforeProviderStop = async (): Promise<void> => {
|
|
3013
|
+
const cutoff = await markWarmBillingStopCutoff(db, {
|
|
3014
|
+
accountId,
|
|
3015
|
+
workspaceId: row.workspaceId,
|
|
3016
|
+
sandboxGroupId: row.sandboxGroupId,
|
|
3017
|
+
expectedEpoch: row.leaseEpoch,
|
|
3018
|
+
});
|
|
3019
|
+
if (!cutoff) throw new Error("sandbox stop cutoff was fenced before provider termination");
|
|
3020
|
+
stopCutoffMarked = true;
|
|
3021
|
+
};
|
|
2942
3022
|
const termination: ProviderTerminationOutcome | boolean = providerMissingBeforeCapture
|
|
2943
3023
|
? { terminated: true, providerMissingBeforeCapture: true }
|
|
2944
3024
|
: await terminateBox(
|
|
@@ -2962,12 +3042,49 @@ async function terminateDrainableBox(
|
|
|
2962
3042
|
});
|
|
2963
3043
|
},
|
|
2964
3044
|
row.workspaceId,
|
|
3045
|
+
beforeProviderStop,
|
|
2965
3046
|
);
|
|
2966
3047
|
const terminated = typeof termination === "boolean" ? termination : termination.terminated;
|
|
2967
3048
|
if (!terminated) {
|
|
2968
3049
|
return false;
|
|
2969
3050
|
}
|
|
2970
3051
|
|
|
3052
|
+
// Self-hosted/none and an unassigned lease have no provider stop callback;
|
|
3053
|
+
// finish their usage at logical detach without touching customer compute.
|
|
3054
|
+
// A managed instance that was stopped without its callback must fail closed.
|
|
3055
|
+
const providerMissingDuringStop =
|
|
3056
|
+
providerMissingBeforeCapture ||
|
|
3057
|
+
(typeof termination !== "boolean" && termination.providerMissingBeforeCapture);
|
|
3058
|
+
if (!stopCutoffMarked && !providerMissingDuringStop) {
|
|
3059
|
+
if (managedProvider && lease.instanceId) {
|
|
3060
|
+
throw new Error("managed sandbox stopped without a warm usage cutoff");
|
|
3061
|
+
}
|
|
3062
|
+
await beforeProviderStop();
|
|
3063
|
+
}
|
|
3064
|
+
|
|
3065
|
+
// Stop was confirmed but the lease is not cold yet. This final epoch-fenced
|
|
3066
|
+
// tick settles the interval since the last heartbeat/reaper sweep, including
|
|
3067
|
+
// a box drained before its first periodic tick. If settlement fails, keep
|
|
3068
|
+
// the draining receipt for retry instead of silently losing the charge.
|
|
3069
|
+
const finalRate =
|
|
3070
|
+
settings.sandboxWarmBillingMode === "usage_only"
|
|
3071
|
+
? 0
|
|
3072
|
+
: sandboxWarmRateMicrosPerSecond(settings, backend);
|
|
3073
|
+
if (!providerMissingDuringStop) {
|
|
3074
|
+
const finalTick = await accrueWarmSeconds(db, {
|
|
3075
|
+
accountId,
|
|
3076
|
+
workspaceId: row.workspaceId,
|
|
3077
|
+
sandboxGroupId: row.sandboxGroupId,
|
|
3078
|
+
expectedEpoch: row.leaseEpoch,
|
|
3079
|
+
warmRateMicrosPerSecond: finalRate,
|
|
3080
|
+
billingMode: settings.sandboxWarmBillingMode,
|
|
3081
|
+
finalDrain: true,
|
|
3082
|
+
});
|
|
3083
|
+
if (finalTick.costMicros > 0 && settings.sandboxWarmBillingMode === "credits") {
|
|
3084
|
+
recordCreditMicros(observability, "usage", finalTick.costMicros);
|
|
3085
|
+
}
|
|
3086
|
+
}
|
|
3087
|
+
|
|
2971
3088
|
const providerMissing =
|
|
2972
3089
|
providerMissingBeforeCapture ||
|
|
2973
3090
|
(typeof termination === "boolean" ? false : termination.providerMissingBeforeCapture);
|
|
@@ -3097,6 +3214,7 @@ export async function terminateProviderBox(
|
|
|
3097
3214
|
diskBackedArchives = false,
|
|
3098
3215
|
releaseFailedCapture?: () => Promise<void>,
|
|
3099
3216
|
workspaceId?: string,
|
|
3217
|
+
beforeProviderStop?: () => Promise<void>,
|
|
3100
3218
|
): Promise<ProviderTerminationOutcome> {
|
|
3101
3219
|
const durableBackendId = (lease.resumeBackendId ?? lease.backend) as string;
|
|
3102
3220
|
const backend = sandboxBackendForSdkBackendId(durableBackendId) ?? durableBackendId;
|
|
@@ -3196,6 +3314,7 @@ export async function terminateProviderBox(
|
|
|
3196
3314
|
return { terminated: false, providerMissingBeforeCapture: false };
|
|
3197
3315
|
}
|
|
3198
3316
|
try {
|
|
3317
|
+
await beforeProviderStop?.();
|
|
3199
3318
|
await terminateModalById(settings, lease.instanceId);
|
|
3200
3319
|
} catch (error) {
|
|
3201
3320
|
if (!isProviderSandboxNotFoundError("modal", error)) {
|
|
@@ -3439,6 +3558,7 @@ export async function terminateProviderBox(
|
|
|
3439
3558
|
throw new Error(`sandbox backend ${backend} returned no terminable session state`);
|
|
3440
3559
|
}
|
|
3441
3560
|
prepareProviderForTeardownAfterCapture(backend, session);
|
|
3561
|
+
await beforeProviderStop?.();
|
|
3442
3562
|
await terminateManagedSandboxSession(client, sessionState, session);
|
|
3443
3563
|
return { terminated: true, providerMissingBeforeCapture: false };
|
|
3444
3564
|
} catch (error) {
|
package/src/sandbox-resume.ts
CHANGED
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
effectiveSandboxLifecycle,
|
|
25
25
|
sandboxArchiveCaptureTimeoutMs,
|
|
26
26
|
sandboxLifecycleTransitionWaitMs,
|
|
27
|
+
sandboxWarmRateMicrosPerSecond,
|
|
27
28
|
type Settings,
|
|
28
29
|
} from "@opengeni/config";
|
|
29
30
|
import { randomUUID } from "node:crypto";
|
|
@@ -1153,6 +1154,10 @@ export async function resumeBoxForTurn(
|
|
|
1153
1154
|
holderId,
|
|
1154
1155
|
subjectId: ids.sessionId,
|
|
1155
1156
|
backend: ids.backend,
|
|
1157
|
+
warmBilling: {
|
|
1158
|
+
mode: settings.sandboxWarmBillingMode,
|
|
1159
|
+
rateMicrosPerSecond: sandboxWarmRateMicrosPerSecond(settings, ids.backend),
|
|
1160
|
+
},
|
|
1156
1161
|
os,
|
|
1157
1162
|
// IMAGE IS SHARED STATE (B3): thread the resolved image so the lease stamps it +
|
|
1158
1163
|
// conflicts on a live box already running a different image. A
|
|
@@ -1283,6 +1288,7 @@ export async function resumeBoxForTurn(
|
|
|
1283
1288
|
holderId,
|
|
1284
1289
|
expectedEpoch: heartbeat.expectedEpoch,
|
|
1285
1290
|
leaseTtlMs: heartbeat.leaseTtlMs,
|
|
1291
|
+
billingMode: services.settings.sandboxWarmBillingMode,
|
|
1286
1292
|
})
|
|
1287
1293
|
.then(async (status) => {
|
|
1288
1294
|
if (heartbeat !== holderLeaseHeartbeat) return;
|
|
@@ -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,
|
|
@@ -160,6 +165,14 @@ export const knowledgeSourceSyncActivity = proxyActivities<
|
|
|
160
165
|
});
|
|
161
166
|
|
|
162
167
|
export function workflowFailureMessage(error: unknown): string {
|
|
168
|
+
if (
|
|
169
|
+
error instanceof ActivityFailure &&
|
|
170
|
+
error.activityType === "runAgentTurn" &&
|
|
171
|
+
error.cause instanceof ApplicationFailure &&
|
|
172
|
+
error.cause.type === "TurnExecutionPolicyDefinitionMismatchError"
|
|
173
|
+
) {
|
|
174
|
+
return error.cause.message;
|
|
175
|
+
}
|
|
163
176
|
if (error instanceof Error) {
|
|
164
177
|
return error.message;
|
|
165
178
|
}
|