@madarco/agentbox 0.8.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.
- package/CHANGELOG.md +89 -0
- package/README.md +161 -0
- package/dist/{_cloud-attach-T727ZPRV.js → _cloud-attach-O6NYTLES.js} +4 -4
- package/dist/{chunk-67N47KUS.js → chunk-2GPORKYF.js} +349 -182
- package/dist/chunk-2GPORKYF.js.map +1 -0
- package/dist/{chunk-6OZDFNBF.js → chunk-7UIAO7PC.js} +401 -82
- package/dist/chunk-7UIAO7PC.js.map +1 -0
- package/dist/{chunk-BGK32PZE.js → chunk-KL36BRN4.js} +2 -2
- package/dist/chunk-KL36BRN4.js.map +1 -0
- package/dist/chunk-MTVI44DW.js +662 -0
- package/dist/chunk-MTVI44DW.js.map +1 -0
- package/dist/{chunk-FODMEHD3.js → chunk-R4O5WPHW.js} +705 -77
- package/dist/chunk-R4O5WPHW.js.map +1 -0
- package/dist/{dist-ZODPD2I6.js → dist-5FQGYRW5.js} +20 -10
- package/dist/dist-5FQGYRW5.js.map +1 -0
- package/dist/{dist-LOZBWMBF.js → dist-BQNX7RQE.js} +19 -3
- package/dist/dist-PZW3GWWU.js +874 -0
- package/dist/dist-PZW3GWWU.js.map +1 -0
- package/dist/{dist-L4LCG5SJ.js → dist-TMHSUVTP.js} +4 -4
- package/dist/index.js +2385 -842
- package/dist/index.js.map +1 -1
- package/dist/{prepared-state-CL4CWXQA-ME4HSKDE.js → prepared-state-CL4CWXQA-H5THETIM.js} +2 -2
- package/package.json +11 -7
- package/runtime/docker/apps/cli/share/agentbox-setup/SKILL.md +9 -8
- package/runtime/docker/packages/ctl/dist/bin.cjs +129 -31
- package/runtime/docker/packages/sandbox-docker/scripts/agentbox-vnc-start +15 -1
- package/runtime/hetzner/agentbox-setup-skill.md +9 -8
- package/runtime/hetzner/agentbox-vnc-start +15 -1
- package/runtime/hetzner/ctl.cjs +129 -31
- package/runtime/relay/bin.cjs +260 -39
- package/runtime/vercel/agentbox-checkpoint-cleanup +52 -0
- package/runtime/vercel/agentbox-codex-hooks.json +68 -0
- package/runtime/vercel/agentbox-open +28 -0
- package/runtime/vercel/agentbox-setup-skill.md +197 -0
- package/runtime/vercel/agentbox-vnc-start +91 -0
- package/runtime/vercel/claude-managed-settings.json +115 -0
- package/runtime/vercel/ctl.cjs +23495 -0
- package/runtime/vercel/custom-system-CLAUDE.md +47 -0
- package/runtime/vercel/gh-shim +263 -0
- package/runtime/vercel/git-shim +131 -0
- package/runtime/vercel/scripts/provision.sh +314 -0
- package/share/agentbox-setup/SKILL.md +9 -8
- package/dist/chunk-67N47KUS.js.map +0 -1
- package/dist/chunk-6OZDFNBF.js.map +0 -1
- package/dist/chunk-BGK32PZE.js.map +0 -1
- package/dist/chunk-FODMEHD3.js.map +0 -1
- package/dist/dist-ZODPD2I6.js.map +0 -1
- /package/dist/{_cloud-attach-T727ZPRV.js.map → _cloud-attach-O6NYTLES.js.map} +0 -0
- /package/dist/{dist-LOZBWMBF.js.map → dist-BQNX7RQE.js.map} +0 -0
- /package/dist/{dist-L4LCG5SJ.js.map → dist-TMHSUVTP.js.map} +0 -0
- /package/dist/{prepared-state-CL4CWXQA-ME4HSKDE.js.map → prepared-state-CL4CWXQA-H5THETIM.js.map} +0 -0
|
@@ -2,32 +2,41 @@
|
|
|
2
2
|
import {
|
|
3
3
|
DEFAULT_RELAY_PORT,
|
|
4
4
|
readBoxStatus
|
|
5
|
-
} from "./chunk-
|
|
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
|
-
var KNOWN = ["docker", "daytona", "hetzner"];
|
|
15
|
+
var KNOWN = ["docker", "daytona", "hetzner", "vercel"];
|
|
12
16
|
function isKnownProvider(name) {
|
|
13
17
|
return KNOWN.includes(name);
|
|
14
18
|
}
|
|
15
19
|
async function getProvider(name) {
|
|
16
20
|
switch (name) {
|
|
17
21
|
case "docker": {
|
|
18
|
-
const mod = await import("./dist-
|
|
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-
|
|
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-
|
|
31
|
+
const mod = await import("./dist-5FQGYRW5.js");
|
|
28
32
|
await mod.ensureHetznerCredentials();
|
|
29
33
|
return mod.hetznerProvider;
|
|
30
34
|
}
|
|
35
|
+
case "vercel": {
|
|
36
|
+
const mod = await import("./dist-PZW3GWWU.js");
|
|
37
|
+
await mod.ensureVercelCredentials();
|
|
38
|
+
return mod.vercelProvider;
|
|
39
|
+
}
|
|
31
40
|
default:
|
|
32
41
|
throw new Error(`unknown sandbox provider: ${String(name)}`);
|
|
33
42
|
}
|
|
@@ -179,6 +188,25 @@ function runQuiet(cmd, argv) {
|
|
|
179
188
|
});
|
|
180
189
|
}
|
|
181
190
|
|
|
191
|
+
// src/terminal/title.ts
|
|
192
|
+
var ESC = "\x1B";
|
|
193
|
+
var BEL = "\x07";
|
|
194
|
+
function sanitize(title) {
|
|
195
|
+
return title.replace(/[\x00-\x1f\x7f]/g, " ").trim();
|
|
196
|
+
}
|
|
197
|
+
function setTerminalTitle(title, stream = process.stdout) {
|
|
198
|
+
if (!stream.isTTY) return;
|
|
199
|
+
stream.write(`${ESC}]0;${sanitize(title)}${BEL}`);
|
|
200
|
+
}
|
|
201
|
+
function pushTerminalTitle(stream = process.stdout) {
|
|
202
|
+
if (!stream.isTTY) return;
|
|
203
|
+
stream.write(`${ESC}[22;2t`);
|
|
204
|
+
}
|
|
205
|
+
function popTerminalTitle(stream = process.stdout) {
|
|
206
|
+
if (!stream.isTTY) return;
|
|
207
|
+
stream.write(`${ESC}[23;2t`);
|
|
208
|
+
}
|
|
209
|
+
|
|
182
210
|
// src/wrapped-pty/input-router.ts
|
|
183
211
|
var KEY_ENTER = 13;
|
|
184
212
|
var KEY_LF = 10;
|
|
@@ -189,12 +217,58 @@ var KEY_Y_UP = 89;
|
|
|
189
217
|
var KEY_N_LOW = 110;
|
|
190
218
|
var KEY_N_UP = 78;
|
|
191
219
|
var KEY_LEADER = 1;
|
|
220
|
+
var KEY_CTRL_V = 22;
|
|
221
|
+
var KEY_A_LOW = 97;
|
|
222
|
+
function parseCsiKey(buf, i) {
|
|
223
|
+
if (buf[i] !== KEY_ESC || buf[i + 1] !== 91) return null;
|
|
224
|
+
const params = [];
|
|
225
|
+
let val = -1;
|
|
226
|
+
for (let j = i + 2; j < buf.length; j++) {
|
|
227
|
+
const b = buf[j];
|
|
228
|
+
if (b !== void 0 && b >= 48 && b <= 57) {
|
|
229
|
+
val = (val < 0 ? 0 : val) * 10 + (b - 48);
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
if (b === 59) {
|
|
233
|
+
params.push(val);
|
|
234
|
+
val = -1;
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
if (b === 58) {
|
|
238
|
+
params.push(val);
|
|
239
|
+
val = -1;
|
|
240
|
+
while (j + 1 < buf.length && buf[j + 1] !== 59 && buf[j + 1] !== 117 && buf[j + 1] !== 126) {
|
|
241
|
+
j++;
|
|
242
|
+
}
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
if (b === 117 || b === 126) {
|
|
246
|
+
if (val >= 0) params.push(val);
|
|
247
|
+
const len = j - i + 1;
|
|
248
|
+
const modsToCtrl = (m) => (m - 1 & 4) !== 0;
|
|
249
|
+
if (b === 117) {
|
|
250
|
+
const code2 = params[0];
|
|
251
|
+
if (code2 === void 0 || code2 < 0) return null;
|
|
252
|
+
return { len, code: code2, ctrl: modsToCtrl(params[1] ?? 1) };
|
|
253
|
+
}
|
|
254
|
+
if (params[0] !== 27) return null;
|
|
255
|
+
const code = params[2];
|
|
256
|
+
if (code === void 0 || code < 0) return null;
|
|
257
|
+
return { len, code, ctrl: modsToCtrl(params[1] ?? 1) };
|
|
258
|
+
}
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
192
263
|
var DEFAULT_LEADER_TIMEOUT_MS = 2e3;
|
|
193
264
|
function createInputRouter(opts) {
|
|
194
265
|
let active = null;
|
|
195
266
|
let disposed = false;
|
|
196
267
|
const leaderChords = opts.leaderChords ?? {};
|
|
197
268
|
const leaderEnabled = Object.keys(leaderChords).length > 0;
|
|
269
|
+
const onPasteImage = opts.onPasteImage;
|
|
270
|
+
const pasteEnabled = typeof onPasteImage === "function";
|
|
271
|
+
let pasteInFlight = false;
|
|
198
272
|
const leaderTimeoutMs = opts.leaderTimeoutMs ?? DEFAULT_LEADER_TIMEOUT_MS;
|
|
199
273
|
const setTimer = opts.setTimer ?? ((ms, fn) => setTimeout(fn, ms));
|
|
200
274
|
const clearTimer = opts.clearTimer ?? ((h) => clearTimeout(h));
|
|
@@ -272,25 +346,74 @@ function createInputRouter(opts) {
|
|
|
272
346
|
return;
|
|
273
347
|
}
|
|
274
348
|
};
|
|
349
|
+
const triggerPaste = () => {
|
|
350
|
+
if (pasteInFlight) return;
|
|
351
|
+
pasteInFlight = true;
|
|
352
|
+
const done = () => {
|
|
353
|
+
pasteInFlight = false;
|
|
354
|
+
if (!disposed) opts.onForward(Buffer.from([KEY_CTRL_V]));
|
|
355
|
+
};
|
|
356
|
+
void Promise.resolve().then(() => onPasteImage?.()).then(done, done);
|
|
357
|
+
};
|
|
275
358
|
const feedSteady = (buf) => {
|
|
276
359
|
let chunkStart = 0;
|
|
277
360
|
const flushChunk = (end) => {
|
|
278
361
|
if (end > chunkStart) opts.onForward(buf.subarray(chunkStart, end));
|
|
279
362
|
chunkStart = end;
|
|
280
363
|
};
|
|
281
|
-
|
|
364
|
+
let i = 0;
|
|
365
|
+
while (i < buf.length) {
|
|
282
366
|
const byte = buf[i];
|
|
283
|
-
if (byte === void 0)
|
|
367
|
+
if (byte === void 0) {
|
|
368
|
+
i++;
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
284
371
|
if (leader) {
|
|
372
|
+
const k = parseCsiKey(buf, i);
|
|
373
|
+
if (k) {
|
|
374
|
+
if (k.ctrl && k.code === KEY_A_LOW) {
|
|
375
|
+
exitLeader();
|
|
376
|
+
opts.onForward(Buffer.from([KEY_LEADER]));
|
|
377
|
+
} else {
|
|
378
|
+
const action = leaderChords[String.fromCharCode(k.code).toLowerCase()];
|
|
379
|
+
exitLeader();
|
|
380
|
+
if (action) opts.onAction?.(action);
|
|
381
|
+
else opts.onForward(buf.subarray(i, i + k.len));
|
|
382
|
+
}
|
|
383
|
+
i += k.len;
|
|
384
|
+
chunkStart = i;
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
285
387
|
resolveLeaderByte(byte);
|
|
286
|
-
|
|
388
|
+
i += 1;
|
|
389
|
+
chunkStart = i;
|
|
287
390
|
continue;
|
|
288
391
|
}
|
|
289
|
-
if (byte === KEY_LEADER) {
|
|
392
|
+
if (leaderEnabled && byte === KEY_LEADER) {
|
|
290
393
|
flushChunk(i);
|
|
291
|
-
chunkStart = i + 1;
|
|
292
394
|
enterLeader();
|
|
395
|
+
i += 1;
|
|
396
|
+
chunkStart = i;
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
if (leaderEnabled && byte === KEY_ESC) {
|
|
400
|
+
const k = parseCsiKey(buf, i);
|
|
401
|
+
if (k && k.ctrl && k.code === KEY_A_LOW) {
|
|
402
|
+
flushChunk(i);
|
|
403
|
+
enterLeader();
|
|
404
|
+
i += k.len;
|
|
405
|
+
chunkStart = i;
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
293
408
|
}
|
|
409
|
+
if (pasteEnabled && byte === KEY_CTRL_V) {
|
|
410
|
+
flushChunk(i);
|
|
411
|
+
i += 1;
|
|
412
|
+
chunkStart = i;
|
|
413
|
+
triggerPaste();
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
i += 1;
|
|
294
417
|
}
|
|
295
418
|
flushChunk(buf.length);
|
|
296
419
|
};
|
|
@@ -314,7 +437,7 @@ function createInputRouter(opts) {
|
|
|
314
437
|
}
|
|
315
438
|
return;
|
|
316
439
|
}
|
|
317
|
-
if (!leaderEnabled) {
|
|
440
|
+
if (!leaderEnabled && !pasteEnabled) {
|
|
318
441
|
opts.onForward(buf);
|
|
319
442
|
return;
|
|
320
443
|
}
|
|
@@ -569,9 +692,13 @@ function statusLine(box, w, stateLabel, groups = HINT_GROUPS, fallbackGroups) {
|
|
|
569
692
|
// src/wrapped-pty/footer.ts
|
|
570
693
|
var SPINNER_FRAMES = ["\u25D0", "\u25D3", "\u25D1", "\u25D2"];
|
|
571
694
|
var URGENT = "\x1B[38;5;220m\x1B[1m";
|
|
695
|
+
var TITLE = "\x1B[1m\x1B[38;5;253m";
|
|
572
696
|
var TXT = "\x1B[38;5;250m";
|
|
573
697
|
var SUBTLE = "\x1B[38;5;245m";
|
|
574
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";
|
|
575
702
|
var NOTICE_BG = "\x1B[48;5;220m";
|
|
576
703
|
var NOTICE_FG = "\x1B[38;5;16m\x1B[1m";
|
|
577
704
|
var FLASH_FG = "\x1B[38;5;150m\x1B[1m";
|
|
@@ -604,6 +731,17 @@ function padTo(visible, width) {
|
|
|
604
731
|
}
|
|
605
732
|
return visible + " ".repeat(width - visible.length);
|
|
606
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
|
+
}
|
|
607
745
|
function renderFooter(state, cols) {
|
|
608
746
|
if (cols <= 0) return "";
|
|
609
747
|
if (state.kind === "idle") {
|
|
@@ -634,18 +772,16 @@ function renderFooter(state, cols) {
|
|
|
634
772
|
return `${BAR_BG}${FLASH_FG}${prefix}${TXT}${message2}${RESET}`;
|
|
635
773
|
}
|
|
636
774
|
if (state.kind === "notice") {
|
|
637
|
-
const
|
|
638
|
-
const prefix = ` ${
|
|
775
|
+
const spinner2 = SPINNER_FRAMES[state.frame % SPINNER_FRAMES.length];
|
|
776
|
+
const prefix = ` ${spinner2} `;
|
|
639
777
|
const inner2 = Math.max(0, cols - prefix.length);
|
|
640
778
|
const message2 = padTo(state.message, inner2);
|
|
641
779
|
return `${NOTICE_BG}${NOTICE_FG}${prefix}${message2}${RESET}`;
|
|
642
780
|
}
|
|
643
|
-
const
|
|
644
|
-
const yn = def === "y" ? "[Y/n]" : "[y/N]";
|
|
781
|
+
const chip = answerChip(state.prompt.defaultAnswer);
|
|
645
782
|
const tag = " [!] ";
|
|
646
783
|
const sep = " ";
|
|
647
|
-
const
|
|
648
|
-
const inner = Math.max(0, cols - tag.length - hintW);
|
|
784
|
+
const inner = Math.max(0, cols - tag.length - chip.width);
|
|
649
785
|
const detailRaw = state.prompt.detail ?? "";
|
|
650
786
|
let message = state.prompt.message;
|
|
651
787
|
let detail = detailRaw;
|
|
@@ -660,7 +796,7 @@ function renderFooter(state, cols) {
|
|
|
660
796
|
}
|
|
661
797
|
const middlePlain = detail.length > 0 ? `${message}${sep}${detail}` : message;
|
|
662
798
|
const padded = padTo(middlePlain, inner);
|
|
663
|
-
return `${BAR_BG}${URGENT}${tag}${TXT}${padded}${
|
|
799
|
+
return `${BAR_BG}${URGENT}${tag}${TXT}${padded}${RESET}${chip.ansi}`;
|
|
664
800
|
}
|
|
665
801
|
function cursorMoveTo(row, col) {
|
|
666
802
|
return `\x1B[${String(row)};${String(col)}H`;
|
|
@@ -669,6 +805,73 @@ var CURSOR_SAVE = "\x1B7";
|
|
|
669
805
|
var CURSOR_RESTORE = "\x1B8";
|
|
670
806
|
var SYNC_BEGIN = "\x1B[?2026h";
|
|
671
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
|
+
}
|
|
672
875
|
|
|
673
876
|
// src/wrapped-pty/prompt-client.ts
|
|
674
877
|
import { request as httpRequest } from "http";
|
|
@@ -837,9 +1040,13 @@ function postAnswer(opts) {
|
|
|
837
1040
|
|
|
838
1041
|
// src/wrapped-pty/run.ts
|
|
839
1042
|
var FOOTER_ROWS = 1;
|
|
1043
|
+
var MIN_INNER_ROWS = 5;
|
|
840
1044
|
var STATUS_POLL_INTERVAL_MS = 3e3;
|
|
841
1045
|
var SPINNER_INTERVAL_MS = 120;
|
|
842
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;
|
|
843
1050
|
var ACTION_FLASH = {
|
|
844
1051
|
screen: "Opening noVNC viewer\u2026",
|
|
845
1052
|
code: "Launching VS Code / Cursor\u2026",
|
|
@@ -880,24 +1087,34 @@ async function runWrappedAttach(opts) {
|
|
|
880
1087
|
}
|
|
881
1088
|
}
|
|
882
1089
|
if (!process.stdout.isTTY || !process.stdin.isTTY) {
|
|
883
|
-
return runFallback(command, opts.dockerArgv);
|
|
1090
|
+
return runFallback(command, opts.dockerArgv, opts.env);
|
|
884
1091
|
}
|
|
885
1092
|
const backend = await loadPtyBackend();
|
|
886
1093
|
if (!backend) {
|
|
887
1094
|
process.stderr.write(
|
|
888
1095
|
"agentbox: permission prompts disabled (node-pty backend unavailable)\n"
|
|
889
1096
|
);
|
|
890
|
-
return runFallback(command, opts.dockerArgv);
|
|
1097
|
+
return runFallback(command, opts.dockerArgv, opts.env);
|
|
891
1098
|
}
|
|
892
1099
|
const cols = process.stdout.columns ?? 80;
|
|
893
1100
|
const rows = process.stdout.rows ?? 24;
|
|
894
1101
|
const innerRows = Math.max(1, rows - FOOTER_ROWS);
|
|
895
|
-
|
|
1102
|
+
let pty = backend.ptySpawn(command, opts.dockerArgv, {
|
|
896
1103
|
name: "xterm-256color",
|
|
897
1104
|
cols,
|
|
898
1105
|
rows: innerRows,
|
|
899
|
-
env: process.env
|
|
1106
|
+
env: opts.env ? { ...process.env, ...opts.env } : process.env
|
|
900
1107
|
});
|
|
1108
|
+
let lastSpawnAt = Date.now();
|
|
1109
|
+
const resizePty = (c, r) => {
|
|
1110
|
+
try {
|
|
1111
|
+
pty.resize(c, r);
|
|
1112
|
+
} catch {
|
|
1113
|
+
}
|
|
1114
|
+
};
|
|
1115
|
+
pushTerminalTitle();
|
|
1116
|
+
let lastEmittedTitle = opts.boxName;
|
|
1117
|
+
setTerminalTitle(lastEmittedTitle);
|
|
901
1118
|
const detachable = opts.detachable ?? opts.mode === "claude";
|
|
902
1119
|
let leaderActive = false;
|
|
903
1120
|
const buildIdle = (sessionTitle, claudeActivity) => ({
|
|
@@ -914,21 +1131,46 @@ async function runWrappedAttach(opts) {
|
|
|
914
1131
|
let lastActivity;
|
|
915
1132
|
let capturingPrompt = null;
|
|
916
1133
|
let activeNotice = null;
|
|
1134
|
+
let reconnectBanner = null;
|
|
917
1135
|
let noticeFrame = 0;
|
|
1136
|
+
let questionPayload = null;
|
|
1137
|
+
let bandState = null;
|
|
1138
|
+
let bandReservedRows = 0;
|
|
918
1139
|
let spinnerTimer = null;
|
|
919
1140
|
let flashMessage = null;
|
|
920
1141
|
let flashTimer = null;
|
|
921
|
-
|
|
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 = () => {
|
|
922
1153
|
const cs = process.stdout.columns ?? cols;
|
|
923
1154
|
const rs = process.stdout.rows ?? rows;
|
|
924
|
-
const
|
|
925
|
-
|
|
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;
|
|
926
1165
|
process.stdout.write(payload);
|
|
927
1166
|
};
|
|
928
1167
|
const recomputeFooter = () => {
|
|
929
|
-
|
|
1168
|
+
const collapsed = bandState !== null && bandReservedRows === 0;
|
|
1169
|
+
if (collapsed && reconnectBanner) {
|
|
1170
|
+
footerState = { kind: "notice", message: reconnectBanner, frame: noticeFrame };
|
|
1171
|
+
} else if (collapsed && capturingPrompt) {
|
|
930
1172
|
footerState = { kind: "prompt", prompt: capturingPrompt };
|
|
931
|
-
} else if (activeNotice) {
|
|
1173
|
+
} else if (collapsed && activeNotice) {
|
|
932
1174
|
footerState = { kind: "notice", message: activeNotice.message, frame: noticeFrame };
|
|
933
1175
|
} else if (flashMessage) {
|
|
934
1176
|
footerState = { kind: "flash", message: flashMessage };
|
|
@@ -936,13 +1178,48 @@ async function runWrappedAttach(opts) {
|
|
|
936
1178
|
footerState = buildIdle(lastSessionTitle, lastActivity);
|
|
937
1179
|
}
|
|
938
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
|
+
};
|
|
939
1215
|
const startSpinner = () => {
|
|
940
1216
|
if (spinnerTimer) return;
|
|
941
1217
|
spinnerTimer = setInterval(() => {
|
|
942
1218
|
noticeFrame++;
|
|
943
|
-
if (
|
|
944
|
-
|
|
945
|
-
|
|
1219
|
+
if (bandState?.kind === "notice") {
|
|
1220
|
+
bandState = { kind: "notice", message: bandState.message, frame: noticeFrame };
|
|
1221
|
+
if (bandReservedRows === 0) recomputeFooter();
|
|
1222
|
+
redrawChrome();
|
|
946
1223
|
}
|
|
947
1224
|
}, SPINNER_INTERVAL_MS);
|
|
948
1225
|
if (typeof spinnerTimer.unref === "function") spinnerTimer.unref();
|
|
@@ -953,14 +1230,20 @@ async function runWrappedAttach(opts) {
|
|
|
953
1230
|
spinnerTimer = null;
|
|
954
1231
|
}
|
|
955
1232
|
};
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
1233
|
+
const wireOutput = () => {
|
|
1234
|
+
pty.onData((d) => {
|
|
1235
|
+
process.stdout.write(d);
|
|
1236
|
+
redrawChrome();
|
|
1237
|
+
});
|
|
1238
|
+
};
|
|
1239
|
+
wireOutput();
|
|
960
1240
|
const leaderChords = detachable ? { c: "code", s: "screen", u: "url", d: "detach" } : { c: "code", s: "screen", u: "url" };
|
|
961
1241
|
const runAction = (name) => {
|
|
962
1242
|
if (name === "detach") {
|
|
963
|
-
|
|
1243
|
+
if (!reconnecting) {
|
|
1244
|
+
userDetached = true;
|
|
1245
|
+
pty.write("d");
|
|
1246
|
+
}
|
|
964
1247
|
return;
|
|
965
1248
|
}
|
|
966
1249
|
const cliEntry = process.argv[1];
|
|
@@ -982,31 +1265,61 @@ async function runWrappedAttach(opts) {
|
|
|
982
1265
|
flashTimer = null;
|
|
983
1266
|
flashMessage = null;
|
|
984
1267
|
recomputeFooter();
|
|
985
|
-
|
|
1268
|
+
redrawChrome();
|
|
986
1269
|
}, FLASH_DURATION_MS);
|
|
987
1270
|
if (typeof flashTimer.unref === "function") flashTimer.unref();
|
|
988
1271
|
recomputeFooter();
|
|
989
|
-
|
|
1272
|
+
redrawChrome();
|
|
1273
|
+
};
|
|
1274
|
+
const handlePasteImage = async () => {
|
|
1275
|
+
if (!opts.onPasteImage) return;
|
|
1276
|
+
if (flashTimer) {
|
|
1277
|
+
clearTimeout(flashTimer);
|
|
1278
|
+
flashTimer = null;
|
|
1279
|
+
}
|
|
1280
|
+
flashMessage = "Pasting image\u2026";
|
|
1281
|
+
recomputeFooter();
|
|
1282
|
+
redrawChrome();
|
|
1283
|
+
let result = "error";
|
|
1284
|
+
try {
|
|
1285
|
+
result = await opts.onPasteImage();
|
|
1286
|
+
} catch (e) {
|
|
1287
|
+
logErr(`paste-image failed: ${e.message}`);
|
|
1288
|
+
}
|
|
1289
|
+
flashMessage = result === "pasted" ? "Image pasted" : result === "no-image" ? "No image in clipboard" : "Image paste failed";
|
|
1290
|
+
flashTimer = setTimeout(() => {
|
|
1291
|
+
flashTimer = null;
|
|
1292
|
+
flashMessage = null;
|
|
1293
|
+
recomputeFooter();
|
|
1294
|
+
redrawChrome();
|
|
1295
|
+
}, FLASH_DURATION_MS);
|
|
1296
|
+
if (typeof flashTimer.unref === "function") flashTimer.unref();
|
|
1297
|
+
recomputeFooter();
|
|
1298
|
+
redrawChrome();
|
|
990
1299
|
};
|
|
991
1300
|
const router = createInputRouter({
|
|
992
1301
|
onForward: (b) => {
|
|
1302
|
+
if (reconnecting) {
|
|
1303
|
+
if (b.length === 1 && b[0] === 3) reconnectAbort?.abort();
|
|
1304
|
+
return;
|
|
1305
|
+
}
|
|
993
1306
|
pty.write(b.toString("utf8"));
|
|
994
1307
|
},
|
|
995
1308
|
onAnswer: (body) => {
|
|
996
1309
|
void postAnswer({ relayBaseUrl: opts.relayBaseUrl, body });
|
|
997
1310
|
capturingPrompt = null;
|
|
998
|
-
|
|
999
|
-
redrawFooter();
|
|
1311
|
+
applyBandChange();
|
|
1000
1312
|
},
|
|
1001
1313
|
leaderChords,
|
|
1002
1314
|
onLeaderChange: (open) => {
|
|
1003
1315
|
leaderActive = open;
|
|
1004
1316
|
recomputeFooter();
|
|
1005
|
-
|
|
1317
|
+
redrawChrome();
|
|
1006
1318
|
},
|
|
1007
1319
|
onAction: (name) => {
|
|
1008
1320
|
runAction(name);
|
|
1009
|
-
}
|
|
1321
|
+
},
|
|
1322
|
+
onPasteImage: opts.onPasteImage ? handlePasteImage : void 0
|
|
1010
1323
|
});
|
|
1011
1324
|
if (process.stdin.isTTY) process.stdin.setRawMode(true);
|
|
1012
1325
|
process.stdin.resume();
|
|
@@ -1017,10 +1330,12 @@ async function runWrappedAttach(opts) {
|
|
|
1017
1330
|
const onResize = () => {
|
|
1018
1331
|
const cs = process.stdout.columns ?? cols;
|
|
1019
1332
|
const rs = process.stdout.rows ?? rows;
|
|
1020
|
-
|
|
1021
|
-
|
|
1333
|
+
bandReservedRows = bandState && bandFits() ? ALERT_BAND_ROWS : 0;
|
|
1334
|
+
const inner = Math.max(1, rs - reservedRows());
|
|
1335
|
+
resizePty(cs, inner);
|
|
1022
1336
|
process.stdout.write(`\x1B[1;${String(inner)}r`);
|
|
1023
|
-
|
|
1337
|
+
recomputeFooter();
|
|
1338
|
+
redrawChrome();
|
|
1024
1339
|
};
|
|
1025
1340
|
process.stdout.on("resize", onResize);
|
|
1026
1341
|
const stream = subscribePrompts({
|
|
@@ -1028,8 +1343,7 @@ async function runWrappedAttach(opts) {
|
|
|
1028
1343
|
boxId: opts.boxId,
|
|
1029
1344
|
onPrompt: (ev) => {
|
|
1030
1345
|
capturingPrompt = ev;
|
|
1031
|
-
|
|
1032
|
-
redrawFooter();
|
|
1346
|
+
applyBandChange();
|
|
1033
1347
|
router.capture(ev).catch((e) => {
|
|
1034
1348
|
const msg = e instanceof Error ? e.message : String(e);
|
|
1035
1349
|
if (msg !== "resolved-elsewhere") {
|
|
@@ -1041,22 +1355,21 @@ async function runWrappedAttach(opts) {
|
|
|
1041
1355
|
if (capturingPrompt && capturingPrompt.id === id) {
|
|
1042
1356
|
capturingPrompt = null;
|
|
1043
1357
|
router.abort("resolved-elsewhere");
|
|
1044
|
-
|
|
1045
|
-
redrawFooter();
|
|
1358
|
+
applyBandChange();
|
|
1046
1359
|
}
|
|
1047
1360
|
},
|
|
1048
1361
|
onNotice: (ev) => {
|
|
1362
|
+
if (ev.kind === "checkpoint") checkpointNoticeAt = Date.now();
|
|
1049
1363
|
activeNotice = ev;
|
|
1050
1364
|
startSpinner();
|
|
1051
|
-
|
|
1052
|
-
redrawFooter();
|
|
1365
|
+
applyBandChange();
|
|
1053
1366
|
},
|
|
1054
1367
|
onNoticeCleared: (id) => {
|
|
1055
1368
|
if (activeNotice && activeNotice.id === id) {
|
|
1369
|
+
if (activeNotice.kind === "checkpoint") checkpointNoticeClearedAt = Date.now();
|
|
1056
1370
|
activeNotice = null;
|
|
1057
1371
|
stopSpinner();
|
|
1058
|
-
|
|
1059
|
-
redrawFooter();
|
|
1372
|
+
applyBandChange();
|
|
1060
1373
|
}
|
|
1061
1374
|
}
|
|
1062
1375
|
});
|
|
@@ -1067,14 +1380,26 @@ async function runWrappedAttach(opts) {
|
|
|
1067
1380
|
name: opts.boxName,
|
|
1068
1381
|
projectIndex: opts.projectIndex
|
|
1069
1382
|
});
|
|
1070
|
-
const
|
|
1071
|
-
const
|
|
1383
|
+
const body = opts.mode === "codex" ? status?.codex : opts.mode === "opencode" ? status?.opencode : opts.mode === "shell" ? void 0 : status?.claude;
|
|
1384
|
+
const nextTitle = body?.sessionTitle?.trim() || void 0;
|
|
1385
|
+
const nextActivity = body?.state || void 0;
|
|
1386
|
+
const desiredTitle = nextTitle ?? opts.boxName;
|
|
1387
|
+
if (desiredTitle !== lastEmittedTitle) {
|
|
1388
|
+
lastEmittedTitle = desiredTitle;
|
|
1389
|
+
setTerminalTitle(desiredTitle);
|
|
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
|
+
}
|
|
1072
1397
|
if (nextTitle === lastSessionTitle && nextActivity === lastActivity) return;
|
|
1073
1398
|
lastSessionTitle = nextTitle;
|
|
1074
1399
|
lastActivity = nextActivity;
|
|
1075
1400
|
if (footerState.kind === "idle") {
|
|
1076
1401
|
recomputeFooter();
|
|
1077
|
-
|
|
1402
|
+
redrawChrome();
|
|
1078
1403
|
}
|
|
1079
1404
|
} catch (e) {
|
|
1080
1405
|
logErr(`status poll failed: ${e.message}`);
|
|
@@ -1089,10 +1414,82 @@ async function runWrappedAttach(opts) {
|
|
|
1089
1414
|
if (opts.mode === "shell" && !detachable) {
|
|
1090
1415
|
process.stdout.write("\x1B[H\x1B[2J");
|
|
1091
1416
|
}
|
|
1092
|
-
|
|
1093
|
-
const
|
|
1094
|
-
|
|
1095
|
-
|
|
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
|
+
}
|
|
1096
1493
|
process.stdin.off("data", onStdinData);
|
|
1097
1494
|
process.stdout.off("resize", onResize);
|
|
1098
1495
|
clearInterval(statusTimer);
|
|
@@ -1104,72 +1501,295 @@ async function runWrappedAttach(opts) {
|
|
|
1104
1501
|
router.dispose();
|
|
1105
1502
|
const rsFinal = process.stdout.rows ?? rows;
|
|
1106
1503
|
const csFinal = process.stdout.columns ?? cols;
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
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);
|
|
1510
|
+
popTerminalTitle();
|
|
1110
1511
|
if (exitCode === 0 && opts.detachNotice) {
|
|
1111
1512
|
process.stdout.write("\x1B[1A\x1B[2K\r" + opts.detachNotice + "\n");
|
|
1112
1513
|
}
|
|
1113
1514
|
return exitCode;
|
|
1114
1515
|
}
|
|
1115
|
-
function runFallback(command, argv) {
|
|
1116
|
-
const child = spawnSync(command, argv, {
|
|
1516
|
+
function runFallback(command, argv, env) {
|
|
1517
|
+
const child = spawnSync(command, argv, {
|
|
1518
|
+
stdio: "inherit",
|
|
1519
|
+
env: env ? { ...process.env, ...env } : process.env
|
|
1520
|
+
});
|
|
1117
1521
|
return child.status ?? 0;
|
|
1118
1522
|
}
|
|
1119
1523
|
|
|
1524
|
+
// src/lib/paste-image.ts
|
|
1525
|
+
import { rm as rm2 } from "fs/promises";
|
|
1526
|
+
import { dirname } from "path";
|
|
1527
|
+
|
|
1528
|
+
// src/lib/host-clipboard.ts
|
|
1529
|
+
import { mkdtemp, rm, stat, writeFile } from "fs/promises";
|
|
1530
|
+
import { tmpdir } from "os";
|
|
1531
|
+
import { join } from "path";
|
|
1532
|
+
import { execa } from "execa";
|
|
1533
|
+
async function captureClipboardImage() {
|
|
1534
|
+
if (process.platform !== "darwin" && process.platform !== "linux") return null;
|
|
1535
|
+
const dir = await mkdtemp(join(tmpdir(), "agentbox-clip-"));
|
|
1536
|
+
const pngPath = join(dir, "clip.png");
|
|
1537
|
+
const ok = process.platform === "darwin" ? await captureDarwin(dir, pngPath) : await captureLinux(pngPath);
|
|
1538
|
+
if (ok) return pngPath;
|
|
1539
|
+
await rm(dir, { recursive: true, force: true }).catch(() => {
|
|
1540
|
+
});
|
|
1541
|
+
return null;
|
|
1542
|
+
}
|
|
1543
|
+
async function clipboardCaptureAvailable() {
|
|
1544
|
+
if (process.platform === "darwin") return true;
|
|
1545
|
+
if (process.platform === "linux") return await linuxClipboardTool() !== null;
|
|
1546
|
+
return false;
|
|
1547
|
+
}
|
|
1548
|
+
function captureScriptArgs(pngPath, tiffPath) {
|
|
1549
|
+
return [
|
|
1550
|
+
"try",
|
|
1551
|
+
" set theData to (the clipboard as \xABclass PNGf\xBB)",
|
|
1552
|
+
` set fh to open for access (POSIX file ${JSON.stringify(pngPath)}) with write permission`,
|
|
1553
|
+
" set eof fh to 0",
|
|
1554
|
+
" write theData to fh",
|
|
1555
|
+
" close access fh",
|
|
1556
|
+
' return "PNG"',
|
|
1557
|
+
"on error",
|
|
1558
|
+
" try",
|
|
1559
|
+
" set theData to (the clipboard as \xABclass TIFF\xBB)",
|
|
1560
|
+
` set fh to open for access (POSIX file ${JSON.stringify(tiffPath)}) with write permission`,
|
|
1561
|
+
" set eof fh to 0",
|
|
1562
|
+
" write theData to fh",
|
|
1563
|
+
" close access fh",
|
|
1564
|
+
' return "TIFF"',
|
|
1565
|
+
" on error",
|
|
1566
|
+
' return "NONE"',
|
|
1567
|
+
" end try",
|
|
1568
|
+
"end try"
|
|
1569
|
+
].map((line) => ["-e", line]).flat();
|
|
1570
|
+
}
|
|
1571
|
+
async function captureDarwin(dir, pngPath) {
|
|
1572
|
+
const tiffPath = join(dir, "clip.tiff");
|
|
1573
|
+
const res = await execa("osascript", captureScriptArgs(pngPath, tiffPath), {
|
|
1574
|
+
reject: false
|
|
1575
|
+
});
|
|
1576
|
+
const kind = res.stdout.trim();
|
|
1577
|
+
if (kind === "PNG") return fileHasBytes(pngPath);
|
|
1578
|
+
if (kind === "TIFF" && await fileHasBytes(tiffPath)) {
|
|
1579
|
+
const conv = await execa(
|
|
1580
|
+
"sips",
|
|
1581
|
+
["-s", "format", "png", tiffPath, "--out", pngPath],
|
|
1582
|
+
{ reject: false }
|
|
1583
|
+
);
|
|
1584
|
+
if (conv.exitCode === 0) return fileHasBytes(pngPath);
|
|
1585
|
+
}
|
|
1586
|
+
return false;
|
|
1587
|
+
}
|
|
1588
|
+
async function linuxClipboardTool() {
|
|
1589
|
+
if (process.env["WAYLAND_DISPLAY"] && await hasCmd("wl-paste")) return "wayland";
|
|
1590
|
+
if (process.env["DISPLAY"] && await hasCmd("xclip")) return "x11";
|
|
1591
|
+
return null;
|
|
1592
|
+
}
|
|
1593
|
+
async function captureLinux(pngPath) {
|
|
1594
|
+
const tool = await linuxClipboardTool();
|
|
1595
|
+
if (!tool) return false;
|
|
1596
|
+
let buf = null;
|
|
1597
|
+
if (tool === "wayland") {
|
|
1598
|
+
const types = await execa("wl-paste", ["--list-types"], { reject: false });
|
|
1599
|
+
if (types.exitCode !== 0 || !/image\/png/i.test(types.stdout)) return false;
|
|
1600
|
+
const r = await execa("wl-paste", ["--type", "image/png"], {
|
|
1601
|
+
encoding: "buffer",
|
|
1602
|
+
reject: false
|
|
1603
|
+
});
|
|
1604
|
+
if (r.exitCode === 0) buf = asBuffer(r.stdout);
|
|
1605
|
+
} else {
|
|
1606
|
+
const sel = ["-selection", "clipboard"];
|
|
1607
|
+
const targets = await execa("xclip", [...sel, "-t", "TARGETS", "-o"], {
|
|
1608
|
+
reject: false
|
|
1609
|
+
});
|
|
1610
|
+
if (targets.exitCode !== 0 || !/image\/png/i.test(targets.stdout)) return false;
|
|
1611
|
+
const r = await execa("xclip", [...sel, "-t", "image/png", "-o"], {
|
|
1612
|
+
encoding: "buffer",
|
|
1613
|
+
reject: false
|
|
1614
|
+
});
|
|
1615
|
+
if (r.exitCode === 0) buf = asBuffer(r.stdout);
|
|
1616
|
+
}
|
|
1617
|
+
if (!buf || !isPng(buf)) return false;
|
|
1618
|
+
await writeFile(pngPath, buf);
|
|
1619
|
+
return true;
|
|
1620
|
+
}
|
|
1621
|
+
async function hasCmd(cmd) {
|
|
1622
|
+
const r = await execa("sh", ["-c", `command -v ${cmd}`], { reject: false });
|
|
1623
|
+
return r.exitCode === 0;
|
|
1624
|
+
}
|
|
1625
|
+
function asBuffer(out) {
|
|
1626
|
+
if (Buffer.isBuffer(out)) return out;
|
|
1627
|
+
if (out instanceof Uint8Array) return Buffer.from(out);
|
|
1628
|
+
return null;
|
|
1629
|
+
}
|
|
1630
|
+
function isPng(buf) {
|
|
1631
|
+
return buf.length >= 8 && buf[0] === 137 && buf[1] === 80 && buf[2] === 78 && buf[3] === 71;
|
|
1632
|
+
}
|
|
1633
|
+
async function fileHasBytes(path) {
|
|
1634
|
+
try {
|
|
1635
|
+
const s = await stat(path);
|
|
1636
|
+
return s.isFile() && s.size > 0;
|
|
1637
|
+
} catch {
|
|
1638
|
+
return false;
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
// src/lib/paste-image.ts
|
|
1643
|
+
function loadClipboardScript(boxPngPath) {
|
|
1644
|
+
return [
|
|
1645
|
+
"pgrep -x Xvnc >/dev/null 2>&1 || /usr/local/bin/agentbox-vnc-start >/dev/null 2>&1 || true",
|
|
1646
|
+
"for _ in $(seq 1 30); do [ -S /tmp/.X11-unix/X1 ] && break; sleep 0.2; done",
|
|
1647
|
+
`setsid sh -c 'DISPLAY=:1 xclip -selection clipboard -t image/png -i ${boxPngPath}' </dev/null >/dev/null 2>&1 &`
|
|
1648
|
+
].join("; ");
|
|
1649
|
+
}
|
|
1650
|
+
async function pasteHostClipboardImage(provider, box) {
|
|
1651
|
+
if (typeof provider.uploadPath !== "function") return "error";
|
|
1652
|
+
const hostPng = await captureClipboardImage();
|
|
1653
|
+
if (!hostPng) return "no-image";
|
|
1654
|
+
const boxPng = `/tmp/agentbox-clip-${String(Date.now())}.png`;
|
|
1655
|
+
try {
|
|
1656
|
+
await provider.uploadPath(box, hostPng, boxPng);
|
|
1657
|
+
await provider.exec(box, ["sh", "-lc", loadClipboardScript(boxPng)], {
|
|
1658
|
+
user: "vscode"
|
|
1659
|
+
});
|
|
1660
|
+
return "pasted";
|
|
1661
|
+
} catch {
|
|
1662
|
+
return "error";
|
|
1663
|
+
} finally {
|
|
1664
|
+
await rm2(dirname(hostPng), { recursive: true, force: true }).catch(() => {
|
|
1665
|
+
});
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1120
1669
|
// src/commands/_cloud-attach.ts
|
|
1121
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
|
+
}
|
|
1122
1689
|
function buildCloudAttachInnerCommand(binary, extraArgs) {
|
|
1123
1690
|
if (!extraArgs || extraArgs.length === 0) {
|
|
1124
1691
|
return `bash -lc exec\\ ${binary}`;
|
|
1125
1692
|
}
|
|
1126
1693
|
const blob = Buffer.from(extraArgs.join("\n"), "utf8").toString("base64");
|
|
1127
|
-
return `bash -lc 'mapfile -t A
|
|
1694
|
+
return `bash -lc 'mapfile -t A <<< "$(echo ${blob} | base64 -d)"; exec ${binary} "\${A[@]}"'`;
|
|
1128
1695
|
}
|
|
1129
1696
|
async function cloudAgentAttach(args) {
|
|
1130
1697
|
const provider = await providerForBox(args.box);
|
|
1131
1698
|
if (!provider.buildAttach) {
|
|
1132
1699
|
throw new Error(`provider '${provider.name}' does not support interactive attach`);
|
|
1133
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
|
+
}
|
|
1134
1713
|
const command = buildCloudAttachInnerCommand(args.binary, args.extraArgs);
|
|
1135
|
-
const safeOpenIn =
|
|
1714
|
+
const safeOpenIn = box.provider === "daytona" ? "same" : args.openIn;
|
|
1136
1715
|
if (safeOpenIn && safeOpenIn !== "same" && args.extraArgs && args.extraArgs.length > 0) {
|
|
1137
|
-
const pre = await provider.buildAttach(
|
|
1716
|
+
const pre = await provider.buildAttach(box, "agent", {
|
|
1138
1717
|
sessionName: args.sessionName,
|
|
1139
1718
|
command,
|
|
1140
1719
|
detached: true
|
|
1141
1720
|
});
|
|
1142
1721
|
try {
|
|
1143
|
-
await runDetached(pre.argv);
|
|
1722
|
+
await runDetached(pre.argv, pre.env);
|
|
1144
1723
|
} finally {
|
|
1145
1724
|
if (pre.cleanup) await pre.cleanup();
|
|
1146
1725
|
}
|
|
1147
1726
|
}
|
|
1148
|
-
|
|
1727
|
+
let spec = await provider.buildAttach(box, "agent", {
|
|
1149
1728
|
sessionName: args.sessionName,
|
|
1150
1729
|
command
|
|
1151
1730
|
});
|
|
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
|
+
};
|
|
1152
1756
|
try {
|
|
1153
1757
|
const code = await runWrappedAttach({
|
|
1154
|
-
container:
|
|
1758
|
+
container: box.name,
|
|
1155
1759
|
command: spec.argv[0],
|
|
1156
1760
|
dockerArgv: spec.argv.slice(1),
|
|
1761
|
+
env: spec.env,
|
|
1157
1762
|
relayBaseUrl: RELAY_HOST_URL,
|
|
1158
|
-
boxId:
|
|
1159
|
-
boxName:
|
|
1160
|
-
projectIndex:
|
|
1763
|
+
boxId: box.id,
|
|
1764
|
+
boxName: box.name,
|
|
1765
|
+
projectIndex: box.projectIndex,
|
|
1161
1766
|
mode: args.mode,
|
|
1162
1767
|
detachable: true,
|
|
1163
|
-
openIn: safeOpenIn
|
|
1768
|
+
openIn: safeOpenIn,
|
|
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
|
|
1164
1781
|
});
|
|
1165
1782
|
process.exit(code);
|
|
1166
1783
|
} finally {
|
|
1167
1784
|
if (spec.cleanup) await spec.cleanup();
|
|
1168
1785
|
}
|
|
1169
1786
|
}
|
|
1170
|
-
function runDetached(argv) {
|
|
1787
|
+
function runDetached(argv, env) {
|
|
1171
1788
|
return new Promise((resolve) => {
|
|
1172
|
-
const child = spawn3(argv[0], argv.slice(1), {
|
|
1789
|
+
const child = spawn3(argv[0], argv.slice(1), {
|
|
1790
|
+
stdio: "ignore",
|
|
1791
|
+
env: env ? { ...process.env, ...env } : process.env
|
|
1792
|
+
});
|
|
1173
1793
|
child.on("error", () => resolve());
|
|
1174
1794
|
child.on("exit", () => resolve());
|
|
1175
1795
|
});
|
|
@@ -1182,8 +1802,12 @@ export {
|
|
|
1182
1802
|
providerForCreate,
|
|
1183
1803
|
loadPtyBackend,
|
|
1184
1804
|
detectHostTerminal,
|
|
1805
|
+
setTerminalTitle,
|
|
1806
|
+
pushTerminalTitle,
|
|
1807
|
+
popTerminalTitle,
|
|
1185
1808
|
NEW_BOX_ID,
|
|
1186
1809
|
NEW_BOX_LABEL,
|
|
1810
|
+
stripTitleGlyph,
|
|
1187
1811
|
sidebarLines,
|
|
1188
1812
|
menuLines,
|
|
1189
1813
|
lifecycleMenuLines,
|
|
@@ -1191,10 +1815,14 @@ export {
|
|
|
1191
1815
|
ADVANCED_HINT_GROUPS,
|
|
1192
1816
|
statusLine,
|
|
1193
1817
|
renderFooter,
|
|
1818
|
+
ALERT_BAND_ROWS,
|
|
1819
|
+
renderAlertBand,
|
|
1194
1820
|
subscribePrompts,
|
|
1195
1821
|
postAnswer,
|
|
1196
1822
|
runWrappedAttach,
|
|
1823
|
+
clipboardCaptureAvailable,
|
|
1824
|
+
pasteHostClipboardImage,
|
|
1197
1825
|
buildCloudAttachInnerCommand,
|
|
1198
1826
|
cloudAgentAttach
|
|
1199
1827
|
};
|
|
1200
|
-
//# sourceMappingURL=chunk-
|
|
1828
|
+
//# sourceMappingURL=chunk-R4O5WPHW.js.map
|