@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
package/CHANGELOG.md CHANGED
@@ -9,6 +9,131 @@ 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
+
72
+ ## [0.14.0] - 2026-06-04
73
+
74
+ ### Added
75
+
76
+ - E2B as a fifth provider (`--provider e2b`): a Firecracker microVM per box with
77
+ public HTTPS preview URLs and free pause/resume. Unlike the other clouds, E2B
78
+ builds its base image directly from a Dockerfile — `agentbox prepare --provider
79
+ e2b` drives the build. Full lifecycle is supported: `agentbox e2b login`,
80
+ create, attach (`shell` / `claude` / `codex` / `opencode`), checkpoints, VNC,
81
+ and `agentbox prune --provider e2b`.
82
+ - `agentbox agent wait-for input-needed` — a single state that fires whenever the
83
+ agent needs you: the turn finished and the prompt is ready, or it's blocked on a
84
+ question, plan approval, permission prompt, or error. Replaces racing separate
85
+ `wait-for` calls that each hang to timeout, and prints the concrete state it
86
+ matched so callers can branch on why it woke.
87
+ - Cloud `<provider> login` now nudges you to run `agentbox prepare` when no base
88
+ has been baked, and `create` detects a stale cloud base (by content checksum)
89
+ and folds a rebuild prompt into the existing recreate wizard. Non-interactive
90
+ runs (`-y` / no TTY) warn and boot on the existing base rather than auto-baking.
91
+
92
+ ### Changed
93
+
94
+ - Cloud provider docs lead with how to use each provider, and the recommended
95
+ setup is the one-flow `agentbox install` wizard (login + base bake in one step).
96
+ - `create` / `claude` / `codex` / `opencode` no longer print the `log: <path>`
97
+ startup line; logs are still written and `~/.agentbox/logs/latest.log` still
98
+ tracks the latest run.
99
+ - Cloud attach shows a "starting <agent>" banner so a freshly attached cold cloud
100
+ box is never blank during cold-start, and credential seeding no longer corrupts
101
+ the create spinner.
102
+
103
+ ### Fixed
104
+
105
+ - Parallel boxes are now reliable: `~/.agentbox/state.json` is written atomically
106
+ under a cross-process lock, so concurrent `create` / `destroy` (the `-i` use
107
+ case) no longer lose records, wedge the queue counter, or leave boxes missing
108
+ from `agentbox list`. Concurrent creates get distinct project indices, and a
109
+ box is recorded as soon as its container starts so a mid-create failure is still
110
+ resolvable by `destroy` / `prune`.
111
+ - A box created from a checkpoint now gets a fresh per-box git branch and worktree
112
+ — previously all boxes from one checkpoint shared a branch and their `.git`
113
+ broke once the source box was destroyed (no diff, commit, or `/review`).
114
+ - `agentbox shell <box> -- <argv>` passes the post-`--` arguments verbatim instead
115
+ of re-parsing them through `bash -c`, fixing corrupted redirects and quoting
116
+ (e.g. `curl -w '%{http_code}'`). One-shot `shell -- cmd` against a cloud box no
117
+ longer hangs.
118
+ - Docker-based services in `agentbox.yaml` no longer race a not-yet-ready docker
119
+ socket: dockerd is launched and awaited before the in-box supervisor on every
120
+ create/restart across all DinD providers.
121
+ - Cloud boxes are seeded with working agent credentials and onboarding state, so a
122
+ fresh box lands at a ready prompt instead of a 401, a bypass-permissions accept
123
+ screen, or the first-run theme picker. Credential seeding is best-effort and
124
+ refreshes from the host before each create.
125
+ - A working box is no longer auto-paused (autopause now considers codex/opencode,
126
+ not just claude), and `agentbox drive` auto-unpauses a paused box before
127
+ attaching.
128
+ - Chromium is resolved lazily and shared with the project's own Playwright build,
129
+ fixing browser launches that hung waiting on a stale baked-in binary; the base
130
+ image is also smaller.
131
+ - Resync only flags an untracked-file conflict when the box and host content
132
+ actually differ, so byte-identical files no longer needlessly skip
133
+ `agentbox.yaml` services.
134
+ - A missing cloud base (skipped `agentbox prepare`) now reports a one-line
135
+ actionable error instead of a full stack trace.
136
+
12
137
  ## [0.13.0] - 2026-06-02
