@madarco/agentbox 0.13.0 → 0.15.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 (74) hide show
  1. package/CHANGELOG.md +125 -0
  2. package/README.md +11 -8
  3. package/dist/{_cloud-attach-HJC672UR.js → _cloud-attach-R6TRWG5L.js} +4 -4
  4. package/dist/{chunk-QYRK5H6Q.js → chunk-43Q5GWP6.js} +108 -56
  5. package/dist/chunk-43Q5GWP6.js.map +1 -0
  6. package/dist/{chunk-ECLLV5JH.js → chunk-72CJTXN6.js} +156 -5
  7. package/dist/chunk-72CJTXN6.js.map +1 -0
  8. package/dist/{chunk-R5XIDQFR.js → chunk-BKU34KYY.js} +170 -6
  9. package/dist/chunk-BKU34KYY.js.map +1 -0
  10. package/dist/{chunk-4NQXNQ53.js → chunk-E7CHS7ZR.js} +168 -58
  11. package/dist/chunk-E7CHS7ZR.js.map +1 -0
  12. package/dist/chunk-MCOU6CZS.js +346 -0
  13. package/dist/chunk-MCOU6CZS.js.map +1 -0
  14. package/dist/{chunk-B4QG2MCW.js → chunk-MLMFNN4T.js} +762 -483
  15. package/dist/chunk-MLMFNN4T.js.map +1 -0
  16. package/dist/{chunk-2LF5YILI.js → chunk-RSKG7AFU.js} +80 -6
  17. package/dist/chunk-RSKG7AFU.js.map +1 -0
  18. package/dist/{chunk-SNTHHWKY.js → chunk-XKH7NTT7.js} +80 -22
  19. package/dist/chunk-XKH7NTT7.js.map +1 -0
  20. package/dist/{dist-7KVUIKJX.js → dist-AGTIA7AD.js} +37 -226
  21. package/dist/dist-AGTIA7AD.js.map +1 -0
  22. package/dist/{dist-OPIBZ7XM.js → dist-FIFEFKJ7.js} +14 -69
  23. package/dist/dist-FIFEFKJ7.js.map +1 -0
  24. package/dist/dist-JZ3XO6EB.js +662 -0
  25. package/dist/dist-JZ3XO6EB.js.map +1 -0
  26. package/dist/{dist-OG6NW6SM.js → dist-OGJGZETZ.js} +5 -3
  27. package/dist/{dist-JAN5VABY.js → dist-S4XR4ACV.js} +25 -177
  28. package/dist/dist-S4XR4ACV.js.map +1 -0
  29. package/dist/index.js +2229 -1314
  30. package/dist/index.js.map +1 -1
  31. package/dist/{prepared-state-MQHD3M5F-KE4DT3GX.js → prepared-state-MQHD3M5F-Q27AZU53.js} +2 -2
  32. package/package.json +6 -4
  33. package/runtime/docker/Dockerfile.box +21 -26
  34. package/runtime/docker/apps/cli/share/agentbox-setup/SKILL.md +67 -1
  35. package/runtime/docker/packages/ctl/dist/bin.cjs +361 -43
  36. package/runtime/docker/packages/sandbox-docker/scripts/agentbox-vnc-start +17 -6
  37. package/runtime/docker/packages/sandbox-docker/scripts/chromium-resolver +57 -0
  38. package/runtime/docker/packages/sandbox-docker/scripts/claude-managed-settings.json +2 -1
  39. package/runtime/e2b/agentbox-checkpoint-cleanup +52 -0
  40. package/runtime/e2b/agentbox-codex-hooks.json +68 -0
  41. package/runtime/e2b/agentbox-open +28 -0
  42. package/runtime/e2b/agentbox-setup-skill.md +263 -0
  43. package/runtime/e2b/agentbox-vnc-start +102 -0
  44. package/runtime/e2b/attach-helper.cjs +167 -0
  45. package/runtime/e2b/claude-managed-settings.json +116 -0
  46. package/runtime/e2b/ctl.cjs +24158 -0
  47. package/runtime/e2b/custom-system-CLAUDE.md +46 -0
  48. package/runtime/e2b/gh-shim +344 -0
  49. package/runtime/e2b/git-shim +131 -0
  50. package/runtime/e2b/scripts/build-template.sh +295 -0
  51. package/runtime/hetzner/agentbox-setup-skill.md +67 -1
  52. package/runtime/hetzner/agentbox-vnc-start +17 -6
  53. package/runtime/hetzner/claude-managed-settings.json +2 -1
  54. package/runtime/hetzner/ctl.cjs +361 -43
  55. package/runtime/relay/bin.cjs +380 -233
  56. package/runtime/vercel/agentbox-setup-skill.md +67 -1
  57. package/runtime/vercel/agentbox-vnc-start +17 -6
  58. package/runtime/vercel/claude-managed-settings.json +2 -1
  59. package/runtime/vercel/ctl.cjs +361 -43
  60. package/share/agentbox-setup/SKILL.md +67 -1
  61. package/share/host-skills/agentbox-info/SKILL.md +47 -35
  62. package/dist/chunk-2LF5YILI.js.map +0 -1
  63. package/dist/chunk-4NQXNQ53.js.map +0 -1
  64. package/dist/chunk-B4QG2MCW.js.map +0 -1
  65. package/dist/chunk-ECLLV5JH.js.map +0 -1
  66. package/dist/chunk-QYRK5H6Q.js.map +0 -1
  67. package/dist/chunk-R5XIDQFR.js.map +0 -1
  68. package/dist/chunk-SNTHHWKY.js.map +0 -1
  69. package/dist/dist-7KVUIKJX.js.map +0 -1
  70. package/dist/dist-JAN5VABY.js.map +0 -1
  71. package/dist/dist-OPIBZ7XM.js.map +0 -1
  72. /package/dist/{_cloud-attach-HJC672UR.js.map → _cloud-attach-R6TRWG5L.js.map} +0 -0
  73. /package/dist/{dist-OG6NW6SM.js.map → dist-OGJGZETZ.js.map} +0 -0
  74. /package/dist/{prepared-state-MQHD3M5F-KE4DT3GX.js.map → prepared-state-MQHD3M5F-Q27AZU53.js.map} +0 -0
