@madarco/agentbox 0.11.3 → 0.13.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 (37) hide show
  1. package/CHANGELOG.md +90 -0
  2. package/README.md +11 -0
  3. package/dist/{_cloud-attach-XWCVLO5V.js → _cloud-attach-HJC672UR.js} +3 -3
  4. package/dist/{chunk-ZGVMN54V.js → chunk-2LF5YILI.js} +21 -3
  5. package/dist/chunk-2LF5YILI.js.map +1 -0
  6. package/dist/{chunk-MXXXKJYS.js → chunk-4NQXNQ53.js} +234 -83
  7. package/dist/chunk-4NQXNQ53.js.map +1 -0
  8. package/dist/{chunk-ZJXTIH6C.js → chunk-B4QG2MCW.js} +1352 -851
  9. package/dist/chunk-B4QG2MCW.js.map +1 -0
  10. package/dist/{chunk-GYJ62GFL.js → chunk-QYRK5H6Q.js} +297 -33
  11. package/dist/chunk-QYRK5H6Q.js.map +1 -0
  12. package/dist/{dist-WMQDMTWS.js → dist-7KVUIKJX.js} +8 -5
  13. package/dist/dist-7KVUIKJX.js.map +1 -0
  14. package/dist/{dist-RAZP76VX.js → dist-JAN5VABY.js} +3 -3
  15. package/dist/{dist-ASLPRUQR.js → dist-OG6NW6SM.js} +28 -2
  16. package/dist/{dist-PTJ6CEQY.js → dist-OPIBZ7XM.js} +4 -4
  17. package/dist/index.js +1720 -876
  18. package/dist/index.js.map +1 -1
  19. package/package.json +4 -4
  20. package/runtime/docker/packages/ctl/dist/bin.cjs +341 -5
  21. package/runtime/docker/packages/sandbox-docker/scripts/gh-shim +86 -5
  22. package/runtime/hetzner/ctl.cjs +341 -5
  23. package/runtime/hetzner/gh-shim +86 -5
  24. package/runtime/relay/bin.cjs +293 -4
  25. package/runtime/vercel/ctl.cjs +341 -5
  26. package/runtime/vercel/gh-shim +86 -5
  27. package/share/host-skills/agentbox/SKILL.md +16 -5
  28. package/share/host-skills/agentbox-info/SKILL.md +3 -1
  29. package/dist/chunk-GYJ62GFL.js.map +0 -1
  30. package/dist/chunk-MXXXKJYS.js.map +0 -1
  31. package/dist/chunk-ZGVMN54V.js.map +0 -1
  32. package/dist/chunk-ZJXTIH6C.js.map +0 -1
  33. package/dist/dist-WMQDMTWS.js.map +0 -1
  34. /package/dist/{_cloud-attach-XWCVLO5V.js.map → _cloud-attach-HJC672UR.js.map} +0 -0
  35. /package/dist/{dist-RAZP76VX.js.map → dist-JAN5VABY.js.map} +0 -0
  36. /package/dist/{dist-ASLPRUQR.js.map → dist-OG6NW6SM.js.map} +0 -0
  37. /package/dist/{dist-PTJ6CEQY.js.map → dist-OPIBZ7XM.js.map} +0 -0
@@ -1,11 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  DEFAULT_RELAY_PORT,
4
+ loadEffectiveConfig,
4
5
  readBoxStatus
5
- } from "./chunk-ZJXTIH6C.js";
6
+ } from "./chunk-B4QG2MCW.js";
6
7
 
7
8
  // src/commands/_cloud-attach.ts
8
- import { spawn as spawn3 } from "child_process";
9
+ import { spawn as spawn4 } from "child_process";
9
10
  import { appendFileSync } from "fs";
10
11
  import { homedir } from "os";
11
12
  import { join as join2 } from "path";
