sliccy 3.5.4 → 3.6.0
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 +1 -2
- package/dist/node-server/_cloud_core/src/errors.d.ts +14 -0
- package/dist/node-server/_cloud_core/src/errors.d.ts.map +1 -0
- package/dist/node-server/_cloud_core/src/errors.js +13 -0
- package/dist/node-server/_cloud_core/src/index.d.ts +19 -0
- package/dist/node-server/_cloud_core/src/index.d.ts.map +1 -0
- package/dist/node-server/_cloud_core/src/index.js +14 -0
- package/dist/node-server/_cloud_core/src/operations/kill.d.ts +18 -0
- package/dist/node-server/_cloud_core/src/operations/kill.d.ts.map +1 -0
- package/dist/node-server/_cloud_core/src/operations/kill.js +32 -0
- package/dist/node-server/_cloud_core/src/operations/list.d.ts +25 -0
- package/dist/node-server/_cloud_core/src/operations/list.d.ts.map +1 -0
- package/dist/node-server/_cloud_core/src/operations/list.js +145 -0
- package/dist/node-server/_cloud_core/src/operations/pause.d.ts +15 -0
- package/dist/node-server/_cloud_core/src/operations/pause.d.ts.map +1 -0
- package/dist/node-server/_cloud_core/src/operations/pause.js +27 -0
- package/dist/node-server/_cloud_core/src/operations/resume.d.ts +22 -0
- package/dist/node-server/_cloud_core/src/operations/resume.d.ts.map +1 -0
- package/dist/node-server/_cloud_core/src/operations/resume.js +86 -0
- package/dist/node-server/_cloud_core/src/operations/start.d.ts +67 -0
- package/dist/node-server/_cloud_core/src/operations/start.d.ts.map +1 -0
- package/dist/node-server/_cloud_core/src/operations/start.js +228 -0
- package/dist/node-server/_cloud_core/src/polling.d.ts +18 -0
- package/dist/node-server/_cloud_core/src/polling.d.ts.map +1 -0
- package/dist/node-server/_cloud_core/src/polling.js +79 -0
- package/dist/node-server/_cloud_core/src/registry.d.ts +27 -0
- package/dist/node-server/_cloud_core/src/registry.d.ts.map +1 -0
- package/dist/node-server/_cloud_core/src/registry.js +1 -0
- package/dist/node-server/_cloud_core/src/secrets-filter.d.ts +2 -0
- package/dist/node-server/_cloud_core/src/secrets-filter.d.ts.map +1 -0
- package/dist/node-server/_cloud_core/src/secrets-filter.js +17 -0
- package/dist/node-server/_cloud_core/src/substrate-factory.d.ts +3 -0
- package/dist/node-server/_cloud_core/src/substrate-factory.d.ts.map +1 -0
- package/dist/node-server/_cloud_core/src/substrate-factory.js +6 -0
- package/dist/node-server/_cloud_core/src/substrate.d.ts +53 -0
- package/dist/node-server/_cloud_core/src/substrate.d.ts.map +1 -0
- package/dist/node-server/_cloud_core/src/substrate.js +1 -0
- package/dist/node-server/_cloud_core/src/substrates/e2b.d.ts +3 -0
- package/dist/node-server/_cloud_core/src/substrates/e2b.d.ts.map +1 -0
- package/dist/node-server/_cloud_core/src/substrates/e2b.js +98 -0
- package/dist/node-server/_cloud_core/src/types.d.ts +94 -0
- package/dist/node-server/_cloud_core/src/types.d.ts.map +1 -0
- package/dist/node-server/_cloud_core/src/types.js +3 -0
- package/dist/node-server/_cloud_core/tests/fixtures/fake-substrate.d.ts +39 -0
- package/dist/node-server/_cloud_core/tests/fixtures/fake-substrate.d.ts.map +1 -0
- package/dist/node-server/_cloud_core/tests/fixtures/fake-substrate.js +75 -0
- package/dist/node-server/_cloud_core/tests/fixtures/index.d.ts +4 -0
- package/dist/node-server/_cloud_core/tests/fixtures/index.d.ts.map +1 -0
- package/dist/node-server/_cloud_core/tests/fixtures/index.js +2 -0
- package/dist/node-server/_cloud_core/tests/fixtures/mem-registry.d.ts +10 -0
- package/dist/node-server/_cloud_core/tests/fixtures/mem-registry.d.ts.map +1 -0
- package/dist/node-server/_cloud_core/tests/fixtures/mem-registry.js +25 -0
- package/dist/node-server/_cloud_core/tests/kill.test.d.ts +2 -0
- package/dist/node-server/_cloud_core/tests/kill.test.d.ts.map +1 -0
- package/dist/node-server/_cloud_core/tests/kill.test.js +164 -0
- package/dist/node-server/_cloud_core/tests/list.test.d.ts +2 -0
- package/dist/node-server/_cloud_core/tests/list.test.d.ts.map +1 -0
- package/dist/node-server/_cloud_core/tests/list.test.js +300 -0
- package/dist/node-server/_cloud_core/tests/pause.test.d.ts +2 -0
- package/dist/node-server/_cloud_core/tests/pause.test.d.ts.map +1 -0
- package/dist/node-server/_cloud_core/tests/pause.test.js +167 -0
- package/dist/node-server/_cloud_core/tests/resume.test.d.ts +2 -0
- package/dist/node-server/_cloud_core/tests/resume.test.d.ts.map +1 -0
- package/dist/node-server/_cloud_core/tests/resume.test.js +203 -0
- package/dist/node-server/_cloud_core/tests/secrets-filter.test.d.ts +2 -0
- package/dist/node-server/_cloud_core/tests/secrets-filter.test.d.ts.map +1 -0
- package/dist/node-server/_cloud_core/tests/secrets-filter.test.js +30 -0
- package/dist/node-server/_cloud_core/tests/start.test.d.ts +2 -0
- package/dist/node-server/_cloud_core/tests/start.test.d.ts.map +1 -0
- package/dist/node-server/_cloud_core/tests/start.test.js +513 -0
- package/dist/node-server/chrome-launch.d.ts +1 -0
- package/dist/node-server/chrome-launch.js +3 -0
- package/dist/node-server/cloud/dispatch.d.ts +34 -0
- package/dist/node-server/cloud/dispatch.js +79 -0
- package/dist/node-server/cloud/kill.d.ts +12 -0
- package/dist/node-server/cloud/kill.js +11 -0
- package/dist/node-server/cloud/list.d.ts +6 -0
- package/dist/node-server/cloud/list.js +6 -0
- package/dist/node-server/cloud/pause.d.ts +7 -0
- package/dist/node-server/cloud/pause.js +6 -0
- package/dist/node-server/cloud/registry-file.d.ts +13 -0
- package/dist/node-server/cloud/registry-file.js +85 -0
- package/dist/node-server/cloud/resume.d.ts +11 -0
- package/dist/node-server/cloud/resume.js +17 -0
- package/dist/node-server/cloud/start.d.ts +16 -0
- package/dist/node-server/cloud/start.js +49 -0
- package/dist/node-server/cloud-status.d.ts +11 -0
- package/dist/node-server/cloud-status.js +49 -0
- package/dist/node-server/hosted-bootstrap.d.ts +21 -0
- package/dist/node-server/hosted-bootstrap.js +14 -0
- package/dist/node-server/index.js +146 -3
- package/dist/node-server/leader-restart.d.ts +29 -0
- package/dist/node-server/leader-restart.js +146 -0
- package/dist/node-server/runtime-flags.d.ts +1 -0
- package/dist/node-server/runtime-flags.js +6 -0
- package/dist/ui/assets/adobe-BLY_1hN7.js +1 -0
- package/dist/ui/assets/adobe-BZS2Kmci.js +2 -0
- package/dist/ui/assets/agent-bridge-BFRFxJAl.js +1 -0
- package/dist/ui/assets/{agent-message-to-chat-H07zq-Kv.js → agent-message-to-chat-Cd-DQATg.js} +1 -1
- package/dist/ui/assets/anthropic-BbwUhO9H.js +39 -0
- package/dist/ui/assets/{anthropic-BK3Od7MT.js → anthropic-Dg9_3G1u.js} +3 -3
- package/dist/ui/assets/{apps-mZJxOz_q.js → apps-BQuoIsxV.js} +1 -1
- package/dist/ui/assets/azure-openai-CilK3M_Q.js +1 -0
- package/dist/ui/assets/{azure-openai-Dch5aR9j.js → azure-openai-CleJGzBE.js} +1 -1
- package/dist/ui/assets/azure-openai-responses-DMLD0Ano.js +1 -0
- package/dist/ui/assets/azure-openai-responses-_joBD1ru.js +1 -0
- package/dist/ui/assets/{bsh-watchdog-i5GXbvla.js → bsh-watchdog-CpJwB685.js} +1 -1
- package/dist/ui/assets/{cdp-Cv6W51Ld.js → cdp-BXSNdZRM.js} +3 -3
- package/dist/ui/assets/cloud-CCHD45tb.js +1 -0
- package/dist/ui/assets/cloud-DyAca2cv.css +1 -0
- package/dist/ui/assets/cost-command-Bv5UQVUC.js +1 -0
- package/dist/ui/assets/{dist-cK1g1pHF.js → dist-ChvbMSOY.js} +1 -1
- package/dist/ui/assets/{dist-BTGs16AV.js → dist-CltZJzbd.js} +1 -1
- package/dist/ui/assets/{dist-BcBxL8nL.js → dist-cZSF0917.js} +1 -1
- package/dist/ui/assets/{dist-Bgcuvc8h.js → dist-ljtB3O-J.js} +1 -1
- package/dist/ui/assets/{dist-EkY3RL7q.js → dist-tLLu0PBg.js} +1 -1
- package/dist/ui/assets/{es-Crcyr8Z7.js → es-C2wm9v2H.js} +1 -1
- package/dist/ui/assets/follower-sprinkle-bridge-Bg1mCcnG.js +1 -0
- package/dist/ui/assets/{github-DKcpc18I.js → github-BDzC2pCD.js} +1 -1
- package/dist/ui/assets/github-BzwlgA3s.js +2 -0
- package/dist/ui/assets/{google-IZXg3PJn.js → google-BVxsfaV3.js} +1 -1
- package/dist/ui/assets/google-C0Zd1qsl.js +1 -0
- package/dist/ui/assets/google-shared-DcZhZ_Z7.js +11 -0
- package/dist/ui/assets/{google-shared-BaIigNCs.js → google-shared-KyLYJrXa.js} +1 -1
- package/dist/ui/assets/google-vertex-BGpbWEWq.js +1 -0
- package/dist/ui/assets/{google-vertex-DDwmtWoJ.js → google-vertex-BSOalca9.js} +1 -1
- package/dist/ui/assets/{intercepted-oauth-CEgKdcyY.js → intercepted-oauth-D6eAxYca.js} +1 -1
- package/dist/ui/assets/{kernel-worker-dg2bRx7X.js → kernel-worker-D1_EtuZW.js} +1016 -980
- package/dist/ui/assets/{lick-ws-bridge-DgWfdUlv.js → lick-ws-bridge-DXFDOui-.js} +1 -1
- package/dist/ui/assets/{local-llm-DuUGv7wX.js → local-llm-B3qsU55x.js} +1 -1
- package/dist/ui/assets/magick-wasm-CasStH-w.js +1 -0
- package/dist/ui/assets/main-BSfjQXCf.js +1612 -0
- package/dist/ui/assets/{mistral-czd8-m0c.js → mistral-BkhZQqeV.js} +1 -1
- package/dist/ui/assets/{mistral-BS_Gm2MH.js → mistral-D130SR-b.js} +1 -1
- package/dist/ui/assets/modulepreload-polyfill-wMinxHhO.js +1 -0
- package/dist/ui/assets/nuke-command-D_sxu_AA.js +1 -0
- package/dist/ui/assets/{oauth-CJxNn7hC.js → oauth-BocTO4G9.js} +1 -1
- package/dist/ui/assets/oauth-bootstrap-DZdd7KZS.js +2 -0
- package/dist/ui/assets/{oauth-service-BAnfipgD.js → oauth-service-DAWR1CpY.js} +1 -1
- package/dist/ui/assets/oauth-service-y04jhWb_.js +1 -0
- package/dist/ui/assets/offscreen-client-HCLuFFlN.js +1 -0
- package/dist/ui/assets/onboarding-orchestrator-qbEVluIu.js +1 -0
- package/dist/ui/assets/openai-codex-responses-Bmd17Jq9.js +7 -0
- package/dist/ui/assets/openai-codex-responses-CMOclqki.js +7 -0
- package/dist/ui/assets/openai-completions-DmwgEsb4.js +5 -0
- package/dist/ui/assets/openai-responses-BYdEOxUq.js +1 -0
- package/dist/ui/assets/openai-responses-C3bs8sd1.js +1 -0
- package/dist/ui/assets/{openai-responses-shared-DDootAUL.js → openai-responses-shared-AM04ghTC.js} +1 -1
- package/dist/ui/assets/{openai-responses-shared-07lPyxEg.js → openai-responses-shared-Be22tDzQ.js} +1 -1
- package/dist/ui/assets/openrouter-BIFqVYxG.js +1 -0
- package/dist/ui/assets/openrouter-Dc1D0QOS.js +1 -0
- package/dist/ui/assets/panel-rpc-handlers-DDaHHzWR.js +1 -0
- package/dist/ui/assets/{provider-rBD-3FHW.js → provider-CU5ao6kF.js} +2 -2
- package/dist/ui/assets/{provider-BhabAJGA.js → provider-ChUiWhH0.js} +1 -1
- package/dist/ui/assets/provider-store-access-Bliwcbgg.js +1 -0
- package/dist/ui/assets/provider-store-access-iTh_6mMO.js +1 -0
- package/dist/ui/assets/proxied-fetch-8v3TaZYg.js +1 -0
- package/dist/ui/assets/{pyodide-BC4PaR54.js → pyodide-B7x60zRI.js} +2 -2
- package/dist/ui/assets/{remote-terminal-view-CXbFLcuS.js → remote-terminal-view-BREvd00U.js} +1 -1
- package/dist/ui/assets/secret-env-Js2g2sNO.js +1 -0
- package/dist/ui/assets/slicc-editor-Bu07BjW4.js +12 -0
- package/dist/ui/assets/{spawn-qtlw6x6p.js → spawn-CQ5XACZ1.js} +1 -1
- package/dist/ui/assets/{sql-wasm-DeVtn0z8.js → sql-wasm-C-pXJvOw.js} +1 -1
- package/dist/ui/assets/{store-DLeVoNF3.js → store-DQUahHdW.js} +1 -1
- package/dist/ui/assets/tray-leave-runtime-BEBMIYY_.js +1 -0
- package/dist/ui/assets/{upgrade-detection-CKrzeeRx.js → upgrade-detection-T74KTnyb.js} +1 -1
- package/dist/ui/assets/{xai-grok-CNyu04Tg.js → xai-grok-CkPPMxfZ.js} +1 -1
- package/dist/ui/assets/{xai-grok-sanitize-BeI8rdV5.js → xai-grok-sanitize-C7TgWvwD.js} +1 -1
- package/dist/ui/assets/{xai-grok-sanitize-h_8XMTD9.js → xai-grok-sanitize-L5nUbDcW.js} +1 -1
- package/dist/ui/assets/xai-grok-u9MyUN8m.js +1 -0
- package/dist/ui/electron-overlay-entry.js +9 -9
- package/dist/ui/index.html +3 -2
- package/dist/ui/packages/webapp/cloud/index.html +38 -0
- package/dist/ui/packages/webapp/index.html +3 -2
- package/package.json +10 -8
- package/dist/ui/assets/adobe-DpCIcyEb.js +0 -2
- package/dist/ui/assets/adobe-NMBK1vG2.js +0 -1
- package/dist/ui/assets/agent-bridge-D2k69QZ-.js +0 -1
- package/dist/ui/assets/anthropic-ZtfIcLyL.js +0 -39
- package/dist/ui/assets/azure-openai-CN9QA_pv.js +0 -1
- package/dist/ui/assets/azure-openai-responses-Bhr0mNdj.js +0 -1
- package/dist/ui/assets/azure-openai-responses-D400BxTc.js +0 -1
- package/dist/ui/assets/cost-command-BGqK_hbH.js +0 -1
- package/dist/ui/assets/follower-sprinkle-bridge-BayCf20v.js +0 -1
- package/dist/ui/assets/github-DpDjTcq9.js +0 -2
- package/dist/ui/assets/google-B97zVBac.js +0 -1
- package/dist/ui/assets/google-shared-DwzANLbz.js +0 -11
- package/dist/ui/assets/google-vertex-ICKJGS1M.js +0 -1
- package/dist/ui/assets/index-CCGqFzR9.js +0 -1575
- package/dist/ui/assets/magick-wasm-Bu7zx3Ux.js +0 -1
- package/dist/ui/assets/nuke-command-BSkSFZ4H.js +0 -1
- package/dist/ui/assets/oauth-bootstrap-BeZQrd7y.js +0 -2
- package/dist/ui/assets/oauth-service-BX55wTV9.js +0 -1
- package/dist/ui/assets/offscreen-client-DSEhUCxj.js +0 -1
- package/dist/ui/assets/onboarding-orchestrator-DjNti83y.js +0 -1
- package/dist/ui/assets/openai-codex-responses-DJKJH9AX.js +0 -7
- package/dist/ui/assets/openai-codex-responses-jB5RkWeq.js +0 -7
- package/dist/ui/assets/openai-completions-BWWM3S2J.js +0 -5
- package/dist/ui/assets/openai-responses-C_3YpAmj.js +0 -1
- package/dist/ui/assets/openai-responses-tJegTQYT.js +0 -1
- package/dist/ui/assets/panel-rpc-handlers-B4jZTNHO.js +0 -1
- package/dist/ui/assets/provider-store-access-H9Lu5HRL.js +0 -1
- package/dist/ui/assets/provider-store-access-biaB--zG.js +0 -1
- package/dist/ui/assets/proxied-fetch-C6dm2Ugc.js +0 -1
- package/dist/ui/assets/secret-env-s_8cP7Wf.js +0 -1
- package/dist/ui/assets/slicc-editor-pZcWEwTm.js +0 -12
- package/dist/ui/assets/tray-leave-runtime-DqA9PbxV.js +0 -1
- package/dist/ui/assets/xai-grok-CN1nP4TF.js +0 -1
- /package/dist/ui/assets/{active-transport-CfU1UxY6.js → active-transport-Clj9slj7.js} +0 -0
- /package/dist/ui/assets/{active-transport-Dm6CWYVE.js → active-transport-DHcaBd4j.js} +0 -0
- /package/dist/ui/assets/{addon-fit-BDUxDgg1.js → addon-fit-CBG3Hsec.js} +0 -0
- /package/dist/ui/assets/{addon-fit-Dkm5m5XK.js → addon-fit-CUt9lrQO.js} +0 -0
- /package/dist/ui/assets/{azure-ai-foundry-BHLfY_0t.js → azure-ai-foundry-B4QrVklT.js} +0 -0
- /package/dist/ui/assets/{azure-ai-foundry-1pnZpxAJ.js → azure-ai-foundry-CGkSI_4f.js} +0 -0
- /package/dist/ui/assets/{chat-fixture-DOzffHLr.js → chat-fixture-ChGRNFda.js} +0 -0
- /package/dist/ui/assets/{dist-BYoCEWmq.js → dist-9RlRmGag.js} +0 -0
- /package/dist/ui/assets/{dist-BNe7ZIzA.js → dist-CZcDRTma.js} +0 -0
- /package/dist/ui/assets/{dist-BrjVVq8c.js → dist-D0kTaNET.js} +0 -0
- /package/dist/ui/assets/{git-config-CGMfR0WK.js → git-config-DcQbkikl.js} +0 -0
- /package/dist/ui/assets/{github-copilot-headers-CTUP_cqn.js → github-copilot-headers-C5CocKfn.js} +0 -0
- /package/dist/ui/assets/{global-db-B97_fwjD.js → global-db-DL3jdM9H.js} +0 -0
- /package/dist/ui/assets/{hash-B9Dtr1VK.js → hash-iitWbNjK.js} +0 -0
- /package/dist/ui/assets/{headers-AeYLn7L4.js → headers-qDMOQQGF.js} +0 -0
- /package/dist/ui/assets/{lick-manager-proxy-BnnMFbgv.js → lick-manager-proxy-1L0EGIWD.js} +0 -0
- /package/dist/ui/assets/{index-CvlDeETm.css → main-CvlDeETm.css} +0 -0
- /package/dist/ui/assets/{openai-Bf1npfRy.js → openai-BR_xAflc.js} +0 -0
- /package/dist/ui/assets/{page-storage-sync-CqMAh8jG.js → page-storage-sync-sv2ogNKh.js} +0 -0
- /package/dist/ui/assets/{pdfjs-DDDkZHZ4.js → pdfjs-CW51NNE6.js} +0 -0
- /package/dist/ui/assets/{rum-DCU8sePD.js → rum-z5wz4Dnj.js} +0 -0
- /package/dist/ui/assets/{sanitize-unicode-h0KEBmEw.js → sanitize-unicode-B1z_Y4Dq.js} +0 -0
- /package/dist/ui/assets/{sprinkle-bridge-channel-DojIojh7.js → sprinkle-bridge-channel-Bw5UaanD.js} +0 -0
- /package/dist/ui/assets/{sprinkle-bridge-channel-DJC5SGWr.js → sprinkle-bridge-channel-ChD0T07C.js} +0 -0
- /package/dist/ui/assets/{sprinkle-proxy-rF3NSOGq.js → sprinkle-proxy-D6wqmewK.js} +0 -0
- /package/dist/ui/assets/{src-BQRb5emh.js → src-DRmQ1wr-.js} +0 -0
- /package/dist/ui/assets/{xai-grok-errors-Bzn3oNvW.js → xai-grok-errors-BdY43UIn.js} +0 -0
- /package/dist/ui/assets/{xai-grok-errors-WbFsMZum.js → xai-grok-errors-C6VnD_MH.js} +0 -0
- /package/dist/ui/assets/{xai-grok-models-cvcIXmNM.js → xai-grok-models-BndZgIFZ.js} +0 -0
- /package/dist/ui/assets/{xai-grok-models-DMYWtapT.js → xai-grok-models-C9MIa-Aj.js} +0 -0
- /package/dist/ui/assets/{xterm-D8ldLLKi.js → xterm-DLCsxsB1.js} +0 -0
- /package/dist/ui/assets/{xterm-DhusGoAm.js → xterm-VndicwFp.js} +0 -0
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { listCones } from '../src/operations/list.js';
|
|
3
|
+
import { MemRegistry, makeFakeSubstrate } from './fixtures/index.js';
|
|
4
|
+
describe('listCones', () => {
|
|
5
|
+
it('returns registry entries enriched with live state', async () => {
|
|
6
|
+
const registry = new MemRegistry();
|
|
7
|
+
await registry.append({
|
|
8
|
+
sandboxId: 's-1',
|
|
9
|
+
substrate: 'e2b',
|
|
10
|
+
createdAt: '',
|
|
11
|
+
lastSeen: '',
|
|
12
|
+
joinUrl: 'https://w/join/s-1',
|
|
13
|
+
state: 'paused',
|
|
14
|
+
});
|
|
15
|
+
const substrate = makeFakeSubstrate({
|
|
16
|
+
listResult: [{ sandboxId: 's-1', state: 'running', metadata: { name: 'mine' } }],
|
|
17
|
+
});
|
|
18
|
+
const result = await listCones({ substrate, registry });
|
|
19
|
+
expect(result).toHaveLength(1);
|
|
20
|
+
expect(result[0]?.state).toBe('running');
|
|
21
|
+
// Registry state flipped:
|
|
22
|
+
expect(registry.entries[0]?.state).toBe('running');
|
|
23
|
+
});
|
|
24
|
+
it("marks registry entries missing from substrate as 'dead'", async () => {
|
|
25
|
+
const registry = new MemRegistry();
|
|
26
|
+
await registry.append({
|
|
27
|
+
sandboxId: 's-stale',
|
|
28
|
+
substrate: 'e2b',
|
|
29
|
+
createdAt: '',
|
|
30
|
+
lastSeen: '',
|
|
31
|
+
joinUrl: 'https://w/join/s-stale',
|
|
32
|
+
state: 'paused',
|
|
33
|
+
});
|
|
34
|
+
const substrate = makeFakeSubstrate({ listResult: [] });
|
|
35
|
+
const result = await listCones({ substrate, registry });
|
|
36
|
+
expect(result[0]?.state).toBe('dead');
|
|
37
|
+
expect(registry.entries[0]?.state).toBe('dead');
|
|
38
|
+
});
|
|
39
|
+
it('rebuilds orphans (substrate sandboxes not in registry) into the registry', async () => {
|
|
40
|
+
const registry = new MemRegistry();
|
|
41
|
+
const substrate = makeFakeSubstrate({
|
|
42
|
+
listResult: [{ sandboxId: 's-orphan', state: 'running', metadata: { name: 'orphan' } }],
|
|
43
|
+
});
|
|
44
|
+
const result = await listCones({ substrate, registry });
|
|
45
|
+
expect(result.some((c) => c.sandboxId === 's-orphan')).toBe(true);
|
|
46
|
+
expect(registry.entries.some((e) => e.sandboxId === 's-orphan')).toBe(true);
|
|
47
|
+
// Check that recovered entry has reasonable defaults:
|
|
48
|
+
const recovered = registry.entries.find((e) => e.sandboxId === 's-orphan');
|
|
49
|
+
expect(recovered?.name).toBe('orphan');
|
|
50
|
+
expect(recovered?.substrate).toBe('e2b');
|
|
51
|
+
expect(recovered?.state).toBe('running');
|
|
52
|
+
});
|
|
53
|
+
it('filters by opts.metadata when provided', async () => {
|
|
54
|
+
const registry = new MemRegistry();
|
|
55
|
+
const substrate = makeFakeSubstrate({
|
|
56
|
+
listResult: [
|
|
57
|
+
{
|
|
58
|
+
sandboxId: 's-mine',
|
|
59
|
+
state: 'running',
|
|
60
|
+
metadata: { userId: 'u1', name: 'mine' },
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
sandboxId: 's-theirs',
|
|
64
|
+
state: 'running',
|
|
65
|
+
metadata: { userId: 'u2', name: 'theirs' },
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
});
|
|
69
|
+
const result = await listCones({ substrate, registry }, { metadata: { userId: 'u1' } });
|
|
70
|
+
expect(result.map((c) => c.sandboxId)).toEqual(['s-mine']);
|
|
71
|
+
});
|
|
72
|
+
it('does not overwrite dead state if already dead', async () => {
|
|
73
|
+
const registry = new MemRegistry();
|
|
74
|
+
await registry.append({
|
|
75
|
+
sandboxId: 's-already-dead',
|
|
76
|
+
substrate: 'e2b',
|
|
77
|
+
createdAt: '',
|
|
78
|
+
lastSeen: '',
|
|
79
|
+
joinUrl: 'https://w/join/s-already-dead',
|
|
80
|
+
state: 'dead',
|
|
81
|
+
});
|
|
82
|
+
const substrate = makeFakeSubstrate({ listResult: [] });
|
|
83
|
+
const result = await listCones({ substrate, registry });
|
|
84
|
+
expect(result[0]?.state).toBe('dead');
|
|
85
|
+
expect(registry.entries[0]?.state).toBe('dead');
|
|
86
|
+
});
|
|
87
|
+
it('does not update registry if state already matches', async () => {
|
|
88
|
+
const registry = new MemRegistry();
|
|
89
|
+
await registry.append({
|
|
90
|
+
sandboxId: 's-running',
|
|
91
|
+
substrate: 'e2b',
|
|
92
|
+
createdAt: '',
|
|
93
|
+
lastSeen: '',
|
|
94
|
+
joinUrl: 'https://w/join/s-running',
|
|
95
|
+
state: 'running',
|
|
96
|
+
});
|
|
97
|
+
const substrate = makeFakeSubstrate({
|
|
98
|
+
listResult: [{ sandboxId: 's-running', state: 'running', metadata: {} }],
|
|
99
|
+
});
|
|
100
|
+
const initialEntry = { ...registry.entries[0] };
|
|
101
|
+
await listCones({ substrate, registry });
|
|
102
|
+
// State should remain unchanged (not re-written):
|
|
103
|
+
expect(registry.entries[0]).toEqual(initialEntry);
|
|
104
|
+
});
|
|
105
|
+
it('recovers metadata fields from substrate during orphan recovery', async () => {
|
|
106
|
+
const registry = new MemRegistry();
|
|
107
|
+
const substrate = makeFakeSubstrate({
|
|
108
|
+
listResult: [
|
|
109
|
+
{
|
|
110
|
+
sandboxId: 's-orphan-with-meta',
|
|
111
|
+
state: 'running',
|
|
112
|
+
metadata: {
|
|
113
|
+
name: 'custom-name',
|
|
114
|
+
createdAt: '2026-01-01T00:00:00Z',
|
|
115
|
+
joinUrl: 'https://custom/join/url',
|
|
116
|
+
trayId: 'tray-123',
|
|
117
|
+
lastJoinUpdatedAt: '2026-01-02T00:00:00Z',
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
});
|
|
122
|
+
const result = await listCones({ substrate, registry });
|
|
123
|
+
const recovered = result.find((c) => c.sandboxId === 's-orphan-with-meta');
|
|
124
|
+
expect(recovered?.name).toBe('custom-name');
|
|
125
|
+
expect(recovered?.createdAt).toBe('2026-01-01T00:00:00Z');
|
|
126
|
+
expect(recovered?.joinUrl).toBe('https://custom/join/url');
|
|
127
|
+
expect(recovered?.trayId).toBe('tray-123');
|
|
128
|
+
expect(recovered?.lastJoinUpdatedAt).toBe('2026-01-02T00:00:00Z');
|
|
129
|
+
});
|
|
130
|
+
it('falls back to reading /tmp/slicc-join.json when metadata lacks joinUrl', async () => {
|
|
131
|
+
const registry = new MemRegistry();
|
|
132
|
+
const substrate = makeFakeSubstrate({
|
|
133
|
+
listResult: [
|
|
134
|
+
{
|
|
135
|
+
sandboxId: 's-orphan-no-metadata',
|
|
136
|
+
state: 'running',
|
|
137
|
+
metadata: { name: 'orphan-fallback' },
|
|
138
|
+
},
|
|
139
|
+
],
|
|
140
|
+
// The default handle will return joinUrl from /tmp/slicc-join.json
|
|
141
|
+
handle: {
|
|
142
|
+
sandboxId: 's-orphan-no-metadata',
|
|
143
|
+
substrate: 'e2b',
|
|
144
|
+
pause: async () => { },
|
|
145
|
+
kill: async () => { },
|
|
146
|
+
getInfo: async () => ({
|
|
147
|
+
sandboxId: 's-orphan-no-metadata',
|
|
148
|
+
state: 'running',
|
|
149
|
+
metadata: {},
|
|
150
|
+
createdAt: new Date().toISOString(),
|
|
151
|
+
}),
|
|
152
|
+
writeFile: async () => { },
|
|
153
|
+
readFile: async (path) => {
|
|
154
|
+
if (path === '/tmp/slicc-join.json') {
|
|
155
|
+
return JSON.stringify({
|
|
156
|
+
joinUrl: 'https://recovered/join/url',
|
|
157
|
+
trayId: 'tray-recovered',
|
|
158
|
+
updatedAt: '2026-05-27T00:00:00Z',
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
throw new Error(`ENOENT ${path}`);
|
|
162
|
+
},
|
|
163
|
+
run: async () => ({ stdout: '', stderr: '', exitCode: 0 }),
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
const result = await listCones({ substrate, registry });
|
|
167
|
+
const recovered = result.find((c) => c.sandboxId === 's-orphan-no-metadata');
|
|
168
|
+
expect(recovered?.joinUrl).toBe('https://recovered/join/url');
|
|
169
|
+
expect(recovered?.trayId).toBe('tray-recovered');
|
|
170
|
+
expect(recovered?.lastJoinUpdatedAt).toBe('2026-05-27T00:00:00Z');
|
|
171
|
+
});
|
|
172
|
+
it('does not call substrate.connect for paused orphans (no auto-resume)', async () => {
|
|
173
|
+
const registry = new MemRegistry();
|
|
174
|
+
// connectError proves connect was NOT called — if it were called, listCones
|
|
175
|
+
// would throw or swallow the error, but we want to prove the branch is skipped.
|
|
176
|
+
const substrate = makeFakeSubstrate({
|
|
177
|
+
listResult: [
|
|
178
|
+
{
|
|
179
|
+
sandboxId: 's-paused-orphan',
|
|
180
|
+
state: 'paused',
|
|
181
|
+
metadata: { name: 'paused-orphan' },
|
|
182
|
+
},
|
|
183
|
+
],
|
|
184
|
+
connectError: new Error('connect should not be called for paused orphans'),
|
|
185
|
+
});
|
|
186
|
+
const result = await listCones({ substrate, registry });
|
|
187
|
+
const recovered = result.find((c) => c.sandboxId === 's-paused-orphan');
|
|
188
|
+
// If connect had been called, the error would have been swallowed by the catch,
|
|
189
|
+
// but we still expect recovery to succeed with empty joinUrl.
|
|
190
|
+
expect(recovered?.joinUrl).toBe('');
|
|
191
|
+
expect(recovered?.state).toBe('paused');
|
|
192
|
+
// Verify the entry was added to registry
|
|
193
|
+
expect(registry.entries.some((e) => e.sandboxId === 's-paused-orphan')).toBe(true);
|
|
194
|
+
});
|
|
195
|
+
it('calls substrate.connect only for running orphans, not paused', async () => {
|
|
196
|
+
const registry = new MemRegistry();
|
|
197
|
+
const connects = [];
|
|
198
|
+
const fakeHandle = makeFakeSubstrate().connect('fake');
|
|
199
|
+
const substrate = {
|
|
200
|
+
id: 'e2b',
|
|
201
|
+
async create() {
|
|
202
|
+
throw new Error('create should not be called');
|
|
203
|
+
},
|
|
204
|
+
async connect(sandboxId) {
|
|
205
|
+
connects.push(sandboxId);
|
|
206
|
+
return fakeHandle;
|
|
207
|
+
},
|
|
208
|
+
async list() {
|
|
209
|
+
return [
|
|
210
|
+
{ sandboxId: 's-running', state: 'running', metadata: { name: 'running' } },
|
|
211
|
+
{ sandboxId: 's-paused', state: 'paused', metadata: { name: 'paused' } },
|
|
212
|
+
];
|
|
213
|
+
},
|
|
214
|
+
async extendTimeout(_sandboxId, _ttlMs) {
|
|
215
|
+
// No-op for this test
|
|
216
|
+
},
|
|
217
|
+
};
|
|
218
|
+
await listCones({ substrate, registry });
|
|
219
|
+
// Only the running sandbox should trigger a connect call
|
|
220
|
+
expect(connects).toEqual(['s-running']);
|
|
221
|
+
// Both orphans should be recovered
|
|
222
|
+
expect(registry.entries).toHaveLength(2);
|
|
223
|
+
const paused = registry.entries.find((e) => e.sandboxId === 's-paused');
|
|
224
|
+
expect(paused?.joinUrl).toBe(''); // No joinUrl recovery for paused
|
|
225
|
+
});
|
|
226
|
+
it('extends timeout on running cones during reconciliation', async () => {
|
|
227
|
+
const registry = new MemRegistry();
|
|
228
|
+
const timeoutCalls = [];
|
|
229
|
+
const substrate = makeFakeSubstrate({
|
|
230
|
+
listResult: [
|
|
231
|
+
{ sandboxId: 's-running', state: 'running', metadata: { userId: 'u1' } },
|
|
232
|
+
{ sandboxId: 's-paused', state: 'paused', metadata: { userId: 'u1' } },
|
|
233
|
+
],
|
|
234
|
+
timeoutCalls,
|
|
235
|
+
});
|
|
236
|
+
await listCones({ substrate, registry });
|
|
237
|
+
const extended = timeoutCalls.map((c) => c.sandboxId);
|
|
238
|
+
expect(extended).toContain('s-running');
|
|
239
|
+
expect(extended).not.toContain('s-paused');
|
|
240
|
+
expect(timeoutCalls[0]?.ttlMs).toBe(60 * 60 * 1000);
|
|
241
|
+
});
|
|
242
|
+
it('preserves reserved entries during reconciliation (not marked dead)', async () => {
|
|
243
|
+
const registry = new MemRegistry();
|
|
244
|
+
const now = new Date().toISOString();
|
|
245
|
+
await registry.append({
|
|
246
|
+
sandboxId: 'pending-abc',
|
|
247
|
+
substrate: 'e2b',
|
|
248
|
+
createdAt: now,
|
|
249
|
+
lastSeen: now,
|
|
250
|
+
joinUrl: '',
|
|
251
|
+
state: 'reserved',
|
|
252
|
+
reservedAt: now,
|
|
253
|
+
});
|
|
254
|
+
const substrate = makeFakeSubstrate({ listResult: [] }); // substrate knows nothing about it
|
|
255
|
+
const result = await listCones({ substrate, registry });
|
|
256
|
+
expect(result).toHaveLength(1);
|
|
257
|
+
expect(result[0]?.state).toBe('reserved');
|
|
258
|
+
expect(result[0]?.sandboxId).toBe('pending-abc');
|
|
259
|
+
// Should NOT have been removed from registry
|
|
260
|
+
expect(registry.entries).toHaveLength(1);
|
|
261
|
+
});
|
|
262
|
+
it('reclaims stale reserved entries (older than 10 min TTL)', async () => {
|
|
263
|
+
const registry = new MemRegistry();
|
|
264
|
+
const staleTime = new Date(Date.now() - 11 * 60 * 1000).toISOString(); // 11 min ago
|
|
265
|
+
await registry.append({
|
|
266
|
+
sandboxId: 'pending-stale',
|
|
267
|
+
substrate: 'e2b',
|
|
268
|
+
createdAt: staleTime,
|
|
269
|
+
lastSeen: staleTime,
|
|
270
|
+
joinUrl: '',
|
|
271
|
+
state: 'reserved',
|
|
272
|
+
reservedAt: staleTime,
|
|
273
|
+
});
|
|
274
|
+
const substrate = makeFakeSubstrate({ listResult: [] });
|
|
275
|
+
const result = await listCones({ substrate, registry });
|
|
276
|
+
// Stale reservation should be reclaimed (not in result)
|
|
277
|
+
expect(result).toHaveLength(0);
|
|
278
|
+
// Should have been removed from registry
|
|
279
|
+
expect(registry.entries).toHaveLength(0);
|
|
280
|
+
});
|
|
281
|
+
it('preserves fresh reserved entries (younger than 10 min TTL)', async () => {
|
|
282
|
+
const registry = new MemRegistry();
|
|
283
|
+
const freshTime = new Date(Date.now() - 5 * 60 * 1000).toISOString(); // 5 min ago
|
|
284
|
+
await registry.append({
|
|
285
|
+
sandboxId: 'pending-fresh',
|
|
286
|
+
substrate: 'e2b',
|
|
287
|
+
createdAt: freshTime,
|
|
288
|
+
lastSeen: freshTime,
|
|
289
|
+
joinUrl: '',
|
|
290
|
+
state: 'reserved',
|
|
291
|
+
reservedAt: freshTime,
|
|
292
|
+
});
|
|
293
|
+
const substrate = makeFakeSubstrate({ listResult: [] });
|
|
294
|
+
const result = await listCones({ substrate, registry });
|
|
295
|
+
// Fresh reservation should be preserved
|
|
296
|
+
expect(result).toHaveLength(1);
|
|
297
|
+
expect(result[0]?.state).toBe('reserved');
|
|
298
|
+
expect(registry.entries).toHaveLength(1);
|
|
299
|
+
});
|
|
300
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pause.test.d.ts","sourceRoot":"","sources":["../../tests/pause.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { pauseCone, CloudError } from '../src/index.js';
|
|
3
|
+
import { MemRegistry, makeFakeSubstrate } from './fixtures/index.js';
|
|
4
|
+
// Specialized handle for pause tests: tracks paused state separately and reflects in getInfo.
|
|
5
|
+
function makePauseTestHandle(id, paused = { value: false }) {
|
|
6
|
+
return {
|
|
7
|
+
sandboxId: id,
|
|
8
|
+
substrate: 'e2b',
|
|
9
|
+
pause: async () => {
|
|
10
|
+
paused.value = true;
|
|
11
|
+
},
|
|
12
|
+
kill: async () => {
|
|
13
|
+
// noop
|
|
14
|
+
},
|
|
15
|
+
getInfo: async () => {
|
|
16
|
+
const state = paused.value ? 'paused' : 'running';
|
|
17
|
+
return {
|
|
18
|
+
sandboxId: id,
|
|
19
|
+
state,
|
|
20
|
+
metadata: {},
|
|
21
|
+
createdAt: new Date().toISOString(),
|
|
22
|
+
};
|
|
23
|
+
},
|
|
24
|
+
writeFile: async () => {
|
|
25
|
+
// noop
|
|
26
|
+
},
|
|
27
|
+
readFile: async () => '',
|
|
28
|
+
run: async () => ({
|
|
29
|
+
stdout: '',
|
|
30
|
+
stderr: '',
|
|
31
|
+
exitCode: 0,
|
|
32
|
+
}),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
describe('pauseCone', () => {
|
|
36
|
+
it('pauses a running cone and updates registry state', async () => {
|
|
37
|
+
const registry = new MemRegistry();
|
|
38
|
+
await registry.append({
|
|
39
|
+
sandboxId: 's-1',
|
|
40
|
+
substrate: 'e2b',
|
|
41
|
+
createdAt: new Date().toISOString(),
|
|
42
|
+
lastSeen: new Date().toISOString(),
|
|
43
|
+
state: 'running',
|
|
44
|
+
joinUrl: 'https://example.com/join',
|
|
45
|
+
});
|
|
46
|
+
const pausedFlag = { value: false };
|
|
47
|
+
const substrate = makeFakeSubstrate({
|
|
48
|
+
handle: makePauseTestHandle('s-1', pausedFlag),
|
|
49
|
+
});
|
|
50
|
+
await pauseCone({ substrate, registry }, 's-1');
|
|
51
|
+
expect(pausedFlag.value).toBe(true);
|
|
52
|
+
const entry = await registry.findByNameOrId('s-1');
|
|
53
|
+
expect(entry).toBeDefined();
|
|
54
|
+
expect(entry?.state).toBe('paused');
|
|
55
|
+
});
|
|
56
|
+
it('throws NOT_FOUND when query does not match', async () => {
|
|
57
|
+
const registry = new MemRegistry();
|
|
58
|
+
const substrate = makeFakeSubstrate({
|
|
59
|
+
handle: makePauseTestHandle('s-x'),
|
|
60
|
+
});
|
|
61
|
+
await expect(pauseCone({ substrate, registry }, 'missing')).rejects.toThrow();
|
|
62
|
+
let thrownErr;
|
|
63
|
+
try {
|
|
64
|
+
await pauseCone({ substrate, registry }, 'missing');
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
thrownErr = err;
|
|
68
|
+
}
|
|
69
|
+
expect(thrownErr).toBeInstanceOf(CloudError);
|
|
70
|
+
const cloudErr = thrownErr;
|
|
71
|
+
expect(cloudErr.code).toBe('NOT_FOUND');
|
|
72
|
+
expect(cloudErr.message).toContain('cloud session not found');
|
|
73
|
+
});
|
|
74
|
+
it('throws ALREADY_PAUSED when entry is already paused', async () => {
|
|
75
|
+
const registry = new MemRegistry();
|
|
76
|
+
await registry.append({
|
|
77
|
+
sandboxId: 's-2',
|
|
78
|
+
substrate: 'e2b',
|
|
79
|
+
createdAt: new Date().toISOString(),
|
|
80
|
+
lastSeen: new Date().toISOString(),
|
|
81
|
+
state: 'paused',
|
|
82
|
+
joinUrl: 'https://example.com/join',
|
|
83
|
+
});
|
|
84
|
+
const substrate = makeFakeSubstrate({
|
|
85
|
+
handle: makePauseTestHandle('s-2'),
|
|
86
|
+
});
|
|
87
|
+
let thrownErr;
|
|
88
|
+
try {
|
|
89
|
+
await pauseCone({ substrate, registry }, 's-2');
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
thrownErr = err;
|
|
93
|
+
}
|
|
94
|
+
expect(thrownErr).toBeInstanceOf(CloudError);
|
|
95
|
+
const cloudErr = thrownErr;
|
|
96
|
+
expect(cloudErr.code).toBe('ALREADY_PAUSED');
|
|
97
|
+
expect(cloudErr.message).toContain('already paused');
|
|
98
|
+
});
|
|
99
|
+
it('preserves trayId and lastJoinUpdatedAt on pause', async () => {
|
|
100
|
+
const registry = new MemRegistry();
|
|
101
|
+
const originalTrayId = 'tray-123';
|
|
102
|
+
const originalLastJoinUpdatedAt = '2026-05-22T10:00:00Z';
|
|
103
|
+
await registry.append({
|
|
104
|
+
sandboxId: 's-3',
|
|
105
|
+
substrate: 'e2b',
|
|
106
|
+
createdAt: new Date().toISOString(),
|
|
107
|
+
lastSeen: new Date().toISOString(),
|
|
108
|
+
state: 'running',
|
|
109
|
+
joinUrl: 'https://example.com/join',
|
|
110
|
+
trayId: originalTrayId,
|
|
111
|
+
lastJoinUpdatedAt: originalLastJoinUpdatedAt,
|
|
112
|
+
});
|
|
113
|
+
const substrate = makeFakeSubstrate({
|
|
114
|
+
handle: makePauseTestHandle('s-3'),
|
|
115
|
+
});
|
|
116
|
+
await pauseCone({ substrate, registry }, 's-3');
|
|
117
|
+
const entry = await registry.findByNameOrId('s-3');
|
|
118
|
+
expect(entry?.trayId).toBe(originalTrayId);
|
|
119
|
+
expect(entry?.lastJoinUpdatedAt).toBe(originalLastJoinUpdatedAt);
|
|
120
|
+
});
|
|
121
|
+
it('finds entry by name', async () => {
|
|
122
|
+
const registry = new MemRegistry();
|
|
123
|
+
await registry.append({
|
|
124
|
+
sandboxId: 's-4',
|
|
125
|
+
name: 'my-session',
|
|
126
|
+
substrate: 'e2b',
|
|
127
|
+
createdAt: new Date().toISOString(),
|
|
128
|
+
lastSeen: new Date().toISOString(),
|
|
129
|
+
state: 'running',
|
|
130
|
+
joinUrl: 'https://example.com/join',
|
|
131
|
+
});
|
|
132
|
+
const pausedFlag = { value: false };
|
|
133
|
+
const substrate = makeFakeSubstrate({
|
|
134
|
+
handle: makePauseTestHandle('s-4', pausedFlag),
|
|
135
|
+
});
|
|
136
|
+
await pauseCone({ substrate, registry }, 'my-session');
|
|
137
|
+
expect(pausedFlag.value).toBe(true);
|
|
138
|
+
const entry = await registry.findByNameOrId('my-session');
|
|
139
|
+
expect(entry?.state).toBe('paused');
|
|
140
|
+
});
|
|
141
|
+
it('throws ALREADY_RUNNING when entry is reserved', async () => {
|
|
142
|
+
const registry = new MemRegistry();
|
|
143
|
+
await registry.append({
|
|
144
|
+
sandboxId: 's-reserved',
|
|
145
|
+
substrate: 'e2b',
|
|
146
|
+
createdAt: new Date().toISOString(),
|
|
147
|
+
lastSeen: new Date().toISOString(),
|
|
148
|
+
state: 'reserved',
|
|
149
|
+
joinUrl: '',
|
|
150
|
+
reservedAt: new Date().toISOString(),
|
|
151
|
+
});
|
|
152
|
+
const substrate = makeFakeSubstrate({
|
|
153
|
+
handle: makePauseTestHandle('s-reserved'),
|
|
154
|
+
});
|
|
155
|
+
let thrownErr;
|
|
156
|
+
try {
|
|
157
|
+
await pauseCone({ substrate, registry }, 's-reserved');
|
|
158
|
+
}
|
|
159
|
+
catch (err) {
|
|
160
|
+
thrownErr = err;
|
|
161
|
+
}
|
|
162
|
+
expect(thrownErr).toBeInstanceOf(CloudError);
|
|
163
|
+
const cloudErr = thrownErr;
|
|
164
|
+
expect(cloudErr.code).toBe('ALREADY_RUNNING');
|
|
165
|
+
expect(cloudErr.message).toContain('being started/resumed');
|
|
166
|
+
});
|
|
167
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resume.test.d.ts","sourceRoot":"","sources":["../../tests/resume.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { resumeCone } from '../src/operations/resume.js';
|
|
3
|
+
import { MemRegistry, makeFakeSubstrate } from './fixtures/index.js';
|
|
4
|
+
// Specialized handle for resume tests: configurable joinJson and kick behavior.
|
|
5
|
+
function makeResumeTestHandle(overrides) {
|
|
6
|
+
const writes = overrides.writes ?? [];
|
|
7
|
+
return {
|
|
8
|
+
sandboxId: 'sbx-1',
|
|
9
|
+
substrate: 'e2b',
|
|
10
|
+
pause: async () => { },
|
|
11
|
+
kill: async () => { },
|
|
12
|
+
getInfo: async () => ({
|
|
13
|
+
sandboxId: 'sbx-1',
|
|
14
|
+
state: 'running',
|
|
15
|
+
metadata: {},
|
|
16
|
+
createdAt: '',
|
|
17
|
+
}),
|
|
18
|
+
writeFile: async (path, contents) => {
|
|
19
|
+
writes.push({ path, contents });
|
|
20
|
+
},
|
|
21
|
+
readFile: async (path) => {
|
|
22
|
+
if (path === '/tmp/slicc-join.json') {
|
|
23
|
+
return (overrides.joinJson ??
|
|
24
|
+
JSON.stringify({
|
|
25
|
+
joinUrl: 'https://w/join/new',
|
|
26
|
+
trayId: 't-new',
|
|
27
|
+
updatedAt: new Date().toISOString(),
|
|
28
|
+
sliccVersion: 'test',
|
|
29
|
+
}));
|
|
30
|
+
}
|
|
31
|
+
throw new Error(`ENOENT ${path}`);
|
|
32
|
+
},
|
|
33
|
+
run: async (_cmd) => ({
|
|
34
|
+
stdout: overrides.kickStatus ?? '200',
|
|
35
|
+
stderr: '',
|
|
36
|
+
exitCode: overrides.kickExitCode ?? 0,
|
|
37
|
+
}),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
describe('resumeCone', () => {
|
|
41
|
+
it('resumes a paused cone and updates registry with refreshed joinUrl', async () => {
|
|
42
|
+
const registry = new MemRegistry();
|
|
43
|
+
await registry.append({
|
|
44
|
+
sandboxId: 'sbx-1',
|
|
45
|
+
substrate: 'e2b',
|
|
46
|
+
createdAt: '',
|
|
47
|
+
joinUrl: 'https://old/join',
|
|
48
|
+
lastSeen: '',
|
|
49
|
+
state: 'paused',
|
|
50
|
+
trayId: 't-old',
|
|
51
|
+
lastJoinUpdatedAt: '2026-05-01T00:00:00.000Z',
|
|
52
|
+
});
|
|
53
|
+
const substrate = makeFakeSubstrate({
|
|
54
|
+
handle: makeResumeTestHandle({}),
|
|
55
|
+
});
|
|
56
|
+
const result = await resumeCone({ substrate, registry }, {
|
|
57
|
+
query: 'sbx-1',
|
|
58
|
+
localSliccVersion: 'test',
|
|
59
|
+
});
|
|
60
|
+
expect(result.joinUrl).toBe('https://w/join/new');
|
|
61
|
+
expect(result.trayRebuilt).toBe(true); // t-old → t-new
|
|
62
|
+
expect(registry.entries[0]?.state).toBe('running');
|
|
63
|
+
expect(registry.entries[0]?.trayId).toBe('t-new');
|
|
64
|
+
expect(registry.entries[0]?.joinUrl).toBe('https://w/join/new');
|
|
65
|
+
});
|
|
66
|
+
it('throws NOT_FOUND when query does not match', async () => {
|
|
67
|
+
const registry = new MemRegistry();
|
|
68
|
+
const substrate = makeFakeSubstrate({
|
|
69
|
+
handle: makeResumeTestHandle({}),
|
|
70
|
+
});
|
|
71
|
+
await expect(resumeCone({ substrate, registry }, { query: 'missing', localSliccVersion: 'test' })).rejects.toMatchObject({ name: 'CloudError', code: 'NOT_FOUND' });
|
|
72
|
+
});
|
|
73
|
+
it('throws ALREADY_RUNNING when entry is already running', async () => {
|
|
74
|
+
const registry = new MemRegistry();
|
|
75
|
+
await registry.append({
|
|
76
|
+
sandboxId: 'sbx-1',
|
|
77
|
+
substrate: 'e2b',
|
|
78
|
+
createdAt: '',
|
|
79
|
+
joinUrl: 'https://w/join',
|
|
80
|
+
lastSeen: '',
|
|
81
|
+
state: 'running',
|
|
82
|
+
});
|
|
83
|
+
const substrate = makeFakeSubstrate({
|
|
84
|
+
handle: makeResumeTestHandle({}),
|
|
85
|
+
});
|
|
86
|
+
await expect(resumeCone({ substrate, registry }, { query: 'sbx-1', localSliccVersion: 'test' })).rejects.toMatchObject({ name: 'CloudError', code: 'ALREADY_RUNNING' });
|
|
87
|
+
});
|
|
88
|
+
it('writes refreshSecretsContents to /slicc/secrets.env when provided', async () => {
|
|
89
|
+
const registry = new MemRegistry();
|
|
90
|
+
await registry.append({
|
|
91
|
+
sandboxId: 'sbx-1',
|
|
92
|
+
substrate: 'e2b',
|
|
93
|
+
createdAt: '',
|
|
94
|
+
joinUrl: 'https://w/join',
|
|
95
|
+
lastSeen: '',
|
|
96
|
+
state: 'paused',
|
|
97
|
+
});
|
|
98
|
+
const writes = [];
|
|
99
|
+
const handle = makeResumeTestHandle({ writes });
|
|
100
|
+
const substrate = makeFakeSubstrate({ handle });
|
|
101
|
+
await resumeCone({ substrate, registry }, {
|
|
102
|
+
query: 'sbx-1',
|
|
103
|
+
localSliccVersion: 'test',
|
|
104
|
+
refreshSecretsContents: 'ADOBE_IMS_TOKEN=fresh',
|
|
105
|
+
});
|
|
106
|
+
expect(writes).toContainEqual({
|
|
107
|
+
path: '/slicc/secrets.env',
|
|
108
|
+
contents: 'ADOBE_IMS_TOKEN=fresh',
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
it('throws LEADER_NOT_READY when kick returns unexpected status', async () => {
|
|
112
|
+
const registry = new MemRegistry();
|
|
113
|
+
await registry.append({
|
|
114
|
+
sandboxId: 'sbx-1',
|
|
115
|
+
substrate: 'e2b',
|
|
116
|
+
createdAt: '',
|
|
117
|
+
joinUrl: 'https://w/join',
|
|
118
|
+
lastSeen: '',
|
|
119
|
+
state: 'paused',
|
|
120
|
+
});
|
|
121
|
+
const substrate = makeFakeSubstrate({
|
|
122
|
+
handle: makeResumeTestHandle({ kickStatus: '418' }),
|
|
123
|
+
});
|
|
124
|
+
await expect(resumeCone({ substrate, registry }, { query: 'sbx-1', localSliccVersion: 'test' })).rejects.toMatchObject({ name: 'CloudError', code: 'LEADER_NOT_READY' });
|
|
125
|
+
});
|
|
126
|
+
it('reports versionMismatch when running version differs from local', async () => {
|
|
127
|
+
const registry = new MemRegistry();
|
|
128
|
+
await registry.append({
|
|
129
|
+
sandboxId: 'sbx-1',
|
|
130
|
+
substrate: 'e2b',
|
|
131
|
+
createdAt: '',
|
|
132
|
+
joinUrl: 'https://w/join',
|
|
133
|
+
lastSeen: '',
|
|
134
|
+
state: 'paused',
|
|
135
|
+
lastJoinUpdatedAt: '2026-05-01T00:00:00.000Z',
|
|
136
|
+
});
|
|
137
|
+
const substrate = makeFakeSubstrate({
|
|
138
|
+
handle: makeResumeTestHandle({
|
|
139
|
+
joinJson: JSON.stringify({
|
|
140
|
+
joinUrl: 'https://w/join/new',
|
|
141
|
+
trayId: 't-new',
|
|
142
|
+
updatedAt: new Date().toISOString(),
|
|
143
|
+
sliccVersion: 'v1.2.3',
|
|
144
|
+
}),
|
|
145
|
+
}),
|
|
146
|
+
});
|
|
147
|
+
const result = await resumeCone({ substrate, registry }, {
|
|
148
|
+
query: 'sbx-1',
|
|
149
|
+
localSliccVersion: 'v1.0.0',
|
|
150
|
+
});
|
|
151
|
+
expect(result.versionMismatch).toEqual({ running: 'v1.2.3', local: 'v1.0.0' });
|
|
152
|
+
});
|
|
153
|
+
it('does not report trayRebuilt when no baseline trayId', async () => {
|
|
154
|
+
const registry = new MemRegistry();
|
|
155
|
+
await registry.append({
|
|
156
|
+
sandboxId: 'sbx-1',
|
|
157
|
+
substrate: 'e2b',
|
|
158
|
+
createdAt: '',
|
|
159
|
+
joinUrl: 'https://w/join',
|
|
160
|
+
lastSeen: '',
|
|
161
|
+
state: 'paused',
|
|
162
|
+
lastJoinUpdatedAt: '2026-05-01T00:00:00.000Z',
|
|
163
|
+
// No trayId in baseline
|
|
164
|
+
});
|
|
165
|
+
const substrate = makeFakeSubstrate({
|
|
166
|
+
handle: makeResumeTestHandle({}),
|
|
167
|
+
});
|
|
168
|
+
const result = await resumeCone({ substrate, registry }, {
|
|
169
|
+
query: 'sbx-1',
|
|
170
|
+
localSliccVersion: 'test',
|
|
171
|
+
});
|
|
172
|
+
expect(result.trayRebuilt).toBe(false);
|
|
173
|
+
});
|
|
174
|
+
it('rejects an older /tmp/slicc-join.json on resume (stale file)', async () => {
|
|
175
|
+
const registry = new MemRegistry();
|
|
176
|
+
const baselineUpdatedAt = '2026-05-01T12:00:00.000Z';
|
|
177
|
+
const olderUpdatedAt = '2026-05-01T11:00:00.000Z'; // 1 hour earlier
|
|
178
|
+
await registry.append({
|
|
179
|
+
sandboxId: 'sbx-1',
|
|
180
|
+
substrate: 'e2b',
|
|
181
|
+
createdAt: '',
|
|
182
|
+
joinUrl: 'https://w/join/old',
|
|
183
|
+
lastSeen: '',
|
|
184
|
+
state: 'paused',
|
|
185
|
+
lastJoinUpdatedAt: baselineUpdatedAt,
|
|
186
|
+
});
|
|
187
|
+
const substrate = makeFakeSubstrate({
|
|
188
|
+
handle: makeResumeTestHandle({
|
|
189
|
+
joinJson: JSON.stringify({
|
|
190
|
+
joinUrl: 'https://w/join/stale',
|
|
191
|
+
trayId: 't-stale',
|
|
192
|
+
updatedAt: olderUpdatedAt,
|
|
193
|
+
}),
|
|
194
|
+
}),
|
|
195
|
+
});
|
|
196
|
+
await expect(resumeCone({ substrate, registry }, {
|
|
197
|
+
query: 'sbx-1',
|
|
198
|
+
localSliccVersion: 'test',
|
|
199
|
+
pollTimeoutMs: 200,
|
|
200
|
+
pollIntervalMs: 50,
|
|
201
|
+
})).rejects.toMatchObject({ name: 'CloudError', code: 'LEADER_NOT_READY' });
|
|
202
|
+
});
|
|
203
|
+
});
|