@@ -3344,15 +3344,15 @@ var require_windows = __commonJS({
3344
3344
  }
3345
3345
  return false;
3346
3346
  }
3347
- function checkStat(stat2, path6, options) {
3348
- if (!stat2.isSymbolicLink() && !stat2.isFile()) {
3347
+ function checkStat(stat3, path6, options) {
3348
+ if (!stat3.isSymbolicLink() && !stat3.isFile()) {
3349
3349
  return false;
3350
3350
  }
3351
3351
  return checkPathExt(path6, options);
3352
3352
  }
3353
3353
  function isexe(path6, options, cb) {
3354
- fs.stat(path6, function(er, stat2) {
3355
- cb(er, er ? false : checkStat(stat2, path6, options));
3354
+ fs.stat(path6, function(er, stat3) {
3355
+ cb(er, er ? false : checkStat(stat3, path6, options));
3356
3356
  });
3357
3357
  }
3358
3358
  function sync(path6, options) {
@@ -3369,20 +3369,20 @@ var require_mode = __commonJS({
3369
3369
  isexe.sync = sync;
3370
3370
  var fs = require("fs");
3371
3371
  function isexe(path6, options, cb) {
3372
- fs.stat(path6, function(er, stat2) {
3373
- cb(er, er ? false : checkStat(stat2, options));
3372
+ fs.stat(path6, function(er, stat3) {
3373
+ cb(er, er ? false : checkStat(stat3, options));
3374
3374
  });
3375
3375
  }
3376
3376
  function sync(path6, options) {
3377
3377
  return checkStat(fs.statSync(path6), options);
3378
3378
  }
3379
- function checkStat(stat2, options) {
3380
- return stat2.isFile() && checkMode(stat2, options);
3379
+ function checkStat(stat3, options) {
3380
+ return stat3.isFile() && checkMode(stat3, options);
3381
3381
  }
3382
- function checkMode(stat2, options) {
3383
- var mod = stat2.mode;
3384
- var uid = stat2.uid;
3385
- var gid = stat2.gid;
3382
+ function checkMode(stat3, options) {
3383
+ var mod = stat3.mode;
3384
+ var uid = stat3.uid;
3385
+ var gid = stat3.gid;
3386
3386
  var myUid = options.uid !== void 0 ? options.uid : process.getuid && process.getuid();
3387
3387
  var myGid = options.gid !== void 0 ? options.gid : process.getgid && process.getgid();
3388
3388
  var u2 = parseInt("100", 8);
@@ -3801,7 +3801,7 @@ var require_cross_spawn = __commonJS({
3801
3801
  var cp = require("child_process");
3802
3802
  var parse = require_parse();
3803
3803
  var enoent = require_enoent();
3804
- function spawn10(command, args, options) {
3804
+ function spawn11(command, args, options) {
3805
3805
  const parsed = parse(command, args, options);
3806
3806
  const spawned = cp.spawn(parsed.command, parsed.args, parsed.options);
3807
3807
  enoent.hookChildProcess(spawned, parsed);
@@ -3813,8 +3813,8 @@ var require_cross_spawn = __commonJS({
3813
3813
  result.error = result.error || enoent.verifyENOENTSync(result.status, parsed);
3814
3814
  return result;
3815
3815
  }
3816
- module2.exports = spawn10;
3817
- module2.exports.spawn = spawn10;
3816
+ module2.exports = spawn11;
3817
+ module2.exports.spawn = spawn11;
3818
3818
  module2.exports.sync = spawnSync2;
3819
3819
  module2.exports._parse = parse;
3820
3820
  module2.exports._enoent = enoent;
@@ -11566,20 +11566,28 @@ async function postRpcAndExit(method, params, opts = {}) {
11566
11566
  }
11567
11567
 
11568
11568
  // src/commands/cp.ts
11569
+ function collectExclude(val, acc) {
11570
+ acc.push(val);
11571
+ return acc;
11572
+ }
11573
+ function buildCpParams(boxPath, hostPath, opts) {
11574
+ const params = { boxPath, hostPath };
11575
+ if (opts.recursive === false) params.recursive = false;
11576
+ if (opts.exclude.length > 0) params.exclude = opts.exclude;
11577
+ if (opts.defaultExcludes === false) params.defaultExcludes = false;
11578
+ if (opts.yes) params.yes = true;
11579
+ return params;
11580
+ }
11569
11581
  var cpCommand = new Command("cp").description("Copy a file/dir between this box and the host (gated by user prompt on the host wrapper)").addCommand(
11570
- new Command("toHost").description("Copy box:<boxPath> -> host:<hostPath>").argument("<boxPath>", "source path inside the container").argument("<hostPath>", "destination path on the host").option("--no-recursive", "reserved; current implementation is always recursive (docker cp -a)").action(async (boxPath, hostPath, opts) => {
11571
- const params = { boxPath, hostPath };
11572
- if (opts.recursive === false) params.recursive = false;
11573
- const code = await postRpcAndExit("cp.toHost", params, {
11582
+ new Command("toHost").description("Copy box:<boxPath> -> host:<hostPath>").argument("<boxPath>", "source path inside the container").argument("<hostPath>", "destination path on the host").option("--no-recursive", "reserved; current implementation is always recursive (docker cp -a)").option("--exclude <pattern>", "exclude paths matching <pattern> (repeatable)", collectExclude, []).option("--no-default-excludes", "keep heavy dirs the host drops by default (.git, node_modules, ...)").option("-y, --yes", "copy even if the source is over the host size limit").action(async (boxPath, hostPath, opts) => {
11583
+ const code = await postRpcAndExit("cp.toHost", buildCpParams(boxPath, hostPath, opts), {
11574
11584
  errorPrefix: "agentbox-ctl cp"
11575
11585
  });
11576
11586
  process.exit(code);
11577
11587
  })
11578
11588
  ).addCommand(
11579
- new Command("fromHost").description("Copy host:<hostPath> -> box:<boxPath>").argument("<hostPath>", "source path on the host").argument("<boxPath>", "destination path inside the container").option("--no-recursive", "reserved; current implementation is always recursive (docker cp -a)").action(async (hostPath, boxPath, opts) => {
11580
- const params = { boxPath, hostPath };
11581
- if (opts.recursive === false) params.recursive = false;
11582
- const code = await postRpcAndExit("cp.fromHost", params, {
11589
+ new Command("fromHost").description("Copy host:<hostPath> -> box:<boxPath>").argument("<hostPath>", "source path on the host").argument("<boxPath>", "destination path inside the container").option("--no-recursive", "reserved; current implementation is always recursive (docker cp -a)").option("--exclude <pattern>", "exclude paths matching <pattern> (repeatable)", collectExclude, []).option("--no-default-excludes", "keep heavy dirs the host drops by default (.git, node_modules, ...)").option("-y, --yes", "copy even if the source is over the host size limit").action(async (hostPath, boxPath, opts) => {
11590
+ const code = await postRpcAndExit("cp.fromHost", buildCpParams(boxPath, hostPath, opts), {
11583
11591
  errorPrefix: "agentbox-ctl cp"
11584
11592
  });
11585
11593
  process.exit(code);
@@ -12239,11 +12247,11 @@ var replacements = Object.entries(specialMainSymbols);
12239
12247
  // ../../node_modules/.pnpm/yoctocolors@2.1.2/node_modules/yoctocolors/base.js
12240
12248
  var import_node_tty = __toESM(require("tty"), 1);
12241
12249
  var hasColors = import_node_tty.default?.WriteStream?.prototype?.hasColors?.() ?? false;
12242
- var format = (open, close) => {
12250
+ var format = (open2, close) => {
12243
12251
  if (!hasColors) {
12244
12252
  return (input) => input;
12245
12253
  }
12246
- const openCode = `\x1B[${open}m`;
12254
+ const openCode = `\x1B[${open2}m`;
12247
12255
  const closeCode = `\x1B[${close}m`;
12248
12256
  return (input) => {
12249
12257
  const string = input + "";
@@ -18468,12 +18476,121 @@ var import_path2 = require("path");
18468
18476
  var import_yaml2 = __toESM(require_dist(), 1);
18469
18477
  var import_path3 = require("path");
18470
18478
  var import_yaml3 = __toESM(require_dist(), 1);
18479
+ var BUILT_IN_DEFAULTS = {
18480
+ box: {
18481
+ provider: "docker",
18482
+ hostSnapshot: void 0,
18483
+ defaultCheckpoint: "",
18484
+ defaultCheckpointDocker: "",
18485
+ defaultCheckpointDaytona: "",
18486
+ defaultCheckpointHetzner: "",
18487
+ defaultCheckpointVercel: "",
18488
+ defaultCheckpointE2b: "",
18489
+ size: "",
18490
+ sizeDocker: "",
18491
+ sizeDaytona: "",
18492
+ sizeHetzner: "",
18493
+ sizeVercel: "",
18494
+ sizeE2b: "",
18495
+ withPlaywright: false,
18496
+ withEnv: false,
18497
+ resyncOnStart: true,
18498
+ vnc: true,
18499
+ autoApproveHostActions: false,
18500
+ isolateClaudeConfig: false,
18501
+ isolateCodexConfig: false,
18502
+ isolateOpencodeConfig: false,
18503
+ image: "agentbox/box:dev",
18504
+ imageDocker: "",
18505
+ imageDaytona: "",
18506
+ imageHetzner: "",
18507
+ imageVercel: "",
18508
+ imageE2b: "",
18509
+ // Mirrors BOX_IMAGE_REGISTRY in @agentbox/sandbox-docker. Empty disables the
18510
+ // registry pull (always build the docker base image locally).
18511
+ imageRegistry: "ghcr.io/madarco/agentbox/box",
18512
+ dockerCacheShared: false,
18513
+ memory: 0,
18514
+ cpus: 0,
18515
+ pidsLimit: 0,
18516
+ disk: "",
18517
+ bundleDepth: void 0,
18518
+ vercelVcpus: 2,
18519
+ vercelTimeoutMs: 27e5,
18520
+ vercelNetworkPolicy: "",
18521
+ cpMaxBytes: 100 * 1024 * 1024
18522
+ },
18523
+ checkpoint: {
18524
+ maxLayers: 3
18525
+ },
18526
+ claude: {
18527
+ sessionName: "claude",
18528
+ dangerouslySkipPermissions: true
18529
+ },
18530
+ codex: {
18531
+ sessionName: "codex",
18532
+ dangerouslySkipPermissions: true
18533
+ },
18534
+ opencode: {
18535
+ sessionName: "opencode"
18536
+ },
18537
+ attach: {
18538
+ openIn: "split",
18539
+ cmuxStatus: true
18540
+ },
18541
+ code: {
18542
+ ide: "auto",
18543
+ wait: true,
18544
+ timeoutMs: 12e4,
18545
+ autoTerminals: true
18546
+ },
18547
+ shell: {
18548
+ user: "vscode",
18549
+ login: true,
18550
+ tmux: true
18551
+ },
18552
+ engine: {
18553
+ kind: "auto"
18554
+ },
18555
+ browser: {
18556
+ default: "agent-browser"
18557
+ },
18558
+ relay: {
18559
+ port: 8787
18560
+ },
18561
+ vnc: {
18562
+ containerPort: 6080
18563
+ },
18564
+ portless: {
18565
+ enabled: void 0,
18566
+ stateDir: ""
18567
+ },
18568
+ autopause: {
18569
+ enabled: true,
18570
+ maxRunningBoxes: 5,
18571
+ idleMinutes: 5
18572
+ },
18573
+ queue: {
18574
+ enabled: true,
18575
+ maxConcurrent: 5,
18576
+ maxWorking: 0,
18577
+ idleGraceSeconds: 15,
18578
+ openIn: "none"
18579
+ },
18580
+ cloud: {
18581
+ useCurrentBranch: false
18582
+ },
18583
+ maintenance: {
18584
+ pruneProjectConfigs: true,
18585
+ pruneProjectConfigsEvery: 50
18586
+ }
18587
+ };
18471
18588
  var KEY_REGISTRY = [
18472
18589
  {
18473
18590
  key: "box.provider",
18474
18591
  type: "enum",
18475
- enumValues: ["docker", "daytona", "hetzner", "vercel"],
18476
- description: "Sandbox backend new boxes are created on: local Docker containers, Daytona Cloud sandboxes, Hetzner Cloud VPSes, or Vercel Sandboxes."
18592
+ enumValues: ["docker", "daytona", "hetzner", "vercel", "e2b"],
18593
+ description: "Sandbox backend new boxes are created on: local Docker containers, Daytona Cloud sandboxes, Hetzner Cloud VPSes, Vercel Sandboxes, or E2B microVMs."
18477
18594
  },
18478
18595
  {
18479
18596
  key: "box.hostSnapshot",
@@ -18509,6 +18626,12 @@ var KEY_REGISTRY = [
18509
18626
  description: "Per-provider override of `box.defaultCheckpoint` for vercel. Wins over the global when set; set via `agentbox checkpoint set-default --provider vercel`.",
18510
18627
  advanced: true
18511
18628
  },
18629
+ {
18630
+ key: "box.defaultCheckpointE2b",
18631
+ type: "string",
18632
+ description: "Per-provider override of `box.defaultCheckpoint` for e2b. Wins over the global when set; set via `agentbox checkpoint set-default --provider e2b`.",
18633
+ advanced: true
18634
+ },
18512
18635
  {
18513
18636
  key: "box.size",
18514
18637
  type: "string",
@@ -18538,6 +18661,12 @@ var KEY_REGISTRY = [
18538
18661
  description: "Per-provider override of `box.size` for vercel. Reserved \u2014 vercel sizing is controlled via `box.vercelVcpus`.",
18539
18662
  advanced: true
18540
18663
  },
18664
+ {
18665
+ key: "box.sizeE2b",
18666
+ type: "string",
18667
+ description: "Per-provider override of `box.size` for e2b. Reserved \u2014 e2b sizing is template-level (set at `agentbox prepare --provider e2b` time via --vcpus / --memory).",
18668
+ advanced: true
18669
+ },
18541
18670
  {
18542
18671
  key: "checkpoint.maxLayers",
18543
18672
  type: "int",
@@ -18564,6 +18693,11 @@ var KEY_REGISTRY = [
18564
18693
  type: "bool",
18565
18694
  description: "Run the per-box Xvnc + noVNC stack."
18566
18695
  },
18696
+ {
18697
+ key: "box.autoApproveHostActions",
18698
+ type: "bool",
18699
+ description: "Auto-approve host-action confirmations (git push, cp host<->box, gh PR writes, checkpoint) for this box without an interactive prompt. Off by default; intended for unattended orchestration of trusted boxes. Each auto-approval is recorded as a relay event (visible in `agentbox agent` / the dashboard)."
18700
+ },
18567
18701
  {
18568
18702
  key: "box.isolateClaudeConfig",
18569
18703
  type: "bool",
@@ -18609,6 +18743,12 @@ var KEY_REGISTRY = [
18609
18743
  description: "Per-provider override of `box.image` for vercel (snapshot id, e.g. `snap_\u2026`). Written by `agentbox prepare --provider vercel`.",
18610
18744
  advanced: true
18611
18745
  },
18746
+ {
18747
+ key: "box.imageE2b",
18748
+ type: "string",
18749
+ description: "Per-provider override of `box.image` for e2b (template id or `name:tag`, e.g. `agentbox-base:latest`). Written by `agentbox prepare --provider e2b`.",
18750
+ advanced: true
18751
+ },
18612
18752
  {
18613
18753
  key: "box.imageRegistry",
18614
18754
  type: "string",
@@ -18656,6 +18796,12 @@ var KEY_REGISTRY = [
18656
18796
  type: "int",
18657
18797
  description: "Max session length (ms) for new --provider vercel boxes before the VM auto-snapshots; persistent mode auto-resumes on the next call. Default 2700000 (45 min, the Hobby ceiling). Vercel-only."
18658
18798
  },
18799
+ {
18800
+ key: "box.cpMaxBytes",
18801
+ type: "int",
18802
+ description: "Max bytes a single host\u2192box copy may transfer after excludes, shared by `agentbox cp` (blocked with a size breakdown unless --yes) and each `carry:` entry (rejected at resolve time). Default 104857600 (100 MiB).",
18803
+ advanced: true
18804
+ },
18659
18805
  {
18660
18806
  key: "box.vercelNetworkPolicy",
18661
18807
  type: "string",
@@ -18803,6 +18949,12 @@ var KEY_REGISTRY = [
18803
18949
  type: "int",
18804
18950
  description: "Seconds an agent must stay non-working before it frees its working slot (debounce against brief idle flaps between turns). Only used when queue.maxWorking > 0."
18805
18951
  },
18952
+ {
18953
+ key: "queue.openIn",
18954
+ type: "enum",
18955
+ enumValues: ["none", "split", "window", "tab"],
18956
+ description: "When a background `-i` job finishes creating its box, where the host relay opens an attached terminal onto it: `none` (default \u2014 open nothing, just queue), `split`, `window`, or `tab`. Honored only when the submitting shell runs inside tmux, cmux, or iTerm2 (the targeting is captured at submit time). Under cmux, `split` splits the pane you submitted from (falling back to the parent workspace, then a new workspace), `tab` adds a tab in the parent workspace, and `window` opens a separate workspace; iTerm2 opens relative to the frontmost window. Unlike `attach.openIn` there is no `same` mode \u2014 the box is created asynchronously, so it is always a fresh terminal."
18957
+ },
18806
18958
  {
18807
18959
  key: "cloud.useCurrentBranch",
18808
18960
  type: "bool",
@@ -18990,6 +19142,21 @@ var EventBuffer = class {
18990
19142
  };
18991
19143
  var PendingPrompts = class {
18992
19144
  entries = /* @__PURE__ */ new Map();
19145
+ autoApprove = null;
19146
+ /** Install the per-box auto-approve policy (relay server, once at startup). */
19147
+ setAutoApprovePolicy(policy) {
19148
+ this.autoApprove = policy;
19149
+ }
19150
+ /**
19151
+ * True when this box opted into `box.autoApproveHostActions`. Records the
19152
+ * bypass to the audit sink as a side effect so the caller short-circuits
19153
+ * with a trail. Returns false when no policy is installed.
19154
+ */
19155
+ consumeAutoApprove(boxId, params) {
19156
+ if (!this.autoApprove || !this.autoApprove.shouldAutoApprove(boxId)) return false;
19157
+ this.autoApprove.audit(boxId, params);
19158
+ return true;
19159
+ }
18993
19160
  add(boxId, ev) {
18994
19161
  return new Promise((resolve2) => {
18995
19162
  this.entries.set(ev.id, {
@@ -19076,6 +19243,9 @@ async function askPrompt(prompts, subscribers, boxId, params, opts) {
19076
19243
  if (process.env.AGENTBOX_PROMPT === "off") {
19077
19244
  return { answer: "y" };
19078
19245
  }
19246
+ if (prompts.consumeAutoApprove(boxId, params)) {
19247
+ return { answer: "y" };
19248
+ }
19079
19249
  const ev = { id: (0, import_crypto2.randomUUID)(), ...params };
19080
19250
  const promise = prompts.add(boxId, ev);
19081
19251
  subscribers.broadcast(boxId, "prompt-ask", ev);
@@ -19544,6 +19714,10 @@ async function resolveCloudBackend(name) {
19544
19714
  const pkg = "@agentbox/sandbox-vercel";
19545
19715
  return loadCloudBackend(pkg, async () => (await import(pkg)).vercelBackend);
19546
19716
  }
19717
+ if (name === "e2b") {
19718
+ const pkg = "@agentbox/sandbox-e2b";
19719
+ return loadCloudBackend(pkg, async () => (await import(pkg)).e2bBackend);
19720
+ }
19547
19721
  throw new Error(`no host executor for cloud backend '${name}'`);
19548
19722
  }
19549
19723
  async function loadCloudBackend(pkg, load2) {
@@ -20218,6 +20392,21 @@ function createRelayServer(opts) {
20218
20392
  const prompts = new PendingPrompts();
20219
20393
  const subscribers = new PromptSubscribers();
20220
20394
  const notices = new BoxNotices(subscribers);
20395
+ prompts.setAutoApprovePolicy({
20396
+ shouldAutoApprove: (boxId) => registry.get(boxId)?.autoApproveHostActions === true,
20397
+ audit: (boxId, params) => {
20398
+ events.append({
20399
+ boxId,
20400
+ type: "host-action-auto-approved",
20401
+ payload: {
20402
+ command: params.context?.command,
20403
+ argv: params.context?.argv,
20404
+ message: params.message
20405
+ }
20406
+ });
20407
+ log(`auto-approved host action for ${boxId}: ${params.context?.command ?? params.message}`);
20408
+ }
20409
+ });
20221
20410
  const hostInitiatedTokens = new HostInitiatedTokens();
20222
20411
  let queuePoke = null;
20223
20412
  const host = opts.host ?? "0.0.0.0";
@@ -20405,11 +20594,22 @@ function createRelayServer(opts) {
20405
20594
  send(res, 400, { error: "cp.* requires {boxPath, hostPath} strings" });
20406
20595
  return;
20407
20596
  }
20597
+ if (params.exclude !== void 0 && (!Array.isArray(params.exclude) || params.exclude.some((p) => typeof p !== "string"))) {
20598
+ send(res, 400, { error: "cp.* exclude must be an array of strings" });
20599
+ return;
20600
+ }
20408
20601
  const direction = body.method === "cp.toHost" ? "box -> host" : "host -> box";
20602
+ const pathDetail = body.method === "cp.toHost" ? `${params.boxPath} -> ${params.hostPath}` : `${params.hostPath} -> ${params.boxPath}`;
20603
+ const detailParts = [pathDetail];
20604
+ if (params.exclude && params.exclude.length > 0) {
20605
+ detailParts.push(`exclude: ${params.exclude.join(", ")}`);
20606
+ }
20607
+ if (params.defaultExcludes === false) detailParts.push("(default excludes off)");
20608
+ if (params.yes) detailParts.push("(over size limit \u2014 confirmed)");
20409
20609
  const verdict = await askPrompt(prompts, subscribers, reg.boxId, {
20410
20610
  kind: "confirm",
20411
20611
  message: `Allow cp (${direction}) on ${reg.name}?`,
20412
- detail: body.method === "cp.toHost" ? `${params.boxPath} -> ${params.hostPath}` : `${params.hostPath} -> ${params.boxPath}`,
20612
+ detail: detailParts.join("\n"),
20413
20613
  defaultAnswer: "n",
20414
20614
  context: {
20415
20615
  command: body.method,
@@ -20575,7 +20775,8 @@ function createRelayServer(opts) {
20575
20775
  worktrees,
20576
20776
  previewUrl: typeof body.previewUrl === "string" && body.previewUrl.length > 0 ? body.previewUrl : void 0,
20577
20777
  previewToken: typeof body.previewToken === "string" && body.previewToken.length > 0 ? body.previewToken : void 0,
20578
- bridgeToken: typeof body.bridgeToken === "string" && body.bridgeToken.length > 0 ? body.bridgeToken : void 0
20778
+ bridgeToken: typeof body.bridgeToken === "string" && body.bridgeToken.length > 0 ? body.bridgeToken : void 0,
20779
+ autoApproveHostActions: body.autoApproveHostActions === true
20579
20780
  };
20580
20781
  registry.register(reg);
20581
20782
  log(
@@ -20683,6 +20884,15 @@ function createRelayServer(opts) {
20683
20884
  send(res, 200, { boxes: redacted });
20684
20885
  return;
20685
20886
  }
20887
+ if (route === "GET /admin/prompts") {
20888
+ const boxId = url.searchParams.get("boxId") ?? "";
20889
+ if (boxId.length === 0) {
20890
+ send(res, 400, { error: "missing boxId query param" });
20891
+ return;
20892
+ }
20893
+ send(res, 200, { prompts: prompts.forBox(boxId) });
20894
+ return;
20895
+ }
20686
20896
  if (route === "GET /admin/prompts/stream") {
20687
20897
  const boxId = url.searchParams.get("boxId") ?? "";
20688
20898
  if (boxId.length === 0) {
@@ -21000,7 +21210,11 @@ async function handleCpRpc(reg, method, params) {
21000
21210
  };
21001
21211
  }
21002
21212
  const boxRef = `${reg.name}:${params.boxPath}`;
21003
- const argv = method === "cp.toHost" ? [process.execPath, entry, "cp", boxRef, params.hostPath] : [process.execPath, entry, "cp", params.hostPath, boxRef];
21213
+ const flags = [];
21214
+ for (const pat of params.exclude ?? []) flags.push("--exclude", pat);
21215
+ if (params.defaultExcludes === false) flags.push("--no-default-excludes");
21216
+ if (params.yes) flags.push("--yes");
21217
+ const argv = method === "cp.toHost" ? [process.execPath, entry, "cp", boxRef, params.hostPath, ...flags] : [process.execPath, entry, "cp", params.hostPath, boxRef, ...flags];
21004
21218
  return runHostCommand(argv, CP_RPC_TIMEOUT_MS);
21005
21219
  }
21006
21220
  async function handleDownloadRpc(reg, kind) {
@@ -21097,6 +21311,8 @@ async function startRelayServer(opts) {
21097
21311
  return handle;
21098
21312
  }
21099
21313
  var QUEUE_DIR = (0, import_path6.join)(STATE_DIR, "queue");
21314
+ var TERMINAL_RETENTION_MS = 60 * 60 * 1e3;
21315
+ var SWEEP_INTERVAL_MS = 60 * 1e3;
21100
21316
  var QUEUE_LOGS_DIR = (0, import_path6.join)(STATE_DIR, "logs");
21101
21317
 
21102
21318
  // src/config.ts
@@ -21615,8 +21831,68 @@ function defaultCapturePane(sessionName) {
21615
21831
  });
21616
21832
  }
21617
21833
 
21618
- // src/supervisor.ts
21834
+ // src/claude-scraper.ts
21619
21835
  var import_node_child_process8 = require("child_process");
21836
+ var DEFAULT_INTERVAL_MS2 = 1500;
21837
+ var DEFAULT_SESSION2 = "claude";
21838
+ var BOTTOM_LINES = 25;
21839
+ var WORKING_GUARD = /\besc to interrupt\b|ctrl-?c to (stop|interrupt)/i;
21840
+ var WAITING_PATTERNS = [
21841
+ /❯\s*\d+\.\s/,
21842
+ // the selector arrow on a numbered option — the Claude select menu
21843
+ /Do you want to proceed\?/i,
21844
+ /Would you like to proceed\?/i,
21845
+ /\b\d+\.\s+Yes\b/,
21846
+ // a "N. Yes" option line
21847
+ /\b(wants to (use|run|access)|Allow .* to (use|run|access))/i
21848
+ // MCP / tool trust dialog
21849
+ ];
21850
+ function startClaudeScraper(opts) {
21851
+ const sessionName = opts.sessionName ?? DEFAULT_SESSION2;
21852
+ const intervalMs = opts.intervalMs ?? DEFAULT_INTERVAL_MS2;
21853
+ const capture = opts.capturePane ?? defaultCapturePane2;
21854
+ let stopped = false;
21855
+ const tick = async () => {
21856
+ if (stopped) return;
21857
+ try {
21858
+ const pane = await capture(sessionName);
21859
+ if (pane === null) return;
21860
+ if (matchWaiting(pane)) opts.reporter.markScreenWaiting();
21861
+ } catch {
21862
+ }
21863
+ };
21864
+ const timer = setInterval(() => void tick(), intervalMs);
21865
+ timer.unref();
21866
+ void tick();
21867
+ return {
21868
+ stop() {
21869
+ stopped = true;
21870
+ clearInterval(timer);
21871
+ }
21872
+ };
21873
+ }
21874
+ function matchWaiting(pane) {
21875
+ const region = pane.split("\n").slice(-BOTTOM_LINES).join("\n");
21876
+ if (WORKING_GUARD.test(region)) return false;
21877
+ return WAITING_PATTERNS.some((re) => re.test(region));
21878
+ }
21879
+ function defaultCapturePane2(sessionName) {
21880
+ return new Promise((resolve2) => {
21881
+ const child = (0, import_node_child_process8.spawn)("tmux", ["capture-pane", "-p", "-t", sessionName], {
21882
+ stdio: ["ignore", "pipe", "ignore"]
21883
+ });
21884
+ let stdout = "";
21885
+ child.stdout.on("data", (b) => stdout += b.toString("utf8"));
21886
+ child.on("error", () => resolve2(null));
21887
+ child.on("close", (code) => {
21888
+ if (code === 0) resolve2(stdout);
21889
+ else resolve2(null);
21890
+ });
21891
+ });
21892
+ }
21893
+
21894
+ // src/supervisor.ts
21895
+ var import_node_child_process9 = require("child_process");
21620
21896
  var import_node_events15 = require("events");
21621
21897
  var import_node_fs7 = require("fs");
21622
21898
  var import_promises18 = require("fs/promises");
@@ -21882,7 +22158,7 @@ var cachedLoginPath;
21882
22158
  function loginShellPath() {
21883
22159
  if (cachedLoginPath !== void 0) return cachedLoginPath;
21884
22160
  try {
21885
- const out = (0, import_node_child_process8.execFileSync)("bash", ["-lc", 'printf %s "$PATH"'], {
22161
+ const out = (0, import_node_child_process9.execFileSync)("bash", ["-lc", 'printf %s "$PATH"'], {
21886
22162
  encoding: "utf8",
21887
22163
  timeout: 5e3
21888
22164
  }).trim();
@@ -21897,7 +22173,7 @@ var ServiceRunner = class extends import_node_events15.EventEmitter {
21897
22173
  super();
21898
22174
  this.spec = spec;
21899
22175
  this.opts = opts;
21900
- this.spawnFn = opts.spawn ?? import_node_child_process8.spawn;
22176
+ this.spawnFn = opts.spawn ?? import_node_child_process9.spawn;
21901
22177
  this.setTimer = opts.setTimer ?? ((fn, ms) => setTimeout(fn, ms));
21902
22178
  this.clearTimer = opts.clearTimer ?? ((h2) => {
21903
22179
  clearTimeout(h2);
@@ -22128,7 +22404,7 @@ var TaskRunner = class extends import_node_events15.EventEmitter {
22128
22404
  super();
22129
22405
  this.spec = spec;
22130
22406
  this.opts = opts;
22131
- this.spawnFn = opts.spawn ?? import_node_child_process8.spawn;
22407
+ this.spawnFn = opts.spawn ?? import_node_child_process9.spawn;
22132
22408
  }
22133
22409
  spec;
22134
22410
  opts;
@@ -22301,6 +22577,20 @@ var Supervisor = class extends import_node_events15.EventEmitter {
22301
22577
  }
22302
22578
  return out;
22303
22579
  }
22580
+ /**
22581
+ * Set of service names that declare a `ready_when` probe of any kind (port or
22582
+ * log_match). A probed service is only "up" once it reaches `ready`; an
22583
+ * unprobed one is up at `running`. The status reporter surfaces this so the
22584
+ * host can tell a warming-up `running` service from a settled one.
22585
+ */
22586
+ probedServices() {
22587
+ const out = /* @__PURE__ */ new Set();
22588
+ for (const u2 of this.units.values()) {
22589
+ if (u2.kind !== "service") continue;
22590
+ if (u2.spec.readyWhen) out.add(u2.name);
22591
+ }
22592
+ return out;
22593
+ }
22304
22594
  /**
22305
22595
  * Map of service name -> `expose:` mapping, for the (at most one) service
22306
22596
  * that declares it. The status reporter surfaces this so the host knows the
@@ -22677,10 +22967,10 @@ var import_promises19 = require("fs/promises");
22677
22967
  var import_node_path7 = require("path");
22678
22968
 
22679
22969
  // src/status-reporter.ts
22680
- var import_node_child_process10 = require("child_process");
22970
+ var import_node_child_process11 = require("child_process");
22681
22971
 
22682
22972
  // src/tmux.ts
22683
- var import_node_child_process9 = require("child_process");
22973
+ var import_node_child_process10 = require("child_process");
22684
22974
  var import_node_os4 = require("os");
22685
22975
  var MAX_TITLE_LEN = 120;
22686
22976
  function sanitizePaneTitle(raw, ctx) {
@@ -22694,7 +22984,7 @@ function sanitizePaneTitle(raw, ctx) {
22694
22984
  }
22695
22985
  function runTool(cmd, args) {
22696
22986
  return new Promise((resolve2) => {
22697
- const child = (0, import_node_child_process9.spawn)(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
22987
+ const child = (0, import_node_child_process10.spawn)(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
22698
22988
  let stdout = "";
22699
22989
  let stderr = "";
22700
22990
  child.stdout.on("data", (b) => stdout += b.toString("utf8"));
@@ -22791,6 +23081,23 @@ var StatusReporter = class {
22791
23081
  }
22792
23082
  this.schedulePush();
22793
23083
  }
23084
+ /**
23085
+ * Screen-scraper safety net: promote a *stuck* `working` to `waiting` when the
23086
+ * Claude tmux pane shows a prompt the hooks missed (MCP tool dialogs have no
23087
+ * hook; the `Notification:permission_prompt` hook can fire late or drop).
23088
+ * Deliberately promote-ONLY — it acts solely when the current state is
23089
+ * `working`, so it never clobbers the richer hook-driven `end-plan`/`question`
23090
+ * (sticky) or `idle`/`compacting`/`error`. The next real hook
23091
+ * (`UserPromptSubmit`/`PreToolUse`) overwrites `waiting`→`working` when the
23092
+ * agent resumes, so no demote path is needed. Returns true if it promoted.
23093
+ */
23094
+ markScreenWaiting() {
23095
+ if (this.claudeState !== "working") return false;
23096
+ this.claudeState = "waiting";
23097
+ this.claudeUpdatedAt = (/* @__PURE__ */ new Date()).toISOString();
23098
+ this.schedulePush();
23099
+ return true;
23100
+ }
22794
23101
  setCodexState(state) {
22795
23102
  this.codexState = state;
22796
23103
  this.codexUpdatedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -22827,11 +23134,13 @@ var StatusReporter = class {
22827
23134
  }
22828
23135
  async snapshot() {
22829
23136
  const probePorts = this.supervisor.serviceProbePorts();
23137
+ const probed = this.supervisor.probedServices();
22830
23138
  const exposes = this.supervisor.serviceExposes();
22831
23139
  const services = this.supervisor.list().map((s) => ({
22832
23140
  name: s.name,
22833
23141
  state: s.state,
22834
23142
  port: probePorts.get(s.name) ?? null,
23143
+ ...probed.has(s.name) ? { probed: true } : {},
22835
23144
  ...exposes.has(s.name) ? { expose: exposes.get(s.name) } : {}
22836
23145
  }));
22837
23146
  const tasks = this.supervisor.listTasks().map((t) => ({ name: t.name, state: t.state }));
@@ -22887,7 +23196,7 @@ async function collectPorts(supervisor) {
22887
23196
  }
22888
23197
  function run(cmd, args) {
22889
23198
  return new Promise((resolve2) => {
22890
- const child = (0, import_node_child_process10.spawn)(cmd, args, { stdio: ["ignore", "pipe", "ignore"] });
23199
+ const child = (0, import_node_child_process11.spawn)(cmd, args, { stdio: ["ignore", "pipe", "ignore"] });
22891
23200
  let stdout = "";
22892
23201
  child.stdout.on("data", (b) => stdout += b.toString("utf8"));
22893
23202
  child.on("error", () => resolve2({ exitCode: 127, stdout }));
@@ -23224,6 +23533,14 @@ var daemonCommand = new Command("daemon").description("Run the agentbox-ctl supe
23224
23533
  } catch (err) {
23225
23534
  const msg = err instanceof Error ? err.message : String(err);
23226
23535
  process.stderr.write(`agentbox-ctl: codex scraper failed to start: ${msg}
23536
+ `);
23537
+ }
23538
+ let claudeScraper = null;
23539
+ try {
23540
+ claudeScraper = startClaudeScraper({ reporter });
23541
+ } catch (err) {
23542
+ const msg = err instanceof Error ? err.message : String(err);
23543
+ process.stderr.write(`agentbox-ctl: claude scraper failed to start: ${msg}
23227
23544
  `);
23228
23545
  }
23229
23546
  const server = await startServer({
@@ -23301,6 +23618,7 @@ var daemonCommand = new Command("daemon").description("Run the agentbox-ctl supe
23301
23618
  process.stdout.write(`agentbox-ctl: ${signal} \u2014 shutting down
23302
23619
  `);
23303
23620
  if (codexScraper) codexScraper.stop();
23621
+ if (claudeScraper) claudeScraper.stop();
23304
23622
  reporter.stop();
23305
23623
  reporter.flush();
23306
23624
  server.close();
@@ -23462,7 +23780,7 @@ var apiCommand = new Command("api").description(
23462
23780
  var ghCommand = new Command("gh").description("GitHub CLI operations routed through the relay (host `gh` runs with host creds; box never sees a token)").addCommand(buildPrCommand("agentbox-ctl gh pr")).addCommand(buildRunCommand("agentbox-ctl gh run")).addCommand(apiCommand).addCommand(repoCommand);
23463
23781
 
23464
23782
  // src/commands/git.ts
23465
- var import_node_child_process11 = require("child_process");
23783
+ var import_node_child_process12 = require("child_process");
23466
23784
  function hostInitiatedOption() {
23467
23785
  return new Option(
23468
23786
  "--host-initiated-token <token>",
@@ -23478,7 +23796,7 @@ function buildParams(opts, extra) {
23478
23796
  }
23479
23797
  function runLocalGit(args, cwd) {
23480
23798
  return new Promise((resolve2) => {
23481
- const child = (0, import_node_child_process11.spawn)("git", args, { cwd, stdio: "inherit" });
23799
+ const child = (0, import_node_child_process12.spawn)("git", args, { cwd, stdio: "inherit" });
23482
23800
  child.on("close", (code) => resolve2(code ?? 1));
23483
23801
  child.on("error", (err) => {
23484
23802
  process.stderr.write(`agentbox-ctl git: ${String(err.message ?? err)}
@@ -23489,7 +23807,7 @@ function runLocalGit(args, cwd) {
23489
23807
  }
23490
23808
  function hasGitIdentity(cwd) {
23491
23809
  return new Promise((resolve2) => {
23492
- const child = (0, import_node_child_process11.spawn)("git", ["config", "user.email"], {
23810
+ const child = (0, import_node_child_process12.spawn)("git", ["config", "user.email"], {
23493
23811
  cwd,
23494
23812
  stdio: ["ignore", "pipe", "ignore"]
23495
23813
  });
@@ -23591,7 +23909,7 @@ var notifyCommand = new Command("notify").description(
23591
23909
  );
23592
23910
 
23593
23911
  // src/commands/open.ts
23594
- var import_node_child_process12 = require("child_process");
23912
+ var import_node_child_process13 = require("child_process");
23595
23913
  var OPEN_TIMEOUT_MS = 3e4;
23596
23914
  function isHttpUrl(value) {
23597
23915
  try {
@@ -23603,7 +23921,7 @@ function isHttpUrl(value) {
23603
23921
  }
23604
23922
  function openInBoxBrowser(url) {
23605
23923
  return new Promise((resolve2) => {
23606
- const child = (0, import_node_child_process12.spawn)("agent-browser", ["open", "--headed", url], { stdio: "inherit" });
23924
+ const child = (0, import_node_child_process13.spawn)("agent-browser", ["open", "--headed", url], { stdio: "inherit" });
23607
23925
  const timer = setTimeout(() => {
23608
23926
  child.kill("SIGTERM");
23609
23927
  process.stderr.write(