@@ -19,21 +20,21 @@ function isKnownProvider(name) {
19
20
  async function getProvider(name) {
20
21
  switch (name) {
21
22
  case "docker": {
22
- const mod = await import("./dist-ASLPRUQR.js");
23
+ const mod = await import("./dist-OG6NW6SM.js");
23
24
  return mod.dockerProvider;
24
25
  }
25
26
  case "daytona": {
26
- const mod = await import("./dist-PTJ6CEQY.js");
27
+ const mod = await import("./dist-OPIBZ7XM.js");
27
28
  await mod.ensureDaytonaCredentials();
28
29
  return mod.daytonaProvider;
29
30
  }
30
31
  case "hetzner": {
31
- const mod = await import("./dist-WMQDMTWS.js");
32
+ const mod = await import("./dist-7KVUIKJX.js");
32
33
  await mod.ensureHetznerCredentials();
33
34
  return mod.hetznerProvider;
34
35
  }
35
36
  case "vercel": {
36
- const mod = await import("./dist-RAZP76VX.js");
37
+ const mod = await import("./dist-JAN5VABY.js");
37
38
  await mod.ensureVercelCredentials();
38
39
  return mod.vercelProvider;
39
40
  }
@@ -56,20 +57,20 @@ async function providerForCreate(choice) {
56
57
  }
57
58
 
58
59
  // src/wrapped-pty/run.ts
59
- import { spawn as spawn2, spawnSync } from "child_process";
60
+ import { spawn as spawn3, spawnSync as spawnSync2 } from "child_process";
60
61
 
61
62
  // src/pty/pty-backend.ts
62
63
  async function loadPtyBackend() {
63
64
  try {
64
65
  const ptyMod = await import("@homebridge/node-pty-prebuilt-multiarch");
65
66
  const xtermMod = await import("@xterm/headless");
66
- const spawn4 = ptyMod["spawn"] ?? ptyMod["default"]?.["spawn"];
67
+ const spawn5 = ptyMod["spawn"] ?? ptyMod["default"]?.["spawn"];
67
68
  const Terminal = xtermMod["Terminal"] ?? xtermMod["default"]?.["Terminal"];
68
- if (typeof spawn4 !== "function" || typeof Terminal !== "function") {
69
+ if (typeof spawn5 !== "function" || typeof Terminal !== "function") {
69
70
  return null;
70
71
  }
71
72
  return {
72
- ptySpawn: spawn4,
73
+ ptySpawn: spawn5,
73
74
  termCtor: Terminal
74
75
  };
75
76
  } catch {
@@ -82,10 +83,16 @@ import { spawn } from "child_process";
82
83
  function detectHostTerminal(env = process.env) {
83
84
  const tmux = env["TMUX"];
84
85
  if (tmux && tmux.length > 0) return "tmux";
86
+ const cmuxSocket = env["CMUX_SOCKET_PATH"];
87
+ if (cmuxSocket && cmuxSocket.length > 0) return "cmux";
85
88
  const termProgram = env["TERM_PROGRAM"];
86
89
  if (termProgram === "iTerm.app") return "iterm2";
87
90
  return "unknown";
88
91
  }
92
+ function cmuxBinary(env = process.env) {
93
+ const bundled = env["CMUX_BUNDLED_CLI_PATH"];
94
+ return bundled && bundled.length > 0 ? bundled : "cmux";
95
+ }
89
96
  function shellQuote(s) {
90
97
  if (s.length === 0) return "''";
91
98
  return "'" + s.replace(/'/g, "'\\''") + "'";
@@ -98,6 +105,7 @@ function shellJoin(argv) {
98
105
  }
99
106
  async function spawnInNewTerminal(args) {
100
107
  if (args.host === "tmux") return spawnInTmux(args);
108
+ if (args.host === "cmux") return spawnInCmux(args);
101
109
  return spawnInITerm2(args);
102
110
  }
103
111
  async function spawnInTmux(args) {
@@ -124,6 +132,63 @@ async function spawnInTmux(args) {
124
132
  note: `Attached in new ${noteKind}.`
125
133
  };
126
134
  }
135
+ async function spawnInCmux(args) {
136
+ const bin = cmuxBinary();
137
+ if (args.mode === "window") {
138
+ const r = await runQuiet(bin, [
139
+ "new-workspace",
140
+ "--name",
141
+ args.title,
142
+ "--cwd",
143
+ args.cwd,
144
+ "--command",
145
+ shellJoin(args.argv),
146
+ "--focus",
147
+ "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) {
180
+ return {
181
+ launched: false,
182
+ note: "",
183
+ error: `cmux send exited ${String(sent.code)}: ${sent.stderr.trim()}`
184
+ };
185
+ }
186
+ return { launched: true, note: `Attached in new ${noteKind}.` };
187
+ }
188
+ function parseCmuxRef(stdout) {
189
+ const m = stdout.match(/\b(?:surface|pane):\d+\b/);
190
+ return m ? m[0] : void 0;
191
+ }
127
192
  async function spawnInITerm2(args) {
128
193
  const inner = shellJoin(args.argv);
129
194
  const cmdLine = `cd ${shellQuote(args.cwd)} && exec ${inner}`;
@@ -174,20 +239,139 @@ async function spawnInITerm2(args) {
174
239
  }
175
240
  function runQuiet(cmd, argv) {
176
241
  return new Promise((resolve) => {
177
- const child = spawn(cmd, argv, { stdio: ["ignore", "ignore", "pipe"] });
242
+ const child = spawn(cmd, argv, { stdio: ["ignore", "pipe", "pipe"] });
243
+ let stdout = "";
178
244
  let stderr = "";
245
+ child.stdout?.on("data", (chunk) => {
246
+ stdout += chunk.toString("utf8");
247
+ });
179
248
  child.stderr?.on("data", (chunk) => {
180
249
  stderr += chunk.toString("utf8");
181
250
  });
182
251
  child.on("error", (err) => {
183
- resolve({ code: 127, stderr: err.message });
252
+ resolve({ code: 127, stdout, stderr: err.message });
184
253
  });
185
254
  child.on("exit", (code) => {
186
- resolve({ code: typeof code === "number" ? code : 1, stderr });
255
+ resolve({ code: typeof code === "number" ? code : 1, stdout, stderr });
187
256
  });
188
257
  });
189
258
  }
190
259
 
260
+ // src/terminal/cmux-status.ts
261
+ import { spawn as spawn2, spawnSync } from "child_process";
262
+ var AGENT_LABEL = {
263
+ claude: "claude",
264
+ codex: "codex",
265
+ opencode: "opencode"
266
+ };
267
+ function mapActivityToWorkspace(mode, activity) {
268
+ if (mode === "shell") return null;
269
+ const label = AGENT_LABEL[mode];
270
+ switch (activity) {
271
+ case "working":
272
+ case "compacting":
273
+ return { description: `${label} \xB7 working`, color: "Blue" };
274
+ case "question":
275
+ case "waiting":
276
+ return { description: `${label} \xB7 needs input`, color: "Amber" };
277
+ case "end-plan":
278
+ return { description: `${label} \xB7 plan ready`, color: "Amber" };
279
+ case "error":
280
+ return { description: `${label} \xB7 error`, color: "Red" };
281
+ case "idle":
282
+ return { description: `${label} \xB7 idle`, color: "" };
283
+ case "unknown":
284
+ case void 0:
285
+ default:
286
+ return null;
287
+ }
288
+ }
289
+ function isAttentionState(activity) {
290
+ return activity === "question" || activity === "waiting" || activity === "end-plan" || activity === "error";
291
+ }
292
+ function attentionReason(activity) {
293
+ switch (activity) {
294
+ case "end-plan":
295
+ return "plan ready";
296
+ case "error":
297
+ return "error";
298
+ case "question":
299
+ case "waiting":
300
+ default:
301
+ return "needs input";
302
+ }
303
+ }
304
+ function cmuxStatusActive(env = process.env) {
305
+ const sock = env["CMUX_SOCKET_PATH"];
306
+ return typeof sock === "string" && sock.length > 0;
307
+ }
308
+ async function cmuxStatusEnabled(env = process.env) {
309
+ if (!cmuxStatusActive(env)) return false;
310
+ try {
311
+ const cfg = await loadEffectiveConfig(process.cwd());
312
+ return cfg.effective.attach.cmuxStatus;
313
+ } catch {
314
+ return true;
315
+ }
316
+ }
317
+ function runCmux(argv) {
318
+ try {
319
+ const child = spawn2(cmuxBinary(), argv, { stdio: "ignore" });
320
+ child.on("error", () => {
321
+ });
322
+ child.unref();
323
+ } catch {
324
+ }
325
+ }
326
+ function captureCmuxWorkspace(env = process.env) {
327
+ const wsId = env["CMUX_WORKSPACE_ID"];
328
+ if (!wsId) return null;
329
+ try {
330
+ const r = spawnSync(cmuxBinary(), ["list-workspaces", "--json", "--id-format", "both"], {
331
+ encoding: "utf8"
332
+ });
333
+ if (r.status !== 0 || !r.stdout) return null;
334
+ const data = JSON.parse(r.stdout);
335
+ for (const w of data.workspaces ?? []) {
336
+ if (!JSON.stringify(w).includes(wsId)) continue;
337
+ const description = typeof w["description"] === "string" ? w["description"] : "";
338
+ const color = typeof w["custom_color"] === "string" ? w["custom_color"] : "";
339
+ return { description, color };
340
+ }
341
+ } catch {
342
+ }
343
+ return null;
344
+ }
345
+ function applyCmuxAgentState(mode, activity) {
346
+ const view = mapActivityToWorkspace(mode, activity);
347
+ if (!view) return;
348
+ runCmux(["workspace-action", "--action", "set-description", "--description", view.description]);
349
+ if (view.color) {
350
+ runCmux(["workspace-action", "--action", "set-color", "--color", view.color]);
351
+ } else {
352
+ runCmux(["workspace-action", "--action", "clear-color"]);
353
+ }
354
+ }
355
+ function markCmuxTabAttention(mode, boxName, activity, env = process.env) {
356
+ const label = mode === "shell" ? "shell" : AGENT_LABEL[mode];
357
+ const argv = ["notify", "--title", boxName, "--body", `${label} \xB7 ${attentionReason(activity)}`];
358
+ const surface = env["CMUX_SURFACE_ID"];
359
+ if (surface && surface.length > 0) argv.push("--surface", surface);
360
+ runCmux(argv);
361
+ }
362
+ function restoreCmuxWorkspace(orig) {
363
+ if (orig && orig.description) {
364
+ runCmux(["workspace-action", "--action", "set-description", "--description", orig.description]);
365
+ } else {
366
+ runCmux(["workspace-action", "--action", "clear-description"]);
367
+ }
368
+ if (orig && orig.color) {
369
+ runCmux(["workspace-action", "--action", "set-color", "--color", orig.color]);
370
+ } else {
371
+ runCmux(["workspace-action", "--action", "clear-color"]);
372
+ }
373
+ }
374
+
191
375
  // src/terminal/title.ts
192
376
  var ESC = "\x1B";
193
377
  var BEL = "\x07";
@@ -665,7 +849,7 @@ var ADVANCED_HINT_GROUPS = [
665
849
  ["u", "url"],
666
850
  ["t", "stop"],
667
851
  ["p", "pause"],
668
- ["d", "destroy"],
852
+ ["k", "destroy"],
669
853
  ["q", "quit"]
670
854
  ];
671
855
  function statusLine(box, w, stateLabel, groups = HINT_GROUPS, fallbackGroups) {
@@ -728,12 +912,16 @@ var DETACHABLE_LEADER_HINTS = [
728
912
  ["c", "code"],
729
913
  ["s", "screen"],
730
914
  ["u", "url"],
915
+ ["t", "shell"],
916
+ ["k", "destroy"],
731
917
  ["d", "detach"]
732
918
  ];
733
919
  var PLAIN_LEADER_HINTS = [
734
920
  ["c", "code"],
735
921
  ["s", "screen"],
736
- ["u", "url"]
922
+ ["u", "url"],
923
+ ["t", "shell"],
924
+ ["k", "destroy"]
737
925
  ];
738
926
  function padTo(visible, width) {
739
927
  if (visible.length === width) return visible;
@@ -1062,7 +1250,8 @@ var CHECKPOINT_DROP_GRACE_MS = 4e3;
1062
1250
  var ACTION_FLASH = {
1063
1251
  screen: "Opening noVNC viewer\u2026",
1064
1252
  code: "Launching VS Code / Cursor\u2026",
1065
- url: "Opening box URL\u2026"
1253
+ url: "Opening box URL\u2026",
1254
+ shell: "Opening shell in box\u2026"
1066
1255
  };
1067
1256
  var ACTION_CMD = {
1068
1257
  screen: { sub: "screen", flags: [] },
@@ -1154,6 +1343,7 @@ async function runWrappedAttach(opts) {
1154
1343
  let reconnecting = false;
1155
1344
  let reconnectAbort = null;
1156
1345
  let userDetached = false;
1346
+ let userKilled = false;
1157
1347
  let checkpointNoticeAt = 0;
1158
1348
  let checkpointNoticeClearedAt = 0;
1159
1349
  const reservedRows = () => FOOTER_ROWS + bandReservedRows;
@@ -1249,7 +1439,7 @@ async function runWrappedAttach(opts) {
1249
1439
  });
1250
1440
  };
1251
1441
  wireOutput();
1252
- const leaderChords = detachable ? { c: "code", s: "screen", u: "url", d: "detach" } : { c: "code", s: "screen", u: "url" };
1442
+ const leaderChords = detachable ? { c: "code", s: "screen", u: "url", t: "shell", k: "kill", d: "detach" } : { c: "code", s: "screen", u: "url", t: "shell", k: "kill" };
1253
1443
  const runAction = (name) => {
1254
1444
  if (name === "detach") {
1255
1445
  if (!reconnecting) {
@@ -1258,20 +1448,76 @@ async function runWrappedAttach(opts) {
1258
1448
  }
1259
1449
  return;
1260
1450
  }
1451
+ if (name === "kill") {
1452
+ if (capturingPrompt) return;
1453
+ const ev = {
1454
+ id: "local:kill",
1455
+ kind: "confirm",
1456
+ message: `Destroy ${opts.boxName}? This wipes the box and all its data.`,
1457
+ detail: "This cannot be undone.",
1458
+ defaultAnswer: "n",
1459
+ context: { command: "destroy box" }
1460
+ };
1461
+ capturingPrompt = ev;
1462
+ applyBandChange();
1463
+ void router.capture(ev).then((body) => {
1464
+ if (body.answer !== "y" || body.cancelled) return;
1465
+ userDetached = true;
1466
+ userKilled = true;
1467
+ flashMessage = `Destroying ${opts.boxName}\u2026`;
1468
+ recomputeFooter();
1469
+ redrawChrome();
1470
+ const cli = process.argv[1];
1471
+ if (typeof cli === "string" && cli.length > 0) {
1472
+ try {
1473
+ spawn3(process.execPath, [cli, "destroy", opts.boxId, "-y"], {
1474
+ detached: true,
1475
+ stdio: "ignore"
1476
+ }).unref();
1477
+ } catch (e) {
1478
+ logErr(`leader-action kill spawn failed: ${e.message}`);
1479
+ }
1480
+ }
1481
+ }).catch((e) => {
1482
+ if (capturingPrompt === ev) {
1483
+ capturingPrompt = null;
1484
+ applyBandChange();
1485
+ }
1486
+ const msg = e instanceof Error ? e.message : String(e);
1487
+ if (msg !== "resolved-elsewhere") logErr(`kill confirm rejected: ${msg}`);
1488
+ });
1489
+ return;
1490
+ }
1261
1491
  const cliEntry = process.argv[1];
1262
- if (typeof cliEntry === "string" && cliEntry.length > 0) {
1263
- const cmd = ACTION_CMD[name];
1264
- try {
1265
- spawn2(
1266
- process.execPath,
1267
- [cliEntry, cmd.sub, opts.boxId, ...cmd.flags],
1268
- { detached: true, stdio: "ignore" }
1269
- ).unref();
1270
- } catch (e) {
1271
- logErr(`leader-action spawn (${name}) failed: ${e.message}`);
1492
+ if (name === "shell") {
1493
+ const host = detectHostTerminal();
1494
+ if (typeof cliEntry === "string" && cliEntry.length > 0 && host !== "unknown") {
1495
+ void spawnInNewTerminal({
1496
+ host,
1497
+ mode: "tab",
1498
+ argv: [process.execPath, cliEntry, "shell", opts.boxId, "--new"],
1499
+ cwd: process.cwd(),
1500
+ title: `${opts.boxName} shell`
1501
+ }).then((r) => {
1502
+ if (!r.launched && r.error) logErr(`leader-action shell: ${r.error}`);
1503
+ }).catch((e) => logErr(`leader-action shell failed: ${e.message}`));
1504
+ }
1505
+ flashMessage = host === "unknown" ? `Run: agentbox shell ${opts.boxId} --new` : ACTION_FLASH.shell;
1506
+ } else {
1507
+ if (typeof cliEntry === "string" && cliEntry.length > 0) {
1508
+ const cmd = ACTION_CMD[name];
1509
+ try {
1510
+ spawn3(
1511
+ process.execPath,
1512
+ [cliEntry, cmd.sub, opts.boxId, ...cmd.flags],
1513
+ { detached: true, stdio: "ignore" }
1514
+ ).unref();
1515
+ } catch (e) {
1516
+ logErr(`leader-action spawn (${name}) failed: ${e.message}`);
1517
+ }
1272
1518
  }
1519
+ flashMessage = ACTION_FLASH[name];
1273
1520
  }
1274
- flashMessage = ACTION_FLASH[name];
1275
1521
  if (flashTimer) clearTimeout(flashTimer);
1276
1522
  flashTimer = setTimeout(() => {
1277
1523
  flashTimer = null;
@@ -1318,7 +1564,9 @@ async function runWrappedAttach(opts) {
1318
1564
  pty.write(b.toString("utf8"));
1319
1565
  },
1320
1566
  onAnswer: (body) => {
1321
- void postAnswer({ relayBaseUrl: opts.relayBaseUrl, body });
1567
+ if (!body.id.startsWith("local:")) {
1568
+ void postAnswer({ relayBaseUrl: opts.relayBaseUrl, body });
1569
+ }
1322
1570
  capturingPrompt = null;
1323
1571
  applyBandChange();
1324
1572
  },
@@ -1385,6 +1633,8 @@ async function runWrappedAttach(opts) {
1385
1633
  }
1386
1634
  }
1387
1635
  });
1636
+ let cmuxOn = false;
1637
+ let cmuxOrig = null;
1388
1638
  const pollStatus = async () => {
1389
1639
  try {
1390
1640
  const status = await readBoxStatus({
@@ -1395,6 +1645,12 @@ async function runWrappedAttach(opts) {
1395
1645
  const body = opts.mode === "codex" ? status?.codex : opts.mode === "opencode" ? status?.opencode : opts.mode === "shell" ? void 0 : status?.claude;
1396
1646
  const nextTitle = body?.sessionTitle?.trim() || void 0;
1397
1647
  const nextActivity = body?.state || void 0;
1648
+ if (cmuxOn && nextActivity !== lastActivity) {
1649
+ applyCmuxAgentState(opts.mode, nextActivity);
1650
+ if (isAttentionState(nextActivity) && !isAttentionState(lastActivity)) {
1651
+ markCmuxTabAttention(opts.mode, opts.boxName, nextActivity);
1652
+ }
1653
+ }
1398
1654
  const desiredTitle = nextTitle ?? opts.boxName;
1399
1655
  if (desiredTitle !== lastEmittedTitle) {
1400
1656
  lastEmittedTitle = desiredTitle;
@@ -1417,6 +1673,10 @@ async function runWrappedAttach(opts) {
1417
1673
  logErr(`status poll failed: ${e.message}`);
1418
1674
  }
1419
1675
  };
1676
+ if (opts.mode !== "shell") {
1677
+ cmuxOn = await cmuxStatusEnabled();
1678
+ if (cmuxOn) cmuxOrig = captureCmuxWorkspace();
1679
+ }
1420
1680
  void pollStatus();
1421
1681
  const statusTimer = setInterval(() => {
1422
1682
  void pollStatus();
@@ -1505,6 +1765,7 @@ async function runWrappedAttach(opts) {
1505
1765
  process.stdin.off("data", onStdinData);
1506
1766
  process.stdout.off("resize", onResize);
1507
1767
  clearInterval(statusTimer);
1768
+ if (cmuxOn) restoreCmuxWorkspace(cmuxOrig);
1508
1769
  stopSpinner();
1509
1770
  if (flashTimer) clearTimeout(flashTimer);
1510
1771
  if (process.stdin.isTTY) process.stdin.setRawMode(false);
@@ -1520,13 +1781,16 @@ async function runWrappedAttach(opts) {
1520
1781
  teardownPaint += cursorMoveTo(rsFinal, csFinal);
1521
1782
  process.stdout.write(teardownPaint);
1522
1783
  popTerminalTitle();
1523
- if (exitCode === 0 && opts.detachNotice) {
1784
+ if (userKilled) {
1785
+ process.stdout.write(`\x1B[1A\x1B[2K\rbox ${opts.boxName} destroyed
1786
+ `);
1787
+ } else if (exitCode === 0 && opts.detachNotice) {
1524
1788
  process.stdout.write("\x1B[1A\x1B[2K\r" + opts.detachNotice + "\n");
1525
1789
  }
1526
1790
  return exitCode;
1527
1791
  }
1528
1792
  function runFallback(command, argv, env) {
1529
- const child = spawnSync(command, argv, {
1793
+ const child = spawnSync2(command, argv, {
1530
1794
  stdio: "inherit",
1531
1795
  env: env ? { ...process.env, ...env } : process.env
1532
1796
  });
@@ -1817,7 +2081,7 @@ async function cloudAgentStartDetached(args) {
1817
2081
  }
1818
2082
  function runDetached(argv, env) {
1819
2083
  return new Promise((resolve) => {
1820
- const child = spawn3(argv[0], argv.slice(1), {
2084
+ const child = spawn4(argv[0], argv.slice(1), {
1821
2085
  stdio: "ignore",
1822
2086
  env: env ? { ...process.env, ...env } : process.env
1823
2087
  });
@@ -1857,4 +2121,4 @@ export {
1857
2121
  cloudAgentAttach,
1858
2122
  cloudAgentStartDetached
1859
2123
  };
1860
- //# sourceMappingURL=chunk-GYJ62GFL.js.map
2124
+ //# sourceMappingURL=chunk-QYRK5H6Q.js.map