@molecule/api-code-sandbox-e2b 1.0.4 → 1.2.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/dist/index.js CHANGED
@@ -32,21 +32,20 @@
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
  *
44
41
  * @remarks
45
- * **`verifyEgress` is intentionally not implemented yet.** The control plane
46
- * treats an absent `verifyEgress` as an `inconclusive` verdict and refuses to
47
- * boot sandboxes in production — the correct safe default until egress
48
- * observation is proven against E2B's `updateNetwork` policy (Rule 18: never
49
- * trade cost for security). Do not stub it with a fabricated `filtered`.
42
+ * **`verifyEgress` OBSERVES, it never attests.** It boots a throwaway sandbox,
43
+ * applies `{ allowOut: [npm], denyOut: [ALL_TRAFFIC] }`, and curls an
44
+ * allow-listed host, a non-allow-listed host AND a raw IP from inside it;
45
+ * `filtered` requires the last two to be blocked while the first answers. Any
46
+ * failure to run that probe is `inconclusive` — never `filtered`, because "I
47
+ * could not look" must not reach a control plane as "I looked, and it is safe"
48
+ * (Rule 18: never trade cost for security).
50
49
  *
51
50
  * **E2B pauses, it does not stop.** `sleep()`/`stop()` both map to E2B pause
52
51
  * (FS + memory snapshot); `wake()`/`start()` reconnect by id. `hibernate()`/
@@ -54,6 +53,94 @@
54
53
  * restores the process tree — unlike a Docker stop, a resumed E2B sandbox's
55
54
  * dev servers are still running.
56
55
  *
