@robzilla1738/agentswarm 0.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.
Files changed (64) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +142 -0
  3. package/bin/swarm.js +10 -0
  4. package/dist/agent.js +211 -0
  5. package/dist/cli.js +667 -0
  6. package/dist/config.js +289 -0
  7. package/dist/control.js +96 -0
  8. package/dist/deepseek.js +321 -0
  9. package/dist/executor.js +988 -0
  10. package/dist/hub.js +553 -0
  11. package/dist/journal.js +152 -0
  12. package/dist/prompts.js +232 -0
  13. package/dist/providers.js +151 -0
  14. package/dist/run.js +309 -0
  15. package/dist/sandbox.js +505 -0
  16. package/dist/state.js +230 -0
  17. package/dist/terminal.js +298 -0
  18. package/dist/tools.js +491 -0
  19. package/dist/types.js +26 -0
  20. package/dist/util.js +209 -0
  21. package/dist/webtools.js +205 -0
  22. package/package.json +63 -0
  23. package/ui/out/404/index.html +1 -0
  24. package/ui/out/404.html +1 -0
  25. package/ui/out/_next/static/chunks/255-2aa030c9ba2867e3.js +1 -0
  26. package/ui/out/_next/static/chunks/383-289a866b246b41cc.js +1 -0
  27. package/ui/out/_next/static/chunks/4bd1b696-c023c6e3521b1417.js +1 -0
  28. package/ui/out/_next/static/chunks/619-ba102abea3e3d0e4.js +1 -0
  29. package/ui/out/_next/static/chunks/677-b37981ba0eca75b2.js +1 -0
  30. package/ui/out/_next/static/chunks/app/_not-found/page-2d0982e372f7be41.js +1 -0
  31. package/ui/out/_next/static/chunks/app/layout-37ad32c5fdb26f29.js +1 -0
  32. package/ui/out/_next/static/chunks/app/page-0c9f35bd4aa8e370.js +1 -0
  33. package/ui/out/_next/static/chunks/app/run/page-13dc41a57e34da71.js +1 -0
  34. package/ui/out/_next/static/chunks/app/settings/page-a1763be7f6de888c.js +1 -0
  35. package/ui/out/_next/static/chunks/framework-2c534e0e662575a2.js +1 -0
  36. package/ui/out/_next/static/chunks/main-app-889ed884f8bc78e3.js +1 -0
  37. package/ui/out/_next/static/chunks/main-eb90ae3b35d2fd16.js +1 -0
  38. package/ui/out/_next/static/chunks/pages/_app-7d307437aca18ad4.js +1 -0
  39. package/ui/out/_next/static/chunks/pages/_error-cb2a52f75f2162e2.js +1 -0
  40. package/ui/out/_next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
  41. package/ui/out/_next/static/chunks/webpack-38639c05c96dbeca.js +1 -0
  42. package/ui/out/_next/static/css/82edaa7a5942f894.css +3 -0
  43. package/ui/out/_next/static/eiQeDU9uBHNsBj0CFkp8M/_buildManifest.js +1 -0
  44. package/ui/out/_next/static/eiQeDU9uBHNsBj0CFkp8M/_ssgManifest.js +1 -0
  45. package/ui/out/_next/static/media/0aa834ed78bf6d07-s.woff2 +0 -0
  46. package/ui/out/_next/static/media/438aa629764e75f3-s.woff2 +0 -0
  47. package/ui/out/_next/static/media/4c9affa5bc8f420e-s.p.woff2 +0 -0
  48. package/ui/out/_next/static/media/51251f8b9793cdb3-s.woff2 +0 -0
  49. package/ui/out/_next/static/media/67957d42bae0796d-s.woff2 +0 -0
  50. package/ui/out/_next/static/media/875ae681bfde4580-s.woff2 +0 -0
  51. package/ui/out/_next/static/media/886030b0b59bc5a7-s.woff2 +0 -0
  52. package/ui/out/_next/static/media/939c4f875ee75fbb-s.woff2 +0 -0
  53. package/ui/out/_next/static/media/bb3ef058b751a6ad-s.p.woff2 +0 -0
  54. package/ui/out/_next/static/media/cc978ac5ee68c2b6-s.woff2 +0 -0
  55. package/ui/out/_next/static/media/e857b654a2caa584-s.woff2 +0 -0
  56. package/ui/out/_next/static/media/f911b923c6adde36-s.woff2 +0 -0
  57. package/ui/out/icon.png +0 -0
  58. package/ui/out/index.html +1 -0
  59. package/ui/out/index.txt +22 -0
  60. package/ui/out/run/index.html +1 -0
  61. package/ui/out/run/index.txt +22 -0
  62. package/ui/out/settings/index.html +1 -0
  63. package/ui/out/settings/index.txt +22 -0
  64. package/ui/out/swarm-mark.png +0 -0
