@madarco/agentbox 0.9.0 → 0.10.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 (36) hide show
  1. package/CHANGELOG.md +89 -0
  2. package/README.md +161 -0
  3. package/dist/{_cloud-attach-ZXBCNWJX.js → _cloud-attach-O6NYTLES.js} +3 -3
  4. package/dist/{chunk-BXQMIEHC.js → chunk-2GPORKYF.js} +254 -162
  5. package/dist/chunk-2GPORKYF.js.map +1 -0
  6. package/dist/{chunk-NCJP5MTN.js → chunk-7UIAO7PC.js} +213 -51
  7. package/dist/chunk-7UIAO7PC.js.map +1 -0
  8. package/dist/{chunk-GU5LW4B5.js → chunk-R4O5WPHW.js} +374 -62
  9. package/dist/chunk-R4O5WPHW.js.map +1 -0
  10. package/dist/{dist-GDHP34ZK.js → dist-5FQGYRW5.js} +15 -3
  11. package/dist/dist-5FQGYRW5.js.map +1 -0
  12. package/dist/{dist-32EZBYG4.js → dist-BQNX7RQE.js} +12 -2
  13. package/dist/{dist-XML54CNB.js → dist-PZW3GWWU.js} +30 -5
  14. package/dist/dist-PZW3GWWU.js.map +1 -0
  15. package/dist/{dist-CX5CGVEB.js → dist-TMHSUVTP.js} +3 -3
  16. package/dist/index.js +1773 -526
  17. package/dist/index.js.map +1 -1
  18. package/package.json +9 -7
  19. package/runtime/docker/apps/cli/share/agentbox-setup/SKILL.md +9 -8
  20. package/runtime/docker/packages/ctl/dist/bin.cjs +32 -3
  21. package/runtime/hetzner/agentbox-setup-skill.md +9 -8
  22. package/runtime/hetzner/ctl.cjs +32 -3
  23. package/runtime/relay/bin.cjs +32 -3
  24. package/runtime/vercel/agentbox-setup-skill.md +9 -8
  25. package/runtime/vercel/ctl.cjs +32 -3
  26. package/runtime/vercel/custom-system-CLAUDE.md +1 -4
  27. package/runtime/vercel/scripts/provision.sh +40 -0
  28. package/share/agentbox-setup/SKILL.md +9 -8
  29. package/dist/chunk-BXQMIEHC.js.map +0 -1
  30. package/dist/chunk-GU5LW4B5.js.map +0 -1
  31. package/dist/chunk-NCJP5MTN.js.map +0 -1
  32. package/dist/dist-GDHP34ZK.js.map +0 -1
  33. package/dist/dist-XML54CNB.js.map +0 -1
  34. /package/dist/{_cloud-attach-ZXBCNWJX.js.map → _cloud-attach-O6NYTLES.js.map} +0 -0
  35. /package/dist/{dist-32EZBYG4.js.map → dist-BQNX7RQE.js.map} +0 -0
  36. /package/dist/{dist-CX5CGVEB.js.map → dist-TMHSUVTP.js.map} +0 -0
@@ -2,10 +2,14 @@
2
2
  import {
3
3
  DEFAULT_RELAY_PORT,
4
4
  readBoxStatus
5
- } from "./chunk-NCJP5MTN.js";
5
+ } from "./chunk-7UIAO7PC.js";
6
6
 
7
7
  // src/commands/_cloud-attach.ts
8
8
  import { spawn as spawn3 } from "child_process";
9
+ import { appendFileSync } from "fs";
10
+ import { homedir } from "os";
11
+ import { join as join2 } from "path";
12
+ import { spinner } from "@clack/prompts";
9
13
 
10
14
  // src/provider/registry.ts
11
15
  var KNOWN = ["docker", "daytona", "hetzner", "vercel"];
