sliccy 6.105.1 → 6.105.2

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.
Files changed (97) hide show
  1. package/dist/node-server/_cloud_core/src/operations/list.d.ts.map +1 -1
  2. package/dist/node-server/_cloud_core/src/operations/list.js +149 -115
  3. package/dist/ui/.vite/manifest.json +118 -118
  4. package/dist/ui/assets/{account-store-BzlZkBRi.js → account-store-BaTmqQCH.js} +1 -1
  5. package/dist/ui/assets/{account-store-DLMQFidg.js → account-store-CHgSp6SB.js} +1 -1
  6. package/dist/ui/assets/{adobe-BihjR2aN.js → adobe-CH2DsFrY.js} +1 -1
  7. package/dist/ui/assets/{adobe-BMlAh6hv.js → adobe-CpalWkIK.js} +1 -1
  8. package/dist/ui/assets/{agent-panels-BU1YRtd6.js → agent-panels-OnTGHQkG.js} +1 -1
  9. package/dist/ui/assets/{apply-layout-D91iJnwj.js → apply-layout-B4zUEdwn.js} +1 -1
  10. package/dist/ui/assets/{apps-C_U-KsGp.js → apps-KhIC51KH.js} +1 -1
  11. package/dist/ui/assets/{azure-openai-sF6H5kgW.js → azure-openai-DzFu6LbW.js} +1 -1
  12. package/dist/ui/assets/{azure-openai-Bw0uWZGO.js → azure-openai-uxQv3w4B.js} +1 -1
  13. package/dist/ui/assets/cdp-ws-page-bridge-DI26JBMr.js +1 -0
  14. package/dist/ui/assets/{cerebras-BXHbMJZ4.js → cerebras-r-in8Inh.js} +1 -1
  15. package/dist/ui/assets/{cerebras-YRvd81gB.js → cerebras-uwqZLHdF.js} +1 -1
  16. package/dist/ui/assets/{connect-surface-xmzMSsEa.js → connect-surface-COZ8qeD4.js} +1 -1
  17. package/dist/ui/assets/{fs-CfV7z7u1.js → fs-B7WAwF-q.js} +1 -1
  18. package/dist/ui/assets/{fs-C5irkDgy.js → fs-CiufgGRG.js} +2 -2
  19. package/dist/ui/assets/{github-CvkSRuma.js → github-C3CflEFq.js} +1 -1
  20. package/dist/ui/assets/{github-dRIJp79S.js → github-CUPlgkJ4.js} +2 -2
  21. package/dist/ui/assets/{github-copilot-Cq69Q1S1.js → github-copilot-BGah-ri-.js} +1 -1
  22. package/dist/ui/assets/{github-copilot-BLe6fyKB.js → github-copilot-DnK9K-w0.js} +1 -1
  23. package/dist/ui/assets/{github-install-BvY8B6fA.js → github-install-B3PxKIA9.js} +1 -1
  24. package/dist/ui/assets/{github-zip-D7XHOBXr.js → github-zip-DlpU53sL.js} +1 -1
  25. package/dist/ui/assets/{js-realm-worker-DYtqK8ky.js → js-realm-worker-zKU0v1vE.js} +1 -1
  26. package/dist/ui/assets/{kernel-worker-Biv5_wUN.js → kernel-worker-v5zxjG5E.js} +28 -28
  27. package/dist/ui/assets/{local-llm-0p1lFHjz.js → local-llm-D-etSg11.js} +1 -1
  28. package/dist/ui/assets/{local-llm-BtUdVk1V.js → local-llm-M_xiCmuW.js} +1 -1
  29. package/dist/ui/assets/{main-XDb3cs-j.js → main-BcfY0APV.js} +3 -3
  30. package/dist/ui/assets/{mount-DrBtXals.js → mount-Bx3D0gVr.js} +2 -2
  31. package/dist/ui/assets/{mount-DMQpmO_S.js → mount-CHtLYCJk.js} +1 -1
  32. package/dist/ui/assets/{mount-recovery-0xhp73i7.js → mount-recovery-DL9GlfHA.js} +1 -1
  33. package/dist/ui/assets/new-session-COA3q4w7.js +2 -0
  34. package/dist/ui/assets/{oauth-bootstrap-DYTFzmoM.js → oauth-bootstrap-BPR0IgmZ.js} +2 -2
  35. package/dist/ui/assets/{onboarding-orchestrator-C3hpm1Jf.js → onboarding-orchestrator-5WhumpNJ.js} +1 -1
  36. package/dist/ui/assets/{openai-codex-BI6aAgz-.js → openai-codex-CjwoH0Hy.js} +1 -1
  37. package/dist/ui/assets/{openai-codex-LoXO8Ljg.js → openai-codex-pUiCqrNz.js} +1 -1
  38. package/dist/ui/assets/{openrouter-D2APEIUs.js → openrouter-BCxd8O71.js} +1 -1
  39. package/dist/ui/assets/{openrouter-BKx5Z6bM.js → openrouter-CdL4vTyx.js} +1 -1
  40. package/dist/ui/assets/{openrouter-oauth-D4wSzWV1.js → openrouter-oauth-CsUVNT2k.js} +1 -1
  41. package/dist/ui/assets/{openrouter-oauth-CwACvxY6.js → openrouter-oauth-Dj48fB3X.js} +1 -1
  42. package/dist/ui/assets/{panel-rpc-handlers-Brwn-P2J.js → panel-rpc-handlers-QwtLrbX7.js} +2 -2
  43. package/dist/ui/assets/{panelize-shell-DOZyJ0fa.js → panelize-shell-CPME7tYP.js} +1 -1
  44. package/dist/ui/assets/provider-CKVeq844.js +2 -0
  45. package/dist/ui/assets/{provider-Ck4d3QKr.js → provider-CR46uvW6.js} +1 -1
  46. package/dist/ui/assets/{provider-settings-HVEYL_IN.js → provider-settings-e94aWkxT.js} +1 -1
  47. package/dist/ui/assets/provider-store-access-CamE9gn8.js +1 -0
  48. package/dist/ui/assets/provider-store-access-wSpMHw1M.js +1 -0
  49. package/dist/ui/assets/{providers-Bv-7eK6T.js → providers-Bpgm6hcR.js} +1 -1
  50. package/dist/ui/assets/{providers-Db6Bcpqh.js → providers-N-GzEZuq.js} +2 -2
  51. package/dist/ui/assets/{quick-llm-r7iioCbc.js → quick-llm-Dx10uCk3.js} +1 -1
  52. package/dist/ui/assets/{recovery-screen-0XV-viDe.js → recovery-screen-Xm3PO1gX.js} +1 -1
  53. package/dist/ui/assets/{redirect-uri-D2xDbDb-.js → redirect-uri-BiQsjA10.js} +1 -1
  54. package/dist/ui/assets/{run-DtXEX6sA.js → run-Bu6cgQMI.js} +1 -1
  55. package/dist/ui/assets/{setup-feature-flags-remote-CqZBHh5l.js → setup-feature-flags-remote-Cf37xnQ1.js} +1 -1
  56. package/dist/ui/assets/sidecar-repair-6yH0jK3Q.js +1 -0
  57. package/dist/ui/assets/{skills-B1sr_Tq0.js → skills-Bm5AxgzO.js} +1 -1
  58. package/dist/ui/assets/slicc-version-rYLQXBjd.js +1 -0
  59. package/dist/ui/assets/{state-DreMrf2k.js → state-CJZU3MUJ.js} +1 -1
  60. package/dist/ui/assets/{store-TMWGaifB.js → store-ByoR63_A.js} +1 -1
  61. package/dist/ui/assets/store-CKhg-xXm.js +1 -0
  62. package/dist/ui/assets/{sudo-COiu75CG.js → sudo-gXakxwpD.js} +1 -1
  63. package/dist/ui/assets/{sync-dialog-D4gGyFPx.js → sync-dialog-CtrrU_-X.js} +1 -1
  64. package/dist/ui/assets/{tray-sidecar-CVKekXJl.js → tray-sidecar-DP-OKeYT.js} +1 -1
  65. package/dist/ui/assets/{upgrade-detection-yf4VBHJa.js → upgrade-detection-4KedpK0u.js} +1 -1
  66. package/dist/ui/assets/{voice-reply-CdQyjZ4U.js → voice-reply-DJDzh5c_.js} +1 -1
  67. package/dist/ui/assets/{wc-attach-DsY3Di7b.js → wc-attach-CHoh_npa.js} +2 -2
  68. package/dist/ui/assets/{wc-extension-BJjoLztQ.js → wc-extension-DinzzE-u.js} +2 -2
  69. package/dist/ui/assets/{wc-float-label-BYMhctbP.js → wc-float-label-_5Qu7XYI.js} +1 -1
  70. package/dist/ui/assets/{wc-floatbar-online-adsNHm5r.js → wc-floatbar-online-DdJjsUiB.js} +1 -1
  71. package/dist/ui/assets/{wc-follower-oauth-CkSuotR4.js → wc-follower-oauth-CQymgqdo.js} +1 -1
  72. package/dist/ui/assets/{wc-follower-Dvb_NBfE.js → wc-follower-orVLyho6.js} +3 -3
  73. package/dist/ui/assets/{wc-live-ChSZdFGO.js → wc-live-Y30MNXP6.js} +6 -6
  74. package/dist/ui/assets/{wc-nav-DML7srXc.js → wc-nav-CObJ1-cm.js} +2 -2
  75. package/dist/ui/assets/{wc-onboarding-BJcv2HxO.js → wc-onboarding-13bz6Y1P.js} +2 -2
  76. package/dist/ui/assets/{wc-placeholder-CBTA32WV.js → wc-placeholder-DEerShZk.js} +3 -3
  77. package/dist/ui/assets/{wc-settings-CTbiADwY.js → wc-settings-S7sjuH3z.js} +3 -3
  78. package/dist/ui/assets/{wc-shell-DGGTq6Dg.js → wc-shell-DHFdDKPB.js} +2 -2
  79. package/dist/ui/assets/{wc-shortcut-config-6WhfEkWk.js → wc-shortcut-config-CY9y5JD2.js} +1 -1
  80. package/dist/ui/assets/{wc-sprinkles-CPStewTt.js → wc-sprinkles-BX0o3VdV.js} +2 -2
  81. package/dist/ui/assets/{wc-transcript-export-DBgvfYAE.js → wc-transcript-export-BS7RBV-1.js} +2 -2
  82. package/dist/ui/assets/{wc-tray-C5ousbvR.js → wc-tray-CdT1k5xd.js} +2 -2
  83. package/dist/ui/assets/welcome-detection-DbHTpMYI.js +1 -0
  84. package/dist/ui/assets/{xai-grok-BMwfBJts.js → xai-grok-DaoF-Hq_.js} +1 -1
  85. package/dist/ui/assets/{xai-grok-DrvDcz8F.js → xai-grok-_z4Y3sUx.js} +1 -1
  86. package/dist/ui/index.html +3 -3
  87. package/dist/ui/packages/webapp/index.html +3 -3
  88. package/package.json +1 -1
  89. package/dist/ui/assets/cdp-ws-page-bridge-D_PY6ku1.js +0 -1
  90. package/dist/ui/assets/new-session-tkh2Pth8.js +0 -2
  91. package/dist/ui/assets/provider-CMyVpl4V.js +0 -2
  92. package/dist/ui/assets/provider-store-access-C4sEMSPk.js +0 -1
  93. package/dist/ui/assets/provider-store-access-DyMm3fYa.js +0 -1
  94. package/dist/ui/assets/sidecar-repair-Ct_a2-ms.js +0 -1
  95. package/dist/ui/assets/slicc-version-D3NdtGfe.js +0 -1
  96. package/dist/ui/assets/store-C7oltmmm.js +0 -1
  97. package/dist/ui/assets/welcome-detection-Bripv_Hp.js +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../../src/operations/list.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,gBAAgB,CAAC;IAC5B,QAAQ,EAAE,QAAQ,CAAC;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACnC;AAED;;;;;;;GAOG;AACH,wBAAsB,SAAS,CAC7B,IAAI,EAAE,aAAa,EACnB,IAAI,GAAE,aAAkB,GACvB,OAAO,CAAC,SAAS,EAAE,CAAC,CA8ItB"}
