@tangle-network/sandbox 0.10.3 → 0.10.5-develop.20260713235503.8f2cefc
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/README.md +13 -0
- package/dist/agent/index.d.ts +3 -3
- package/dist/{client-Cr39zlgv.d.ts → client-C-4NcZoa.d.ts} +7 -3
- package/dist/{client-Df1K1d_Q.js → client-CL_cbjU4.js} +106 -54
- package/dist/collaboration/index.js +1 -1
- package/dist/{collaboration-QZXrXdIb.js → collaboration-CcZE2xMl.js} +1 -1
- package/dist/core.d.ts +4 -4
- package/dist/core.js +3 -3
- package/dist/{errors--P_nbLzM.d.ts → errors-CNCz3Ms9.d.ts} +1 -1
- package/dist/{errors-DSz87Rkk.js → errors-wd266B9Q.js} +3 -3
- package/dist/{index-DzEUjav-.d.ts → index-DFJiN98I.d.ts} +2 -2
- package/dist/index.d.ts +6 -6
- package/dist/index.js +6 -6
- package/dist/intelligence/index.js +1 -1
- package/dist/{runtime-api-CYA2DUQw.js → runtime-api-CaXI7Kl2.js} +16 -2
- package/dist/runtime.d.ts +9 -6
- package/dist/runtime.js +5 -3
- package/dist/{sandbox-BhYmj7L3.d.ts → sandbox-BvNEOnIl.d.ts} +62 -10
- package/dist/{sandbox-CxBHIBV1.js → sandbox-CIHssZCH.js} +205 -52
- package/dist/tangle/index.d.ts +1 -1
- package/dist/tangle/index.js +1 -1
- package/dist/{tangle-BdRRHMpX.js → tangle-C-5OvIj8.js} +2 -2
- package/dist/{types-Cmpv-ox_.d.ts → types-yNRVRww-.d.ts} +155 -24
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as EventStreamOptions, A as CommitTaskSessionOptions, B as CreateTaskSessionOptions,
|
|
1
|
+
import { $ as EventStreamOptions, $r as SessionListOptions, A as CommitTaskSessionOptions, Ai as WaitForOptions, An as RestoreSnapshotOptions, Ar as SandboxStatus, B as CreateTaskSessionOptions, Bn as SSHCommandDescriptor, Br as ScopedToken, Bt as InstalledTool, D as CodeLanguage, Dr as SandboxRuntimeHealth, Dt as GitDiff, E as CodeExecutionResult, Fr as SandboxTraceBundle, Gn as SandboxEvent, Gt as IntelligenceReportWindow, Hr as SearchMatch, Ht as IntelligenceReportBudget, In as RolloutStartResult, Jr as SentSessionMessage, Kr as SendSessionMessageOptions, Kt as JsonValue, Mi as WriteFileOptions, Mn as Rollout, Ni as WriteManyFile, Nr as SandboxTerminalManager, Ot as GitStatus, Pi as WriteManyOptions, Pn as RolloutOptions, Pt as GpuLeaseManager, Qr as SessionInfo, Qt as MintScopedTokenOptions, Rr as SandboxTraceOptions, Si as TeePublicKeyResponse, Sr as SandboxPortBinding, T as CodeExecutionOptions, Ti as TurnDriveResult, Tr as SandboxResourceUsage, Tt as GitCommit, U as DispatchPromptOptions, Un as SandboxConnection, Ur as SearchOptions, Ut as IntelligenceReportCompareTo, Vn as SSHCredentials, Vt as IntelligenceReport, W as DispatchedSession, Xr as SessionEventStreamOptions, Y as DriverInfo, Z as EgressManager, Zr as SessionForkOptions, _n as ProvisionEvent, ai as SnapshotResult, bi as TeeAttestationResponse, ct as FileSystem, di as TaskOptions, ei as SessionMessage, et as ExecOptions, fi as TaskResult, ft as FileWriteResult, gi as TaskSessionInfo, gn as PromptResult, hn as PromptOptions, ii as SnapshotOptions, in as PermissionsManager, j as CompletedTurnResult, ji as WaitForRolloutOptions, jn as ResumeOptions, kr as SandboxRuntimeProfileList, kt as GpuLease, l as BackendManager, mi as TaskSessionCommitResult, mn as PromptInputPart, on as PreviewLinkManager, pi as TaskSessionChanges, q as DriveTurnOptions, qr as SendSessionMessageRequest, qt as ListMessagesOptions, ri as SnapshotInfo, s as BackendConfig, si as StartupDiagnostics, tn as NetworkManager, tt as ExecResult, un as ProcessManager, ur as SandboxFleetTraceBundle, vi as TeeAttestationOptions, vn as ProvisionResult, w as BranchOptions, wt as GitBranch, yr as SandboxInfo, z as CreateSessionOptions } from "./types-yNRVRww-.js";
|
|
2
2
|
|
|
3
3
|
//#region src/mcp.d.ts
|
|
4
4
|
/**
|
|
@@ -900,6 +900,7 @@ declare class SandboxInstance {
|
|
|
900
900
|
private fsList;
|
|
901
901
|
private fsStat;
|
|
902
902
|
private fsDelete;
|
|
903
|
+
private fsRename;
|
|
903
904
|
private fsMkdir;
|
|
904
905
|
private fsExists;
|
|
905
906
|
/**
|
|
@@ -1236,8 +1237,52 @@ declare class SandboxInstance {
|
|
|
1236
1237
|
* ```
|
|
1237
1238
|
*/
|
|
1238
1239
|
branch(count: number, options?: BranchOptions): Promise<SandboxInstance[]>;
|
|
1240
|
+
/**
|
|
1241
|
+
* Speculative rollouts: branch this RUNNING sandbox into `n` copy-on-write
|
|
1242
|
+
* children, run the SAME agent turn in every child in parallel, and
|
|
1243
|
+
* optionally score each child by running `scorer.command` inside it (exit 0
|
|
1244
|
+
* passes; stdout is parsed as the numeric score — highest wins).
|
|
1245
|
+
*
|
|
1246
|
+
* Async-op shape: resolves as soon as the children exist (their turns are
|
|
1247
|
+
* still running); poll {@link getRollout} or use {@link waitForRollout} for
|
|
1248
|
+
* results. Children are first-class sandboxes the caller may promote or
|
|
1249
|
+
* delete — each occupies a concurrent-session quota slot like a create.
|
|
1250
|
+
*
|
|
1251
|
+
* @example
|
|
1252
|
+
* ```typescript
|
|
1253
|
+
* const started = await box.rollout({
|
|
1254
|
+
* n: 4,
|
|
1255
|
+
* parts: [{ type: "text", text: "make the failing test pass" }],
|
|
1256
|
+
* scorer: { type: "command", command: "npm test --silent && echo 1" },
|
|
1257
|
+
* });
|
|
1258
|
+
* const settled = await box.waitForRollout(started.rolloutId);
|
|
1259
|
+
* console.log(settled.winner, settled.results);
|
|
1260
|
+
* ```
|
|
1261
|
+
*/
|
|
1262
|
+
rollout(options: RolloutOptions): Promise<RolloutStartResult>;
|
|
1263
|
+
/**
|
|
1264
|
+
* Poll a rollout's status and per-child results.
|
|
1265
|
+
*
|
|
1266
|
+
* Failed or timed-out children appear as result entries with `error` set —
|
|
1267
|
+
* partial results are never dropped. Rollout poll-state lives in
|
|
1268
|
+
* orchestrator RAM: after an orchestrator restart this returns 404 while
|
|
1269
|
+
* the children themselves survive as first-class sandboxes.
|
|
1270
|
+
*/
|
|
1271
|
+
getRollout(rolloutId: string): Promise<Rollout>;
|
|
1272
|
+
/**
|
|
1273
|
+
* Poll {@link getRollout} until the rollout leaves `running`, then return
|
|
1274
|
+
* the terminal record — including `partial`/`failed` outcomes, whose
|
|
1275
|
+
* per-child `error` entries are results the caller inspects, not
|
|
1276
|
+
* exceptions. Throws {@link TimeoutError} on deadline or abort.
|
|
1277
|
+
*/
|
|
1278
|
+
waitForRollout(rolloutId: string, options?: WaitForRolloutOptions): Promise<Rollout>;
|
|
1239
1279
|
/**
|
|
1240
1280
|
* Stop the sandbox (keeps state for resume).
|
|
1281
|
+
*
|
|
1282
|
+
* The stop response carries the orchestrator's per-phase stop breakdown
|
|
1283
|
+
* (`stop_metering` / `container_kill` / `stop_complete` /
|
|
1284
|
+
* `stop_unaccounted`); after this resolves, {@link startupDiagnostics}
|
|
1285
|
+
* returns it (replacing the create breakdown).
|
|
1241
1286
|
*/
|
|
1242
1287
|
stop(): Promise<void>;
|
|
1243
1288
|
/**
|
|
@@ -1246,8 +1291,20 @@ declare class SandboxInstance {
|
|
|
1246
1291
|
resume(options?: ResumeOptions): Promise<void>;
|
|
1247
1292
|
/**
|
|
1248
1293
|
* Delete the sandbox permanently.
|
|
1294
|
+
*
|
|
1295
|
+
* The delete response carries the orchestrator's per-phase delete breakdown
|
|
1296
|
+
* (`container_remove` / `storage_binding_wait` / `cleanup_parallel` /
|
|
1297
|
+
* `delete_complete` / `delete_unaccounted`); after this resolves,
|
|
1298
|
+
* {@link startupDiagnostics} returns it for post-mortem reads on the
|
|
1299
|
+
* now-gone sandbox.
|
|
1249
1300
|
*/
|
|
1250
1301
|
delete(): Promise<void>;
|
|
1302
|
+
/**
|
|
1303
|
+
* Read the per-phase lifecycle breakdown off a stop/resume/delete response
|
|
1304
|
+
* body. Never throws: diagnostics are measurement data — a body that fails
|
|
1305
|
+
* to parse (older API, empty body) must not fail the lifecycle call.
|
|
1306
|
+
*/
|
|
1307
|
+
private readLifecycleDiagnostics;
|
|
1251
1308
|
/**
|
|
1252
1309
|
* keepAlive is intentionally unavailable until the API exposes timeout updates.
|
|
1253
1310
|
* @param seconds - Reserved for future support
|
|
@@ -1442,15 +1499,10 @@ declare class SandboxInstance {
|
|
|
1442
1499
|
*/
|
|
1443
1500
|
driveTurn(message: string | PromptInputPart[], opts: DriveTurnOptions): Promise<TurnDriveResult>;
|
|
1444
1501
|
/**
|
|
1445
|
-
* Mint a scoped, time-bounded JWT for
|
|
1446
|
-
*
|
|
1447
|
-
*
|
|
1448
|
-
*
|
|
1449
|
-
*
|
|
1450
|
-
* Use this to give a browser direct read access to the sandbox without
|
|
1451
|
-
* leaking the full bearer (`box.connection.authToken`). The returned
|
|
1452
|
-
* token verifies against the same sidecar middleware that already
|
|
1453
|
-
* gates ProductTokenIssuer-issued JWTs — no new sidecar surface.
|
|
1502
|
+
* Mint a scoped, time-bounded JWT for browser access (Issue #913 Gap 1).
|
|
1503
|
+
* Authority is the caller's `TANGLE_API_KEY` (sk-tan-*); signing secrets
|
|
1504
|
+
* stay server-side. Session/project scopes work with SessionGatewayClient,
|
|
1505
|
+
* while session-runtime/read-only scopes work with SandboxRuntimeClient.
|
|
1454
1506
|
*/
|
|
1455
1507
|
mintScopedToken(opts: MintScopedTokenOptions): Promise<ScopedToken>;
|
|
1456
1508
|
/** @internal — invoked by SandboxSession.status(). */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { n as normalizeSessionInfo, r as normalizeRuntimeBackendConfig, t as SandboxRuntimeApi } from "./runtime-api-
|
|
2
|
-
import { c as SandboxError, d as TimeoutError, f as ValidationError, i as NetworkError, l as ServerError, n as CapabilityError, p as parseErrorResponse, s as QuotaError, t as AuthError, u as StateError } from "./errors-
|
|
1
|
+
import { n as normalizeSessionInfo, r as normalizeRuntimeBackendConfig, t as SandboxRuntimeApi } from "./runtime-api-CaXI7Kl2.js";
|
|
2
|
+
import { c as SandboxError, d as TimeoutError, f as ValidationError, i as NetworkError, l as ServerError, n as CapabilityError, p as parseErrorResponse, s as QuotaError, t as AuthError, u as StateError } from "./errors-wd266B9Q.js";
|
|
3
3
|
import { addTokenUsage, readTokenCostUsd, readTokenUsage } from "@tangle-network/agent-core";
|
|
4
4
|
//#region src/lib/sse-parser.ts
|
|
5
5
|
/**
|
|
@@ -752,6 +752,13 @@ function isTransientSnapshotVisibilityError(error) {
|
|
|
752
752
|
return message.includes("SNAPSHOT_NOT_FOUND") || message.includes("Failed to list snapshot") || message.includes("Storage agent error (404)");
|
|
753
753
|
}
|
|
754
754
|
/**
|
|
755
|
+
* The extra separator is intentional: absolute paths produce `//`, allowing
|
|
756
|
+
* the runtime route wildcard to retain the path's leading slash.
|
|
757
|
+
*/
|
|
758
|
+
function runtimeFilesystemPath(endpoint, path) {
|
|
759
|
+
return `${endpoint}/${path}`;
|
|
760
|
+
}
|
|
761
|
+
/**
|
|
755
762
|
* Single source of truth for a run's outcome, shared by
|
|
756
763
|
* `prompt`/`task`/`_sessionResult`/turn settling. A run succeeds only when it
|
|
757
764
|
* reached a terminal event, raised no run-level error, hit no approval gate,
|
|
@@ -913,6 +920,22 @@ const WRITE_MANY_DEFAULT_PACE_MS = 150;
|
|
|
913
920
|
const WRITE_MANY_DEFAULT_MAX_RETRIES = 4;
|
|
914
921
|
const WRITE_MANY_RETRY_BASE_MS = 250;
|
|
915
922
|
const WRITE_MANY_RETRY_MAX_MS = 2e3;
|
|
923
|
+
function parseTextFileReadResponse(body) {
|
|
924
|
+
let payload;
|
|
925
|
+
try {
|
|
926
|
+
payload = JSON.parse(body);
|
|
927
|
+
} catch {
|
|
928
|
+
throw new ServerError("Runtime returned invalid JSON for file read", 502, {
|
|
929
|
+
origin: "runtime",
|
|
930
|
+
endpoint: "/files/read"
|
|
931
|
+
});
|
|
932
|
+
}
|
|
933
|
+
if (typeof payload !== "object" || payload === null || !("success" in payload) || payload.success !== true || !("data" in payload) || typeof payload.data !== "object" || payload.data === null || !("content" in payload.data) || typeof payload.data.content !== "string" || !("encoding" in payload.data) || payload.data.encoding !== "utf8") throw new ServerError("Runtime returned a malformed text file response", 502, {
|
|
934
|
+
origin: "runtime",
|
|
935
|
+
endpoint: "/files/read"
|
|
936
|
+
});
|
|
937
|
+
return payload.data.content;
|
|
938
|
+
}
|
|
916
939
|
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
917
940
|
/** Transient file-write failures worth retrying: rate-limit (quota), 5xx,
|
|
918
941
|
* transport (network), and request timeouts. A non-transient error (bad path,
|
|
@@ -1324,18 +1347,20 @@ var SandboxInstance = class SandboxInstance {
|
|
|
1324
1347
|
*/
|
|
1325
1348
|
async read(path, options) {
|
|
1326
1349
|
await this.ensureRunning();
|
|
1327
|
-
const headers = {};
|
|
1328
|
-
if (options?.sessionId) headers["X-Session-Id"] = options.sessionId;
|
|
1350
|
+
const headers = options?.sessionId ? { "X-Session-Id": options.sessionId } : void 0;
|
|
1329
1351
|
const response = await this.runtimeFetch("/files/read", {
|
|
1330
1352
|
method: "POST",
|
|
1331
1353
|
headers,
|
|
1332
|
-
body: JSON.stringify({
|
|
1354
|
+
body: JSON.stringify({
|
|
1355
|
+
path,
|
|
1356
|
+
encoding: "utf8"
|
|
1357
|
+
})
|
|
1333
1358
|
});
|
|
1334
1359
|
if (!response.ok) {
|
|
1335
1360
|
const body = await response.text();
|
|
1336
1361
|
throw parseErrorResponse(response.status, body, void 0, response.headers);
|
|
1337
1362
|
}
|
|
1338
|
-
return (await response.
|
|
1363
|
+
return parseTextFileReadResponse(await response.text());
|
|
1339
1364
|
}
|
|
1340
1365
|
/**
|
|
1341
1366
|
* Write content to a file in the sandbox.
|
|
@@ -1537,8 +1562,8 @@ var SandboxInstance = class SandboxInstance {
|
|
|
1537
1562
|
* `error` + `done` so callers never see a thrown generator.
|
|
1538
1563
|
*/
|
|
1539
1564
|
async *streamPromptInner(message, options) {
|
|
1565
|
+
if (options?.lastEventId && !options.executionId) throw new ValidationError("lastEventId requires executionId so the SDK can replay the correct execution", { executionId: "Required when lastEventId is set" });
|
|
1540
1566
|
await this.ensureRunning();
|
|
1541
|
-
const parts = encodePromptForWire(message);
|
|
1542
1567
|
const timeoutMs = typeof options?.timeoutMs === "number" && options.timeoutMs > 0 ? options.timeoutMs : void 0;
|
|
1543
1568
|
const timeoutSignal = timeoutMs ? AbortSignal.timeout(timeoutMs) : void 0;
|
|
1544
1569
|
const streamSignal = timeoutSignal ? options?.signal ? AbortSignal.any([options.signal, timeoutSignal]) : timeoutSignal : options?.signal;
|
|
@@ -1556,26 +1581,47 @@ var SandboxInstance = class SandboxInstance {
|
|
|
1556
1581
|
...data
|
|
1557
1582
|
});
|
|
1558
1583
|
};
|
|
1584
|
+
const explicitReplay = Boolean(options?.executionId && options.lastEventId);
|
|
1585
|
+
const requestEndpoint = explicitReplay ? `/agents/run/${encodeURIComponent(options?.executionId ?? "")}/events?lastEventId=${encodeURIComponent(options?.lastEventId ?? "")}&format=sse` : "/agents/run/stream";
|
|
1586
|
+
let lastEventId = options?.lastEventId;
|
|
1587
|
+
let executionId = options?.executionId;
|
|
1588
|
+
let sessionId = options?.sessionId;
|
|
1589
|
+
let receivedTerminal = false;
|
|
1590
|
+
const RECONNECT_BACKOFF_MS = [
|
|
1591
|
+
2e3,
|
|
1592
|
+
4e3,
|
|
1593
|
+
8e3,
|
|
1594
|
+
16e3,
|
|
1595
|
+
3e4,
|
|
1596
|
+
3e4
|
|
1597
|
+
];
|
|
1598
|
+
const MAX_RECONNECT_ATTEMPTS = RECONNECT_BACKOFF_MS.length;
|
|
1599
|
+
let lastReplayStatus;
|
|
1600
|
+
let lastExecutionStatus;
|
|
1601
|
+
let firstSseEventSeen = false;
|
|
1559
1602
|
let response;
|
|
1560
1603
|
try {
|
|
1561
1604
|
logStreamCheckpoint("runtime_post_start", {
|
|
1562
|
-
endpoint:
|
|
1605
|
+
endpoint: requestEndpoint,
|
|
1563
1606
|
sessionId: options?.sessionId,
|
|
1564
1607
|
executionId: options?.executionId,
|
|
1565
1608
|
hasLastEventId: Boolean(options?.lastEventId),
|
|
1566
|
-
detach: options?.detach === true
|
|
1609
|
+
detach: options?.detach === true,
|
|
1610
|
+
replayOnly: explicitReplay
|
|
1567
1611
|
});
|
|
1568
|
-
|
|
1612
|
+
const request = explicitReplay ? {
|
|
1613
|
+
method: "GET",
|
|
1614
|
+
signal: streamSignal
|
|
1615
|
+
} : {
|
|
1569
1616
|
method: "POST",
|
|
1570
1617
|
signal: streamSignal,
|
|
1571
1618
|
headers: {
|
|
1572
1619
|
...options?.executionId ? { "X-Execution-ID": options.executionId } : {},
|
|
1573
|
-
...options?.lastEventId ? { "Last-Event-ID": options.lastEventId } : {},
|
|
1574
1620
|
...options?.detach ? { "x-agent-detach": "true" } : {}
|
|
1575
1621
|
},
|
|
1576
1622
|
body: JSON.stringify({
|
|
1577
1623
|
identifier: "default",
|
|
1578
|
-
parts,
|
|
1624
|
+
parts: encodePromptForWire(message),
|
|
1579
1625
|
sessionId: options?.sessionId,
|
|
1580
1626
|
turnId: options?.turnId,
|
|
1581
1627
|
metadata: options?.context,
|
|
@@ -1584,7 +1630,8 @@ var SandboxInstance = class SandboxInstance {
|
|
|
1584
1630
|
...options?.detach ? { detach: true } : {},
|
|
1585
1631
|
backend: normalizeRuntimeBackendConfig(options?.backend ?? this.defaultRuntimeBackend, { model: options?.model })
|
|
1586
1632
|
})
|
|
1587
|
-
}
|
|
1633
|
+
};
|
|
1634
|
+
response = await this.runtimeFetch(requestEndpoint, request);
|
|
1588
1635
|
logStreamCheckpoint("runtime_response_headers", {
|
|
1589
1636
|
status: response.status,
|
|
1590
1637
|
ok: response.ok
|
|
@@ -1594,27 +1641,20 @@ var SandboxInstance = class SandboxInstance {
|
|
|
1594
1641
|
throwIfTimedOut();
|
|
1595
1642
|
throw err;
|
|
1596
1643
|
}
|
|
1597
|
-
if (!response.ok) {
|
|
1644
|
+
if (!response.ok && !explicitReplay) {
|
|
1598
1645
|
const body = await response.text();
|
|
1599
1646
|
throw parseErrorResponse(response.status, body, void 0, response.headers);
|
|
1600
1647
|
}
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
3e4
|
|
1612
|
-
];
|
|
1613
|
-
const MAX_RECONNECT_ATTEMPTS = RECONNECT_BACKOFF_MS.length;
|
|
1614
|
-
let lastReplayStatus;
|
|
1615
|
-
let lastExecutionStatus;
|
|
1616
|
-
let firstSseEventSeen = false;
|
|
1617
|
-
for await (const event of this.parseSSEStream(response, streamSignal)) {
|
|
1648
|
+
if (!response.ok) {
|
|
1649
|
+
lastReplayStatus = response.status;
|
|
1650
|
+
await response.text();
|
|
1651
|
+
logStreamCheckpoint("replay_response_status", {
|
|
1652
|
+
attempt: 0,
|
|
1653
|
+
status: response.status,
|
|
1654
|
+
ok: false
|
|
1655
|
+
});
|
|
1656
|
+
} else for await (const event of this.parseSSEStream(response, streamSignal)) {
|
|
1657
|
+
if (event.type === "history.replay.start" || event.type === "history.replay.end") continue;
|
|
1618
1658
|
if (!firstSseEventSeen) {
|
|
1619
1659
|
firstSseEventSeen = true;
|
|
1620
1660
|
logStreamCheckpoint("first_sse_event", {
|
|
@@ -2092,11 +2132,19 @@ var SandboxInstance = class SandboxInstance {
|
|
|
2092
2132
|
async resourceUsage() {
|
|
2093
2133
|
await this.ensureRunning();
|
|
2094
2134
|
const response = await this.runtimeFetch("/health/detailed", { method: "GET" });
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2135
|
+
const body = await response.text();
|
|
2136
|
+
let data;
|
|
2137
|
+
try {
|
|
2138
|
+
data = JSON.parse(body);
|
|
2139
|
+
} catch (error) {
|
|
2140
|
+
if (response.ok) throw new NetworkError("Detailed health returned a non-JSON response", error instanceof Error ? error : void 0, {
|
|
2141
|
+
endpoint: "/health/detailed",
|
|
2142
|
+
origin: "runtime"
|
|
2143
|
+
});
|
|
2098
2144
|
}
|
|
2099
|
-
|
|
2145
|
+
if (data?.resources) return data.resources;
|
|
2146
|
+
if (!response.ok) throw parseErrorResponse(response.status, body, void 0, response.headers);
|
|
2147
|
+
return null;
|
|
2100
2148
|
}
|
|
2101
2149
|
/**
|
|
2102
2150
|
* Git capability object for repository operations.
|
|
@@ -2321,6 +2369,7 @@ var SandboxInstance = class SandboxInstance {
|
|
|
2321
2369
|
list: (path, options) => this.fsList(path, options),
|
|
2322
2370
|
stat: (path) => this.fsStat(path),
|
|
2323
2371
|
delete: (path, options) => this.fsDelete(path, options),
|
|
2372
|
+
rename: (sourcePath, destinationPath, options) => this.fsRename(sourcePath, destinationPath, options),
|
|
2324
2373
|
mkdir: (path, options) => this.fsMkdir(path, options),
|
|
2325
2374
|
exists: (path) => this.fsExists(path)
|
|
2326
2375
|
};
|
|
@@ -2366,7 +2415,7 @@ var SandboxInstance = class SandboxInstance {
|
|
|
2366
2415
|
const fs = await import("node:fs/promises");
|
|
2367
2416
|
const path = await import("node:path");
|
|
2368
2417
|
await fs.mkdir(path.dirname(localPath), { recursive: true });
|
|
2369
|
-
const response = await this.runtimeFetch(
|
|
2418
|
+
const response = await this.runtimeFetch(runtimeFilesystemPath("/fs/download", remotePath), { method: "GET" });
|
|
2370
2419
|
if (!response.ok) {
|
|
2371
2420
|
const body = await response.text();
|
|
2372
2421
|
throw parseErrorResponse(response.status, body, void 0, response.headers);
|
|
@@ -2416,7 +2465,7 @@ var SandboxInstance = class SandboxInstance {
|
|
|
2416
2465
|
const fs = await import("node:fs/promises");
|
|
2417
2466
|
const path = await import("node:path");
|
|
2418
2467
|
const { execSync } = await import("node:child_process");
|
|
2419
|
-
const response = await this.runtimeFetch(
|
|
2468
|
+
const response = await this.runtimeFetch(runtimeFilesystemPath("/fs/download-dir", remoteDir), { method: "GET" });
|
|
2420
2469
|
if (!response.ok) {
|
|
2421
2470
|
const body = await response.text();
|
|
2422
2471
|
throw parseErrorResponse(response.status, body, void 0, response.headers);
|
|
@@ -2439,7 +2488,8 @@ var SandboxInstance = class SandboxInstance {
|
|
|
2439
2488
|
if (options?.long) params.set("long", "true");
|
|
2440
2489
|
if (options?.sessionId) params.set("sessionId", options.sessionId);
|
|
2441
2490
|
const query = params.toString();
|
|
2442
|
-
const
|
|
2491
|
+
const basePath = runtimeFilesystemPath("/fs/list", path);
|
|
2492
|
+
const url = query ? `${basePath}?${query}` : basePath;
|
|
2443
2493
|
const response = await this.runtimeFetch(url, { method: "GET" });
|
|
2444
2494
|
if (!response.ok) {
|
|
2445
2495
|
const body = await response.text();
|
|
@@ -2461,7 +2511,7 @@ var SandboxInstance = class SandboxInstance {
|
|
|
2461
2511
|
}
|
|
2462
2512
|
async fsStat(path) {
|
|
2463
2513
|
await this.ensureRunning();
|
|
2464
|
-
const response = await this.runtimeFetch(
|
|
2514
|
+
const response = await this.runtimeFetch(runtimeFilesystemPath("/fs/stat", path), { method: "GET" });
|
|
2465
2515
|
if (!response.ok) {
|
|
2466
2516
|
const body = await response.text();
|
|
2467
2517
|
throw parseErrorResponse(response.status, body, void 0, response.headers);
|
|
@@ -2499,19 +2549,24 @@ var SandboxInstance = class SandboxInstance {
|
|
|
2499
2549
|
if (options?.recursive) params.set("recursive", "true");
|
|
2500
2550
|
if (options?.sessionId) params.set("sessionId", options.sessionId);
|
|
2501
2551
|
const query = params.toString();
|
|
2502
|
-
const
|
|
2552
|
+
const basePath = runtimeFilesystemPath("/fs", path);
|
|
2553
|
+
const url = query ? `${basePath}?${query}` : basePath;
|
|
2503
2554
|
const response = await this.runtimeFetch(url, { method: "DELETE" });
|
|
2504
2555
|
if (!response.ok) {
|
|
2505
2556
|
const body = await response.text();
|
|
2506
2557
|
throw parseErrorResponse(response.status, body, void 0, response.headers);
|
|
2507
2558
|
}
|
|
2508
2559
|
}
|
|
2560
|
+
async fsRename(sourcePath, destinationPath, options) {
|
|
2561
|
+
return this.runtime.renameFile(sourcePath, destinationPath, options);
|
|
2562
|
+
}
|
|
2509
2563
|
async fsMkdir(path, options) {
|
|
2510
2564
|
await this.ensureRunning();
|
|
2511
2565
|
const params = new URLSearchParams();
|
|
2512
2566
|
if (options?.recursive) params.set("recursive", "true");
|
|
2513
2567
|
const query = params.toString();
|
|
2514
|
-
const
|
|
2568
|
+
const basePath = runtimeFilesystemPath("/fs/mkdir", path);
|
|
2569
|
+
const url = query ? `${basePath}?${query}` : basePath;
|
|
2515
2570
|
const response = await this.runtimeFetch(url, { method: "POST" });
|
|
2516
2571
|
if (!response.ok) {
|
|
2517
2572
|
const body = await response.text();
|
|
@@ -2520,7 +2575,7 @@ var SandboxInstance = class SandboxInstance {
|
|
|
2520
2575
|
}
|
|
2521
2576
|
async fsExists(path) {
|
|
2522
2577
|
await this.ensureRunning();
|
|
2523
|
-
const response = await this.runtimeFetch(
|
|
2578
|
+
const response = await this.runtimeFetch(runtimeFilesystemPath("/fs/exists", path), { method: "GET" });
|
|
2524
2579
|
if (!response.ok) {
|
|
2525
2580
|
const body = await response.text();
|
|
2526
2581
|
throw parseErrorResponse(response.status, body, void 0, response.headers);
|
|
@@ -3721,7 +3776,89 @@ var SandboxInstance = class SandboxInstance {
|
|
|
3721
3776
|
return ((await response.json()).children ?? []).map((child) => new SandboxInstance(this.client, this.parseInfo(child), this.defaultRuntimeBackend));
|
|
3722
3777
|
}
|
|
3723
3778
|
/**
|
|
3779
|
+
* Speculative rollouts: branch this RUNNING sandbox into `n` copy-on-write
|
|
3780
|
+
* children, run the SAME agent turn in every child in parallel, and
|
|
3781
|
+
* optionally score each child by running `scorer.command` inside it (exit 0
|
|
3782
|
+
* passes; stdout is parsed as the numeric score — highest wins).
|
|
3783
|
+
*
|
|
3784
|
+
* Async-op shape: resolves as soon as the children exist (their turns are
|
|
3785
|
+
* still running); poll {@link getRollout} or use {@link waitForRollout} for
|
|
3786
|
+
* results. Children are first-class sandboxes the caller may promote or
|
|
3787
|
+
* delete — each occupies a concurrent-session quota slot like a create.
|
|
3788
|
+
*
|
|
3789
|
+
* @example
|
|
3790
|
+
* ```typescript
|
|
3791
|
+
* const started = await box.rollout({
|
|
3792
|
+
* n: 4,
|
|
3793
|
+
* parts: [{ type: "text", text: "make the failing test pass" }],
|
|
3794
|
+
* scorer: { type: "command", command: "npm test --silent && echo 1" },
|
|
3795
|
+
* });
|
|
3796
|
+
* const settled = await box.waitForRollout(started.rolloutId);
|
|
3797
|
+
* console.log(settled.winner, settled.results);
|
|
3798
|
+
* ```
|
|
3799
|
+
*/
|
|
3800
|
+
async rollout(options) {
|
|
3801
|
+
if (!Number.isInteger(options.n) || options.n < 1 || options.n > 8) throw new ValidationError(`rollout n must be an integer in [1, 8] (got ${options.n})`);
|
|
3802
|
+
if (!Array.isArray(options.parts) || options.parts.length === 0) throw new ValidationError("rollout parts must be a non-empty array");
|
|
3803
|
+
if (options.scorer?.type === "command" && !options.scorer.command) throw new ValidationError("scorer.command is required when scorer.type is \"command\"");
|
|
3804
|
+
const response = await this.client.fetch(`/v1/sandboxes/${this.id}/rollouts`, {
|
|
3805
|
+
method: "POST",
|
|
3806
|
+
body: JSON.stringify({
|
|
3807
|
+
n: options.n,
|
|
3808
|
+
parts: options.parts,
|
|
3809
|
+
...options.scorer ? { scorer: options.scorer } : {}
|
|
3810
|
+
})
|
|
3811
|
+
});
|
|
3812
|
+
if (!response.ok) {
|
|
3813
|
+
const body = await response.text();
|
|
3814
|
+
throw parseErrorResponse(response.status, body, void 0, response.headers);
|
|
3815
|
+
}
|
|
3816
|
+
const data = await response.json();
|
|
3817
|
+
if (typeof data.rolloutId !== "string" || data.rolloutId.length === 0) throw new ServerError(`Rollout create returned ${response.status} but body had no rolloutId: ${JSON.stringify(data).slice(0, 200)}`, response.status);
|
|
3818
|
+
return data;
|
|
3819
|
+
}
|
|
3820
|
+
/**
|
|
3821
|
+
* Poll a rollout's status and per-child results.
|
|
3822
|
+
*
|
|
3823
|
+
* Failed or timed-out children appear as result entries with `error` set —
|
|
3824
|
+
* partial results are never dropped. Rollout poll-state lives in
|
|
3825
|
+
* orchestrator RAM: after an orchestrator restart this returns 404 while
|
|
3826
|
+
* the children themselves survive as first-class sandboxes.
|
|
3827
|
+
*/
|
|
3828
|
+
async getRollout(rolloutId) {
|
|
3829
|
+
if (!rolloutId) throw new ValidationError("rolloutId is required");
|
|
3830
|
+
const response = await this.client.fetch(`/v1/sandboxes/${this.id}/rollouts/${encodeURIComponent(rolloutId)}`, { method: "GET" });
|
|
3831
|
+
if (!response.ok) {
|
|
3832
|
+
const body = await response.text();
|
|
3833
|
+
throw parseErrorResponse(response.status, body, void 0, response.headers);
|
|
3834
|
+
}
|
|
3835
|
+
return await response.json();
|
|
3836
|
+
}
|
|
3837
|
+
/**
|
|
3838
|
+
* Poll {@link getRollout} until the rollout leaves `running`, then return
|
|
3839
|
+
* the terminal record — including `partial`/`failed` outcomes, whose
|
|
3840
|
+
* per-child `error` entries are results the caller inspects, not
|
|
3841
|
+
* exceptions. Throws {@link TimeoutError} on deadline or abort.
|
|
3842
|
+
*/
|
|
3843
|
+
async waitForRollout(rolloutId, options) {
|
|
3844
|
+
const timeoutMs = options?.timeoutMs ?? 36e4;
|
|
3845
|
+
const pollIntervalMs = options?.pollIntervalMs ?? 2e3;
|
|
3846
|
+
const startTime = Date.now();
|
|
3847
|
+
while (true) {
|
|
3848
|
+
if (options?.signal?.aborted) throw new TimeoutError(0, "Aborted");
|
|
3849
|
+
const record = await this.getRollout(rolloutId);
|
|
3850
|
+
if (record.status !== "running") return record;
|
|
3851
|
+
if (Date.now() - startTime > timeoutMs) throw new TimeoutError(timeoutMs, `Timed out waiting for rollout ${rolloutId} to settle`);
|
|
3852
|
+
await this.sleep(pollIntervalMs);
|
|
3853
|
+
}
|
|
3854
|
+
}
|
|
3855
|
+
/**
|
|
3724
3856
|
* Stop the sandbox (keeps state for resume).
|
|
3857
|
+
*
|
|
3858
|
+
* The stop response carries the orchestrator's per-phase stop breakdown
|
|
3859
|
+
* (`stop_metering` / `container_kill` / `stop_complete` /
|
|
3860
|
+
* `stop_unaccounted`); after this resolves, {@link startupDiagnostics}
|
|
3861
|
+
* returns it (replacing the create breakdown).
|
|
3725
3862
|
*/
|
|
3726
3863
|
async stop() {
|
|
3727
3864
|
const response = await this.client.fetch(`/v1/sandboxes/${this.id}/stop`, { method: "POST" });
|
|
@@ -3729,7 +3866,9 @@ var SandboxInstance = class SandboxInstance {
|
|
|
3729
3866
|
const body = await response.text();
|
|
3730
3867
|
throw parseErrorResponse(response.status, body, void 0, response.headers);
|
|
3731
3868
|
}
|
|
3869
|
+
const diagnostics = await this.readLifecycleDiagnostics(response);
|
|
3732
3870
|
await this.refresh();
|
|
3871
|
+
if (diagnostics) this.info.startupDiagnostics = diagnostics;
|
|
3733
3872
|
}
|
|
3734
3873
|
/**
|
|
3735
3874
|
* Resume a stopped sandbox.
|
|
@@ -3746,10 +3885,18 @@ var SandboxInstance = class SandboxInstance {
|
|
|
3746
3885
|
const body = await response.text();
|
|
3747
3886
|
throw parseErrorResponse(response.status, body, void 0, response.headers);
|
|
3748
3887
|
}
|
|
3888
|
+
const diagnostics = await this.readLifecycleDiagnostics(response);
|
|
3749
3889
|
await this.refresh();
|
|
3890
|
+
if (diagnostics) this.info.startupDiagnostics = diagnostics;
|
|
3750
3891
|
}
|
|
3751
3892
|
/**
|
|
3752
3893
|
* Delete the sandbox permanently.
|
|
3894
|
+
*
|
|
3895
|
+
* The delete response carries the orchestrator's per-phase delete breakdown
|
|
3896
|
+
* (`container_remove` / `storage_binding_wait` / `cleanup_parallel` /
|
|
3897
|
+
* `delete_complete` / `delete_unaccounted`); after this resolves,
|
|
3898
|
+
* {@link startupDiagnostics} returns it for post-mortem reads on the
|
|
3899
|
+
* now-gone sandbox.
|
|
3753
3900
|
*/
|
|
3754
3901
|
async delete() {
|
|
3755
3902
|
const response = await this.client.fetch(`/v1/sandboxes/${this.id}`, { method: "DELETE" });
|
|
@@ -3757,6 +3904,17 @@ var SandboxInstance = class SandboxInstance {
|
|
|
3757
3904
|
const body = await response.text();
|
|
3758
3905
|
throw parseErrorResponse(response.status, body, void 0, response.headers);
|
|
3759
3906
|
}
|
|
3907
|
+
const diagnostics = await this.readLifecycleDiagnostics(response);
|
|
3908
|
+
if (diagnostics) this.info.startupDiagnostics = diagnostics;
|
|
3909
|
+
}
|
|
3910
|
+
/**
|
|
3911
|
+
* Read the per-phase lifecycle breakdown off a stop/resume/delete response
|
|
3912
|
+
* body. Never throws: diagnostics are measurement data — a body that fails
|
|
3913
|
+
* to parse (older API, empty body) must not fail the lifecycle call.
|
|
3914
|
+
*/
|
|
3915
|
+
async readLifecycleDiagnostics(response) {
|
|
3916
|
+
const data = await response.json().catch(() => null);
|
|
3917
|
+
return data ? normalizeStartupDiagnostics(data.startupDiagnostics) : null;
|
|
3760
3918
|
}
|
|
3761
3919
|
/**
|
|
3762
3920
|
* keepAlive is intentionally unavailable until the API exposes timeout updates.
|
|
@@ -4330,15 +4488,10 @@ var SandboxInstance = class SandboxInstance {
|
|
|
4330
4488
|
return { state: "running" };
|
|
4331
4489
|
}
|
|
4332
4490
|
/**
|
|
4333
|
-
* Mint a scoped, time-bounded JWT for
|
|
4334
|
-
*
|
|
4335
|
-
*
|
|
4336
|
-
*
|
|
4337
|
-
*
|
|
4338
|
-
* Use this to give a browser direct read access to the sandbox without
|
|
4339
|
-
* leaking the full bearer (`box.connection.authToken`). The returned
|
|
4340
|
-
* token verifies against the same sidecar middleware that already
|
|
4341
|
-
* gates ProductTokenIssuer-issued JWTs — no new sidecar surface.
|
|
4491
|
+
* Mint a scoped, time-bounded JWT for browser access (Issue #913 Gap 1).
|
|
4492
|
+
* Authority is the caller's `TANGLE_API_KEY` (sk-tan-*); signing secrets
|
|
4493
|
+
* stay server-side. Session/project scopes work with SessionGatewayClient,
|
|
4494
|
+
* while session-runtime/read-only scopes work with SandboxRuntimeClient.
|
|
4342
4495
|
*/
|
|
4343
4496
|
async mintScopedToken(opts) {
|
|
4344
4497
|
if ((opts.scope === "session" || opts.scope === "session-runtime") && !opts.sessionId) throw new ValidationError(`${opts.scope} scope requires a sessionId`);
|
|
@@ -4382,7 +4535,7 @@ var SandboxInstance = class SandboxInstance {
|
|
|
4382
4535
|
const search = new URLSearchParams();
|
|
4383
4536
|
search.set("sessionId", id);
|
|
4384
4537
|
if (opts?.since) search.set("since", opts.since);
|
|
4385
|
-
const response = await this.runtimeFetch(`/agents/events
|
|
4538
|
+
const response = await this.runtimeFetch(`/agents/events?${search.toString()}`, { signal: opts?.signal });
|
|
4386
4539
|
if (!response.ok) {
|
|
4387
4540
|
const body = await response.text();
|
|
4388
4541
|
throw parseErrorResponse(response.status, body, void 0, response.headers);
|
package/dist/tangle/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as TANGLE_JOBS_CONTRACT, c as AgentSandboxBlueprintAbi, d as SandboxCreateParamTypes, f as SandboxCreateResponseParamTypes, i as TANGLE_CHAIN_ID, l as ITangleJobsAbi, n as JOB_SANDBOX_CREATE, o as TANGLE_MAINNET_RPC, p as SandboxIdParamTypes, r as JOB_SANDBOX_DELETE, s as TangleSandboxClientConfig, t as TangleSandboxClient, u as JsonResponseParamTypes } from "../index-
|
|
1
|
+
import { a as TANGLE_JOBS_CONTRACT, c as AgentSandboxBlueprintAbi, d as SandboxCreateParamTypes, f as SandboxCreateResponseParamTypes, i as TANGLE_CHAIN_ID, l as ITangleJobsAbi, n as JOB_SANDBOX_CREATE, o as TANGLE_MAINNET_RPC, p as SandboxIdParamTypes, r as JOB_SANDBOX_DELETE, s as TangleSandboxClientConfig, t as TangleSandboxClient, u as JsonResponseParamTypes } from "../index-DFJiN98I.js";
|
|
2
2
|
export { AgentSandboxBlueprintAbi, ITangleJobsAbi, JOB_SANDBOX_CREATE, JOB_SANDBOX_DELETE, JsonResponseParamTypes, SandboxCreateParamTypes, SandboxCreateResponseParamTypes, SandboxIdParamTypes, TANGLE_CHAIN_ID, TANGLE_JOBS_CONTRACT, TANGLE_MAINNET_RPC, TangleSandboxClient, TangleSandboxClientConfig };
|
package/dist/tangle/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as TANGLE_JOBS_CONTRACT, c as ITangleJobsAbi, d as SandboxCreateResponseParamTypes, f as SandboxIdParamTypes, i as TANGLE_CHAIN_ID, l as JsonResponseParamTypes, n as JOB_SANDBOX_CREATE, o as TANGLE_MAINNET_RPC, r as JOB_SANDBOX_DELETE, s as AgentSandboxBlueprintAbi, t as TangleSandboxClient, u as SandboxCreateParamTypes } from "../tangle-
|
|
1
|
+
import { a as TANGLE_JOBS_CONTRACT, c as ITangleJobsAbi, d as SandboxCreateResponseParamTypes, f as SandboxIdParamTypes, i as TANGLE_CHAIN_ID, l as JsonResponseParamTypes, n as JOB_SANDBOX_CREATE, o as TANGLE_MAINNET_RPC, r as JOB_SANDBOX_DELETE, s as AgentSandboxBlueprintAbi, t as TangleSandboxClient, u as SandboxCreateParamTypes } from "../tangle-C-5OvIj8.js";
|
|
2
2
|
export { AgentSandboxBlueprintAbi, ITangleJobsAbi, JOB_SANDBOX_CREATE, JOB_SANDBOX_DELETE, JsonResponseParamTypes, SandboxCreateParamTypes, SandboxCreateResponseParamTypes, SandboxIdParamTypes, TANGLE_CHAIN_ID, TANGLE_JOBS_CONTRACT, TANGLE_MAINNET_RPC, TangleSandboxClient };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { p as parseErrorResponse } from "./errors-
|
|
2
|
-
import { t as SandboxInstance } from "./sandbox-
|
|
1
|
+
import { p as parseErrorResponse } from "./errors-wd266B9Q.js";
|
|
2
|
+
import { t as SandboxInstance } from "./sandbox-CIHssZCH.js";
|
|
3
3
|
//#region src/tangle/abi.ts
|
|
4
4
|
/**
|
|
5
5
|
* Tangle Contract ABI Definitions
|