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,228 @@
|
|
|
1
|
+
import { CloudError } from '../errors.js';
|
|
2
|
+
import { filterSecretsEnv } from '../secrets-filter.js';
|
|
3
|
+
import { pollCloudStatus } from '../polling.js';
|
|
4
|
+
/** Fetch the last n lines of /tmp/slicc-stderr.log from inside the sandbox. */
|
|
5
|
+
async function tailStderr(handle, n) {
|
|
6
|
+
try {
|
|
7
|
+
const raw = await handle.readFile('/tmp/slicc-stderr.log');
|
|
8
|
+
const lines = raw.split('\n');
|
|
9
|
+
return lines.slice(Math.max(0, lines.length - n)).join('\n');
|
|
10
|
+
}
|
|
11
|
+
catch (err) {
|
|
12
|
+
// Discriminate "file absent" (acceptable fallback) from other errors
|
|
13
|
+
// (substrate read failure — worth surfacing for debug).
|
|
14
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
15
|
+
if (/ENOENT|not found/i.test(msg)) {
|
|
16
|
+
return '(no /tmp/slicc-stderr.log produced)';
|
|
17
|
+
}
|
|
18
|
+
return `(failed to read /tmp/slicc-stderr.log: ${msg})`;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function parseCapLimit(name, raw) {
|
|
22
|
+
const n = Number.parseInt(raw, 10);
|
|
23
|
+
if (!Number.isFinite(n) || n < 0) {
|
|
24
|
+
throw new Error(`Invalid cap env ${name}=${JSON.stringify(raw)}: must be a non-negative integer`);
|
|
25
|
+
}
|
|
26
|
+
return n;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Reserve a slot in the registry atomically under DO lock, BEFORE substrate.create.
|
|
30
|
+
* Returns a synthetic reservationId (pending-<uuid>) that counts toward the cap.
|
|
31
|
+
* Throws CloudError('CAP_EXCEEDED' | 'NAME_TAKEN') on conflict.
|
|
32
|
+
*
|
|
33
|
+
* Callers MUST wrap this in blockConcurrencyWhile so two concurrent calls
|
|
34
|
+
* serialize and the second sees the first's placeholder.
|
|
35
|
+
*/
|
|
36
|
+
export async function reserveSlot(deps, opts) {
|
|
37
|
+
const reservationId = `pending-${crypto.randomUUID()}`;
|
|
38
|
+
const createdAt = new Date().toISOString();
|
|
39
|
+
// Read existing entries to enforce caps + name conflicts.
|
|
40
|
+
// If caller provided a pre-reconciled list (worker DO path), use it directly.
|
|
41
|
+
// Otherwise (CLI path), do a full reconciliation via listCones.
|
|
42
|
+
let existing;
|
|
43
|
+
if (opts.reconciledCones) {
|
|
44
|
+
existing = opts.reconciledCones;
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
const { listCones } = await import('./list.js');
|
|
48
|
+
existing = await listCones(deps, opts.userId ? { metadata: { userId: opts.userId } } : {});
|
|
49
|
+
}
|
|
50
|
+
// Cap check: count both running and reserved entries (reservations count as running)
|
|
51
|
+
if (opts.env) {
|
|
52
|
+
const running = existing.filter((e) => e.state === 'running' || e.state === 'reserved').length;
|
|
53
|
+
const paused = existing.filter((e) => e.state === 'paused').length;
|
|
54
|
+
const runningCap = parseCapLimit('CONE_CAP_RUNNING', opts.env.CONE_CAP_RUNNING);
|
|
55
|
+
const pausedCap = parseCapLimit('CONE_CAP_PAUSED', opts.env.CONE_CAP_PAUSED);
|
|
56
|
+
if (running >= runningCap) {
|
|
57
|
+
throw new CloudError('CAP_EXCEEDED', `at running cap (${running}/${runningCap})`, {
|
|
58
|
+
running,
|
|
59
|
+
cap: runningCap,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
if (paused >= pausedCap) {
|
|
63
|
+
throw new CloudError('CAP_EXCEEDED', `at paused cap (${paused}/${pausedCap})`, {
|
|
64
|
+
paused,
|
|
65
|
+
cap: pausedCap,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// Name conflict check
|
|
70
|
+
const requestedName = opts.name?.trim();
|
|
71
|
+
if (requestedName && existing.some((e) => e.state !== 'dead' && e.name === requestedName)) {
|
|
72
|
+
throw new CloudError('NAME_TAKEN', `cloud session name already exists: ${requestedName}`);
|
|
73
|
+
}
|
|
74
|
+
// Append placeholder entry with 'reserved' state
|
|
75
|
+
const placeholder = {
|
|
76
|
+
substrate: deps.substrate.id,
|
|
77
|
+
sandboxId: reservationId,
|
|
78
|
+
name: requestedName,
|
|
79
|
+
createdAt,
|
|
80
|
+
lastSeen: createdAt,
|
|
81
|
+
state: 'reserved',
|
|
82
|
+
reservedAt: createdAt,
|
|
83
|
+
joinUrl: '',
|
|
84
|
+
metadata: opts.metadata,
|
|
85
|
+
};
|
|
86
|
+
await deps.registry.append(placeholder);
|
|
87
|
+
return { reservationId };
|
|
88
|
+
}
|
|
89
|
+
export async function startCone(deps, opts) {
|
|
90
|
+
const safeSecrets = filterSecretsEnv(opts.envContents);
|
|
91
|
+
// Track whichever registry entry is currently live, for cleanup on failure.
|
|
92
|
+
let activeRegistryId = opts.reservationId;
|
|
93
|
+
let handle;
|
|
94
|
+
try {
|
|
95
|
+
// Wrap create inside try block to ensure reservation cleanup on failure.
|
|
96
|
+
handle = await deps.substrate.create({
|
|
97
|
+
template: opts.template ?? 'slicc',
|
|
98
|
+
autoPauseOnCap: opts.autoPauseOnCap ?? true,
|
|
99
|
+
envVars: {
|
|
100
|
+
SLICC_TRAY_WORKER_BASE_URL: opts.workerBaseUrl,
|
|
101
|
+
...(opts.envs ?? {}),
|
|
102
|
+
},
|
|
103
|
+
metadata: {
|
|
104
|
+
sliccVersion: opts.sliccVersion,
|
|
105
|
+
...(opts.name ? { name: opts.name } : {}),
|
|
106
|
+
...(opts.metadata ?? {}),
|
|
107
|
+
},
|
|
108
|
+
name: opts.name,
|
|
109
|
+
});
|
|
110
|
+
// Capture freshness baseline AFTER sandbox creation: any /tmp/slicc-join.json
|
|
111
|
+
// with updatedAt at or before this ISO is from the template snapshot, not
|
|
112
|
+
// the new sandbox's leader. Subtract a small skew margin for clock drift
|
|
113
|
+
// between the worker fetching this timestamp and the sandbox writing the
|
|
114
|
+
// file: the sandbox's clock might be slightly behind, so a tiny margin
|
|
115
|
+
// gives the leader's first real write a chance to be accepted.
|
|
116
|
+
const minUpdatedAt = new Date(Date.now() - 5_000).toISOString();
|
|
117
|
+
const createdAt = new Date().toISOString();
|
|
118
|
+
// Update or append placeholder depending on whether we have a reservation.
|
|
119
|
+
// If reservationId is provided, update it to the real sandboxId and remove the old entry.
|
|
120
|
+
// Otherwise, append a new placeholder as before.
|
|
121
|
+
//
|
|
122
|
+
// The placeholder ensures concurrent /list-cones calls see the cone in the registry
|
|
123
|
+
// (pass 1) instead of treating it as an orphan (pass 2). The empty joinUrl
|
|
124
|
+
// means the dashboard hides the Open button until pollCloudStatus completes
|
|
125
|
+
// and the entry is updated below. State is 'reserved' until poll completes.
|
|
126
|
+
if (opts.reservationId) {
|
|
127
|
+
// Remove the reservation entry and append the real one
|
|
128
|
+
await deps.registry.remove(opts.reservationId);
|
|
129
|
+
const placeholder = {
|
|
130
|
+
substrate: deps.substrate.id,
|
|
131
|
+
sandboxId: handle.sandboxId,
|
|
132
|
+
name: opts.name,
|
|
133
|
+
createdAt,
|
|
134
|
+
lastSeen: createdAt,
|
|
135
|
+
state: 'reserved',
|
|
136
|
+
joinUrl: '',
|
|
137
|
+
metadata: opts.metadata,
|
|
138
|
+
};
|
|
139
|
+
await deps.registry.append(placeholder);
|
|
140
|
+
// After swapping, the real sandboxId is the active registry key.
|
|
141
|
+
activeRegistryId = handle.sandboxId;
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
// Legacy path: no reservation, append directly
|
|
145
|
+
const placeholder = {
|
|
146
|
+
substrate: deps.substrate.id,
|
|
147
|
+
sandboxId: handle.sandboxId,
|
|
148
|
+
name: opts.name,
|
|
149
|
+
createdAt,
|
|
150
|
+
lastSeen: createdAt,
|
|
151
|
+
state: 'reserved',
|
|
152
|
+
joinUrl: '',
|
|
153
|
+
};
|
|
154
|
+
await deps.registry.append(placeholder);
|
|
155
|
+
// The newly-appended sandboxId is the active registry key.
|
|
156
|
+
activeRegistryId = handle.sandboxId;
|
|
157
|
+
}
|
|
158
|
+
// Two-layer secrets bootstrap (see Plan B):
|
|
159
|
+
// 1. start.sh writes /slicc/secrets.env from $ADOBE_IMS_TOKEN if the file
|
|
160
|
+
// doesn't already exist (fallback for race-free worker path where env-vars
|
|
161
|
+
// arrive before this writeFile lands).
|
|
162
|
+
// 2. THIS writeFile uploads the full filtered secrets.env. Worker overwrites
|
|
163
|
+
// with Adobe-only content (effectively a no-op since start.sh already wrote
|
|
164
|
+
// the same token). CLI overwrites with non-Adobe secrets (GitHub PATs, S3
|
|
165
|
+
// keys, etc.), load-bearing for CLI-launched cones. The CLI race is benign
|
|
166
|
+
// because the page-side bootstrap polls /api/hosted-bootstrap after a 5s
|
|
167
|
+
// delay, by which time this writeFile has landed.
|
|
168
|
+
await handle.writeFile('/slicc/secrets.env', safeSecrets);
|
|
169
|
+
let status;
|
|
170
|
+
try {
|
|
171
|
+
status = await pollCloudStatus(handle, {
|
|
172
|
+
timeoutMs: opts.pollTimeoutMs ?? 60_000,
|
|
173
|
+
intervalMs: opts.pollIntervalMs ?? 500,
|
|
174
|
+
minUpdatedAt,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
catch (pollErr) {
|
|
178
|
+
// Surface boot diagnostics before tearing down. Spec failure mode #7.
|
|
179
|
+
const stderr = await tailStderr(handle, 50);
|
|
180
|
+
throw new CloudError('SANDBOX_NOT_READY', `${pollErr instanceof Error ? pollErr.message : String(pollErr)}\n` +
|
|
181
|
+
`--- last 50 lines of /tmp/slicc-stderr.log ---\n${stderr}`, { sandboxId: handle.sandboxId });
|
|
182
|
+
}
|
|
183
|
+
// Promote the placeholder to a fully-populated running entry.
|
|
184
|
+
await deps.registry.update(handle.sandboxId, {
|
|
185
|
+
state: 'running',
|
|
186
|
+
joinUrl: status.joinUrl,
|
|
187
|
+
trayId: status.trayId,
|
|
188
|
+
lastJoinUpdatedAt: status.updatedAt,
|
|
189
|
+
lastSeen: new Date().toISOString(),
|
|
190
|
+
});
|
|
191
|
+
return {
|
|
192
|
+
sandboxId: handle.sandboxId,
|
|
193
|
+
name: opts.name,
|
|
194
|
+
joinUrl: status.joinUrl,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
catch (err) {
|
|
198
|
+
// Best-effort cleanup: remove whichever registry entry is currently active.
|
|
199
|
+
if (activeRegistryId) {
|
|
200
|
+
try {
|
|
201
|
+
await deps.registry.remove(activeRegistryId);
|
|
202
|
+
}
|
|
203
|
+
catch (cleanupErr) {
|
|
204
|
+
const msg = cleanupErr instanceof Error ? cleanupErr.message : String(cleanupErr);
|
|
205
|
+
console.warn('[cloud-core] start cleanup', {
|
|
206
|
+
phase: 'registry-remove',
|
|
207
|
+
sandboxId: activeRegistryId,
|
|
208
|
+
err: msg,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
// Always kill the real sandbox if it was created (handle exists at this point)
|
|
213
|
+
if (handle) {
|
|
214
|
+
try {
|
|
215
|
+
await handle.kill();
|
|
216
|
+
}
|
|
217
|
+
catch (cleanupErr) {
|
|
218
|
+
const msg = cleanupErr instanceof Error ? cleanupErr.message : String(cleanupErr);
|
|
219
|
+
console.warn('[cloud-core] start cleanup', {
|
|
220
|
+
phase: 'handle-kill',
|
|
221
|
+
sandboxId: handle.sandboxId,
|
|
222
|
+
err: msg,
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
throw err;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { SandboxHandle } from './substrate.js';
|
|
2
|
+
import type { CloudStatus } from './types.js';
|
|
3
|
+
export interface PollOpts {
|
|
4
|
+
timeoutMs: number;
|
|
5
|
+
intervalMs: number;
|
|
6
|
+
/**
|
|
7
|
+
* Reject reads where `updatedAt` is `<=` this ISO timestamp. Required when
|
|
8
|
+
* polling from a fresh sandbox built from a template snapshot — the
|
|
9
|
+
* template-build leaves a stale /tmp/slicc-join.json baked in, so without
|
|
10
|
+
* a freshness floor the first poll returns the build-time tray URL.
|
|
11
|
+
* Caller should pass an ISO timestamp captured at the moment substrate.create()
|
|
12
|
+
* completed. If unset (CLI legacy callers), no freshness check is applied.
|
|
13
|
+
*/
|
|
14
|
+
minUpdatedAt?: string;
|
|
15
|
+
}
|
|
16
|
+
export declare function pollCloudStatus(handle: SandboxHandle, opts: PollOpts): Promise<CloudStatus>;
|
|
17
|
+
export declare function pollForRefreshedStatus(handle: SandboxHandle, baselineUpdatedAt: string | undefined, opts: PollOpts): Promise<CloudStatus>;
|
|
18
|
+
//# sourceMappingURL=polling.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"polling.d.ts","sourceRoot":"","sources":["../../src/polling.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAG9C,MAAM,WAAW,QAAQ;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,wBAAsB,eAAe,CAAC,MAAM,EAAE,aAAa,EAAE,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,WAAW,CAAC,CAiCjG;AAED,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,aAAa,EACrB,iBAAiB,EAAE,MAAM,GAAG,SAAS,EACrC,IAAI,EAAE,QAAQ,GACb,OAAO,CAAC,WAAW,CAAC,CAwCtB"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { CloudError } from './errors.js';
|
|
2
|
+
export async function pollCloudStatus(handle, opts) {
|
|
3
|
+
const start = Date.now();
|
|
4
|
+
let lastError = null;
|
|
5
|
+
let lastStalePayload = null;
|
|
6
|
+
while (Date.now() - start < opts.timeoutMs) {
|
|
7
|
+
try {
|
|
8
|
+
const raw = await handle.readFile('/tmp/slicc-join.json');
|
|
9
|
+
const parsed = JSON.parse(raw);
|
|
10
|
+
if (parsed.joinUrl) {
|
|
11
|
+
if (!opts.minUpdatedAt)
|
|
12
|
+
return parsed;
|
|
13
|
+
if (parsed.updatedAt && parsed.updatedAt > opts.minUpdatedAt)
|
|
14
|
+
return parsed;
|
|
15
|
+
lastStalePayload = parsed;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
lastError = err;
|
|
20
|
+
}
|
|
21
|
+
await new Promise((r) => setTimeout(r, opts.intervalMs));
|
|
22
|
+
}
|
|
23
|
+
let errSuffix = '';
|
|
24
|
+
if (lastStalePayload) {
|
|
25
|
+
errSuffix =
|
|
26
|
+
` (file present but stale: minUpdatedAt=${opts.minUpdatedAt}, ` +
|
|
27
|
+
`current.updatedAt=${lastStalePayload.updatedAt}, ` +
|
|
28
|
+
`current.trayId=${lastStalePayload.trayId})`;
|
|
29
|
+
}
|
|
30
|
+
else if (lastError) {
|
|
31
|
+
errSuffix = ` (last error: ${lastError instanceof Error ? lastError.message : String(lastError)})`;
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
errSuffix = ' (file never appeared)';
|
|
35
|
+
}
|
|
36
|
+
throw new CloudError('SANDBOX_NOT_READY', `cloud-status did not appear within ${opts.timeoutMs}ms; sandbox may have failed to boot${errSuffix}`);
|
|
37
|
+
}
|
|
38
|
+
export async function pollForRefreshedStatus(handle, baselineUpdatedAt, opts) {
|
|
39
|
+
const start = Date.now();
|
|
40
|
+
let lastError = null;
|
|
41
|
+
let lastStalePayload = null;
|
|
42
|
+
while (Date.now() - start < opts.timeoutMs) {
|
|
43
|
+
try {
|
|
44
|
+
const raw = await handle.readFile('/tmp/slicc-join.json');
|
|
45
|
+
const parsed = JSON.parse(raw);
|
|
46
|
+
if (parsed.joinUrl) {
|
|
47
|
+
// Require a STRICTLY newer updatedAt than the registry baseline.
|
|
48
|
+
// If we have no baseline (first-time resume of an externally-created
|
|
49
|
+
// sandbox), accept any well-formed read.
|
|
50
|
+
if (!baselineUpdatedAt)
|
|
51
|
+
return parsed;
|
|
52
|
+
if (parsed.updatedAt && parsed.updatedAt > baselineUpdatedAt) {
|
|
53
|
+
return parsed;
|
|
54
|
+
}
|
|
55
|
+
// File exists, joinUrl present, but updatedAt unchanged — capture for
|
|
56
|
+
// the timeout error so we can tell "missing" from "stale".
|
|
57
|
+
lastStalePayload = parsed;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
lastError = err;
|
|
62
|
+
}
|
|
63
|
+
await new Promise((r) => setTimeout(r, opts.intervalMs));
|
|
64
|
+
}
|
|
65
|
+
let errSuffix = '';
|
|
66
|
+
if (lastStalePayload) {
|
|
67
|
+
errSuffix =
|
|
68
|
+
` (file present but stale: baseline.updatedAt=${baselineUpdatedAt}, ` +
|
|
69
|
+
`current.updatedAt=${lastStalePayload.updatedAt}, ` +
|
|
70
|
+
`current.trayId=${lastStalePayload.trayId})`;
|
|
71
|
+
}
|
|
72
|
+
else if (lastError) {
|
|
73
|
+
errSuffix = ` (last error: ${lastError instanceof Error ? lastError.message : String(lastError)})`;
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
errSuffix = ' (file never appeared)';
|
|
77
|
+
}
|
|
78
|
+
throw new CloudError('LEADER_NOT_READY', `cloud-status did not refresh within ${opts.timeoutMs}ms${errSuffix}`);
|
|
79
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ConeEntry } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Per-user (or per-machine) registry of cloud cones. Two implementations:
|
|
4
|
+
* - node-server uses `FileRegistry` (backed by ~/.slicc/cloud-sessions.json)
|
|
5
|
+
* - cloudflare-worker uses `LocalRegistry` (backed by DurableObject storage)
|
|
6
|
+
*
|
|
7
|
+
* Both implementations:
|
|
8
|
+
* - Persist the `{ sessions: ConeEntry[] }` JSON shape (legacy schema, do not
|
|
9
|
+
* rename — existing CLI files in the wild depend on it).
|
|
10
|
+
* - Have UPSERT semantics on `append`: replacing an entry by sandboxId, not
|
|
11
|
+
* throwing on duplicate. Same upsert behavior as the previous file-backed
|
|
12
|
+
* implementation and is load-bearing for reconciliation passes.
|
|
13
|
+
*/
|
|
14
|
+
export interface Registry {
|
|
15
|
+
/** Read all entries. */
|
|
16
|
+
list(): Promise<ConeEntry[]>;
|
|
17
|
+
/** Resolve a query (sandboxId OR name) to a single entry, or null. */
|
|
18
|
+
findByNameOrId(query: string): Promise<ConeEntry | null>;
|
|
19
|
+
/** Add or replace an entry. Idempotent: re-appending an existing sandboxId
|
|
20
|
+
* replaces the previous record in place. */
|
|
21
|
+
append(entry: ConeEntry): Promise<void>;
|
|
22
|
+
/** Partial update by sandboxId. Throws if not found. */
|
|
23
|
+
update(sandboxId: string, patch: Partial<ConeEntry>): Promise<void>;
|
|
24
|
+
/** Remove by sandboxId. Idempotent — no-op if absent. */
|
|
25
|
+
remove(sandboxId: string): Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,QAAQ;IACvB,wBAAwB;IACxB,IAAI,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;IAC7B,sEAAsE;IACtE,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC;IACzD;gDAC4C;IAC5C,MAAM,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxC,wDAAwD;IACxD,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,SAAS,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpE,yDAAyD;IACzD,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1C"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secrets-filter.d.ts","sourceRoot":"","sources":["../../src/secrets-filter.ts"],"names":[],"mappings":"AAQA,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAQzD"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strip locally-only keys from secrets.env before upload. E2B_API_KEY is the
|
|
3
|
+
* user's substrate credential — there is no reason for it to live inside the
|
|
4
|
+
* cloud sandbox where the cone could use it to spawn additional sandboxes
|
|
5
|
+
* against the user's e2b account. Keep this list narrow.
|
|
6
|
+
*/
|
|
7
|
+
const SECRETS_STRIP_KEYS = ['E2B_API_KEY', 'E2B_API_KEY_DOMAINS'];
|
|
8
|
+
export function filterSecretsEnv(contents) {
|
|
9
|
+
const out = [];
|
|
10
|
+
for (const line of contents.split('\n')) {
|
|
11
|
+
const m = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=/.exec(line);
|
|
12
|
+
if (m && SECRETS_STRIP_KEYS.includes(m[1]))
|
|
13
|
+
continue;
|
|
14
|
+
out.push(line);
|
|
15
|
+
}
|
|
16
|
+
return out.join('\n');
|
|
17
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"substrate-factory.d.ts","sourceRoot":"","sources":["../../src/substrate-factory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAGrF,wBAAgB,eAAe,CAAC,EAAE,EAAE,WAAW,EAAE,GAAG,EAAE,eAAe,GAAG,gBAAgB,CAGvF"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { SandboxSummary } from './types.js';
|
|
2
|
+
export type SubstrateId = 'e2b';
|
|
3
|
+
export interface SubstrateConfig {
|
|
4
|
+
/** Credential for the substrate (e.g. E2B_API_KEY). */
|
|
5
|
+
apiKey: string;
|
|
6
|
+
}
|
|
7
|
+
export interface CreateOpts {
|
|
8
|
+
template: string;
|
|
9
|
+
envVars: Record<string, string>;
|
|
10
|
+
metadata: Record<string, string>;
|
|
11
|
+
autoPauseOnCap: boolean;
|
|
12
|
+
name?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface SandboxInfo {
|
|
15
|
+
sandboxId: string;
|
|
16
|
+
state: 'running' | 'paused' | 'dead';
|
|
17
|
+
metadata: Record<string, string>;
|
|
18
|
+
createdAt: string;
|
|
19
|
+
}
|
|
20
|
+
export interface RunResult {
|
|
21
|
+
stdout: string;
|
|
22
|
+
stderr: string;
|
|
23
|
+
exitCode: number;
|
|
24
|
+
}
|
|
25
|
+
export interface SandboxHandle {
|
|
26
|
+
readonly sandboxId: string;
|
|
27
|
+
readonly substrate: SubstrateId;
|
|
28
|
+
pause(): Promise<void>;
|
|
29
|
+
kill(): Promise<void>;
|
|
30
|
+
getInfo(): Promise<SandboxInfo>;
|
|
31
|
+
writeFile(path: string, contents: string | Uint8Array): Promise<void>;
|
|
32
|
+
readFile(path: string): Promise<string>;
|
|
33
|
+
run(cmd: string): Promise<RunResult>;
|
|
34
|
+
}
|
|
35
|
+
export interface ListOpts {
|
|
36
|
+
metadata?: Record<string, string>;
|
|
37
|
+
}
|
|
38
|
+
export interface SandboxSubstrate {
|
|
39
|
+
readonly id: SubstrateId;
|
|
40
|
+
create(opts: CreateOpts): Promise<SandboxHandle>;
|
|
41
|
+
connect(sandboxId: string): Promise<SandboxHandle>;
|
|
42
|
+
list(opts?: ListOpts): Promise<SandboxSummary[]>;
|
|
43
|
+
/**
|
|
44
|
+
* Reset the sandbox's auto-pause countdown. The substrate-specific TTL
|
|
45
|
+
* floor applies (e.g. e2b Hobby plan caps at 1h, Pro at 24h). For non-e2b
|
|
46
|
+
* substrates that don't support this concept, implement as a no-op.
|
|
47
|
+
*/
|
|
48
|
+
extendTimeout(sandboxId: string, ttlMs: number): Promise<void>;
|
|
49
|
+
}
|
|
50
|
+
export interface SubstrateFactory {
|
|
51
|
+
(id: SubstrateId, cfg: SubstrateConfig): SandboxSubstrate;
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=substrate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"substrate.d.ts","sourceRoot":"","sources":["../../src/substrate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAIjD,MAAM,MAAM,WAAW,GAAG,KAAK,CAAC;AAEhC,MAAM,WAAW,eAAe;IAC9B,uDAAuD;IACvD,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,cAAc,EAAE,OAAO,CAAC;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,SAAS,GAAG,QAAQ,GAAG,MAAM,CAAC;IACrC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAC;IAChC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,OAAO,IAAI,OAAO,CAAC,WAAW,CAAC,CAAC;IAChC,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtE,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACxC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;CACtC;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,EAAE,WAAW,CAAC;IACzB,MAAM,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;IACjD,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;IACnD,IAAI,CAAC,IAAI,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC;IACjD;;;;OAIG;IACH,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAChE;AAED,MAAM,WAAW,gBAAgB;IAC/B,CAAC,EAAE,EAAE,WAAW,EAAE,GAAG,EAAE,eAAe,GAAG,gBAAgB,CAAC;CAC3D"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"e2b.d.ts","sourceRoot":"","sources":["../../../src/substrates/e2b.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAKV,gBAAgB,EAChB,eAAe,EAChB,MAAM,iBAAiB,CAAC;AAKzB,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,eAAe,GAAG,gBAAgB,CAmDzE"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { Sandbox } from 'e2b';
|
|
2
|
+
const DEFAULT_TTL_MS = 60 * 60 * 1000; // 1 hour
|
|
3
|
+
export function createE2bSubstrate(cfg) {
|
|
4
|
+
// Capture apiKey locally to pass explicitly to every SDK call (worker-safe).
|
|
5
|
+
const apiKey = cfg.apiKey;
|
|
6
|
+
return {
|
|
7
|
+
id: 'e2b',
|
|
8
|
+
async create(opts) {
|
|
9
|
+
const sbx = await Sandbox.create(opts.template, {
|
|
10
|
+
apiKey,
|
|
11
|
+
envs: opts.envVars,
|
|
12
|
+
metadata: opts.metadata,
|
|
13
|
+
timeoutMs: DEFAULT_TTL_MS,
|
|
14
|
+
...(opts.autoPauseOnCap ? { lifecycle: { onTimeout: 'pause' } } : {}),
|
|
15
|
+
});
|
|
16
|
+
return wrap(sbx);
|
|
17
|
+
},
|
|
18
|
+
async connect(sandboxId) {
|
|
19
|
+
const sbx = await Sandbox.connect(sandboxId, { apiKey });
|
|
20
|
+
return wrap(sbx);
|
|
21
|
+
},
|
|
22
|
+
async list(opts) {
|
|
23
|
+
// The e2b SDK accepts query.metadata for server-side filtering.
|
|
24
|
+
// Pass it through if provided.
|
|
25
|
+
const paginator = Sandbox.list({
|
|
26
|
+
apiKey,
|
|
27
|
+
...(opts?.metadata ? { query: { metadata: opts.metadata } } : {}),
|
|
28
|
+
});
|
|
29
|
+
const items = [];
|
|
30
|
+
while (paginator.hasNext) {
|
|
31
|
+
const page = await paginator.nextItems();
|
|
32
|
+
for (const info of page) {
|
|
33
|
+
// The SDK's `templateId` is the immutable hash (e.g. cjd0k6foq…);
|
|
34
|
+
// the alias 'slicc' is on `info.name`. Filter on the alias.
|
|
35
|
+
if (info.name === 'slicc') {
|
|
36
|
+
items.push({
|
|
37
|
+
sandboxId: info.sandboxId,
|
|
38
|
+
// Sandbox name (user-supplied `--name`) lives in metadata.
|
|
39
|
+
// info.name is the *template* name.
|
|
40
|
+
name: info.metadata?.['name'],
|
|
41
|
+
state: mapState(info.state),
|
|
42
|
+
metadata: info.metadata,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return items;
|
|
48
|
+
},
|
|
49
|
+
async extendTimeout(sandboxId, ttlMs) {
|
|
50
|
+
await Sandbox.setTimeout(sandboxId, ttlMs, { apiKey });
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function wrap(sbx) {
|
|
55
|
+
return {
|
|
56
|
+
sandboxId: sbx.sandboxId,
|
|
57
|
+
substrate: 'e2b',
|
|
58
|
+
async pause() {
|
|
59
|
+
await sbx.pause();
|
|
60
|
+
},
|
|
61
|
+
async kill() {
|
|
62
|
+
await sbx.kill();
|
|
63
|
+
},
|
|
64
|
+
async getInfo() {
|
|
65
|
+
const info = await sbx.getInfo();
|
|
66
|
+
return {
|
|
67
|
+
sandboxId: sbx.sandboxId,
|
|
68
|
+
state: mapState(info.state),
|
|
69
|
+
metadata: info.metadata,
|
|
70
|
+
createdAt: info.startedAt.toISOString(),
|
|
71
|
+
};
|
|
72
|
+
},
|
|
73
|
+
async writeFile(path, contents) {
|
|
74
|
+
// The e2b SDK accepts string | ArrayBuffer | Blob | ReadableStream.
|
|
75
|
+
// If contents is Uint8Array, convert to Blob to avoid ArrayBufferLike issues.
|
|
76
|
+
const data = contents instanceof Uint8Array ? new Blob([contents]) : contents;
|
|
77
|
+
await sbx.files.write(path, data);
|
|
78
|
+
},
|
|
79
|
+
async readFile(path) {
|
|
80
|
+
return sbx.files.read(path);
|
|
81
|
+
},
|
|
82
|
+
async run(cmd) {
|
|
83
|
+
const result = await sbx.commands.run(cmd);
|
|
84
|
+
return {
|
|
85
|
+
stdout: result.stdout,
|
|
86
|
+
stderr: result.stderr,
|
|
87
|
+
exitCode: result.exitCode,
|
|
88
|
+
};
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function mapState(s) {
|
|
93
|
+
// The e2b SDK only returns 'running' or 'paused'. Map appropriately.
|
|
94
|
+
if (s === 'running' || s === 'paused')
|
|
95
|
+
return s;
|
|
96
|
+
// Unreachable in practice, but TypeScript needs this to satisfy the return type.
|
|
97
|
+
return 'dead';
|
|
98
|
+
}
|