@molecule/api-code-sandbox-e2b 1.0.4 → 1.1.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 CHANGED
@@ -3,7 +3,7 @@ AUTO-GENERATED — DO NOT EDIT THIS FILE.
3
3
  Generated by `mlcl sync-docs` from the package's src/index.ts JSDoc + mlcl/registry.json.
4
4
  Edits here are overwritten on the next commit (molecule's pre-commit hook regenerates).
5
5
  To change this document, edit the module-level JSDoc in src/index.ts.
6
- Generated: 2026-08-11T00:53:00.606Z
6
+ Generated: 2026-08-15T18:25:48.482Z
7
7
  -->
8
8
 
9
9
  # @molecule/api-code-sandbox-e2b
@@ -45,12 +45,9 @@ import { createProvider } from '@molecule/api-code-sandbox-e2b'
45
45
  const provider = createProvider({
46
46
  templateId: 'molecule-superset',
47
47
  defaultPreviewPort: 5173,
48
- defaultNetworkRules: [
49
- { domain: 'registry.npmjs.org', action: 'allow' },
50
- { domain: '*.npmjs.org', action: 'allow' },
51
- { domain: 'github.com', action: 'allow' },
52
- { domain: '*', action: 'deny' },
53
- ],
48
+ // Deny-by-default egress: everything not listed here is blocked, raw IPs
49
+ // included. An empty/omitted list applies NO policy at all.
50
+ defaultAllowOut: ['registry.npmjs.org', '*.npmjs.org', 'github.com'],
54
51
  })
55
52
  ```
56
53
 
@@ -61,7 +58,7 @@ const provider = createProvider({
61
58
  ## Installation
62
59
 
63
60
  ```bash
64
- npm install @molecule/api-code-sandbox-e2b e2b @molecule/api-bond @molecule/api-code-sandbox @molecule/api-i18n
61
+ npm install @molecule/api-code-sandbox-e2b @bufbuild/protobuf @molecule/api-bond @molecule/api-code-sandbox @molecule/api-i18n e2b
65
62
  ```
66
63
 
67
64
  ## API
@@ -125,6 +122,10 @@ interface E2BConfig {
125
122
  /**
126
123
  * Default sandbox lifetime before E2B auto-pauses it, in milliseconds. The
127
124
  * control plane extends this per-heartbeat; this is only the initial ceiling.
125
+ *
126
+ * E2B caps this per account: 1 hour on Hobby, 24 hours on Pro. A value above
127
+ * the account's cap is rejected at create time, so raising it is a decision
128
+ * about the account, not just the config.
128
129
  */
129
130
  defaultTimeoutMs?: number
130
131
  /**
@@ -168,8 +169,48 @@ interface E2BSandboxClientLike {
168
169
  connect(sandboxId: string, opts?: Record<string, unknown>): Promise<E2BSandboxLike>
169
170
  list(
170
171
  opts?: Record<string, unknown>,
171
- ): Promise<Array<{ sandboxId: string }> | { sandboxes?: Array<{ sandboxId: string }> }>
172
+ ): Promise<
173
+ | Array<{ sandboxId: string; state?: string }>
174
+ | { sandboxes?: Array<{ sandboxId: string; state?: string }> }
175
+ >
172
176
  kill?(sandboxId: string, opts?: Record<string, unknown>): Promise<boolean>
177
+ /**
178
+ * Read a sandbox's record WITHOUT connecting to it — the only lookup that does
179
+ * not resume a paused sandbox. Throws `SandboxNotFoundError` on a 404.
180
+ */
181
+ getInfo?(sandboxId: string, opts?: Record<string, unknown>): Promise<E2BSandboxInfoLike>
182
+ /**
183
+ * Whether an error means "this sandbox does not exist", as opposed to "the
184
+ * lookup failed". The distinction cannot be recovered from the error's shape by
185
+ * a consumer, and getting it wrong is what makes a control plane treat a
186
+ * provider outage as a destroyed sandbox.
187
+ */
188
+ isNotFound?(error: unknown): boolean
189
+ }
190
+ ```
191
+
192
+ #### `E2BSandboxInfoLike`
193
+
194
+ Subset of the SDK's `SandboxInfo` the bond reads.
195
+
196
+ This is what a sandbox looks like when you only LOOK at it. `connect` — which
197
+ is how a handle is obtained — RESUMES a paused sandbox and extends its
198
+ deadline, so it can never be used to answer "is this thing asleep?".
199
+
200
+ ```typescript
201
+ interface E2BSandboxInfoLike {
202
+ sandboxId: string
203
+ templateId?: string
204
+ /** `'running'` or `'paused'`; anything else is treated as running. */
205
+ state?: string
206
+ /** Caller metadata supplied at create (the bond puts `projectId` here). */
207
+ metadata?: Record<string, string>
208
+ /** When the sandbox last started running. */
209
+ startedAt?: Date | string
210
+ /** When the sandbox's current deadline expires. */
211
+ endAt?: Date | string
212
+ /** Volumes mounted into the sandbox, when the account uses them. */
213
+ volumeMounts?: Array<{ name: string; path: string }>
173
214
  }
174
215
  ```
175
216
 
@@ -272,10 +313,11 @@ Peer dependencies:
272
313
 
273
314
  ### Runtime Dependencies
274
315
 
275
- - `e2b`
316
+ - `@bufbuild/protobuf`
276
317
  - `@molecule/api-bond`
277
318
  - `@molecule/api-code-sandbox`
278
319
  - `@molecule/api-i18n`
320
+ - `e2b`
279
321
 
280
322
  **`verifyEgress` is intentionally not implemented yet.** The control plane
281
323
  treats an absent `verifyEgress` as an `inconclusive` verdict and refuses to
@@ -288,3 +330,26 @@ trade cost for security). Do not stub it with a fabricated `filtered`.
288
330
  `resume()` report `processesPreserved: true` because the memory snapshot