@@ -15,21 +19,21 @@ function isKnownProvider(name) {
15
19
  async function getProvider(name) {
16
20
  switch (name) {
17
21
  case "docker": {
18
- const mod = await import("./dist-32EZBYG4.js");
22
+ const mod = await import("./dist-BQNX7RQE.js");
19
23
  return mod.dockerProvider;
20
24
  }
21
25
  case "daytona": {
22
- const mod = await import("./dist-CX5CGVEB.js");
26
+ const mod = await import("./dist-TMHSUVTP.js");
23
27
  await mod.ensureDaytonaCredentials();
24
28
  return mod.daytonaProvider;
25
29
  }
26
30
  case "hetzner": {
27
- const mod = await import("./dist-GDHP34ZK.js");
31
+ const mod = await import("./dist-5FQGYRW5.js");
28
32
  await mod.ensureHetznerCredentials();
29
33
  return mod.hetznerProvider;
30
34
  }
31
35
  case "vercel": {
32
- const mod = await import("./dist-XML54CNB.js");
36
+ const mod = await import("./dist-PZW3GWWU.js");
33
37
  await mod.ensureVercelCredentials();
34
38
  return mod.vercelProvider;
35
39
  }
@@ -688,9 +692,13 @@ function statusLine(box, w, stateLabel, groups = HINT_GROUPS, fallbackGroups) {
688
692
  // src/wrapped-pty/footer.ts
689
693
  var SPINNER_FRAMES = ["\u25D0", "\u25D3", "\u25D1", "\u25D2"];
690
694
  var URGENT = "\x1B[38;5;220m\x1B[1m";
695
+ var TITLE = "\x1B[1m\x1B[38;5;253m";
691
696
  var TXT = "\x1B[38;5;250m";
692
697
  var SUBTLE = "\x1B[38;5;245m";
693
698
  var RESET = "\x1B[0m";
699
+ var UNDERLINE = "\x1B[4m";
700
+ var NO_UNDERLINE = "\x1B[24m";
701
+ var QUESTION_ACCENT = "\x1B[38;5;51m\x1B[1m";
694
702
  var NOTICE_BG = "\x1B[48;5;220m";
695
703
  var NOTICE_FG = "\x1B[38;5;16m\x1B[1m";
696
704
  var FLASH_FG = "\x1B[38;5;150m\x1B[1m";
@@ -723,6 +731,17 @@ function padTo(visible, width) {
723
731
  }
724
732
  return visible + " ".repeat(width - visible.length);
725
733
  }
734
+ function answerChip(defaultAnswer) {
735
+ const yesKey = "y Yes";
736
+ const noKey = "n No";
737
+ const sep = " \xB7 ";
738
+ const yesIsDefault = defaultAnswer === "y";
739
+ const yes = yesIsDefault ? `${UNDERLINE}${yesKey}${NO_UNDERLINE}` : yesKey;
740
+ const no = yesIsDefault ? noKey : `${UNDERLINE}${noKey}${NO_UNDERLINE}`;
741
+ const ansi = `${NOTICE_BG}${NOTICE_FG} ${yes}${sep}${no} ${RESET}`;
742
+ const width = ` ${yesKey}${sep}${noKey} `.length;
743
+ return { ansi, width };
744
+ }
726
745
  function renderFooter(state, cols) {
727
746
  if (cols <= 0) return "";
728
747
  if (state.kind === "idle") {
@@ -753,18 +772,16 @@ function renderFooter(state, cols) {
753
772
  return `${BAR_BG}${FLASH_FG}${prefix}${TXT}${message2}${RESET}`;
754
773
  }
755
774
  if (state.kind === "notice") {
756
- const spinner = SPINNER_FRAMES[state.frame % SPINNER_FRAMES.length];
757
- const prefix = ` ${spinner} `;
775
+ const spinner2 = SPINNER_FRAMES[state.frame % SPINNER_FRAMES.length];
776
+ const prefix = ` ${spinner2} `;
758
777
  const inner2 = Math.max(0, cols - prefix.length);
759
778
  const message2 = padTo(state.message, inner2);
760
779
  return `${NOTICE_BG}${NOTICE_FG}${prefix}${message2}${RESET}`;
761
780
  }
762
- const def = state.prompt.defaultAnswer ?? "n";
763
- const yn = def === "y" ? "[Y/n]" : "[y/N]";
781
+ const chip = answerChip(state.prompt.defaultAnswer);
764
782
  const tag = " [!] ";
765
783
  const sep = " ";
766
- const hintW = ` ${yn} `.length;
767
- const inner = Math.max(0, cols - tag.length - hintW);
784
+ const inner = Math.max(0, cols - tag.length - chip.width);
768
785
  const detailRaw = state.prompt.detail ?? "";
769
786
  let message = state.prompt.message;
770
787
  let detail = detailRaw;
@@ -779,7 +796,7 @@ function renderFooter(state, cols) {
779
796
  }
780
797
  const middlePlain = detail.length > 0 ? `${message}${sep}${detail}` : message;
781
798
  const padded = padTo(middlePlain, inner);
782
- return `${BAR_BG}${URGENT}${tag}${TXT}${padded}${SUBTLE} ${yn} ${RESET}`;
799
+ return `${BAR_BG}${URGENT}${tag}${TXT}${padded}${RESET}${chip.ansi}`;
783
800
  }
784
801
  function cursorMoveTo(row, col) {
785
802
  return `\x1B[${String(row)};${String(col)}H`;
@@ -788,6 +805,73 @@ var CURSOR_SAVE = "\x1B7";
788
805
  var CURSOR_RESTORE = "\x1B8";
789
806
  var SYNC_BEGIN = "\x1B[?2026h";
790
807
  var SYNC_END = "\x1B[?2026l";
808
+ var ALERT_BAND_ROWS = 3;
809
+ function blankBar(cols, bg) {
810
+ return `${bg}${" ".repeat(Math.max(0, cols))}${RESET}`;
811
+ }
812
+ function renderPromptBand(prompt, cols, rows) {
813
+ const tag = " [!] ";
814
+ const indent = " ".repeat(tag.length);
815
+ const contW = Math.max(0, cols - indent.length);
816
+ const chip = answerChip(prompt.defaultAnswer);
817
+ const title = (prompt.context?.command ?? "confirm").toUpperCase();
818
+ const titleW = Math.max(0, cols - tag.length - chip.width);
819
+ const titlePadded = padTo(title, titleW);
820
+ const line1 = `${BAR_BG}${URGENT}${tag}${TITLE}${titlePadded}${RESET}${chip.ansi}`;
821
+ const line2 = `${BAR_BG}${TXT}${indent}${padTo(prompt.message, contW)}${RESET}`;
822
+ const line3 = `${BAR_BG}${SUBTLE}${indent}${padTo(prompt.detail ?? "", contW)}${RESET}`;
823
+ return [line1, line2, line3].slice(0, rows);
824
+ }
825
+ function renderNoticeBand(message, frame, cols, rows) {
826
+ const spinner2 = SPINNER_FRAMES[frame % SPINNER_FRAMES.length];
827
+ const prefix = ` ${spinner2} `;
828
+ const indent = " ".repeat(prefix.length);
829
+ const firstW = Math.max(0, cols - prefix.length);
830
+ const contW = Math.max(0, cols - indent.length);
831
+ const out = [];
832
+ let i = 0;
833
+ for (let r = 0; r < rows; r++) {
834
+ const isLast = r === rows - 1;
835
+ const w = r === 0 ? firstW : contW;
836
+ let cell;
837
+ if (i >= message.length) {
838
+ cell = " ".repeat(w);
839
+ } else if (isLast) {
840
+ cell = padTo(message.slice(i), w);
841
+ i = message.length;
842
+ } else {
843
+ cell = message.slice(i, i + w).padEnd(w);
844
+ i += w;
845
+ }
846
+ const lead = r === 0 ? prefix : indent;
847
+ out.push(`${NOTICE_BG}${NOTICE_FG}${lead}${cell}${RESET}`);
848
+ }
849
+ return out;
850
+ }
851
+ function renderQuestionBand(payload, cols, rows) {
852
+ const q = payload.questions[0];
853
+ if (!q) return Array.from({ length: rows }, () => blankBar(cols, BAR_BG));
854
+ const tag = " [?] ";
855
+ const indent = " ".repeat(tag.length);
856
+ const innerW = Math.max(0, cols - tag.length);
857
+ const contW = Math.max(0, cols - indent.length);
858
+ const header = q.header && q.header.trim().length > 0 ? q.header : "Question";
859
+ const headerPadded = padTo(header, innerW);
860
+ const line1 = `${BAR_BG}${QUESTION_ACCENT}${tag}${TXT}${headerPadded}${RESET}`;
861
+ const questionText = padTo(q.question, contW);
862
+ const line2 = `${BAR_BG}${TXT}${indent}${questionText}${RESET}`;
863
+ const optLabels = q.options.map((o) => o.label).join(" \xB7 ");
864
+ const optsLine = optLabels.length > 0 ? `options: ${optLabels}` : "";
865
+ const optsPadded = padTo(optsLine, contW);
866
+ const line3 = `${BAR_BG}${SUBTLE}${indent}${optsPadded}${RESET}`;
867
+ return [line1, line2, line3].slice(0, rows);
868
+ }
869
+ function renderAlertBand(state, cols, rows = ALERT_BAND_ROWS) {
870
+ if (cols <= 0 || rows <= 0) return Array.from({ length: Math.max(0, rows) }, () => "");
871
+ if (state.kind === "prompt") return renderPromptBand(state.prompt, cols, rows);
872
+ if (state.kind === "notice") return renderNoticeBand(state.message, state.frame, cols, rows);
873
+ return renderQuestionBand(state.question, cols, rows);
874
+ }
791
875
 
792
876
  // src/wrapped-pty/prompt-client.ts
793
877
  import { request as httpRequest } from "http";
@@ -956,9 +1040,13 @@ function postAnswer(opts) {
956
1040
 
957
1041
  // src/wrapped-pty/run.ts
958
1042
  var FOOTER_ROWS = 1;
1043
+ var MIN_INNER_ROWS = 5;
959
1044
  var STATUS_POLL_INTERVAL_MS = 3e3;
960
1045
  var SPINNER_INTERVAL_MS = 120;
961
1046
  var FLASH_DURATION_MS = 2e3;
1047
+ var RAPID_RECONNECT_MS = 8e3;
1048
+ var MAX_RAPID_RECONNECTS = 3;
1049
+ var CHECKPOINT_DROP_GRACE_MS = 4e3;
962
1050
  var ACTION_FLASH = {
963
1051
  screen: "Opening noVNC viewer\u2026",
964
1052
  code: "Launching VS Code / Cursor\u2026",
@@ -1011,12 +1099,19 @@ async function runWrappedAttach(opts) {
1011
1099
  const cols = process.stdout.columns ?? 80;
1012
1100
  const rows = process.stdout.rows ?? 24;
1013
1101
  const innerRows = Math.max(1, rows - FOOTER_ROWS);
1014
- const pty = backend.ptySpawn(command, opts.dockerArgv, {
1102
+ let pty = backend.ptySpawn(command, opts.dockerArgv, {
1015
1103
  name: "xterm-256color",
1016
1104
  cols,
1017
1105
  rows: innerRows,
1018
1106
  env: opts.env ? { ...process.env, ...opts.env } : process.env
1019
1107
  });
1108
+ let lastSpawnAt = Date.now();
1109
+ const resizePty = (c, r) => {
1110
+ try {
1111
+ pty.resize(c, r);
1112
+ } catch {
1113
+ }
1114
+ };
1020
1115
  pushTerminalTitle();
1021
1116
  let lastEmittedTitle = opts.boxName;
1022
1117
  setTerminalTitle(lastEmittedTitle);
@@ -1036,21 +1131,46 @@ async function runWrappedAttach(opts) {
1036
1131
  let lastActivity;
1037
1132
  let capturingPrompt = null;
1038
1133
  let activeNotice = null;
1134
+ let reconnectBanner = null;
1039
1135
  let noticeFrame = 0;
1136
+ let questionPayload = null;
1137
+ let bandState = null;
1138
+ let bandReservedRows = 0;
1040
1139
  let spinnerTimer = null;
1041
1140
  let flashMessage = null;
1042
1141
  let flashTimer = null;
1043
- const redrawFooter = () => {
1142
+ let reconnecting = false;
1143
+ let reconnectAbort = null;
1144
+ let userDetached = false;
1145
+ let checkpointNoticeAt = 0;
1146
+ let checkpointNoticeClearedAt = 0;
1147
+ const reservedRows = () => FOOTER_ROWS + bandReservedRows;
1148
+ const bandFits = () => {
1149
+ const rs = process.stdout.rows ?? rows;
1150
+ return rs - FOOTER_ROWS - ALERT_BAND_ROWS >= MIN_INNER_ROWS;
1151
+ };
1152
+ const redrawChrome = () => {
1044
1153
  const cs = process.stdout.columns ?? cols;
1045
1154
  const rs = process.stdout.rows ?? rows;
1046
- const line = renderFooter(footerState, cs);
1047
- const payload = SYNC_BEGIN + CURSOR_SAVE + cursorMoveTo(rs, 1) + line + CURSOR_RESTORE + SYNC_END;
1155
+ const footerLine = renderFooter(footerState, cs);
1156
+ let payload = SYNC_BEGIN + CURSOR_SAVE;
1157
+ if (bandReservedRows > 0 && bandState) {
1158
+ const bandLines = renderAlertBand(bandState, cs, bandReservedRows);
1159
+ for (let i = 0; i < bandLines.length; i++) {
1160
+ const row = rs - FOOTER_ROWS - (bandLines.length - i);
1161
+ payload += cursorMoveTo(row + 1, 1) + bandLines[i];
1162
+ }
1163
+ }
1164
+ payload += cursorMoveTo(rs, 1) + footerLine + CURSOR_RESTORE + SYNC_END;
1048
1165
  process.stdout.write(payload);
1049
1166
  };
1050
1167
  const recomputeFooter = () => {
1051
- if (capturingPrompt) {
1168
+ const collapsed = bandState !== null && bandReservedRows === 0;
1169
+ if (collapsed && reconnectBanner) {
1170
+ footerState = { kind: "notice", message: reconnectBanner, frame: noticeFrame };
1171
+ } else if (collapsed && capturingPrompt) {
1052
1172
  footerState = { kind: "prompt", prompt: capturingPrompt };
1053
- } else if (activeNotice) {
1173
+ } else if (collapsed && activeNotice) {
1054
1174
  footerState = { kind: "notice", message: activeNotice.message, frame: noticeFrame };
1055
1175
  } else if (flashMessage) {
1056
1176
  footerState = { kind: "flash", message: flashMessage };
@@ -1058,13 +1178,48 @@ async function runWrappedAttach(opts) {
1058
1178
  footerState = buildIdle(lastSessionTitle, lastActivity);
1059
1179
  }
1060
1180
  };
1181
+ const recomputeBand = () => {
1182
+ if (reconnectBanner) {
1183
+ bandState = { kind: "notice", message: reconnectBanner, frame: noticeFrame };
1184
+ } else if (capturingPrompt) {
1185
+ bandState = { kind: "prompt", prompt: capturingPrompt };
1186
+ } else if (activeNotice) {
1187
+ bandState = { kind: "notice", message: activeNotice.message, frame: noticeFrame };
1188
+ } else if (questionPayload) {
1189
+ bandState = { kind: "question", question: questionPayload };
1190
+ } else {
1191
+ bandState = null;
1192
+ }
1193
+ };
1194
+ const relayoutForBand = () => {
1195
+ const cs = process.stdout.columns ?? cols;
1196
+ const rs = process.stdout.rows ?? rows;
1197
+ const inner = Math.max(1, rs - reservedRows());
1198
+ resizePty(cs, inner);
1199
+ process.stdout.write(`\x1B[1;${String(inner)}r`);
1200
+ let clear = SYNC_BEGIN + CURSOR_SAVE;
1201
+ for (let r = inner + 1; r <= rs; r++) clear += cursorMoveTo(r, 1) + "\x1B[2K";
1202
+ clear += CURSOR_RESTORE + SYNC_END;
1203
+ process.stdout.write(clear);
1204
+ };
1205
+ const applyBandChange = () => {
1206
+ recomputeBand();
1207
+ const wantRows = bandState && bandFits() ? ALERT_BAND_ROWS : 0;
1208
+ if (wantRows !== bandReservedRows) {
1209
+ bandReservedRows = wantRows;
1210
+ relayoutForBand();
1211
+ }
1212
+ recomputeFooter();
1213
+ redrawChrome();
1214
+ };
1061
1215
  const startSpinner = () => {
1062
1216
  if (spinnerTimer) return;
1063
1217
  spinnerTimer = setInterval(() => {
1064
1218
  noticeFrame++;
1065
- if (footerState.kind === "notice") {
1066
- recomputeFooter();
1067
- redrawFooter();
1219
+ if (bandState?.kind === "notice") {
1220
+ bandState = { kind: "notice", message: bandState.message, frame: noticeFrame };
1221
+ if (bandReservedRows === 0) recomputeFooter();
1222
+ redrawChrome();
1068
1223
  }
1069
1224
  }, SPINNER_INTERVAL_MS);
1070
1225
  if (typeof spinnerTimer.unref === "function") spinnerTimer.unref();
@@ -1075,14 +1230,20 @@ async function runWrappedAttach(opts) {
1075
1230
  spinnerTimer = null;
1076
1231
  }
1077
1232
  };
1078
- pty.onData((d) => {
1079
- process.stdout.write(d);
1080
- redrawFooter();
1081
- });
1233
+ const wireOutput = () => {
1234
+ pty.onData((d) => {
1235
+ process.stdout.write(d);
1236
+ redrawChrome();
1237
+ });
1238
+ };
1239
+ wireOutput();
1082
1240
  const leaderChords = detachable ? { c: "code", s: "screen", u: "url", d: "detach" } : { c: "code", s: "screen", u: "url" };
1083
1241
  const runAction = (name) => {
1084
1242
  if (name === "detach") {
1085
- pty.write("d");
1243
+ if (!reconnecting) {
1244
+ userDetached = true;
1245
+ pty.write("d");
1246
+ }
1086
1247
  return;
1087
1248
  }
1088
1249
  const cliEntry = process.argv[1];
@@ -1104,11 +1265,11 @@ async function runWrappedAttach(opts) {
1104
1265
  flashTimer = null;
1105
1266
  flashMessage = null;
1106
1267
  recomputeFooter();
1107
- redrawFooter();
1268
+ redrawChrome();
1108
1269
  }, FLASH_DURATION_MS);
1109
1270
  if (typeof flashTimer.unref === "function") flashTimer.unref();
1110
1271
  recomputeFooter();
1111
- redrawFooter();
1272
+ redrawChrome();
1112
1273
  };
1113
1274
  const handlePasteImage = async () => {
1114
1275
  if (!opts.onPasteImage) return;
@@ -1118,7 +1279,7 @@ async function runWrappedAttach(opts) {
1118
1279
  }
1119
1280
  flashMessage = "Pasting image\u2026";
1120
1281
  recomputeFooter();
1121
- redrawFooter();
1282
+ redrawChrome();
1122
1283
  let result = "error";
1123
1284
  try {
1124
1285
  result = await opts.onPasteImage();
@@ -1130,27 +1291,30 @@ async function runWrappedAttach(opts) {
1130
1291
  flashTimer = null;
1131
1292
  flashMessage = null;
1132
1293
  recomputeFooter();
1133
- redrawFooter();
1294
+ redrawChrome();
1134
1295
  }, FLASH_DURATION_MS);
1135
1296
  if (typeof flashTimer.unref === "function") flashTimer.unref();
1136
1297
  recomputeFooter();
1137
- redrawFooter();
1298
+ redrawChrome();
1138
1299
  };
1139
1300
  const router = createInputRouter({
1140
1301
  onForward: (b) => {
1302
+ if (reconnecting) {
1303
+ if (b.length === 1 && b[0] === 3) reconnectAbort?.abort();
1304
+ return;
1305
+ }
1141
1306
  pty.write(b.toString("utf8"));
1142
1307
  },
1143
1308
  onAnswer: (body) => {
1144
1309
  void postAnswer({ relayBaseUrl: opts.relayBaseUrl, body });
1145
1310
  capturingPrompt = null;
1146
- recomputeFooter();
1147
- redrawFooter();
1311
+ applyBandChange();
1148
1312
  },
1149
1313
  leaderChords,
1150
1314
  onLeaderChange: (open) => {
1151
1315
  leaderActive = open;
1152
1316
  recomputeFooter();
1153
- redrawFooter();
1317
+ redrawChrome();
1154
1318
  },
1155
1319
  onAction: (name) => {
1156
1320
  runAction(name);
@@ -1166,10 +1330,12 @@ async function runWrappedAttach(opts) {
1166
1330
  const onResize = () => {
1167
1331
  const cs = process.stdout.columns ?? cols;
1168
1332
  const rs = process.stdout.rows ?? rows;
1169
- const inner = Math.max(1, rs - FOOTER_ROWS);
1170
- pty.resize(cs, inner);
1333
+ bandReservedRows = bandState && bandFits() ? ALERT_BAND_ROWS : 0;
1334
+ const inner = Math.max(1, rs - reservedRows());
1335
+ resizePty(cs, inner);
1171
1336
  process.stdout.write(`\x1B[1;${String(inner)}r`);
1172
- redrawFooter();
1337
+ recomputeFooter();
1338
+ redrawChrome();
1173
1339
  };
1174
1340
  process.stdout.on("resize", onResize);
1175
1341
  const stream = subscribePrompts({
@@ -1177,8 +1343,7 @@ async function runWrappedAttach(opts) {
1177
1343
  boxId: opts.boxId,
1178
1344
  onPrompt: (ev) => {
1179
1345
  capturingPrompt = ev;
1180
- recomputeFooter();
1181
- redrawFooter();
1346
+ applyBandChange();
1182
1347
  router.capture(ev).catch((e) => {
1183
1348
  const msg = e instanceof Error ? e.message : String(e);
1184
1349
  if (msg !== "resolved-elsewhere") {
@@ -1190,22 +1355,21 @@ async function runWrappedAttach(opts) {
1190
1355
  if (capturingPrompt && capturingPrompt.id === id) {
1191
1356
  capturingPrompt = null;
1192
1357
  router.abort("resolved-elsewhere");
1193
- recomputeFooter();
1194
- redrawFooter();
1358
+ applyBandChange();
1195
1359
  }
1196
1360
  },
1197
1361
  onNotice: (ev) => {
1362
+ if (ev.kind === "checkpoint") checkpointNoticeAt = Date.now();
1198
1363
  activeNotice = ev;
1199
1364
  startSpinner();
1200
- recomputeFooter();
1201
- redrawFooter();
1365
+ applyBandChange();
1202
1366
  },
1203
1367
  onNoticeCleared: (id) => {
1204
1368
  if (activeNotice && activeNotice.id === id) {
1369
+ if (activeNotice.kind === "checkpoint") checkpointNoticeClearedAt = Date.now();
1205
1370
  activeNotice = null;
1206
1371
  stopSpinner();
1207
- recomputeFooter();
1208
- redrawFooter();
1372
+ applyBandChange();
1209
1373
  }
1210
1374
  }
1211
1375
  });
@@ -1224,12 +1388,18 @@ async function runWrappedAttach(opts) {
1224
1388
  lastEmittedTitle = desiredTitle;
1225
1389
  setTerminalTitle(desiredTitle);
1226
1390
  }
1391
+ const nextQuestion = opts.mode === "claude" && status?.claude.state === "question" ? status.claude.question ?? null : null;
1392
+ const questionChanged = (nextQuestion?.capturedAt ?? null) !== (questionPayload?.capturedAt ?? null);
1393
+ if (questionChanged) {
1394
+ questionPayload = nextQuestion;
1395
+ applyBandChange();
1396
+ }
1227
1397
  if (nextTitle === lastSessionTitle && nextActivity === lastActivity) return;
1228
1398
  lastSessionTitle = nextTitle;
1229
1399
  lastActivity = nextActivity;
1230
1400
  if (footerState.kind === "idle") {
1231
1401
  recomputeFooter();
1232
- redrawFooter();
1402
+ redrawChrome();
1233
1403
  }
1234
1404
  } catch (e) {
1235
1405
  logErr(`status poll failed: ${e.message}`);
@@ -1244,10 +1414,82 @@ async function runWrappedAttach(opts) {
1244
1414
  if (opts.mode === "shell" && !detachable) {
1245
1415
  process.stdout.write("\x1B[H\x1B[2J");
1246
1416
  }
1247
- redrawFooter();
1248
- const exitCode = await new Promise((resolve) => {
1249
- pty.onExit(({ exitCode: exitCode2 }) => resolve(exitCode2));
1250
- });
1417
+ redrawChrome();
1418
+ const reconnectFlow = async (code) => {
1419
+ const controller = new AbortController();
1420
+ reconnecting = true;
1421
+ reconnectAbort = controller;
1422
+ reconnectBanner = "box rebooting \u2014 reconnecting\u2026";
1423
+ startSpinner();
1424
+ applyBandChange();
1425
+ let spec = null;
1426
+ try {
1427
+ spec = await opts.reconnect?.(controller.signal, code) ?? null;
1428
+ } catch (e) {
1429
+ logErr(`reconnect failed: ${e.message}`);
1430
+ } finally {
1431
+ reconnecting = false;
1432
+ reconnectAbort = null;
1433
+ reconnectBanner = null;
1434
+ if (!activeNotice) stopSpinner();
1435
+ applyBandChange();
1436
+ }
1437
+ if (spec) {
1438
+ flashMessage = "reconnected";
1439
+ if (flashTimer) clearTimeout(flashTimer);
1440
+ flashTimer = setTimeout(() => {
1441
+ flashTimer = null;
1442
+ flashMessage = null;
1443
+ recomputeFooter();
1444
+ redrawChrome();
1445
+ }, FLASH_DURATION_MS);
1446
+ if (typeof flashTimer.unref === "function") flashTimer.unref();
1447
+ recomputeFooter();
1448
+ redrawChrome();
1449
+ }
1450
+ return spec;
1451
+ };
1452
+ let exitCode = 0;
1453
+ let rapidFails = 0;
1454
+ for (; ; ) {
1455
+ const code = await new Promise((resolve) => {
1456
+ pty.onExit(({ exitCode: exitCode2 }) => resolve(exitCode2));
1457
+ });
1458
+ if (userDetached || !opts.reconnect) {
1459
+ exitCode = code;
1460
+ break;
1461
+ }
1462
+ const checkpointing = checkpointNoticeAt > checkpointNoticeClearedAt || Date.now() - checkpointNoticeClearedAt < CHECKPOINT_DROP_GRACE_MS;
1463
+ if (!checkpointing && code === 0) {
1464
+ exitCode = code;
1465
+ break;
1466
+ }
1467
+ rapidFails = Date.now() - lastSpawnAt < RAPID_RECONNECT_MS ? rapidFails + 1 : 0;
1468
+ if (rapidFails >= MAX_RAPID_RECONNECTS) {
1469
+ logErr("giving up reconnect after repeated rapid failures");
1470
+ exitCode = code;
1471
+ break;
1472
+ }
1473
+ const next = await reconnectFlow(code);
1474
+ if (!next) {
1475
+ exitCode = code;
1476
+ break;
1477
+ }
1478
+ const rsNow = process.stdout.rows ?? rows;
1479
+ const innerNow = Math.max(1, rsNow - reservedRows());
1480
+ pty = backend.ptySpawn(next.command, next.argv, {
1481
+ name: "xterm-256color",
1482
+ cols: process.stdout.columns ?? cols,
1483
+ rows: innerNow,
1484
+ env: next.env ? { ...process.env, ...next.env } : process.env
1485
+ });
1486
+ wireOutput();
1487
+ lastSpawnAt = Date.now();
1488
+ checkpointNoticeAt = 0;
1489
+ checkpointNoticeClearedAt = 0;
1490
+ process.stdout.write(`\x1B[1;${String(innerNow)}r`);
1491
+ redrawChrome();
1492
+ }
1251
1493
  process.stdin.off("data", onStdinData);
1252
1494
  process.stdout.off("resize", onResize);
1253
1495
  clearInterval(statusTimer);
@@ -1259,9 +1501,12 @@ async function runWrappedAttach(opts) {
1259
1501
  router.dispose();
1260
1502
  const rsFinal = process.stdout.rows ?? rows;
1261
1503
  const csFinal = process.stdout.columns ?? cols;
1262
- process.stdout.write(
1263
- "\x1B[r" + cursorMoveTo(rsFinal, 1) + `\x1B[2K` + cursorMoveTo(rsFinal, csFinal)
1264
- );
1504
+ let teardownPaint = "\x1B[r";
1505
+ for (let r = rsFinal - bandReservedRows; r <= rsFinal; r++) {
1506
+ if (r >= 1) teardownPaint += cursorMoveTo(r, 1) + "\x1B[2K";
1507
+ }
1508
+ teardownPaint += cursorMoveTo(rsFinal, csFinal);
1509
+ process.stdout.write(teardownPaint);
1265
1510
  popTerminalTitle();
1266
1511
  if (exitCode === 0 && opts.detachNotice) {
1267
1512
  process.stdout.write("\x1B[1A\x1B[2K\r" + opts.detachNotice + "\n");
@@ -1423,6 +1668,24 @@ async function pasteHostClipboardImage(provider, box) {
1423
1668
 
1424
1669
  // src/commands/_cloud-attach.ts
1425
1670
  var RELAY_HOST_URL = `http://127.0.0.1:${String(DEFAULT_RELAY_PORT)}`;
1671
+ var RECONNECT_TIMEOUT_MS = 5 * 6e4;
1672
+ function abortableSleep(ms, signal) {
1673
+ return new Promise((resolve) => {
1674
+ if (signal.aborted) {
1675
+ resolve();
1676
+ return;
1677
+ }
1678
+ const t = setTimeout(resolve, ms);
1679
+ signal.addEventListener(
1680
+ "abort",
1681
+ () => {
1682
+ clearTimeout(t);
1683
+ resolve();
1684
+ },
1685
+ { once: true }
1686
+ );
1687
+ });
1688
+ }
1426
1689
  function buildCloudAttachInnerCommand(binary, extraArgs) {
1427
1690
  if (!extraArgs || extraArgs.length === 0) {
1428
1691
  return `bash -lc exec\\ ${binary}`;
@@ -1435,10 +1698,22 @@ async function cloudAgentAttach(args) {
1435
1698
  if (!provider.buildAttach) {
1436
1699
  throw new Error(`provider '${provider.name}' does not support interactive attach`);
1437
1700
  }
1701
+ const buildAttach = provider.buildAttach.bind(provider);
1702
+ let box = args.box;
1703
+ const state = await provider.probeState(box);
1704
+ if (state === "missing") {
1705
+ throw new Error(`cloud sandbox for ${box.name} is missing; was it destroyed?`);
1706
+ }
1707
+ if (state !== "running") {
1708
+ const s = spinner();
1709
+ s.start(state === "paused" ? "resuming box" : "starting box");
1710
+ box = await provider.start(box);
1711
+ s.stop("box running");
1712
+ }
1438
1713
  const command = buildCloudAttachInnerCommand(args.binary, args.extraArgs);
1439
- const safeOpenIn = args.box.provider === "daytona" ? "same" : args.openIn;
1714
+ const safeOpenIn = box.provider === "daytona" ? "same" : args.openIn;
1440
1715
  if (safeOpenIn && safeOpenIn !== "same" && args.extraArgs && args.extraArgs.length > 0) {
1441
- const pre = await provider.buildAttach(args.box, "agent", {
1716
+ const pre = await provider.buildAttach(box, "agent", {
1442
1717
  sessionName: args.sessionName,
1443
1718
  command,
1444
1719
  detached: true
@@ -1449,25 +1724,60 @@ async function cloudAgentAttach(args) {
1449
1724
  if (pre.cleanup) await pre.cleanup();
1450
1725
  }
1451
1726
  }
1452
- const spec = await provider.buildAttach(args.box, "agent", {
1727
+ let spec = await provider.buildAttach(box, "agent", {
1453
1728
  sessionName: args.sessionName,
1454
1729
  command
1455
1730
  });
1456
1731
  const canPaste = args.mode === "claude" && await clipboardCaptureAvailable();
1732
+ const reconnect = async (signal) => {
1733
+ const deadline = Date.now() + RECONNECT_TIMEOUT_MS;
1734
+ let backoff = 500;
1735
+ for (; ; ) {
1736
+ if (signal.aborted || Date.now() > deadline) return null;
1737
+ try {
1738
+ box = await provider.start(box);
1739
+ break;
1740
+ } catch {
1741
+ await abortableSleep(backoff, signal);
1742
+ backoff = Math.min(backoff * 2, 5e3);
1743
+ }
1744
+ }
1745
+ if (signal.aborted) return null;
1746
+ const prev = spec;
1747
+ spec = await buildAttach(box, "agent", { sessionName: args.sessionName, command });
1748
+ if (prev.cleanup) {
1749
+ try {
1750
+ await prev.cleanup();
1751
+ } catch {
1752
+ }
1753
+ }
1754
+ return { command: spec.argv[0], argv: spec.argv.slice(1), env: spec.env };
1755
+ };
1457
1756
  try {
1458
1757
  const code = await runWrappedAttach({
1459
- container: args.box.name,
1758
+ container: box.name,
1460
1759
  command: spec.argv[0],
1461
1760
  dockerArgv: spec.argv.slice(1),
1462
1761
  env: spec.env,
1463
1762
  relayBaseUrl: RELAY_HOST_URL,
1464
- boxId: args.box.id,
1465
- boxName: args.box.name,
1466
- projectIndex: args.box.projectIndex,
1763
+ boxId: box.id,
1764
+ boxName: box.name,
1765
+ projectIndex: box.projectIndex,
1467
1766
  mode: args.mode,
1468
1767
  detachable: true,
1469
1768
  openIn: safeOpenIn,
1470
- onPasteImage: canPaste ? () => pasteHostClipboardImage(provider, args.box) : void 0
1769
+ reconnect,
1770
+ onError: (msg) => {
1771
+ try {
1772
+ appendFileSync(
1773
+ join2(homedir(), ".agentbox", "logs", "attach.log"),
1774
+ `${(/* @__PURE__ */ new Date()).toISOString()} [${box.name}] ${msg}
1775
+ `
1776
+ );
1777
+ } catch {
1778
+ }
1779
+ },
1780
+ onPasteImage: canPaste ? () => pasteHostClipboardImage(provider, box) : void 0
1471
1781
  });
1472
1782
  process.exit(code);
1473
1783
  } finally {
@@ -1505,6 +1815,8 @@ export {
1505
1815
  ADVANCED_HINT_GROUPS,
1506
1816
  statusLine,
1507
1817
  renderFooter,
1818
+ ALERT_BAND_ROWS,
1819
+ renderAlertBand,
1508
1820
  subscribePrompts,
1509
1821
  postAnswer,
1510
1822
  runWrappedAttach,
@@ -1513,4 +1825,4 @@ export {
1513
1825
  buildCloudAttachInnerCommand,
1514
1826
  cloudAgentAttach
1515
1827
  };
1516
- //# sourceMappingURL=chunk-GU5LW4B5.js.map
1828
+ //# sourceMappingURL=chunk-R4O5WPHW.js.map