@isol8/core 0.18.0 → 0.20.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
@@ -48,6 +48,37 @@ var init_adapter = __esm(() => {
48
48
  };
49
49
  });
50
50
 
51
+ // src/runtime/adapters/agent.ts
52
+ function shellQuote(s) {
53
+ return `'${s.replace(/'/g, "'\\''")}'`;
54
+ }
55
+ var SANDBOX_SYSTEM_PROMPT, AgentAdapter;
56
+ var init_agent = __esm(() => {
57
+ SANDBOX_SYSTEM_PROMPT = "You are running inside an isol8 sandbox — a Docker container with strict " + "resource limits and controlled network access. isol8 exists to execute " + "untrusted code safely: outbound network is filtered to a whitelist, the " + "filesystem is ephemeral, and some system calls are restricted. Work within " + "these constraints: do not assume open internet access, do not rely on " + "persistent state across runs, and do not attempt to escape the sandbox.";
58
+ AgentAdapter = {
59
+ name: "agent",
60
+ image: "isol8:agent",
61
+ getCommand(code) {
62
+ return [
63
+ "bash",
64
+ "-c",
65
+ `pi --no-session --append-system-prompt ${shellQuote(SANDBOX_SYSTEM_PROMPT)} -p ${shellQuote(code)}`
66
+ ];
67
+ },
68
+ getCommandWithOptions(code, options) {
69
+ const flags = options.agentFlags ? `${options.agentFlags} ` : "";
70
+ return [
71
+ "bash",
72
+ "-c",
73
+ `pi --no-session --append-system-prompt ${shellQuote(SANDBOX_SYSTEM_PROMPT)} ${flags}-p ${shellQuote(code)}`
74
+ ];
75
+ },
76
+ getFileExtension() {
77
+ return ".txt";
78
+ }
79
+ };
80
+ });
81
+
51
82
  // src/runtime/adapters/bash.ts
52
83
  var bashAdapter;