1
+ {"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../../src/operations/list.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,KAAK,EAAE,SAAS,EAAkB,MAAM,aAAa,CAAC;AAE7D,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,gBAAgB,CAAC;IAC5B,QAAQ,EAAE,QAAQ,CAAC;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACnC;AAwLD;;;;;;;GAOG;AACH,wBAAsB,SAAS,CAC7B,IAAI,EAAE,aAAa,EACnB,IAAI,GAAE,aAAkB,GACvB,OAAO,CAAC,SAAS,EAAE,CAAC,CA8BtB"}
@@ -1,3 +1,146 @@
1
+ const STALE_RESERVATION_MS = 10 * 60 * 1000;
2
+ const DEFAULT_TTL_MS = 60 * 60 * 1000;
3
+ /** True when entry metadata matches every key/value in the filter (or no filter). */
4
+ function matchesMetadataFilter(entry, metadata) {
5
+ if (!metadata)
6
+ return true;
7
+ if (!entry.metadata)
8
+ return false;
9
+ for (const [k, v] of Object.entries(metadata)) {
10
+ if (entry.metadata[k] !== v)
11
+ return false;
12
+ }
13
+ return true;
14
+ }
15
+ function isStaleReservation(entry) {
16
+ // Reclaim if stale OR if reservedAt is missing (legacy/malformed entry
17
+ // from before commit e9011ba6 — could otherwise wedge cap forever).
18
+ if (!entry.reservedAt)
19
+ return true;
20
+ return Date.now() - new Date(entry.reservedAt).getTime() > STALE_RESERVATION_MS;
21
+ }
22
+ /**
23
+ * Handle a reserved registry entry: reclaim if stale, otherwise keep.
24
+ * Returns the entry to include in the reconciled list, or null if reclaimed.
25
+ */
26
+ async function reconcileReserved(entry, registry) {
27
+ if (!isStaleReservation(entry))
28
+ return entry;
29
+ await registry.remove(entry.sandboxId);
30
+ console.warn('[cloud-core] reclaimed stale reservation', {
31
+ sandboxId: entry.sandboxId,
32
+ reservedAt: entry.reservedAt ?? '(missing)',
33
+ });
34
+ return null;
35
+ }
36
+ /** Mark a missing real sandbox dead (idempotent registry write). */
37
+ async function markDead(entry, registry) {
38
+ if (entry.state !== 'dead') {
39
+ await registry.update(entry.sandboxId, { state: 'dead' });
40
+ }
41
+ return { ...entry, state: 'dead' };
42
+ }
43
+ /**
44
+ * Reconcile one non-reserved registry entry against live substrate state.
45
+ * Mutates liveById (deletes matched sandbox ids).
46
+ */
47
+ async function reconcileLiveEntry(entry, liveById, registry) {
48
+ const liveEntry = liveById.get(entry.sandboxId);
49
+ if (!liveEntry) {
50
+ // Substrate doesn't know about it — mark dead unless it's a placeholder.
51
+ // The 'pending-' prefix is a sentinel for "no real sandbox yet" (paired
52
+ // with state:'reserved' by reserveSlot before substrate.create).
53
+ if (entry.sandboxId.startsWith('pending-')) {
54
+ return entry;
55
+ }
56
+ return markDead(entry, registry);
57
+ }
58
+ if (entry.state !== liveEntry.state) {
59
+ await registry.update(entry.sandboxId, { state: liveEntry.state });
60
+ }
61
+ liveById.delete(entry.sandboxId);
62
+ return { ...entry, state: liveEntry.state };
63
+ }
64
+ async function reconcileRegistryEntry(entry, liveById, registry) {
65
+ // Reserved entries: check for stale reservations (TTL: 10 min) and reclaim.
66
+ // Stale reservations are from crashed operations that never completed or rolled back.
67
+ if (entry.state === 'reserved') {
68
+ return reconcileReserved(entry, registry);
69
+ }
70
+ return reconcileLiveEntry(entry, liveById, registry);
71
+ }
72
+ /**
73
+ * Recover joinUrl from /tmp/slicc-join.json ONLY when sandbox is running.
74
+ * Calling substrate.connect() on a paused sandbox would RESUME it, which:
75
+ * (a) burns sandbox runtime silently
76
+ * (b) destabilizes the leader chromium mid-list
77
+ * Paused orphans surface with joinUrl='' — UI hides the Open button.
78
+ */
79
+ async function recoverJoinFromSandbox(summary, substrate, seed) {
80
+ if (seed.joinUrl || summary.state !== 'running')
81
+ return seed;
82
+ try {
83
+ const handle = await substrate.connect(summary.sandboxId);
84
+ const joinData = await handle.readFile('/tmp/slicc-join.json');
85
+ const parsed = JSON.parse(joinData);
86
+ return {
87
+ joinUrl: parsed.joinUrl ?? '',
88
+ trayId: seed.trayId ?? parsed.trayId,
89
+ lastJoinUpdatedAt: seed.lastJoinUpdatedAt ?? parsed.updatedAt,
90
+ };
91
+ }
92
+ catch (err) {
93
+ // File not readable (sandbox is transitioning, or file doesn't exist).
94
+ // Leave joinUrl empty — UI will handle gracefully.
95
+ const msg = err instanceof Error ? err.message : String(err);
96
+ console.warn('[cloud-core] orphan recovery readFile failed', {
97
+ sandboxId: summary.sandboxId,
98
+ err: msg,
99
+ });
100
+ return seed;
101
+ }
102
+ }
103
+ /** Rebuild a ConeEntry for a substrate sandbox missing from the registry. */
104
+ async function recoverOrphan(summary, substrate) {
105
+ const now = new Date().toISOString();
106
+ const recoveredJoin = await recoverJoinFromSandbox(summary, substrate, {
107
+ joinUrl: summary.metadata?.['joinUrl'] ?? '',
108
+ trayId: summary.metadata?.['trayId'],
109
+ lastJoinUpdatedAt: summary.metadata?.['lastJoinUpdatedAt'],
110
+ });
111
+ return {
112
+ sandboxId: summary.sandboxId,
113
+ substrate: 'e2b',
114
+ name: summary.metadata?.['name'] ?? summary.name,
115
+ createdAt: summary.metadata?.['createdAt'] ?? now,
116
+ joinUrl: recoveredJoin.joinUrl,
117
+ lastSeen: now,
118
+ state: summary.state,
119
+ trayId: recoveredJoin.trayId,
120
+ lastJoinUpdatedAt: recoveredJoin.lastJoinUpdatedAt,
121
+ metadata: summary.metadata,
122
+ };
123
+ }
124
+ /**
125
+ * Refresh the timeout of every running cone so active users keep their cones
126
+ * alive past the 1h default. Failures are non-fatal — a sandbox that
127
+ * disappears between substrate.list() and extendTimeout() will be discovered
128
+ * dead on the NEXT list call.
129
+ */
130
+ async function extendRunningTimeouts(cones, substrate) {
131
+ await Promise.all(cones
132
+ .filter((c) => c.state === 'running')
133
+ .map(async (c) => {
134
+ try {
135
+ await substrate.extendTimeout(c.sandboxId, DEFAULT_TTL_MS);
136
+ }
137
+ catch (err) {
138
+ // Sandbox may have died between list and extendTimeout; ignore.
139
+ const msg = err instanceof Error ? err.message : String(err);
140
+ console.warn('[cloud-core] extendTimeout failed', { sandboxId: c.sandboxId, err: msg });
141
+ }
142
+ }));
143
+ }
1
144
  /**
2
145
  * List cones reconciling registry against substrate.
3
146
  * - Substrate is source of truth for state (running/paused).
@@ -11,18 +154,6 @@ export async function listCones(deps, opts = {}) {
11
154
  // Pass metadata filter to substrate.list for server-side filtering
12
155
  const live = await deps.substrate.list(opts.metadata ? { metadata: opts.metadata } : undefined);
13
156
  const liveById = new Map(live.map((s) => [s.sandboxId, s]));
14
- // Helper to check if an entry matches the metadata filter
15
- const matchesFilter = (entry) => {
16
- if (!opts.metadata)
17
- return true;
18
- if (!entry.metadata)
19
- return false;
20
- for (const [k, v] of Object.entries(opts.metadata)) {
21
- if (entry.metadata[k] !== v)
22
- return false;
23
- }
24
- return true;
25
- };
26
157
  // Pass 1: walk registry; reconcile against live.
27
158
  // Reconciliation runs for EVERY registry entry regardless of metadata filter
28
159
  // — otherwise zombie entries (e.g. legacy entries without userId metadata)
@@ -31,115 +162,18 @@ export async function listCones(deps, opts = {}) {
31
162
  // per-user view.
32
163
  const reconciled = [];
33
164
  for (const entry of registryEntries) {
34
- // Reserved entries: check for stale reservations (TTL: 10 min) and reclaim.
35
- // Stale reservations are from crashed operations that never completed or rolled back.
36
- if (entry.state === 'reserved') {
37
- const STALE_MS = 10 * 60 * 1000;
38
- // Reclaim if stale OR if reservedAt is missing (legacy/malformed entry
39
- // from before commit e9011ba6 — could otherwise wedge cap forever).
40
- const isStale = !entry.reservedAt || Date.now() - new Date(entry.reservedAt).getTime() > STALE_MS;
41
- if (isStale) {
42
- await deps.registry.remove(entry.sandboxId);
43
- console.warn('[cloud-core] reclaimed stale reservation', {
44
- sandboxId: entry.sandboxId,
45
- reservedAt: entry.reservedAt ?? '(missing)',
46
- });
47
- continue;
48
- }
49
- // Active reservation — preserve as-is (in-flight operation holding a cap slot)
50
- reconciled.push(entry);
51
- continue;
52
- }
53
- const liveEntry = liveById.get(entry.sandboxId);
54
- if (!liveEntry) {
55
- // Substrate doesn't know about it — mark dead unless it's a placeholder.
56
- // The 'pending-' prefix is a sentinel for "no real sandbox yet" (paired
57
- // with state:'reserved' by reserveSlot before substrate.create).
58
- if (entry.sandboxId.startsWith('pending-')) {
59
- // Reservation placeholder — no real sandbox yet. Keep it alive.
60
- reconciled.push(entry);
61
- }
62
- else {
63
- // Real sandbox is missing — mark dead
64
- if (entry.state !== 'dead') {
65
- await deps.registry.update(entry.sandboxId, { state: 'dead' });
66
- }
67
- reconciled.push({ ...entry, state: 'dead' });
68
- }
69
- continue;
70
- }
71
- if (entry.state !== liveEntry.state) {
72
- await deps.registry.update(entry.sandboxId, { state: liveEntry.state });
73
- }
74
- reconciled.push({ ...entry, state: liveEntry.state });
75
- liveById.delete(entry.sandboxId);
165
+ const next = await reconcileRegistryEntry(entry, liveById, deps.registry);
166
+ if (next)
167
+ reconciled.push(next);
76
168
  }
77
169
  // Pass 2: any substrate entries not in registry → recover.
78
170
  for (const summary of liveById.values()) {
79
- const now = new Date().toISOString();
80
- // Try to read the real joinUrl from the sandbox. Orphans created before this
81
- // fix won't have it in metadata, but we can read /tmp/slicc-join.json directly.
82
- let joinUrl = summary.metadata?.['joinUrl'] ?? '';
83
- let trayId = summary.metadata?.['trayId'];
84
- let lastJoinUpdatedAt = summary.metadata?.['lastJoinUpdatedAt'];
85
- // Recover joinUrl from /tmp/slicc-join.json ONLY when sandbox is running.
86
- // Calling substrate.connect() on a paused sandbox would RESUME it, which:
87
- // (a) burns sandbox runtime silently
88
- // (b) destabilizes the leader chromium mid-list
89
- // Paused orphans surface with joinUrl='' — UI hides the Open button.
90
- // User explicitly resumes via /api/cloud/resume which writes a fresh joinUrl.
91
- if (!joinUrl && summary.state === 'running') {
92
- try {
93
- const handle = await deps.substrate.connect(summary.sandboxId);
94
- const joinData = await handle.readFile('/tmp/slicc-join.json');
95
- const parsed = JSON.parse(joinData);
96
- joinUrl = parsed.joinUrl ?? '';
97
- trayId = trayId ?? parsed.trayId;
98
- lastJoinUpdatedAt = lastJoinUpdatedAt ?? parsed.updatedAt;
99
- }
100
- catch (err) {
101
- // File not readable (sandbox is transitioning, or file doesn't exist).
102
- // Leave joinUrl empty — UI will handle gracefully.
103
- const msg = err instanceof Error ? err.message : String(err);
104
- console.warn('[cloud-core] orphan recovery readFile failed', {
105
- sandboxId: summary.sandboxId,
106
- err: msg,
107
- });
108
- }
109
- }
110
- const recovered = {
111
- sandboxId: summary.sandboxId,
112
- substrate: 'e2b',
113
- name: summary.metadata?.['name'] ?? summary.name,
114
- createdAt: summary.metadata?.['createdAt'] ?? now,
115
- joinUrl,
116
- lastSeen: now,
117
- state: summary.state,
118
- trayId,
119
- lastJoinUpdatedAt,
120
- metadata: summary.metadata,
121
- };
171
+ const recovered = await recoverOrphan(summary, deps.substrate);
122
172
  await deps.registry.append(recovered);
123
173
  reconciled.push(recovered);
124
174
  }
125
- // Refresh the timeout of every running cone so active users keep their cones
126
- // alive past the 1h default. Failures are non-fatal — a sandbox that
127
- // disappears between substrate.list() and extendTimeout() will be discovered
128
- // dead on the NEXT list call.
129
- const DEFAULT_TTL_MS = 60 * 60 * 1000;
130
- await Promise.all(reconciled
131
- .filter((c) => c.state === 'running')
132
- .map(async (c) => {
133
- try {
134
- await deps.substrate.extendTimeout(c.sandboxId, DEFAULT_TTL_MS);
135
- }
136
- catch (err) {
137
- // Sandbox may have died between list and extendTimeout; ignore.
138
- const msg = err instanceof Error ? err.message : String(err);
139
- console.warn('[cloud-core] extendTimeout failed', { sandboxId: c.sandboxId, err: msg });
140
- }
141
- }));
175
+ await extendRunningTimeouts(reconciled, deps.substrate);
142
176
  // Apply the metadata filter to the RETURN value only — reconciliation
143
177
  // above ran on all entries to keep zombies from accumulating.
144
- return reconciled.filter(matchesFilter);
178
+ return reconciled.filter((e) => matchesMetadataFilter(e, opts.metadata));
145
179
  }