289
331
  restores the process tree — unlike a Docker stop, a resumed E2B sandbox's
290
332
  dev servers are still running.
333
+
334
+ **`get()` RESUMES a paused sandbox — use `describe()` to look at one.**
335
+ Obtaining a handle is `POST /sandboxes/{id}/connect`, which resumes a paused
336
+ sandbox and extends its deadline. That is right for a caller about to USE the
337
+ sandbox and wrong for every status check: polling `get()` every few seconds
338
+ silently un-hibernates every sleeping project and bills for the compute while
339
+ the UI still says "asleep". `describe(id)` reads the record instead, reports
340
+ `sleeping` for a paused sandbox, and changes nothing.
341
+
342
+ **`get()` returns `null` ONLY for a sandbox that does not exist.** Every other
343
+ failure throws. A control plane reads `null` as "gone", detaches the project
344
+ and rebuilds it from a template — and since an E2B microVM is the only copy of
345
+ a project's files, answering a transient 5xx with `null` destroys the user's
346
+ code. "I could not look" must never be delivered as "I looked, and it is not
347
+ there". The same rule governs `describe()`.
348
+
349
+ **Sandboxes are created to PAUSE at their timeout, not to be killed.** E2B's
350
+ default is `onTimeout: 'kill'`, so a sandbox nothing touched for its lifetime
351
+ would be destroyed with its files. This bond creates every sandbox with
352
+ `lifecycle: { onTimeout: { action: 'pause', keepMemory: true } }`; the memory
353
+ snapshot is what lets `resume()` truthfully report `processesPreserved: true`.
354
+ Extending the deadline is `keepAlive(ms)` — call it from a real activity
355
+ signal (an open editor's heartbeat), never as a side effect of polling.
package/dist/index.d.ts CHANGED
@@ -32,12 +32,9 @@
32
32
  * const provider = createProvider({
33
33
  * templateId: 'molecule-superset',
34
34
  * defaultPreviewPort: 5173,
35
- * defaultNetworkRules: [
36
- * { domain: 'registry.npmjs.org', action: 'allow' },
37
- * { domain: '*.npmjs.org', action: 'allow' },
38
- * { domain: 'github.com', action: 'allow' },
39
- * { domain: '*', action: 'deny' },
40
- * ],
35
+ * // Deny-by-default egress: everything not listed here is blocked, raw IPs
36
+ * // included. An empty/omitted list applies NO policy at all.
37
+ * defaultAllowOut: ['registry.npmjs.org', '*.npmjs.org', 'github.com'],
41
38
  * })
42
39
  * ```
43
40
  *
@@ -54,6 +51,29 @@
54
51
  * restores the process tree — unlike a Docker stop, a resumed E2B sandbox's
55
52
  * dev servers are still running.
56
53
  *
54
+ * **`get()` RESUMES a paused sandbox — use `describe()` to look at one.**
55
+ * Obtaining a handle is `POST /sandboxes/{id}/connect`, which resumes a paused
56
+ * sandbox and extends its deadline. That is right for a caller about to USE the
57
+ * sandbox and wrong for every status check: polling `get()` every few seconds
58
+ * silently un-hibernates every sleeping project and bills for the compute while
59
+ * the UI still says "asleep". `describe(id)` reads the record instead, reports
60
+ * `sleeping` for a paused sandbox, and changes nothing.
61
+ *
62
+ * **`get()` returns `null` ONLY for a sandbox that does not exist.** Every other
63
+ * failure throws. A control plane reads `null` as "gone", detaches the project
64
+ * and rebuilds it from a template — and since an E2B microVM is the only copy of
65
+ * a project's files, answering a transient 5xx with `null` destroys the user's
66
+ * code. "I could not look" must never be delivered as "I looked, and it is not
67
+ * there". The same rule governs `describe()`.
68
+ *
69
+ * **Sandboxes are created to PAUSE at their timeout, not to be killed.** E2B's
70
+ * default is `onTimeout: 'kill'`, so a sandbox nothing touched for its lifetime
71
+ * would be destroyed with its files. This bond creates every sandbox with
72
+ * `lifecycle: { onTimeout: { action: 'pause', keepMemory: true } }`; the memory
73
+ * snapshot is what lets `resume()` truthfully report `processesPreserved: true`.
74
+ * Extending the deadline is `keepAlive(ms)` — call it from a real activity
75
+ * signal (an open editor's heartbeat), never as a side effect of polling.
76
+ *
57
77
  * @module
58
78
  */
59
79
  export * from './provider.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyDG;AAEH,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6EG;AAEH,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA"}
package/dist/index.js CHANGED
@@ -32,12 +32,9 @@
32
32
  * const provider = createProvider({
33
33
  * templateId: 'molecule-superset',
34
34
  * defaultPreviewPort: 5173,
35
- * defaultNetworkRules: [
36
- * { domain: 'registry.npmjs.org', action: 'allow' },
37
- * { domain: '*.npmjs.org', action: 'allow' },
38
- * { domain: 'github.com', action: 'allow' },
39
- * { domain: '*', action: 'deny' },
40
- * ],
35
+ * // Deny-by-default egress: everything not listed here is blocked, raw IPs
36
+ * // included. An empty/omitted list applies NO policy at all.
37
+ * defaultAllowOut: ['registry.npmjs.org', '*.npmjs.org', 'github.com'],
41
38
  * })
42
39
  * ```
43
40
  *
@@ -54,6 +51,29 @@
54
51
  * restores the process tree — unlike a Docker stop, a resumed E2B sandbox's
55
52
  * dev servers are still running.
56
53
  *
54
+ * **`get()` RESUMES a paused sandbox — use `describe()` to look at one.**
55
+ * Obtaining a handle is `POST /sandboxes/{id}/connect`, which resumes a paused
56
+ * sandbox and extends its deadline. That is right for a caller about to USE the
57
+ * sandbox and wrong for every status check: polling `get()` every few seconds
58
+ * silently un-hibernates every sleeping project and bills for the compute while
59
+ * the UI still says "asleep". `describe(id)` reads the record instead, reports
60
+ * `sleeping` for a paused sandbox, and changes nothing.
61
+ *
62
+ * **`get()` returns `null` ONLY for a sandbox that does not exist.** Every other
63
+ * failure throws. A control plane reads `null` as "gone", detaches the project
64
+ * and rebuilds it from a template — and since an E2B microVM is the only copy of
65
+ * a project's files, answering a transient 5xx with `null` destroys the user's
66
+ * code. "I could not look" must never be delivered as "I looked, and it is not
67
+ * there". The same rule governs `describe()`.
68
+ *
69
+ * **Sandboxes are created to PAUSE at their timeout, not to be killed.** E2B's
70
+ * default is `onTimeout: 'kill'`, so a sandbox nothing touched for its lifetime
71
+ * would be destroyed with its files. This bond creates every sandbox with
72
+ * `lifecycle: { onTimeout: { action: 'pause', keepMemory: true } }`; the memory
73
+ * snapshot is what lets `resume()` truthfully report `processesPreserved: true`.
74
+ * Extending the deadline is `keepAlive(ms)` — call it from a real activity
75
+ * signal (an open editor's heartbeat), never as a side effect of polling.
76
+ *
57
77
  * @module
58
78
  */
59
79
  export * from './provider.js';
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * @module
10
10
  */
11
- import type { EgressVerdict, Sandbox, SandboxConfig, SandboxProvider } from '@molecule/api-code-sandbox';
11
+ import type { EgressVerdict, Sandbox, SandboxConfig, SandboxDescriptor, SandboxProvider } from '@molecule/api-code-sandbox';
12
12
  import type { E2BConfig, E2BSandboxClientLike } from './types.js';
13
13
  /**
14
14
  * E2B implementation of {@link SandboxProvider}.
@@ -41,6 +41,15 @@ export declare class E2BSandboxProvider implements SandboxProvider {
41
41
  /**
42
42
  * Create a new sandbox from the golden template and apply the egress policy.
43
43
  *
44
+ * The sandbox is created to PAUSE at its timeout, not to be killed. E2B's
45
+ * default is `onTimeout: 'kill'`, which means a sandbox nothing has touched for
46
+ * its lifetime is destroyed — and on this platform the sandbox is the only copy
47
+ * of the project, so the default turns "the tab was closed over lunch" into
48
+ * permanent data loss. `keepMemory` stays on: the pause snapshot restores the
49
+ * process tree, which is what makes `resume()` honest when it reports
50
+ * `processesPreserved: true`. A filesystem-only snapshot would cold-boot
51
+ * instead, leaving a sandbox that is running with every dev server dead.
52
+ *
44
53
  * @param config - Project id, env, optional per-boot templateId + labels.
45
54
  * @returns A live sandbox handle.
46
55
  */
@@ -52,6 +61,23 @@ export declare class E2BSandboxProvider implements SandboxProvider {
52
61
  * @returns A live handle, or `null` if no sandbox has that id.
53
62
  */
54
63
  get(id: string): Promise<Sandbox | null>;
64
+ /**
65
+ * Read a sandbox's record WITHOUT connecting to it.
66
+ *
67
+ * The lookup a control plane polls with. `get()` cannot serve that purpose on
68
+ * E2B: obtaining a handle is `POST /sandboxes/{id}/connect`, which RESUMES a
69
+ * paused sandbox and extends its deadline — so a status poll every few seconds
70
+ * silently un-hibernates every sleeping project, bills for the compute, and
71
+ * leaves the UI showing "asleep". This reads the record instead, so `paused`
72
+ * stays paused and is reported as `sleeping`.
73
+ *
74
+ * `null` means the sandbox positively does not exist. A failed lookup throws,
75
+ * for the same reason `get()` does.
76
+ *
77
+ * @param id - The sandbox id.
78
+ * @returns The descriptor, or `null` when no sandbox has that id.
79
+ */
80
+ describe(id: string): Promise<SandboxDescriptor | null>;
55
81
  /**
56
82
  * List the caller's live sandboxes.
57
83
  *
@@ -1 +1 @@
1
- {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAEV,aAAa,EAKb,OAAO,EACP,aAAa,EACb,eAAe,EAChB,MAAM,4BAA4B,CAAA;AAEnC,OAAO,KAAK,EAAE,SAAS,EAAE,oBAAoB,EAAkB,MAAM,YAAY,CAAA;AAqYjF;;;;;;;;;GASG;AACH,qBAAa,kBAAmB,YAAW,eAAe;IACxD,QAAQ,CAAC,IAAI,SAAQ;IAErB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA0D;IACjF,OAAO,CAAC,aAAa,CAA6C;IAClE,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAsB;IAEtD;;;;;OAKG;gBACS,MAAM,GAAE,SAAc,EAAE,cAAc,CAAC,EAAE,oBAAoB;IAYzE;;;;OAIG;YACW,MAAM;IASpB;;;;;OAKG;IACG,MAAM,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC;IAmBrD;;;;;OAKG;IACG,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IAgB9C;;;;;OAKG;IACG,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IAY/C;;;;OAIG;IACG,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAcxC;;;;;;;;;;;OAWG;IACG,YAAY,IAAI,OAAO,CAAC,aAAa,CAAC;CAyD7C;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,MAAM,GAAE,SAAc,EACtB,cAAc,CAAC,EAAE,oBAAoB,GACpC,kBAAkB,CAEpB;AAED,kEAAkE;AAClE,eAAO,MAAM,QAAQ,EAAE,eAAkC,CAAA"}
1
+ {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAEV,aAAa,EAKb,OAAO,EACP,aAAa,EACb,iBAAiB,EACjB,eAAe,EAChB,MAAM,4BAA4B,CAAA;AAEnC,OAAO,KAAK,EACV,SAAS,EACT,oBAAoB,EAGrB,MAAM,YAAY,CAAA;AAwdnB;;;;;;;;;GASG;AACH,qBAAa,kBAAmB,YAAW,eAAe;IACxD,QAAQ,CAAC,IAAI,SAAQ;IAErB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA0D;IACjF,OAAO,CAAC,aAAa,CAA6C;IAClE,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAsB;IAEtD;;;;;OAKG;gBACS,MAAM,GAAE,SAAc,EAAE,cAAc,CAAC,EAAE,oBAAoB;IAYzE;;;;OAIG;YACW,MAAM;IASpB;;;;;;;;;;;;;;OAcG;IACG,MAAM,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC;IAoBrD;;;;;OAKG;IACG,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IAoB9C;;;;;;;;;;;;;;;OAeG;IACG,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC;IAkC7D;;;;;OAKG;IACG,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IAgB/C;;;;OAIG;IACG,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAcxC;;;;;;;;;;;OAWG;IACG,YAAY,IAAI,OAAO,CAAC,aAAa,CAAC;CAyD7C;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,MAAM,GAAE,SAAc,EACtB,cAAc,CAAC,EAAE,oBAAoB,GACpC,kBAAkB,CAEpB;AAED,kEAAkE;AAClE,eAAO,MAAM,QAAQ,EAAE,eAAkC,CAAA"}
package/dist/provider.js CHANGED
@@ -28,12 +28,18 @@ const EGRESS_PROBE_DENY = 'example.com';
28
28
  * Imported lazily so the SDK is only required when the bond is actually used.
29
29
  */
30
30
  async function defaultClient(apiKey) {
31
- const { Sandbox } = await import('e2b');
31
+ const { Sandbox, SandboxNotFoundError } = await import('e2b');
32
32
  const S = Sandbox;
33
33
  return {
34
34
  create: (templateId, opts) => S.create(templateId, { apiKey, ...opts }),
35
35
  connect: (id, opts) => S.connect(id, { apiKey, ...opts }),
36
36
  kill: (id, opts) => S.kill(id, { apiKey, ...opts }),
37
+ getInfo: (id, opts) => S.getInfo(id, { apiKey, ...opts }),
38
+ // The SDK raises `SandboxNotFoundError` from `getInfo`/`connect` for exactly
39
+ // one condition — the API answered 404 — and routes every other failure to a
40
+ // different class. That makes it a POSITIVE not-found, which is the whole
41
+ // basis for `get()` returning null.
42
+ isNotFound: (error) => error instanceof SandboxNotFoundError,
37
43
  async list(opts) {
38
44
  // Sandbox.list returns a paginator; normalize to a flat array of running
39
45
  // sandboxes. Support the async-iterator, nextItems(), and array shapes so
@@ -41,9 +47,11 @@ async function defaultClient(apiKey) {
41
47
  const result = S.list({ apiKey, ...opts });
42
48
  const out = [];
43
49
  const push = (items) => {
50
+ // Carry the listed state through: it is the only way a caller can skip a
51
+ // PAUSED sandbox, and building a handle for one would resume it.
44
52
  for (const it of items)
45
53
  if (it?.sandboxId)
46
- out.push({ sandboxId: it.sandboxId });
54
+ out.push({ sandboxId: it.sandboxId, state: it.state });
47
55
  };
48
56
  const r = await Promise.resolve(result).catch(() => result);
49
57
  if (Array.isArray(r)) {
@@ -67,6 +75,15 @@ async function defaultClient(apiKey) {
67
75
  },
68
76
  };
69
77
  }
78
+ /**
79
+ * Single-quote a value for POSIX `sh` so an arbitrary path is one argument.
80
+ *
81
+ * @param value - The raw string.
82
+ * @returns The quoted string.
83
+ */
84
+ function shellQuote(value) {
85
+ return `'${value.replace(/'/g, "'\\''")}'`;
86
+ }
70
87
  /**
71
88
  * Build a deny-by-default network update from an allow-list.
72
89
  *
@@ -76,6 +93,49 @@ async function defaultClient(apiKey) {
76
93
  function denyByDefault(allowOut) {
77
94
  return { allowOut, denyOut: [ALL_TRAFFIC] };
78
95
  }
96
+ /**
97
+ * Whether an error means the sandbox POSITIVELY does not exist.
98
+ *
99
+ * Asks the client (the real one compares against the SDK's own
100
+ * `SandboxNotFoundError` class), and falls back to the error's `name` so an
101
+ * injected/duck-typed client — or an SDK loaded twice under different module
102
+ * instances, where `instanceof` silently fails — is still classified correctly.
103
+ * Everything else is a failure to LOOK and must propagate.
104
+ *
105
+ * @param client - The client the error came from.
106
+ * @param error - The thrown value.
107
+ * @returns True only for a not-found.
108
+ */
109
+ function isNotFound(client, error) {
110
+ if (client.isNotFound?.(error))
111
+ return true;
112
+ return error?.name === 'SandboxNotFoundError';
113
+ }
114
+ /**
115
+ * Map an E2B sandbox record onto the core's coarse lifecycle status.
116
+ *
117
+ * E2B has exactly two states. `paused` is a filesystem + memory snapshot, which
118
+ * is what the core calls `sleeping` — NOT `stopped`, because a resume brings the
119
+ * process tree back with it and callers branch on that.
120
+ *
121
+ * @param state - The SDK's `state` field.
122
+ * @returns The core status.
123
+ */
124
+ function toCoreStatus(state) {
125
+ return state === 'paused' ? 'sleeping' : 'running';
126
+ }
127
+ /**
128
+ * Normalize an SDK date field (a `Date`, an ISO string, or absent) to ISO.
129
+ *
130
+ * @param value - The SDK value.
131
+ * @returns An ISO 8601 string, or `null`.
132
+ */
133
+ function toIso(value) {
134
+ if (!value)
135
+ return null;
136
+ const date = value instanceof Date ? value : new Date(value);
137
+ return Number.isNaN(date.getTime()) ? null : date.toISOString();
138
+ }
79
139
  /**
80
140
  * A live E2B sandbox mapped onto the core `Sandbox` handle.
81
141
  *
@@ -316,7 +376,7 @@ class E2BSandbox {
316
376
  chunks.push(chunk);
317
377
  const tarPath = `/tmp/mol-import-${Date.now().toString(36)}.tar`;
318
378
  await this.sbx.files.write(tarPath, new Blob([Buffer.concat(chunks)]));
319
- const r = await this.sbx.commands.run(`mkdir -p ${path} && tar xf ${tarPath} -C ${path} --no-same-owner --no-same-permissions && rm -f ${tarPath}`, { timeoutMs: 300_000 });
379
+ const r = await this.sbx.commands.run(`mkdir -p ${shellQuote(path)} && tar xf ${tarPath} -C ${shellQuote(path)} --no-same-owner --no-same-permissions && rm -f ${tarPath}`, { timeoutMs: 300_000 });
320
380
  if (r.exitCode !== 0) {
321
381
  throw new Error(`importFiles: tar extract failed (${r.exitCode}): ${r.stderr.slice(0, 300)}`);
322
382
  }
@@ -326,14 +386,30 @@ class E2BSandbox {
326
386
  *
327
387
  * Tars the tree in-sandbox, reads it back as bytes, and yields it as a single
328
388
  * chunk. Sufficient for archive/migrate (whole-workspace capture); not a
329
- * chunked pipe.
389
+ * chunked pipe — the whole archive is held in this process's memory once, so
390
+ * callers moving large trees must bound what they export.
391
+ *
392
+ * Entries are rooted at the LAST SEGMENT of `path` (`tar -C <parent>
393
+ * <name>`), so `exportFiles('/workspace/my-app')` yields `my-app/…` — the
394
+ * shape Docker's archive endpoint produces and the shape every consumer
395
+ * (`importFiles(<parent>)`, host-side unpackers with `strip: 1`) expects.
396
+ * The previous `tar -C <path> .` rooting yielded `./…`, which
397
+ * `importFiles('/')` extracted at the filesystem root instead of the
398
+ * directory it was taken from.
330
399
  *
331
- * @param path - Absolute path inside the sandbox to archive.
400
+ * @param path - Absolute path inside the sandbox to archive (not `/`).
332
401
  * @returns A POSIX tar byte stream of that path's contents.
333
402
  */
334
403
  async exportFiles(path) {
404
+ const trimmed = path.replace(/\/+$/, '');
405
+ const slash = trimmed.lastIndexOf('/');
406
+ const parent = slash <= 0 ? '/' : trimmed.slice(0, slash);
407
+ const name = trimmed.slice(slash + 1);
408
+ if (!trimmed.startsWith('/') || !name) {
409
+ throw new Error(`exportFiles: path must be an absolute directory below "/" (got "${path}")`);
410
+ }
335
411
  const tarPath = `/tmp/mol-export-${Date.now().toString(36)}.tar`;
336
- const r = await this.sbx.commands.run(`tar cf ${tarPath} -C ${path} .`, { timeoutMs: 300_000 });
412
+ const r = await this.sbx.commands.run(`tar cf ${tarPath} -C ${shellQuote(parent)} ${shellQuote(name)}`, { timeoutMs: 300_000 });
337
413
  if (r.exitCode !== 0) {
338
414
  throw new Error(`exportFiles: tar create failed (${r.exitCode}): ${r.stderr.slice(0, 300)}`);
339
415
  }
@@ -415,6 +491,15 @@ export class E2BSandboxProvider {
415
491
  /**
416
492
  * Create a new sandbox from the golden template and apply the egress policy.
417
493
  *
494
+ * The sandbox is created to PAUSE at its timeout, not to be killed. E2B's
495
+ * default is `onTimeout: 'kill'`, which means a sandbox nothing has touched for
496
+ * its lifetime is destroyed — and on this platform the sandbox is the only copy
497
+ * of the project, so the default turns "the tab was closed over lunch" into
498
+ * permanent data loss. `keepMemory` stays on: the pause snapshot restores the
499
+ * process tree, which is what makes `resume()` honest when it reports
500
+ * `processesPreserved: true`. A filesystem-only snapshot would cold-boot
501
+ * instead, leaving a sandbox that is running with every dev server dead.
502
+ *
418
503
  * @param config - Project id, env, optional per-boot templateId + labels.
419
504
  * @returns A live sandbox handle.
420
505
  */
@@ -425,6 +510,7 @@ export class E2BSandboxProvider {
425
510
  timeoutMs: this.config.defaultTimeoutMs,
426
511
  envs: config.env ?? {},
427
512
  metadata: { projectId: config.projectId, ...(config.labels ?? {}) },
513
+ lifecycle: { onTimeout: { action: 'pause', keepMemory: true } },
428
514
  });
429
515
  const handle = new E2BSandbox(sbx, client, {
430
516
  previewPort: this.config.defaultPreviewPort,
@@ -451,12 +537,68 @@ export class E2BSandboxProvider {
451
537
  timeoutMs: this.config.defaultTimeoutMs,
452
538
  });
453
539
  }
454
- catch (_error) {
455
- // connect throws SandboxNotFoundError for a genuinely absent id → null.
456
- // A transient error also lands here; get() is used as an existence probe,
457
- // and the callers re-resolve, so returning null is the safe answer.
458
- return null;
540
+ catch (error) {
541
+ // `null` means the sandbox is GONE, and nothing else. Every other failure
542
+ // THROWS. This used to swallow all of them, and the cost of that is not
543
+ // hypothetical: a control plane reads `null` as "the container is gone",
544
+ // detaches the project, and rebuilds it from a template — so one 5xx from
545
+ // this API destroyed a user's only copy of their code. "I could not look"
546
+ // must never be delivered as "I looked, and it is not there".
547
+ if (isNotFound(client, error))
548
+ return null;
549
+ throw error;
550
+ }
551
+ }
552
+ /**
553
+ * Read a sandbox's record WITHOUT connecting to it.
554
+ *
555
+ * The lookup a control plane polls with. `get()` cannot serve that purpose on
556
+ * E2B: obtaining a handle is `POST /sandboxes/{id}/connect`, which RESUMES a
557
+ * paused sandbox and extends its deadline — so a status poll every few seconds
558
+ * silently un-hibernates every sleeping project, bills for the compute, and
559
+ * leaves the UI showing "asleep". This reads the record instead, so `paused`
560
+ * stays paused and is reported as `sleeping`.
561
+ *
562
+ * `null` means the sandbox positively does not exist. A failed lookup throws,
563
+ * for the same reason `get()` does.
564
+ *
565
+ * @param id - The sandbox id.
566
+ * @returns The descriptor, or `null` when no sandbox has that id.
567
+ */
568
+ async describe(id) {
569
+ const client = await this.client();
570
+ if (!client.getInfo) {
571
+ // Refusing is the point: answering `null` here would report "no such
572
+ // sandbox" for a client that simply cannot look.
573
+ throw new Error('E2B client does not expose getInfo(); cannot describe a sandbox');
574
+ }
575
+ let info;
576
+ try {
577
+ info = await client.getInfo(id);
459
578
  }
579
+ catch (error) {
580
+ if (isNotFound(client, error))
581
+ return null;
582
+ throw error;
583
+ }
584
+ const startedAt = toIso(info.startedAt);
585
+ return {
586
+ id: info.sandboxId ?? id,
587
+ projectId: info.metadata?.projectId ?? null,
588
+ status: toCoreStatus(info.state),
589
+ labels: info.metadata ?? {},
590
+ // E2B has no created-but-never-started state — a sandbox exists only once
591
+ // it has run — so both timestamps are the same fact, and neither is ever
592
+ // the `null` that marks wreckage on a provider that does have one.
593
+ createdAt: startedAt,
594
+ startedAt,
595
+ templateRef: info.templateId ?? null,
596
+ volumeName: info.volumeMounts?.[0]?.name ?? null,
597
+ // E2B publishes every internal port at `<port>-<id>.e2b.app` rather than
598
+ // mapping it to a host port, so there are no mappings to report. The
599
+ // reachable URL comes from `getPreviewUrl(port)`.
600
+ ports: [],
601
+ };
460
602
  }
461
603
  /**
462
604
  * List the caller's live sandboxes.
@@ -470,6 +612,11 @@ export class E2BSandboxProvider {
470
612
  const items = Array.isArray(running) ? running : (running.sandboxes ?? []);
471
613
  const handles = [];
472
614
  for (const it of items) {
615
+ // A PAUSED sandbox is deliberately skipped: building its handle means
616
+ // connecting, and connecting resumes it. Enumerating an account would
617
+ // otherwise wake — and start billing — every hibernated project on it.
618
+ if (it.state === 'paused')
619
+ continue;
473
620
  const h = await this.get(it.sandboxId);
474
621
  if (h)
475
622
  handles.push(h);
package/dist/types.d.ts CHANGED
@@ -31,6 +31,10 @@ export interface E2BConfig {
31
31
  /**
32
32
  * Default sandbox lifetime before E2B auto-pauses it, in milliseconds. The
33
33
  * control plane extends this per-heartbeat; this is only the initial ceiling.
34
+ *
35
+ * E2B caps this per account: 1 hour on Hobby, 24 hours on Pro. A value above
36
+ * the account's cap is rejected at create time, so raising it is a decision
37
+ * about the account, not just the config.
34
38
  */
35
39
  defaultTimeoutMs?: number;
36
40
  /**
@@ -94,17 +98,55 @@ export interface E2BSandboxLike {
94
98
  denyOut?: string[];
95
99
  }): Promise<void>;
96
100
  }
101
+ /**
102
+ * Subset of the SDK's `SandboxInfo` the bond reads.
103
+ *
104
+ * This is what a sandbox looks like when you only LOOK at it. `connect` — which
105
+ * is how a handle is obtained — RESUMES a paused sandbox and extends its
106
+ * deadline, so it can never be used to answer "is this thing asleep?".
107
+ */
108
+ export interface E2BSandboxInfoLike {
109
+ sandboxId: string;
110
+ templateId?: string;
111
+ /** `'running'` or `'paused'`; anything else is treated as running. */
112
+ state?: string;
113
+ /** Caller metadata supplied at create (the bond puts `projectId` here). */
114
+ metadata?: Record<string, string>;
115
+ /** When the sandbox last started running. */
116
+ startedAt?: Date | string;
117
+ /** When the sandbox's current deadline expires. */
118
+ endAt?: Date | string;
119
+ /** Volumes mounted into the sandbox, when the account uses them. */
120
+ volumeMounts?: Array<{
121
+ name: string;
122
+ path: string;
123
+ }>;
124
+ }
97
125
  /** Subset of the SDK's `Sandbox` static surface the bond uses. */
98
126
  export interface E2BSandboxClientLike {
99
127
  create(templateId: string, opts?: Record<string, unknown>): Promise<E2BSandboxLike>;
100
128
  connect(sandboxId: string, opts?: Record<string, unknown>): Promise<E2BSandboxLike>;
101
129
  list(opts?: Record<string, unknown>): Promise<Array<{
102
130
  sandboxId: string;
131
+ state?: string;
103
132
  }> | {
104
133
  sandboxes?: Array<{
105
134
  sandboxId: string;
135
+ state?: string;
106
136
  }>;
107
137
  }>;
108
138
  kill?(sandboxId: string, opts?: Record<string, unknown>): Promise<boolean>;
139
+ /**
140
+ * Read a sandbox's record WITHOUT connecting to it — the only lookup that does
141
+ * not resume a paused sandbox. Throws `SandboxNotFoundError` on a 404.
142
+ */
143
+ getInfo?(sandboxId: string, opts?: Record<string, unknown>): Promise<E2BSandboxInfoLike>;
144
+ /**
145
+ * Whether an error means "this sandbox does not exist", as opposed to "the
146
+ * lookup failed". The distinction cannot be recovered from the error's shape by
147
+ * a consumer, and getting it wrong is what makes a control plane treat a
148
+ * provider outage as a destroyed sandbox.
149
+ */
150
+ isNotFound?(error: unknown): boolean;
109
151
  }
110
152
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,qFAAqF;AACrF,MAAM,WAAW,SAAS;IACxB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B;;;;;;;OAOG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB;;;;;;;;;;OAUG;IACH,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;CAC3B;AAED,0EAA0E;AAC1E,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,sDAAsD;AACtD,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IACnC,mFAAmF;IACnF,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,MAAM,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,UAAU,CAAC,CAAA;IAClE,kEAAkE;IAClE,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,WAAW,GAAG,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IACrF,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAA;IAClF,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACpC;AAED,oDAAoD;AACpD,MAAM,WAAW,eAAe;IAC9B,GAAG,CACD,GAAG,EAAE,MAAM,EACX,IAAI,CAAC,EAAE;QACL,GAAG,CAAC,EAAE,MAAM,CAAA;QACZ,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;QAC7B,qEAAqE;QACrE,UAAU,CAAC,EAAE,OAAO,CAAA;KACrB,GACA,OAAO,CAAC,oBAAoB,CAAC,CAAA;CACjC;AAED,4DAA4D;AAC5D,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,eAAe,CAAA;IACzB,KAAK,EAAE,iBAAiB,CAAA;IACxB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAA;IAC7B,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACrC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACrB,KAAK,CAAC,IAAI,OAAO,CAAC,MAAM,CAAC,CAAA;IACzB,SAAS,CAAC,IAAI,OAAO,CAAC,MAAM,CAAC,CAAA;IAC7B,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC,CAAA;IAC7B,aAAa,CAAC,CAAC,IAAI,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACjF;AAED;;;;;;GAMG;AACH,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,sEAAsE;IACtE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACjC,6CAA6C;IAC7C,SAAS,CAAC,EAAE,IAAI,GAAG,MAAM,CAAA;IACzB,mDAAmD;IACnD,KAAK,CAAC,EAAE,IAAI,GAAG,MAAM,CAAA;IACrB,oEAAoE;IACpE,YAAY,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CACrD;AAED,kEAAkE;AAClE,MAAM,WAAW,oBAAoB;IACnC,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,cAAc,CAAC,CAAA;IACnF,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,cAAc,CAAC,CAAA;IACnF,IAAI,CACF,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,OAAO,CACN,KAAK,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,GAC5C;QAAE,SAAS,CAAC,EAAE,KAAK,CAAC;YAAE,SAAS,EAAE,MAAM,CAAC;YAAC,KAAK,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KAAE,CAC/D,CAAA;IACD,IAAI,CAAC,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAC1E;;;OAGG;IACH,OAAO,CAAC,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAA;IACxF;;;;;OAKG;IACH,UAAU,CAAC,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAA;CACrC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@molecule/api-code-sandbox-e2b",
3
- "version": "1.0.4",
3
+ "version": "1.1.0",
4
4
  "description": "E2B (e2b.dev) code sandbox provider — Firecracker microVMs with golden templates, fork, and pause/resume",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -30,7 +30,7 @@
30
30
  "license": "Apache-2.0",
31
31
  "devDependencies": {
32
32
  "@molecule/api-bond": "1.0.1",
33
- "@molecule/api-code-sandbox": "1.0.3",
33
+ "@molecule/api-code-sandbox": "1.1.1",
34
34
  "@types/node": "26.1.2",
35
35
  "typescript": "6.0.3",
36
36
  "vitest": "4.1.10"