@gjermundgaraba/clankerbox-rivet-sandbox-agent 0.15.5 → 0.16.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
@@ -2,49 +2,41 @@ 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
4
  import { randomBytes } from "node:crypto";
5
- import { Client, Internal, Prebuild, Precondition, loadProfile, pinnedSsh, readSetup, sshTarget } from "@gjermundgaraba/clankerbox-sdk";
5
+ import { Client, Internal, Prebuild, Precondition, actionsByName, loadProfile, pinnedSsh, specRequest, sshTarget } from "@gjermundgaraba/clankerbox-sdk";
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
18
  /**
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.
19
+ * The tunnel's ssh runs `cat` on a pipe this process holds, so it ends however this process ends.
20
+ * Neither keeps the event loop alive; that takes `node:child_process`, as Effect's process handle
21
+ * can't unref the pipe.
22
22
  */
23
23
  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
- */
24
+ /** A failure on this machine's side, such as a taken port, which no start of the guest changes. */
28
25
  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
- */
26
+ /** ssh's default ConnectTimeout is the system's TCP timeout, minutes; the SDK waits on this. */
33
27
  const openWait = Duration.seconds(30);
34
- /** How often the port is tried while ssh logs in: a login takes about a second. */
28
+ /** A login takes about a second. */
35
29
  const openPause = Duration.millis(100);
36
- /** How long ssh may take to end once told to, before it is killed: it ends in milliseconds. */
30
+ /** ssh ends in milliseconds once told to. */
37
31
  const closeWait = Duration.seconds(5);
38
- /** A loopback port nothing listens on now. */
39
32
  const freePort = Effect.callback((resume) => {
40
33
  const server = createServer();
41
- server.once("error", (error) => resume(Effect.fail(new Internal({ message: `couldn't find a free port: ${error.message}` }))));
34
+ server.once("error", (error) => resume(Effect.fail(new LocalTunnelError({ message: `couldn't find a free port: ${error.message}` }))));
42
35
  server.listen(0, loopback, () => {
43
36
  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)));
37
+ server.close(() => resume(address === null || Predicate.isString(address) ? Effect.fail(new LocalTunnelError({ message: "couldn't find a free port" })) : Effect.succeed(address.port)));
45
38
  });
46
39
  });
