@tangle-network/agent-app 0.45.5 → 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-QGHQ2IRC.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,8 +155,213 @@ async function readSandboxBinaryBytes(box, absolutePath, expectedSize, options)
155
155
  return { succeeded: true, value: { bytes, size: expectedSize } };
156
156
  }
157
157
 
158
- // src/sandbox/diagnostics.ts
158
+ // src/sandbox/recovery.ts
159
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
160
365
  var SAFE_ERROR_FIELDS = [
161
366
  "code",
162
367
  "status",
@@ -167,7 +372,7 @@ var SAFE_ERROR_FIELDS = [
167
372
  "sidecarVersion",
168
373
  "containerImage"
169
374
  ];
170
- function isRecord(value) {
375
+ function isRecord2(value) {
171
376
  return typeof value === "object" && value !== null;
172
377
  }
173
378
  function asSafeScalar(value) {
@@ -197,18 +402,18 @@ function readMessage(value) {
197
402
  if (typeof value === "string" && value.length > 0) return redactDiagnosticText(value);
198
403
  if (typeof value === "number" && Number.isFinite(value)) return String(value);
199
404
  if (typeof value === "boolean") return String(value);
200
- if (!isRecord(value)) return void 0;
405
+ if (!isRecord2(value)) return void 0;
201
406
  const message = value.message;
202
407
  return typeof message === "string" && message.length > 0 ? redactDiagnosticText(message) : void 0;
203
408
  }
204
409
  function readName(value) {
205
410
  if (value instanceof Error) return redactDiagnosticText(value.name);
206
- if (!isRecord(value)) return void 0;
411
+ if (!isRecord2(value)) return void 0;
207
412
  const name = value.name;
208
413
  return typeof name === "string" && name.length > 0 ? redactDiagnosticText(name) : void 0;
209
414
  }
210
415
  function readCause(value) {
211
- if (!isRecord(value)) return void 0;
416
+ if (!isRecord2(value)) return void 0;
212
417
  return value.cause;
213
418
  }
214
419
  function serializeSandboxProvisioningError(error, options = {}) {
@@ -228,13 +433,13 @@ function serializeSandboxProvisioningError(error, options = {}) {
228
433
  });
229
434
  break;
230
435
  }
231
- if (isRecord(current)) seen.add(current);
436
+ if (isRecord2(current)) seen.add(current);
232
437
  const cause = {};
233
438
  const name = readName(current);
234
439
  const message = readMessage(current);
235
440
  if (name) cause.name = name;
236
441
  if (message) cause.message = message;
237
- if (isRecord(current)) {
442
+ if (isRecord2(current)) {
238
443
  for (const field of SAFE_ERROR_FIELDS) {
239
444
  const safeValue = asSafeScalar(current[field]);
240
445
  if (safeValue !== void 0) {
@@ -245,7 +450,7 @@ function serializeSandboxProvisioningError(error, options = {}) {
245
450
  if (Object.keys(cause).length > 0) causes.push(cause);
246
451
  current = readCause(current);
247
452
  }
248
- if (!cycle && isRecord(current) && seen.has(current)) {
453
+ if (!cycle && isRecord2(current) && seen.has(current)) {
249
454
  cycle = true;
250
455
  causes.push({
251
456
  name: "CauseChainCycle",
@@ -1940,6 +2145,27 @@ export {
1940
2145
  statSandboxFileSize,
1941
2146
  readSandboxBinaryBytes,
1942
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,
1943
2169
  serializeSandboxProvisioningError,
1944
2170
  formatSandboxProvisioningSupportDetails,
1945
2171
  isSandboxAuthFailure,
@@ -1998,4 +2224,4 @@ export {
1998
2224
  isTerminalPromptEvent,
1999
2225
  detectInteractiveQuestion
2000
2226
  };
2001
- //# sourceMappingURL=chunk-QGHQ2IRC.js.map
2227
+ //# sourceMappingURL=chunk-4XNCMUVI.js.map