@opengeni/runtime 3.0.1 → 4.0.0-canary.35625901699001
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/assets/codemode-client.json +1 -0
- package/dist/chunk-4A4XTKKX.js +1 -0
- package/dist/{chunk-FHGLYLBL.js → chunk-SPT3PLMX.js} +1 -87
- package/dist/chunk-SPT3PLMX.js.map +1 -0
- package/dist/{chunk-GB763TSV.js → chunk-TE27J65Q.js} +1 -1
- package/dist/chunk-TE27J65Q.js.map +1 -0
- package/dist/{chunk-46BFUGFL.js → chunk-WIHICNWS.js} +1660 -388
- package/dist/chunk-WIHICNWS.js.map +1 -0
- package/dist/{chunk-RIJ6DCYS.js → chunk-YKI7DF2L.js} +9866 -9476
- package/dist/chunk-YKI7DF2L.js.map +1 -0
- package/dist/{chunk-UU6EJR52.js → chunk-YU4VRTR4.js} +2 -2
- package/dist/chunk-YU4VRTR4.js.map +1 -0
- package/dist/chunk-ZG2YH25H.js +94 -0
- package/dist/chunk-ZG2YH25H.js.map +1 -0
- package/dist/codex-tool-search.d.ts +5 -4
- package/dist/context-compaction.d.ts +19 -0
- package/dist/gmail-rest-mcp.js +1 -1
- package/dist/index.d.ts +38 -23
- package/dist/index.js +29 -5
- package/dist/lazy-manifest-OEYEHNVY.js +45 -0
- package/dist/lazy-manifest-OEYEHNVY.js.map +1 -0
- package/dist/lazy-manifest.d.ts +6 -0
- package/dist/lazy-tool-transport.d.ts +3 -2
- package/dist/{localSources-PVFCAPN2.js → localSources-TWHTEKGO.js} +3 -2
- package/dist/localSources-TWHTEKGO.js.map +1 -0
- package/dist/metrics.d.ts +6 -0
- package/dist/operational-instructions.d.ts +1 -1
- package/dist/prepared-compaction-request.d.ts +8 -0
- package/dist/provider-history-adapter.d.ts +2 -2
- package/dist/runtime-skills.d.ts +3 -10
- package/dist/sandbox/codemode-client.d.ts +17 -0
- package/dist/sandbox/codemode-token.d.ts +3 -3
- package/dist/sandbox/index.d.ts +2 -1
- package/dist/sandbox/index.js +8 -2
- package/dist/sandbox/materialization-verification-error.d.ts +27 -0
- package/dist/sandbox/provider-command-session.d.ts +44 -2
- package/dist/sandbox/providers/modal-command-control.d.ts +32 -29
- package/dist/sandbox/providers/modal-command-router-wire.d.ts +49 -0
- package/dist/sandbox/providers/modal-command-session.d.ts +2 -1
- package/dist/sandbox/providers/modal-legacy-command-control.d.ts +50 -0
- package/dist/sandbox/providers/modal-materialization-verification.d.ts +7 -0
- package/dist/sandbox/providers/modal.d.ts +0 -8
- package/dist/sandbox/routing/routing-session.d.ts +21 -0
- package/dist/sandbox/turn-tool-cancellation.d.ts +1 -0
- package/dist/skill-library.d.ts +0 -6
- package/dist/skill-library.js +1 -1
- package/dist/workspace-tool-gateway.js +7 -5
- package/package.json +13 -11
- package/src/bundled_artifact_skills/opengeni-documents/SKILL.md +19 -0
- package/src/bundled_artifact_skills/opengeni-documents/references/api.md +22 -0
- package/src/bundled_default_skills/opengeni-client/SKILL.md +349 -0
- package/src/bundled_default_skills/opengeni-client/agents/openai.yaml +4 -0
- package/src/bundled_default_skills/opengeni-client/references/api-workflows.md +256 -0
- package/src/bundled_default_skills/opengeni-client/references/compatibility-and-troubleshooting.md +84 -0
- package/src/bundled_default_skills/opengeni-client/references/customer-skill-template.md +96 -0
- package/src/bundled_default_skills/opengeni-client/references/data-tools-and-credentials.md +99 -0
- package/src/bundled_default_skills/opengeni-client/references/discovery-and-autonomy.md +81 -0
- package/src/bundled_default_skills/opengeni-client/references/external-users-and-connect.md +457 -0
- package/src/bundled_default_skills/opengeni-client/references/isolation-and-authorization.md +77 -0
- package/src/bundled_default_skills/opengeni-client/references/product-integration-shapes.md +232 -0
- package/src/bundled_default_skills/opengeni-client/references/product-shapes-and-ui.md +116 -0
- package/src/bundled_default_skills/opengeni-client/references/runtime-profile-and-verification.md +157 -0
- package/src/bundled_default_skills/opengeni-help/SKILL.md +101 -0
- package/src/bundled_management_skills/opengeni-skills/SKILL.md +34 -4
- package/src/codex-tool-search.ts +13 -5
- package/src/context-compaction.ts +87 -4
- package/src/gmail-rest-mcp.ts +1 -2
- package/src/index.ts +452 -139
- package/src/lazy-manifest.ts +55 -0
- package/src/lazy-tool-transport.ts +15 -2
- package/src/metrics.ts +6 -0
- package/src/model-request-capture.ts +3 -0
- package/src/operational-instructions.ts +13 -2
- package/src/prepared-compaction-request.ts +51 -0
- package/src/provider-history-adapter.ts +12 -3
- package/src/runtime-skills.ts +3 -17
- package/src/sandbox/codemode-client.ts +204 -0
- package/src/sandbox/codemode-token.ts +7 -2
- package/src/sandbox/index.ts +7 -0
- package/src/sandbox/materialization-verification-error.ts +65 -0
- package/src/sandbox/provider-command-session.ts +70 -2
- package/src/sandbox/providers/modal-command-control.ts +386 -242
- package/src/sandbox/providers/modal-command-router-wire.ts +248 -0
- package/src/sandbox/providers/modal-command-session.ts +177 -4
- package/src/sandbox/providers/modal-legacy-command-control.ts +351 -0
- package/src/sandbox/providers/modal-materialization-verification.ts +122 -0
- package/src/sandbox/providers/modal.ts +1 -9
- package/src/sandbox/routing/routing-session.ts +417 -79
- package/src/sandbox/turn-tool-cancellation.ts +38 -2
- package/src/skill-catalog.ts +1 -0
- package/src/skill-library.ts +0 -6
- package/src/workspace-governance.ts +0 -1
- package/src/workspace-skills.ts +114 -74
- package/dist/chunk-46BFUGFL.js.map +0 -1
- package/dist/chunk-FHGLYLBL.js.map +0 -1
- package/dist/chunk-GB763TSV.js.map +0 -1
- package/dist/chunk-RIJ6DCYS.js.map +0 -1
- package/dist/chunk-UU6EJR52.js.map +0 -1
- /package/dist/{localSources-PVFCAPN2.js.map → chunk-4A4XTKKX.js.map} +0 -0
|
@@ -28,13 +28,23 @@
|
|
|
28
28
|
// `@opengeni/db`.
|
|
29
29
|
|
|
30
30
|
import type { ExposedPortEndpoint } from "../stream-port";
|
|
31
|
+
import { isDeepStrictEqual } from "node:util";
|
|
31
32
|
import {
|
|
32
33
|
withProviderCommandHandle,
|
|
34
|
+
withCommandSupervisionReady,
|
|
35
|
+
withSupervisedLaunchReservation,
|
|
36
|
+
ProviderCommandStartRejectedError,
|
|
33
37
|
type ProviderCommandPersistence,
|
|
34
38
|
type ProviderCommandSession,
|
|
35
39
|
} from "../provider-command-session";
|
|
36
40
|
import type { SandboxProviderCommand } from "@opengeni/contracts";
|
|
37
|
-
import { hasTypedExecHandleLoss } from "../exec-banner";
|
|
41
|
+
import { hasTypedExecHandleLoss, parseExecResponseBanner } from "../exec-banner";
|
|
42
|
+
import {
|
|
43
|
+
SandboxMaterializationVerificationError,
|
|
44
|
+
materializationVerificationDiagnostic,
|
|
45
|
+
retainMaterializationVerificationDiagnostic,
|
|
46
|
+
type MaterializationFailureReason,
|
|
47
|
+
} from "../materialization-verification-error";
|
|
38
48
|
import { CAPABILITY_DESCRIPTORS, type SandboxBackend } from "@opengeni/contracts";
|
|
39
49
|
import { SelfhostedControlError } from "../selfhosted/control-rpc";
|
|
40
50
|
import {
|
|
@@ -102,6 +112,8 @@ export interface RoutableBackendSession extends ProviderCommandSession {
|
|
|
102
112
|
pathExists?(path: string, runAs?: string): Promise<boolean>;
|
|
103
113
|
viewImage?(args: unknown): Promise<unknown>;
|
|
104
114
|
materializeEntry?(args: unknown): Promise<void>;
|
|
115
|
+
/** Provider-owned fixed read-only probe; never an agent command surface. */
|
|
116
|
+
verifyMaterializedPath?(path: string, workdir: string): Promise<void>;
|
|
105
117
|
supportsPty?(): boolean;
|
|
106
118
|
resolveExposedPort?(port: number): Promise<ExposedPortEndpoint>;
|
|
107
119
|
serializeSessionState?(): Promise<unknown>;
|
|
@@ -169,6 +181,7 @@ export type RoutingRetainedProcessTerminalProof =
|
|
|
169
181
|
|
|
170
182
|
export interface RoutingSandboxSessionDeps {
|
|
171
183
|
providerCommandHandle?: (admission: unknown) => number | undefined;
|
|
184
|
+
providerSupervisionReady?: () => Promise<boolean>;
|
|
172
185
|
providerCommandPersistence?: (process: RoutingRetainedProcess) => ProviderCommandPersistence;
|
|
173
186
|
/**
|
|
174
187
|
* The DEFAULT backend resolved at construction time (the same shape `resolve()`
|
|
@@ -208,6 +221,8 @@ export interface RoutingSandboxSessionDeps {
|
|
|
208
221
|
* diagnostic only: callback failures are isolated and can never change the
|
|
209
222
|
* provider result or durable mutation-settlement ordering. */
|
|
210
223
|
onOperation?: RoutingSandboxOperationObserver;
|
|
224
|
+
/** Capture gates are not physical provider calls; observe every wait separately. */
|
|
225
|
+
onCaptureWait?: RoutingSandboxCaptureWaitObserver;
|
|
211
226
|
/** Observe the first complete routed operation and only the subphases that
|
|
212
227
|
* actually ran. Child durations are exclusive; capture waits are removed
|
|
213
228
|
* from their enclosing admission/provider spans. */
|
|
@@ -294,17 +309,35 @@ export interface RoutingSandboxSessionDeps {
|
|
|
294
309
|
}) => Promise<DefaultBackendLossResult | null>;
|
|
295
310
|
}
|
|
296
311
|
|
|
312
|
+
function eligibleForSupervision(args: unknown): boolean {
|
|
313
|
+
return Boolean(
|
|
314
|
+
args &&
|
|
315
|
+
typeof args === "object" &&
|
|
316
|
+
!(args as { tty?: boolean }).tty &&
|
|
317
|
+
!(args as { runAs?: string }).runAs,
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
|
|
297
321
|
export type RoutingSandboxOperationObservation = {
|
|
298
322
|
backend: string;
|
|
299
323
|
op: string;
|
|
300
324
|
outcome: "ok" | "not_found" | "failed";
|
|
301
325
|
durationMs: number;
|
|
326
|
+
materializationFailureReason?: MaterializationFailureReason;
|
|
302
327
|
};
|
|
303
328
|
|
|
304
329
|
export type RoutingSandboxOperationObserver = (
|
|
305
330
|
observation: RoutingSandboxOperationObservation,
|
|
306
331
|
) => void;
|
|
307
332
|
|
|
333
|
+
export type RoutingSandboxCaptureWaitObserver = (observation: {
|
|
334
|
+
backend: string;
|
|
335
|
+
op: string;
|
|
336
|
+
outcome: "ok" | "failed";
|
|
337
|
+
durationMs: number;
|
|
338
|
+
captureWaitStage: "admission" | "provider";
|
|
339
|
+
}) => void;
|
|
340
|
+
|
|
308
341
|
export type RoutingSandboxPhaseOutcome = "completed" | "failed";
|
|
309
342
|
|
|
310
343
|
export type RoutingSandboxWaitObservation = {
|
|
@@ -707,6 +740,7 @@ export class RoutingSandboxSession implements RoutableBackendSession {
|
|
|
707
740
|
private cachedEpoch: number | undefined;
|
|
708
741
|
private cachedSandboxId: string | null | undefined;
|
|
709
742
|
private cached: ResolvedActiveBackend | undefined;
|
|
743
|
+
private readonly homeResolutions = new Map<number, Promise<ResolvedActiveBackend>>();
|
|
710
744
|
// The last-resolved backend, exposed via the `state` getter (a method-free read
|
|
711
745
|
// of the active backend's `state`). Updated on every resolve.
|
|
712
746
|
private lastResolved: ResolvedActiveBackend | undefined;
|
|
@@ -804,6 +838,23 @@ export class RoutingSandboxSession implements RoutableBackendSession {
|
|
|
804
838
|
) {
|
|
805
839
|
return this.cached;
|
|
806
840
|
}
|
|
841
|
+
// Home repair may prepare the release client before publishing a handle.
|
|
842
|
+
// Concurrent first operations must join that preparation, including its
|
|
843
|
+
// failure, instead of each resuming/preparing the replacement independently.
|
|
844
|
+
// Explicit machine routes retain their existing resolution behavior.
|
|
845
|
+
if (pointer.activeSandboxId !== null) return await this.resolvePointer(pointer);
|
|
846
|
+
const pending = this.homeResolutions.get(pointer.activeEpoch);
|
|
847
|
+
if (pending) return await pending;
|
|
848
|
+
const resolution = this.resolvePointer(pointer);
|
|
849
|
+
this.homeResolutions.set(pointer.activeEpoch, resolution);
|
|
850
|
+
try {
|
|
851
|
+
return await resolution;
|
|
852
|
+
} finally {
|
|
853
|
+
this.homeResolutions.delete(pointer.activeEpoch);
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
private async resolvePointer(pointer: ActivePointer): Promise<ResolvedActiveBackend> {
|
|
807
858
|
const fromEpoch = this.cachedEpoch ?? pointer.activeEpoch;
|
|
808
859
|
const resolved = await this.deps.resolveActiveBackend(pointer);
|
|
809
860
|
// Re-entrancy guard: a resolver that returns THIS proxy as the active backend
|
|
@@ -1243,6 +1294,24 @@ export class RoutingSandboxSession implements RoutableBackendSession {
|
|
|
1243
1294
|
const providerSessionId = providerSessionIdFromArgs(args);
|
|
1244
1295
|
if (providerSessionId === null) throw new RoutingRetainedProcessNotFoundError(-1);
|
|
1245
1296
|
const record = this.retainedProcess(providerSessionId);
|
|
1297
|
+
if (
|
|
1298
|
+
!modelVisible &&
|
|
1299
|
+
args &&
|
|
1300
|
+
typeof args === "object" &&
|
|
1301
|
+
(args as { chars?: unknown }).chars === "\u0003" &&
|
|
1302
|
+
record.process.providerCommand?.kind === "modal-router-v1" &&
|
|
1303
|
+
record.process.providerCommand.supervision
|
|
1304
|
+
) {
|
|
1305
|
+
await this.ensureParentPromotion(record);
|
|
1306
|
+
if (
|
|
1307
|
+
!(await record.backend.session.cancelSupervisedCommand?.(
|
|
1308
|
+
providerSessionId,
|
|
1309
|
+
"explicit_stop",
|
|
1310
|
+
))
|
|
1311
|
+
)
|
|
1312
|
+
throw new Error("Supervised command cancellation is unavailable");
|
|
1313
|
+
args = { ...args, chars: "" };
|
|
1314
|
+
}
|
|
1246
1315
|
await this.captureRetainedOutput(record);
|
|
1247
1316
|
const priorTerminal = await this.flushPendingProcessMutation(record);
|
|
1248
1317
|
if (priorTerminal !== null) {
|
|
@@ -1276,6 +1345,31 @@ export class RoutingSandboxSession implements RoutableBackendSession {
|
|
|
1276
1345
|
result?: unknown,
|
|
1277
1346
|
): Promise<void> {
|
|
1278
1347
|
if (!this.deps.captureProcessOutput) return;
|
|
1348
|
+
// Router byte-offset pages atomically append output and advance the exact
|
|
1349
|
+
// expected cursor. Never send these through the legacy append-then-ack path.
|
|
1350
|
+
const atomicReceipt =
|
|
1351
|
+
record.pendingProviderReceipt ?? (typeof result === "string" ? result : undefined);
|
|
1352
|
+
if (
|
|
1353
|
+
atomicReceipt &&
|
|
1354
|
+
record.backend.session.captureCommandOutput &&
|
|
1355
|
+
record.backend.session.getProviderCommandOutput?.(atomicReceipt)?.command.kind ===
|
|
1356
|
+
"modal-router-v1"
|
|
1357
|
+
) {
|
|
1358
|
+
record.pendingProviderReceipt = atomicReceipt;
|
|
1359
|
+
try {
|
|
1360
|
+
if (await record.backend.session.captureCommandOutput(atomicReceipt)) {
|
|
1361
|
+
delete record.pendingProviderReceipt;
|
|
1362
|
+
return;
|
|
1363
|
+
}
|
|
1364
|
+
} catch (error) {
|
|
1365
|
+
throw new RoutingMutationOutcomeUnknownError(
|
|
1366
|
+
"captureProcessOutput",
|
|
1367
|
+
"Provider output atomic capture remains pending; the provider operation was not replayed",
|
|
1368
|
+
{ cause: error, retainedProcess: record.process },
|
|
1369
|
+
);
|
|
1370
|
+
}
|
|
1371
|
+
delete record.pendingProviderReceipt;
|
|
1372
|
+
}
|
|
1279
1373
|
const providerPage = record.backend.session.getProviderCommandOutput?.(result);
|
|
1280
1374
|
const structured =
|
|
1281
1375
|
result && typeof result === "object"
|
|
@@ -1363,17 +1457,30 @@ export class RoutingSandboxSession implements RoutableBackendSession {
|
|
|
1363
1457
|
op: string,
|
|
1364
1458
|
mutatesWorkspace: boolean,
|
|
1365
1459
|
fn: (session: RoutableBackendSession, backend: ResolvedActiveBackend) => Promise<T>,
|
|
1460
|
+
supervisionEligible = false,
|
|
1366
1461
|
): Promise<T> {
|
|
1367
1462
|
const firstOperationObserver = this.deps.onFirstOperation;
|
|
1368
1463
|
if (this.firstOperationClaimed || !firstOperationObserver) {
|
|
1369
|
-
return await this.dispatchWithRetries(
|
|
1464
|
+
return await this.dispatchWithRetries(
|
|
1465
|
+
op,
|
|
1466
|
+
mutatesWorkspace,
|
|
1467
|
+
fn,
|
|
1468
|
+
undefined,
|
|
1469
|
+
supervisionEligible,
|
|
1470
|
+
);
|
|
1370
1471
|
}
|
|
1371
1472
|
this.firstOperationClaimed = true;
|
|
1372
1473
|
const startedAt = performance.now();
|
|
1373
1474
|
const timing: RoutingSandboxFirstOperationTiming = { phases: {} };
|
|
1374
1475
|
let outcome: RoutingSandboxPhaseOutcome = "failed";
|
|
1375
1476
|
try {
|
|
1376
|
-
const result = await this.dispatchWithRetries(
|
|
1477
|
+
const result = await this.dispatchWithRetries(
|
|
1478
|
+
op,
|
|
1479
|
+
mutatesWorkspace,
|
|
1480
|
+
fn,
|
|
1481
|
+
timing,
|
|
1482
|
+
supervisionEligible,
|
|
1483
|
+
);
|
|
1377
1484
|
outcome = "completed";
|
|
1378
1485
|
return result;
|
|
1379
1486
|
} finally {
|
|
@@ -1390,11 +1497,34 @@ export class RoutingSandboxSession implements RoutableBackendSession {
|
|
|
1390
1497
|
}
|
|
1391
1498
|
}
|
|
1392
1499
|
|
|
1500
|
+
/** Classify loss on the copied backend, including read-only preflight. This
|
|
1501
|
+
* path never admits work or retries the failed operation on a new route. */
|
|
1502
|
+
private async throwProviderError(
|
|
1503
|
+
op: string,
|
|
1504
|
+
backend: ResolvedActiveBackend,
|
|
1505
|
+
error: unknown,
|
|
1506
|
+
): Promise<never> {
|
|
1507
|
+
if (!isFenceError(error) && backend.sandboxId === null && this.deps.onDefaultBackendError) {
|
|
1508
|
+
const loss = await this.deps.onDefaultBackendError({
|
|
1509
|
+
op,
|
|
1510
|
+
error,
|
|
1511
|
+
kind: backend.kind,
|
|
1512
|
+
backend,
|
|
1513
|
+
});
|
|
1514
|
+
if (loss) {
|
|
1515
|
+
this.invalidate(backend);
|
|
1516
|
+
throw new RoutingBackendRecoveryRequiredError(op, loss.leaseEpoch, loss.recovery);
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
throw error;
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1393
1522
|
private async dispatchWithRetries<T>(
|
|
1394
1523
|
op: string,
|
|
1395
1524
|
mutatesWorkspace: boolean,
|
|
1396
1525
|
fn: (session: RoutableBackendSession, backend: ResolvedActiveBackend) => Promise<T>,
|
|
1397
1526
|
firstOperationTiming?: RoutingSandboxFirstOperationTiming,
|
|
1527
|
+
supervisionEligible = false,
|
|
1398
1528
|
): Promise<T> {
|
|
1399
1529
|
let attempt = 0;
|
|
1400
1530
|
let lastError: unknown;
|
|
@@ -1416,7 +1546,35 @@ export class RoutingSandboxSession implements RoutableBackendSession {
|
|
|
1416
1546
|
// Admission failures are NOT provider fence errors and must never enter
|
|
1417
1547
|
// the retry/rebind loop. If this exact route cannot advance its durable
|
|
1418
1548
|
// mutation generation, fail before the provider sees the operation.
|
|
1549
|
+
const supervisionReady =
|
|
1550
|
+
supervisionEligible &&
|
|
1551
|
+
backend.kind === "modal" &&
|
|
1552
|
+
((await this.deps.providerSupervisionReady?.()) ?? false);
|
|
1553
|
+
let verifiedSupervision: { sandboxId: string; taskId: string } | undefined;
|
|
1554
|
+
if (supervisionReady) {
|
|
1555
|
+
if (
|
|
1556
|
+
!backend.session.verifyCommandSupervisionCapability ||
|
|
1557
|
+
!this.deps.afterMutation ||
|
|
1558
|
+
!this.deps.providerCommandPersistence
|
|
1559
|
+
)
|
|
1560
|
+
throw new Error(
|
|
1561
|
+
"Supervised command requires exact-instance verification and durable reservation wiring",
|
|
1562
|
+
);
|
|
1563
|
+
try {
|
|
1564
|
+
verifiedSupervision = await backend.session.verifyCommandSupervisionCapability();
|
|
1565
|
+
} catch (error) {
|
|
1566
|
+
// A missing helper is merely an incompatible instance. Only the
|
|
1567
|
+
// existing sandbox-scoped classifier may retire a missing provider.
|
|
1568
|
+
return await this.throwProviderError(op, backend, error);
|
|
1569
|
+
}
|
|
1570
|
+
if (
|
|
1571
|
+
backend.providerInstanceId &&
|
|
1572
|
+
verifiedSupervision.sandboxId !== backend.providerInstanceId
|
|
1573
|
+
)
|
|
1574
|
+
throw new Error("Supervision capability does not match the admitted provider instance");
|
|
1575
|
+
}
|
|
1419
1576
|
let admission: unknown;
|
|
1577
|
+
let reservedProcess: RoutingRetainedProcess | undefined;
|
|
1420
1578
|
if (mutatesWorkspace && this.deps.beforeMutation) {
|
|
1421
1579
|
const admissionStartedAt = performance.now();
|
|
1422
1580
|
let admissionWaitMs = 0;
|
|
@@ -1425,19 +1583,16 @@ export class RoutingSandboxSession implements RoutableBackendSession {
|
|
|
1425
1583
|
admission = await this.deps.beforeMutation({
|
|
1426
1584
|
op,
|
|
1427
1585
|
backend,
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
},
|
|
1439
|
-
}
|
|
1440
|
-
: {}),
|
|
1586
|
+
onCaptureWait: (observation: RoutingSandboxWaitObservation) => {
|
|
1587
|
+
admissionWaitMs += Math.max(0, observation.durationMs);
|
|
1588
|
+
recordFirstOperationPhase(
|
|
1589
|
+
firstOperationTiming,
|
|
1590
|
+
"snapshotWait",
|
|
1591
|
+
observation.durationMs,
|
|
1592
|
+
observation.outcome,
|
|
1593
|
+
);
|
|
1594
|
+
this.observeCaptureWait(op, backend, "admission", observation);
|
|
1595
|
+
},
|
|
1441
1596
|
});
|
|
1442
1597
|
admissionOutcome = "completed";
|
|
1443
1598
|
} finally {
|
|
@@ -1460,19 +1615,64 @@ export class RoutingSandboxSession implements RoutableBackendSession {
|
|
|
1460
1615
|
backend,
|
|
1461
1616
|
() =>
|
|
1462
1617
|
withProviderCommandHandle(this.deps.providerCommandHandle?.(admission), () =>
|
|
1463
|
-
|
|
1618
|
+
withCommandSupervisionReady(supervisionReady, () =>
|
|
1619
|
+
withSupervisedLaunchReservation(
|
|
1620
|
+
{
|
|
1621
|
+
reserve: async (command) => {
|
|
1622
|
+
const handle = this.deps.providerCommandHandle?.(admission);
|
|
1623
|
+
if (!supervisionReady || !handle || reservedProcess || !command.supervision)
|
|
1624
|
+
throw new Error("Invalid supervised launch reservation");
|
|
1625
|
+
if (
|
|
1626
|
+
command.sandboxId !== verifiedSupervision?.sandboxId ||
|
|
1627
|
+
command.taskId !== verifiedSupervision.taskId
|
|
1628
|
+
)
|
|
1629
|
+
throw new Error(
|
|
1630
|
+
"Modal task changed after supervision capability verification",
|
|
1631
|
+
);
|
|
1632
|
+
const process = {
|
|
1633
|
+
id: crypto.randomUUID(),
|
|
1634
|
+
providerSessionId: handle,
|
|
1635
|
+
providerCommand: command,
|
|
1636
|
+
};
|
|
1637
|
+
reservedProcess = process;
|
|
1638
|
+
const reservationResult = await this.deps.afterMutation!({
|
|
1639
|
+
op,
|
|
1640
|
+
backend,
|
|
1641
|
+
admission,
|
|
1642
|
+
outcome: "resolved",
|
|
1643
|
+
retainedProcess: process,
|
|
1644
|
+
});
|
|
1645
|
+
// Even a durable-but-authority-rejected reservation must
|
|
1646
|
+
// never dispatch user work. Its exact row remains recoverable.
|
|
1647
|
+
if (reservationResult)
|
|
1648
|
+
throw new RoutingMutationOutcomeUnknownError(
|
|
1649
|
+
op,
|
|
1650
|
+
"Supervised launch reservation lost authority before dispatch",
|
|
1651
|
+
{ retainedProcess: process },
|
|
1652
|
+
);
|
|
1653
|
+
const persisted =
|
|
1654
|
+
await this.deps.providerCommandPersistence!(process).load();
|
|
1655
|
+
if (!persisted || !isDeepStrictEqual(persisted, command))
|
|
1656
|
+
throw new Error(
|
|
1657
|
+
"Supervised launch reservation did not retain the exact invocation",
|
|
1658
|
+
);
|
|
1659
|
+
this.registerRetainedProcess(process, backend).durable = true;
|
|
1660
|
+
},
|
|
1661
|
+
},
|
|
1662
|
+
() => fn(backend.session, backend),
|
|
1663
|
+
),
|
|
1664
|
+
),
|
|
1464
1665
|
),
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
: undefined,
|
|
1666
|
+
(observation) => {
|
|
1667
|
+
providerWaitMs += Math.max(0, observation.durationMs);
|
|
1668
|
+
recordFirstOperationPhase(
|
|
1669
|
+
firstOperationTiming,
|
|
1670
|
+
"snapshotWait",
|
|
1671
|
+
observation.durationMs,
|
|
1672
|
+
observation.outcome,
|
|
1673
|
+
);
|
|
1674
|
+
this.observeCaptureWait(op, backend, "provider", observation);
|
|
1675
|
+
},
|
|
1476
1676
|
);
|
|
1477
1677
|
providerOutcome = "completed";
|
|
1478
1678
|
} finally {
|
|
@@ -1484,6 +1684,34 @@ export class RoutingSandboxSession implements RoutableBackendSession {
|
|
|
1484
1684
|
);
|
|
1485
1685
|
}
|
|
1486
1686
|
} catch (error) {
|
|
1687
|
+
if (reservedProcess) {
|
|
1688
|
+
if (
|
|
1689
|
+
error instanceof ProviderCommandStartRejectedError &&
|
|
1690
|
+
reservedProcess.providerCommand?.kind === "modal-router-v1"
|
|
1691
|
+
) {
|
|
1692
|
+
const persistence = this.deps.providerCommandPersistence?.(reservedProcess);
|
|
1693
|
+
if (persistence?.rejectSupervisedLaunch) {
|
|
1694
|
+
try {
|
|
1695
|
+
await persistence.rejectSupervisedLaunch(reservedProcess.providerCommand);
|
|
1696
|
+
} catch (cause) {
|
|
1697
|
+
throw new RoutingMutationOutcomeUnknownError(
|
|
1698
|
+
op,
|
|
1699
|
+
"Never-started provider rejection could not settle its exact reservation",
|
|
1700
|
+
{ cause, retainedProcess: reservedProcess },
|
|
1701
|
+
);
|
|
1702
|
+
}
|
|
1703
|
+
this.retainedProcesses.delete(reservedProcess.providerSessionId);
|
|
1704
|
+
throw error;
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
// A retained pre-dispatch identity is never erased or converted into
|
|
1708
|
+
// ordinary rejected-admission settlement. Reconciliation owns it.
|
|
1709
|
+
throw new RoutingMutationOutcomeUnknownError(
|
|
1710
|
+
op,
|
|
1711
|
+
"Reserved supervised launch did not return; exact invocation retained and not replayed",
|
|
1712
|
+
{ cause: error, retainedProcess: reservedProcess },
|
|
1713
|
+
);
|
|
1714
|
+
}
|
|
1487
1715
|
const partialMutation = error instanceof ChannelAPartialMutationError;
|
|
1488
1716
|
if (mutatesWorkspace && this.deps.afterMutation) {
|
|
1489
1717
|
const settlementStartedAt = performance.now();
|
|
@@ -1522,23 +1750,7 @@ export class RoutingSandboxSession implements RoutableBackendSession {
|
|
|
1522
1750
|
);
|
|
1523
1751
|
}
|
|
1524
1752
|
if (!isFenceError(error)) {
|
|
1525
|
-
|
|
1526
|
-
const loss = await this.deps.onDefaultBackendError({
|
|
1527
|
-
op,
|
|
1528
|
-
error,
|
|
1529
|
-
kind: backend.kind,
|
|
1530
|
-
backend,
|
|
1531
|
-
});
|
|
1532
|
-
if (loss) {
|
|
1533
|
-
// Never replay an operation after provider disappearance. Even a
|
|
1534
|
-
// read may race a route change, and a mutation's provider outcome
|
|
1535
|
-
// is ambiguous. The next independently-admitted call observes the
|
|
1536
|
-
// advanced epoch and elected recovery state.
|
|
1537
|
-
this.invalidate(backend);
|
|
1538
|
-
throw new RoutingBackendRecoveryRequiredError(op, loss.leaseEpoch, loss.recovery);
|
|
1539
|
-
}
|
|
1540
|
-
}
|
|
1541
|
-
throw error;
|
|
1753
|
+
return await this.throwProviderError(op, backend, error);
|
|
1542
1754
|
}
|
|
1543
1755
|
this.invalidate(backend);
|
|
1544
1756
|
if (mutatesWorkspace) {
|
|
@@ -1563,18 +1775,21 @@ export class RoutingSandboxSession implements RoutableBackendSession {
|
|
|
1563
1775
|
const providerCommand =
|
|
1564
1776
|
yieldedSessionId === null ? null : backend.session.getProviderCommand?.(yieldedSessionId);
|
|
1565
1777
|
const retainedProcess =
|
|
1566
|
-
|
|
1778
|
+
reservedProcess ??
|
|
1779
|
+
(yieldedSessionId === null
|
|
1567
1780
|
? undefined
|
|
1568
1781
|
: {
|
|
1569
1782
|
id: crypto.randomUUID(),
|
|
1570
1783
|
providerSessionId: yieldedSessionId,
|
|
1571
1784
|
...(providerCommand ? { providerCommand } : {}),
|
|
1572
|
-
};
|
|
1785
|
+
});
|
|
1573
1786
|
const retainedRecord = retainedProcess
|
|
1574
|
-
?
|
|
1787
|
+
? reservedProcess
|
|
1788
|
+
? this.retainedProcess(reservedProcess.providerSessionId)
|
|
1789
|
+
: this.registerRetainedProcess(retainedProcess, backend)
|
|
1575
1790
|
: null;
|
|
1576
1791
|
|
|
1577
|
-
if (mutatesWorkspace && this.deps.afterMutation) {
|
|
1792
|
+
if (mutatesWorkspace && this.deps.afterMutation && !reservedProcess) {
|
|
1578
1793
|
const settlement: PendingParentPromotion = {
|
|
1579
1794
|
op,
|
|
1580
1795
|
backend,
|
|
@@ -1639,6 +1854,17 @@ export class RoutingSandboxSession implements RoutableBackendSession {
|
|
|
1639
1854
|
// let process-aware methods use the copied backend identity.
|
|
1640
1855
|
if (retainedRecord) {
|
|
1641
1856
|
await this.captureRetainedOutput(retainedRecord, result);
|
|
1857
|
+
try {
|
|
1858
|
+
await backend.session.releaseSupervisedCommand?.(
|
|
1859
|
+
retainedRecord.process.providerSessionId,
|
|
1860
|
+
);
|
|
1861
|
+
} catch (cause) {
|
|
1862
|
+
throw new RoutingMutationOutcomeUnknownError(
|
|
1863
|
+
op,
|
|
1864
|
+
"Supervisor release is unresolved after durable retention; the exact command remains tracked and was not replayed",
|
|
1865
|
+
{ cause, retainedProcess: retainedRecord.process },
|
|
1866
|
+
);
|
|
1867
|
+
}
|
|
1642
1868
|
return result;
|
|
1643
1869
|
}
|
|
1644
1870
|
|
|
@@ -1681,6 +1907,25 @@ export class RoutingSandboxSession implements RoutableBackendSession {
|
|
|
1681
1907
|
throw lastError ?? new Error(`routing op "${op}" exhausted fence retries`);
|
|
1682
1908
|
}
|
|
1683
1909
|
|
|
1910
|
+
private observeCaptureWait(
|
|
1911
|
+
op: string,
|
|
1912
|
+
backend: ResolvedActiveBackend,
|
|
1913
|
+
captureWaitStage: "admission" | "provider",
|
|
1914
|
+
observation: RoutingSandboxWaitObservation,
|
|
1915
|
+
): void {
|
|
1916
|
+
try {
|
|
1917
|
+
this.deps.onCaptureWait?.({
|
|
1918
|
+
backend: backend.kind,
|
|
1919
|
+
op,
|
|
1920
|
+
outcome: observation.outcome === "completed" ? "ok" : "failed",
|
|
1921
|
+
durationMs: observation.durationMs,
|
|
1922
|
+
captureWaitStage,
|
|
1923
|
+
});
|
|
1924
|
+
} catch {
|
|
1925
|
+
// Capture admission and provider execution never depend on telemetry.
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
|
|
1684
1929
|
private async invokeProviderOperation<T>(
|
|
1685
1930
|
op: string,
|
|
1686
1931
|
backend: ResolvedActiveBackend,
|
|
@@ -1689,11 +1934,13 @@ export class RoutingSandboxSession implements RoutableBackendSession {
|
|
|
1689
1934
|
): Promise<T> {
|
|
1690
1935
|
const startedAt = performance.now();
|
|
1691
1936
|
let outcome: RoutingSandboxOperationObservation["outcome"] = "failed";
|
|
1937
|
+
let materializationFailureReason: MaterializationFailureReason | undefined;
|
|
1692
1938
|
try {
|
|
1693
1939
|
const result = await withSandboxProviderOperation(backend.session, fn, onCaptureWait);
|
|
1694
1940
|
outcome = "ok";
|
|
1695
1941
|
return result;
|
|
1696
1942
|
} catch (error) {
|
|
1943
|
+
materializationFailureReason = materializationVerificationDiagnostic(error)?.reason;
|
|
1697
1944
|
if (
|
|
1698
1945
|
(op === "readFile" || op === "listDir" || op === "pathExists" || op === "viewImage") &&
|
|
1699
1946
|
isDefinitePathNotFoundError(error)
|
|
@@ -1708,6 +1955,7 @@ export class RoutingSandboxSession implements RoutableBackendSession {
|
|
|
1708
1955
|
op,
|
|
1709
1956
|
outcome,
|
|
1710
1957
|
durationMs: Math.max(0, performance.now() - startedAt),
|
|
1958
|
+
...(materializationFailureReason ? { materializationFailureReason } : {}),
|
|
1711
1959
|
});
|
|
1712
1960
|
} catch {
|
|
1713
1961
|
// Telemetry is never part of provider or durable-settlement authority.
|
|
@@ -1746,30 +1994,40 @@ export class RoutingSandboxSession implements RoutableBackendSession {
|
|
|
1746
1994
|
// degrades via the natural fallback or RoutingUnsupportedError.
|
|
1747
1995
|
|
|
1748
1996
|
async exec(args: unknown): Promise<unknown> {
|
|
1749
|
-
return this.dispatch(
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1997
|
+
return this.dispatch(
|
|
1998
|
+
"exec",
|
|
1999
|
+
true,
|
|
2000
|
+
async (s) => {
|
|
2001
|
+
if (s.exec) {
|
|
2002
|
+
return s.exec(args);
|
|
2003
|
+
}
|
|
2004
|
+
// Some backends (selfhosted) only expose exec; others only execCommand.
|
|
2005
|
+
if (s.execCommand) {
|
|
2006
|
+
return structuredExecResultFromBanner(await s.execCommand(args));
|
|
2007
|
+
}
|
|
2008
|
+
throw new RoutingUnsupportedError("exec", this.cached?.kind ?? "unknown");
|
|
2009
|
+
},
|
|
2010
|
+
eligibleForSupervision(args),
|
|
2011
|
+
);
|
|
1759
2012
|
}
|
|
1760
2013
|
|
|
1761
2014
|
async execCommand(args: unknown): Promise<string> {
|
|
1762
2015
|
try {
|
|
1763
|
-
return await this.dispatch(
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
2016
|
+
return await this.dispatch(
|
|
2017
|
+
"execCommand",
|
|
2018
|
+
true,
|
|
2019
|
+
async (s) => {
|
|
2020
|
+
if (s.execCommand) {
|
|
2021
|
+
return s.execCommand(args);
|
|
2022
|
+
}
|
|
2023
|
+
if (s.exec) {
|
|
2024
|
+
const r = (await s.exec(args)) as { stdout?: string; output?: string };
|
|
2025
|
+
return r.stdout ?? r.output ?? "";
|
|
2026
|
+
}
|
|
2027
|
+
throw new RoutingUnsupportedError("execCommand", this.cached?.kind ?? "unknown");
|
|
2028
|
+
},
|
|
2029
|
+
eligibleForSupervision(args),
|
|
2030
|
+
);
|
|
1773
2031
|
} catch (error) {
|
|
1774
2032
|
// Render a terminal selfhosted fault as the tool's result (four fields, correct
|
|
1775
2033
|
// verdict) instead of letting the SDK mislabel it "Please try again".
|
|
@@ -1849,7 +2107,10 @@ export class RoutingSandboxSession implements RoutableBackendSession {
|
|
|
1849
2107
|
await this.writeStdinForProcessControl({
|
|
1850
2108
|
sessionId: record.process.providerSessionId,
|
|
1851
2109
|
chars: "",
|
|
1852
|
-
|
|
2110
|
+
// A near-zero provider long-poll can expire before even an already
|
|
2111
|
+
// completed command's terminal batch is delivered. Use the ordinary
|
|
2112
|
+
// bounded command-read window; never infer completion from silence.
|
|
2113
|
+
yieldTimeMs: 250,
|
|
1853
2114
|
});
|
|
1854
2115
|
return true;
|
|
1855
2116
|
}
|
|
@@ -1917,6 +2178,23 @@ export class RoutingSandboxSession implements RoutableBackendSession {
|
|
|
1917
2178
|
return await this.dispatchProcessControl(args);
|
|
1918
2179
|
}
|
|
1919
2180
|
|
|
2181
|
+
async cancelSupervisedCommand(
|
|
2182
|
+
providerSessionId: number,
|
|
2183
|
+
reason: "provider_deadline" | "explicit_stop",
|
|
2184
|
+
): Promise<boolean> {
|
|
2185
|
+
const record = this.retainedProcesses.get(providerSessionId);
|
|
2186
|
+
if (
|
|
2187
|
+
!record ||
|
|
2188
|
+
record.process.providerCommand?.kind !== "modal-router-v1" ||
|
|
2189
|
+
!record.process.providerCommand.supervision
|
|
2190
|
+
)
|
|
2191
|
+
return false;
|
|
2192
|
+
await this.ensureParentPromotion(record);
|
|
2193
|
+
if (!record.backend.session.cancelSupervisedCommand)
|
|
2194
|
+
throw new Error("Supervised command control is unavailable");
|
|
2195
|
+
return await record.backend.session.cancelSupervisedCommand(providerSessionId, reason);
|
|
2196
|
+
}
|
|
2197
|
+
|
|
1920
2198
|
/** Empty-input eager reads belong to the model's foreground wait, while
|
|
1921
2199
|
* cancellation/drain reads use the non-observing control method above. */
|
|
1922
2200
|
async writeStdinForProcessRead(args: unknown): Promise<string> {
|
|
@@ -1935,6 +2213,13 @@ export class RoutingSandboxSession implements RoutableBackendSession {
|
|
|
1935
2213
|
const exactProviderSessionId = positiveProviderSessionId(providerSessionId);
|
|
1936
2214
|
if (exactProviderSessionId === null) throw new RoutingRetainedProcessNotFoundError(-1);
|
|
1937
2215
|
const record = this.retainedProcess(exactProviderSessionId);
|
|
2216
|
+
if (
|
|
2217
|
+
record.process.providerCommand?.kind === "modal-router-v1" &&
|
|
2218
|
+
record.process.providerCommand.supervision
|
|
2219
|
+
)
|
|
2220
|
+
throw new Error(
|
|
2221
|
+
"Legacy process helpers are unavailable for a supervised command; use its native control protocol",
|
|
2222
|
+
);
|
|
1938
2223
|
const priorTerminal = await this.flushPendingProcessMutation(record);
|
|
1939
2224
|
if (priorTerminal !== null) return priorTerminal;
|
|
1940
2225
|
if (record.pendingTerminal) {
|
|
@@ -2289,6 +2574,9 @@ async function assertProviderCanSeeMaterializedPath(
|
|
|
2289
2574
|
session: RoutableBackendSession,
|
|
2290
2575
|
path: string,
|
|
2291
2576
|
): Promise<void> {
|
|
2577
|
+
if (session.verifyMaterializedPath) {
|
|
2578
|
+
return session.verifyMaterializedPath(path, providerManifestRoot(session) ?? "/workspace");
|
|
2579
|
+
}
|
|
2292
2580
|
const command = `test -e ${shellSingleQuote(path)} && printf %s ${shellSingleQuote(
|
|
2293
2581
|
MATERIALIZED_PATH_MARKER,
|
|
2294
2582
|
)}`;
|
|
@@ -2300,18 +2588,68 @@ async function assertProviderCanSeeMaterializedPath(
|
|
|
2300
2588
|
tty: false,
|
|
2301
2589
|
};
|
|
2302
2590
|
let output: string;
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
} else if (session.exec) {
|
|
2306
|
-
output = formatExecResult(await session.exec(args));
|
|
2307
|
-
} else {
|
|
2591
|
+
let result: unknown;
|
|
2592
|
+
if (!session.execCommand && !session.exec) {
|
|
2308
2593
|
throw new RoutingUnsupportedError("materializeEntry.verify", "unknown");
|
|
2309
2594
|
}
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2595
|
+
try {
|
|
2596
|
+
result = session.execCommand ? await session.execCommand(args) : await session.exec!(args);
|
|
2597
|
+
} catch (cause) {
|
|
2598
|
+
retainMaterializationVerificationDiagnostic(cause, {
|
|
2599
|
+
reason: "command_error",
|
|
2600
|
+
path,
|
|
2601
|
+
workdir: args.workdir,
|
|
2602
|
+
command,
|
|
2603
|
+
output: null,
|
|
2604
|
+
exitCode: null,
|
|
2605
|
+
providerSessionId: null,
|
|
2606
|
+
causeMessage: cause instanceof Error ? cause.message : String(cause),
|
|
2607
|
+
});
|
|
2608
|
+
throw cause;
|
|
2314
2609
|
}
|
|
2610
|
+
try {
|
|
2611
|
+
output = formatExecResult(result);
|
|
2612
|
+
} catch (cause) {
|
|
2613
|
+
const record = result && typeof result === "object" ? (result as Record<string, unknown>) : {};
|
|
2614
|
+
const returnedOutput = [record.output, record.stderr, record.stdout]
|
|
2615
|
+
.filter((value): value is string => typeof value === "string")
|
|
2616
|
+
.join("\n");
|
|
2617
|
+
throw new SandboxMaterializationVerificationError({
|
|
2618
|
+
reason: "invalid_response",
|
|
2619
|
+
path,
|
|
2620
|
+
workdir: args.workdir,
|
|
2621
|
+
command,
|
|
2622
|
+
output: returnedOutput || null,
|
|
2623
|
+
exitCode: null,
|
|
2624
|
+
providerSessionId: null,
|
|
2625
|
+
causeMessage: cause instanceof Error ? cause.message : String(cause),
|
|
2626
|
+
});
|
|
2627
|
+
}
|
|
2628
|
+
const status = parseExecResponseBanner(output);
|
|
2629
|
+
const markerPresent = stripExecBanner(output).includes(MATERIALIZED_PATH_MARKER);
|
|
2630
|
+
if (
|
|
2631
|
+
markerPresent &&
|
|
2632
|
+
(status.kind === "absent" || (status.kind === "exited" && status.exitCode === 0))
|
|
2633
|
+
) {
|
|
2634
|
+
return;
|
|
2635
|
+
}
|
|
2636
|
+
const reason: MaterializationFailureReason =
|
|
2637
|
+
status.kind === "running"
|
|
2638
|
+
? "command_pending"
|
|
2639
|
+
: status.kind === "exited" && status.exitCode === 1
|
|
2640
|
+
? "path_not_visible"
|
|
2641
|
+
: status.kind === "exited" && status.exitCode !== 0
|
|
2642
|
+
? "command_failed"
|
|
2643
|
+
: "invalid_response";
|
|
2644
|
+
throw new SandboxMaterializationVerificationError({
|
|
2645
|
+
reason,
|
|
2646
|
+
path,
|
|
2647
|
+
workdir: args.workdir,
|
|
2648
|
+
command,
|
|
2649
|
+
output,
|
|
2650
|
+
exitCode: status.kind === "exited" ? status.exitCode : null,
|
|
2651
|
+
providerSessionId: status.kind === "running" ? status.sessionId : null,
|
|
2652
|
+
});
|
|
2315
2653
|
}
|
|
2316
2654
|
|
|
2317
2655
|
function providerManifestRoot(session: RoutableBackendSession): string | null {
|