@tangle-network/agent-app 0.45.4 → 0.45.6

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.
@@ -100,7 +100,7 @@ import {
100
100
  flattenHistory,
101
101
  readSandboxBinaryBytes,
102
102
  statSandboxFileSize
103
- } from "../chunk-CS6MJDIM.js";
103
+ } from "../chunk-4XNCMUVI.js";
104
104
  import "../chunk-LWSJK546.js";
105
105
  import "../chunk-73F3CKVK.js";
106
106
  import "../chunk-ITCINLSU.js";
@@ -155,6 +155,411 @@ async function readSandboxBinaryBytes(box, absolutePath, expectedSize, options)
155
155
  return { succeeded: true, value: { bytes, size: expectedSize } };
156
156
  }
157
157
 
158
+ // src/sandbox/recovery.ts
159
+ var EGRESS_PROXY_RECOVERY_REQUIRED = "EGRESS_PROXY_RECOVERY_REQUIRED";
160
+ var EGRESS_PROXY_RECOVERY_PHASE = "egress_proxy_recovery";
161
+ var WORKSPACE_SANDBOX_MISSING = "WORKSPACE_SANDBOX_MISSING";
162
+ var WORKSPACE_SANDBOX_HOST_EXHAUSTED = "WORKSPACE_SANDBOX_HOST_EXHAUSTED";
163
+ var WORKSPACE_SANDBOX_UNRECOVERABLE = "WORKSPACE_SANDBOX_UNRECOVERABLE";
164
+ var WORKSPACE_SANDBOX_SNAPSHOT_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
165
+ var CODES = {
166
+ [EGRESS_PROXY_RECOVERY_REQUIRED]: { snapshotUsable: true },
167
+ [WORKSPACE_SANDBOX_MISSING]: { snapshotUsable: false },
168
+ [WORKSPACE_SANDBOX_HOST_EXHAUSTED]: { snapshotUsable: false },
169
+ [WORKSPACE_SANDBOX_UNRECOVERABLE]: { snapshotUsable: false }
170
+ };
171
+ var ACTIONS = {
172
+ confirmation_required: { replacementChosen: false },
173
+ deletion_declined: { replacementChosen: false },
174
+ replacement_authorized: { replacementChosen: false },
175
+ snapshot_replacement_authorized: { replacementChosen: false },
176
+ replacement_started: { replacementChosen: true },
177
+ replacement_completed: { replacementChosen: true },
178
+ snapshot_replacement_started: { replacementChosen: true },
179
+ snapshot_restore_failed: { replacementChosen: true },
180
+ snapshot_replacement_completed: { replacementChosen: true },
181
+ missing_replacement_started: { replacementChosen: true },
182
+ missing_replacement_completed: { replacementChosen: true },
183
+ unrecoverable_replacement_started: { replacementChosen: true },
184
+ unrecoverable_replacement_completed: { replacementChosen: true }
185
+ };
186
+ var WorkspaceSandboxRecoveryRequiredError = class extends Error {
187
+ code = EGRESS_PROXY_RECOVERY_REQUIRED;
188
+ status = 409;
189
+ phase = EGRESS_PROXY_RECOVERY_PHASE;
190
+ recovery;
191
+ constructor(recovery, cause) {
192
+ super(workspaceSandboxRecoveryMessage(recovery), { cause });
193
+ this.name = "WorkspaceSandboxRecoveryRequiredError";
194
+ this.recovery = recovery;
195
+ }
196
+ };
197
+ function isRecord(value) {
198
+ return typeof value === "object" && value !== null;
199
+ }
200
+ function asNonEmptyString(value) {
201
+ return typeof value === "string" && value.trim().length > 0 ? value : void 0;
202
+ }
203
+ function errorChain(error) {
204
+ const pending = [error];
205
+ const visited = /* @__PURE__ */ new Set();
206
+ const chain = [];
207
+ while (pending.length > 0) {
208
+ const current = pending.shift();
209
+ if (current === void 0 || current === null || visited.has(current)) continue;
210
+ visited.add(current);
211
+ chain.push(current);
212
+ if (!isRecord(current)) continue;
213
+ if (current.cause !== void 0) pending.push(current.cause);
214
+ if (Array.isArray(current.errors)) pending.push(...current.errors);
215
+ }
216
+ return chain;
217
+ }
218
+ function isEgressProxyRecoveryRequiredError(error) {
219
+ return errorChain(error).some(
220
+ (current) => isRecord(current) && current.code === EGRESS_PROXY_RECOVERY_REQUIRED
221
+ );
222
+ }
223
+ function isWorkspaceSandboxSnapshotRestoreError(error) {
224
+ return errorChain(error).some((current) => {
225
+ const message = current instanceof Error ? current.message : isRecord(current) && typeof current.message === "string" ? current.message : "";
226
+ return /snapshot|fromSnapshot|restore/i.test(message);
227
+ });
228
+ }
229
+ function assessWorkspaceSandboxSnapshot(snapshot, sandboxId, now = Date.now()) {
230
+ if (!snapshot) return { availability: "missing", freshness: "unknown" };
231
+ const createdAt = Date.parse(snapshot.createdAt);
232
+ const ageMs = now - createdAt;
233
+ const isFresh = snapshot.fromSandboxId === sandboxId && Number.isFinite(createdAt) && ageMs >= 0 && ageMs <= WORKSPACE_SANDBOX_SNAPSHOT_MAX_AGE_MS;
234
+ return isFresh ? { availability: "available", freshness: "fresh", snapshot } : { availability: "stale", freshness: "stale", snapshot };
235
+ }
236
+ function isSnapshotAssessment(value) {
237
+ if (!isRecord(value)) return false;
238
+ const availability = value.availability;
239
+ const freshness = value.freshness;
240
+ return (availability === "available" || availability === "missing" || availability === "stale") && (freshness === "fresh" || freshness === "stale" || freshness === "unknown");
241
+ }
242
+ function isWorkspaceSandboxRecoveryAction(value) {
243
+ return typeof value === "string" && Object.hasOwn(ACTIONS, value);
244
+ }
245
+ function isWorkspaceSandboxRecoveryCode(value) {
246
+ return typeof value === "string" && Object.hasOwn(CODES, value);
247
+ }
248
+ function isWorkspaceSandboxRecoveryState(value) {
249
+ if (!isRecord(value)) return false;
250
+ return isWorkspaceSandboxRecoveryCode(value.code) && !!asNonEmptyString(value.sandboxId) && !!asNonEmptyString(value.detectedAt) && isSnapshotAssessment(value.snapshot) && isWorkspaceSandboxRecoveryAction(value.action);
251
+ }
252
+ function workspaceSandboxRecoveryFromError(error) {
253
+ for (const current of errorChain(error)) {
254
+ if (!isRecord(current) || !isWorkspaceSandboxRecoveryState(current.recovery)) continue;
255
+ return current.recovery;
256
+ }
257
+ return void 0;
258
+ }
259
+ function workspaceSandboxRecoveryMessage(recovery) {
260
+ if (recovery.code === WORKSPACE_SANDBOX_MISSING) {
261
+ return "The sandbox behind this workspace no longer exists on the platform. A replacement is being provisioned and your saved work is restored into it.";
262
+ }
263
+ if (!CODES[recovery.code].snapshotUsable) {
264
+ return "The sandbox behind this workspace could not be started again. A replacement is being provisioned and your saved work is restored into it.";
265
+ }
266
+ if (recovery.action === "deletion_declined") {
267
+ return "Sandbox recovery remains paused because replacement was declined. The stopped sandbox has not been deleted.";
268
+ }
269
+ if (recovery.action === "confirmation_required") {
270
+ const snapshotReason = recovery.snapshot.availability === "missing" ? "No restorable sandbox snapshot is available." : "The available sandbox snapshot is stale.";
271
+ return `Sandbox recovery requires owner confirmation before replacement. ${snapshotReason} The stopped sandbox has not been deleted.`;
272
+ }
273
+ if (recovery.action === "snapshot_restore_failed") {
274
+ return "Sandbox replacement could not restore its verified snapshot, and an empty fallback was not created because it could lose workspace state.";
275
+ }
276
+ return "Sandbox recovery is required before chat can continue. The stopped sandbox has not been deleted.";
277
+ }
278
+ function workspaceSandboxRecoveryRecommendedActions(recovery) {
279
+ if (!CODES[recovery.code].snapshotUsable) {
280
+ return ["Retry after the replacement sandbox finishes provisioning."];
281
+ }
282
+ if (recovery.action === "deletion_declined") {
283
+ return ["Keep the stopped sandbox for support or ask a workspace owner to authorize replacement."];
284
+ }
285
+ if (recovery.action === "confirmation_required") {
286
+ return ["An owner can explicitly replace the sandbox after accepting loss of unsnapshotted sandbox-local changes."];
287
+ }
288
+ if (recovery.action === "snapshot_restore_failed") {
289
+ return ["Retry snapshot restoration or contact support. An empty replacement is not created automatically."];
290
+ }
291
+ return ["Retry after sandbox recovery completes."];
292
+ }
293
+ function workspaceSandboxRecoveryDiagnostic(recovery) {
294
+ return {
295
+ sandboxId: recovery.sandboxId,
296
+ recoveryCode: recovery.code,
297
+ snapshotAvailability: recovery.snapshot.availability,
298
+ snapshotFreshness: recovery.snapshot.freshness,
299
+ selectedRecoveryAction: recovery.action,
300
+ replacementSandboxId: recovery.replacementSandboxId
301
+ };
302
+ }
303
+ function preferredWorkspaceSandboxRecoveryBoxKey(recovery) {
304
+ if (!recovery?.replacementBoxKey) return void 0;
305
+ return ACTIONS[recovery.action].replacementChosen ? recovery.replacementBoxKey : void 0;
306
+ }
307
+ function shouldRestoreWorkspaceSandboxRecovery(recovery) {
308
+ if (!recovery) return false;
309
+ if (!CODES[recovery.code].snapshotUsable) return false;
310
+ return !!preferredWorkspaceSandboxRecoveryBoxKey(recovery) && recovery.snapshot.availability === "available";
311
+ }
312
+ function createWorkspaceSandboxRecoveryManager(store) {
313
+ async function record(workspaceId, recovery) {
314
+ await store.write(workspaceId, recovery);
315
+ }
316
+ return {
317
+ read: store.read,
318
+ record,
319
+ async decide({ workspaceId, sandboxId, decision, replacementBoxKey }) {
320
+ const current = await store.read(workspaceId);
321
+ if (!current || current.sandboxId !== sandboxId) return void 0;
322
+ const next = {
323
+ ...current,
324
+ action: decision === "replace" ? current.snapshot.availability === "available" ? "snapshot_replacement_authorized" : "replacement_authorized" : "deletion_declined",
325
+ confirmedAt: (/* @__PURE__ */ new Date()).toISOString(),
326
+ ...decision === "replace" && replacementBoxKey ? { replacementBoxKey } : {}
327
+ };
328
+ await record(workspaceId, next);
329
+ return next;
330
+ },
331
+ async complete({ workspaceId, replacementSandboxId }) {
332
+ const current = await store.read(workspaceId);
333
+ if (!current) return void 0;
334
+ const next = {
335
+ ...current,
336
+ action: completionFor(current.action),
337
+ replacementSandboxId
338
+ };
339
+ await record(workspaceId, next);
340
+ return next;
341
+ }
342
+ };
343
+ }
344
+ function completionFor(action) {
345
+ switch (action) {
346
+ case "missing_replacement_started":
347
+ return "missing_replacement_completed";
348
+ case "unrecoverable_replacement_started":
349
+ return "unrecoverable_replacement_completed";
350
+ case "snapshot_replacement_started":
351
+ case "snapshot_replacement_authorized":
352
+ return "snapshot_replacement_completed";
353
+ default:
354
+ return "replacement_completed";
355
+ }
356
+ }
357
+ var WORKSPACE_SANDBOX_RECOVERY_ACTIONS = Object.keys(
358
+ ACTIONS
359
+ );
360
+ var WORKSPACE_SANDBOX_RECOVERY_CODES = Object.keys(
361
+ CODES
362
+ );
363
+
364
+ // src/sandbox/diagnostics.ts
365
+ var SAFE_ERROR_FIELDS = [
366
+ "code",
367
+ "status",
368
+ "phase",
369
+ "endpoint",
370
+ "origin",
371
+ "retryAfterMs",
372
+ "sidecarVersion",
373
+ "containerImage"
374
+ ];
375
+ function isRecord2(value) {
376
+ return typeof value === "object" && value !== null;
377
+ }
378
+ function asSafeScalar(value) {
379
+ if (typeof value === "string" && value.length > 0) return redactDiagnosticText(value);
380
+ if (typeof value === "number" && Number.isFinite(value)) return value;
381
+ return void 0;
382
+ }
383
+ function redactDiagnosticText(value) {
384
+ const secretKeyPattern = "(?:(?:[A-Z0-9]+_)*(?:api_?key|auth_?token|access_?token|refresh_?token|key|token|password|secret|credential)|apiKey|authToken|accessToken|refreshToken)";
385
+ return value.replace(/(['"]authorization['"]\s*:\s*)(['"])[^'"]*\2/gi, "$1$2[REDACTED]$2").replace(/\b(authorization\s*[:=]\s*)(['"])[^'"]*\2/gi, "$1$2[REDACTED]$2").replace(/\b(authorization\s*[:=]\s*)Digest\b[^;}\n]*(?:\r?\n[ \t]+[^;}\n]*)*/gi, "$1[REDACTED]").replace(/\b(authorization\s*[:=]\s*)(?:Basic|Bearer|Negotiate)\s+[^\s;,&}\n]+(?:\r?\n[ \t]+[^\s;,&}\n]+)*/gi, "$1[REDACTED]").replace(new RegExp(`\\b(authorization\\s*[:=]\\s*)(?!(?:Basic|Bearer|Digest|Negotiate)\\b)([A-Za-z][A-Za-z0-9._-]+)\\s+(?!(?:authorization|${secretKeyPattern})\\s*[:=]|['"](?:authorization|${secretKeyPattern})['"]\\s*:)([^}\\n]*(?:\\r?\\n[ \\t]+[^}\\n]*)*)`, "gi"), (_match, prefix, _scheme, valuePart) => {
386
+ if (valuePart.includes(";") || /\r?\n[ \t]+/.test(valuePart)) return `${prefix}[REDACTED]`;
387
+ const tokenMatch = valuePart.match(/^\S+(.*)$/);
388
+ return `${prefix}[REDACTED]${tokenMatch?.[1] ?? ""}`;
389
+ }).replace(/\b(authorization\s*[:=]\s*)[A-Za-z][\w.-]+\s+[^\s;,&}\n]+(?=[;&}\n]|$)/gi, "$1[REDACTED]").replace(/\b(authorization\s*[:=]\s*)(?!\[REDACTED\])[^'"\s&}]+(?:[;,][^'"\s&}]+)*/gi, "$1[REDACTED]").replace(/\bBearer\s+[A-Za-z0-9._~+/-]+=*/gi, "Bearer [REDACTED]").replace(/\bsk-[A-Za-z0-9_-]{6,}\b/g, "sk-[REDACTED]").replace(
390
+ /([?&][^=&#\s]*(?:token|key|secret|password|authorization)[^=&#\s]*=)[^&#\s]+/gi,
391
+ "$1[REDACTED]"
392
+ ).replace(
393
+ new RegExp(`(['"])(${secretKeyPattern})\\1\\s*:\\s*(['"])[^'"]+\\3`, "gi"),
394
+ "$1$2$1:$3[REDACTED]$3"
395
+ ).replace(
396
+ new RegExp(`\\b(${secretKeyPattern})\\s*([:=])(\\s*)(['"]?)(?!Bearer\\b)[^'"\\s;,&}]+(?:[;,]\\s*[^'"\\s;,&}]+)*`, "gi"),
397
+ "$1$2$3$4[REDACTED]"
398
+ );
399
+ }
400
+ function readMessage(value) {
401
+ if (value instanceof Error) return redactDiagnosticText(value.message);
402
+ if (typeof value === "string" && value.length > 0) return redactDiagnosticText(value);
403
+ if (typeof value === "number" && Number.isFinite(value)) return String(value);
404
+ if (typeof value === "boolean") return String(value);
405
+ if (!isRecord2(value)) return void 0;
406
+ const message = value.message;
407
+ return typeof message === "string" && message.length > 0 ? redactDiagnosticText(message) : void 0;
408
+ }
409
+ function readName(value) {
410
+ if (value instanceof Error) return redactDiagnosticText(value.name);
411
+ if (!isRecord2(value)) return void 0;
412
+ const name = value.name;
413
+ return typeof name === "string" && name.length > 0 ? redactDiagnosticText(name) : void 0;
414
+ }
415
+ function readCause(value) {
416
+ if (!isRecord2(value)) return void 0;
417
+ return value.cause;
418
+ }
419
+ function serializeSandboxProvisioningError(error, options = {}) {
420
+ const maxDepth = options.maxDepth ?? 6;
421
+ const causes = [];
422
+ const seen = /* @__PURE__ */ new Set();
423
+ let truncatedAtDepth;
424
+ let cycle = false;
425
+ let current = error;
426
+ let depth = 0;
427
+ for (; depth < maxDepth && current !== void 0 && current !== null; depth += 1) {
428
+ if (seen.has(current)) {
429
+ cycle = true;
430
+ causes.push({
431
+ name: "CauseChainCycle",
432
+ message: `cause chain cycle detected after ${depth} entries`
433
+ });
434
+ break;
435
+ }
436
+ if (isRecord2(current)) seen.add(current);
437
+ const cause = {};
438
+ const name = readName(current);
439
+ const message = readMessage(current);
440
+ if (name) cause.name = name;
441
+ if (message) cause.message = message;
442
+ if (isRecord2(current)) {
443
+ for (const field of SAFE_ERROR_FIELDS) {
444
+ const safeValue = asSafeScalar(current[field]);
445
+ if (safeValue !== void 0) {
446
+ Object.assign(cause, { [field]: safeValue });
447
+ }
448
+ }
449
+ }
450
+ if (Object.keys(cause).length > 0) causes.push(cause);
451
+ current = readCause(current);
452
+ }
453
+ if (!cycle && isRecord2(current) && seen.has(current)) {
454
+ cycle = true;
455
+ causes.push({
456
+ name: "CauseChainCycle",
457
+ message: `cause chain cycle detected after ${depth} entries`
458
+ });
459
+ } else if (current !== void 0 && current !== null && depth >= maxDepth) {
460
+ truncatedAtDepth = maxDepth;
461
+ causes.push({
462
+ name: "CauseChainTruncated",
463
+ message: `cause chain truncated after ${maxDepth} entries`
464
+ });
465
+ }
466
+ return {
467
+ message: readMessage(error) ?? "Sandbox unavailable",
468
+ causes,
469
+ truncated: truncatedAtDepth !== void 0,
470
+ truncatedAtDepth,
471
+ cycle
472
+ };
473
+ }
474
+ function formatSandboxProvisioningSupportDetails(diagnostics) {
475
+ const actionableCause = diagnostics.causes.find(
476
+ (cause) => cause.code !== void 0 || cause.status !== void 0 || cause.phase !== void 0 || cause.endpoint !== void 0 || cause.origin !== void 0 || cause.retryAfterMs !== void 0 || cause.sidecarVersion !== void 0 || cause.containerImage !== void 0
477
+ ) ?? diagnostics.causes[1];
478
+ if (!actionableCause) return "Support details: no nested sandbox cause details were available.";
479
+ const details = [
480
+ typeof actionableCause.name === "string" ? redactDiagnosticText(actionableCause.name) : void 0,
481
+ actionableCause.code !== void 0 ? `code=${actionableCause.code}` : void 0,
482
+ actionableCause.status !== void 0 ? `status=${actionableCause.status}` : void 0,
483
+ actionableCause.phase !== void 0 ? `phase=${redactDiagnosticText(actionableCause.phase)}` : void 0,
484
+ actionableCause.endpoint !== void 0 ? `endpoint=${redactDiagnosticText(actionableCause.endpoint)}` : void 0,
485
+ actionableCause.origin !== void 0 ? `origin=${redactDiagnosticText(actionableCause.origin)}` : void 0,
486
+ actionableCause.retryAfterMs !== void 0 ? `retryAfterMs=${actionableCause.retryAfterMs}` : void 0,
487
+ actionableCause.sidecarVersion !== void 0 ? `sidecarVersion=${redactDiagnosticText(actionableCause.sidecarVersion)}` : void 0,
488
+ actionableCause.containerImage !== void 0 ? `containerImage=${redactDiagnosticText(actionableCause.containerImage)}` : void 0,
489
+ typeof actionableCause.message === "string" ? redactDiagnosticText(actionableCause.message) : void 0
490
+ ].filter(Boolean);
491
+ if (details.length === 0) return "Support details: no nested sandbox cause details were available.";
492
+ return `Support details: ${details.join("; ")}`;
493
+ }
494
+ function isSandboxAuthFailure(diagnostics) {
495
+ return diagnostics.causes.some((cause) => {
496
+ const code = typeof cause.code === "string" ? cause.code.toUpperCase() : void 0;
497
+ const status = typeof cause.status === "number" ? cause.status : typeof cause.status === "string" ? Number.parseInt(cause.status, 10) : void 0;
498
+ const name = typeof cause.name === "string" ? cause.name.toLowerCase() : "";
499
+ const message = typeof cause.message === "string" ? cause.message.toLowerCase() : "";
500
+ return code === "AUTH_ERROR" || status === 401 || name.includes("autherror") || message.includes("missing or invalid authentication");
501
+ });
502
+ }
503
+ function isSandboxApiBearerAuthFailure(diagnostics) {
504
+ return diagnostics.causes.some((cause) => {
505
+ const status = typeof cause.status === "number" ? cause.status : typeof cause.status === "string" ? Number.parseInt(cause.status, 10) : void 0;
506
+ if (status !== 401) return false;
507
+ if (cause.origin !== "sandbox-api") return false;
508
+ if (typeof cause.endpoint !== "string") return false;
509
+ const endpointPath = sandboxApiEndpointPath(cause.endpoint);
510
+ if (!endpointPath) return false;
511
+ return /^\/v1\/sandboxes\/[^/?#]+(?:\/(?!runtime(?:[/?#]|$))[^?#]*)?(?:[?#].*)?$/.test(endpointPath);
512
+ });
513
+ }
514
+ function isSandboxApiSandboxMissingFailure(diagnostics) {
515
+ return diagnostics.causes.some((cause) => {
516
+ const status = typeof cause.status === "number" ? cause.status : typeof cause.status === "string" ? Number.parseInt(cause.status, 10) : void 0;
517
+ if (status !== 404) return false;
518
+ if (cause.origin !== "sandbox-api") return false;
519
+ if (typeof cause.endpoint !== "string") return false;
520
+ const endpointPath = sandboxApiEndpointPath(cause.endpoint);
521
+ if (!endpointPath) return false;
522
+ return /^\/v1\/sandboxes\/[^/?#]+(?:\/(?!runtime(?:[/?#]|$))[^?#]*)?(?:[?#].*)?$/.test(endpointPath);
523
+ });
524
+ }
525
+ function isSandboxHostCapacityFailure(diagnostics) {
526
+ return diagnostics.causes.some((cause) => {
527
+ if (cause.origin !== "sandbox-api") return false;
528
+ if (typeof cause.message !== "string") return false;
529
+ return /host has no available slot|host capacity reservation failed/i.test(cause.message);
530
+ });
531
+ }
532
+ function sandboxApiEndpointPath(endpoint) {
533
+ if (endpoint.startsWith("/")) return endpoint;
534
+ try {
535
+ const url = new URL(endpoint);
536
+ return `${url.pathname}${url.search}${url.hash}`;
537
+ } catch {
538
+ return null;
539
+ }
540
+ }
541
+ function formatSandboxProvisioningUserMessage(diagnostics) {
542
+ if (diagnostics.causes.some((cause) => cause.code === EGRESS_PROXY_RECOVERY_REQUIRED)) {
543
+ return "Sandbox recovery is required before chat can continue. The stopped sandbox has not been deleted.";
544
+ }
545
+ if (diagnostics.causes.some((cause) => cause.code === "vault.hydration_incomplete")) {
546
+ return "I couldn't finish copying your Vault into the sandbox, so I stopped rather than work from a partial copy. The copy resumes where it left off \u2014 try again in a moment.";
547
+ }
548
+ if (isSandboxApiBearerAuthFailure(diagnostics)) {
549
+ return "I'm unable to reconnect to the sandbox because its sandbox API credential was rejected. Another request may have rotated the bearer used for this operation.";
550
+ }
551
+ if (isSandboxAuthFailure(diagnostics)) {
552
+ return "I'm unable to reconnect to the sandbox because its runtime authentication failed. This can happen when an existing sandbox is reused with stale credentials.";
553
+ }
554
+ if (diagnostics.causes.some((cause) => cause.code === "PAYLOAD_TOO_LARGE" || cause.code === "FILE_TOO_LARGE" || cause.status === 413 || cause.status === "413")) {
555
+ return "An attachment is too large for the sandbox to accept. Use a smaller file and try again.";
556
+ }
557
+ if (diagnostics.causes.some((cause) => cause.code === "UPLOAD_BUDGET_EXHAUSTED")) {
558
+ return "Too much attachment data is staged in the sandbox at once. Retry shortly.";
559
+ }
560
+ return "I'm unable to connect to the sandbox right now. This usually means the sandbox service is not configured or is temporarily unavailable.";
561
+ }
562
+
158
563
  // src/sandbox/workspace-sandbox-manager.ts
159
564
  function createWorkspaceSandboxManager(opts) {
160
565
  return {
@@ -1250,10 +1655,14 @@ async function ensureWorkspaceSandbox(shell, options) {
1250
1655
  } catch (err) {
1251
1656
  if (!shell.replaceUnbringableBox) throw err;
1252
1657
  if (options.forceNew) throw err;
1253
- if (!(err instanceof SandboxRecoveryFailedError)) throw err;
1658
+ if (!isUnbringableBoxError(err)) throw err;
1254
1659
  return await provisionWorkspaceSandbox(shell, { ...options, forceNew: true });
1255
1660
  }
1256
1661
  }
1662
+ function isUnbringableBoxError(error) {
1663
+ if (error instanceof SandboxRecoveryFailedError) return true;
1664
+ return isSandboxHostCapacityFailure(serializeSandboxProvisioningError(error));
1665
+ }
1257
1666
  async function provisionWorkspaceSandbox(shell, options) {
1258
1667
  const { workspaceId, userId, harness, forceNew, onProgress, billingOwnerId } = options;
1259
1668
  const resolved = await resolveWorkspaceSandboxClient(shell, workspaceId, userId);
@@ -1735,6 +2144,35 @@ export {
1735
2144
  shellQuote,
1736
2145
  statSandboxFileSize,
1737
2146
  readSandboxBinaryBytes,
2147
+ EGRESS_PROXY_RECOVERY_REQUIRED,
2148
+ EGRESS_PROXY_RECOVERY_PHASE,
2149
+ WORKSPACE_SANDBOX_MISSING,
2150
+ WORKSPACE_SANDBOX_HOST_EXHAUSTED,
2151
+ WORKSPACE_SANDBOX_UNRECOVERABLE,
2152
+ WORKSPACE_SANDBOX_SNAPSHOT_MAX_AGE_MS,
2153
+ WorkspaceSandboxRecoveryRequiredError,
2154
+ isEgressProxyRecoveryRequiredError,
2155
+ isWorkspaceSandboxSnapshotRestoreError,
2156
+ assessWorkspaceSandboxSnapshot,
2157
+ isWorkspaceSandboxRecoveryAction,
2158
+ isWorkspaceSandboxRecoveryCode,
2159
+ isWorkspaceSandboxRecoveryState,
2160
+ workspaceSandboxRecoveryFromError,
2161
+ workspaceSandboxRecoveryMessage,
2162
+ workspaceSandboxRecoveryRecommendedActions,
2163
+ workspaceSandboxRecoveryDiagnostic,
2164
+ preferredWorkspaceSandboxRecoveryBoxKey,
2165
+ shouldRestoreWorkspaceSandboxRecovery,
2166
+ createWorkspaceSandboxRecoveryManager,
2167
+ WORKSPACE_SANDBOX_RECOVERY_ACTIONS,
2168
+ WORKSPACE_SANDBOX_RECOVERY_CODES,
2169
+ serializeSandboxProvisioningError,
2170
+ formatSandboxProvisioningSupportDetails,
2171
+ isSandboxAuthFailure,
2172
+ isSandboxApiBearerAuthFailure,
2173
+ isSandboxApiSandboxMissingFailure,
2174
+ isSandboxHostCapacityFailure,
2175
+ formatSandboxProvisioningUserMessage,
1738
2176
  createWorkspaceSandboxManager,
1739
2177
  createSandboxTerminalConnectionRoute,
1740
2178
  createSandboxPrewarmer,
@@ -1786,4 +2224,4 @@ export {
1786
2224
  isTerminalPromptEvent,
1787
2225
  detectInteractiveQuestion
1788
2226
  };
1789
- //# sourceMappingURL=chunk-CS6MJDIM.js.map
2227
+ //# sourceMappingURL=chunk-4XNCMUVI.js.map