13
138
 
14
139
  ### Added
package/README.md CHANGED
@@ -6,6 +6,8 @@
6
6
  </h1>
7
7
 
8
8
  Run multiple agents in parallel, with a single command, on your PC, self-hosted, or in the cloud
9
+
10
+ Works with [iterm2](https://agent-box.sh/docs/integrations-iterm2) - [cmux](https://agent-box.sh/docs/integrations-cmux) - [tmux](https://agent-box.sh/docs/integrations-tmux)
9
11
  <br>
10
12
 
11
13
  <p align="center">
@@ -79,12 +81,12 @@ Uses `portless` to give box web apps the same URL from inside the box and on the
79
81
 
80
82
  ## Cloud Providers
81
83
 
82
- | | local docker | hetzner | daytona | vercel |
83
- | ------------------- | ------------------------- | ---------------------- | ------------------ | ------------------ |
84
- | Support | ✅ | ✅ | ⚠️ Partial | ✅ |
85
- | Base image | Dockerfile | Setup script (Ubuntu) | Dockerfile | Setup script |
86
- | Live snapshots | ✅ | ✅ | 🧪 Experimental | ✅ |
87
- | Private preview URLs| ✅ (portless or OrbStack) | ✅ (portless) | ✅ (native) | ✅ (native) |
84
+ | | local docker | hetzner | daytona | vercel | e2b |
85
+ | ------------------- | ------------------------- | ---------------------- | ------------------ | ------------------ | ------------------ |
86
+ | Support | ✅ | ✅ | ⚠️ Partial | ✅ | ✅ |
87
+ | Base image | Dockerfile | Setup script (Ubuntu) | Dockerfile | Setup script | Dockerfile (`Template.build`) |
88
+ | Live snapshots | ✅ | ✅ | 🧪 Experimental | ✅ | ✅ |
89
+ | Private preview URLs| ✅ (portless or OrbStack) | ✅ (portless) | ✅ (native) | ✅ (native) | ✅ (native) |
88
90
 
89
91
  **Cloud setup** (optional — skip for local Docker)
90
92
 
@@ -92,7 +94,8 @@ Uses `portless` to give box web apps the same URL from inside the box and on the
92
94
  - `agentbox vercel login` — interactive Vercel Sandbox token setup, saved to `~/.agentbox/secrets.env`
93
95
  - `agentbox hetzner login` — interactive Hetzner Cloud token setup, saved to `~/.agentbox/secrets.env`
94
96
  - `agentbox daytona login` — interactive Daytona API key setup, saved to `~/.agentbox/secrets.env`
95
- - `agentbox prepare [--provider daytona|hetzner]` — build the image and initial snapshot
97
+ - `agentbox e2b login` — interactive E2B API key setup, saved to `~/.agentbox/secrets.env`
98
+ - `agentbox prepare [--provider daytona|hetzner|vercel|e2b]` — build the image and initial snapshot (e2b builds from a Dockerfile via `Template.build()`)
96
99
  - `agentbox hetzner claude`, `agentbox hetzner codex`, `agentbox hetzner create`, etc.
97
100
 
98
101
  ## How to use
@@ -150,7 +153,7 @@ Full documentation lives at **[agent-box.sh/docs](https://agent-box.sh/docs)**:
150
153
  - [Quickstart](https://agent-box.sh/docs) and [Core concepts](https://agent-box.sh/docs/core-concepts)
151
154
  - [Teleport a project](https://agent-box.sh/docs/teleport-a-project), [Run an agent](https://agent-box.sh/docs/run-an-agent), [Access your box](https://agent-box.sh/docs/access-your-box)
152
155
  - [Configuration](https://agent-box.sh/docs/configuration), [Services & tasks](https://agent-box.sh/docs/services-and-tasks), [Sync & git](https://agent-box.sh/docs/sync-and-git)
153
- - Cloud providers: [Hetzner](https://agent-box.sh/docs/hetzner), [Daytona](https://agent-box.sh/docs/daytona), [Vercel](https://agent-box.sh/docs/vercel)
156
+ - Cloud providers: [Hetzner](https://agent-box.sh/docs/hetzner), [Daytona](https://agent-box.sh/docs/daytona), [Vercel](https://agent-box.sh/docs/vercel), [E2B](https://agent-box.sh/docs/e2b)
154
157
  - Full [CLI reference](https://agent-box.sh/docs/cli)
155
158
 
156
159
  ## Development
@@ -3,13 +3,13 @@ import {
3
3
  buildCloudAttachInnerCommand,
4
4
  cloudAgentAttach,
5
5
  cloudAgentStartDetached
6
- } from "./chunk-QYRK5H6Q.js";
7
- import "./chunk-B4QG2MCW.js";
8
- import "./chunk-SNTHHWKY.js";
6
+ } from "./chunk-43Q5GWP6.js";
7
+ import "./chunk-MLMFNN4T.js";
8
+ import "./chunk-XKH7NTT7.js";
9
9
  import "./chunk-G3H2L3O2.js";
10
10
  export {
11
11
  buildCloudAttachInnerCommand,
12
12
  cloudAgentAttach,
13
13
  cloudAgentStartDetached
14
14
  };
15
- //# sourceMappingURL=_cloud-attach-HJC672UR.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-B4QG2MCW.js";
6
+ } from "./chunk-MLMFNN4T.js";
7
7
 
8
8
  // src/commands/_cloud-attach.ts
9
9
  import { spawn as spawn4 } from "child_process";
@@ -13,31 +13,36 @@ import { join as join2 } from "path";
13
13
  import { spinner } from "@clack/prompts";
14
14
 
15
15
  // src/provider/registry.ts
16
- var KNOWN = ["docker", "daytona", "hetzner", "vercel"];
16
+ var KNOWN = ["docker", "daytona", "hetzner", "vercel", "e2b"];
17
17
  function isKnownProvider(name) {
18
18
  return KNOWN.includes(name);
19
19
  }
20
20
  async function getProvider(name) {
21
21
  switch (name) {
22
22
  case "docker": {
23
- const mod = await import("./dist-OG6NW6SM.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-OPIBZ7XM.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-7KVUIKJX.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-JAN5VABY.js");
37
+ const mod = await import("./dist-S4XR4ACV.js");
38
38
  await mod.ensureVercelCredentials();
39
39
  return mod.vercelProvider;
40
40
  }
41
+ case "e2b": {
42
+ const mod = await import("./dist-JZ3XO6EB.js");
43
+ await mod.ensureE2bCredentials();
44
+ return mod.e2bProvider;
45
+ }
41
46
  default:
42
47
  throw new Error(`unknown sandbox provider: ${String(name)}`);
43
48
  }
@@ -59,6 +64,24 @@ async function providerForCreate(choice) {
59
64
  // src/wrapped-pty/run.ts
60
65
  import { spawn as spawn3, spawnSync as spawnSync2 } from "child_process";
61
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
+
62
85
  // src/pty/pty-backend.ts
63
86
  async function loadPtyBackend() {
64
87
  try {
@@ -110,16 +133,17 @@ async function spawnInNewTerminal(args) {
110
133
  }
111
134
  async function spawnInTmux(args) {
112
135
  const cmdStr = shellJoin(args.argv);
136
+ const target = args.tmuxTarget ? ["-t", args.tmuxTarget] : [];
113
137
  let tmuxArgv;
114
138
  let noteKind;
115
139
  if (args.mode === "split") {
116
- tmuxArgv = ["split-window", "-h", "-c", args.cwd, "--", cmdStr];
140
+ tmuxArgv = ["split-window", "-h", ...target, "-c", args.cwd, "--", cmdStr];
117
141
  noteKind = "tmux split";
118
142
  } else {
119
- tmuxArgv = ["new-window", "-n", args.title, "-c", args.cwd, "--", cmdStr];
143
+ tmuxArgv = ["new-window", ...target, "-n", args.title, "-c", args.cwd, "--", cmdStr];
120
144
  noteKind = "tmux window";
121
145
  }
122
- const r = await runQuiet("tmux", tmuxArgv);
146
+ const r = await runQuiet("tmux", tmuxArgv, args.env);
123
147
  if (r.code !== 0) {
124
148
  return {
125
149
  launched: false,
@@ -133,9 +157,54 @@ async function spawnInTmux(args) {
133
157
  };
134
158
  }
135
159
  async function spawnInCmux(args) {
136
- const bin = cmuxBinary();
137
- if (args.mode === "window") {
138
- 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
+ [
139
208
  "new-workspace",
140
209
  "--name",
141
210
  args.title,
@@ -145,45 +214,17 @@ async function spawnInCmux(args) {
145
214
  shellJoin(args.argv),
146
215
  "--focus",
147
216
  "true"
148
- ]);
149
- if (r.code !== 0) {
150
- return {
151
- launched: false,
152
- note: "",
153
- error: `cmux new-workspace exited ${String(r.code)}: ${r.stderr.trim()}`
154
- };
155
- }
156
- return { launched: true, note: "Attached in new cmux workspace." };
157
- }
158
- const createArgv = args.mode === "split" ? ["new-split", "right", "--focus", "true"] : ["new-surface", "--focus", "true"];
159
- const noteKind = args.mode === "split" ? "cmux split" : "cmux tab";
160
- const created = await runQuiet(bin, createArgv);
161
- if (created.code !== 0) {
162
- return {
163
- launched: false,
164
- note: "",
165
- error: `cmux ${createArgv[0]} exited ${String(created.code)}: ${created.stderr.trim()}`
166
- };
167
- }
168
- const surfaceRef = parseCmuxRef(created.stdout);
169
- if (!surfaceRef) {
170
- return {
171
- launched: false,
172
- note: "",
173
- error: `cmux ${createArgv[0]} gave no surface ref: ${created.stdout.trim()}`
174
- };
175
- }
176
- const cmdLine = `cd ${shellQuote(args.cwd)} && exec ${shellJoin(args.argv)}`;
177
- const sent = await runQuiet(bin, ["send", "--surface", surfaceRef, `${cmdLine}
178
- `]);
179
- if (sent.code !== 0) {
217
+ ],
218
+ args.env
219
+ );
220
+ if (r.code !== 0) {
180
221
  return {
181
222
  launched: false,
182
223
  note: "",
183
- error: `cmux send exited ${String(sent.code)}: ${sent.stderr.trim()}`
224
+ error: `cmux new-workspace exited ${String(r.code)}: ${r.stderr.trim()}`
184
225
  };
185
226
  }
186
- return { launched: true, note: `Attached in new ${noteKind}.` };
227
+ return { launched: true, note: "Attached in new cmux workspace." };
187
228
  }
188
229
  function parseCmuxRef(stdout) {
189
230
  const m = stdout.match(/\b(?:surface|pane):\d+\b/);
@@ -237,9 +278,9 @@ async function spawnInITerm2(args) {
237
278
  note: `Attached in new ${noteKind}.`
238
279
  };
239
280
  }
240
- function runQuiet(cmd, argv) {
281
+ function runQuiet(cmd, argv, env) {
241
282
  return new Promise((resolve) => {
242
- const child = spawn(cmd, argv, { stdio: ["ignore", "pipe", "pipe"] });
283
+ const child = spawn(cmd, argv, { stdio: ["ignore", "pipe", "pipe"], env });
243
284
  let stdout = "";
244
285
  let stderr = "";
245
286
  child.stdout?.on("data", (chunk) => {
@@ -956,7 +997,8 @@ function renderFooter(state, cols) {
956
997
  };
957
998
  const isClaude = state.mode === "claude";
958
999
  const detachable = state.detachable ?? isClaude;
959
- 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;
960
1002
  if (state.leaderActive) {
961
1003
  const leaderHints = detachable ? DETACHABLE_LEADER_HINTS : PLAIN_LEADER_HINTS;
962
1004
  return statusLine(sidebarBox, cols, stateLabel, leaderHints);
@@ -1318,11 +1360,12 @@ async function runWrappedAttach(opts) {
1318
1360
  setTerminalTitle(lastEmittedTitle);
1319
1361
  const detachable = opts.detachable ?? opts.mode === "claude";
1320
1362
  let leaderActive = false;
1321
- const buildIdle = (sessionTitle, claudeActivity) => ({
1363
+ const buildIdle = (sessionTitle, claudeActivity, boxServiceStatus) => ({
1322
1364
  kind: "idle",
1323
1365
  boxName: opts.boxName,
1324
1366
  sessionTitle,
1325
1367
  claudeActivity,
1368
+ boxServiceStatus,
1326
1369
  mode: opts.mode,
1327
1370
  detachable,
1328
1371
  leaderActive
@@ -1330,6 +1373,7 @@ async function runWrappedAttach(opts) {
1330
1373
  let footerState = buildIdle();
1331
1374
  let lastSessionTitle;
1332
1375
  let lastActivity;
1376
+ let lastServiceStatus;
1333
1377
  let capturingPrompt = null;
1334
1378
  let activeNotice = null;
1335
1379
  let reconnectBanner = null;
@@ -1377,7 +1421,7 @@ async function runWrappedAttach(opts) {
1377
1421
  } else if (flashMessage) {
1378
1422
  footerState = { kind: "flash", message: flashMessage };
1379
1423
  } else {
1380
- footerState = buildIdle(lastSessionTitle, lastActivity);
1424
+ footerState = buildIdle(lastSessionTitle, lastActivity, lastServiceStatus);
1381
1425
  }
1382
1426
  };
1383
1427
  const recomputeBand = () => {
@@ -1645,6 +1689,7 @@ async function runWrappedAttach(opts) {
1645
1689
  const body = opts.mode === "codex" ? status?.codex : opts.mode === "opencode" ? status?.opencode : opts.mode === "shell" ? void 0 : status?.claude;
1646
1690
  const nextTitle = body?.sessionTitle?.trim() || void 0;
1647
1691
  const nextActivity = body?.state || void 0;
1692
+ const nextServiceStatus = serviceStatusLabel(status) ?? void 0;
1648
1693
  if (cmuxOn && nextActivity !== lastActivity) {
1649
1694
  applyCmuxAgentState(opts.mode, nextActivity);
1650
1695
  if (isAttentionState(nextActivity) && !isAttentionState(lastActivity)) {
@@ -1662,9 +1707,11 @@ async function runWrappedAttach(opts) {
1662
1707
  questionPayload = nextQuestion;
1663
1708
  applyBandChange();
1664
1709
  }
1665
- if (nextTitle === lastSessionTitle && nextActivity === lastActivity) return;
1710
+ if (nextTitle === lastSessionTitle && nextActivity === lastActivity && nextServiceStatus === lastServiceStatus)
1711
+ return;
1666
1712
  lastSessionTitle = nextTitle;
1667
1713
  lastActivity = nextActivity;
1714
+ lastServiceStatus = nextServiceStatus;
1668
1715
  if (footerState.kind === "idle") {
1669
1716
  recomputeFooter();
1670
1717
  redrawChrome();
@@ -1962,12 +2009,15 @@ function abortableSleep(ms, signal) {
1962
2009
  );
1963
2010
  });
1964
2011
  }
2012
+ function agentStartBanner(binary) {
2013
+ return `printf " agentbox: starting ${binary} (first paint may take a few seconds)...\\r\\n"; `;
2014
+ }
1965
2015
  function buildCloudAttachInnerCommand(binary, extraArgs) {
1966
2016
  if (!extraArgs || extraArgs.length === 0) {
1967
- return `bash -lc exec\\ ${binary}`;
2017
+ return `bash -lc '${agentStartBanner(binary)}exec ${binary}'`;
1968
2018
  }
1969
2019
  const blob = Buffer.from(extraArgs.join("\n"), "utf8").toString("base64");
1970
- return `bash -lc 'mapfile -t A <<< "$(echo ${blob} | base64 -d)"; exec ${binary} "\${A[@]}"'`;
2020
+ return `bash -lc '${agentStartBanner(binary)}mapfile -t A <<< "$(echo ${blob} | base64 -d)"; exec ${binary} "\${A[@]}"'`;
1971
2021
  }
1972
2022
  async function cloudAgentAttach(args) {
1973
2023
  const provider = await providerForBox(args.box);
@@ -2095,8 +2145,10 @@ export {
2095
2145
  getProvider,
2096
2146
  providerForBox,
2097
2147
  providerForCreate,
2098
- loadPtyBackend,
2099
2148
  detectHostTerminal,
2149
+ cmuxBinary,
2150
+ spawnInNewTerminal,
2151
+ loadPtyBackend,
2100
2152
  setTerminalTitle,
2101
2153
  pushTerminalTitle,
2102
2154
  popTerminalTitle,
@@ -2121,4 +2173,4 @@ export {
2121
2173
  cloudAgentAttach,
2122
2174
  cloudAgentStartDetached
2123
2175
  };
2124
- //# sourceMappingURL=chunk-QYRK5H6Q.js.map
2176
+ //# sourceMappingURL=chunk-43Q5GWP6.js.map