@madarco/agentbox 0.14.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 (40) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/dist/{_cloud-attach-GUBB5RH2.js → _cloud-attach-R6TRWG5L.js} +3 -3
  3. package/dist/{chunk-BYCLD6D6.js → chunk-43Q5GWP6.js} +98 -54
  4. package/dist/chunk-43Q5GWP6.js.map +1 -0
  5. package/dist/{chunk-VATTS2MR.js → chunk-72CJTXN6.js} +2 -2
  6. package/dist/{chunk-TBSIJVSN.js → chunk-E7CHS7ZR.js} +21 -13
  7. package/dist/chunk-E7CHS7ZR.js.map +1 -0
  8. package/dist/{chunk-LDMYHWUS.js → chunk-MCOU6CZS.js} +2 -2
  9. package/dist/{chunk-TCS5HXJX.js → chunk-MLMFNN4T.js} +396 -324
  10. package/dist/chunk-MLMFNN4T.js.map +1 -0
  11. package/dist/{dist-J2IHD5T7.js → dist-AGTIA7AD.js} +3 -3
  12. package/dist/{dist-3IMQNTTV.js → dist-FIFEFKJ7.js} +3 -3
  13. package/dist/{dist-34RKQ74M.js → dist-JZ3XO6EB.js} +4 -4
  14. package/dist/{dist-4DPOL5A7.js → dist-OGJGZETZ.js} +2 -2
  15. package/dist/{dist-57M6ZA7H.js → dist-S4XR4ACV.js} +4 -4
  16. package/dist/index.js +868 -300
  17. package/dist/index.js.map +1 -1
  18. package/package.json +5 -5
  19. package/runtime/docker/apps/cli/share/agentbox-setup/SKILL.md +30 -0
  20. package/runtime/docker/packages/ctl/dist/bin.cjs +321 -27
  21. package/runtime/e2b/agentbox-setup-skill.md +30 -0
  22. package/runtime/e2b/ctl.cjs +321 -27
  23. package/runtime/hetzner/agentbox-setup-skill.md +30 -0
  24. package/runtime/hetzner/ctl.cjs +321 -27
  25. package/runtime/relay/bin.cjs +83 -5
  26. package/runtime/vercel/agentbox-setup-skill.md +30 -0
  27. package/runtime/vercel/ctl.cjs +321 -27
  28. package/share/agentbox-setup/SKILL.md +30 -0
  29. package/share/host-skills/agentbox-info/SKILL.md +21 -1
  30. package/dist/chunk-BYCLD6D6.js.map +0 -1
  31. package/dist/chunk-TBSIJVSN.js.map +0 -1
  32. package/dist/chunk-TCS5HXJX.js.map +0 -1
  33. /package/dist/{_cloud-attach-GUBB5RH2.js.map → _cloud-attach-R6TRWG5L.js.map} +0 -0
  34. /package/dist/{chunk-VATTS2MR.js.map → chunk-72CJTXN6.js.map} +0 -0
  35. /package/dist/{chunk-LDMYHWUS.js.map → chunk-MCOU6CZS.js.map} +0 -0
  36. /package/dist/{dist-J2IHD5T7.js.map → dist-AGTIA7AD.js.map} +0 -0
  37. /package/dist/{dist-3IMQNTTV.js.map → dist-FIFEFKJ7.js.map} +0 -0
  38. /package/dist/{dist-34RKQ74M.js.map → dist-JZ3XO6EB.js.map} +0 -0
  39. /package/dist/{dist-4DPOL5A7.js.map → dist-OGJGZETZ.js.map} +0 -0
  40. /package/dist/{dist-57M6ZA7H.js.map → dist-S4XR4ACV.js.map} +0 -0
package/CHANGELOG.md CHANGED
@@ -9,6 +9,66 @@ Entries are generated from the commit history with `/release-notes` and then
9
9
  hand-reviewed — they describe what changed for someone using the `agentbox`
10
10
  CLI, not the raw commits.
11
11
 