53
84
  var init_bash = __esm(() => {
@@ -149,12 +180,14 @@ var init_python = __esm(() => {
149
180
  // src/runtime/index.ts
150
181
  var init_runtime = __esm(() => {
151
182
  init_adapter();
183
+ init_agent();
152
184
  init_bash();
153
185
  init_bun();
154
186
  init_deno();
155
187
  init_node();
156
188
  init_python();
157
189
  init_adapter();
190
+ init_agent();
158
191
  init_bash();
159
192
  init_bun();
160
193
  init_deno();
@@ -165,6 +198,7 @@ var init_runtime = __esm(() => {
165
198
  RuntimeRegistry.register(BunAdapter);
166
199
  RuntimeRegistry.register(bashAdapter);
167
200
  RuntimeRegistry.register(DenoAdapter);
201
+ RuntimeRegistry.register(AgentAdapter);
168
202
  });
169
203
 
170
204
  // src/utils/logger.ts
@@ -530,9 +564,9 @@ ${setupLines}
530
564
  }
531
565
  }, (event) => {
532
566
  if (event.stream) {
533
- process.stdout.write(event.stream);
567
+ onProgress?.({ runtime: String(runtime), status: "building", message: event.stream });
534
568
  } else if (event.error) {
535
- console.error(event.error);
569
+ onProgress?.({ runtime: String(runtime), status: "error", message: event.error });
536
570
  }
537
571
  });
538
572
  });
@@ -1384,6 +1418,8 @@ class ExecutionManager {
1384
1418
  return ["npm", "install", "--prefix", "/sandbox", ...packages];
1385
1419
  case "bun":
1386
1420
  return ["bun", "install", "-g", "--global-dir=/sandbox/.bun-global", ...packages];
1421
+ case "agent":
1422
+ return ["bun", "install", "-g", "--global-dir=/sandbox/.bun-global", ...packages];
1387
1423
  case "deno":
1388
1424
  return ["sh", "-c", packages.map((p) => `deno cache ${p}`).join(" && ")];
1389
1425
  case "bash":
@@ -1411,7 +1447,7 @@ class ExecutionManager {
1411
1447
  env.push("npm_config_fetch_retry_mintimeout=1000");
1412
1448
  env.push("NPM_CONFIG_FETCH_RETRY_MAXTIMEOUT=2000");
1413
1449
  env.push("npm_config_fetch_retry_maxtimeout=2000");
1414
- } else if (runtime === "bun") {
1450
+ } else if (runtime === "bun" || runtime === "agent") {
1415
1451
  env.push("BUN_INSTALL_GLOBAL_DIR=/sandbox/.bun-global");
1416
1452
  env.push("BUN_INSTALL_CACHE_DIR=/sandbox/.bun-cache");
1417
1453
  env.push("BUN_INSTALL_BIN=/sandbox/.bun-global/bin");
@@ -1455,7 +1491,7 @@ class ExecutionManager {
1455
1491
  stream.on("error", reject);
1456
1492
  });
1457
1493
  }
1458
- async runSetupScript(container, script, timeoutMs, volumeManager) {
1494
+ async* runSetupScript(container, script, timeoutMs, volumeManager) {
1459
1495
  const scriptPath = "/sandbox/.isol8-setup.sh";
1460
1496
  await volumeManager.writeFileViaExec(container, scriptPath, script);
1461
1497
  const chmodExec = await container.exec({
@@ -1483,34 +1519,67 @@ class ExecutionManager {
1483
1519
  User: "sandbox"
1484
1520
  });
1485
1521
  const stream = await exec.start({ Detach: false, Tty: false });
1486
- return new Promise((resolve2, reject) => {
1487
- let stderr = "";
1488
- const stdoutStream = new PassThrough;
1489
- const stderrStream = new PassThrough;
1490
- container.modem.demuxStream(stream, stdoutStream, stderrStream);
1491
- stderrStream.on("data", (chunk) => {
1492
- const text = chunk.toString();
1493
- stderr += text;
1494
- logger.debug(`[setup:stderr] ${text.trimEnd()}`);
1495
- });
1496
- stdoutStream.on("data", (chunk) => {
1497
- const text = chunk.toString();
1498
- logger.debug(`[setup:stdout] ${text.trimEnd()}`);
1499
- });
1500
- stream.on("end", async () => {
1501
- try {
1502
- const info = await exec.inspect();
1503
- if (info.ExitCode !== 0) {
1504
- reject(new Error(`Setup script failed (exit code ${info.ExitCode}): ${stderr}`));
1505
- } else {
1506
- resolve2();
1507
- }
1508
- } catch (err) {
1509
- reject(err);
1522
+ const queue = [];
1523
+ let notify = null;
1524
+ let done = false;
1525
+ const push = (event) => {
1526
+ queue.push(event);
1527
+ if (notify) {
1528
+ notify();
1529
+ notify = null;
1530
+ }
1531
+ };
1532
+ const timer = setTimeout(() => {
1533
+ push({ type: "error", data: "SETUP SCRIPT TIMED OUT", phase: "setup" });
1534
+ push({ type: "exit", data: "137", phase: "setup" });
1535
+ done = true;
1536
+ }, timeoutMs);
1537
+ const stdoutStream = new PassThrough;
1538
+ const stderrStream = new PassThrough;
1539
+ container.modem.demuxStream(stream, stdoutStream, stderrStream);
1540
+ stdoutStream.on("data", (chunk) => {
1541
+ const text = chunk.toString("utf-8");
1542
+ logger.debug(`[setup:stdout] ${text.trimEnd()}`);
1543
+ push({ type: "stdout", data: text, phase: "setup" });
1544
+ });
1545
+ stderrStream.on("data", (chunk) => {
1546
+ const text = chunk.toString("utf-8");
1547
+ logger.debug(`[setup:stderr] ${text.trimEnd()}`);
1548
+ push({ type: "stderr", data: text, phase: "setup" });
1549
+ });
1550
+ stream.on("end", async () => {
1551
+ clearTimeout(timer);
1552
+ try {
1553
+ const info = await exec.inspect();
1554
+ const exitCode = info.ExitCode ?? 0;
1555
+ if (exitCode !== 0) {
1556
+ push({
1557
+ type: "error",
1558
+ data: `Setup script failed (exit code ${exitCode})`,
1559
+ phase: "setup"
1560
+ });
1510
1561
  }
1511
- });
1512
- stream.on("error", reject);
1562
+ push({ type: "exit", data: exitCode.toString(), phase: "setup" });
1563
+ } catch {
1564
+ push({ type: "exit", data: "1", phase: "setup" });
1565
+ }
1566
+ done = true;
1567
+ });
1568
+ stream.on("error", (err) => {
1569
+ clearTimeout(timer);
1570
+ push({ type: "error", data: err.message, phase: "setup" });
1571
+ push({ type: "exit", data: "1", phase: "setup" });
1572
+ done = true;
1513
1573
  });
1574
+ while (!done || queue.length > 0) {
1575
+ if (queue.length > 0) {
1576
+ yield queue.shift();
1577
+ } else {
1578
+ await new Promise((r) => {
1579
+ notify = r;
1580
+ });
1581
+ }
1582
+ }
1514
1583
  }
1515
1584
  async* streamExecOutput(stream, exec, container, timeoutMs) {
1516
1585
  const queue = [];
@@ -1524,8 +1593,8 @@ class ExecutionManager {
1524
1593
  }
1525
1594
  };
1526
1595
  const timer = setTimeout(() => {
1527
- push({ type: "error", data: "EXECUTION TIMED OUT" });
1528
- push({ type: "exit", data: "137" });
1596
+ push({ type: "error", data: "EXECUTION TIMED OUT", phase: "code" });
1597
+ push({ type: "exit", data: "137", phase: "code" });
1529
1598
  done = true;
1530
1599
  }, timeoutMs);
1531
1600
  const stdoutStream = new PassThrough;
@@ -1536,29 +1605,29 @@ class ExecutionManager {
1536
1605
  if (Object.keys(this.secrets).length > 0) {
1537
1606
  text = maskSecrets(text, this.secrets);
1538
1607
  }
1539
- push({ type: "stdout", data: text });
1608
+ push({ type: "stdout", data: text, phase: "code" });
1540
1609
  });
1541
1610
  stderrStream.on("data", (chunk) => {
1542
1611
  let text = chunk.toString("utf-8");
1543
1612
  if (Object.keys(this.secrets).length > 0) {
1544
1613
  text = maskSecrets(text, this.secrets);
1545
1614
  }
1546
- push({ type: "stderr", data: text });
1615
+ push({ type: "stderr", data: text, phase: "code" });
1547
1616
  });
1548
1617
  stream.on("end", async () => {
1549
1618
  clearTimeout(timer);
1550
1619
  try {
1551
1620
  const info = await exec.inspect();
1552
- push({ type: "exit", data: (info.ExitCode ?? 0).toString() });
1621
+ push({ type: "exit", data: (info.ExitCode ?? 0).toString(), phase: "code" });
1553
1622
  } catch {
1554
- push({ type: "exit", data: "1" });
1623
+ push({ type: "exit", data: "1", phase: "code" });
1555
1624
  }
1556
1625
  done = true;
1557
1626
  });
1558
1627
  stream.on("error", (err) => {
1559
1628
  clearTimeout(timer);
1560
- push({ type: "error", data: err.message });
1561
- push({ type: "exit", data: "1" });
1629
+ push({ type: "error", data: err.message, phase: "code" });
1630
+ push({ type: "exit", data: "1", phase: "code" });
1562
1631
  done = true;
1563
1632
  });
1564
1633
  while (!done || queue.length > 0) {
@@ -2196,11 +2265,16 @@ class DockerIsol8 {
2196
2265
  async resolveExecutionRequest(req) {
2197
2266
  const inlineCode = req.code?.trim();
2198
2267
  const codeUrl = req.codeUrl?.trim();
2199
- if (inlineCode && codeUrl) {
2200
- throw new Error("ExecutionRequest.code and ExecutionRequest.codeUrl are mutually exclusive.");
2268
+ const cmd = req.cmd?.trim();
2269
+ const sourceCount = [inlineCode, codeUrl, cmd].filter(Boolean).length;
2270
+ if (sourceCount > 1) {
2271
+ throw new Error("ExecutionRequest.code, ExecutionRequest.codeUrl, and ExecutionRequest.cmd are mutually exclusive — provide exactly one.");
2201
2272
  }
2202
- if (!(inlineCode || codeUrl)) {
2203
- throw new Error("ExecutionRequest must include either code or codeUrl.");
2273
+ if (sourceCount === 0) {
2274
+ throw new Error("ExecutionRequest must include exactly one of: code, codeUrl, or cmd.");
2275
+ }
2276
+ if (cmd) {
2277
+ return { ...req, cmd: req.cmd };
2204
2278
  }
2205
2279
  if (inlineCode) {
2206
2280
  return { ...req, code: req.code };
@@ -2311,6 +2385,7 @@ class DockerIsol8 {
2311
2385
  await this.semaphore.acquire();
2312
2386
  const startTime = Date.now();
2313
2387
  try {
2388
+ this.validateAgentRuntime(req);
2314
2389
  const request = await this.resolveExecutionRequest(req);
2315
2390
  const result = this.mode === "persistent" ? await this.executePersistent(request, startTime) : await this.executeEphemeral(request, startTime);
2316
2391
  return result;
@@ -2321,7 +2396,7 @@ class DockerIsol8 {
2321
2396
  async recordAudit(req, result, startTime, container) {
2322
2397
  try {
2323
2398
  const enc = new TextEncoder;
2324
- const data = enc.encode(req.code);
2399
+ const data = enc.encode(req.code ?? req.cmd ?? "");
2325
2400
  const digest = await crypto.subtle.digest("SHA-256", data);
2326
2401
  const codeHash = Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, "0")).join("");
2327
2402
  let securityEvents;
@@ -2452,74 +2527,173 @@ class DockerIsol8 {
2452
2527
  async* executeStream(req) {
2453
2528
  await this.semaphore.acquire();
2454
2529
  try {
2530
+ this.validateAgentRuntime(req);
2455
2531
  const request = await this.resolveExecutionRequest(req);
2456
- const adapter = this.getAdapter(request.runtime);
2457
- const timeoutMs = request.timeoutMs ?? this.defaultTimeoutMs;
2458
- const resolved = await this.resolveImage(adapter, request.installPackages);
2459
- const image = resolved.image;
2460
- const execWorkdir = request.workdir ? resolveWorkdir(request.workdir) : SANDBOX_WORKDIR;
2461
- const container = await this.docker.createContainer({
2462
- Image: image,
2463
- Cmd: ["sleep", "infinity"],
2464
- WorkingDir: SANDBOX_WORKDIR,
2465
- Env: this.executionManager.buildEnv(undefined, this.networkManager.proxyPort, this.network, this.networkFilter),
2466
- NetworkDisabled: this.network === "none",
2467
- HostConfig: this.buildHostConfig(),
2468
- StopTimeout: 2
2469
- });
2470
- try {
2471
- await container.start();
2472
- await this.networkManager.startProxy(container);
2473
- await this.networkManager.setupIptables(container);
2474
- const ext = request.fileExtension ?? adapter.getFileExtension();
2475
- const filePath = `${SANDBOX_WORKDIR}/main${ext}`;
2476
- await this.volumeManager.writeFileViaExec(container, filePath, request.code);
2477
- if (resolved.remainingPackages.length > 0) {
2478
- await this.executionManager.installPackages(container, request.runtime, resolved.remainingPackages, timeoutMs);
2532
+ if (this.mode === "persistent") {
2533
+ yield* this.executeStreamPersistent(request);
2534
+ } else {
2535
+ yield* this.executeStreamEphemeral(request);
2536
+ }
2537
+ } finally {
2538
+ this.semaphore.release();
2539
+ }
2540
+ }
2541
+ async* executeStreamPersistent(request) {
2542
+ const adapter = this.getAdapter(request.runtime);
2543
+ const timeoutMs = request.timeoutMs ?? this.defaultTimeoutMs;
2544
+ const execWorkdir = request.workdir ? resolveWorkdir(request.workdir) : SANDBOX_WORKDIR;
2545
+ let remainingPackages = request.installPackages ?? [];
2546
+ let imageSetupScript;
2547
+ if (!this.container) {
2548
+ const started = await this.startPersistentContainer(adapter, request.installPackages);
2549
+ remainingPackages = started.remainingPackages;
2550
+ imageSetupScript = started.imageSetupScript;
2551
+ } else if (this.persistentRuntime?.name !== adapter.name) {
2552
+ throw new Error(`Cannot switch runtime from "${this.persistentRuntime?.name}" to "${adapter.name}". Each persistent container supports a single runtime. Create a new Isol8 instance for a different runtime.`);
2553
+ }
2554
+ let rawCmd;
2555
+ if (request.cmd) {
2556
+ rawCmd = ["bash", "-c", request.cmd];
2557
+ } else {
2558
+ const ext = request.fileExtension ?? adapter.getFileExtension();
2559
+ const filePath = `${SANDBOX_WORKDIR}/exec_${Date.now()}${ext}`;
2560
+ await this.volumeManager.putFile(this.container, filePath, request.code);
2561
+ rawCmd = this.buildAdapterCommand(adapter, request, filePath);
2562
+ }
2563
+ if (request.files) {
2564
+ for (const [fPath, fContent] of Object.entries(request.files)) {
2565
+ await this.volumeManager.putFile(this.container, fPath, fContent);
2566
+ }
2567
+ }
2568
+ if (remainingPackages.length > 0) {
2569
+ await this.executionManager.installPackages(this.container, request.runtime, remainingPackages, timeoutMs);
2570
+ }
2571
+ if (imageSetupScript) {
2572
+ let setupExitCode = 0;
2573
+ for await (const event of this.executionManager.runSetupScript(this.container, imageSetupScript, timeoutMs, this.volumeManager)) {
2574
+ yield event;
2575
+ if (event.type === "exit") {
2576
+ setupExitCode = Number(event.data);
2577
+ }
2578
+ }
2579
+ if (setupExitCode !== 0) {
2580
+ return;
2581
+ }
2582
+ }
2583
+ if (request.setupScript) {
2584
+ let setupExitCode = 0;
2585
+ for await (const event of this.executionManager.runSetupScript(this.container, request.setupScript, timeoutMs, this.volumeManager)) {
2586
+ yield event;
2587
+ if (event.type === "exit") {
2588
+ setupExitCode = Number(event.data);
2479
2589
  }
2480
- if (resolved.imageSetupScript) {
2481
- await this.executionManager.runSetupScript(container, resolved.imageSetupScript, timeoutMs, this.volumeManager);
2590
+ }
2591
+ if (setupExitCode !== 0) {
2592
+ return;
2593
+ }
2594
+ }
2595
+ const timeoutSec = Math.ceil(timeoutMs / 1000);
2596
+ let cmd;
2597
+ if (request.stdin) {
2598
+ const stdinPath = `${SANDBOX_WORKDIR}/_stdin_${Date.now()}`;
2599
+ await this.volumeManager.writeFileViaExec(this.container, stdinPath, request.stdin);
2600
+ const cmdStr = rawCmd.map((a) => `'${a.replace(/'/g, "'\\''")}'`).join(" ");
2601
+ cmd = this.executionManager.wrapWithTimeout(["sh", "-c", `cat ${stdinPath} | ${cmdStr}`], timeoutSec);
2602
+ } else {
2603
+ cmd = this.executionManager.wrapWithTimeout(rawCmd, timeoutSec);
2604
+ }
2605
+ const exec = await this.container.exec({
2606
+ Cmd: cmd,
2607
+ Env: this.executionManager.buildEnv(request.env, this.networkManager.proxyPort, this.network, this.networkFilter),
2608
+ AttachStdout: true,
2609
+ AttachStderr: true,
2610
+ WorkingDir: execWorkdir,
2611
+ User: "sandbox"
2612
+ });
2613
+ const execStream = await exec.start({ Tty: false });
2614
+ yield* this.executionManager.streamExecOutput(execStream, exec, this.container, timeoutMs);
2615
+ }
2616
+ async* executeStreamEphemeral(request) {
2617
+ const adapter = this.getAdapter(request.runtime);
2618
+ const timeoutMs = request.timeoutMs ?? this.defaultTimeoutMs;
2619
+ const resolved = await this.resolveImage(adapter, request.installPackages);
2620
+ const image = resolved.image;
2621
+ const execWorkdir = request.workdir ? resolveWorkdir(request.workdir) : SANDBOX_WORKDIR;
2622
+ const pool = this.ensurePool();
2623
+ const container = await pool.acquire(image);
2624
+ try {
2625
+ await this.networkManager.startProxy(container);
2626
+ await this.networkManager.setupIptables(container);
2627
+ if (resolved.remainingPackages.length > 0) {
2628
+ await this.executionManager.installPackages(container, request.runtime, resolved.remainingPackages, timeoutMs);
2629
+ }
2630
+ if (resolved.imageSetupScript) {
2631
+ let setupExitCode = 0;
2632
+ for await (const event of this.executionManager.runSetupScript(container, resolved.imageSetupScript, timeoutMs, this.volumeManager)) {
2633
+ yield event;
2634
+ if (event.type === "exit") {
2635
+ setupExitCode = Number(event.data);
2636
+ }
2482
2637
  }
2483
- if (request.setupScript) {
2484
- await this.executionManager.runSetupScript(container, request.setupScript, timeoutMs, this.volumeManager);
2638
+ if (setupExitCode !== 0) {
2639
+ return;
2485
2640
  }
2486
- if (request.files) {
2487
- for (const [fPath, fContent] of Object.entries(request.files)) {
2488
- await this.volumeManager.writeFileViaExec(container, fPath, fContent);
2641
+ }
2642
+ if (request.setupScript) {
2643
+ let setupExitCode = 0;
2644
+ for await (const event of this.executionManager.runSetupScript(container, request.setupScript, timeoutMs, this.volumeManager)) {
2645
+ yield event;
2646
+ if (event.type === "exit") {
2647
+ setupExitCode = Number(event.data);
2489
2648
  }
2490
2649
  }
2491
- const rawCmd = adapter.getCommand(request.code, filePath);
2492
- const timeoutSec = Math.ceil(timeoutMs / 1000);
2493
- let cmd;
2494
- if (request.stdin) {
2495
- const stdinPath = `${SANDBOX_WORKDIR}/_stdin`;
2496
- await this.volumeManager.writeFileViaExec(container, stdinPath, request.stdin);
2497
- const cmdStr = rawCmd.map((a) => `'${a.replace(/'/g, "'\\''")}'`).join(" ");
2498
- cmd = this.executionManager.wrapWithTimeout(["sh", "-c", `cat ${stdinPath} | ${cmdStr}`], timeoutSec);
2499
- } else {
2500
- cmd = this.executionManager.wrapWithTimeout(rawCmd, timeoutSec);
2650
+ if (setupExitCode !== 0) {
2651
+ return;
2501
2652
  }
2502
- const exec = await container.exec({
2503
- Cmd: cmd,
2504
- Env: this.executionManager.buildEnv(request.env, this.networkManager.proxyPort, this.network, this.networkFilter),
2505
- AttachStdout: true,
2506
- AttachStderr: true,
2507
- WorkingDir: execWorkdir,
2508
- User: "sandbox"
2509
- });
2510
- const execStream = await exec.start({ Tty: false });
2511
- yield* this.executionManager.streamExecOutput(execStream, exec, container, timeoutMs);
2512
- } finally {
2513
- if (this.persist) {
2514
- logger.debug(`[Persist] Leaving container running for inspection: ${container.id}`);
2515
- } else {
2516
- try {
2517
- await container.remove({ force: true });
2518
- } catch {}
2653
+ }
2654
+ if (request.files) {
2655
+ for (const [fPath, fContent] of Object.entries(request.files)) {
2656
+ await this.volumeManager.writeFileViaExec(container, fPath, fContent);
2519
2657
  }
2520
2658
  }
2659
+ let rawCmd;
2660
+ if (request.cmd) {
2661
+ rawCmd = ["bash", "-c", request.cmd];
2662
+ } else {
2663
+ const ext = request.fileExtension ?? adapter.getFileExtension();
2664
+ const filePath = `${SANDBOX_WORKDIR}/main${ext}`;
2665
+ await this.volumeManager.writeFileViaExec(container, filePath, request.code);
2666
+ rawCmd = this.buildAdapterCommand(adapter, request, filePath);
2667
+ }
2668
+ const timeoutSec = Math.ceil(timeoutMs / 1000);
2669
+ let cmd;
2670
+ if (request.stdin) {
2671
+ const stdinPath = `${SANDBOX_WORKDIR}/_stdin`;
2672
+ await this.volumeManager.writeFileViaExec(container, stdinPath, request.stdin);
2673
+ const cmdStr = rawCmd.map((a) => `'${a.replace(/'/g, "'\\''")}'`).join(" ");
2674
+ cmd = this.executionManager.wrapWithTimeout(["sh", "-c", `cat ${stdinPath} | ${cmdStr}`], timeoutSec);
2675
+ } else {
2676
+ cmd = this.executionManager.wrapWithTimeout(rawCmd, timeoutSec);
2677
+ }
2678
+ const exec = await container.exec({
2679
+ Cmd: cmd,
2680
+ Env: this.executionManager.buildEnv(request.env, this.networkManager.proxyPort, this.network, this.networkFilter),
2681
+ AttachStdout: true,
2682
+ AttachStderr: true,
2683
+ WorkingDir: execWorkdir,
2684
+ User: "sandbox"
2685
+ });
2686
+ const execStream = await exec.start({ Tty: false });
2687
+ yield* this.executionManager.streamExecOutput(execStream, exec, container, timeoutMs);
2521
2688
  } finally {
2522
- this.semaphore.release();
2689
+ if (this.persist) {
2690
+ logger.debug(`[Persist] Leaving container running for inspection: ${container.id}`);
2691
+ } else {
2692
+ pool.release(container, image).catch((err) => {
2693
+ logger.debug(`[Pool] release failed: ${err}`);
2694
+ container.remove({ force: true }).catch(() => {});
2695
+ });
2696
+ }
2523
2697
  }
2524
2698
  }
2525
2699
  async resolveImage(adapter, requestedPackages) {
@@ -2646,31 +2820,59 @@ class DockerIsol8 {
2646
2820
  try {
2647
2821
  await this.networkManager.startProxy(container);
2648
2822
  await this.networkManager.setupIptables(container);
2649
- const canUseInline = !(req.stdin || req.files || req.outputPaths) && (!req.installPackages || req.installPackages.length === 0);
2650
2823
  let rawCmd;
2651
- if (canUseInline) {
2652
- try {
2653
- rawCmd = adapter.getCommand(req.code);
2654
- } catch {
2824
+ if (req.cmd) {
2825
+ rawCmd = ["bash", "-c", req.cmd];
2826
+ } else {
2827
+ const canUseInline = !(req.stdin || req.files || req.outputPaths) && (!req.installPackages || req.installPackages.length === 0);
2828
+ if (canUseInline) {
2829
+ try {
2830
+ rawCmd = this.buildAdapterCommand(adapter, req);
2831
+ } catch {
2832
+ const ext = req.fileExtension ?? adapter.getFileExtension();
2833
+ const filePath = `${SANDBOX_WORKDIR}/main${ext}`;
2834
+ await this.volumeManager.writeFileViaExec(container, filePath, req.code);
2835
+ rawCmd = this.buildAdapterCommand(adapter, req, filePath);
2836
+ }
2837
+ } else {
2655
2838
  const ext = req.fileExtension ?? adapter.getFileExtension();
2656
2839
  const filePath = `${SANDBOX_WORKDIR}/main${ext}`;
2657
2840
  await this.volumeManager.writeFileViaExec(container, filePath, req.code);
2658
- rawCmd = adapter.getCommand(req.code, filePath);
2841
+ rawCmd = this.buildAdapterCommand(adapter, req, filePath);
2659
2842
  }
2660
- } else {
2661
- const ext = req.fileExtension ?? adapter.getFileExtension();
2662
- const filePath = `${SANDBOX_WORKDIR}/main${ext}`;
2663
- await this.volumeManager.writeFileViaExec(container, filePath, req.code);
2664
- rawCmd = adapter.getCommand(req.code, filePath);
2665
2843
  }
2666
2844
  if (resolved.remainingPackages.length > 0) {
2667
2845
  await this.executionManager.installPackages(container, req.runtime, resolved.remainingPackages, timeoutMs);
2668
2846
  }
2669
2847
  if (resolved.imageSetupScript) {
2670
- await this.executionManager.runSetupScript(container, resolved.imageSetupScript, timeoutMs, this.volumeManager);
2848
+ let stderr2 = "";
2849
+ let exitCode = 0;
2850
+ for await (const event of this.executionManager.runSetupScript(container, resolved.imageSetupScript, timeoutMs, this.volumeManager)) {
2851
+ if (event.type === "stderr") {
2852
+ stderr2 += event.data;
2853
+ }
2854
+ if (event.type === "exit") {
2855
+ exitCode = Number(event.data);
2856
+ }
2857
+ }
2858
+ if (exitCode !== 0) {
2859
+ throw new Error(`Setup script failed (exit code ${exitCode}): ${stderr2}`);
2860
+ }
2671
2861
  }
2672
2862
  if (req.setupScript) {
2673
- await this.executionManager.runSetupScript(container, req.setupScript, timeoutMs, this.volumeManager);
2863
+ let stderr2 = "";
2864
+ let exitCode = 0;
2865
+ for await (const event of this.executionManager.runSetupScript(container, req.setupScript, timeoutMs, this.volumeManager)) {
2866
+ if (event.type === "stderr") {
2867
+ stderr2 += event.data;
2868
+ }
2869
+ if (event.type === "exit") {
2870
+ exitCode = Number(event.data);
2871
+ }
2872
+ }
2873
+ if (exitCode !== 0) {
2874
+ throw new Error(`Setup script failed (exit code ${exitCode}): ${stderr2}`);
2875
+ }
2674
2876
  }
2675
2877
  const timeoutSec = Math.ceil(timeoutMs / 1000);
2676
2878
  let cmd;
@@ -2762,24 +2964,53 @@ class DockerIsol8 {
2762
2964
  } else if (this.persistentRuntime?.name !== adapter.name) {
2763
2965
  throw new Error(`Cannot switch runtime from "${this.persistentRuntime?.name}" to "${adapter.name}". Each persistent container supports a single runtime. Create a new Isol8 instance for a different runtime.`);
2764
2966
  }
2765
- const ext = req.fileExtension ?? adapter.getFileExtension();
2766
- const filePath = `${SANDBOX_WORKDIR}/exec_${Date.now()}${ext}`;
2767
- await this.volumeManager.putFile(this.container, filePath, req.code);
2967
+ let rawCmd;
2968
+ if (req.cmd) {
2969
+ rawCmd = ["bash", "-c", req.cmd];
2970
+ } else {
2971
+ const ext = req.fileExtension ?? adapter.getFileExtension();
2972
+ const filePath = `${SANDBOX_WORKDIR}/exec_${Date.now()}${ext}`;
2973
+ await this.volumeManager.putFile(this.container, filePath, req.code);
2974
+ rawCmd = this.buildAdapterCommand(adapter, req, filePath);
2975
+ }
2768
2976
  if (req.files) {
2769
2977
  for (const [fPath, fContent] of Object.entries(req.files)) {
2770
2978
  await this.volumeManager.putFile(this.container, fPath, fContent);
2771
2979
  }
2772
2980
  }
2773
- const rawCmd = adapter.getCommand(req.code, filePath);
2774
2981
  const timeoutSec = Math.ceil(timeoutMs / 1000);
2775
2982
  if (remainingPackages.length > 0) {
2776
2983
  await this.executionManager.installPackages(this.container, req.runtime, remainingPackages, timeoutMs);
2777
2984
  }
2778
2985
  if (imageSetupScript) {
2779
- await this.executionManager.runSetupScript(this.container, imageSetupScript, timeoutMs, this.volumeManager);
2986
+ let stderr2 = "";
2987
+ let exitCode = 0;
2988
+ for await (const event of this.executionManager.runSetupScript(this.container, imageSetupScript, timeoutMs, this.volumeManager)) {
2989
+ if (event.type === "stderr") {
2990
+ stderr2 += event.data;
2991
+ }
2992
+ if (event.type === "exit") {
2993
+ exitCode = Number(event.data);
2994
+ }
2995
+ }
2996
+ if (exitCode !== 0) {
2997
+ throw new Error(`Setup script failed (exit code ${exitCode}): ${stderr2}`);
2998
+ }
2780
2999
  }
2781
3000
  if (req.setupScript) {
2782
- await this.executionManager.runSetupScript(this.container, req.setupScript, timeoutMs, this.volumeManager);
3001
+ let stderr2 = "";
3002
+ let exitCode = 0;
3003
+ for await (const event of this.executionManager.runSetupScript(this.container, req.setupScript, timeoutMs, this.volumeManager)) {
3004
+ if (event.type === "stderr") {
3005
+ stderr2 += event.data;
3006
+ }
3007
+ if (event.type === "exit") {
3008
+ exitCode = Number(event.data);
3009
+ }
3010
+ }
3011
+ if (exitCode !== 0) {
3012
+ throw new Error(`Setup script failed (exit code ${exitCode}): ${stderr2}`);
3013
+ }
2783
3014
  }
2784
3015
  let cmd;
2785
3016
  if (req.stdin) {
@@ -2878,6 +3109,30 @@ class DockerIsol8 {
2878
3109
  getAdapter(runtime) {
2879
3110
  return RuntimeRegistry.get(runtime);
2880
3111
  }
3112
+ validateAgentRuntime(req) {
3113
+ if (req.runtime !== "agent") {
3114
+ return;
3115
+ }
3116
+ if (this.network === "none") {
3117
+ throw new Error(`Agent runtime requires network access. The AI coding agent needs to reach its LLM provider API. Use --net host, or --net filtered --allow "api.anthropic.com" (or your provider's domain).`);
3118
+ }
3119
+ if (this.network === "filtered") {
3120
+ const whitelist = this.networkFilter?.whitelist ?? [];
3121
+ if (whitelist.length === 0) {
3122
+ throw new Error(`Agent runtime requires at least one network whitelist entry. The AI coding agent needs to reach its LLM provider API. Use --allow "api.anthropic.com" (or your provider's domain).`);
3123
+ }
3124
+ }
3125
+ }
3126
+ buildAdapterCommand(adapter, req, filePath) {
3127
+ const code = req.code ?? "";
3128
+ if (adapter.getCommandWithOptions) {
3129
+ return adapter.getCommandWithOptions(code, {
3130
+ filePath,
3131
+ agentFlags: req.agentFlags
3132
+ });
3133
+ }
3134
+ return adapter.getCommand(code, filePath);
3135
+ }
2881
3136
  buildHostConfig() {
2882
3137
  const config = {
2883
3138
  Memory: parseMemoryLimit(this.memoryLimit),
@@ -2986,7 +3241,7 @@ init_logger();
2986
3241
  // package.json
2987
3242
  var package_default = {
2988
3243
  name: "@isol8/core",
2989
- version: "0.18.0",
3244
+ version: "0.20.0",
2990
3245
  description: "Sandboxed code execution engine for AI agents and apps (Docker, runtime and network controls)",
2991
3246
  author: "Illusion47586",
2992
3247
  license: "MIT",
@@ -3067,7 +3322,8 @@ export {
3067
3322
  LABELS,
3068
3323
  DockerIsol8,
3069
3324
  DenoAdapter,
3070
- BunAdapter
3325
+ BunAdapter,
3326
+ AgentAdapter
3071
3327
  };
3072
3328
 
3073
- //# debugId=014F8E5DF8C3A76364756E2164756E21
3329
+ //# debugId=767B2279906CCA3764756E2164756E21