@opengeni/worker-bundle 1.0.1 → 2.0.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/codex.d.ts +1 -1
- package/dist/activities/agent-turn/compaction-prep.d.ts +1 -5
- package/dist/activities/agent-turn/errors.d.ts +31 -5
- package/dist/activities/agent-turn/finalization-monitor.d.ts +17 -0
- package/dist/activities/agent-turn/finalization.d.ts +1 -1
- package/dist/activities/agent-turn/governance-model.d.ts +0 -2
- package/dist/activities/agent-turn/quiescence.d.ts +1 -0
- package/dist/activities/agent-turn/recovery-warning.d.ts +8 -0
- package/dist/activities/agent-turn/run-credentials.d.ts +0 -1
- package/dist/activities/agent-turn/sandbox-establish.d.ts +1 -1
- package/dist/activities/agent-turn/sandbox-route.d.ts +0 -20
- package/dist/activities/agent-turn/sandbox-runtime.d.ts +1 -1
- package/dist/activities/agent-turn/skill-remove.d.ts +14 -0
- package/dist/activities/agent-turn/skill-search.d.ts +1 -1
- package/dist/activities/agent-turn/stream-attempt.d.ts +1 -0
- package/dist/activities/agent-turn/tool-environment.d.ts +0 -4
- package/dist/activities/agent-turn/turn-context.d.ts +2 -0
- package/dist/activities/agent-turn/turn-sandbox-access.d.ts +3 -0
- package/dist/activities/codex-capacity.d.ts +6 -1
- package/dist/activities/codex-rotation.d.ts +1 -1
- package/dist/activities/context-compaction.d.ts +7 -1
- package/dist/activities/mcp-account-routes.d.ts +22 -0
- package/dist/activities/mcp-credentials.d.ts +12 -13
- package/dist/activities/mcp-operation-authority.d.ts +0 -5
- package/dist/activities/rig-verification.d.ts +2 -3
- package/dist/activities/sandbox-images.d.ts +1 -1
- package/dist/activities/sandbox-lease.d.ts +4 -3
- package/dist/activities/session-state.d.ts +57 -2
- package/dist/activities/types.d.ts +12 -3
- package/dist/{activities-control-HMFGSKC5.js → activities-control-BOLIY3H4.js} +366 -187
- package/dist/activities-control-BOLIY3H4.js.map +1 -0
- package/dist/activities-control.d.ts +110 -2
- package/dist/{activities-turn-AP67JADS.js → activities-turn-2B3WMHBB.js} +767 -471
- package/dist/activities-turn-2B3WMHBB.js.map +1 -0
- package/dist/activities.d.ts +110 -2
- package/dist/artifact-materializer-entry.js +5 -2
- package/dist/artifact-materializer-entry.js.map +1 -1
- package/dist/artifact-outbox-entry.js +2 -2
- package/dist/artifact-outbox-entry.js.map +1 -1
- package/dist/{chunk-A4XQWVZK.js → chunk-I7HKKMUJ.js} +77 -1
- package/dist/chunk-I7HKKMUJ.js.map +1 -0
- package/dist/{chunk-CYITU5UH.js → chunk-TVEE2UUC.js} +275 -146
- package/dist/chunk-TVEE2UUC.js.map +1 -0
- package/dist/editable-artifact-materializer-service.d.ts +2 -2
- package/dist/editable-artifact-outbox-service.d.ts +2 -2
- package/dist/index.d.ts +55 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/observability-metrics.d.ts +11 -3
- package/dist/retained-process-diagnostics.d.ts +4 -0
- package/dist/sandbox-resume.d.ts +2 -12
- package/dist/sandbox-routing.d.ts +5 -3
- package/dist/sandbox-snapshot-diagnostics.d.ts +20 -0
- package/dist/warm-capture-holder.d.ts +3 -0
- package/dist/workflow-bundle.js +51 -11
- package/package.json +15 -15
- package/src/activities/agent-turn/agent-build.ts +42 -5
- package/src/activities/agent-turn/claim.ts +12 -0
- package/src/activities/agent-turn/codex-capacity.ts +26 -4
- package/src/activities/agent-turn/codex.ts +6 -2
- package/src/activities/agent-turn/compaction-prep.ts +53 -72
- package/src/activities/agent-turn/errors.ts +98 -10
- package/src/activities/agent-turn/failure-settlement.ts +49 -28
- package/src/activities/agent-turn/finalization-monitor.ts +121 -0
- package/src/activities/agent-turn/finalization.ts +79 -21
- package/src/activities/agent-turn/governance-model.ts +15 -19
- package/src/activities/agent-turn/history.ts +24 -10
- package/src/activities/agent-turn/quiescence.ts +3 -0
- package/src/activities/agent-turn/recovery-warning.ts +18 -0
- package/src/activities/agent-turn/run-credentials.ts +3 -7
- package/src/activities/agent-turn/run.ts +68 -52
- package/src/activities/agent-turn/sandbox-establish.ts +10 -2
- package/src/activities/agent-turn/sandbox-route.ts +0 -20
- package/src/activities/agent-turn/sandbox-runtime.ts +40 -7
- package/src/activities/agent-turn/skill-remove.ts +46 -0
- package/src/activities/agent-turn/skill-save.ts +1 -1
- package/src/activities/agent-turn/skill-search.ts +1 -1
- package/src/activities/agent-turn/skill-selection.ts +2 -0
- package/src/activities/agent-turn/skill-tools.ts +10 -5
- package/src/activities/agent-turn/stream-attempt.ts +38 -15
- package/src/activities/agent-turn/tool-environment.ts +44 -29
- package/src/activities/agent-turn/turn-context.ts +3 -0
- package/src/activities/agent-turn/turn-sandbox-access.ts +7 -1
- package/src/activities/capabilities.ts +17 -2
- package/src/activities/codex-capacity.ts +38 -9
- package/src/activities/codex-rotation.ts +18 -5
- package/src/activities/context-compaction.ts +26 -4
- package/src/activities/goals.ts +1 -0
- package/src/activities/google-drive-publication.ts +1 -3
- package/src/activities/mcp-account-routes.ts +139 -0
- package/src/activities/mcp-credentials.ts +83 -194
- package/src/activities/mcp-operation-authority.ts +16 -41
- package/src/activities/parent-wake.ts +8 -0
- package/src/activities/rig-verification.ts +38 -37
- package/src/activities/run-input.ts +25 -16
- package/src/activities/sandbox-images.ts +0 -1
- package/src/activities/sandbox-lease.ts +177 -44
- package/src/activities/scheduled-tasks.ts +118 -93
- package/src/activities/session-state.ts +83 -3
- package/src/activities/types.ts +18 -2
- package/src/activities/workflow-wake.ts +2 -17
- package/src/editable-artifact-materializer-service.ts +7 -3
- package/src/editable-artifact-outbox-service.ts +4 -4
- package/src/github-rest-mcp.ts +0 -1
- package/src/index.ts +2 -1
- package/src/observability-metrics.ts +105 -2
- package/src/personal-github-git-credentials.ts +0 -1
- package/src/pr-review-credentials.ts +0 -2
- package/src/retained-process-diagnostics.ts +50 -0
- package/src/sandbox-resume.ts +58 -73
- package/src/sandbox-routing.ts +44 -19
- package/src/sandbox-snapshot-diagnostics.ts +112 -0
- package/src/warm-capture-holder.ts +15 -0
- package/src/workflows/session.ts +64 -2
- package/dist/activities/agent-turn/knowledge-sources.d.ts +0 -20
- package/dist/activities/packs.d.ts +0 -55
- package/dist/activities-control-HMFGSKC5.js.map +0 -1
- package/dist/activities-turn-AP67JADS.js.map +0 -1
- package/dist/chunk-A4XQWVZK.js.map +0 -1
- package/dist/chunk-CYITU5UH.js.map +0 -1
- package/src/activities/agent-turn/knowledge-sources.ts +0 -86
- package/src/activities/packs.ts +0 -166
|
@@ -13,14 +13,17 @@
|
|
|
13
13
|
// harness and executes admitted drains concurrently. There is no per-session
|
|
14
14
|
// timer, viewer activity, owner task queue, or provider-specific lifecycle path
|
|
15
15
|
// in the normal drain state machine.
|
|
16
|
+
import { warnDrainSnapshotFailure } from "../sandbox-snapshot-diagnostics";
|
|
17
|
+
import { warnRetainedProcessProofFailure } from "../retained-process-diagnostics";
|
|
16
18
|
|
|
17
19
|
import { createHash, randomUUID } from "node:crypto";
|
|
18
|
-
import {
|
|
20
|
+
import { requestRetainedProcessDeadlineCancellation } from "@opengeni/db/retained-provider-commands";
|
|
19
21
|
import type { ProviderCommandPersistence, ProviderCommandSession } from "@opengeni/runtime";
|
|
20
22
|
import { Context } from "@temporalio/activity";
|
|
21
23
|
import { OpLostReason, OpState, type OpStatus } from "@opengeni/agent-proto";
|
|
22
24
|
import {
|
|
23
25
|
accrueWarmSeconds,
|
|
26
|
+
retainedProviderCommandPersistence,
|
|
24
27
|
adoptLegacyModalCheckpointArtifact,
|
|
25
28
|
confirmDrainCold,
|
|
26
29
|
appendSessionEventToSandboxGroup,
|
|
@@ -49,6 +52,7 @@ import {
|
|
|
49
52
|
recordRetainedProcessReconciliationProof,
|
|
50
53
|
replaceWorkspaceArchiveCaptureAfterProof,
|
|
51
54
|
readLease,
|
|
55
|
+
readWorkspaceArchiveCapturePreflight,
|
|
52
56
|
reapExpiredSessionListSnapshots,
|
|
53
57
|
reapStaleLeaseHoldersGlobal,
|
|
54
58
|
requestDueSandboxRotationsGlobal,
|
|
@@ -251,6 +255,7 @@ export type TerminateBoxFn = (
|
|
|
251
255
|
capturePolicy?: ProviderWorkspaceCapturePolicy | null,
|
|
252
256
|
diskBackedArchives?: boolean,
|
|
253
257
|
releaseFailedCapture?: () => Promise<void>,
|
|
258
|
+
workspaceId?: string,
|
|
254
259
|
) => Promise<boolean | ProviderTerminationOutcome>;
|
|
255
260
|
|
|
256
261
|
export type SweepModalOrphansFn = (
|
|
@@ -488,6 +493,7 @@ export function createSandboxLeaseActivities(
|
|
|
488
493
|
capturePolicy,
|
|
489
494
|
diskBackedArchives,
|
|
490
495
|
releaseFailedCapture,
|
|
496
|
+
workspaceId,
|
|
491
497
|
) =>
|
|
492
498
|
await terminateProviderBox(
|
|
493
499
|
settings,
|
|
@@ -501,6 +507,7 @@ export function createSandboxLeaseActivities(
|
|
|
501
507
|
capturePolicy,
|
|
502
508
|
diskBackedArchives,
|
|
503
509
|
releaseFailedCapture,
|
|
510
|
+
workspaceId,
|
|
504
511
|
));
|
|
505
512
|
const sweepModalOrphans: SweepModalOrphansFn =
|
|
506
513
|
options.sweepModalOrphans ?? sweepModalOrphansForConfiguredBackend;
|
|
@@ -1160,18 +1167,14 @@ export async function reconcileConnectedMachineBackgroundCommands(
|
|
|
1160
1167
|
retryClock: defaultSelfhostedRetryClock,
|
|
1161
1168
|
journal: { attachGeneration: () => String(Date.now()), persistSettled: () => {} },
|
|
1162
1169
|
});
|
|
1163
|
-
|
|
1170
|
+
await replayConnectedCommandOutput(
|
|
1171
|
+
client,
|
|
1164
1172
|
claim.opId,
|
|
1165
|
-
proof
|
|
1173
|
+
Boolean(proof),
|
|
1166
1174
|
async (frames) => {
|
|
1167
1175
|
await captureConnectedCommandOutput(db, claim, bus)(claim.commandId, frames);
|
|
1168
1176
|
},
|
|
1169
1177
|
);
|
|
1170
|
-
if (proof && replay.status !== "completed" && !replay.terminal) {
|
|
1171
|
-
throw new Error(
|
|
1172
|
-
"Connected command terminal output replay has not reached its exit frontier",
|
|
1173
|
-
);
|
|
1174
|
-
}
|
|
1175
1178
|
}
|
|
1176
1179
|
if (!proof) {
|
|
1177
1180
|
await deferConnectedCommandClaim(
|
|
@@ -1250,6 +1253,35 @@ export async function reconcileConnectedMachineBackgroundCommands(
|
|
|
1250
1253
|
} while (Date.now() < deadline);
|
|
1251
1254
|
}
|
|
1252
1255
|
|
|
1256
|
+
export async function replayConnectedCommandOutput(
|
|
1257
|
+
client: Pick<OpStreamExecClient, "readExisting">,
|
|
1258
|
+
opId: string,
|
|
1259
|
+
terminalKnown: boolean,
|
|
1260
|
+
capture: Parameters<OpStreamExecClient["readExisting"]>[2],
|
|
1261
|
+
): Promise<void> {
|
|
1262
|
+
// A completed operation can need several bounded reads to replay its retained
|
|
1263
|
+
// frames. Keep the reader's integrity checkpoint until the exit is verified;
|
|
1264
|
+
// replacing it after each partial read would restart forever at frame one.
|
|
1265
|
+
let capturedThrough = 0n;
|
|
1266
|
+
while (true) {
|
|
1267
|
+
const before = capturedThrough;
|
|
1268
|
+
const replay = await client.readExisting(opId, terminalKnown ? 5_000 : 250, async (frames) => {
|
|
1269
|
+
await capture(frames);
|
|
1270
|
+
for (const frame of frames) {
|
|
1271
|
+
const sequence = BigInt(frame.sequence);
|
|
1272
|
+
if (sequence > capturedThrough) capturedThrough = sequence;
|
|
1273
|
+
}
|
|
1274
|
+
});
|
|
1275
|
+
if (!terminalKnown || replay.status === "completed" || replay.terminal) return;
|
|
1276
|
+
// This is a finite terminal drain, not a poller for a running command.
|
|
1277
|
+
// Failed persistence, transport/integrity errors, and no-progress reads
|
|
1278
|
+
// return control to normal reconciliation without licensing settlement.
|
|
1279
|
+
if (capturedThrough === before) {
|
|
1280
|
+
throw new Error("Connected command terminal output replay has not reached its exit frontier");
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1253
1285
|
export async function probeConnectedMachineBackgroundCommand(
|
|
1254
1286
|
claim: ConnectedMachineBackgroundCommandClaim,
|
|
1255
1287
|
controlRpc: ControlRpc,
|
|
@@ -1381,6 +1413,47 @@ async function reconcileTerminalRetainedProcesses(
|
|
|
1381
1413
|
let process = claim.process;
|
|
1382
1414
|
const expected = retainedProcessSettlementIdentity(process);
|
|
1383
1415
|
let proof = retainedProcessReconciliationProof(process);
|
|
1416
|
+
const processScope = {
|
|
1417
|
+
accountId: process.accountId,
|
|
1418
|
+
workspaceId: process.workspaceId,
|
|
1419
|
+
sessionId: process.sessionId,
|
|
1420
|
+
processId: process.id,
|
|
1421
|
+
};
|
|
1422
|
+
const storedCommandPersistence = retainedProviderCommandPersistence(
|
|
1423
|
+
db,
|
|
1424
|
+
processScope,
|
|
1425
|
+
bus ? (events) => bus.publish(process.workspaceId, process.sessionId, events) : undefined,
|
|
1426
|
+
);
|
|
1427
|
+
const commandPersistence = {
|
|
1428
|
+
...storedCommandPersistence,
|
|
1429
|
+
requestCancellation: async (reason: "provider_deadline" | "explicit_stop") => {
|
|
1430
|
+
if (reason === "provider_deadline") {
|
|
1431
|
+
await requestRetainedProcessDeadlineCancellation(db, processScope);
|
|
1432
|
+
if (!(await storedCommandPersistence.cancellationRequested()))
|
|
1433
|
+
throw new Error(
|
|
1434
|
+
"Supervised deadline cancellation no longer owns its original rotating lease",
|
|
1435
|
+
);
|
|
1436
|
+
} else await storedCommandPersistence.requestCancellation(reason);
|
|
1437
|
+
},
|
|
1438
|
+
};
|
|
1439
|
+
if (proof && process.providerBackend === "modal") {
|
|
1440
|
+
try {
|
|
1441
|
+
const command = await commandPersistence.load();
|
|
1442
|
+
if (
|
|
1443
|
+
command?.kind === "modal-router-v1" &&
|
|
1444
|
+
command.supervision &&
|
|
1445
|
+
(!(await commandPersistence.loadSupervisionReceipt()) ||
|
|
1446
|
+
![command.streams.stdout, command.streams.stderr].every(
|
|
1447
|
+
(stream) => stream.eof && stream.exitCode === 0,
|
|
1448
|
+
))
|
|
1449
|
+
)
|
|
1450
|
+
proof = null;
|
|
1451
|
+
} catch {
|
|
1452
|
+
// An earlier terminal observation cannot make missing control/output
|
|
1453
|
+
// proof unreachable. Reprobe the original binding; DB remains the gate.
|
|
1454
|
+
proof = null;
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1384
1457
|
if (!proof) {
|
|
1385
1458
|
let observation: RetainedProcessProbeResult | null = null;
|
|
1386
1459
|
|
|
@@ -1490,6 +1563,26 @@ async function reconcileTerminalRetainedProcesses(
|
|
|
1490
1563
|
}
|
|
1491
1564
|
} else {
|
|
1492
1565
|
try {
|
|
1566
|
+
const command = await commandPersistence.load();
|
|
1567
|
+
const supervised = command?.kind === "modal-router-v1" && Boolean(command.supervision);
|
|
1568
|
+
const supervisionMetric = (outcome: string) =>
|
|
1569
|
+
observability.incrementCounter({
|
|
1570
|
+
name: "opengeni_command_supervision_total",
|
|
1571
|
+
help: "Native retained command supervision reconciliation outcomes.",
|
|
1572
|
+
labels: { outcome },
|
|
1573
|
+
});
|
|
1574
|
+
if (
|
|
1575
|
+
supervised &&
|
|
1576
|
+
(lease!.rotationReason === "provider_deadline" ||
|
|
1577
|
+
claim.ownerState === "background_stopping")
|
|
1578
|
+
) {
|
|
1579
|
+
await commandPersistence.requestCancellation(
|
|
1580
|
+
lease!.rotationReason === "provider_deadline"
|
|
1581
|
+
? "provider_deadline"
|
|
1582
|
+
: "explicit_stop",
|
|
1583
|
+
);
|
|
1584
|
+
supervisionMetric("cancellation_intent");
|
|
1585
|
+
}
|
|
1493
1586
|
observation = await probe(
|
|
1494
1587
|
settings,
|
|
1495
1588
|
lease!,
|
|
@@ -1516,13 +1609,21 @@ async function reconcileTerminalRetainedProcesses(
|
|
|
1516
1609
|
.publish(process.workspaceId, process.sessionId, events)
|
|
1517
1610
|
.catch(() => undefined);
|
|
1518
1611
|
},
|
|
1519
|
-
|
|
1520
|
-
accountId: process.accountId,
|
|
1521
|
-
workspaceId: process.workspaceId,
|
|
1522
|
-
sessionId: process.sessionId,
|
|
1523
|
-
processId: process.id,
|
|
1524
|
-
}),
|
|
1612
|
+
commandPersistence,
|
|
1525
1613
|
);
|
|
1614
|
+
if (supervised) {
|
|
1615
|
+
supervisionMetric(
|
|
1616
|
+
(await commandPersistence.loadSupervisionReceipt())
|
|
1617
|
+
? "proof_retained"
|
|
1618
|
+
: "proof_missing",
|
|
1619
|
+
);
|
|
1620
|
+
if (observation.status === "deferred")
|
|
1621
|
+
supervisionMetric(
|
|
1622
|
+
observation.reason === "provider_running"
|
|
1623
|
+
? "checkpoint_blocked"
|
|
1624
|
+
: "provider_failure",
|
|
1625
|
+
);
|
|
1626
|
+
}
|
|
1526
1627
|
} catch (error) {
|
|
1527
1628
|
observability.warn("sandbox reaper: retained-process provider probe failed", {
|
|
1528
1629
|
processId: process.id,
|
|
@@ -1581,10 +1682,7 @@ async function reconcileTerminalRetainedProcesses(
|
|
|
1581
1682
|
recordRetainedProcessReconciliation(observability, `proof_${proof.outcome}`);
|
|
1582
1683
|
} catch (error) {
|
|
1583
1684
|
recordRetainedProcessReconciliation(observability, "proof_checkpoint_failed");
|
|
1584
|
-
observability
|
|
1585
|
-
processId: process.id,
|
|
1586
|
-
error: error instanceof Error ? error.message : String(error),
|
|
1587
|
-
});
|
|
1685
|
+
warnRetainedProcessProofFailure(observability, error, process);
|
|
1588
1686
|
continue;
|
|
1589
1687
|
}
|
|
1590
1688
|
}
|
|
@@ -1788,7 +1886,8 @@ export async function probeRetainedProcessAtProvider(
|
|
|
1788
1886
|
lease.leaseEpoch !== process.leaseEpoch ||
|
|
1789
1887
|
lease.backend !== process.providerBackend ||
|
|
1790
1888
|
lease.instanceId !== process.providerInstanceId ||
|
|
1791
|
-
lease.resumeBackendId
|
|
1889
|
+
!lease.resumeBackendId ||
|
|
1890
|
+
sandboxBackendForSdkBackendId(lease.resumeBackendId) !== process.providerBackend ||
|
|
1792
1891
|
process.routeTargetId !== null
|
|
1793
1892
|
) {
|
|
1794
1893
|
return { status: "deferred", reason: "identity_mismatch" };
|
|
@@ -1803,7 +1902,11 @@ export async function probeRetainedProcessAtProvider(
|
|
|
1803
1902
|
return classifyRetainedProcessPollResult(pending.result, process.providerSessionId);
|
|
1804
1903
|
}
|
|
1805
1904
|
const envelopeBackend = (lease.resumeState as { backendId?: unknown }).backendId;
|
|
1806
|
-
if (
|
|
1905
|
+
if (
|
|
1906
|
+
envelopeBackend !== undefined &&
|
|
1907
|
+
(typeof envelopeBackend !== "string" ||
|
|
1908
|
+
sandboxBackendForSdkBackendId(envelopeBackend) !== process.providerBackend)
|
|
1909
|
+
) {
|
|
1807
1910
|
return { status: "deferred", reason: "identity_mismatch" };
|
|
1808
1911
|
}
|
|
1809
1912
|
if (sandboxProviderInstanceIdFromEnvelope(lease.resumeState) !== process.providerInstanceId) {
|
|
@@ -1837,7 +1940,11 @@ export async function probeRetainedProcessAtProvider(
|
|
|
1837
1940
|
} catch {
|
|
1838
1941
|
return { status: "deferred", reason: "provider_error" };
|
|
1839
1942
|
}
|
|
1840
|
-
if (
|
|
1943
|
+
if (
|
|
1944
|
+
!client ||
|
|
1945
|
+
sandboxBackendForSdkBackendId(client.backendId) !== process.providerBackend ||
|
|
1946
|
+
!client.resume
|
|
1947
|
+
) {
|
|
1841
1948
|
return { status: "deferred", reason: "backend_unsupported" };
|
|
1842
1949
|
}
|
|
1843
1950
|
const envelopeSessionState =
|
|
@@ -1928,6 +2035,11 @@ export async function probeRetainedProcessAtProvider(
|
|
|
1928
2035
|
return;
|
|
1929
2036
|
}
|
|
1930
2037
|
const page = session.getProviderCommandOutput?.(value);
|
|
2038
|
+
if (page?.command.kind === "modal-router-v1" && typeof value === "string") {
|
|
2039
|
+
if (!session.captureCommandOutput || !(await session.captureCommandOutput(value)))
|
|
2040
|
+
throw new Error("Byte-offset output requires atomic capture before settlement");
|
|
2041
|
+
return;
|
|
2042
|
+
}
|
|
1931
2043
|
if (page) {
|
|
1932
2044
|
for (const chunk of page.chunks)
|
|
1933
2045
|
if (chunk.text)
|
|
@@ -1940,10 +2052,17 @@ export async function probeRetainedProcessAtProvider(
|
|
|
1940
2052
|
|
|
1941
2053
|
let result: unknown;
|
|
1942
2054
|
try {
|
|
2055
|
+
const supervisedCancelled =
|
|
2056
|
+
mode === "cancel" || lease.rotationReason === "provider_deadline"
|
|
2057
|
+
? ((await session.cancelSupervisedCommand?.(
|
|
2058
|
+
process.providerSessionId,
|
|
2059
|
+
lease.rotationReason === "provider_deadline" ? "provider_deadline" : "explicit_stop",
|
|
2060
|
+
)) ?? false)
|
|
2061
|
+
: false;
|
|
1943
2062
|
result = await withRetainedProcessProbeTimeout(
|
|
1944
2063
|
session.writeStdin({
|
|
1945
2064
|
sessionId: process.providerSessionId,
|
|
1946
|
-
chars: mode === "cancel" ? "\u0003" : "",
|
|
2065
|
+
chars: mode === "cancel" && !supervisedCancelled ? "\u0003" : "",
|
|
1947
2066
|
yieldTimeMs: 1_000,
|
|
1948
2067
|
maxOutputTokens: 2_000,
|
|
1949
2068
|
}),
|
|
@@ -1972,7 +2091,10 @@ export async function probeRetainedProcessAtProvider(
|
|
|
1972
2091
|
if (
|
|
1973
2092
|
observation.status === "deferred" &&
|
|
1974
2093
|
observation.reason === "provider_running" &&
|
|
1975
|
-
lease.rotationRequestedAt !== null
|
|
2094
|
+
lease.rotationRequestedAt !== null &&
|
|
2095
|
+
!(await providerPersistence
|
|
2096
|
+
?.load()
|
|
2097
|
+
.then((command) => command?.kind === "modal-router-v1" && command.supervision))
|
|
1976
2098
|
) {
|
|
1977
2099
|
// A background PTY cannot outlive the finite provider box. Interrupt only
|
|
1978
2100
|
// this exact durable provider session after rotation admission is fenced;
|
|
@@ -2799,6 +2921,24 @@ async function terminateDrainableBox(
|
|
|
2799
2921
|
// lease draining for a later sweep (NEVER terminate a box whose files we
|
|
2800
2922
|
// could not capture). A persist CAS miss means the box was re-armed and left
|
|
2801
2923
|
// running, so the cold commit is skipped.
|
|
2924
|
+
// A published capture retry skips persistence. Revalidate legacy containment
|
|
2925
|
+
// even on that path: stale enrollment is not native-supervisor exit proof.
|
|
2926
|
+
// Typed provider absence is independently fenced by the canonical loss commit.
|
|
2927
|
+
if (
|
|
2928
|
+
!providerMissingBeforeCapture &&
|
|
2929
|
+
backend === "modal" &&
|
|
2930
|
+
lease.instanceId &&
|
|
2931
|
+
!(await readWorkspaceArchiveCapturePreflight(db, {
|
|
2932
|
+
accountId,
|
|
2933
|
+
workspaceId: row.workspaceId,
|
|
2934
|
+
sandboxGroupId: row.sandboxGroupId,
|
|
2935
|
+
expectedEpoch: row.leaseEpoch,
|
|
2936
|
+
expectedInstanceId: lease.instanceId,
|
|
2937
|
+
liveness: "draining",
|
|
2938
|
+
}))
|
|
2939
|
+
) {
|
|
2940
|
+
return false;
|
|
2941
|
+
}
|
|
2802
2942
|
const termination: ProviderTerminationOutcome | boolean = providerMissingBeforeCapture
|
|
2803
2943
|
? { terminated: true, providerMissingBeforeCapture: true }
|
|
2804
2944
|
: await terminateBox(
|
|
@@ -2821,6 +2961,7 @@ async function terminateDrainableBox(
|
|
|
2821
2961
|
expectedInstanceId: lease.instanceId,
|
|
2822
2962
|
});
|
|
2823
2963
|
},
|
|
2964
|
+
row.workspaceId,
|
|
2824
2965
|
);
|
|
2825
2966
|
const terminated = typeof termination === "boolean" ? termination : termination.terminated;
|
|
2826
2967
|
if (!terminated) {
|
|
@@ -2955,18 +3096,22 @@ export async function terminateProviderBox(
|
|
|
2955
3096
|
claimedCapturePolicy?: ProviderWorkspaceCapturePolicy | null,
|
|
2956
3097
|
diskBackedArchives = false,
|
|
2957
3098
|
releaseFailedCapture?: () => Promise<void>,
|
|
3099
|
+
workspaceId?: string,
|
|
2958
3100
|
): Promise<ProviderTerminationOutcome> {
|
|
2959
3101
|
const durableBackendId = (lease.resumeBackendId ?? lease.backend) as string;
|
|
2960
3102
|
const backend = sandboxBackendForSdkBackendId(durableBackendId) ?? durableBackendId;
|
|
2961
3103
|
const logIdentity = providerDrainLogIdentity(lease, backend);
|
|
3104
|
+
const warnCaptureFailure = (message: string, error: unknown): void =>
|
|
3105
|
+
warnDrainSnapshotFailure(observability, message, error, {
|
|
3106
|
+
sandboxGroupId: lease.sandboxGroupId,
|
|
3107
|
+
leaseEpoch: lease.leaseEpoch,
|
|
3108
|
+
...(workspaceId ? { workspaceId } : {}),
|
|
3109
|
+
});
|
|
2962
3110
|
const releaseAfterCaptureFailure = async (): Promise<void> => {
|
|
2963
3111
|
try {
|
|
2964
3112
|
await releaseFailedCapture?.();
|
|
2965
3113
|
} catch (error) {
|
|
2966
|
-
|
|
2967
|
-
...logIdentity,
|
|
2968
|
-
error: error instanceof Error ? error.message : String(error),
|
|
2969
|
-
});
|
|
3114
|
+
warnCaptureFailure("sandbox reaper: failed capture claim release failed", error);
|
|
2970
3115
|
}
|
|
2971
3116
|
};
|
|
2972
3117
|
// 'none' / no backend -> nothing to terminate.
|
|
@@ -3208,29 +3353,20 @@ export async function terminateProviderBox(
|
|
|
3208
3353
|
},
|
|
3209
3354
|
);
|
|
3210
3355
|
} catch (error) {
|
|
3211
|
-
|
|
3212
|
-
...logIdentity,
|
|
3213
|
-
error: error instanceof Error ? error.message : String(error),
|
|
3214
|
-
});
|
|
3356
|
+
warnCaptureFailure("sandbox reaper: late workspace capture publication failed", error);
|
|
3215
3357
|
} finally {
|
|
3216
3358
|
await disposeWorkspaceArchive(archive);
|
|
3217
3359
|
}
|
|
3218
3360
|
},
|
|
3219
3361
|
observeLatePublicationFailure: (error) => {
|
|
3220
|
-
|
|
3221
|
-
...logIdentity,
|
|
3222
|
-
error: error instanceof Error ? error.message : String(error),
|
|
3223
|
-
});
|
|
3362
|
+
warnCaptureFailure("sandbox reaper: late capture publication cleanup failed", error);
|
|
3224
3363
|
},
|
|
3225
3364
|
observeLateFailure: (error) => {
|
|
3226
3365
|
// Capture has now rejected and this timed-out path can never reach
|
|
3227
3366
|
// provider teardown. Release only our unpublished claim so a waiting
|
|
3228
3367
|
// turn can re-arm the intact live box; stale callbacks are DB-fenced.
|
|
3229
3368
|
void releaseAfterCaptureFailure();
|
|
3230
|
-
|
|
3231
|
-
...logIdentity,
|
|
3232
|
-
error: error instanceof Error ? error.message : String(error),
|
|
3233
|
-
});
|
|
3369
|
+
warnCaptureFailure("sandbox reaper: timed-out provider capture later failed", error);
|
|
3234
3370
|
},
|
|
3235
3371
|
});
|
|
3236
3372
|
}
|
|
@@ -3241,12 +3377,9 @@ export async function terminateProviderBox(
|
|
|
3241
3377
|
});
|
|
3242
3378
|
return { terminated: true, providerMissingBeforeCapture: true };
|
|
3243
3379
|
}
|
|
3244
|
-
|
|
3380
|
+
warnCaptureFailure(
|
|
3245
3381
|
"sandbox reaper: persistWorkspace failed — leaving box draining (files NOT lost)",
|
|
3246
|
-
|
|
3247
|
-
...logIdentity,
|
|
3248
|
-
error: error instanceof Error ? error.message : String(error),
|
|
3249
|
-
},
|
|
3382
|
+
error,
|
|
3250
3383
|
);
|
|
3251
3384
|
// An unresolved timeout is not proof of capture failure: retain the fence
|
|
3252
3385
|
// until its provider promise settles. A settled failure cannot enter the
|