12
+ ## [0.15.0] - 2026-06-05
13
+
14
+ ### Breaking
15
+
16
+ - Carry and `cp` now share a single size cap. The `AGENTBOX_CARRY_MAX_BYTES`
17
+ env var is removed; both the `carry:` step and `agentbox cp` are governed by
18
+ the `box.cpMaxBytes` config key (default 100 MiB, up from carry's old 50 MiB).
19
+ Scripts that set `AGENTBOX_CARRY_MAX_BYTES` no longer have any effect — set
20
+ `box.cpMaxBytes` instead.
21
+
22
+ ### Added
23
+
24
+ - `queue.openIn` config key: when a background `-i` job's box becomes ready,
25
+ optionally open an attached terminal onto it — `split`, `window`, or `tab`
26
+ (default `none`, the previous behavior). Fires only when you submit from
27
+ inside tmux, cmux, or iTerm2.
28
+ - `agentbox cp` (and the `carry:` copy step) now stream the tar instead of
29
+ buffering it, so copies are no longer capped by Node's buffer limit (large
30
+ folders that silently failed with "tar: Write error" now work). Added a
31
+ repeatable `--exclude=<glob|name>` and `--no-default-excludes`; heavy
32
+ regenerable dirs (`.git`, `node_modules`, `dist`, `.next`, `target`, …) are
33
+ excluded by default. Copies larger than `box.cpMaxBytes` are blocked with a
34
+ du-style tree of the biggest folders and a suggested strategy unless `--yes`.
35
+ - `agentbox agent approvals` / `agentbox agent approve`: inspect and answer
36
+ relay host-action confirmations (git push, `cp`, `gh` PR writes, checkpoint)
37
+ from a host orchestrator, instead of hand-curling the loopback endpoint.
38
+ Prompt ids are content-derived, so a prompt that changed since you listed it
39
+ is refused rather than mis-answered. Adds an opt-in per-box
40
+ `box.autoApproveHostActions` (default off, audited) for unattended runs.
41
+ - The attach footer's `(...)` slot now shows aggregate box service status —
42
+ `starting N/M…` while services boot, `service error` on a crash/failed task,
43
+ `ready` once all are up (probe-aware: a `ready_when` service counts as up only
44
+ once its probe passes, so the footer no longer flashes `ready` early). Boxes
45
+ with no services fall back to the agent activity label.
46
+
47
+ ### Changed
48
+
49
+ - `queue.openIn` under cmux: `split` and `tab` queued jobs now open in the
50
+ workspace you submitted from (split targets the original pane, falling back to
51
+ the parent workspace) instead of always spawning a new top-level workspace.
52
+ `window` still opens a separate workspace.
53
+ - `agentbox config set queue.openIn` now warns that the feature only fires
54
+ inside tmux/cmux/iTerm2, and that cmux additionally needs `socketControlMode`
55
+ set to `automation`/`password` plus `cmux reload-config`.
56
+
57
+ ### Fixed
58
+
59
+ - The `carry:` block is now documented in the published `agentbox.yaml` JSON
60
+ schema, so editors and in-box agents that fetch the schema no longer see it as
61
+ invalid or undiscoverable.
62
+ - The stale default-checkpoint recreate prompt now fires for already-configured
63
+ projects too (it was skipped for them, silently booting old base layers), and
64
+ on recreate it reuses the existing `agentbox.yaml` instead of telling the agent
65
+ to regenerate a config that already exists.
66
+ - `agentbox cp` now enforces `box.cpMaxBytes` on single-file uploads, not just
67
+ directories.
68
+ - A supervisor screen-scrape safety net flips a stuck Claude `working` state to
69
+ `waiting` when its hooks miss a prompt (MCP dialogs, dropped notifications), so
70
+ `agent wait-for input-needed` reliably wakes.
71
+
12
72
  ## [0.14.0] - 2026-06-04
13
73
 
14
74
  ### Added