56
+ * **`get()` RESUMES a paused sandbox — use `describe()` to look at one.**
57
+ * Obtaining a handle is `POST /sandboxes/{id}/connect`, which resumes a paused
58
+ * sandbox and extends its deadline. That is right for a caller about to USE the
59
+ * sandbox and wrong for every status check: polling `get()` every few seconds
60
+ * silently un-hibernates every sleeping project and bills for the compute while
61
+ * the UI still says "asleep". `describe(id)` reads the record instead, reports
62
+ * `sleeping` for a paused sandbox, and changes nothing.
63
+ *
64
+ * **`get()` returns `null` ONLY for a sandbox that does not exist.** Every other
65
+ * failure throws. A control plane reads `null` as "gone", detaches the project
66
+ * and rebuilds it from a template — and since an E2B microVM is the only copy of
67
+ * a project's files, answering a transient 5xx with `null` destroys the user's
68
+ * code. "I could not look" must never be delivered as "I looked, and it is not
69
+ * there". The same rule governs `describe()`.
70
+ *
71
+ * **`hibernate()`/`stop()`/`sleep()` pause or THROW.** They never resolve a
72
+ * success-shaped outcome for a sandbox that is still running: a caller's next act
73
+ * is to record the sandbox as stopped, and a control plane that believes a running
74
+ * sandbox is asleep bills for it and — with the kill timeout — watches it die at
75
+ * its deadline instead of hibernating. Note the converse trap: a pause is undone by
76
+ * the very next `get()`, since obtaining a handle connects. Anything that polls a
77
+ * stopped sandbox (status, logs, files) must go through `describe()`.
78
+ *
79
+ * **`exec()` starts the command and waits on its HANDLE, never inline.** E2B's
80
+ * inline `commands.run` waits for the whole process GROUP, so any command that
81
+ * leaves a detached child behind — `nohup … >log 2>&1 &`, i.e. every dev-server
82
+ * launch — blocks until the request deadline and then throws while the child is
83
+ * running perfectly. Starting in the background and awaiting the handle returns
84
+ * the STARTED process's real exit code in milliseconds, so a launcher learns
85
+ * whether its command was accepted. Do not reintroduce the old shortcut of
86
+ * sniffing the command string for a trailing `&`: it classified shell text
87
+ * instead of observing the process, and got both halves wrong — a launch shaped
88
+ * `… & fi` did not match and hung, while a user's `npm run build &` was answered
89
+ * with a fabricated empty success.
90
+ *
91
+ * **`spawn()` is what an editor and a terminal need, and `exec()` cannot give.**
92
+ * It returns a live process: streaming stdout/stderr, writable stdin, `kill()`.
93
+ * Pass `pty: { cols, rows }` for a real controlling terminal — then Ctrl-C
94
+ * (`0x03`) becomes SIGINT for the foreground job and `handle.resize({cols,rows})`
95
+ * renegotiates the width. Omit it for a language server, whose framed JSON-RPC a
96
+ * PTY would corrupt with echo and CR translation. A PTY request is REJECTED
97
+ * rather than downgraded when the SDK build has no `pty` module, because a
98
+ * terminal that silently got pipes is a terminal whose Ctrl-C does nothing.
99
+ *
100
+ * **Two ways to make a project outlive its sandbox, and they are not the same
101
+ * strength.** A **volume** (`SandboxConfig.volumeName` + `volumeMountPath`) is
102
+ * continuous: every write already lives outside the microVM, so losing the
103
+ * sandbox loses nothing. A **snapshot** (`commitTemplate`, restored by
104
+ * `create({ templateId })`) is point-in-time: a persistent image that survives
105
+ * the sandbox, at the cost of everything written since the capture. Prefer the
106
+ * volume; take snapshots when the account has no volumes, or as restore points
107
+ * alongside one.
108
+ *
109
+ * **A volume needs a mount path, and `create()` refuses without one.** E2B
110
+ * mounts shadow whatever the image had at that path, and this bond's superset
111
+ * template keeps a multi-GB `/workspace/node_modules` there — so the obvious
112
+ * default (the workspace root) is the one value that boots a project unable to
113
+ * resolve a single import. Mount the app directory instead
114
+ * (`/workspace/<appDir>`): the durable source lands on the volume and the
115
+ * regenerable tooling stays on the faster image-backed rootfs.
116
+ *
117
+ * **A volume can only be attached when the sandbox is created.** There is no
118
+ * attach-to-a-running-sandbox call, so a sandbox claimed from a pre-warmed pool
119
+ * can never be given a project's volume afterwards — a project that needs one
120
+ * must be booted fresh with it.
121
+ *
122
+ * **Volumes are a private beta on E2B.** An account without them answers
123
+ * `403 use of volumes is not enabled` (measured on the production account,
124
+ * 2026-08-16); ask E2B support to enable them. Every volume method throws in
125
+ * that state rather than no-op-ing, because a control plane that believes it has
126
+ * durable storage and does not is the failure this bond exists to prevent.
127
+ *
128
+ * **Snapshots ARE available and they do survive a kill** — verified live: a
129
+ * sandbox was killed and a new one created from its snapshot came up with the
130
+ * same files, in ~2.3 s. Capturing takes well under a second, leaves the sandbox
131
+ * running, and re-using a name replaces that snapshot rather than adding one, so
132
+ * a per-project restore point is a fixed-size resource. It BRIEFLY pauses the
133
+ * sandbox and drops open connections (PTYs, command streams, websockets), so
134
+ * capture when a project goes quiet — never underneath a live terminal.
135
+ *
136
+ * **Sandboxes are created to PAUSE at their timeout, not to be killed.** E2B's
137
+ * default is `onTimeout: 'kill'`, so a sandbox nothing touched for its lifetime
138
+ * would be destroyed with its files. This bond creates every sandbox with
139
+ * `lifecycle: { onTimeout: { action: 'pause', keepMemory: true } }`; the memory
140
+ * snapshot is what lets `resume()` truthfully report `processesPreserved: true`.
141
+ * Extending the deadline is `keepAlive(ms)` — call it from a real activity
142
+ * signal (an open editor's heartbeat), never as a side effect of polling.
143
+ *
57
144
  * @module
58
145
  */
59
146
  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 { CommitTemplateOptions, EgressVerdict, ListTemplatesOptions, ListVolumesOptions, Sandbox, SandboxConfig, SandboxDescriptor, SandboxProvider, SandboxTemplate, VolumeInfo } from '@molecule/api-code-sandbox';
12
12
  import type { E2BConfig, E2BSandboxClientLike } from './types.js';
13
13
  /**
14
14
  * E2B implementation of {@link SandboxProvider}.
@@ -41,7 +41,26 @@ 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
- * @param config - Project id, env, optional per-boot templateId + labels.
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
+ *
53
+ * `config.volumeName` mounts a persistent volume, which E2B can only attach at
54
+ * CREATE time — there is no attach-to-a-running-sandbox call — so a sandbox
55
+ * claimed from a pre-warmed pool can never be given one afterwards. A mount
56
+ * path is REQUIRED alongside it: E2B mounts shadow whatever the image had at
57
+ * that path, and this bond's superset template keeps a 2.4 GB
58
+ * `/workspace/node_modules` there, so defaulting to the workspace root would
59
+ * hide the entire dependency fleet behind an empty volume and boot a project
60
+ * that cannot resolve a single import.
61
+ *
62
+ * @param config - Project id, env, optional per-boot templateId + labels, and
63
+ * an optional `volumeName` + `volumeMountPath` pair.
45
64
  * @returns A live sandbox handle.
46
65
  */
47
66
  create(config: SandboxConfig): Promise<Sandbox>;
@@ -52,6 +71,23 @@ export declare class E2BSandboxProvider implements SandboxProvider {
52
71
  * @returns A live handle, or `null` if no sandbox has that id.
53
72
  */
54
73
  get(id: string): Promise<Sandbox | null>;
74
+ /**
75
+ * Read a sandbox's record WITHOUT connecting to it.
76
+ *
77
+ * The lookup a control plane polls with. `get()` cannot serve that purpose on
78
+ * E2B: obtaining a handle is `POST /sandboxes/{id}/connect`, which RESUMES a
79
+ * paused sandbox and extends its deadline — so a status poll every few seconds
80
+ * silently un-hibernates every sleeping project, bills for the compute, and
81
+ * leaves the UI showing "asleep". This reads the record instead, so `paused`
82
+ * stays paused and is reported as `sleeping`.
83
+ *
84
+ * `null` means the sandbox positively does not exist. A failed lookup throws,
85
+ * for the same reason `get()` does.
86
+ *
87
+ * @param id - The sandbox id.
88
+ * @returns The descriptor, or `null` when no sandbox has that id.
89
+ */
90
+ describe(id: string): Promise<SandboxDescriptor | null>;
55
91
  /**
56
92
  * List the caller's live sandboxes.
57
93
  *
@@ -65,6 +101,129 @@ export declare class E2BSandboxProvider implements SandboxProvider {
65
101
  * @param id - The sandbox id.
66
102
  */
67
103
  destroy(id: string): Promise<void>;
104
+ /**
105
+ * Every sandbox that currently EXISTS, running or paused.
106
+ *
107
+ * The basis for both "is this volume attached?" and "is this snapshot in use?".
108
+ * Paused sandboxes are included deliberately — a paused sandbox still owns its
109
+ * volume and still boots from its template, and treating it as absent is how a
110
+ * reclamation sweep deletes the storage under a hibernated project.
111
+ *
112
+ * @returns The raw listing rows.
113
+ */
114
+ private liveSandboxes;
115
+ /**
116
+ * Resolve a volume by the caller's name.
117
+ *
118
+ * @param name - The volume name.
119
+ * @returns The volume, or `null` when the account has no volume by that name.
120
+ */
121
+ private findVolume;
122
+ /**
123
+ * Create a named volume, idempotently.
124
+ *
125
+ * Callers create the volume on EVERY boot (they cannot know whether a previous
126
+ * sandbox already made it), so an existing volume is a success and must not be
127
+ * re-created — re-creating would either fail the boot or, worse, hand back an
128
+ * empty volume in place of the user's files.
129
+ *
130
+ * Volumes are a private beta on E2B: an account without them answers
131
+ * `403 use of volumes is not enabled`, which surfaces here as a throw rather
132
+ * than a silent no-op, because a caller that believes it has a durable volume
133
+ * and does not is exactly the failure this method exists to prevent.
134
+ *
135
+ * @param name - The volume name.
136
+ */
137
+ createVolume(name: string): Promise<void>;
138
+ /**
139
+ * Remove a named volume. Removing one that does not exist is a success.
140
+ *
141
+ * @param name - The volume name.
142
+ */
143
+ removeVolume(name: string): Promise<void>;
144
+ /**
145
+ * Whether a named volume exists.
146
+ *
147
+ * THROWS when it cannot look. A control plane asks this to decide whether a
148
+ * project's files survived losing its sandbox, and answering `false` for a
149
+ * failed lookup tells it the user's work is gone — the same "could not look
150
+ * delivered as looked-and-empty" confusion that `get()` returning `null` used
151
+ * to be.
152
+ *
153
+ * @param name - The volume name.
154
+ * @returns `true` when the account has a volume by that name.
155
+ */
156
+ volumeExists(name: string): Promise<boolean>;
157
+ /**
158
+ * Enumerate volumes, reporting which ones a sandbox currently holds.
159
+ *
160
+ * `attached` is OBSERVED from the sandbox listing (running and paused alike),
161
+ * not assumed, because the only reason to enumerate volumes is to delete the
162
+ * unattached ones. E2B reports neither a creation time nor a size for a volume,
163
+ * so both are `null` rather than invented.
164
+ *
165
+ * @param options - Narrowing by name prefix and attachment.
166
+ * @returns Every matching volume.
167
+ */
168
+ listVolumes(options?: ListVolumesOptions): Promise<VolumeInfo[]>;
169
+ /**
170
+ * Capture a sandbox as a reusable template, using E2B snapshots.
171
+ *
172
+ * A snapshot is a persistent image of the sandbox's filesystem AND memory that
173
+ * outlives the sandbox itself — verified against production: a sandbox was
174
+ * killed and a new one created from its snapshot came up with the same files.
175
+ * Re-committing the same `templateId` assigns a new build to the same snapshot
176
+ * rather than accumulating a second one, so a per-project restore point stays
177
+ * one resource no matter how often it is refreshed.
178
+ *
179
+ * Snapshotting BRIEFLY PAUSES the sandbox and drops its open connections
180
+ * (PTYs, command streams, websockets). Take one when the project is going
181
+ * quiet, not underneath a user's terminal.
182
+ *
183
+ * `capturePaths` inside a MOUNTED VOLUME are refused rather than silently
184
+ * omitted: E2B's snapshot images the sandbox's own disk, and a volume is
185
+ * storage attached from outside it. Committing anyway would produce a template
186
+ * that boots perfectly into a workspace missing exactly the files it was asked
187
+ * to preserve.
188
+ *
189
+ * @param options - The sandbox, the caller's template id, optional capture paths and label.
190
+ * @returns The template as it now exists.
191
+ */
192
+ commitTemplate(options: CommitTemplateOptions): Promise<SandboxTemplate>;
193
+ /**
194
+ * Look up one template by the caller's identifier.
195
+ *
196
+ * `null` means E2B has no snapshot by that name. A failed lookup throws: a
197
+ * caller reads `null` as "there is no restore point" and scaffolds a fresh
198
+ * project over the user's, so a transient API error must never wear that shape.
199
+ *
200
+ * @param templateId - The caller's identifier.
201
+ * @returns The template, or `null` when it does not exist.
202
+ */
203
+ getTemplate(templateId: string): Promise<SandboxTemplate | null>;
204
+ /**
205
+ * Enumerate templates so a caller can enforce its retention policy.
206
+ *
207
+ * Throws rather than answering `[]` when it cannot enumerate — an eviction
208
+ * loop that reads "nothing exists" from a failed listing stops evicting, and a
209
+ * reconciler that reads it stops reconciling.
210
+ *
211
+ * @param options - Narrowing by id prefix.
212
+ * @returns Every matching template.
213
+ */
214
+ listTemplates(options?: ListTemplatesOptions): Promise<SandboxTemplate[]>;
215
+ /**
216
+ * Delete a template. Deleting one that does not exist is a success.
217
+ *
218
+ * Refuses when a sandbox still boots from it, by CHECKING the sandbox listing
219
+ * rather than trusting the caller. E2B enforces the same rule server-side (a
220
+ * `400` naming the template), which is left to surface as a throw — two
221
+ * independent refusals for an operation whose failure mode is destroying a
222
+ * running project.
223
+ *
224
+ * @param templateId - The caller's identifier.
225
+ */
226
+ removeTemplate(templateId: string): Promise<void>;
68
227
  /**
69
228
  * PROVE egress is deny-by-default by OBSERVING it on a throwaway sandbox.
70
229
  *
@@ -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,EACV,qBAAqB,EAErB,aAAa,EAKb,oBAAoB,EACpB,kBAAkB,EAClB,OAAO,EACP,aAAa,EACb,iBAAiB,EACjB,eAAe,EACf,eAAe,EAGf,UAAU,EACX,MAAM,4BAA4B,CAAA;AAEnC,OAAO,KAAK,EAGV,SAAS,EACT,oBAAoB,EAMrB,MAAM,YAAY,CAAA;AAkvBnB;;;;;;;;;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;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACG,MAAM,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC;IA+BrD;;;;;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;;;;;;;;;OASG;YACW,aAAa;IAM3B;;;;;OAKG;YACW,UAAU;IASxB;;;;;;;;;;;;;;OAcG;IACG,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAS/C;;;;OAIG;IACG,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAU/C;;;;;;;;;;;OAWG;IACG,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAIlD;;;;;;;;;;OAUG;IACG,WAAW,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IAmBtE;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACG,cAAc,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,eAAe,CAAC;IA6B9E;;;;;;;;;OASG;IACG,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IAoBtE;;;;;;;;;OASG;IACG,aAAa,CAAC,OAAO,CAAC,EAAE,oBAAoB,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IAmB/E;;;;;;;;;;OAUG;IACG,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAevD;;;;;;;;;;;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"}