@@ -0,0 +1,505 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.SANDBOX_KINDS = void 0;
37
+ exports.dockerAvailable = dockerAvailable;
38
+ exports.resolveSandboxKind = resolveSandboxKind;
39
+ exports.createSandbox = createSandbox;
40
+ exports.testSandbox = testSandbox;
41
+ const child_process_1 = require("child_process");
42
+ const fs = __importStar(require("fs"));
43
+ const path = __importStar(require("path"));
44
+ const config_1 = require("./config");
45
+ const util_1 = require("./util");
46
+ exports.SANDBOX_KINDS = ["host", "docker", "e2b", "modal", "vercel"];
47
+ // ---------------------------------------------------------------- resolution
48
+ let dockerOk = null;
49
+ /** Is a container daemon actually reachable (not just the CLI installed)? */
50
+ function dockerAvailable(refresh = false) {
51
+ if (dockerOk !== null && !refresh)
52
+ return dockerOk;
53
+ try {
54
+ const r = (0, child_process_1.spawnSync)("docker", ["info", "--format", "{{.ServerVersion}}"], { timeout: 8000, encoding: "utf8" });
55
+ dockerOk = r.status === 0 && Boolean((r.stdout || "").trim());
56
+ }
57
+ catch {
58
+ dockerOk = false;
59
+ }
60
+ return dockerOk;
61
+ }
62
+ /**
63
+ * Resolve the configured runtime. The default config is "host" — the run's
64
+ * isolated workspace on this machine, no sandbox required. "auto" is the
65
+ * opt-in auto-detect: cloud sandboxes when configured (e2b → modal → vercel),
66
+ * then a local container daemon, then the host.
67
+ */
68
+ function resolveSandboxKind(cfg) {
69
+ if (cfg.sandboxRuntime !== "auto")
70
+ return cfg.sandboxRuntime;
71
+ if (cfg.e2bApiKey)
72
+ return "e2b";
73
+ if (cfg.modalTokenId && cfg.modalTokenSecret)
74
+ return "modal";
75
+ if (cfg.vercelToken)
76
+ return "vercel";
77
+ if (dockerAvailable())
78
+ return "docker";
79
+ return "host";
80
+ }
81
+ function createSandbox(kind, input) {
82
+ switch (kind) {
83
+ case "docker": return new DockerRuntime(input);
84
+ case "e2b": return new E2BRuntime(input);
85
+ case "modal": return new ModalRuntime(input);
86
+ case "vercel": return new VercelRuntime(input);
87
+ default: return new HostRuntime(input);
88
+ }
89
+ }
90
+ /** Boot a runtime, run a hello command, tear down. Used by the hub's test endpoint. */
91
+ async function testSandbox(cfg, kind) {
92
+ const dir = fs.mkdtempSync(path.join(require("os").tmpdir(), "swarm-sbx-test-"));
93
+ const rt = createSandbox(kind, { runId: `test_${Date.now().toString(36)}`, hostDir: dir, cfg });
94
+ try {
95
+ await rt.start();
96
+ const r = await rt.exec("echo swarm-sandbox-ok && uname -a", { timeoutSec: 60 });
97
+ if (r.code !== 0 || !r.out.includes("swarm-sandbox-ok")) {
98
+ return { ok: false, detail: `command failed (exit ${r.code}): ${r.out.slice(0, 300)}` };
99
+ }
100
+ return { ok: true, detail: r.out.split("\n").filter(Boolean).slice(0, 2).join(" · ").slice(0, 200) };
101
+ }
102
+ catch (e) {
103
+ return { ok: false, detail: (0, util_1.errMsg)(e) };
104
+ }
105
+ finally {
106
+ await rt.destroy().catch(() => { });
107
+ fs.rmSync(dir, { recursive: true, force: true });
108
+ }
109
+ }
110
+ function tryRequire(name) {
111
+ try {
112
+ // Optional cloud SDKs; absence is handled with a clear operator message.
113
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
114
+ return require(name);
115
+ }
116
+ catch {
117
+ return null;
118
+ }
119
+ }
120
+ function needSdk(pkg, provider) {
121
+ throw new Error(`The ${provider} sandbox needs the "${pkg}" package. Install it in the agentswarm folder: npm install ${pkg}`);
122
+ }
123
+ // ---------------------------------------------------------------- host
124
+ /** Run a command on the host (also used by docker via argv spawn). */
125
+ function spawnCollect(cmd, argv, opts) {
126
+ return new Promise((resolve) => {
127
+ const child = (0, child_process_1.spawn)(cmd, argv, {
128
+ shell: opts.shell ?? false,
129
+ cwd: opts.cwd,
130
+ env: opts.env ?? process.env,
131
+ detached: true,
132
+ stdio: ["ignore", "pipe", "pipe"],
133
+ });
134
+ let out = "";
135
+ let bytes = 0;
136
+ const CAP = 400_000;
137
+ const grab = (b) => {
138
+ bytes += b.length;
139
+ if (out.length < CAP)
140
+ out += b.toString("utf8").slice(0, CAP - out.length);
141
+ };
142
+ child.stdout?.on("data", grab);
143
+ child.stderr?.on("data", grab);
144
+ let timedOut = false;
145
+ const killTree = () => {
146
+ try {
147
+ if (child.pid)
148
+ process.kill(-child.pid, "SIGKILL");
149
+ }
150
+ catch {
151
+ try {
152
+ child.kill("SIGKILL");
153
+ }
154
+ catch { /* gone */ }
155
+ }
156
+ };
157
+ const timer = setTimeout(() => {
158
+ timedOut = true;
159
+ killTree();
160
+ }, opts.timeoutSec * 1000);
161
+ const onAbort = () => killTree();
162
+ opts.signal?.addEventListener("abort", onAbort, { once: true });
163
+ child.on("error", (e) => {
164
+ clearTimeout(timer);
165
+ opts.signal?.removeEventListener("abort", onAbort);
166
+ resolve({ code: -1, out: out + `\n[spawn error: ${e.message}]`, timedOut });
167
+ });
168
+ child.on("close", (code) => {
169
+ clearTimeout(timer);
170
+ opts.signal?.removeEventListener("abort", onAbort);
171
+ if (bytes > CAP)
172
+ out += `\n[output capped at ${CAP} bytes; ${bytes} produced]`;
173
+ resolve({ code, out, timedOut });
174
+ });
175
+ });
176
+ }
177
+ class HostRuntime {
178
+ kind = "host";
179
+ label = "host process";
180
+ localFs = true;
181
+ workdir;
182
+ constructor(input) {
183
+ this.workdir = input.hostDir;
184
+ }
185
+ async start() {
186
+ (0, util_1.ensureDir)(this.workdir);
187
+ }
188
+ exec(command, opts) {
189
+ const env = { ...process.env };
190
+ for (const k of config_1.SECRET_ENV_KEYS)
191
+ delete env[k];
192
+ return spawnCollect(command, [], {
193
+ shell: "/bin/bash",
194
+ cwd: opts.cwd ?? this.workdir,
195
+ timeoutSec: opts.timeoutSec,
196
+ signal: opts.signal,
197
+ env,
198
+ });
199
+ }
200
+ async readFile(abs) {
201
+ return fs.readFileSync(abs, "utf8");
202
+ }
203
+ async writeFile(abs, content) {
204
+ (0, util_1.ensureDir)(path.dirname(abs));
205
+ fs.writeFileSync(abs, content, "utf8");
206
+ }
207
+ async pull(remoteAbs, localAbs) {
208
+ (0, util_1.ensureDir)(path.dirname(localAbs));
209
+ fs.copyFileSync(remoteAbs, localAbs);
210
+ }
211
+ async destroy() {
212
+ /* nothing to tear down */
213
+ }
214
+ }
215
+ // ---------------------------------------------------------------- docker
216
+ const CONTAINER_WORKDIR = "/workspace";
217
+ class DockerRuntime {
218
+ input;
219
+ kind = "docker";
220
+ localFs = true;
221
+ workdir;
222
+ name;
223
+ image;
224
+ constructor(input) {
225
+ this.input = input;
226
+ // File tools keep using the host path — the bind mount makes both views
227
+ // of the workspace identical.
228
+ this.workdir = input.hostDir;
229
+ this.name = `swarm-sbx-${input.runId.replace(/[^a-zA-Z0-9_.-]/g, "")}`;
230
+ this.image = input.cfg.sandboxImage || "node:22-bookworm";
231
+ }
232
+ get label() {
233
+ return `docker container (${this.image})`;
234
+ }
235
+ /** Map a host path under the workspace to its in-container path. */
236
+ containerPath(hostAbs) {
237
+ const rel = path.relative(this.workdir, hostAbs);
238
+ if (rel === "" || rel === ".")
239
+ return CONTAINER_WORKDIR;
240
+ if (rel.startsWith(".."))
241
+ return CONTAINER_WORKDIR; // outside the mount — stay home
242
+ return path.posix.join(CONTAINER_WORKDIR, rel.split(path.sep).join("/"));
243
+ }
244
+ async start(log) {
245
+ if (!dockerAvailable(true)) {
246
+ throw new Error("Docker daemon is not reachable. Start Docker Desktop / OrbStack / Colima, " +
247
+ "or set the sandbox runtime to a cloud provider or host in Settings.");
248
+ }
249
+ (0, util_1.ensureDir)(this.workdir);
250
+ (0, child_process_1.spawnSync)("docker", ["rm", "-f", this.name], { timeout: 20000 });
251
+ // Pull explicitly first so a cold image doesn't eat the run's first command.
252
+ const have = (0, child_process_1.spawnSync)("docker", ["image", "inspect", this.image], { timeout: 15000 });
253
+ if (have.status !== 0) {
254
+ log?.(`docker: pulling ${this.image} (first time only)…`);
255
+ const pull = (0, child_process_1.spawnSync)("docker", ["pull", this.image], { timeout: 600_000, encoding: "utf8" });
256
+ if (pull.status !== 0)
257
+ throw new Error(`docker pull ${this.image} failed: ${(pull.stderr || "").slice(0, 300)}`);
258
+ }
259
+ const run = (0, child_process_1.spawnSync)("docker", [
260
+ "run", "-d", "--init", "--name", this.name,
261
+ "--memory", "4g", "--cpus", "4", "--pids-limit", "1024",
262
+ "-v", `${this.workdir}:${CONTAINER_WORKDIR}`,
263
+ "-w", CONTAINER_WORKDIR,
264
+ this.image, "sleep", "infinity",
265
+ ], { timeout: 60_000, encoding: "utf8" });
266
+ if (run.status !== 0)
267
+ throw new Error(`docker run failed: ${(run.stderr || "").slice(0, 400)}`);
268
+ log?.(`docker: container ${this.name} up (${this.image})`);
269
+ }
270
+ exec(command, opts) {
271
+ const cwd = this.containerPath(opts.cwd ?? this.workdir);
272
+ // In-container `timeout` guards against the docker CLI dying while the
273
+ // process inside lives on; the outer watchdog guards the CLI itself.
274
+ return spawnCollect("docker", ["exec", "-w", cwd, this.name, "timeout", "-k", "5", String(opts.timeoutSec), "bash", "-lc", command], { timeoutSec: opts.timeoutSec + 15, signal: opts.signal }).then((r) => (r.code === 124 ? { ...r, timedOut: true } : r));
275
+ }
276
+ async readFile(abs) {
277
+ return fs.readFileSync(abs, "utf8");
278
+ }
279
+ async writeFile(abs, content) {
280
+ (0, util_1.ensureDir)(path.dirname(abs));
281
+ fs.writeFileSync(abs, content, "utf8");
282
+ }
283
+ async pull(remoteAbs, localAbs) {
284
+ (0, util_1.ensureDir)(path.dirname(localAbs));
285
+ fs.copyFileSync(remoteAbs, localAbs);
286
+ }
287
+ async destroy() {
288
+ (0, child_process_1.spawnSync)("docker", ["rm", "-f", this.name], { timeout: 30_000 });
289
+ }
290
+ }
291
+ // ---------------------------------------------------------------- remote base
292
+ /**
293
+ * Remote sandboxes share exec-based file IO (base64 over the shell) so every
294
+ * provider gets correct read/write/pull even where the SDK lacks a files API.
295
+ * Chunked to stay under argv limits.
296
+ */
297
+ class RemoteRuntime {
298
+ localFs = false;
299
+ async execOk(command, what) {
300
+ const r = await this.exec(command, { timeoutSec: 120 });
301
+ if (r.code !== 0)
302
+ throw new Error(`${what} failed (exit ${r.code}): ${r.out.slice(0, 300)}`);
303
+ return r.out;
304
+ }
305
+ async readFile(abs) {
306
+ const out = await this.execOk(`base64 < ${shq(abs)}`, `read ${abs}`);
307
+ return Buffer.from(out.replace(/\s+/g, ""), "base64").toString("utf8");
308
+ }
309
+ async writeFile(abs, content) {
310
+ const b64 = Buffer.from(content, "utf8").toString("base64");
311
+ const dir = path.posix.dirname(abs);
312
+ await this.execOk(`mkdir -p ${shq(dir)} && : > ${shq(abs)}`, `create ${abs}`);
313
+ const CHUNK = 60_000;
314
+ for (let i = 0; i < b64.length || i === 0; i += CHUNK) {
315
+ const part = b64.slice(i, i + CHUNK);
316
+ await this.execOk(`printf %s ${shq(part)} | base64 -d >> ${shq(abs)}`, `write ${abs}`);
317
+ if (b64.length === 0)
318
+ break;
319
+ }
320
+ }
321
+ async pull(remoteAbs, localAbs) {
322
+ const out = await this.execOk(`base64 < ${shq(remoteAbs)}`, `pull ${remoteAbs}`);
323
+ (0, util_1.ensureDir)(path.dirname(localAbs));
324
+ fs.writeFileSync(localAbs, Buffer.from(out.replace(/\s+/g, ""), "base64"));
325
+ }
326
+ }
327
+ /** POSIX single-quote escaping. */
328
+ function shq(s) {
329
+ return `'${s.replace(/'/g, `'\\''`)}'`;
330
+ }
331
+ // ---------------------------------------------------------------- e2b
332
+ class E2BRuntime extends RemoteRuntime {
333
+ input;
334
+ kind = "e2b";
335
+ workdir = "/home/user/workspace";
336
+ sbx = null;
337
+ template;
338
+ constructor(input) {
339
+ super();
340
+ this.input = input;
341
+ this.template = input.cfg.e2bTemplate || "base";
342
+ }
343
+ get label() {
344
+ return `E2B cloud sandbox (${this.template})`;
345
+ }
346
+ async start(log) {
347
+ const mod = tryRequire("e2b") ?? needSdk("e2b", "E2B");
348
+ const Sandbox = mod.Sandbox ?? mod.default;
349
+ this.sbx = await Sandbox.create(this.template, {
350
+ apiKey: this.input.cfg.e2bApiKey || process.env.E2B_API_KEY,
351
+ timeoutMs: 3_600_000,
352
+ });
353
+ // The workdir doesn't exist yet — run the bootstrap from a dir that does.
354
+ await this.exec(`mkdir -p ${shq(this.workdir)}`, { timeoutSec: 30, cwd: "/home/user" });
355
+ log?.(`e2b: sandbox ${this.sbx.sandboxId ?? ""} up`);
356
+ }
357
+ async exec(command, opts) {
358
+ if (!this.sbx)
359
+ throw new Error("e2b sandbox not started");
360
+ // Keep the sandbox alive as long as the run keeps working.
361
+ this.sbx.setTimeout?.(3_600_000)?.catch?.(() => { });
362
+ const cwd = opts.cwd ?? this.workdir;
363
+ try {
364
+ const r = await this.sbx.commands.run(command, {
365
+ cwd,
366
+ timeoutMs: opts.timeoutSec * 1000,
367
+ });
368
+ return { code: r.exitCode ?? 0, out: joinOut(r.stdout, r.stderr), timedOut: false };
369
+ }
370
+ catch (e) {
371
+ // Non-zero exits surface as CommandExitError carrying the result.
372
+ if (e && typeof e.exitCode === "number") {
373
+ return { code: e.exitCode, out: joinOut(e.stdout, e.stderr) || (0, util_1.errMsg)(e), timedOut: false };
374
+ }
375
+ const timedOut = /timeout|timed out/i.test((0, util_1.errMsg)(e));
376
+ return { code: -1, out: (0, util_1.errMsg)(e), timedOut };
377
+ }
378
+ }
379
+ async destroy() {
380
+ await this.sbx?.kill?.().catch?.(() => { });
381
+ this.sbx = null;
382
+ }
383
+ }
384
+ // ---------------------------------------------------------------- modal
385
+ class ModalRuntime extends RemoteRuntime {
386
+ input;
387
+ kind = "modal";
388
+ workdir = "/workspace";
389
+ sb = null;
390
+ image;
391
+ constructor(input) {
392
+ super();
393
+ this.input = input;
394
+ this.image = input.cfg.sandboxImage || "node:22-bookworm";
395
+ }
396
+ get label() {
397
+ return `Modal cloud sandbox (${this.image})`;
398
+ }
399
+ async start(log) {
400
+ const mod = tryRequire("modal") ?? needSdk("modal", "Modal");
401
+ const { cfg } = this.input;
402
+ if (cfg.modalTokenId)
403
+ process.env.MODAL_TOKEN_ID = cfg.modalTokenId;
404
+ if (cfg.modalTokenSecret)
405
+ process.env.MODAL_TOKEN_SECRET = cfg.modalTokenSecret;
406
+ const ModalClient = mod.ModalClient ?? mod.default?.ModalClient;
407
+ const client = ModalClient ? new ModalClient() : mod;
408
+ const app = await client.apps.fromName("agentswarm", { createIfMissing: true });
409
+ const image = client.images.fromRegistry(this.image);
410
+ try {
411
+ this.sb = await client.sandboxes.create(app, image, { timeout: 3_600_000 });
412
+ }
413
+ catch {
414
+ this.sb = await client.sandboxes.create(app, image);
415
+ }
416
+ await this.exec(`mkdir -p ${shq(this.workdir)}`, { timeoutSec: 60, cwd: "/" });
417
+ log?.(`modal: sandbox up (${this.image})`);
418
+ }
419
+ async exec(command, opts) {
420
+ if (!this.sb)
421
+ throw new Error("modal sandbox not started");
422
+ const cwd = opts.cwd ?? this.workdir;
423
+ try {
424
+ const p = await this.sb.exec(["bash", "-lc", `cd ${shq(cwd)} 2>/dev/null; ${command}`], {
425
+ stdout: "pipe",
426
+ stderr: "pipe",
427
+ });
428
+ const timer = new Promise((_, rej) => setTimeout(() => rej(new Error(`timed out after ${opts.timeoutSec}s`)), opts.timeoutSec * 1000));
429
+ const result = (async () => {
430
+ const [so, se] = await Promise.all([p.stdout.readText(), p.stderr.readText()]);
431
+ const code = await p.wait();
432
+ return { code, out: joinOut(so, se), timedOut: false };
433
+ })();
434
+ return await Promise.race([result, timer]);
435
+ }
436
+ catch (e) {
437
+ const timedOut = /timed out/i.test((0, util_1.errMsg)(e));
438
+ return { code: -1, out: (0, util_1.errMsg)(e), timedOut };
439
+ }
440
+ }
441
+ async destroy() {
442
+ await this.sb?.terminate?.().catch?.(() => { });
443
+ this.sb = null;
444
+ }
445
+ }
446
+ // ---------------------------------------------------------------- vercel
447
+ class VercelRuntime extends RemoteRuntime {
448
+ input;
449
+ kind = "vercel";
450
+ workdir = "/vercel/sandbox";
451
+ sb = null;
452
+ constructor(input) {
453
+ super();
454
+ this.input = input;
455
+ }
456
+ get label() {
457
+ return "Vercel sandbox (node22)";
458
+ }
459
+ async start(log) {
460
+ const mod = tryRequire("@vercel/sandbox") ?? needSdk("@vercel/sandbox", "Vercel");
461
+ const Sandbox = mod.Sandbox ?? mod.default;
462
+ const { cfg } = this.input;
463
+ this.sb = await Sandbox.create({
464
+ ...(cfg.vercelToken ? { token: cfg.vercelToken } : {}),
465
+ ...(cfg.vercelTeamId ? { teamId: cfg.vercelTeamId } : {}),
466
+ ...(cfg.vercelProjectId ? { projectId: cfg.vercelProjectId } : {}),
467
+ runtime: "node22",
468
+ timeout: 2_700_000, // 45 min; extended implicitly by activity on paid plans
469
+ });
470
+ log?.("vercel: sandbox up (node22)");
471
+ }
472
+ async exec(command, opts) {
473
+ if (!this.sb)
474
+ throw new Error("vercel sandbox not started");
475
+ const cwd = opts.cwd ?? this.workdir;
476
+ try {
477
+ const timer = new Promise((_, rej) => setTimeout(() => rej(new Error(`timed out after ${opts.timeoutSec}s`)), opts.timeoutSec * 1000));
478
+ const result = (async () => {
479
+ const done = await this.sb.runCommand({
480
+ cmd: "bash",
481
+ args: ["-lc", `cd ${shq(cwd)} 2>/dev/null; ${command}`],
482
+ });
483
+ const [so, se] = await Promise.all([
484
+ typeof done.stdout === "function" ? done.stdout() : done.stdout,
485
+ typeof done.stderr === "function" ? done.stderr() : done.stderr,
486
+ ]);
487
+ return { code: done.exitCode ?? 0, out: joinOut(so, se), timedOut: false };
488
+ })();
489
+ return await Promise.race([result, timer]);
490
+ }
491
+ catch (e) {
492
+ const timedOut = /timed out/i.test((0, util_1.errMsg)(e));
493
+ return { code: -1, out: (0, util_1.errMsg)(e), timedOut };
494
+ }
495
+ }
496
+ async destroy() {
497
+ await this.sb?.stop?.().catch?.(() => { });
498
+ this.sb = null;
499
+ }
500
+ }
501
+ function joinOut(stdout, stderr) {
502
+ const a = typeof stdout === "string" ? stdout : "";
503
+ const b = typeof stderr === "string" ? stderr : "";
504
+ return [a, b].filter(Boolean).join(b && a && !a.endsWith("\n") ? "\n" : "");
505
+ }