47
- /** Whether something accepts a connection on the loopback port. */
48
40
  const listening = (port) => Effect.callback((resume) => {
49
41
  const socket = connect({
50
42
  host: loopback,
@@ -57,14 +49,9 @@ const listening = (port) => Effect.callback((resume) => {
57
49
  socket.once("error", () => resume(Effect.succeed(false)));
58
50
  return Effect.sync(() => socket.destroy());
59
51
  });
60
- /** The port the Sandbox Agent listens on in the guest: its default, which the recipes keep. */
61
- const agentPort = 2468;
62
52
  /**
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.
53
+ * No connection master, which would outlive the forward, and keepalives that end it when the
54
+ * guest stops answering. These precede `extra`: OpenSSH keeps an option's first value.
68
55
  */
69
56
  const forwarding = (port, extra) => [
70
57
  "-T",
@@ -85,12 +72,9 @@ const forwarding = (port, extra) => [
85
72
  `${loopback}:${port}:${loopback}:${agentPort}`,
86
73
  "cat"
87
74
  ];
88
- /** Whether the child has ended, or failed to start: Node sets an exit code for either. */
75
+ /** Node sets an exit code for a child that failed to start too. */
89
76
  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
- */
77
+ /** A program that couldn't be run emits `error`, with no `exit`. */
94
78
  const exited = (child) => Effect.callback((resume) => {
95
79
  if (over(child)) return resume(Effect.void);
96
80
  const done = () => resume(Effect.void);
@@ -102,10 +86,8 @@ const exited = (child) => Effect.callback((resume) => {
102
86
  });
103
87
  });
104
88
  /**
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.
89
+ * A port another program holds is refused first: the wait for the port to listen can't tell whose
90
+ * listener it finds. Closing the scope waits for ssh's exit, so the port is free again.
109
91
  */
110
92
  const open = (start) => Effect.gen(function* () {
111
93
  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 +132,17 @@ const open = (start) => Effect.gen(function* () {
150
132
  });
151
133
  //#endregion
152
134
  //#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. */
135
+ /** The calls that make a machine, whose failure may leave it on its host. */
136
+ const makers = [
137
+ actionsByName.createMachine,
138
+ actionsByName.forkMachine,
139
+ actionsByName.restoreMachine
140
+ ];
165
141
  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
- */
142
+ /** The agent answers on loopback in milliseconds, so a slower one counts as not answering. */
170
143
  const healthWait = Duration.seconds(3);
171
144
  const Health = Schema.Struct({ status: Schema.Literal("ok") });
172
145
  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
146
  const make = (options, ssh = "ssh") => {
178
147
  const sandboxes = /* @__PURE__ */ new Map();
179
148
  const sandbox = (id) => {
@@ -185,16 +154,11 @@ const make = (options, ssh = "ssh") => {
185
154
  sandboxes.set(id, known);
186
155
  return known;
187
156
  };
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
- */
157
+ /** A sandbox's operations never overlap: the host would refuse a second start or stop as busy. */
193
158
  const locked = (id, use) => Effect.suspend(() => {
194
159
  const known = sandbox(id);
195
160
  return Semaphore.withPermit(known.lock, Effect.suspend(() => use(known)));
196
161
  });
197
- /** The sandbox's port: its own, or a free one no other sandbox of this provider keeps. */
198
162
  const portOf = (known) => Effect.gen(function* () {
199
163
  if (known.port !== void 0) return known.port;
200
164
  const taken = (port) => [...sandboxes.values()].some((other) => other.port === port);
@@ -203,16 +167,12 @@ const make = (options, ssh = "ssh") => {
203
167
  known.port = port;
204
168
  return port;
205
169
  });
206
- /** Ends the sandbox's tunnel, if it has one. */
207
170
  const closed = (known) => Effect.suspend(() => {
208
171
  const was = known.held;
209
172
  known.held = void 0;
210
173
  return was === void 0 ? Effect.void : Scope.close(was.scope, Exit.void);
211
174
  });
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
- */
175
+ /** The machine is read for each tunnel, as its endpoint changes: on boat at every start. */
216
176
  const opened = (id, known) => Effect.gen(function* () {
217
177
  if (known.held?.tunnel.running() === true) return yield* portOf(known);
218
178
  yield* closed(known);
@@ -237,105 +197,61 @@ const make = (options, ssh = "ssh") => {
237
197
  };
238
198
  return port;
239
199
  });
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({
200
+ 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
201
  duration: healthWait,
243
202
  orElse: () => Effect.succeed(false)
244
203
  }), 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
- */
204
+ /** A start reruns a running machine's start hook and may move its endpoint: the tunnel ends. */
251
205
  const started = (id, known) => Effect.gen(function* () {
252
206
  yield* (yield* Client.Client).start(id);
253
207
  yield* closed(known);
254
208
  }).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
209
  const made = (named) => Effect.gen(function* () {
260
210
  const client = yield* Client.Client;
261
211
  const { from } = options;
262
212
  if ("checkpoint" in from) return yield* client.restore(from.checkpoint, named);
263
213
  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 });
214
+ const { spec, host } = yield* specRequest("create", yield* loadProfile(from.profile), {});
215
+ const { machine } = yield* Prebuild.create(client, named, spec, {
216
+ host,
217
+ fresh: options.fresh
218
+ });
281
219
  return machine;
282
220
  });
283
221
  /**
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`.
222
+ * Only a failed create, fork or restore leaves its machine on the host, and then its error names
223
+ * it. A placement or prebuild read failure left nothing. A failed delete leaves it to the user.
288
224
  */
289
- const discard = (named) => Effect.gen(function* () {
225
+ const discard = (error) => Effect.gen(function* () {
290
226
  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 });
227
+ if (error.id !== void 0 && makers.some(({ name }) => name === error.action)) yield* Effect.ignore(client.delete(error.id));
228
+ return yield* error;
293
229
  });
294
230
  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
231
  create: Effect.suspend(() => {
301
232
  const named = `${options.namePrefix ?? "sandbox"}-${randomBytes(4).toString("hex")}`;
302
- const discarding = (error) => Effect.andThen(discard(named), Effect.fail(error));
303
233
  return made(named).pipe(Effect.map((machine) => machine.id), Effect.catchTags({
304
- Precondition: discarding,
305
- Internal: discarding,
306
- Unavailable: discarding
234
+ Precondition: discard,
235
+ Internal: discard,
236
+ Unavailable: discard
307
237
  }));
308
238
  }),
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
239
  destroy: (id) => locked(id, (known) => Effect.gen(function* () {
315
240
  const client = yield* Client.Client;
316
241
  yield* closed(known);
317
242
  yield* Effect.catchTag(client.delete(id), "NotFound", () => Effect.void);
318
243
  known.port = void 0;
319
244
  })),
320
- /** Stops the machine: its disk stays, and `reconnect` starts it again. */
321
245
  pause: (id) => locked(id, (known) => Effect.gen(function* () {
322
246
  const client = yield* Client.Client;
323
247
  yield* closed(known);
324
248
  yield* client.stop(id);
325
249
  })),
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
- */
250
+ /** The SDK's `ensureServer`, which follows, opens the tunnel. */
331
251
  reconnect: (id) => locked(id, (known) => started(id, known)),
332
252
  /**
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.
253
+ * A local tunnel failure fails at once, since no start changes it. The SDK calls this only
254
+ * while it starts: a tunnel that ends later stays down until a caller calls it again.
339
255
  */
340
256
  ensureServer: (id) => locked(id, (known) => Effect.gen(function* () {
341
257
  const port = yield* opened(id, known).pipe(Effect.asSome, Effect.catchIf((error) => !(error instanceof LocalTunnelError), () => Effect.succeedNone));
@@ -343,22 +259,12 @@ const make = (options, ssh = "ssh") => {
343
259
  yield* started(id, known);
344
260
  yield* opened(id, known);
345
261
  })),
346
- /** The loopback URL that leads to the sandbox's agent. */
347
262
  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
263
  closeAll: Effect.suspend(() => Effect.forEach([...sandboxes.keys()], (id) => locked(id, closed), { discard: true }))
350
264
  };
351
265
  };
352
266
  //#endregion
353
267
  //#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
268
  const provider = (options, layer, ssh) => {
363
269
  const runtime = ManagedRuntime.make(layer);
364
270
  const sandboxes = make(options, ssh);
@@ -380,13 +286,8 @@ const provider = (options, layer, ssh) => {
380
286
  //#endregion
381
287
  //#region src/index.ts
382
288
  /**
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.
289
+ * Sandboxes on clankerbox machines, with IDs `clankerbox/<machine ID>`, so `sandboxId` reconnects
290
+ * to a machine. Their tunnels end with the process, or at `dispose`.
390
291
  */
391
292
  const clankerbox = (options) => provider(options, Layer.mergeAll(NodeClient.layer(options.hosts), NodeServices.layer));
392
293
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjermundgaraba/clankerbox-rivet-sandbox-agent",
3
- "version": "0.15.5",
3
+ "version": "0.16.0",
4
4
  "description": "clankerbox machines as sandboxes for Rivet's Sandbox Agent",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -25,20 +25,20 @@
25
25
  "access": "public"
26
26
  },
27
27
  "dependencies": {
28
- "@gjermundgaraba/clankerbox-sdk": "0.15.5"
28
+ "@gjermundgaraba/clankerbox-sdk": "0.16.0"
29
29
  },
30
30
  "devDependencies": {
31
- "@effect/platform-node": "4.0.2",
31
+ "@effect/platform-node": "4.0.4",
32
32
  "@types/node": "26.6.4",
33
- "effect": "4.0.2",
33
+ "effect": "4.0.4",
34
34
  "sandbox-agent": "0.5.2",
35
35
  "typescript": "7.0.2",
36
36
  "vite": "npm:@voidzero-dev/vite-plus-core@1.1.0",
37
37
  "vite-plus": "1.1.0"
38
38
  },
39
39
  "peerDependencies": {
40
- "@effect/platform-node": "~4.0.2",
41
- "effect": "~4.0.2",
40
+ "@effect/platform-node": "~4.0.4",
41
+ "effect": "~4.0.4",
42
42
  "sandbox-agent": "~0.5.2"
43
43
  },
44
44
  "scripts": {