@@ -3,8 +3,8 @@ import {
3
3
  buildCloudAttachInnerCommand,
4
4
  cloudAgentAttach,
5
5
  cloudAgentStartDetached
6
- } from "./chunk-BYCLD6D6.js";
7
- import "./chunk-TCS5HXJX.js";
6
+ } from "./chunk-43Q5GWP6.js";
7
+ import "./chunk-MLMFNN4T.js";
8
8
  import "./chunk-XKH7NTT7.js";
9
9
  import "./chunk-G3H2L3O2.js";
10
10
  export {
@@ -12,4 +12,4 @@ export {
12
12
  cloudAgentAttach,
13
13
  cloudAgentStartDetached
14
14
  };
15
- //# sourceMappingURL=_cloud-attach-GUBB5RH2.js.map
15
+ //# sourceMappingURL=_cloud-attach-R6TRWG5L.js.map
@@ -3,7 +3,7 @@ import {
3
3
  DEFAULT_RELAY_PORT,
4
4
  loadEffectiveConfig,
5
5
  readBoxStatus
6
- } from "./chunk-TCS5HXJX.js";
6
+ } from "./chunk-MLMFNN4T.js";
7
7
 
8
8
  // src/commands/_cloud-attach.ts
9
9
  import { spawn as spawn4 } from "child_process";
