@gjermundgaraba/clankerbox-rivet-sandbox-agent 0.15.6 → 0.18.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.d.mts CHANGED
@@ -2,9 +2,8 @@ import { Client } from "@gjermundgaraba/clankerbox-sdk";
2
2
  import { SandboxProvider } from "sandbox-agent";
3
3
  //#region src/options.d.ts
4
4
  /**
5
- * What a new sandbox is made from: a profile file, whose setup installs the Sandbox Agent; a
6
- * checkpoint of a machine that has it; or a machine that runs it, to fork. A fork or a `ram`
7
- * restore carries its source's sessions and processes, so take either from an idle machine.
5
+ * A profile whose setup installs the Sandbox Agent, a checkpoint of a machine that has it, or a
6
+ * machine to fork. A fork or `ram` restore carries its source's sessions: take it from an idle one.
8
7
  */
9
8
  type Source = {
10
9
  readonly profile: string;
@@ -14,34 +13,27 @@ type Source = {
14
13
  readonly machine: string;
15
14
  };
16
15
  interface Options {
17
- /** The hosts to make sandboxes on, in placement order, as in the client config. */
16
+ /** In placement order, as in the client config. */
18
17
  readonly hosts: ReadonlyArray<Client.HostEntry>;
19
18
  readonly from: Source;
20
- /**
21
- * Whether a profile's setup runs even when the sandbox's host has a prebuild of it. By default
22
- * a create from a profile with a setup restores that prebuild when there is one, as
23
- * `clankerbox create` does; `clankerbox prebuild --profile <the same file>` makes it.
24
- */
19
+ /** Runs a profile's setup even when the host has its prebuild, from `clankerbox prebuild`. */
25
20
  readonly fresh?: boolean | undefined;
26
- /** What every sandbox's machine name starts with, `sandbox` by default. */
21
+ /** `sandbox` by default. */
27
22
  readonly namePrefix?: string | undefined;
28
- /** The guest user the tunnel logs in as; by default the machine's own login user. */
23
+ /** The machine's own login user by default. */
29
24
  readonly sshUser?: string | undefined;
30
25
  /**
31
- * More arguments for the tunnel's ssh, such as `["-i", "/path/to/key"]`. Neither these nor the
32
- * user's ssh config for the machine may fork the forward (`-f`, `-N`, `ForkAfterAuthentication`,
33
- * `SessionType none`) or hand it to a connection master (`-M`, `-S`): the tunnel ends with its
34
- * ssh process, and one of those would outlive it.
26
+ * Such as `["-i", "/path/to/key"]`. Neither these nor ssh config may fork the forward (`-f`,
27
+ * `-N`, `ForkAfterAuthentication`, `SessionType none`) or give it to a connection master (`-M`,
28
+ * `-S`): the tunnel ends with its ssh, and those would outlive it.
35
29
  */
36
30
  readonly sshArguments?: ReadonlyArray<string> | undefined;
37
- /** The directory sessions start in, `/home/sandbox` by default, as the recipes make it. */
31
+ /** `/home/sandbox` by default, as the recipes make it. */
38
32
  readonly cwd?: string | undefined;
39
33
  }
40
34
  /**
41
- * The provider, with `dispose`, which ends its tunnels and leaves the machines as they are. The
42
- * SDK never calls it: a program that keeps running after it is done with its sandboxes does.
43
- * The SDK's optional members it has are declared present, so a caller can call them; each takes
44
- * the machine's ID, `sdk.sandboxId` without its `clankerbox/`.
35
+ * `dispose` ends the tunnels and leaves the machines; the SDK never calls it. Each method takes the
36
+ * machine's ID, `sdk.sandboxId` without its `clankerbox/`.
45
37
  */
46
38
  interface ClankerboxProvider extends SandboxProvider {
47
39
  reconnect(sandboxId: string): Promise<void>;
@@ -54,9 +46,8 @@ interface ClankerboxProvider extends SandboxProvider {
54
46
  //#endregion
55
47
  //#region src/index.d.ts
56
48
  /**
57
- * A Sandbox Agent provider whose sandboxes are clankerbox machines on `options.hosts`. Each
58
- * sandbox's ID is `clankerbox/<machine ID>`, so `SandboxAgent.start({ sandbox, sandboxId })`
59
- * reconnects to a machine by its ID. Its tunnels end with the process; `dispose` ends them sooner.
49
+ * Sandboxes on clankerbox machines, with IDs `clankerbox/<machine ID>`, so `sandboxId` reconnects
50
+ * to a machine. Their tunnels end with the process, or at `dispose`.
60
51
  */
61
52
  export declare const clankerbox: (options: Options) => ClankerboxProvider;
62
53
  //#endregion
package/dist/index.mjs CHANGED
@@ -1,50 +1,34 @@
1
1
  import * as NodeServices from "@effect/platform-node/NodeServices";
2
2
  import * as NodeClient from "@gjermundgaraba/clankerbox-sdk/node";
3
3
  import { Data, Duration, Effect, Exit, FileSystem, Layer, ManagedRuntime, Option, Predicate, Schedule, Schema, Scope, Semaphore } from "effect";
4
- import { randomBytes } from "node:crypto";
5
- import { Client, Internal, Prebuild, Precondition, loadProfile, pinnedSsh, readSetup, sshTarget } from "@gjermundgaraba/clankerbox-sdk";
4
+ import { ActionName, Client, Conflict, Internal, Prebuild, loadProfile, pinnedSsh, specRequest, sshTarget } from "@gjermundgaraba/clankerbox-sdk";
5
+ import { Hex } from "effect/encoding";
6
6
  import { SandboxDestroyedError } from "sandbox-agent";
7
7
  import { spawn } from "node:child_process";
8
8
  import { closeSync, openSync } from "node:fs";
9
9
  import { readFile } from "node:fs/promises";
10
10
  import { Socket, connect, createServer } from "node:net";
11
11
  import { join } from "node:path";
12
+ //#region src/agent.ts
13
+ /** The Sandbox Agent's default port, which the recipes keep, on the guest's loopback. */
14
+ const agentPort = 2468;
15
+ const healthPath = "/v1/health";
16
+ //#endregion
12
17
  //#region src/tunnel.ts
13
- /**
14
- * A sandbox's tunnel: the system `ssh` forwarding a loopback port of this machine to the Sandbox
15
- * Agent's port on the guest's loopback, over the machine's one published port, guest port 22.
16
- *
17
- * The tunnel runs `cat` in the guest and holds the pipe to its stdin, so it ends when this
18
- * process does, however that ends: the pipe closes, `cat` reads its end, and ssh exits. Neither
19
- * the child nor the pipe keeps the event loop alive, so a program that leaves a sandbox running
20
- * still exits. That takes `node:child_process` itself: Effect's process handle has no way to
21
- * unref the pipe.
22
- */
23
18
  const loopback = "127.0.0.1";
24
- /**
25
- * A tunnel that couldn't open for a reason of this machine's: its port taken, ssh not runnable,
26
- * its files not written. No start of the sandbox's machine changes it.
27
- */
19
+ /** A failure on this machine's side, such as a taken port, which no start of the guest changes. */
28
20
  var LocalTunnelError = class extends Data.TaggedError("LocalTunnelError") {};
29
- /**
30
- * How long ssh may take to log in and listen: ssh's own default ConnectTimeout is the system's
31
- * TCP timeout, minutes, and a tunnel the SDK waits on should fail sooner than that.
32
- */
33
21
  const openWait = Duration.seconds(30);
34
- /** How often the port is tried while ssh logs in: a login takes about a second. */
35
22
  const openPause = Duration.millis(100);
36
- /** How long ssh may take to end once told to, before it is killed: it ends in milliseconds. */
37
23
  const closeWait = Duration.seconds(5);
38
- /** A loopback port nothing listens on now. */
39
24
  const freePort = Effect.callback((resume) => {
40
25
  const server = createServer();
41
- server.once("error", (error) => resume(Effect.fail(new Internal({ message: `couldn't find a free port: ${error.message}` }))));
26
+ server.once("error", (error) => resume(Effect.fail(new LocalTunnelError({ message: `couldn't find a free port: ${error.message}` }))));
42
27
  server.listen(0, loopback, () => {
43
28
  const address = server.address();
44
- server.close(() => resume(address === null || Predicate.isString(address) ? Effect.fail(new Internal({ message: "couldn't find a free port" })) : Effect.succeed(address.port)));
29
+ server.close(() => resume(address === null || Predicate.isString(address) ? Effect.fail(new LocalTunnelError({ message: "couldn't find a free port" })) : Effect.succeed(address.port)));
45
30
  });
46
31
  });
47
- /** Whether something accepts a connection on the loopback port. */
48
32
  const listening = (port) => Effect.callback((resume) => {
49
33
  const socket = connect({
50
34
  host: loopback,
@@ -57,14 +41,9 @@ const listening = (port) => Effect.callback((resume) => {
57
41
  socket.once("error", () => resume(Effect.succeed(false)));
58
42
  return Effect.sync(() => socket.destroy());
59
43
  });
60
- /** The port the Sandbox Agent listens on in the guest: its default, which the recipes keep. */
61
- const agentPort = 2468;
62
44
  /**
63
- * The arguments that forward `port` to the guest's Sandbox Agent, after the pinned ones, with the
64
- * caller's `extra` among them. The forward is this ssh's own, never a connection master's, which
65
- * would outlive it; and keepalives end it when the guest stops answering without closing. These
66
- * come before `extra`: OpenSSH keeps an option's first value, so neither `extra` nor the user's
67
- * ssh config can change them.
45
+ * No connection master, which would outlive the forward, and keepalives that end it when the
46
+ * guest stops answering. These precede `extra`: OpenSSH keeps an option's first value.
68
47
  */
69
48
  const forwarding = (port, extra) => [
70
49
  "-T",
@@ -85,12 +64,7 @@ const forwarding = (port, extra) => [
85
64
  `${loopback}:${port}:${loopback}:${agentPort}`,
86
65
  "cat"
87
66
  ];
88
- /** Whether the child has ended, or failed to start: Node sets an exit code for either. */
89
67
  const over = (child) => child.exitCode !== null || child.signalCode !== null;
90
- /**
91
- * Waits for the child to end. A program that couldn't be run says so in an `error` event, with
92
- * no `exit`.
93
- */
94
68
  const exited = (child) => Effect.callback((resume) => {
95
69
  if (over(child)) return resume(Effect.void);
96
70
  const done = () => resume(Effect.void);
@@ -102,10 +76,10 @@ const exited = (child) => Effect.callback((resume) => {
102
76
  });
103
77
  });
104
78
  /**
105
- * Starts ssh and waits until it listens on the port. Closing the scope ends it, and waits for
106
- * its exit, so the port is free for the next tunnel. An ssh that exits first fails the open with
107
- * the last of what it printed, and one that couldn't be run with why. A port another program
108
- * holds is refused first: the wait for the port to listen can't tell whose listener it finds.
79
+ * A port another program holds is refused first: the wait for the port to listen can't tell whose
80
+ * listener it finds. Closing the scope waits for ssh's exit, so the port is free again. The ssh
81
+ * runs `cat` on a pipe this process holds, so it ends however this process ends, and neither keeps
82
+ * the event loop alive.
109
83
  */
110
84
  const open = (start) => Effect.gen(function* () {
111
85
  if (yield* listening(start.port)) return yield* new LocalTunnelError({ message: `the tunnel to ${start.machine} can't use port ${start.port}: another program listens on it` });
@@ -150,30 +124,21 @@ const open = (start) => Effect.gen(function* () {
150
124
  });
151
125
  //#endregion
152
126
  //#region src/sandboxes.ts
153
- /**
154
- * Sandboxes for Rivet's Sandbox Agent, each a clankerbox machine: the lifecycle a
155
- * `SandboxProvider` asks for, over the clankerbox client, and a tunnel to the Sandbox Agent
156
- * each machine runs.
157
- *
158
- * The machine runs the Sandbox Agent itself: its setup installs it, and its
159
- * `/etc/clankerbox/start` launches it on the guest's loopback at every activation, as the
160
- * recipes beside this package do. A machine exposes only guest port 22, so the provider reaches
161
- * the agent through an ssh port forward, pinned to the machine's host key, and gives the SDK a
162
- * loopback URL of this machine.
163
- */
164
- /** The provider's name, which prefixes every sandbox ID the SDK hands out. */
127
+ const makesMachine = {
128
+ createMachine: true,
129
+ forkMachine: true,
130
+ restoreMachine: true,
131
+ startMachine: false,
132
+ stopMachine: false,
133
+ deleteMachine: false,
134
+ captureCheckpoint: false,
135
+ deleteCheckpoint: false
136
+ };
137
+ const isActionName = Schema.is(ActionName);
165
138
  const name = "clankerbox";
166
- /**
167
- * How long a health check through the tunnel may take: the agent answers on loopback in
168
- * milliseconds, so one that takes longer is treated as not answering.
169
- */
170
139
  const healthWait = Duration.seconds(3);
171
140
  const Health = Schema.Struct({ status: Schema.Literal("ok") });
172
141
  const isHealth = Schema.is(Health);
173
- /**
174
- * The sandboxes of one provider. `ssh` is the program its tunnels run. A sandbox's ID is its
175
- * machine's ID.
176
- */
177
142
  const make = (options, ssh = "ssh") => {
178
143
  const sandboxes = /* @__PURE__ */ new Map();
179
144
  const sandbox = (id) => {
@@ -185,16 +150,10 @@ const make = (options, ssh = "ssh") => {
185
150
  sandboxes.set(id, known);
186
151
  return known;
187
152
  };
188
- /**
189
- * Runs `use` on the sandbox's record, holding its lock: each of the sandbox's operations runs
190
- * whole under it, so two never overlap, as the host would refuse the second's start or stop
191
- * while the first's runs.
192
- */
193
153
  const locked = (id, use) => Effect.suspend(() => {
194
154
  const known = sandbox(id);
195
155
  return Semaphore.withPermit(known.lock, Effect.suspend(() => use(known)));
196
156
  });
197
- /** The sandbox's port: its own, or a free one no other sandbox of this provider keeps. */
198
157
  const portOf = (known) => Effect.gen(function* () {
199
158
  if (known.port !== void 0) return known.port;
200
159
  const taken = (port) => [...sandboxes.values()].some((other) => other.port === port);
@@ -203,22 +162,16 @@ const make = (options, ssh = "ssh") => {
203
162
  known.port = port;
204
163
  return port;
205
164
  });
206
- /** Ends the sandbox's tunnel, if it has one. */
207
165
  const closed = (known) => Effect.suspend(() => {
208
166
  const was = known.held;
209
167
  known.held = void 0;
210
168
  return was === void 0 ? Effect.void : Scope.close(was.scope, Exit.void);
211
169
  });
212
- /**
213
- * The sandbox's port, with its tunnel open: the one it has while that runs, and otherwise a
214
- * new one. The machine is read for each tunnel, as its endpoint changes: on boat at every start.
215
- */
216
170
  const opened = (id, known) => Effect.gen(function* () {
217
171
  if (known.held?.tunnel.running() === true) return yield* portOf(known);
218
172
  yield* closed(known);
219
173
  const client = yield* Client.Client;
220
- const target = sshTarget(yield* client.machine(id));
221
- if ("lacks" in target) return yield* new Precondition({ message: `${id} has no ${target.lacks}, which its tunnel needs: see clankerbox get ${id}` });
174
+ const target = yield* sshTarget(yield* client.getMachine({ id }));
222
175
  const port = yield* portOf(known);
223
176
  const scope = yield* Scope.make();
224
177
  known.held = {
@@ -237,128 +190,66 @@ const make = (options, ssh = "ssh") => {
237
190
  };
238
191
  return port;
239
192
  });
240
- /** Whether the Sandbox Agent answers its health check through the tunnel. */
241
- const healthy = (port) => Effect.tryPromise((signal) => fetch(`http://127.0.0.1:${port}/v1/health`, { signal }).then((reply) => reply.json())).pipe(Effect.map(isHealth), Effect.timeoutOrElse({
193
+ const healthy = (port) => Effect.tryPromise((signal) => fetch(`http://127.0.0.1:${port}${healthPath}`, { signal }).then((reply) => reply.json())).pipe(Effect.map(isHealth), Effect.timeoutOrElse({
242
194
  duration: healthWait,
243
195
  orElse: () => Effect.succeed(false)
244
196
  }), Effect.catch(() => Effect.succeed(false)));
245
- /**
246
- * Starts the machine, which boots a stopped one and runs a running one's preparation again,
247
- * and so its `/etc/clankerbox/start`. Its tunnel ends: the endpoint may have changed. A machine
248
- * that no longer exists is the SDK's `SandboxDestroyedError`, for `reconnect` and
249
- * `ensureServer` alike.
250
- */
251
197
  const started = (id, known) => Effect.gen(function* () {
252
- yield* (yield* Client.Client).start(id);
198
+ yield* (yield* Client.Client).startMachine({ id });
253
199
  yield* closed(known);
254
200
  }).pipe(Effect.catchTag("NotFound", (cause) => Effect.fail(new SandboxDestroyedError(id, name, { cause }))));
255
- /**
256
- * Makes the machine `named`, from the options' source. A profile with a setup is restored from
257
- * its host's prebuild of it when there is one, as `clankerbox create` does, unless `fresh`.
258
- */
259
201
  const made = (named) => Effect.gen(function* () {
260
202
  const client = yield* Client.Client;
261
203
  const { from } = options;
262
- if ("checkpoint" in from) return yield* client.restore(from.checkpoint, named);
263
- if ("machine" in from) return yield* client.fork(from.machine, named);
264
- const { label, host, setup, ...sizes } = yield* loadProfile(from.profile);
265
- let spec = {
266
- ...sizes,
267
- profile: label
268
- };
269
- if (setup !== void 0) {
270
- const { path, ...timeout } = setup;
271
- spec = {
272
- ...spec,
273
- setup: {
274
- script: yield* readSetup(path),
275
- ...timeout
276
- }
277
- };
278
- }
279
- if (options.fresh === true) return yield* client.create(named, spec, { host });
280
- const { machine } = yield* Prebuild.create(client, named, spec, { host });
204
+ if ("checkpoint" in from) return yield* client.restoreMachine({
205
+ checkpoint: from.checkpoint,
206
+ name: named
207
+ });
208
+ if ("machine" in from) return yield* client.forkMachine({
209
+ machine: from.machine,
210
+ name: named
211
+ });
212
+ const { spec, host } = yield* specRequest("create", yield* loadProfile(from.profile), {});
213
+ const { machine } = yield* Prebuild.create(client, named, spec, {
214
+ host,
215
+ fresh: options.fresh
216
+ });
281
217
  return machine;
282
218
  });
283
- /**
284
- * Tries to delete what a failed create may have left: a machine whose setup failed, or whose
285
- * fork or restore failed natively, stays on its host, and only the provider knows its name. The
286
- * host is the placement's to pick, so the machine is found by its name, which is random. A host
287
- * that doesn't answer, or a delete that fails, leaves it to `clankerbox machines`.
288
- */
289
- const discard = (named) => Effect.gen(function* () {
219
+ const discard = (error) => Effect.gen(function* () {
290
220
  const client = yield* Client.Client;
291
- const { answers } = yield* client.machines;
292
- yield* Effect.forEach(answers.filter((machine) => machine.id.endsWith(`_${named}`)), (machine) => Effect.ignore(client.delete(machine.id)), { discard: true });
221
+ if (error.made !== void 0 && isActionName(error.action) && makesMachine[error.action]) yield* Effect.ignore(client.deleteMachine({ id: error.made }));
222
+ return yield* error;
293
223
  });
294
224
  return {
295
- /**
296
- * Makes a sandbox's machine, and returns its ID once its create, fork or restore is done.
297
- * Only the failures that can leave a machine behind discard it: a refusal, such as a name
298
- * already taken, leaves nothing of this create's.
299
- */
300
225
  create: Effect.suspend(() => {
301
- const named = `${options.namePrefix ?? "sandbox"}-${randomBytes(4).toString("hex")}`;
302
- const discarding = (error) => Effect.andThen(discard(named), Effect.fail(error));
303
- return made(named).pipe(Effect.map((machine) => machine.id), Effect.catchTags({
304
- Precondition: discarding,
305
- Internal: discarding,
306
- Unavailable: discarding
307
- }));
226
+ const named = `${options.namePrefix ?? "sandbox"}-${Hex.encode(globalThis.crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(4)))}`;
227
+ return made(named).pipe(Effect.map((machine) => machine.id), Effect.catch((error) => error instanceof Conflict ? Effect.fail(error) : discard(error)));
308
228
  }),
309
- /**
310
- * Deletes the machine, holding the sandbox's lock throughout. One that is already gone is
311
- * destroyed. Its port goes only once it is: a delete that fails leaves the sandbox as it was,
312
- * its tunnel aside.
313
- */
314
229
  destroy: (id) => locked(id, (known) => Effect.gen(function* () {
315
230
  const client = yield* Client.Client;
316
231
  yield* closed(known);
317
- yield* Effect.catchTag(client.delete(id), "NotFound", () => Effect.void);
232
+ yield* client.deleteMachine({ id });
318
233
  known.port = void 0;
319
234
  })),
320
- /** Stops the machine: its disk stays, and `reconnect` starts it again. */
321
235
  pause: (id) => locked(id, (known) => Effect.gen(function* () {
322
236
  const client = yield* Client.Client;
323
237
  yield* closed(known);
324
- yield* client.stop(id);
238
+ yield* client.stopMachine({ id });
325
239
  })),
326
- /**
327
- * Starts the machine, which boots a stopped one and leaves a running one running. A machine
328
- * that no longer exists is the SDK's `SandboxDestroyedError`. The SDK's `ensureServer`,
329
- * which follows, opens the tunnel.
330
- */
331
240
  reconnect: (id) => locked(id, (known) => started(id, known)),
332
- /**
333
- * Brings back what a failed health check may have lost. A tunnel that ended is opened again;
334
- * if the agent still doesn't answer, or the tunnel can't open for a reason on the machine's
335
- * side, the machine is started, whose start hook relaunches an agent that doesn't answer, and
336
- * its tunnel opened anew. A tunnel that fails for a reason of this machine's fails at once,
337
- * since no start changes it. The SDK calls this only while it starts: a tunnel that ends
338
- * later stays down until a caller calls it again.
339
- */
340
241
  ensureServer: (id) => locked(id, (known) => Effect.gen(function* () {
341
242
  const port = yield* opened(id, known).pipe(Effect.asSome, Effect.catchIf((error) => !(error instanceof LocalTunnelError), () => Effect.succeedNone));
342
243
  if (Option.isSome(port) && (yield* healthy(port.value))) return;
343
244
  yield* started(id, known);
344
245
  yield* opened(id, known);
345
246
  })),
346
- /** The loopback URL that leads to the sandbox's agent. */
347
247
  url: (id) => locked(id, (known) => Effect.map(opened(id, known), (port) => `http://127.0.0.1:${port}`)),
348
- /** Ends every tunnel. The machines stay as they are. */
349
248
  closeAll: Effect.suspend(() => Effect.forEach([...sandboxes.keys()], (id) => locked(id, closed), { discard: true }))
350
249
  };
351
250
  };
352
251
  //#endregion
353
252
  //#region src/provider.ts
354
- /**
355
- * The `SandboxProvider` the Sandbox Agent SDK takes: the sandboxes' effects, run as promises
356
- * over the layer that gives them the clankerbox client and the platform.
357
- */
358
- /**
359
- * The provider over `layer`, whose tunnels run `ssh`. Nothing is built until the first call.
360
- * Tests give it an in-memory host and a stand-in for ssh.
361
- */
362
253
  const provider = (options, layer, ssh) => {
363
254
  const runtime = ManagedRuntime.make(layer);
364
255
  const sandboxes = make(options, ssh);
@@ -380,13 +271,8 @@ const provider = (options, layer, ssh) => {
380
271
  //#endregion
381
272
  //#region src/index.ts
382
273
  /**
383
- * clankerbox machines as sandboxes for Rivet's Sandbox Agent:
384
- * `SandboxAgent.start({ sandbox: clankerbox({ hosts, from }) })`.
385
- */
386
- /**
387
- * A Sandbox Agent provider whose sandboxes are clankerbox machines on `options.hosts`. Each
388
- * sandbox's ID is `clankerbox/<machine ID>`, so `SandboxAgent.start({ sandbox, sandboxId })`
389
- * reconnects to a machine by its ID. Its tunnels end with the process; `dispose` ends them sooner.
274
+ * Sandboxes on clankerbox machines, with IDs `clankerbox/<machine ID>`, so `sandboxId` reconnects
275
+ * to a machine. Their tunnels end with the process, or at `dispose`.
390
276
  */
391
277
  const clankerbox = (options) => provider(options, Layer.mergeAll(NodeClient.layer(options.hosts), NodeServices.layer));
392
278
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjermundgaraba/clankerbox-rivet-sandbox-agent",
3
- "version": "0.15.6",
3
+ "version": "0.18.0",
4
4
  "description": "clankerbox machines as sandboxes for Rivet's Sandbox Agent",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -25,7 +25,7 @@
25
25
  "access": "public"
26
26
  },
27
27
  "dependencies": {
28
- "@gjermundgaraba/clankerbox-sdk": "0.15.6"
28
+ "@gjermundgaraba/clankerbox-sdk": "0.18.0"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@effect/platform-node": "4.0.4",
@@ -33,8 +33,8 @@
33
33
  "effect": "4.0.4",
34
34
  "sandbox-agent": "0.5.2",
35
35
  "typescript": "7.0.2",
36
- "vite": "npm:@voidzero-dev/vite-plus-core@1.1.0",
37
- "vite-plus": "1.1.0"
36
+ "vite": "npm:@voidzero-dev/vite-plus-core@1.2.0",
37
+ "vite-plus": "1.2.0"
38
38
  },
39
39
  "peerDependencies": {
40
40
  "@effect/platform-node": "~4.0.4",