@@ -20,26 +20,26 @@ function isKnownProvider(name) {
20
20
  async function getProvider(name) {
21
21
  switch (name) {
22
22
  case "docker": {
23
- const mod = await import("./dist-4DPOL5A7.js");
23
+ const mod = await import("./dist-OGJGZETZ.js");
24
24
  return mod.dockerProvider;
25
25
  }
26
26
  case "daytona": {
27
- const mod = await import("./dist-3IMQNTTV.js");
27
+ const mod = await import("./dist-FIFEFKJ7.js");
28
28
  await mod.ensureDaytonaCredentials();
29
29
  return mod.daytonaProvider;
30
30
  }
31
31
  case "hetzner": {
32
- const mod = await import("./dist-J2IHD5T7.js");
32
+ const mod = await import("./dist-AGTIA7AD.js");
33
33
  await mod.ensureHetznerCredentials();
34
34
  return mod.hetznerProvider;
35
35
  }
36
36
  case "vercel": {
37
- const mod = await import("./dist-57M6ZA7H.js");
37
+ const mod = await import("./dist-S4XR4ACV.js");
38
38
  await mod.ensureVercelCredentials();
39
39
  return mod.vercelProvider;
40
40
  }
41
41
  case "e2b": {
42
- const mod = await import("./dist-34RKQ74M.js");
42
+ const mod = await import("./dist-JZ3XO6EB.js");
43
43
  await mod.ensureE2bCredentials();
44
44
  return mod.e2bProvider;
45
45
  }
@@ -64,6 +64,24 @@ async function providerForCreate(choice) {
64
64
  // src/wrapped-pty/run.ts
65
65
  import { spawn as spawn3, spawnSync as spawnSync2 } from "child_process";
66
66
 
67
+ // src/wrapped-pty/service-status.ts
68
+ function serviceStatusLabel(status) {
69
+ if (!status) return null;
70
+ const services = status.services ?? [];
71
+ const tasks = status.tasks ?? [];
72
+ if (services.length === 0 && tasks.length === 0) return null;
73
+ const errored = services.some((s) => s.state === "crashed" || s.state === "unhealthy" || s.state === "backoff") || tasks.some((t) => t.state === "failed");
74
+ if (errored) return "service error";
75
+ const isUp = (s) => s.state === "ready" || s.state === "running" && !s.probed;
76
+ const counted = services.filter((s) => s.state !== "stopped");
77
+ const total = counted.length;
78
+ const up = counted.filter(isUp).length;
79
+ const settling = services.some((s) => s.state === "pending" || s.state === "waiting" || s.state === "starting") || services.some((s) => s.state === "running" && Boolean(s.probed)) || tasks.some((t) => t.state === "pending" || t.state === "waiting" || t.state === "running");
80
+ if (total === 0) return settling ? "starting\u2026" : null;
81
+ if (settling || up < total) return `starting ${String(up)}/${String(total)}\u2026`;
82
+ return "ready";
83
+ }
84
+
67
85
  // src/pty/pty-backend.ts
68
86
  async function loadPtyBackend() {
69
87
  try {
@@ -115,16 +133,17 @@ async function spawnInNewTerminal(args) {
115
133
  }
116
134
  async function spawnInTmux(args) {
117
135
  const cmdStr = shellJoin(args.argv);
136
+ const target = args.tmuxTarget ? ["-t", args.tmuxTarget] : [];
118
137
  let tmuxArgv;
119
138
  let noteKind;
120
139
  if (args.mode === "split") {
121
- tmuxArgv = ["split-window", "-h", "-c", args.cwd, "--", cmdStr];
140
+ tmuxArgv = ["split-window", "-h", ...target, "-c", args.cwd, "--", cmdStr];
122
141
  noteKind = "tmux split";
123
142
  } else {
124
- tmuxArgv = ["new-window", "-n", args.title, "-c", args.cwd, "--", cmdStr];
143
+ tmuxArgv = ["new-window", ...target, "-n", args.title, "-c", args.cwd, "--", cmdStr];
125
144
  noteKind = "tmux window";
126
145
  }
127
- const r = await runQuiet("tmux", tmuxArgv);
146
+ const r = await runQuiet("tmux", tmuxArgv, args.env);
128
147
  if (r.code !== 0) {
129
148
  return {
130
149
  launched: false,
@@ -138,9 +157,54 @@ async function spawnInTmux(args) {
138
157
  };
139
158
  }
140
159
  async function spawnInCmux(args) {
141
- const bin = cmuxBinary();
142
- if (args.mode === "window") {
143
- const r = await runQuiet(bin, [
160
+ const bin = cmuxBinary(args.env);
161
+ if (args.mode === "window") return newCmuxWorkspace(bin, args);
162
+ const base = args.mode === "split" ? ["new-split", "right"] : ["new-surface"];
163
+ const noteKind = args.mode === "split" ? "cmux split" : "cmux tab";
164
+ const attempts = [];
165
+ if (args.mode === "split" && args.cmuxTargetSurface) {
166
+ attempts.push([...base, "--surface", args.cmuxTargetSurface, "--focus", "true"]);
167
+ }
168
+ if (args.cmuxTargetWorkspace) {
169
+ attempts.push([...base, "--workspace", args.cmuxTargetWorkspace, "--focus", "true"]);
170
+ }
171
+ if (!args.cmuxWorkspaceFallback) {
172
+ attempts.push([...base, "--focus", "true"]);
173
+ }
174
+ const cmdLine = `cd ${shellQuote(args.cwd)} && exec ${shellJoin(args.argv)}`;
175
+ let lastError = "";
176
+ for (const createArgv of attempts) {
177
+ const created = await runQuiet(bin, createArgv, args.env);
178
+ if (created.code !== 0) {
179
+ lastError = `cmux ${createArgv.join(" ")} exited ${String(created.code)}: ${created.stderr.trim()}`;
180
+ continue;
181
+ }
182
+ const surfaceRef = parseCmuxRef(created.stdout);
183
+ if (!surfaceRef) {
184
+ return {
185
+ launched: false,
186
+ note: "",
187
+ error: `cmux ${createArgv[0]} gave no surface ref: ${created.stdout.trim()}`
188
+ };
189
+ }
190
+ const sent = await runQuiet(bin, ["send", "--surface", surfaceRef, `${cmdLine}
191
+ `], args.env);
192
+ if (sent.code !== 0) {
193
+ return {
194
+ launched: false,
195
+ note: "",
196
+ error: `cmux send exited ${String(sent.code)}: ${sent.stderr.trim()}`
197
+ };
198
+ }
199
+ return { launched: true, note: `Attached in new ${noteKind}.` };
200
+ }
201
+ if (args.cmuxWorkspaceFallback) return newCmuxWorkspace(bin, args);
202
+ return { launched: false, note: "", error: lastError };
203
+ }
204
+ async function newCmuxWorkspace(bin, args) {
205
+ const r = await runQuiet(
206
+ bin,
207
+ [
144
208
  "new-workspace",
145
209
  "--name",
146
210
  args.title,
@@ -150,45 +214,17 @@ async function spawnInCmux(args) {
150
214
  shellJoin(args.argv),
151
215
  "--focus",
152
216
  "true"
153
- ]);
154
- if (r.code !== 0) {
155
- return {
156
- launched: false,
157
- note: "",
158
- error: `cmux new-workspace exited ${String(r.code)}: ${r.stderr.trim()}`
159
- };
160
- }
161
- return { launched: true, note: "Attached in new cmux workspace." };
162
- }
163
- const createArgv = args.mode === "split" ? ["new-split", "right", "--focus", "true"] : ["new-surface", "--focus", "true"];
164
- const noteKind = args.mode === "split" ? "cmux split" : "cmux tab";
165
- const created = await runQuiet(bin, createArgv);
166
- if (created.code !== 0) {
167
- return {
168
- launched: false,
169
- note: "",
170
- error: `cmux ${createArgv[0]} exited ${String(created.code)}: ${created.stderr.trim()}`
171
- };
172
- }
173
- const surfaceRef = parseCmuxRef(created.stdout);
174
- if (!surfaceRef) {
175
- return {
176
- launched: false,
177
- note: "",
178
- error: `cmux ${createArgv[0]} gave no surface ref: ${created.stdout.trim()}`
179
- };
180
- }
181
- const cmdLine = `cd ${shellQuote(args.cwd)} && exec ${shellJoin(args.argv)}`;
182
- const sent = await runQuiet(bin, ["send", "--surface", surfaceRef, `${cmdLine}
183
- `]);
184
- if (sent.code !== 0) {
217
+ ],
218
+ args.env
219
+ );
220
+ if (r.code !== 0) {
185
221
  return {
186
222
  launched: false,
187
223
  note: "",
188
- error: `cmux send exited ${String(sent.code)}: ${sent.stderr.trim()}`
224
+ error: `cmux new-workspace exited ${String(r.code)}: ${r.stderr.trim()}`
189
225
  };
190
226
  }
191
- return { launched: true, note: `Attached in new ${noteKind}.` };
227
+ return { launched: true, note: "Attached in new cmux workspace." };
192
228
  }
193
229
  function parseCmuxRef(stdout) {
194
230
  const m = stdout.match(/\b(?:surface|pane):\d+\b/);
@@ -242,9 +278,9 @@ async function spawnInITerm2(args) {
242
278
  note: `Attached in new ${noteKind}.`
243
279
  };
244
280
  }
245
- function runQuiet(cmd, argv) {
281
+ function runQuiet(cmd, argv, env) {
246
282
  return new Promise((resolve) => {
247
- const child = spawn(cmd, argv, { stdio: ["ignore", "pipe", "pipe"] });
283
+ const child = spawn(cmd, argv, { stdio: ["ignore", "pipe", "pipe"], env });
248
284
  let stdout = "";
249
285
  let stderr = "";
250
286
  child.stdout?.on("data", (chunk) => {
@@ -961,7 +997,8 @@ function renderFooter(state, cols) {
961
997
  };
962
998
  const isClaude = state.mode === "claude";
963
999
  const detachable = state.detachable ?? isClaude;
964
- const stateLabel = isClaude ? void 0 : state.mode === "shell" ? "shell" : state.mode;
1000
+ const modeLabel = isClaude ? void 0 : state.mode === "shell" ? "shell" : state.mode;
1001
+ const stateLabel = state.boxServiceStatus ?? modeLabel;
965
1002
  if (state.leaderActive) {
966
1003
  const leaderHints = detachable ? DETACHABLE_LEADER_HINTS : PLAIN_LEADER_HINTS;
967
1004
  return statusLine(sidebarBox, cols, stateLabel, leaderHints);
@@ -1323,11 +1360,12 @@ async function runWrappedAttach(opts) {
1323
1360
  setTerminalTitle(lastEmittedTitle);
1324
1361
  const detachable = opts.detachable ?? opts.mode === "claude";
1325
1362
  let leaderActive = false;
1326
- const buildIdle = (sessionTitle, claudeActivity) => ({
1363
+ const buildIdle = (sessionTitle, claudeActivity, boxServiceStatus) => ({
1327
1364
  kind: "idle",
1328
1365
  boxName: opts.boxName,
1329
1366
  sessionTitle,
1330
1367
  claudeActivity,
1368
+ boxServiceStatus,
1331
1369
  mode: opts.mode,
1332
1370
  detachable,
1333
1371
  leaderActive
@@ -1335,6 +1373,7 @@ async function runWrappedAttach(opts) {
1335
1373
  let footerState = buildIdle();
1336
1374
  let lastSessionTitle;
1337
1375
  let lastActivity;
1376
+ let lastServiceStatus;
1338
1377
  let capturingPrompt = null;
1339
1378
  let activeNotice = null;
1340
1379
  let reconnectBanner = null;
@@ -1382,7 +1421,7 @@ async function runWrappedAttach(opts) {
1382
1421
  } else if (flashMessage) {
1383
1422
  footerState = { kind: "flash", message: flashMessage };
1384
1423
  } else {
1385
- footerState = buildIdle(lastSessionTitle, lastActivity);
1424
+ footerState = buildIdle(lastSessionTitle, lastActivity, lastServiceStatus);
1386
1425
  }
1387
1426
  };
1388
1427
  const recomputeBand = () => {
@@ -1650,6 +1689,7 @@ async function runWrappedAttach(opts) {
1650
1689
  const body = opts.mode === "codex" ? status?.codex : opts.mode === "opencode" ? status?.opencode : opts.mode === "shell" ? void 0 : status?.claude;
1651
1690
  const nextTitle = body?.sessionTitle?.trim() || void 0;
1652
1691
  const nextActivity = body?.state || void 0;
1692
+ const nextServiceStatus = serviceStatusLabel(status) ?? void 0;
1653
1693
  if (cmuxOn && nextActivity !== lastActivity) {
1654
1694
  applyCmuxAgentState(opts.mode, nextActivity);
1655
1695
  if (isAttentionState(nextActivity) && !isAttentionState(lastActivity)) {
@@ -1667,9 +1707,11 @@ async function runWrappedAttach(opts) {
1667
1707
  questionPayload = nextQuestion;
1668
1708
  applyBandChange();
1669
1709
  }
1670
- if (nextTitle === lastSessionTitle && nextActivity === lastActivity) return;
1710
+ if (nextTitle === lastSessionTitle && nextActivity === lastActivity && nextServiceStatus === lastServiceStatus)
1711
+ return;
1671
1712
  lastSessionTitle = nextTitle;
1672
1713
  lastActivity = nextActivity;
1714
+ lastServiceStatus = nextServiceStatus;
1673
1715
  if (footerState.kind === "idle") {
1674
1716
  recomputeFooter();
1675
1717
  redrawChrome();
@@ -2103,8 +2145,10 @@ export {
2103
2145
  getProvider,
2104
2146
  providerForBox,
2105
2147
  providerForCreate,
2106
- loadPtyBackend,
2107
2148
  detectHostTerminal,
2149
+ cmuxBinary,
2150
+ spawnInNewTerminal,
2151
+ loadPtyBackend,
2108
2152
  setTerminalTitle,
2109
2153
  pushTerminalTitle,
2110
2154
  popTerminalTitle,
@@ -2129,4 +2173,4 @@ export {
2129
2173
  cloudAgentAttach,
2130
2174
  cloudAgentStartDetached
2131
2175
  };
2132
- //# sourceMappingURL=chunk-BYCLD6D6.js.map
2176
+ //# sourceMappingURL=chunk-43Q5GWP6.js.map