@madarco/agentbox 0.14.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +60 -0
- package/dist/{_cloud-attach-GUBB5RH2.js → _cloud-attach-R6TRWG5L.js} +3 -3
- package/dist/{chunk-BYCLD6D6.js → chunk-43Q5GWP6.js} +98 -54
- package/dist/chunk-43Q5GWP6.js.map +1 -0
- package/dist/{chunk-VATTS2MR.js → chunk-72CJTXN6.js} +2 -2
- package/dist/{chunk-TBSIJVSN.js → chunk-E7CHS7ZR.js} +21 -13
- package/dist/chunk-E7CHS7ZR.js.map +1 -0
- package/dist/{chunk-LDMYHWUS.js → chunk-MCOU6CZS.js} +2 -2
- package/dist/{chunk-TCS5HXJX.js → chunk-MLMFNN4T.js} +396 -324
- package/dist/chunk-MLMFNN4T.js.map +1 -0
- package/dist/{dist-J2IHD5T7.js → dist-AGTIA7AD.js} +3 -3
- package/dist/{dist-3IMQNTTV.js → dist-FIFEFKJ7.js} +3 -3
- package/dist/{dist-34RKQ74M.js → dist-JZ3XO6EB.js} +4 -4
- package/dist/{dist-4DPOL5A7.js → dist-OGJGZETZ.js} +2 -2
- package/dist/{dist-57M6ZA7H.js → dist-S4XR4ACV.js} +4 -4
- package/dist/index.js +868 -300
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
- package/runtime/docker/apps/cli/share/agentbox-setup/SKILL.md +30 -0
- package/runtime/docker/packages/ctl/dist/bin.cjs +321 -27
- package/runtime/e2b/agentbox-setup-skill.md +30 -0
- package/runtime/e2b/ctl.cjs +321 -27
- package/runtime/hetzner/agentbox-setup-skill.md +30 -0
- package/runtime/hetzner/ctl.cjs +321 -27
- package/runtime/relay/bin.cjs +83 -5
- package/runtime/vercel/agentbox-setup-skill.md +30 -0
- package/runtime/vercel/ctl.cjs +321 -27
- package/share/agentbox-setup/SKILL.md +30 -0
- package/share/host-skills/agentbox-info/SKILL.md +21 -1
- package/dist/chunk-BYCLD6D6.js.map +0 -1
- package/dist/chunk-TBSIJVSN.js.map +0 -1
- package/dist/chunk-TCS5HXJX.js.map +0 -1
- /package/dist/{_cloud-attach-GUBB5RH2.js.map → _cloud-attach-R6TRWG5L.js.map} +0 -0
- /package/dist/{chunk-VATTS2MR.js.map → chunk-72CJTXN6.js.map} +0 -0
- /package/dist/{chunk-LDMYHWUS.js.map → chunk-MCOU6CZS.js.map} +0 -0
- /package/dist/{dist-J2IHD5T7.js.map → dist-AGTIA7AD.js.map} +0 -0
- /package/dist/{dist-3IMQNTTV.js.map → dist-FIFEFKJ7.js.map} +0 -0
- /package/dist/{dist-34RKQ74M.js.map → dist-JZ3XO6EB.js.map} +0 -0
- /package/dist/{dist-4DPOL5A7.js.map → dist-OGJGZETZ.js.map} +0 -0
- /package/dist/{dist-57M6ZA7H.js.map → dist-S4XR4ACV.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
clipboardCaptureAvailable,
|
|
9
9
|
cloudAgentAttach,
|
|
10
10
|
cloudAgentStartDetached,
|
|
11
|
+
cmuxBinary,
|
|
11
12
|
createMenuLines,
|
|
12
13
|
detectHostTerminal,
|
|
13
14
|
getProvider,
|
|
@@ -26,10 +27,11 @@ import {
|
|
|
26
27
|
runWrappedAttach,
|
|
27
28
|
setTerminalTitle,
|
|
28
29
|
sidebarLines,
|
|
30
|
+
spawnInNewTerminal,
|
|
29
31
|
statusLine,
|
|
30
32
|
stripTitleGlyph,
|
|
31
33
|
subscribePrompts
|
|
32
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-43Q5GWP6.js";
|
|
33
35
|
import {
|
|
34
36
|
daytonaBackend,
|
|
35
37
|
ensureDaytonaCredentials,
|
|
@@ -57,14 +59,14 @@ import {
|
|
|
57
59
|
readPreparedState as readPreparedState2,
|
|
58
60
|
readVercelCredStatus,
|
|
59
61
|
secretsPath as secretsPath3
|
|
60
|
-
} from "./chunk-
|
|
62
|
+
} from "./chunk-72CJTXN6.js";
|
|
61
63
|
import {
|
|
62
64
|
ensureE2bCredentials,
|
|
63
65
|
maskKey as maskKey4,
|
|
64
66
|
readE2bCredStatus,
|
|
65
67
|
readPreparedState as readPreparedState3,
|
|
66
68
|
secretsPath as secretsPath4
|
|
67
|
-
} from "./chunk-
|
|
69
|
+
} from "./chunk-MCOU6CZS.js";
|
|
68
70
|
import {
|
|
69
71
|
agentSpecsForCloud,
|
|
70
72
|
currentCloudBaseFingerprint,
|
|
@@ -74,10 +76,11 @@ import {
|
|
|
74
76
|
probeCloudCheckpoint,
|
|
75
77
|
resolveCloudCheckpoint,
|
|
76
78
|
seedAgentVolumesIfFresh
|
|
77
|
-
} from "./chunk-
|
|
79
|
+
} from "./chunk-E7CHS7ZR.js";
|
|
78
80
|
import {
|
|
79
81
|
AmbiguousBoxError,
|
|
80
82
|
BOX_STATUS_EVENT,
|
|
83
|
+
BUILT_IN_DEFAULTS,
|
|
81
84
|
BoxNotFoundError,
|
|
82
85
|
CODEX_CREDENTIALS_BACKUP_FILE,
|
|
83
86
|
ClaudeSessionError,
|
|
@@ -229,7 +232,7 @@ import {
|
|
|
229
232
|
waitForTmuxPaneContent,
|
|
230
233
|
warmUpClaudeCredentials,
|
|
231
234
|
writeJob
|
|
232
|
-
} from "./chunk-
|
|
235
|
+
} from "./chunk-MLMFNN4T.js";
|
|
233
236
|
import {
|
|
234
237
|
DEFAULT_BOX_IMAGE,
|
|
235
238
|
STATE_DIR,
|
|
@@ -246,8 +249,8 @@ import {
|
|
|
246
249
|
import "./chunk-G3H2L3O2.js";
|
|
247
250
|
|
|
248
251
|
// src/version.ts
|
|
249
|
-
var AGENTBOX_VERSION = true ? "0.
|
|
250
|
-
var AGENTBOX_COMMIT = true ? "
|
|
252
|
+
var AGENTBOX_VERSION = true ? "0.15.0" : "0.0.0-dev";
|
|
253
|
+
var AGENTBOX_COMMIT = true ? "dfe136d17" : "dev";
|
|
251
254
|
|
|
252
255
|
// src/index.ts
|
|
253
256
|
import { Command as Command48 } from "commander";
|
|
@@ -430,6 +433,178 @@ function derivedAgentState(claude) {
|
|
|
430
433
|
return claude.state;
|
|
431
434
|
}
|
|
432
435
|
|
|
436
|
+
// src/lib/agent-answer.ts
|
|
437
|
+
import { createHash } from "crypto";
|
|
438
|
+
var TUI_ID_PREFIX = "tui";
|
|
439
|
+
function inTuiKind(claude) {
|
|
440
|
+
if (claude.state === "working" || claude.state === "compacting") return null;
|
|
441
|
+
if (claude.plan !== void 0) return "plan";
|
|
442
|
+
if (claude.question !== void 0) return "question";
|
|
443
|
+
if (claude.state === "waiting") return "permission";
|
|
444
|
+
return null;
|
|
445
|
+
}
|
|
446
|
+
function digestForBlock(claude, kind) {
|
|
447
|
+
let material;
|
|
448
|
+
if (kind === "plan") {
|
|
449
|
+
material = `plan|${claude.plan?.capturedAt ?? ""}|${claude.plan?.plan ?? ""}`;
|
|
450
|
+
} else if (kind === "question") {
|
|
451
|
+
material = `question|${claude.question?.capturedAt ?? ""}|${JSON.stringify(claude.question?.questions ?? [])}`;
|
|
452
|
+
} else {
|
|
453
|
+
material = `permission|${claude.updatedAt ?? ""}`;
|
|
454
|
+
}
|
|
455
|
+
return createHash("sha256").update(material).digest("hex").slice(0, 12);
|
|
456
|
+
}
|
|
457
|
+
function mintTuiId(boxId, claude) {
|
|
458
|
+
const kind = inTuiKind(claude);
|
|
459
|
+
if (kind === null) return null;
|
|
460
|
+
return { id: `${TUI_ID_PREFIX}:${boxId}:${kind}:${digestForBlock(claude, kind)}`, kind };
|
|
461
|
+
}
|
|
462
|
+
function parseTuiId(id) {
|
|
463
|
+
const parts = id.split(":");
|
|
464
|
+
if (parts.length !== 4 || parts[0] !== TUI_ID_PREFIX) return null;
|
|
465
|
+
const [, boxId, kind, digest] = parts;
|
|
466
|
+
if (!boxId || !digest) return null;
|
|
467
|
+
if (kind !== "plan" && kind !== "question" && kind !== "permission") return null;
|
|
468
|
+
return { boxId, kind, digest };
|
|
469
|
+
}
|
|
470
|
+
function isTuiId(id) {
|
|
471
|
+
return id.startsWith(`${TUI_ID_PREFIX}:`);
|
|
472
|
+
}
|
|
473
|
+
var OPTION_ENTER_DELAY_MS = 150;
|
|
474
|
+
function answerKeystrokes(_agent, _kind, decision) {
|
|
475
|
+
if (decision.deny === true) {
|
|
476
|
+
return [{ type: "key", value: "Escape" }];
|
|
477
|
+
}
|
|
478
|
+
if (decision.option !== void 0) {
|
|
479
|
+
return [
|
|
480
|
+
{ type: "literal", value: String(decision.option) },
|
|
481
|
+
{ type: "delay", ms: OPTION_ENTER_DELAY_MS },
|
|
482
|
+
{ type: "key", value: "Enter" }
|
|
483
|
+
];
|
|
484
|
+
}
|
|
485
|
+
return [{ type: "key", value: "Enter" }];
|
|
486
|
+
}
|
|
487
|
+
function resolveQuestionOption(claude, raw) {
|
|
488
|
+
const options = claude.question?.questions?.[0]?.options ?? [];
|
|
489
|
+
const asNum = Number.parseInt(raw, 10);
|
|
490
|
+
if (Number.isFinite(asNum) && String(asNum) === raw.trim()) {
|
|
491
|
+
return asNum >= 1 && asNum <= options.length ? asNum : null;
|
|
492
|
+
}
|
|
493
|
+
const needle = raw.trim().toLowerCase();
|
|
494
|
+
const exact = options.findIndex((o) => o.label.toLowerCase() === needle);
|
|
495
|
+
if (exact !== -1) return exact + 1;
|
|
496
|
+
const prefix = options.findIndex((o) => o.label.toLowerCase().startsWith(needle));
|
|
497
|
+
return prefix !== -1 ? prefix + 1 : null;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// src/lib/drive/tmux.ts
|
|
501
|
+
var TMUX_USER = "vscode";
|
|
502
|
+
async function captureSession(provider, box, session, opts = {}) {
|
|
503
|
+
const argv2 = ["tmux", "capture-pane", opts.ansi ? "-pe" : "-p", "-t", session];
|
|
504
|
+
if (opts.rows) {
|
|
505
|
+
argv2.push("-S", String(opts.rows.from), "-E", String(opts.rows.to));
|
|
506
|
+
}
|
|
507
|
+
const res = await provider.exec(box, argv2, { user: TMUX_USER });
|
|
508
|
+
if (res.exitCode !== 0) {
|
|
509
|
+
throw new Error(failure("capture-pane", session, res.stderr || res.stdout));
|
|
510
|
+
}
|
|
511
|
+
return res.stdout.replace(/\n$/, "");
|
|
512
|
+
}
|
|
513
|
+
async function paneInfo(provider, box, session) {
|
|
514
|
+
const fmt = "#{pane_width},#{pane_height},#{cursor_x},#{cursor_y}";
|
|
515
|
+
const res = await provider.exec(box, ["tmux", "display-message", "-p", "-t", session, fmt], {
|
|
516
|
+
user: TMUX_USER
|
|
517
|
+
});
|
|
518
|
+
if (res.exitCode !== 0) {
|
|
519
|
+
throw new Error(failure("display-message", session, res.stderr || res.stdout));
|
|
520
|
+
}
|
|
521
|
+
const m = /^(\d+),(\d+),(\d+),(\d+)/.exec(res.stdout.trim());
|
|
522
|
+
if (!m) throw new Error(`tmux display-message returned unexpected output: ${res.stdout}`);
|
|
523
|
+
return {
|
|
524
|
+
cols: Number(m[1]),
|
|
525
|
+
rows: Number(m[2]),
|
|
526
|
+
cursor: { x: Number(m[3]), y: Number(m[4]) }
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
async function sendLiteral(provider, box, session, literal) {
|
|
530
|
+
if (literal.length === 0) return;
|
|
531
|
+
const res = await provider.exec(box, ["tmux", "send-keys", "-t", session, "-l", "--", literal], {
|
|
532
|
+
user: TMUX_USER
|
|
533
|
+
});
|
|
534
|
+
if (res.exitCode !== 0) {
|
|
535
|
+
throw new Error(failure("send-keys -l", session, res.stderr || res.stdout));
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
async function sendKey(provider, box, session, key) {
|
|
539
|
+
const res = await provider.exec(box, ["tmux", "send-keys", "-t", session, key], {
|
|
540
|
+
user: TMUX_USER
|
|
541
|
+
});
|
|
542
|
+
if (res.exitCode !== 0) {
|
|
543
|
+
throw new Error(failure("send-keys", session, res.stderr || res.stdout));
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
async function resizeWindow(provider, box, session, cols, rows) {
|
|
547
|
+
const res = await provider.exec(
|
|
548
|
+
box,
|
|
549
|
+
["tmux", "resize-window", "-t", session, "-x", String(cols), "-y", String(rows)],
|
|
550
|
+
{ user: TMUX_USER }
|
|
551
|
+
);
|
|
552
|
+
if (res.exitCode !== 0) {
|
|
553
|
+
throw new Error(failure("resize-window", session, res.stderr || res.stdout));
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
async function listSessions(provider, box) {
|
|
557
|
+
const res = await provider.exec(
|
|
558
|
+
box,
|
|
559
|
+
["tmux", "list-sessions", "-F", "#{session_name}"],
|
|
560
|
+
{ user: TMUX_USER }
|
|
561
|
+
);
|
|
562
|
+
if (res.exitCode !== 0) return [];
|
|
563
|
+
return res.stdout.split("\n").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
564
|
+
}
|
|
565
|
+
function failure(op, session, detail) {
|
|
566
|
+
const tail = detail.trim();
|
|
567
|
+
return `tmux ${op} failed for session '${session}'${tail ? `: ${tail}` : ""}`;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// src/lib/drive/session.ts
|
|
571
|
+
var AGENT_SESSION_PRIORITY = ["claude", "codex", "opencode"];
|
|
572
|
+
async function resolveDriveSession(provider, box, explicit) {
|
|
573
|
+
if (await provider.probeState(box) === "paused") {
|
|
574
|
+
process.stderr.write(`drive: box ${box.name} is paused; unpausing
|
|
575
|
+
`);
|
|
576
|
+
await provider.resume(box);
|
|
577
|
+
}
|
|
578
|
+
const sessions = await listSessions(provider, box);
|
|
579
|
+
if (explicit !== void 0 && explicit !== "") {
|
|
580
|
+
if (!sessions.includes(explicit)) {
|
|
581
|
+
throw new SessionNotFoundError(explicit, sessions);
|
|
582
|
+
}
|
|
583
|
+
return { name: explicit, available: sessions };
|
|
584
|
+
}
|
|
585
|
+
for (const candidate of AGENT_SESSION_PRIORITY) {
|
|
586
|
+
if (sessions.includes(candidate)) {
|
|
587
|
+
return { name: candidate, available: sessions };
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
if (sessions.length === 1 && sessions[0]) {
|
|
591
|
+
return { name: sessions[0], available: sessions };
|
|
592
|
+
}
|
|
593
|
+
throw new SessionNotFoundError(void 0, sessions);
|
|
594
|
+
}
|
|
595
|
+
var SessionNotFoundError = class extends Error {
|
|
596
|
+
wanted;
|
|
597
|
+
available;
|
|
598
|
+
constructor(wanted, available) {
|
|
599
|
+
const head = wanted ? `no tmux session '${wanted}' in this box` : "no agent tmux session running in this box";
|
|
600
|
+
const tail = available.length ? ` (running: ${available.join(", ")})` : " (tmux server not running or no sessions)";
|
|
601
|
+
super(head + tail);
|
|
602
|
+
this.name = "SessionNotFoundError";
|
|
603
|
+
this.wanted = wanted;
|
|
604
|
+
this.available = available;
|
|
605
|
+
}
|
|
606
|
+
};
|
|
607
|
+
|
|
433
608
|
// src/lib/wait/events.ts
|
|
434
609
|
var POLL_INTERVAL_MS = 500;
|
|
435
610
|
var WaitTimeoutError = class extends Error {
|
|
@@ -606,9 +781,209 @@ var agentGetPlanQuestionCommand = new Command("get-plan-question").description("
|
|
|
606
781
|
handleLifecycleError(err);
|
|
607
782
|
}
|
|
608
783
|
});
|
|
784
|
+
var agentApprovalsCommand = new Command("approvals").description(
|
|
785
|
+
"List everything a box is blocked on: relay host-action approvals (git push, cp host<->box, gh PR writes, checkpoint) AND the agent's in-TUI prompts (plan approval, question, tool permission). Each row carries an id to pass to `agent approve`."
|
|
786
|
+
).argument("[box]", "box ref (default: only box in this project)").option("--json", "emit the pending approvals as a JSON array").option(
|
|
787
|
+
"--wait <ms>",
|
|
788
|
+
"block until at least one approval is pending (or this wall-clock cap elapses), then print"
|
|
789
|
+
).action(async (boxRef, opts) => {
|
|
790
|
+
try {
|
|
791
|
+
const box = await resolveBoxOrExit(boxRef);
|
|
792
|
+
const relayUrl = (await ensureRelay()).hostUrl;
|
|
793
|
+
const waitMs = opts.wait !== void 0 ? parsePositiveInt(opts.wait, "--wait") : void 0;
|
|
794
|
+
let rows = await gatherApprovals(relayUrl, box);
|
|
795
|
+
if (waitMs !== void 0 && rows.length === 0) {
|
|
796
|
+
const start = Date.now();
|
|
797
|
+
while (rows.length === 0 && Date.now() - start < waitMs) {
|
|
798
|
+
await sleep2(Math.min(500, waitMs - (Date.now() - start)));
|
|
799
|
+
rows = await gatherApprovals(relayUrl, box);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
if (opts.json === true) {
|
|
803
|
+
process.stdout.write(JSON.stringify(rows) + "\n");
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
806
|
+
if (rows.length === 0) {
|
|
807
|
+
log3.info("nothing pending for this box (no relay approvals, agent not parked on a prompt)");
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
810
|
+
for (const row2 of rows) {
|
|
811
|
+
process.stdout.write(approvalDisplay(row2) + "\n");
|
|
812
|
+
}
|
|
813
|
+
} catch (err) {
|
|
814
|
+
handleLifecycleError(err);
|
|
815
|
+
}
|
|
816
|
+
});
|
|
817
|
+
var agentApproveCommand = new Command("approve").description(
|
|
818
|
+
"Answer a pending approval by id (see `agent approvals`). The id is a safety token: you answer the exact prompt you inspected, and if a different one has since taken its place the approve is refused. Works for both relay host-action approvals and the agent's in-TUI prompts (plan / question / tool permission). Approves by default; --deny rejects."
|
|
819
|
+
).argument("<id>", "approval id from `agent approvals` (relay UUID or a tui:... id)").option("--deny", "reject instead of approving").option("--cancel", "relay approvals only: dismiss (treated as denied; marks it cancelled)").option(
|
|
820
|
+
"--option <n|label>",
|
|
821
|
+
"in-TUI question/permission: pick this 1-based option (or match its label) instead of the default"
|
|
822
|
+
).action(async (id, opts) => {
|
|
823
|
+
try {
|
|
824
|
+
if (isTuiId(id)) {
|
|
825
|
+
await approveInTui(id, opts);
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
await approveRelay(id, opts);
|
|
829
|
+
} catch (err) {
|
|
830
|
+
handleLifecycleError(err);
|
|
831
|
+
}
|
|
832
|
+
});
|
|
833
|
+
async function approveRelay(id, opts) {
|
|
834
|
+
const relayUrl = (await ensureRelay()).hostUrl;
|
|
835
|
+
const cancelled = opts.cancel === true;
|
|
836
|
+
const answer = opts.deny === true || cancelled ? "n" : "y";
|
|
837
|
+
const url = new URL("/admin/prompts/answer", relayUrl);
|
|
838
|
+
const res = await fetch(url, {
|
|
839
|
+
method: "POST",
|
|
840
|
+
headers: { "Content-Type": "application/json" },
|
|
841
|
+
body: JSON.stringify({ id, answer, cancelled: cancelled || void 0 })
|
|
842
|
+
});
|
|
843
|
+
if (res.status === 204) {
|
|
844
|
+
log3.success(`approval ${id}: ${answer === "y" ? "approved" : "denied"}`);
|
|
845
|
+
return;
|
|
846
|
+
}
|
|
847
|
+
if (res.status === 404) {
|
|
848
|
+
log3.info(`approval ${id} already resolved (or expired)`);
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
851
|
+
log3.error(`relay /admin/prompts/answer: HTTP ${String(res.status)}`);
|
|
852
|
+
process.exit(1);
|
|
853
|
+
}
|
|
854
|
+
async function approveInTui(id, opts) {
|
|
855
|
+
const parsed = parseTuiId(id);
|
|
856
|
+
if (!parsed) {
|
|
857
|
+
log3.error(`malformed in-TUI approval id: ${id}`);
|
|
858
|
+
process.exit(2);
|
|
859
|
+
}
|
|
860
|
+
if (opts.cancel === true) {
|
|
861
|
+
log3.error("--cancel applies to relay approvals only; use --deny for in-TUI prompts");
|
|
862
|
+
process.exit(2);
|
|
863
|
+
}
|
|
864
|
+
const box = await resolveBoxOrExit(parsed.boxId);
|
|
865
|
+
const status = await readBoxStatus(box);
|
|
866
|
+
const claude = status?.claude;
|
|
867
|
+
const current = claude ? mintTuiId(box.id, claude) : null;
|
|
868
|
+
if (!current || current.id !== id) {
|
|
869
|
+
log3.error(
|
|
870
|
+
`approval ${id} is no longer the pending prompt for ${box.name} (it changed or was answered) \u2014 re-run \`agentbox agent approvals ${box.name}\``
|
|
871
|
+
);
|
|
872
|
+
process.exit(1);
|
|
873
|
+
}
|
|
874
|
+
let option;
|
|
875
|
+
if (opts.option !== void 0) {
|
|
876
|
+
if (parsed.kind === "question" && claude) {
|
|
877
|
+
const resolved = resolveQuestionOption(claude, opts.option);
|
|
878
|
+
if (resolved === null) {
|
|
879
|
+
const labels = (claude.question?.questions?.[0]?.options ?? []).map((o) => o.label);
|
|
880
|
+
log3.error(`--option '${opts.option}' did not match an option (have: ${labels.join(" | ")})`);
|
|
881
|
+
process.exit(2);
|
|
882
|
+
}
|
|
883
|
+
option = resolved;
|
|
884
|
+
} else {
|
|
885
|
+
const n = Number.parseInt(opts.option, 10);
|
|
886
|
+
if (!Number.isFinite(n) || n < 1) {
|
|
887
|
+
log3.error(`--option must be a 1-based number for a ${parsed.kind} prompt (got: ${opts.option})`);
|
|
888
|
+
process.exit(2);
|
|
889
|
+
}
|
|
890
|
+
option = n;
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
const provider = await providerForBox(box);
|
|
894
|
+
const session = await resolveDriveSession(provider, box, void 0);
|
|
895
|
+
const agent = agentKindForSession(session.name);
|
|
896
|
+
const steps = answerKeystrokes(agent, parsed.kind, { option, deny: opts.deny });
|
|
897
|
+
await runAnswerSteps(provider, box, session.name, steps);
|
|
898
|
+
const verb = opts.deny === true ? "denied" : option !== void 0 ? `answered (option ${String(option)})` : "approved";
|
|
899
|
+
log3.success(`${parsed.kind} prompt on ${box.name}: ${verb}`);
|
|
900
|
+
}
|
|
901
|
+
function agentKindForSession(session) {
|
|
902
|
+
if (session === "codex") return "codex";
|
|
903
|
+
if (session === "opencode") return "opencode";
|
|
904
|
+
return "claude";
|
|
905
|
+
}
|
|
906
|
+
async function runAnswerSteps(provider, box, session, steps) {
|
|
907
|
+
for (const step of steps) {
|
|
908
|
+
if (step.type === "literal") await sendLiteral(provider, box, session, step.value);
|
|
909
|
+
else if (step.type === "key") await sendKey(provider, box, session, step.value);
|
|
910
|
+
else await sleep2(step.ms);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
609
913
|
agentCommand.addCommand(agentStateCommand);
|
|
610
914
|
agentCommand.addCommand(agentWaitForCommand);
|
|
611
915
|
agentCommand.addCommand(agentGetPlanQuestionCommand);
|
|
916
|
+
agentCommand.addCommand(agentApprovalsCommand);
|
|
917
|
+
agentCommand.addCommand(agentApproveCommand);
|
|
918
|
+
async function gatherApprovals(relayUrl, box) {
|
|
919
|
+
const rows = [];
|
|
920
|
+
const relay = await fetchRelayApprovals(relayUrl, box.id);
|
|
921
|
+
for (const ev of relay) {
|
|
922
|
+
rows.push({
|
|
923
|
+
id: ev.id,
|
|
924
|
+
kind: "host-action",
|
|
925
|
+
command: ev.context?.command,
|
|
926
|
+
argv: ev.context?.argv,
|
|
927
|
+
cwd: ev.context?.cwd,
|
|
928
|
+
message: ev.message,
|
|
929
|
+
detail: ev.detail,
|
|
930
|
+
defaultAnswer: ev.defaultAnswer
|
|
931
|
+
});
|
|
932
|
+
}
|
|
933
|
+
const claude = (await readBoxStatus(box))?.claude;
|
|
934
|
+
const tui = claude ? mintTuiId(box.id, claude) : null;
|
|
935
|
+
if (claude && tui) {
|
|
936
|
+
if (tui.kind === "plan") {
|
|
937
|
+
rows.push({ id: tui.id, kind: "plan", message: "Approve plan?", plan: claude.plan?.plan ?? "" });
|
|
938
|
+
} else if (tui.kind === "question") {
|
|
939
|
+
const q = claude.question?.questions?.[0];
|
|
940
|
+
rows.push({
|
|
941
|
+
id: tui.id,
|
|
942
|
+
kind: "question",
|
|
943
|
+
message: q?.question ?? "Answer question?",
|
|
944
|
+
options: (q?.options ?? []).map((o) => o.label)
|
|
945
|
+
});
|
|
946
|
+
} else {
|
|
947
|
+
rows.push({
|
|
948
|
+
id: tui.id,
|
|
949
|
+
kind: "permission",
|
|
950
|
+
message: "Tool-permission prompt (screen-driven; inspect with `agentbox drive snapshot`)",
|
|
951
|
+
state: claude.state
|
|
952
|
+
});
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
return rows;
|
|
956
|
+
}
|
|
957
|
+
async function fetchRelayApprovals(relayUrl, boxId) {
|
|
958
|
+
const url = new URL("/admin/prompts", relayUrl);
|
|
959
|
+
url.searchParams.set("boxId", boxId);
|
|
960
|
+
const res = await fetch(url);
|
|
961
|
+
if (!res.ok) throw new Error(`relay /admin/prompts: HTTP ${String(res.status)}`);
|
|
962
|
+
const body = await res.json();
|
|
963
|
+
return body.prompts ?? [];
|
|
964
|
+
}
|
|
965
|
+
function approvalDisplay(row2) {
|
|
966
|
+
if (row2.kind === "host-action") {
|
|
967
|
+
const cmd = row2.command ?? row2.message;
|
|
968
|
+
const argv2 = row2.argv?.length ? ` ${row2.argv.join(" ")}` : "";
|
|
969
|
+
const detail = row2.detail ? ` (${row2.detail})` : "";
|
|
970
|
+
return `${row2.id} [host-action] ${cmd}${argv2}${detail}`;
|
|
971
|
+
}
|
|
972
|
+
if (row2.kind === "plan") {
|
|
973
|
+
return `${row2.id} [plan] ${firstLine(row2.plan)}`;
|
|
974
|
+
}
|
|
975
|
+
if (row2.kind === "question") {
|
|
976
|
+
return `${row2.id} [question] ${row2.message} {${row2.options.join(" | ")}}`;
|
|
977
|
+
}
|
|
978
|
+
return `${row2.id} [permission] ${row2.message}`;
|
|
979
|
+
}
|
|
980
|
+
function firstLine(s) {
|
|
981
|
+
const line = s.split("\n", 1)[0] ?? "";
|
|
982
|
+
return line.length > 100 ? line.slice(0, 99) + "\u2026" : line;
|
|
983
|
+
}
|
|
984
|
+
function sleep2(ms) {
|
|
985
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
986
|
+
}
|
|
612
987
|
function emitMatch(claude, asJson) {
|
|
613
988
|
if (asJson) {
|
|
614
989
|
process.stdout.write(JSON.stringify(claude) + "\n");
|
|
@@ -727,18 +1102,141 @@ function buildPromptArgs(agentKind, prompt, userArgs) {
|
|
|
727
1102
|
}
|
|
728
1103
|
|
|
729
1104
|
// src/lib/carry-resync.ts
|
|
730
|
-
import { join as
|
|
1105
|
+
import { join as join5 } from "path";
|
|
731
1106
|
|
|
732
1107
|
// src/lib/carry-resolve.ts
|
|
733
|
-
import { realpath, stat as
|
|
1108
|
+
import { realpath, stat as stat3 } from "fs/promises";
|
|
734
1109
|
import { homedir as homedir2 } from "os";
|
|
735
|
-
import { isAbsolute, join as
|
|
736
|
-
|
|
1110
|
+
import { isAbsolute, join as join4, normalize, relative, resolve } from "path";
|
|
1111
|
+
|
|
1112
|
+
// src/lib/dir-breakdown.ts
|
|
1113
|
+
import { readdir, stat as stat2 } from "fs/promises";
|
|
1114
|
+
import { join as join3 } from "path";
|
|
1115
|
+
var DEFAULT_CP_EXCLUDES = [
|
|
1116
|
+
".git",
|
|
1117
|
+
"node_modules",
|
|
1118
|
+
"bin",
|
|
1119
|
+
"obj",
|
|
1120
|
+
"packages",
|
|
1121
|
+
"dist",
|
|
1122
|
+
".next",
|
|
1123
|
+
"target"
|
|
1124
|
+
];
|
|
1125
|
+
function isBareName(token) {
|
|
1126
|
+
return !token.includes("/") && !token.includes("*") && !token.includes("?");
|
|
1127
|
+
}
|
|
1128
|
+
function toTarExcludes(tokens) {
|
|
1129
|
+
const out = [];
|
|
1130
|
+
for (const t of tokens) {
|
|
1131
|
+
if (isBareName(t)) {
|
|
1132
|
+
out.push(`*/${t}`, t);
|
|
1133
|
+
} else {
|
|
1134
|
+
out.push(t);
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
return out;
|
|
1138
|
+
}
|
|
1139
|
+
function effectiveExcludes(userTokens, useDefaults) {
|
|
1140
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1141
|
+
const out = [];
|
|
1142
|
+
const add = (t) => {
|
|
1143
|
+
if (t && !seen.has(t)) {
|
|
1144
|
+
seen.add(t);
|
|
1145
|
+
out.push(t);
|
|
1146
|
+
}
|
|
1147
|
+
};
|
|
1148
|
+
if (useDefaults) DEFAULT_CP_EXCLUDES.forEach(add);
|
|
1149
|
+
userTokens.forEach(add);
|
|
1150
|
+
return out;
|
|
1151
|
+
}
|
|
1152
|
+
function globToRegExp(glob) {
|
|
1153
|
+
const escaped = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
1154
|
+
return new RegExp(`^${escaped}$`);
|
|
1155
|
+
}
|
|
1156
|
+
function isPathExcluded(relPath, tokens) {
|
|
1157
|
+
const segs = relPath.split("/");
|
|
1158
|
+
for (const t of tokens) {
|
|
1159
|
+
if (isBareName(t)) {
|
|
1160
|
+
if (segs.includes(t)) return true;
|
|
1161
|
+
} else if (globToRegExp(t).test(relPath)) {
|
|
1162
|
+
return true;
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
return false;
|
|
1166
|
+
}
|
|
1167
|
+
async function buildNode(abs, rel, tokens, seen) {
|
|
1168
|
+
let bytes = 0;
|
|
1169
|
+
const children = [];
|
|
1170
|
+
let entries;
|
|
1171
|
+
try {
|
|
1172
|
+
entries = await readdir(abs);
|
|
1173
|
+
} catch {
|
|
1174
|
+
return { path: rel || ".", bytes: 0, children };
|
|
1175
|
+
}
|
|
1176
|
+
for (const name of entries) {
|
|
1177
|
+
const childRel = rel ? `${rel}/${name}` : name;
|
|
1178
|
+
if (isPathExcluded(childRel, tokens)) continue;
|
|
1179
|
+
const full = join3(abs, name);
|
|
1180
|
+
let st;
|
|
1181
|
+
try {
|
|
1182
|
+
st = await stat2(full);
|
|
1183
|
+
} catch {
|
|
1184
|
+
continue;
|
|
1185
|
+
}
|
|
1186
|
+
const key = `${String(st.dev)}:${String(st.ino)}`;
|
|
1187
|
+
if (seen.has(key)) continue;
|
|
1188
|
+
seen.add(key);
|
|
1189
|
+
if (st.isDirectory()) {
|
|
1190
|
+
const child = await buildNode(full, childRel, tokens, seen);
|
|
1191
|
+
bytes += child.bytes;
|
|
1192
|
+
children.push(child);
|
|
1193
|
+
} else if (st.isFile()) {
|
|
1194
|
+
bytes += st.size;
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
return { path: rel || ".", bytes, children };
|
|
1198
|
+
}
|
|
1199
|
+
async function measureCopy(absSrc, tokens, opts = {}) {
|
|
1200
|
+
const st = await stat2(absSrc);
|
|
1201
|
+
if (!st.isDirectory()) {
|
|
1202
|
+
return { totalBytes: st.size, isDir: false, treeLines: [], topChildren: [] };
|
|
1203
|
+
}
|
|
1204
|
+
const root = await buildNode(absSrc, "", tokens, /* @__PURE__ */ new Set());
|
|
1205
|
+
const maxDepth = opts.maxDepth ?? 3;
|
|
1206
|
+
const floor = opts.floorBytes ?? 10 * 1024 * 1024;
|
|
1207
|
+
const perLevel = opts.perLevel ?? 8;
|
|
1208
|
+
const lines = [];
|
|
1209
|
+
const render = (node, depth, label) => {
|
|
1210
|
+
const pad4 = " ".repeat(depth);
|
|
1211
|
+
lines.push(` ${fmtBytes(node.bytes).padStart(8)} ${pad4}${label}`);
|
|
1212
|
+
if (depth >= maxDepth) return;
|
|
1213
|
+
const kids = [...node.children].sort((a, b) => b.bytes - a.bytes).filter((c) => c.bytes >= floor).slice(0, perLevel);
|
|
1214
|
+
for (const kid of kids) {
|
|
1215
|
+
render(kid, depth + 1, `./${kid.path}`);
|
|
1216
|
+
}
|
|
1217
|
+
};
|
|
1218
|
+
render(root, 0, "./");
|
|
1219
|
+
const topChildren = [...root.children].sort((a, b) => b.bytes - a.bytes).map((c) => ({ path: c.path, bytes: c.bytes }));
|
|
1220
|
+
return { totalBytes: root.bytes, isDir: true, treeLines: lines, topChildren };
|
|
1221
|
+
}
|
|
1222
|
+
function fmtBytes(n) {
|
|
1223
|
+
if (n < 1024) return `${String(n)} B`;
|
|
1224
|
+
const units = ["KB", "MB", "GB", "TB"];
|
|
1225
|
+
let v = n / 1024;
|
|
1226
|
+
let i = 0;
|
|
1227
|
+
while (v >= 1024 && i < units.length - 1) {
|
|
1228
|
+
v /= 1024;
|
|
1229
|
+
i += 1;
|
|
1230
|
+
}
|
|
1231
|
+
return `${v >= 10 || Number.isInteger(v) ? v.toFixed(0) : v.toFixed(1)} ${units[i]}`;
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
// src/lib/carry-resolve.ts
|
|
737
1235
|
var DENYLIST_DEST_PREFIXES = ["/proc", "/sys", "/dev"];
|
|
738
1236
|
var DENYLIST_DEST_EXACT = /* @__PURE__ */ new Set(["/etc/passwd", "/etc/shadow"]);
|
|
739
1237
|
async function resolveCarry(items, opts) {
|
|
740
1238
|
const home = opts.homeDir ?? homedir2();
|
|
741
|
-
const cap = opts.maxBytes ??
|
|
1239
|
+
const cap = opts.maxBytes ?? BUILT_IN_DEFAULTS.box.cpMaxBytes;
|
|
742
1240
|
const projectRoot = opts.projectRoot;
|
|
743
1241
|
const entries = [];
|
|
744
1242
|
const errors = [];
|
|
@@ -753,13 +1251,6 @@ async function resolveCarry(items, opts) {
|
|
|
753
1251
|
}
|
|
754
1252
|
return { entries, errors };
|
|
755
1253
|
}
|
|
756
|
-
function readMaxBytesFromEnv() {
|
|
757
|
-
const raw = process.env.AGENTBOX_CARRY_MAX_BYTES;
|
|
758
|
-
if (!raw) return void 0;
|
|
759
|
-
const n = Number(raw);
|
|
760
|
-
if (!Number.isFinite(n) || n <= 0) return void 0;
|
|
761
|
-
return Math.floor(n);
|
|
762
|
-
}
|
|
763
1254
|
async function resolveOne(item, ctx) {
|
|
764
1255
|
const absSrc = expandHostPath(item.src, ctx);
|
|
765
1256
|
if (containsDotDot(absSrc)) {
|
|
@@ -772,7 +1263,7 @@ async function resolveOne(item, ctx) {
|
|
|
772
1263
|
const absDest = item.dest;
|
|
773
1264
|
let st;
|
|
774
1265
|
try {
|
|
775
|
-
st = await
|
|
1266
|
+
st = await stat3(absSrc);
|
|
776
1267
|
} catch (err) {
|
|
777
1268
|
if (err.code === "ENOENT") {
|
|
778
1269
|
if (optional) {
|
|
@@ -806,10 +1297,12 @@ async function resolveOne(item, ctx) {
|
|
|
806
1297
|
} catch {
|
|
807
1298
|
}
|
|
808
1299
|
if (st.isDirectory()) {
|
|
809
|
-
const
|
|
1300
|
+
const tokens = effectiveExcludes(item.exclude ?? [], true);
|
|
1301
|
+
const tarPatterns = toTarExcludes(tokens);
|
|
1302
|
+
const bytes = await dirSizeCapped(absSrc, ctx.cap, tokens);
|
|
810
1303
|
if (bytes > ctx.cap) {
|
|
811
1304
|
throw new Error(
|
|
812
|
-
`${ctx.where}: dir "${absSrc}" exceeds ${String(ctx.cap)} bytes (
|
|
1305
|
+
`${ctx.where}: dir "${absSrc}" exceeds ${String(ctx.cap)} bytes after excludes (add carry exclude: patterns, raise box.cpMaxBytes, or narrow the path)`
|
|
813
1306
|
);
|
|
814
1307
|
}
|
|
815
1308
|
return {
|
|
@@ -821,6 +1314,7 @@ async function resolveOne(item, ctx) {
|
|
|
821
1314
|
bytes,
|
|
822
1315
|
...item.mode !== void 0 ? { mode: item.mode } : {},
|
|
823
1316
|
...item.user !== void 0 ? { user: item.user } : {},
|
|
1317
|
+
...tarPatterns.length > 0 ? { exclude: tarPatterns } : {},
|
|
824
1318
|
optional,
|
|
825
1319
|
...symlinkInfo ? { symlinkInfo } : {}
|
|
826
1320
|
};
|
|
@@ -828,7 +1322,7 @@ async function resolveOne(item, ctx) {
|
|
|
828
1322
|
if (st.isFile()) {
|
|
829
1323
|
if (st.size > ctx.cap) {
|
|
830
1324
|
throw new Error(
|
|
831
|
-
`${ctx.where}: file "${absSrc}" is ${String(st.size)} bytes, exceeds cap ${String(ctx.cap)} (
|
|
1325
|
+
`${ctx.where}: file "${absSrc}" is ${String(st.size)} bytes, exceeds cap ${String(ctx.cap)} (raise box.cpMaxBytes)`
|
|
832
1326
|
);
|
|
833
1327
|
}
|
|
834
1328
|
return {
|
|
@@ -893,24 +1387,26 @@ async function realpathSafe(p) {
|
|
|
893
1387
|
return resolve(p);
|
|
894
1388
|
}
|
|
895
1389
|
}
|
|
896
|
-
async function dirSizeCapped(dir, cap) {
|
|
1390
|
+
async function dirSizeCapped(dir, cap, exclude = []) {
|
|
897
1391
|
let total = 0;
|
|
898
1392
|
const seen = /* @__PURE__ */ new Set();
|
|
899
1393
|
async function walk(d) {
|
|
900
1394
|
if (total > cap) return;
|
|
901
|
-
const { readdir:
|
|
1395
|
+
const { readdir: readdir4 } = await import("fs/promises");
|
|
902
1396
|
let names;
|
|
903
1397
|
try {
|
|
904
|
-
names = await
|
|
1398
|
+
names = await readdir4(d);
|
|
905
1399
|
} catch {
|
|
906
1400
|
return;
|
|
907
1401
|
}
|
|
908
1402
|
for (const name of names) {
|
|
909
1403
|
if (total > cap) return;
|
|
910
|
-
const full =
|
|
1404
|
+
const full = join4(d, name);
|
|
1405
|
+
const rel = relative(dir, full).split(/[\\/]/).join("/");
|
|
1406
|
+
if (exclude.length > 0 && isPathExcluded(rel, exclude)) continue;
|
|
911
1407
|
let st;
|
|
912
1408
|
try {
|
|
913
|
-
st = await
|
|
1409
|
+
st = await stat3(full);
|
|
914
1410
|
} catch {
|
|
915
1411
|
continue;
|
|
916
1412
|
}
|
|
@@ -935,9 +1431,13 @@ async function resyncCarryFiles(args) {
|
|
|
935
1431
|
});
|
|
936
1432
|
const prior = args.box.carry?.entries ?? [];
|
|
937
1433
|
if (prior.length === 0) return { recopied: 0, skippedNew: 0 };
|
|
938
|
-
const items = await loadCarrySection(
|
|
1434
|
+
const items = await loadCarrySection(join5(args.projectRoot, "agentbox.yaml"));
|
|
939
1435
|
if (items.length === 0) return { recopied: 0, skippedNew: 0 };
|
|
940
|
-
const
|
|
1436
|
+
const cfg = await loadEffectiveConfig(args.projectRoot);
|
|
1437
|
+
const resolved = await resolveCarry(items, {
|
|
1438
|
+
projectRoot: args.projectRoot,
|
|
1439
|
+
maxBytes: cfg.effective.box.cpMaxBytes
|
|
1440
|
+
});
|
|
941
1441
|
if (resolved.errors.length > 0) {
|
|
942
1442
|
log47(`carry: resync skipped (resolve errors: ${resolved.errors.length})`);
|
|
943
1443
|
return { recopied: 0, skippedNew: 0 };
|
|
@@ -1072,6 +1572,7 @@ async function submitQueueJob(input) {
|
|
|
1072
1572
|
createOpts: input.createOpts,
|
|
1073
1573
|
maxConcurrent: ceiling,
|
|
1074
1574
|
...maxWorking !== void 0 ? { maxWorking } : {},
|
|
1575
|
+
...input.openTerminal !== void 0 ? { openTerminal: input.openTerminal } : {},
|
|
1075
1576
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1076
1577
|
logPath: queueLogPath(id)
|
|
1077
1578
|
};
|
|
@@ -1126,6 +1627,58 @@ function postEnqueue(id) {
|
|
|
1126
1627
|
});
|
|
1127
1628
|
}
|
|
1128
1629
|
|
|
1630
|
+
// src/terminal/queue-open.ts
|
|
1631
|
+
function captureOpenTerminalContext(mode, env = process.env, cwd = process.cwd()) {
|
|
1632
|
+
if (mode === "none") return void 0;
|
|
1633
|
+
const host = detectHostTerminal(env);
|
|
1634
|
+
if (host === "unknown") return void 0;
|
|
1635
|
+
const base = { host, mode, cwd };
|
|
1636
|
+
if (host === "tmux") {
|
|
1637
|
+
return { ...base, host, tmuxSocket: env["TMUX"], tmuxPane: env["TMUX_PANE"] };
|
|
1638
|
+
}
|
|
1639
|
+
if (host === "cmux") {
|
|
1640
|
+
return {
|
|
1641
|
+
...base,
|
|
1642
|
+
host,
|
|
1643
|
+
cmuxSocket: env["CMUX_SOCKET_PATH"],
|
|
1644
|
+
cmuxBundledCli: cmuxBinary(env),
|
|
1645
|
+
cmuxSurfaceId: env["CMUX_SURFACE_ID"],
|
|
1646
|
+
cmuxWorkspaceId: env["CMUX_WORKSPACE_ID"]
|
|
1647
|
+
};
|
|
1648
|
+
}
|
|
1649
|
+
return { ...base, host };
|
|
1650
|
+
}
|
|
1651
|
+
function spawnQueuedOpenTerminal(ctx, argv2, title) {
|
|
1652
|
+
if (ctx.host === "tmux") {
|
|
1653
|
+
return spawnInNewTerminal({
|
|
1654
|
+
host: "tmux",
|
|
1655
|
+
mode: ctx.mode,
|
|
1656
|
+
argv: argv2,
|
|
1657
|
+
cwd: ctx.cwd,
|
|
1658
|
+
title,
|
|
1659
|
+
env: ctx.tmuxSocket ? { ...process.env, TMUX: ctx.tmuxSocket } : process.env,
|
|
1660
|
+
tmuxTarget: ctx.tmuxPane
|
|
1661
|
+
});
|
|
1662
|
+
}
|
|
1663
|
+
if (ctx.host === "cmux") {
|
|
1664
|
+
const env = { ...process.env };
|
|
1665
|
+
if (ctx.cmuxSocket) env["CMUX_SOCKET_PATH"] = ctx.cmuxSocket;
|
|
1666
|
+
if (ctx.cmuxBundledCli) env["CMUX_BUNDLED_CLI_PATH"] = ctx.cmuxBundledCli;
|
|
1667
|
+
return spawnInNewTerminal({
|
|
1668
|
+
host: "cmux",
|
|
1669
|
+
mode: ctx.mode,
|
|
1670
|
+
argv: argv2,
|
|
1671
|
+
cwd: ctx.cwd,
|
|
1672
|
+
title,
|
|
1673
|
+
env,
|
|
1674
|
+
cmuxTargetSurface: ctx.cmuxSurfaceId,
|
|
1675
|
+
cmuxTargetWorkspace: ctx.cmuxWorkspaceId,
|
|
1676
|
+
cmuxWorkspaceFallback: true
|
|
1677
|
+
});
|
|
1678
|
+
}
|
|
1679
|
+
return spawnInNewTerminal({ host: "iterm2", mode: ctx.mode, argv: argv2, cwd: ctx.cwd, title });
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1129
1682
|
// src/commands/_attach-in.ts
|
|
1130
1683
|
var VALUES = ["split", "window", "tab", "same"];
|
|
1131
1684
|
var ATTACH_IN_HELP = "where to open the attached session: split | window | tab | same (default from attach.openIn, built-in: split). Only effective when running inside tmux or iTerm2; falls back to inline attach otherwise.";
|
|
@@ -1366,14 +1919,14 @@ async function cloudAgentCreate(args) {
|
|
|
1366
1919
|
}
|
|
1367
1920
|
|
|
1368
1921
|
// src/lib/carry-gate.ts
|
|
1369
|
-
import { join as
|
|
1922
|
+
import { join as join6 } from "path";
|
|
1370
1923
|
import { log as log5 } from "@clack/prompts";
|
|
1371
1924
|
|
|
1372
1925
|
// src/carry-prompt.ts
|
|
1373
1926
|
import { isCancel, log as log4, select } from "@clack/prompts";
|
|
1374
1927
|
|
|
1375
1928
|
// src/fmt.ts
|
|
1376
|
-
function
|
|
1929
|
+
function fmtBytes2(n) {
|
|
1377
1930
|
if (n === null || n === void 0) return "n/a";
|
|
1378
1931
|
if (n < 1024) return `${String(n)} B`;
|
|
1379
1932
|
if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KiB`;
|
|
@@ -1449,7 +2002,7 @@ function printSummary(entries) {
|
|
|
1449
2002
|
if (e.mode !== void 0) flags.push(`mode ${e.mode.toString(8).padStart(4, "0")}`);
|
|
1450
2003
|
if (e.user !== void 0 && e.user !== 1e3) flags.push(`user ${String(e.user)}`);
|
|
1451
2004
|
if (e.symlinkInfo === "outside-home") flags.push("symlink \u2192 outside $HOME!");
|
|
1452
|
-
const size = e.kind === "missing" ? "\u2014" :
|
|
2005
|
+
const size = e.kind === "missing" ? "\u2014" : fmtBytes2(e.bytes ?? 0);
|
|
1453
2006
|
rows.push(
|
|
1454
2007
|
` ${pad(e.rawSrc, srcW)} \u2192 ${pad(e.rawDest, destW)} ${pad(size, 9)} ${flags.join(", ")}`
|
|
1455
2008
|
);
|
|
@@ -1465,10 +2018,14 @@ function pad(s, w) {
|
|
|
1465
2018
|
async function runCarryGate(args) {
|
|
1466
2019
|
const emit = args.onLog ?? (() => {
|
|
1467
2020
|
});
|
|
1468
|
-
const yamlPath =
|
|
2021
|
+
const yamlPath = join6(args.projectRoot, "agentbox.yaml");
|
|
1469
2022
|
const items = await loadCarrySection(yamlPath);
|
|
1470
2023
|
if (items.length === 0) return { decision: "approve", entries: [] };
|
|
1471
|
-
const
|
|
2024
|
+
const cfg = await loadEffectiveConfig(args.projectRoot);
|
|
2025
|
+
const resolved = await resolveCarry(items, {
|
|
2026
|
+
projectRoot: args.projectRoot,
|
|
2027
|
+
maxBytes: cfg.effective.box.cpMaxBytes
|
|
2028
|
+
});
|
|
1472
2029
|
if (resolved.errors.length > 0) {
|
|
1473
2030
|
const msg = ["carry: refused to proceed:", ...resolved.errors.map((e) => ` - ${e}`)].join("\n");
|
|
1474
2031
|
throw new Error(msg);
|
|
@@ -1513,10 +2070,10 @@ async function runQueuedCarryGate(args) {
|
|
|
1513
2070
|
}
|
|
1514
2071
|
|
|
1515
2072
|
// src/session-teleport/claude.ts
|
|
1516
|
-
import { mkdir, mkdtemp, readdir, readFile as readFile2, stat as
|
|
2073
|
+
import { mkdir, mkdtemp, readdir as readdir2, readFile as readFile2, stat as stat4, writeFile } from "fs/promises";
|
|
1517
2074
|
import { existsSync } from "fs";
|
|
1518
2075
|
import { homedir as homedir4, tmpdir } from "os";
|
|
1519
|
-
import { join as
|
|
2076
|
+
import { join as join7 } from "path";
|
|
1520
2077
|
|
|
1521
2078
|
// src/session-teleport/cwd-encoding.ts
|
|
1522
2079
|
function encodeClaudeProjectsDir(absPath) {
|
|
@@ -1537,7 +2094,7 @@ var TeleportError = class extends Error {
|
|
|
1537
2094
|
var BOX_CLAUDE_PROJECTS_DIR = `/home/vscode/.claude/projects/${BOX_WORKSPACE_ENCODED}`;
|
|
1538
2095
|
async function resolveClaudeTeleport(opts) {
|
|
1539
2096
|
const hostHome = opts.hostHome ?? homedir4();
|
|
1540
|
-
const projectDir =
|
|
2097
|
+
const projectDir = join7(
|
|
1541
2098
|
hostHome,
|
|
1542
2099
|
".claude",
|
|
1543
2100
|
"projects",
|
|
@@ -1550,8 +2107,8 @@ async function resolveClaudeTeleport(opts) {
|
|
|
1550
2107
|
}
|
|
1551
2108
|
const sessionPath = await pickSessionFile(projectDir, opts.mode);
|
|
1552
2109
|
const sessionId = sessionPath.replace(/\.jsonl$/u, "").split("/").pop() ?? "";
|
|
1553
|
-
const stage = await mkdtemp(
|
|
1554
|
-
const stagedFile =
|
|
2110
|
+
const stage = await mkdtemp(join7(tmpdir(), "agentbox-teleport-claude-"));
|
|
2111
|
+
const stagedFile = join7(stage, `${sessionId}.jsonl`);
|
|
1555
2112
|
await rewriteSessionFile(sessionPath, stagedFile, opts.hostCwd);
|
|
1556
2113
|
opts.log?.(`teleport: claude session ${sessionId} staged for upload`);
|
|
1557
2114
|
return {
|
|
@@ -1565,7 +2122,7 @@ async function resolveClaudeTeleport(opts) {
|
|
|
1565
2122
|
}
|
|
1566
2123
|
async function pickSessionFile(projectDir, mode) {
|
|
1567
2124
|
if (mode.kind === "resume") {
|
|
1568
|
-
const target =
|
|
2125
|
+
const target = join7(projectDir, `${mode.id}.jsonl`);
|
|
1569
2126
|
if (!existsSync(target)) {
|
|
1570
2127
|
throw new TeleportError(
|
|
1571
2128
|
`Claude session "${mode.id}" not found in ${projectDir}. List available sessions with: ls "${projectDir}"`
|
|
@@ -1573,7 +2130,7 @@ async function pickSessionFile(projectDir, mode) {
|
|
|
1573
2130
|
}
|
|
1574
2131
|
return target;
|
|
1575
2132
|
}
|
|
1576
|
-
const entries = await
|
|
2133
|
+
const entries = await readdir2(projectDir);
|
|
1577
2134
|
const jsonl = entries.filter((e) => e.endsWith(".jsonl"));
|
|
1578
2135
|
if (jsonl.length === 0) {
|
|
1579
2136
|
throw new TeleportError(
|
|
@@ -1582,8 +2139,8 @@ async function pickSessionFile(projectDir, mode) {
|
|
|
1582
2139
|
}
|
|
1583
2140
|
const stats = await Promise.all(
|
|
1584
2141
|
jsonl.map(async (name) => {
|
|
1585
|
-
const full =
|
|
1586
|
-
const s = await
|
|
2142
|
+
const full = join7(projectDir, name);
|
|
2143
|
+
const s = await stat4(full);
|
|
1587
2144
|
return { full, mtimeMs: s.mtimeMs };
|
|
1588
2145
|
})
|
|
1589
2146
|
);
|
|
@@ -1616,18 +2173,18 @@ async function rewriteSessionFile(src, dst, hostCwd) {
|
|
|
1616
2173
|
}
|
|
1617
2174
|
out.push(line);
|
|
1618
2175
|
}
|
|
1619
|
-
await mkdir(
|
|
2176
|
+
await mkdir(join7(dst, ".."), { recursive: true });
|
|
1620
2177
|
await writeFile(dst, out.join("\n"), "utf8");
|
|
1621
2178
|
}
|
|
1622
2179
|
|
|
1623
2180
|
// src/session-teleport/codex.ts
|
|
1624
|
-
import { mkdtemp as mkdtemp2, readdir as
|
|
2181
|
+
import { mkdtemp as mkdtemp2, readdir as readdir3, readFile as readFile3, stat as stat5, writeFile as writeFile2 } from "fs/promises";
|
|
1625
2182
|
import { existsSync as existsSync2 } from "fs";
|
|
1626
2183
|
import { homedir as homedir5, tmpdir as tmpdir2 } from "os";
|
|
1627
|
-
import { join as
|
|
2184
|
+
import { join as join8 } from "path";
|
|
1628
2185
|
async function resolveCodexTeleport(opts) {
|
|
1629
2186
|
const hostHome = opts.hostHome ?? homedir5();
|
|
1630
|
-
const sessionsRoot =
|
|
2187
|
+
const sessionsRoot = join8(hostHome, ".codex", "sessions");
|
|
1631
2188
|
if (!existsSync2(sessionsRoot)) {
|
|
1632
2189
|
throw new TeleportError(
|
|
1633
2190
|
`no Codex session history found on the host (expected at ${sessionsRoot}). Run \`codex\` at least once before using -c / --resume.`
|
|
@@ -1669,8 +2226,8 @@ async function resolveCodexTeleport(opts) {
|
|
|
1669
2226
|
matching.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
1670
2227
|
picked = matching[0];
|
|
1671
2228
|
}
|
|
1672
|
-
const stage = await mkdtemp2(
|
|
1673
|
-
const stagedFile =
|
|
2229
|
+
const stage = await mkdtemp2(join8(tmpdir2(), "agentbox-teleport-codex-"));
|
|
2230
|
+
const stagedFile = join8(stage, posixBasename(picked.relPath));
|
|
1674
2231
|
await rewriteCodexSession(picked.hostPath, stagedFile, opts.hostCwd);
|
|
1675
2232
|
opts.log?.(`teleport: codex session ${picked.uuid} staged for upload`);
|
|
1676
2233
|
const boxParentDir = `/home/vscode/.codex/sessions/${posixDirname(picked.relPath)}`;
|
|
@@ -1696,24 +2253,24 @@ async function listCodexSessions(sessionsRoot) {
|
|
|
1696
2253
|
const years = await safeReaddir(sessionsRoot);
|
|
1697
2254
|
for (const y of years) {
|
|
1698
2255
|
if (!/^\d{4}$/u.test(y)) continue;
|
|
1699
|
-
const yDir =
|
|
2256
|
+
const yDir = join8(sessionsRoot, y);
|
|
1700
2257
|
const months = await safeReaddir(yDir);
|
|
1701
2258
|
for (const m of months) {
|
|
1702
2259
|
if (!/^\d{2}$/u.test(m)) continue;
|
|
1703
|
-
const mDir =
|
|
2260
|
+
const mDir = join8(yDir, m);
|
|
1704
2261
|
const days = await safeReaddir(mDir);
|
|
1705
2262
|
for (const d of days) {
|
|
1706
2263
|
if (!/^\d{2}$/u.test(d)) continue;
|
|
1707
|
-
const dDir =
|
|
2264
|
+
const dDir = join8(mDir, d);
|
|
1708
2265
|
const files = await safeReaddir(dDir);
|
|
1709
2266
|
for (const name of files) {
|
|
1710
2267
|
if (!name.startsWith("rollout-") || !name.endsWith(".jsonl")) continue;
|
|
1711
2268
|
const uuid = extractCodexUuid(name);
|
|
1712
2269
|
if (uuid === null) continue;
|
|
1713
|
-
const hostPath =
|
|
2270
|
+
const hostPath = join8(dDir, name);
|
|
1714
2271
|
let mtimeMs = 0;
|
|
1715
2272
|
try {
|
|
1716
|
-
mtimeMs = (await
|
|
2273
|
+
mtimeMs = (await stat5(hostPath)).mtimeMs;
|
|
1717
2274
|
} catch {
|
|
1718
2275
|
continue;
|
|
1719
2276
|
}
|
|
@@ -1733,7 +2290,7 @@ async function listCodexSessions(sessionsRoot) {
|
|
|
1733
2290
|
}
|
|
1734
2291
|
async function safeReaddir(dir) {
|
|
1735
2292
|
try {
|
|
1736
|
-
return await
|
|
2293
|
+
return await readdir3(dir);
|
|
1737
2294
|
} catch {
|
|
1738
2295
|
return [];
|
|
1739
2296
|
}
|
|
@@ -1745,16 +2302,16 @@ function extractCodexUuid(filename) {
|
|
|
1745
2302
|
return m ? m[1] : null;
|
|
1746
2303
|
}
|
|
1747
2304
|
async function peekCodexCwd(file) {
|
|
1748
|
-
let
|
|
2305
|
+
let firstLine3;
|
|
1749
2306
|
try {
|
|
1750
2307
|
const buf = await readFile3(file, "utf8");
|
|
1751
2308
|
const nl = buf.indexOf("\n");
|
|
1752
|
-
|
|
2309
|
+
firstLine3 = nl === -1 ? buf : buf.slice(0, nl);
|
|
1753
2310
|
} catch {
|
|
1754
2311
|
return null;
|
|
1755
2312
|
}
|
|
1756
2313
|
try {
|
|
1757
|
-
const parsed = JSON.parse(
|
|
2314
|
+
const parsed = JSON.parse(firstLine3);
|
|
1758
2315
|
if (parsed.type === "session_meta" && typeof parsed.payload?.cwd === "string") {
|
|
1759
2316
|
return parsed.payload.cwd;
|
|
1760
2317
|
}
|
|
@@ -1836,11 +2393,11 @@ async function uploadTeleport(input) {
|
|
|
1836
2393
|
import { mkdtemp as mkdtemp3, readFile as readFile4, writeFile as writeFile3 } from "fs/promises";
|
|
1837
2394
|
import { existsSync as existsSync3 } from "fs";
|
|
1838
2395
|
import { homedir as homedir6, tmpdir as tmpdir3 } from "os";
|
|
1839
|
-
import { basename, isAbsolute as isAbsolute2, join as
|
|
2396
|
+
import { basename, isAbsolute as isAbsolute2, join as join9, resolve as resolve2 } from "path";
|
|
1840
2397
|
var BOX_CLAUDE_PLANS_DIR = "/home/vscode/.claude/plans";
|
|
1841
2398
|
function expandHome(p, hostHome) {
|
|
1842
2399
|
if (p === "~") return hostHome;
|
|
1843
|
-
if (p.startsWith("~/")) return
|
|
2400
|
+
if (p.startsWith("~/")) return join9(hostHome, p.slice(2));
|
|
1844
2401
|
return isAbsolute2(p) ? p : resolve2(p);
|
|
1845
2402
|
}
|
|
1846
2403
|
async function resolvePlanTeleport(opts) {
|
|
@@ -1852,8 +2409,8 @@ async function resolvePlanTeleport(opts) {
|
|
|
1852
2409
|
);
|
|
1853
2410
|
}
|
|
1854
2411
|
const name = basename(planFile);
|
|
1855
|
-
const stage = await mkdtemp3(
|
|
1856
|
-
const stagedFile =
|
|
2412
|
+
const stage = await mkdtemp3(join9(tmpdir3(), "agentbox-teleport-plan-"));
|
|
2413
|
+
const stagedFile = join9(stage, name);
|
|
1857
2414
|
const raw = await readFile4(planFile, "utf8");
|
|
1858
2415
|
const absCwd = opts.hostCwd ? resolve2(expandHome(opts.hostCwd, hostHome)) : "";
|
|
1859
2416
|
const rewritten = absCwd ? raw.split(absCwd).join(BOX_WORKSPACE) : raw;
|
|
@@ -1873,14 +2430,14 @@ async function resolvePlanTeleport(opts) {
|
|
|
1873
2430
|
import { log as log6 } from "@clack/prompts";
|
|
1874
2431
|
import { existsSync as existsSync4, mkdirSync, writeFileSync } from "fs";
|
|
1875
2432
|
import { homedir as homedir7 } from "os";
|
|
1876
|
-
import { join as
|
|
2433
|
+
import { join as join10 } from "path";
|
|
1877
2434
|
function maybeShowInstallHint() {
|
|
1878
2435
|
try {
|
|
1879
|
-
const skill =
|
|
2436
|
+
const skill = join10(homedir7(), ".claude", "skills", "agentbox", "SKILL.md");
|
|
1880
2437
|
if (existsSync4(skill)) return;
|
|
1881
|
-
const marker =
|
|
2438
|
+
const marker = join10(homedir7(), ".agentbox", "install-hint-shown");
|
|
1882
2439
|
if (existsSync4(marker)) return;
|
|
1883
|
-
mkdirSync(
|
|
2440
|
+
mkdirSync(join10(homedir7(), ".agentbox"), { recursive: true });
|
|
1884
2441
|
writeFileSync(marker, "");
|
|
1885
2442
|
log6.info("tip: run 'agentbox install' to enable the /agentbox fork command in host Claude");
|
|
1886
2443
|
} catch {
|
|
@@ -1890,18 +2447,18 @@ function maybeShowInstallHint() {
|
|
|
1890
2447
|
// src/lib/log-file.ts
|
|
1891
2448
|
import { closeSync, mkdirSync as mkdirSync2, openSync, renameSync, symlinkSync, unlinkSync, writeFileSync as writeFileSync2, writeSync } from "fs";
|
|
1892
2449
|
import { homedir as homedir8 } from "os";
|
|
1893
|
-
import { join as
|
|
2450
|
+
import { join as join11 } from "path";
|
|
1894
2451
|
function stateDir() {
|
|
1895
|
-
return process.env.AGENTBOX_HOME ??
|
|
2452
|
+
return process.env.AGENTBOX_HOME ?? join11(homedir8(), ".agentbox");
|
|
1896
2453
|
}
|
|
1897
2454
|
function logsDir() {
|
|
1898
|
-
return
|
|
2455
|
+
return join11(stateDir(), "logs");
|
|
1899
2456
|
}
|
|
1900
2457
|
function openCommandLog(command) {
|
|
1901
2458
|
const dir = logsDir();
|
|
1902
2459
|
mkdirSync2(dir, { recursive: true });
|
|
1903
|
-
const path =
|
|
1904
|
-
const prev =
|
|
2460
|
+
const path = join11(dir, `${command}.log`);
|
|
2461
|
+
const prev = join11(dir, `${command}.log.prev`);
|
|
1905
2462
|
try {
|
|
1906
2463
|
renameSync(path, prev);
|
|
1907
2464
|
} catch {
|
|
@@ -1961,7 +2518,7 @@ function openCommandLog(command) {
|
|
|
1961
2518
|
};
|
|
1962
2519
|
}
|
|
1963
2520
|
function updateLatestSymlink(dir, target) {
|
|
1964
|
-
const link =
|
|
2521
|
+
const link = join11(dir, "latest.log");
|
|
1965
2522
|
try {
|
|
1966
2523
|
unlinkSync(link);
|
|
1967
2524
|
} catch {
|
|
@@ -2075,13 +2632,13 @@ import { basename as basename2 } from "path";
|
|
|
2075
2632
|
async function cloudBackendForProvider(provider) {
|
|
2076
2633
|
switch (provider) {
|
|
2077
2634
|
case "daytona":
|
|
2078
|
-
return (await import("./dist-
|
|
2635
|
+
return (await import("./dist-FIFEFKJ7.js")).daytonaBackend;
|
|
2079
2636
|
case "hetzner":
|
|
2080
|
-
return (await import("./dist-
|
|
2637
|
+
return (await import("./dist-AGTIA7AD.js")).hetznerBackend;
|
|
2081
2638
|
case "vercel":
|
|
2082
|
-
return (await import("./dist-
|
|
2639
|
+
return (await import("./dist-S4XR4ACV.js")).vercelBackend;
|
|
2083
2640
|
case "e2b":
|
|
2084
|
-
return (await import("./dist-
|
|
2641
|
+
return (await import("./dist-JZ3XO6EB.js")).e2bBackend;
|
|
2085
2642
|
default:
|
|
2086
2643
|
return null;
|
|
2087
2644
|
}
|
|
@@ -2089,13 +2646,13 @@ async function cloudBackendForProvider(provider) {
|
|
|
2089
2646
|
async function currentCloudBaseFingerprintLive(provider) {
|
|
2090
2647
|
switch (provider) {
|
|
2091
2648
|
case "daytona":
|
|
2092
|
-
return (await import("./dist-
|
|
2649
|
+
return (await import("./dist-FIFEFKJ7.js")).currentDaytonaBaseFingerprintLive();
|
|
2093
2650
|
case "hetzner":
|
|
2094
|
-
return (await import("./dist-
|
|
2651
|
+
return (await import("./dist-AGTIA7AD.js")).currentHetznerBaseFingerprintLive();
|
|
2095
2652
|
case "vercel":
|
|
2096
|
-
return (await import("./dist-
|
|
2653
|
+
return (await import("./dist-S4XR4ACV.js")).currentVercelBaseFingerprintLive();
|
|
2097
2654
|
case "e2b":
|
|
2098
|
-
return (await import("./dist-
|
|
2655
|
+
return (await import("./dist-JZ3XO6EB.js")).currentE2bBaseFingerprintLive();
|
|
2099
2656
|
default:
|
|
2100
2657
|
return void 0;
|
|
2101
2658
|
}
|
|
@@ -2173,18 +2730,30 @@ async function evaluateBaseFreshness(provider) {
|
|
|
2173
2730
|
|
|
2174
2731
|
// src/wizard.ts
|
|
2175
2732
|
var IN_BOX_SETUP_GUIDE_PATH = "/usr/local/share/agentbox/setup-guide.md";
|
|
2176
|
-
function buildSetupInitialPrompt(workspace) {
|
|
2733
|
+
function buildSetupInitialPrompt(workspace, hasAgentboxYaml = false) {
|
|
2177
2734
|
const name = basename2(workspace);
|
|
2735
|
+
if (hasAgentboxYaml) {
|
|
2736
|
+
return `The user reopened the agentbox sandbox for "${name}". The workspace already has a /workspace/agentbox.yaml, but the previous snapshot was stale so the box was rebuilt from a fresh base image \u2014 its dependencies and system setup are NOT installed yet. Please run the /agentbox-setup skill (or read ${IN_BOX_SETUP_GUIDE_PATH} if the skill is not loaded), then reuse the existing /workspace/agentbox.yaml: verify the declared tasks and services still fit the project, (re)install dependencies and any system packages the setup needs on this fresh base, and update agentbox.yaml only if something is missing or wrong \u2014 do not regenerate it from scratch. Then run \`agentbox-ctl reload\` from inside the box so the already-running supervisor re-applies the config and immediately runs the declared tasks and autostarts the services (no box restart needed). When the box is warm, capture it with \`agentbox checkpoint --set-default\` so future boxes skip this rebuild. Finally, summarise what you verified and changed, and remind the user how to land any agentbox.yaml change on the host (commit through the bind-mounted .git, or "agentbox download env" on the host).`;
|
|
2737
|
+
}
|
|
2178
2738
|
return `The user just opened a new agentbox sandbox for "${name}" but the workspace has no agentbox.yaml yet. Please run the /agentbox-setup skill (or read ${IN_BOX_SETUP_GUIDE_PATH} if the skill is not loaded), then explore /workspace and propose an agentbox.yaml. Save the file to /workspace/agentbox.yaml. Then run \`agentbox-ctl reload\` from inside the box so the already-running supervisor applies the new config and immediately runs the declared tasks and autostarts the services (no box restart needed). When done, summarise what services and tasks you declared, and remind the user how to land the file on the host (commit through the bind-mounted .git, or "agentbox download env" on the host).`;
|
|
2179
2739
|
}
|
|
2180
2740
|
var WIZARD_AUTOLAUNCH_ENV = "AGENTBOX_WIZARD_AUTOLAUNCH";
|
|
2181
2741
|
var WIZARD_ENV_FILES_ENV = "AGENTBOX_WIZARD_ENV_FILES";
|
|
2742
|
+
var WIZARD_RECREATE_ENV = "AGENTBOX_WIZARD_RECREATE";
|
|
2182
2743
|
var WIZARD_ENV_SCAN_PATTERNS = DEFAULT_ENV_PATTERNS.filter((p) => p !== "agentbox.yaml");
|
|
2183
2744
|
async function maybeRunSetupWizard(args) {
|
|
2184
2745
|
if (process.env[WIZARD_AUTOLAUNCH_ENV] === "1") {
|
|
2185
2746
|
if (args.command !== "claude") return { action: "proceed" };
|
|
2186
2747
|
const envFiles = parseEnvFilesFromEnv(process.env[WIZARD_ENV_FILES_ENV]);
|
|
2187
2748
|
const proj2 = await findProjectRoot(args.workspace);
|
|
2749
|
+
if (process.env[WIZARD_RECREATE_ENV] === "1") {
|
|
2750
|
+
return {
|
|
2751
|
+
action: "launch-with-prompt",
|
|
2752
|
+
initialPrompt: buildSetupInitialPrompt(proj2.root, proj2.hasAgentboxYaml),
|
|
2753
|
+
envFilesToImport: envFiles,
|
|
2754
|
+
discardCheckpoint: true
|
|
2755
|
+
};
|
|
2756
|
+
}
|
|
2188
2757
|
return nonInteractiveOutcome(args, proj2, await checkpointStatus(args, proj2.root), envFiles);
|
|
2189
2758
|
}
|
|
2190
2759
|
const proj = await findProjectRoot(args.workspace);
|
|
@@ -2218,13 +2787,6 @@ async function maybeRunSetupWizard(args) {
|
|
|
2218
2787
|
};
|
|
2219
2788
|
}
|
|
2220
2789
|
}
|
|
2221
|
-
if (proj.hasAgentboxYaml) {
|
|
2222
|
-
return { action: "proceed", discardCheckpoint: discardOnMissing(status, fromDefault) };
|
|
2223
|
-
}
|
|
2224
|
-
if (!rebuildBase && (status.state === "fresh" || status.state === "stale" && !fromDefault)) {
|
|
2225
|
-
log8.info(`starting from checkpoint "${args.checkpointRef}"; skipping agentbox.yaml setup`);
|
|
2226
|
-
return { action: "proceed" };
|
|
2227
|
-
}
|
|
2228
2790
|
let discardCheckpoint = rebuildBase;
|
|
2229
2791
|
let recreateChosen = rebuildBase;
|
|
2230
2792
|
if (!rebuildBase && status.state === "stale" && fromDefault) {
|
|
@@ -2237,7 +2799,15 @@ async function maybeRunSetupWizard(args) {
|
|
|
2237
2799
|
}
|
|
2238
2800
|
discardCheckpoint = true;
|
|
2239
2801
|
recreateChosen = true;
|
|
2240
|
-
}
|
|
2802
|
+
}
|
|
2803
|
+
if (proj.hasAgentboxYaml && !recreateChosen) {
|
|
2804
|
+
return { action: "proceed", discardCheckpoint: discardOnMissing(status, fromDefault) };
|
|
2805
|
+
}
|
|
2806
|
+
if (!rebuildBase && !recreateChosen && (status.state === "fresh" || status.state === "stale" && !fromDefault)) {
|
|
2807
|
+
log8.info(`starting from checkpoint "${args.checkpointRef}"; skipping agentbox.yaml setup`);
|
|
2808
|
+
return { action: "proceed" };
|
|
2809
|
+
}
|
|
2810
|
+
if (!rebuildBase && !recreateChosen && status.state === "missing") {
|
|
2241
2811
|
discardCheckpoint = discardOnMissing(status, fromDefault) ?? false;
|
|
2242
2812
|
}
|
|
2243
2813
|
let envFilesToImport;
|
|
@@ -2274,15 +2844,17 @@ async function maybeRunSetupWizard(args) {
|
|
|
2274
2844
|
action: "switch-to-claude",
|
|
2275
2845
|
envFilesToImport,
|
|
2276
2846
|
discardCheckpoint: discardCheckpoint || void 0,
|
|
2277
|
-
rebuildBase: rebuildBase || void 0
|
|
2847
|
+
rebuildBase: rebuildBase || void 0,
|
|
2848
|
+
recreate: recreateChosen || void 0
|
|
2278
2849
|
};
|
|
2279
2850
|
}
|
|
2280
2851
|
return {
|
|
2281
2852
|
action: "launch-with-prompt",
|
|
2282
|
-
initialPrompt: buildSetupInitialPrompt(proj.root),
|
|
2853
|
+
initialPrompt: buildSetupInitialPrompt(proj.root, proj.hasAgentboxYaml),
|
|
2283
2854
|
envFilesToImport,
|
|
2284
2855
|
discardCheckpoint: discardCheckpoint || void 0,
|
|
2285
|
-
rebuildBase: rebuildBase || void 0
|
|
2856
|
+
rebuildBase: rebuildBase || void 0,
|
|
2857
|
+
recreate: recreateChosen || void 0
|
|
2286
2858
|
};
|
|
2287
2859
|
}
|
|
2288
2860
|
function rebuildMinutesFor(provider) {
|
|
@@ -2323,7 +2895,7 @@ function nonInteractiveOutcome(args, proj, status, envFilesToImport) {
|
|
|
2323
2895
|
if (args.command === "claude") {
|
|
2324
2896
|
return {
|
|
2325
2897
|
action: "launch-with-prompt",
|
|
2326
|
-
initialPrompt: buildSetupInitialPrompt(proj.root),
|
|
2898
|
+
initialPrompt: buildSetupInitialPrompt(proj.root, proj.hasAgentboxYaml),
|
|
2327
2899
|
envFilesToImport,
|
|
2328
2900
|
discardCheckpoint
|
|
2329
2901
|
};
|
|
@@ -2419,7 +2991,7 @@ async function renderDocker(status) {
|
|
|
2419
2991
|
}
|
|
2420
2992
|
async function daytonaStatus() {
|
|
2421
2993
|
try {
|
|
2422
|
-
const mod = await import("./dist-
|
|
2994
|
+
const mod = await import("./dist-FIFEFKJ7.js");
|
|
2423
2995
|
return await mod.getDaytonaStatus();
|
|
2424
2996
|
} catch (err) {
|
|
2425
2997
|
return {
|
|
@@ -2430,7 +3002,7 @@ async function daytonaStatus() {
|
|
|
2430
3002
|
}
|
|
2431
3003
|
async function e2bStatus() {
|
|
2432
3004
|
try {
|
|
2433
|
-
const mod = await import("./dist-
|
|
3005
|
+
const mod = await import("./dist-JZ3XO6EB.js");
|
|
2434
3006
|
const cred = mod.readE2bCredStatus();
|
|
2435
3007
|
if (cred.auth === "none") {
|
|
2436
3008
|
return { configured: false, reason: "not configured \u2014 run `agentbox e2b login`" };
|
|
@@ -2944,7 +3516,8 @@ var claudeCommand = new Command3("claude").description("Create a sandboxed box a
|
|
|
2944
3516
|
agentArgs: claudeArgs,
|
|
2945
3517
|
createOpts: { ...pickCreateOpts(opts), carry: carryForQueue },
|
|
2946
3518
|
maxRunningOverride,
|
|
2947
|
-
maxWorkingOverride
|
|
3519
|
+
maxWorkingOverride,
|
|
3520
|
+
openTerminal: captureOpenTerminalContext(cfg.effective.queue.openIn)
|
|
2948
3521
|
});
|
|
2949
3522
|
outro(
|
|
2950
3523
|
`job ${result.job.id} queued (${String(result.runningCount)}/${String(result.maxConcurrent)} running); log: ${result.job.logPath}`
|
|
@@ -3572,13 +4145,13 @@ var CLOUD_BACKENDS = ["daytona", "hetzner", "vercel", "e2b"];
|
|
|
3572
4145
|
async function cloudProviderFor(backend) {
|
|
3573
4146
|
switch (backend) {
|
|
3574
4147
|
case "daytona":
|
|
3575
|
-
return (await import("./dist-
|
|
4148
|
+
return (await import("./dist-FIFEFKJ7.js")).daytonaProvider;
|
|
3576
4149
|
case "hetzner":
|
|
3577
|
-
return (await import("./dist-
|
|
4150
|
+
return (await import("./dist-AGTIA7AD.js")).hetznerProvider;
|
|
3578
4151
|
case "vercel":
|
|
3579
|
-
return (await import("./dist-
|
|
4152
|
+
return (await import("./dist-S4XR4ACV.js")).vercelProvider;
|
|
3580
4153
|
case "e2b":
|
|
3581
|
-
return (await import("./dist-
|
|
4154
|
+
return (await import("./dist-JZ3XO6EB.js")).e2bProvider;
|
|
3582
4155
|
}
|
|
3583
4156
|
}
|
|
3584
4157
|
var CHECKPOINT_NOTICE = "Checkpoint in progress \u2014 the box will be unresponsive for a moment";
|
|
@@ -3948,9 +4521,9 @@ import { Command as Command5, InvalidArgumentError } from "commander";
|
|
|
3948
4521
|
// src/ssh-config.ts
|
|
3949
4522
|
import { promises as fs } from "fs";
|
|
3950
4523
|
import { homedir as homedir9 } from "os";
|
|
3951
|
-
import { join as
|
|
4524
|
+
import { join as join12 } from "path";
|
|
3952
4525
|
function sshConfigPath() {
|
|
3953
|
-
return
|
|
4526
|
+
return join12(homedir9(), ".ssh", "config");
|
|
3954
4527
|
}
|
|
3955
4528
|
function beginMarker(alias) {
|
|
3956
4529
|
return `# BEGIN agentbox cloud box ${alias}`;
|
|
@@ -4001,7 +4574,7 @@ function buildBlock(opts) {
|
|
|
4001
4574
|
}
|
|
4002
4575
|
async function writeAgentboxSshAlias(opts) {
|
|
4003
4576
|
const path = sshConfigPath();
|
|
4004
|
-
await fs.mkdir(
|
|
4577
|
+
await fs.mkdir(join12(homedir9(), ".ssh"), { recursive: true, mode: 448 });
|
|
4005
4578
|
const existing = await readConfig();
|
|
4006
4579
|
const stripped = stripBlock(existing, opts.alias);
|
|
4007
4580
|
const separator = stripped.length === 0 || stripped.endsWith("\n") ? "" : "\n";
|
|
@@ -4231,10 +4804,10 @@ async function launchOne(flavor, folderUri) {
|
|
|
4231
4804
|
return { code: fallback, flavor, via: "open" };
|
|
4232
4805
|
}
|
|
4233
4806
|
function spawnCommand(cmd, args) {
|
|
4234
|
-
return new Promise((
|
|
4807
|
+
return new Promise((resolve5) => {
|
|
4235
4808
|
const child = spawn(cmd, args, { stdio: "ignore" });
|
|
4236
|
-
child.once("error", () =>
|
|
4237
|
-
child.once("exit", (code) =>
|
|
4809
|
+
child.once("error", () => resolve5(127));
|
|
4810
|
+
child.once("exit", (code) => resolve5(code ?? -1));
|
|
4238
4811
|
});
|
|
4239
4812
|
}
|
|
4240
4813
|
async function fetchServiceNamesDocker(container) {
|
|
@@ -4253,7 +4826,7 @@ async function fetchServiceNamesDocker(container) {
|
|
|
4253
4826
|
// src/commands/codex.ts
|
|
4254
4827
|
import { access } from "fs/promises";
|
|
4255
4828
|
import { homedir as homedir10 } from "os";
|
|
4256
|
-
import { join as
|
|
4829
|
+
import { join as join13 } from "path";
|
|
4257
4830
|
import { confirm as confirm5, intro as intro3, isCancel as isCancel6, log as log13, outro as outro2, spinner as spinner5 } from "@clack/prompts";
|
|
4258
4831
|
import { Command as Command6 } from "commander";
|
|
4259
4832
|
function reattachRef2(r) {
|
|
@@ -4350,7 +4923,7 @@ async function maybeRunCodexLogin(args) {
|
|
|
4350
4923
|
}
|
|
4351
4924
|
async function cloudCodexCredAvailable(env = process.env) {
|
|
4352
4925
|
if ((env["OPENAI_API_KEY"] ?? "").length > 0) return true;
|
|
4353
|
-
for (const p of [CODEX_CREDENTIALS_BACKUP_FILE,
|
|
4926
|
+
for (const p of [CODEX_CREDENTIALS_BACKUP_FILE, join13(homedir10(), ".codex", "auth.json")]) {
|
|
4354
4927
|
try {
|
|
4355
4928
|
await access(p);
|
|
4356
4929
|
return true;
|
|
@@ -4518,7 +5091,8 @@ var codexCommand = new Command6("codex").description("Create a sandboxed box and
|
|
|
4518
5091
|
agentArgs: codexArgs,
|
|
4519
5092
|
createOpts: { ...pickCodexCreateOpts(opts), carry: carryForQueue },
|
|
4520
5093
|
maxRunningOverride,
|
|
4521
|
-
maxWorkingOverride
|
|
5094
|
+
maxWorkingOverride,
|
|
5095
|
+
openTerminal: captureOpenTerminalContext(cfg.effective.queue.openIn)
|
|
4522
5096
|
});
|
|
4523
5097
|
outro2(
|
|
4524
5098
|
`job ${result.job.id} queued (${String(result.runningCount)}/${String(result.maxConcurrent)} running); log: ${result.job.logPath}`
|
|
@@ -5008,7 +5582,7 @@ codexCommand.addCommand(codexLoginCommand);
|
|
|
5008
5582
|
// src/commands/opencode.ts
|
|
5009
5583
|
import { access as access2 } from "fs/promises";
|
|
5010
5584
|
import { homedir as homedir11 } from "os";
|
|
5011
|
-
import { join as
|
|
5585
|
+
import { join as join14 } from "path";
|
|
5012
5586
|
import { confirm as confirm6, intro as intro4, isCancel as isCancel7, log as log14, outro as outro3, spinner as spinner6 } from "@clack/prompts";
|
|
5013
5587
|
import { Command as Command7 } from "commander";
|
|
5014
5588
|
function reattachRef3(r) {
|
|
@@ -5107,7 +5681,7 @@ async function cloudOpencodeCredAvailable(env = process.env) {
|
|
|
5107
5681
|
for (const k of OPENCODE_FORWARDED_ENV_KEYS) {
|
|
5108
5682
|
if ((env[k] ?? "").length > 0) return true;
|
|
5109
5683
|
}
|
|
5110
|
-
for (const p of [OPENCODE_CREDENTIALS_BACKUP_FILE,
|
|
5684
|
+
for (const p of [OPENCODE_CREDENTIALS_BACKUP_FILE, join14(homedir11(), ".local", "share", "opencode", "auth.json")]) {
|
|
5111
5685
|
try {
|
|
5112
5686
|
await access2(p);
|
|
5113
5687
|
return true;
|
|
@@ -5257,7 +5831,8 @@ var opencodeCommand = new Command7("opencode").description("Create a sandboxed b
|
|
|
5257
5831
|
agentArgs: opencodeArgs,
|
|
5258
5832
|
createOpts: { ...pickOpencodeCreateOpts(opts), carry: carryForQueue },
|
|
5259
5833
|
maxRunningOverride,
|
|
5260
|
-
maxWorkingOverride
|
|
5834
|
+
maxWorkingOverride,
|
|
5835
|
+
openTerminal: captureOpenTerminalContext(cfg.effective.queue.openIn)
|
|
5261
5836
|
});
|
|
5262
5837
|
outro3(
|
|
5263
5838
|
`job ${result.job.id} queued (${String(result.runningCount)}/${String(result.maxConcurrent)} running); log: ${result.job.logPath}`
|
|
@@ -5756,11 +6331,19 @@ var setCommand = new Command8("set").description("Set a config key in the global
|
|
|
5756
6331
|
} else {
|
|
5757
6332
|
process.stdout.write(`${key} = ${fmtValue(r.coerced)} (wrote ${r.path})
|
|
5758
6333
|
`);
|
|
6334
|
+
warnOnSet(key, r.coerced);
|
|
5759
6335
|
}
|
|
5760
6336
|
} catch (err) {
|
|
5761
6337
|
handleError(err);
|
|
5762
6338
|
}
|
|
5763
6339
|
});
|
|
6340
|
+
function warnOnSet(key, value) {
|
|
6341
|
+
if (key === "queue.openIn" && value !== "none") {
|
|
6342
|
+
process.stderr.write(
|
|
6343
|
+
"note: queue.openIn only opens a terminal when you submit the `-i` job from inside tmux, cmux, or iTerm2.\n cmux: the box is opened by the relay's queue worker, a cmux-external process, so cmux's default\n `socketControlMode: cmuxOnly` blocks it. Set `socketControlMode` to `automation` (or `password`)\n in ~/.config/cmux/cmux.json and run `cmux reload-config` for cmux opens to work.\n"
|
|
6344
|
+
);
|
|
6345
|
+
}
|
|
6346
|
+
}
|
|
5764
6347
|
var unsetCommand = new Command8("unset").description("Remove a config key from the global or per-project file (default: --project)").argument("<key>", "dot-path key (e.g. box.hostSnapshot)").option("--global", "edit ~/.agentbox/config.yaml").option("--project", "edit ~/.agentbox/projects/<hash>/config.yaml (default)").action(async (key, opts) => {
|
|
5765
6348
|
const scope = resolveWriteScope(opts);
|
|
5766
6349
|
try {
|
|
@@ -5898,8 +6481,51 @@ function handleError(err) {
|
|
|
5898
6481
|
var configCommand = new Command8("config").description("Read / write layered config (global, per-project, workspace `defaults:` block)").addCommand(getCommand).addCommand(setCommand).addCommand(unsetCommand).addCommand(listCommand).addCommand(pathCommand).addCommand(editCommand).addCommand(listProjectsCommand);
|
|
5899
6482
|
|
|
5900
6483
|
// src/commands/cp.ts
|
|
6484
|
+
import { resolve as resolve3 } from "path";
|
|
5901
6485
|
import { log as log15 } from "@clack/prompts";
|
|
5902
6486
|
import { Command as Command9 } from "commander";
|
|
6487
|
+
function collectExclude(val, acc) {
|
|
6488
|
+
acc.push(val);
|
|
6489
|
+
return acc;
|
|
6490
|
+
}
|
|
6491
|
+
async function guardUploadSize(hostSrc, boxDst, opts) {
|
|
6492
|
+
const tokens = effectiveExcludes(opts.exclude, opts.defaultExcludes);
|
|
6493
|
+
const tarPatterns = toTarExcludes(tokens);
|
|
6494
|
+
const cfg = await loadEffectiveConfig(process.cwd());
|
|
6495
|
+
const maxBytes = cfg.effective.box.cpMaxBytes;
|
|
6496
|
+
const absSrc = resolve3(hostSrc);
|
|
6497
|
+
const measured = await measureCopy(absSrc, tokens);
|
|
6498
|
+
if (opts.yes || measured.totalBytes <= maxBytes) {
|
|
6499
|
+
return tarPatterns;
|
|
6500
|
+
}
|
|
6501
|
+
const dropped = measured.isDir && opts.defaultExcludes ? ` after default excludes (${effectiveExcludes([], true).join(", ")})` : "";
|
|
6502
|
+
const lines = [
|
|
6503
|
+
`${hostSrc} is ${fmtBytes(measured.totalBytes)}${dropped}, over the ${fmtBytes(maxBytes)} per-copy limit.`
|
|
6504
|
+
];
|
|
6505
|
+
if (measured.isDir) {
|
|
6506
|
+
lines.push(
|
|
6507
|
+
"Biggest remaining folders/subfolders:",
|
|
6508
|
+
...measured.treeLines,
|
|
6509
|
+
"Each copy must be under the limit. To proceed, EITHER:",
|
|
6510
|
+
" - copy the heavy folders one at a time, e.g.:"
|
|
6511
|
+
);
|
|
6512
|
+
for (const child of measured.topChildren.slice(0, 3)) {
|
|
6513
|
+
lines.push(` agentbox cp ${hostSrc}/${child.path} ${boxDst}`);
|
|
6514
|
+
}
|
|
6515
|
+
lines.push(
|
|
6516
|
+
" - or drop what you do not need:",
|
|
6517
|
+
` agentbox cp ${hostSrc} ${boxDst} --exclude=<dir>`,
|
|
6518
|
+
" - or copy the whole thing anyway:",
|
|
6519
|
+
` agentbox cp ${hostSrc} ${boxDst} --yes`
|
|
6520
|
+
);
|
|
6521
|
+
} else {
|
|
6522
|
+
lines.push(
|
|
6523
|
+
"A single file cannot be split or trimmed. To copy it anyway:",
|
|
6524
|
+
` agentbox cp ${hostSrc} ${boxDst} --yes`
|
|
6525
|
+
);
|
|
6526
|
+
}
|
|
6527
|
+
throw new Error(lines.join("\n"));
|
|
6528
|
+
}
|
|
5903
6529
|
function parseBoxArg(arg) {
|
|
5904
6530
|
const idx = arg.indexOf(":");
|
|
5905
6531
|
if (idx === -1) return null;
|
|
@@ -5944,7 +6570,15 @@ function parseArgs(src, dst) {
|
|
|
5944
6570
|
var cpCommand = new Command9("cp").description("Copy files between host and box (like `docker cp`; direction picked by `name:` prefix)").argument("<src>", "`box:/path` (download) or host path (upload)").argument(
|
|
5945
6571
|
"[dst]",
|
|
5946
6572
|
"`box:/path` (upload) or host path (download); defaults to cwd when downloading"
|
|
5947
|
-
).
|
|
6573
|
+
).option(
|
|
6574
|
+
"--exclude <pattern>",
|
|
6575
|
+
'exclude paths matching <pattern> (repeatable; tar glob like "*/foo" or a bare dir name)',
|
|
6576
|
+
collectExclude,
|
|
6577
|
+
[]
|
|
6578
|
+
).option(
|
|
6579
|
+
"--no-default-excludes",
|
|
6580
|
+
`keep the heavy dirs cp drops by default (${effectiveExcludes([], true).join(", ")})`
|
|
6581
|
+
).option("-y, --yes", "copy even if the source is over the box.cpMaxBytes size limit").addHelpText(
|
|
5948
6582
|
"after",
|
|
5949
6583
|
[
|
|
5950
6584
|
"",
|
|
@@ -5952,27 +6586,30 @@ var cpCommand = new Command9("cp").description("Copy files between host and box
|
|
|
5952
6586
|
" agentbox cp mybox:/etc/foo ./foo # download (host path optional)",
|
|
5953
6587
|
" agentbox cp mybox:/workspace/.env # download into cwd",
|
|
5954
6588
|
" agentbox cp ./local.txt mybox:/workspace/ # upload (host path required)",
|
|
5955
|
-
" agentbox cp ./dir mybox:/workspace/ # upload directory (recursive)"
|
|
6589
|
+
" agentbox cp ./dir mybox:/workspace/ # upload directory (recursive)",
|
|
6590
|
+
' agentbox cp ./dir mybox:/workspace/ --exclude=.git --exclude="*/cache"'
|
|
5956
6591
|
].join("\n")
|
|
5957
|
-
).action(async (src, dst) => {
|
|
6592
|
+
).action(async (src, dst, opts) => {
|
|
5958
6593
|
try {
|
|
5959
6594
|
const parsed = parseArgs(src, dst);
|
|
5960
6595
|
const box = await resolveBoxOrExit(parsed.boxRef);
|
|
5961
6596
|
const isCloud = (box.provider ?? "docker") !== "docker";
|
|
6597
|
+
const tarPatterns = parsed.direction === "upload" ? await guardUploadSize(parsed.hostPath, `${parsed.boxRef}:${parsed.boxPath}`, opts) : toTarExcludes(effectiveExcludes(opts.exclude, opts.defaultExcludes));
|
|
5962
6598
|
if (isCloud) {
|
|
5963
6599
|
const provider = await providerForBox(box);
|
|
5964
6600
|
if (!provider.uploadPath || !provider.downloadPath) {
|
|
5965
6601
|
throw new Error(`provider '${provider.name}' does not support cp`);
|
|
5966
6602
|
}
|
|
5967
6603
|
if (parsed.direction === "upload") {
|
|
5968
|
-
const result = await provider.uploadPath(box, parsed.hostPath, parsed.boxPath);
|
|
6604
|
+
const result = await provider.uploadPath(box, parsed.hostPath, parsed.boxPath, tarPatterns);
|
|
5969
6605
|
process.stdout.write(`copied to ${box.name}:${result.finalPath}
|
|
5970
6606
|
`);
|
|
5971
6607
|
} else {
|
|
5972
6608
|
const result = await provider.downloadPath(
|
|
5973
6609
|
box,
|
|
5974
6610
|
parsed.boxPath,
|
|
5975
|
-
parsed.hostPath ?? process.cwd()
|
|
6611
|
+
parsed.hostPath ?? process.cwd(),
|
|
6612
|
+
tarPatterns
|
|
5976
6613
|
);
|
|
5977
6614
|
process.stdout.write(`copied to ${result.finalPath}
|
|
5978
6615
|
`);
|
|
@@ -5990,7 +6627,7 @@ var cpCommand = new Command9("cp").description("Copy files between host and box
|
|
|
5990
6627
|
throw new Error(`box ${box.name} has no container; was it destroyed?`);
|
|
5991
6628
|
}
|
|
5992
6629
|
if (parsed.direction === "upload") {
|
|
5993
|
-
const result = await uploadToBox(box, parsed.hostPath, parsed.boxPath);
|
|
6630
|
+
const result = await uploadToBox(box, parsed.hostPath, parsed.boxPath, tarPatterns);
|
|
5994
6631
|
if (result.warn) {
|
|
5995
6632
|
log15.warn(`copied to ${box.name}:${result.finalPath}, but ${result.warn}`);
|
|
5996
6633
|
} else {
|
|
@@ -5998,7 +6635,12 @@ var cpCommand = new Command9("cp").description("Copy files between host and box
|
|
|
5998
6635
|
`);
|
|
5999
6636
|
}
|
|
6000
6637
|
} else {
|
|
6001
|
-
const result = await downloadFromBox(
|
|
6638
|
+
const result = await downloadFromBox(
|
|
6639
|
+
box,
|
|
6640
|
+
parsed.boxPath,
|
|
6641
|
+
parsed.hostPath ?? process.cwd(),
|
|
6642
|
+
tarPatterns
|
|
6643
|
+
);
|
|
6002
6644
|
process.stdout.write(`copied to ${result.finalPath}
|
|
6003
6645
|
`);
|
|
6004
6646
|
}
|
|
@@ -6189,12 +6831,14 @@ var createCommand = new Command10("create").description(
|
|
|
6189
6831
|
const effectiveCheckpointRef = wiz.discardCheckpoint ? void 0 : checkpointRef;
|
|
6190
6832
|
if (wiz.action === "switch-to-claude" && isDocker) {
|
|
6191
6833
|
process.env[WIZARD_AUTOLAUNCH_ENV] = "1";
|
|
6834
|
+
if (wiz.recreate) process.env[WIZARD_RECREATE_ENV] = "1";
|
|
6192
6835
|
const serialized = serializeEnvFilesForEnv(wiz.envFilesToImport);
|
|
6193
6836
|
if (serialized !== void 0) process.env[WIZARD_ENV_FILES_ENV] = serialized;
|
|
6194
6837
|
try {
|
|
6195
6838
|
await claudeCommand.parseAsync(passthroughFlags(opts), { from: "user" });
|
|
6196
6839
|
} finally {
|
|
6197
6840
|
delete process.env[WIZARD_AUTOLAUNCH_ENV];
|
|
6841
|
+
delete process.env[WIZARD_RECREATE_ENV];
|
|
6198
6842
|
delete process.env[WIZARD_ENV_FILES_ENV];
|
|
6199
6843
|
}
|
|
6200
6844
|
return;
|
|
@@ -6320,7 +6964,7 @@ var createCommand = new Command10("create").description(
|
|
|
6320
6964
|
}
|
|
6321
6965
|
outro4("done");
|
|
6322
6966
|
if (attachClaudeAfter) {
|
|
6323
|
-
const { cloudAgentAttach: cloudAgentAttach2 } = await import("./_cloud-attach-
|
|
6967
|
+
const { cloudAgentAttach: cloudAgentAttach2 } = await import("./_cloud-attach-R6TRWG5L.js");
|
|
6324
6968
|
await cloudAgentAttach2({
|
|
6325
6969
|
box: result.record,
|
|
6326
6970
|
binary: "claude",
|
|
@@ -6953,8 +7597,8 @@ var Compositor = class {
|
|
|
6953
7597
|
this.drawChrome();
|
|
6954
7598
|
this.scheduleRender();
|
|
6955
7599
|
this.pollTimer = setInterval(() => void this.poll(), POLL_MS);
|
|
6956
|
-
await new Promise((
|
|
6957
|
-
this.resolveDone =
|
|
7600
|
+
await new Promise((resolve5) => {
|
|
7601
|
+
this.resolveDone = resolve5;
|
|
6958
7602
|
});
|
|
6959
7603
|
}
|
|
6960
7604
|
async refreshBoxes() {
|
|
@@ -9332,114 +9976,6 @@ function resolveToken(raw) {
|
|
|
9332
9976
|
return `<${raw}>`;
|
|
9333
9977
|
}
|
|
9334
9978
|
|
|
9335
|
-
// src/lib/drive/tmux.ts
|
|
9336
|
-
var TMUX_USER = "vscode";
|
|
9337
|
-
async function captureSession(provider, box, session, opts = {}) {
|
|
9338
|
-
const argv2 = ["tmux", "capture-pane", opts.ansi ? "-pe" : "-p", "-t", session];
|
|
9339
|
-
if (opts.rows) {
|
|
9340
|
-
argv2.push("-S", String(opts.rows.from), "-E", String(opts.rows.to));
|
|
9341
|
-
}
|
|
9342
|
-
const res = await provider.exec(box, argv2, { user: TMUX_USER });
|
|
9343
|
-
if (res.exitCode !== 0) {
|
|
9344
|
-
throw new Error(failure("capture-pane", session, res.stderr || res.stdout));
|
|
9345
|
-
}
|
|
9346
|
-
return res.stdout.replace(/\n$/, "");
|
|
9347
|
-
}
|
|
9348
|
-
async function paneInfo(provider, box, session) {
|
|
9349
|
-
const fmt = "#{pane_width},#{pane_height},#{cursor_x},#{cursor_y}";
|
|
9350
|
-
const res = await provider.exec(box, ["tmux", "display-message", "-p", "-t", session, fmt], {
|
|
9351
|
-
user: TMUX_USER
|
|
9352
|
-
});
|
|
9353
|
-
if (res.exitCode !== 0) {
|
|
9354
|
-
throw new Error(failure("display-message", session, res.stderr || res.stdout));
|
|
9355
|
-
}
|
|
9356
|
-
const m = /^(\d+),(\d+),(\d+),(\d+)/.exec(res.stdout.trim());
|
|
9357
|
-
if (!m) throw new Error(`tmux display-message returned unexpected output: ${res.stdout}`);
|
|
9358
|
-
return {
|
|
9359
|
-
cols: Number(m[1]),
|
|
9360
|
-
rows: Number(m[2]),
|
|
9361
|
-
cursor: { x: Number(m[3]), y: Number(m[4]) }
|
|
9362
|
-
};
|
|
9363
|
-
}
|
|
9364
|
-
async function sendLiteral(provider, box, session, literal) {
|
|
9365
|
-
if (literal.length === 0) return;
|
|
9366
|
-
const res = await provider.exec(box, ["tmux", "send-keys", "-t", session, "-l", "--", literal], {
|
|
9367
|
-
user: TMUX_USER
|
|
9368
|
-
});
|
|
9369
|
-
if (res.exitCode !== 0) {
|
|
9370
|
-
throw new Error(failure("send-keys -l", session, res.stderr || res.stdout));
|
|
9371
|
-
}
|
|
9372
|
-
}
|
|
9373
|
-
async function sendKey(provider, box, session, key) {
|
|
9374
|
-
const res = await provider.exec(box, ["tmux", "send-keys", "-t", session, key], {
|
|
9375
|
-
user: TMUX_USER
|
|
9376
|
-
});
|
|
9377
|
-
if (res.exitCode !== 0) {
|
|
9378
|
-
throw new Error(failure("send-keys", session, res.stderr || res.stdout));
|
|
9379
|
-
}
|
|
9380
|
-
}
|
|
9381
|
-
async function resizeWindow(provider, box, session, cols, rows) {
|
|
9382
|
-
const res = await provider.exec(
|
|
9383
|
-
box,
|
|
9384
|
-
["tmux", "resize-window", "-t", session, "-x", String(cols), "-y", String(rows)],
|
|
9385
|
-
{ user: TMUX_USER }
|
|
9386
|
-
);
|
|
9387
|
-
if (res.exitCode !== 0) {
|
|
9388
|
-
throw new Error(failure("resize-window", session, res.stderr || res.stdout));
|
|
9389
|
-
}
|
|
9390
|
-
}
|
|
9391
|
-
async function listSessions(provider, box) {
|
|
9392
|
-
const res = await provider.exec(
|
|
9393
|
-
box,
|
|
9394
|
-
["tmux", "list-sessions", "-F", "#{session_name}"],
|
|
9395
|
-
{ user: TMUX_USER }
|
|
9396
|
-
);
|
|
9397
|
-
if (res.exitCode !== 0) return [];
|
|
9398
|
-
return res.stdout.split("\n").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
9399
|
-
}
|
|
9400
|
-
function failure(op, session, detail) {
|
|
9401
|
-
const tail = detail.trim();
|
|
9402
|
-
return `tmux ${op} failed for session '${session}'${tail ? `: ${tail}` : ""}`;
|
|
9403
|
-
}
|
|
9404
|
-
|
|
9405
|
-
// src/lib/drive/session.ts
|
|
9406
|
-
var AGENT_SESSION_PRIORITY = ["claude", "codex", "opencode"];
|
|
9407
|
-
async function resolveDriveSession(provider, box, explicit) {
|
|
9408
|
-
if (await provider.probeState(box) === "paused") {
|
|
9409
|
-
process.stderr.write(`drive: box ${box.name} is paused; unpausing
|
|
9410
|
-
`);
|
|
9411
|
-
await provider.resume(box);
|
|
9412
|
-
}
|
|
9413
|
-
const sessions = await listSessions(provider, box);
|
|
9414
|
-
if (explicit !== void 0 && explicit !== "") {
|
|
9415
|
-
if (!sessions.includes(explicit)) {
|
|
9416
|
-
throw new SessionNotFoundError(explicit, sessions);
|
|
9417
|
-
}
|
|
9418
|
-
return { name: explicit, available: sessions };
|
|
9419
|
-
}
|
|
9420
|
-
for (const candidate of AGENT_SESSION_PRIORITY) {
|
|
9421
|
-
if (sessions.includes(candidate)) {
|
|
9422
|
-
return { name: candidate, available: sessions };
|
|
9423
|
-
}
|
|
9424
|
-
}
|
|
9425
|
-
if (sessions.length === 1 && sessions[0]) {
|
|
9426
|
-
return { name: sessions[0], available: sessions };
|
|
9427
|
-
}
|
|
9428
|
-
throw new SessionNotFoundError(void 0, sessions);
|
|
9429
|
-
}
|
|
9430
|
-
var SessionNotFoundError = class extends Error {
|
|
9431
|
-
wanted;
|
|
9432
|
-
available;
|
|
9433
|
-
constructor(wanted, available) {
|
|
9434
|
-
const head = wanted ? `no tmux session '${wanted}' in this box` : "no agent tmux session running in this box";
|
|
9435
|
-
const tail = available.length ? ` (running: ${available.join(", ")})` : " (tmux server not running or no sessions)";
|
|
9436
|
-
super(head + tail);
|
|
9437
|
-
this.name = "SessionNotFoundError";
|
|
9438
|
-
this.wanted = wanted;
|
|
9439
|
-
this.available = available;
|
|
9440
|
-
}
|
|
9441
|
-
};
|
|
9442
|
-
|
|
9443
9979
|
// src/commands/drive.ts
|
|
9444
9980
|
var PROMPT_ENTER_DELAY_MS = 200;
|
|
9445
9981
|
var POLL_INTERVAL_MS2 = 250;
|
|
@@ -9507,7 +10043,7 @@ var drivePromptCommand = new Command24("prompt").description('Type text into the
|
|
|
9507
10043
|
const session = await resolveDriveSession(provider, box, opts.session);
|
|
9508
10044
|
const delay = opts.delay !== void 0 ? parsePositiveInt2(opts.delay, "--delay") : PROMPT_ENTER_DELAY_MS;
|
|
9509
10045
|
await sendLiteral(provider, box, session.name, text);
|
|
9510
|
-
if (delay > 0) await
|
|
10046
|
+
if (delay > 0) await sleep3(delay);
|
|
9511
10047
|
await sendKey(provider, box, session.name, "Enter");
|
|
9512
10048
|
} catch (err) {
|
|
9513
10049
|
handleDriveError(err);
|
|
@@ -9533,7 +10069,7 @@ var driveWaitCommand = new Command24("wait").description("Block until --text app
|
|
|
9533
10069
|
}
|
|
9534
10070
|
return;
|
|
9535
10071
|
}
|
|
9536
|
-
await
|
|
10072
|
+
await sleep3(POLL_INTERVAL_MS2);
|
|
9537
10073
|
}
|
|
9538
10074
|
const elapsedMs = Date.now() - start;
|
|
9539
10075
|
if (opts.json === true) {
|
|
@@ -9593,7 +10129,7 @@ function parsePositiveInt2(raw, label) {
|
|
|
9593
10129
|
}
|
|
9594
10130
|
return n;
|
|
9595
10131
|
}
|
|
9596
|
-
function
|
|
10132
|
+
function sleep3(ms) {
|
|
9597
10133
|
return new Promise((r) => setTimeout(r, ms));
|
|
9598
10134
|
}
|
|
9599
10135
|
|
|
@@ -9602,7 +10138,7 @@ import { log as log30 } from "@clack/prompts";
|
|
|
9602
10138
|
import { Command as Command25 } from "commander";
|
|
9603
10139
|
import { existsSync as existsSync5, readdirSync, statSync } from "fs";
|
|
9604
10140
|
import { homedir as homedir12 } from "os";
|
|
9605
|
-
import { join as
|
|
10141
|
+
import { join as join15 } from "path";
|
|
9606
10142
|
var FORK_AGENTS = ["claude", "codex", "opencode"];
|
|
9607
10143
|
var AGENT_COMMAND = {
|
|
9608
10144
|
claude: claudeCommand,
|
|
@@ -9622,12 +10158,12 @@ function resolveSessionArgs(agent, opts) {
|
|
|
9622
10158
|
}
|
|
9623
10159
|
if (opts.session) return ["--resume", opts.session];
|
|
9624
10160
|
if (agent === "codex") return ["--continue"];
|
|
9625
|
-
const dir =
|
|
10161
|
+
const dir = join15(homedir12(), ".claude", "projects", encodeClaudeProjectsDir(opts.workspace));
|
|
9626
10162
|
if (!existsSync5(dir)) return ["--continue"];
|
|
9627
10163
|
const now = Date.now();
|
|
9628
10164
|
const recent = readdirSync(dir).filter((f) => f.endsWith(".jsonl")).map((f) => {
|
|
9629
10165
|
try {
|
|
9630
|
-
return statSync(
|
|
10166
|
+
return statSync(join15(dir, f)).mtimeMs;
|
|
9631
10167
|
} catch {
|
|
9632
10168
|
return 0;
|
|
9633
10169
|
}
|
|
@@ -9738,13 +10274,13 @@ import {
|
|
|
9738
10274
|
writeFileSync as writeFileSync5
|
|
9739
10275
|
} from "fs";
|
|
9740
10276
|
import { homedir as homedir16 } from "os";
|
|
9741
|
-
import { dirname as dirname3, join as
|
|
10277
|
+
import { dirname as dirname3, join as join19, resolve as resolve4, sep } from "path";
|
|
9742
10278
|
import { fileURLToPath } from "url";
|
|
9743
10279
|
|
|
9744
10280
|
// src/lib/doctor-checks.ts
|
|
9745
10281
|
import { accessSync, constants as fsConstants, mkdirSync as mkdirSync3 } from "fs";
|
|
9746
10282
|
import { homedir as homedir13 } from "os";
|
|
9747
|
-
import { join as
|
|
10283
|
+
import { join as join16 } from "path";
|
|
9748
10284
|
import { execa as execa3 } from "execa";
|
|
9749
10285
|
var ALL_PROVIDERS = ["docker", "daytona", "hetzner", "vercel", "e2b"];
|
|
9750
10286
|
var NODE_MIN_MAJOR = 20;
|
|
@@ -9764,12 +10300,12 @@ function parseNodeMajorMinor(v) {
|
|
|
9764
10300
|
if (!m) return [0, 0];
|
|
9765
10301
|
return [Number(m[1]), Number(m[2])];
|
|
9766
10302
|
}
|
|
9767
|
-
function
|
|
10303
|
+
function firstLine2(s) {
|
|
9768
10304
|
const i = s.indexOf("\n");
|
|
9769
10305
|
return i === -1 ? s : s.slice(0, i);
|
|
9770
10306
|
}
|
|
9771
10307
|
function errSummary(err) {
|
|
9772
|
-
return err instanceof Error ?
|
|
10308
|
+
return err instanceof Error ? firstLine2(err.message) : String(err);
|
|
9773
10309
|
}
|
|
9774
10310
|
function checkNode() {
|
|
9775
10311
|
const v = process.versions.node;
|
|
@@ -9792,7 +10328,7 @@ function checkPlatform() {
|
|
|
9792
10328
|
};
|
|
9793
10329
|
}
|
|
9794
10330
|
function checkAgentboxHome() {
|
|
9795
|
-
const dir =
|
|
10331
|
+
const dir = join16(homedir13(), ".agentbox");
|
|
9796
10332
|
try {
|
|
9797
10333
|
mkdirSync3(dir, { recursive: true });
|
|
9798
10334
|
accessSync(dir, fsConstants.W_OK);
|
|
@@ -9864,7 +10400,7 @@ async function dockerChecks() {
|
|
|
9864
10400
|
];
|
|
9865
10401
|
}
|
|
9866
10402
|
const daemonRes = { label: "docker daemon", status: "ok", detail: "reachable" };
|
|
9867
|
-
const mod = await import("./dist-
|
|
10403
|
+
const mod = await import("./dist-OGJGZETZ.js");
|
|
9868
10404
|
let imgRes;
|
|
9869
10405
|
try {
|
|
9870
10406
|
const img = await mod.imageInfo(mod.DEFAULT_BOX_IMAGE);
|
|
@@ -9895,7 +10431,7 @@ async function dockerChecks() {
|
|
|
9895
10431
|
}
|
|
9896
10432
|
async function daytonaChecks() {
|
|
9897
10433
|
try {
|
|
9898
|
-
const mod = await import("./dist-
|
|
10434
|
+
const mod = await import("./dist-FIFEFKJ7.js");
|
|
9899
10435
|
const status = await mod.getDaytonaStatus();
|
|
9900
10436
|
if (!status.configured) {
|
|
9901
10437
|
return [
|
|
@@ -9931,7 +10467,7 @@ async function daytonaChecks() {
|
|
|
9931
10467
|
}
|
|
9932
10468
|
async function hetznerChecks() {
|
|
9933
10469
|
try {
|
|
9934
|
-
const mod = await import("./dist-
|
|
10470
|
+
const mod = await import("./dist-AGTIA7AD.js");
|
|
9935
10471
|
const cred = mod.readHetznerCredStatus();
|
|
9936
10472
|
const credRes = cred.source === "none" ? {
|
|
9937
10473
|
label: "credentials",
|
|
@@ -9963,7 +10499,7 @@ async function hetznerChecks() {
|
|
|
9963
10499
|
}
|
|
9964
10500
|
async function vercelChecks() {
|
|
9965
10501
|
try {
|
|
9966
|
-
const mod = await import("./dist-
|
|
10502
|
+
const mod = await import("./dist-S4XR4ACV.js");
|
|
9967
10503
|
const cred = mod.readVercelCredStatus();
|
|
9968
10504
|
const credRes = cred.auth === "none" ? {
|
|
9969
10505
|
label: "credentials",
|
|
@@ -9999,7 +10535,7 @@ async function vercelChecks() {
|
|
|
9999
10535
|
}
|
|
10000
10536
|
async function e2bChecks() {
|
|
10001
10537
|
try {
|
|
10002
|
-
const mod = await import("./dist-
|
|
10538
|
+
const mod = await import("./dist-JZ3XO6EB.js");
|
|
10003
10539
|
const cred = mod.readE2bCredStatus();
|
|
10004
10540
|
const credRes = cred.auth === "none" ? {
|
|
10005
10541
|
label: "credentials",
|
|
@@ -10130,10 +10666,10 @@ function formatDetailed(groups) {
|
|
|
10130
10666
|
// src/lib/first-run.ts
|
|
10131
10667
|
import { existsSync as existsSync6, mkdirSync as mkdirSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
10132
10668
|
import { homedir as homedir14 } from "os";
|
|
10133
|
-
import { dirname, join as
|
|
10669
|
+
import { dirname, join as join17 } from "path";
|
|
10134
10670
|
var MARKER_VERSION = 1;
|
|
10135
10671
|
function setupMarkerPath() {
|
|
10136
|
-
return
|
|
10672
|
+
return join17(homedir14(), ".agentbox", "setup-complete.json");
|
|
10137
10673
|
}
|
|
10138
10674
|
function isFirstRun() {
|
|
10139
10675
|
return !existsSync6(setupMarkerPath());
|
|
@@ -10154,12 +10690,12 @@ import { intro as intro6, log as log31, note as note2, outro as outro5 } from "@
|
|
|
10154
10690
|
import { Command as Command26 } from "commander";
|
|
10155
10691
|
import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync, renameSync as renameSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
10156
10692
|
import { homedir as homedir15 } from "os";
|
|
10157
|
-
import { dirname as dirname2, join as
|
|
10693
|
+
import { dirname as dirname2, join as join18 } from "path";
|
|
10158
10694
|
var CONTROL_ID = "agentbox";
|
|
10159
10695
|
function cmuxDockPath(env = process.env) {
|
|
10160
10696
|
const xdg = env["XDG_CONFIG_HOME"];
|
|
10161
|
-
const base = xdg && xdg.length > 0 ? xdg :
|
|
10162
|
-
return
|
|
10697
|
+
const base = xdg && xdg.length > 0 ? xdg : join18(homedir15(), ".config");
|
|
10698
|
+
return join18(base, "cmux", "dock.json");
|
|
10163
10699
|
}
|
|
10164
10700
|
function upsertAgentboxControl(doc, opts) {
|
|
10165
10701
|
const controls = Array.isArray(doc.controls) ? doc.controls : [];
|
|
@@ -10251,7 +10787,7 @@ var SYNC_END2 = "\x1B[?2026l";
|
|
|
10251
10787
|
var HIDE_CURSOR = "\x1B[?25l";
|
|
10252
10788
|
var SHOW_CURSOR = "\x1B[?25h";
|
|
10253
10789
|
var SPIN = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
10254
|
-
var
|
|
10790
|
+
var sleep4 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
10255
10791
|
function shineColor(dist) {
|
|
10256
10792
|
const d = Math.abs(dist);
|
|
10257
10793
|
if (d === 0) return 231;
|
|
@@ -10301,11 +10837,11 @@ ${HIDE_CURSOR}`);
|
|
|
10301
10837
|
statusLine2(spin) + "\x1B[3A\r" + // back up to the logo's first row
|
|
10302
10838
|
SYNC_END2;
|
|
10303
10839
|
process.stdout.write(frame);
|
|
10304
|
-
await
|
|
10840
|
+
await sleep4(frameMs);
|
|
10305
10841
|
}
|
|
10306
10842
|
process.stdout.write(SYNC_BEGIN2 + `\x1B[38;5;39m${LOGO_L1}
|
|
10307
10843
|
${LOGO_L2}\x1B[0m` + SYNC_END2);
|
|
10308
|
-
await
|
|
10844
|
+
await sleep4(250);
|
|
10309
10845
|
process.stdout.write(SYNC_BEGIN2 + "\n\x1B[2K\n\x1B[2K" + SHOW_CURSOR + SYNC_END2);
|
|
10310
10846
|
process.removeListener("exit", restoreCursor);
|
|
10311
10847
|
process.removeListener("SIGINT", onSigint);
|
|
@@ -10313,30 +10849,30 @@ ${LOGO_L2}\x1B[0m` + SYNC_END2);
|
|
|
10313
10849
|
var LEGACY_INFO_MARKER = "Drive AgentBox from the host:";
|
|
10314
10850
|
function installTargets() {
|
|
10315
10851
|
const home = homedir16();
|
|
10316
|
-
const claudeSkills =
|
|
10852
|
+
const claudeSkills = join19(home, ".claude", "skills");
|
|
10317
10853
|
return [
|
|
10318
|
-
{ src:
|
|
10854
|
+
{ src: join19("agentbox", "SKILL.md"), dest: join19(claudeSkills, "agentbox", "SKILL.md") },
|
|
10319
10855
|
{
|
|
10320
|
-
src:
|
|
10321
|
-
dest:
|
|
10856
|
+
src: join19("agentbox-info", "SKILL.md"),
|
|
10857
|
+
dest: join19(claudeSkills, "agentbox-info", "SKILL.md")
|
|
10322
10858
|
},
|
|
10323
10859
|
{
|
|
10324
|
-
src:
|
|
10325
|
-
dest:
|
|
10326
|
-
gateDir:
|
|
10860
|
+
src: join19("codex", "agentbox.md"),
|
|
10861
|
+
dest: join19(home, ".codex", "prompts", "agentbox.md"),
|
|
10862
|
+
gateDir: join19(home, ".codex")
|
|
10327
10863
|
},
|
|
10328
10864
|
{
|
|
10329
|
-
src:
|
|
10330
|
-
dest:
|
|
10331
|
-
gateDir:
|
|
10865
|
+
src: join19("opencode", "agentbox.md"),
|
|
10866
|
+
dest: join19(home, ".config", "opencode", "commands", "agentbox.md"),
|
|
10867
|
+
gateDir: join19(home, ".config", "opencode")
|
|
10332
10868
|
}
|
|
10333
10869
|
];
|
|
10334
10870
|
}
|
|
10335
10871
|
function resolveHostSkillsDir() {
|
|
10336
10872
|
const here = dirname3(fileURLToPath(import.meta.url));
|
|
10337
10873
|
const candidates = [
|
|
10338
|
-
|
|
10339
|
-
|
|
10874
|
+
resolve4(here, "..", "share", "host-skills"),
|
|
10875
|
+
resolve4(here, "..", "..", "share", "host-skills")
|
|
10340
10876
|
];
|
|
10341
10877
|
for (const c of candidates) {
|
|
10342
10878
|
if (existsSync8(c)) return c;
|
|
@@ -10375,7 +10911,7 @@ function installHostSkills(opts = {}) {
|
|
|
10375
10911
|
const blocked = [];
|
|
10376
10912
|
let skipped = 0;
|
|
10377
10913
|
for (const t of installTargets()) {
|
|
10378
|
-
const src =
|
|
10914
|
+
const src = join19(srcDir, t.src);
|
|
10379
10915
|
if (!existsSync8(src)) {
|
|
10380
10916
|
if (!quiet) log32.warn(`bundled file missing (skipped): ${src}`);
|
|
10381
10917
|
skipped++;
|
|
@@ -10397,7 +10933,7 @@ function installHostSkills(opts = {}) {
|
|
|
10397
10933
|
mkdirSync6(dirname3(t.dest), { recursive: true });
|
|
10398
10934
|
if (link) {
|
|
10399
10935
|
rmSync(t.dest, { force: true });
|
|
10400
|
-
symlinkSync2(
|
|
10936
|
+
symlinkSync2(resolve4(srcDir, t.src), t.dest);
|
|
10401
10937
|
} else {
|
|
10402
10938
|
if (isSymlink(t.dest)) rmSync(t.dest, { force: true });
|
|
10403
10939
|
writeFileSync5(t.dest, readFileSync2(src, "utf8"));
|
|
@@ -10430,7 +10966,7 @@ function ensureTty() {
|
|
|
10430
10966
|
async function runProviderLogin(name) {
|
|
10431
10967
|
if (name === "docker") return true;
|
|
10432
10968
|
if (name === "daytona") {
|
|
10433
|
-
const mod2 = await import("./dist-
|
|
10969
|
+
const mod2 = await import("./dist-FIFEFKJ7.js");
|
|
10434
10970
|
const status2 = await mod2.getDaytonaStatus();
|
|
10435
10971
|
if (status2.configured) {
|
|
10436
10972
|
log32.info("daytona: already configured");
|
|
@@ -10443,7 +10979,7 @@ async function runProviderLogin(name) {
|
|
|
10443
10979
|
return true;
|
|
10444
10980
|
}
|
|
10445
10981
|
if (name === "hetzner") {
|
|
10446
|
-
const mod2 = await import("./dist-
|
|
10982
|
+
const mod2 = await import("./dist-AGTIA7AD.js");
|
|
10447
10983
|
const status2 = mod2.readHetznerCredStatus();
|
|
10448
10984
|
if (status2.source !== "none") {
|
|
10449
10985
|
log32.info("hetzner: already configured");
|
|
@@ -10456,7 +10992,7 @@ async function runProviderLogin(name) {
|
|
|
10456
10992
|
return true;
|
|
10457
10993
|
}
|
|
10458
10994
|
if (name === "vercel") {
|
|
10459
|
-
const mod2 = await import("./dist-
|
|
10995
|
+
const mod2 = await import("./dist-S4XR4ACV.js");
|
|
10460
10996
|
const status2 = mod2.readVercelCredStatus();
|
|
10461
10997
|
if (status2.auth !== "none") {
|
|
10462
10998
|
log32.info(`vercel: already configured (${status2.auth})`);
|
|
@@ -10468,7 +11004,7 @@ async function runProviderLogin(name) {
|
|
|
10468
11004
|
await mod2.ensureVercelCredentials();
|
|
10469
11005
|
return true;
|
|
10470
11006
|
}
|
|
10471
|
-
const mod = await import("./dist-
|
|
11007
|
+
const mod = await import("./dist-JZ3XO6EB.js");
|
|
10472
11008
|
const status = mod.readE2bCredStatus();
|
|
10473
11009
|
if (status.auth !== "none") {
|
|
10474
11010
|
log32.info(`e2b: already configured (${status.auth})`);
|
|
@@ -10868,17 +11404,17 @@ async function applyLiveCloudStates(boxes) {
|
|
|
10868
11404
|
);
|
|
10869
11405
|
}
|
|
10870
11406
|
function withTimeout(p, ms) {
|
|
10871
|
-
return new Promise((
|
|
10872
|
-
const t = setTimeout(() =>
|
|
11407
|
+
return new Promise((resolve5) => {
|
|
11408
|
+
const t = setTimeout(() => resolve5(null), ms);
|
|
10873
11409
|
if (typeof t.unref === "function") t.unref();
|
|
10874
11410
|
p.then(
|
|
10875
11411
|
(v) => {
|
|
10876
11412
|
clearTimeout(t);
|
|
10877
|
-
|
|
11413
|
+
resolve5(v);
|
|
10878
11414
|
},
|
|
10879
11415
|
() => {
|
|
10880
11416
|
clearTimeout(t);
|
|
10881
|
-
|
|
11417
|
+
resolve5(null);
|
|
10882
11418
|
}
|
|
10883
11419
|
);
|
|
10884
11420
|
});
|
|
@@ -10932,7 +11468,7 @@ async function watchRender(produce, rawInterval, opts = {}) {
|
|
|
10932
11468
|
process.once("SIGINT", () => process.exit(0));
|
|
10933
11469
|
}
|
|
10934
11470
|
const hint = interactive ? "q or Ctrl-C to exit" : "Ctrl-C to exit";
|
|
10935
|
-
const
|
|
11471
|
+
const sleep6 = (d) => new Promise((r) => {
|
|
10936
11472
|
const t = setTimeout(() => {
|
|
10937
11473
|
wake = null;
|
|
10938
11474
|
r();
|
|
@@ -10963,7 +11499,7 @@ async function watchRender(produce, rawInterval, opts = {}) {
|
|
|
10963
11499
|
${trimmed}
|
|
10964
11500
|
`);
|
|
10965
11501
|
}
|
|
10966
|
-
await
|
|
11502
|
+
await sleep6(ms);
|
|
10967
11503
|
if (exiting) {
|
|
10968
11504
|
restore();
|
|
10969
11505
|
process.exit(0);
|
|
@@ -11338,7 +11874,7 @@ import { log as log35 } from "@clack/prompts";
|
|
|
11338
11874
|
import { execa as execa4 } from "execa";
|
|
11339
11875
|
import { existsSync as existsSync9, mkdirSync as mkdirSync7 } from "fs";
|
|
11340
11876
|
import { homedir as homedir17 } from "os";
|
|
11341
|
-
import { join as
|
|
11877
|
+
import { join as join20 } from "path";
|
|
11342
11878
|
import { Command as Command32 } from "commander";
|
|
11343
11879
|
|
|
11344
11880
|
// src/commands/path.ts
|
|
@@ -11400,7 +11936,7 @@ var openCommand = new Command32("open").description("Open a box's /workspace in
|
|
|
11400
11936
|
}
|
|
11401
11937
|
});
|
|
11402
11938
|
async function runCloudOpen(box, provider, opts) {
|
|
11403
|
-
const mountRoot =
|
|
11939
|
+
const mountRoot = join20(homedir17(), ".agentbox", "mounts", box.name);
|
|
11404
11940
|
if (opts.unmount) {
|
|
11405
11941
|
const ok = await tryUnmount(mountRoot);
|
|
11406
11942
|
if (ok) process.stdout.write(`unmounted ${mountRoot}
|
|
@@ -11674,7 +12210,7 @@ async function pruneCloud(provider, opts) {
|
|
|
11674
12210
|
}
|
|
11675
12211
|
|
|
11676
12212
|
// src/commands/queue.ts
|
|
11677
|
-
import { readFile as readFile5, stat as
|
|
12213
|
+
import { readFile as readFile5, stat as stat6 } from "fs/promises";
|
|
11678
12214
|
import { intro as intro8, log as log37, outro as outro7 } from "@clack/prompts";
|
|
11679
12215
|
import { Command as Command35 } from "commander";
|
|
11680
12216
|
var TERMINAL_STATUSES = /* @__PURE__ */ new Set(["done", "failed", "cancelled"]);
|
|
@@ -11721,7 +12257,7 @@ var queueShowCommand = new Command35("show").description("Dump a job manifest an
|
|
|
11721
12257
|
process.stdout.write(JSON.stringify(job, null, 2) + "\n");
|
|
11722
12258
|
const tailN = Number.parseInt(opts.tail, 10) || 50;
|
|
11723
12259
|
try {
|
|
11724
|
-
await
|
|
12260
|
+
await stat6(job.logPath);
|
|
11725
12261
|
const text = await readFile5(job.logPath, "utf8");
|
|
11726
12262
|
const lines = text.split(/\r?\n/);
|
|
11727
12263
|
const slice = lines.slice(Math.max(0, lines.length - tailN - 1));
|
|
@@ -11873,11 +12409,11 @@ async function pollUntil(deadline, probe) {
|
|
|
11873
12409
|
if (result !== void 0) return result;
|
|
11874
12410
|
const remaining = deadline - Date.now();
|
|
11875
12411
|
if (remaining <= 0) break;
|
|
11876
|
-
await
|
|
12412
|
+
await sleep5(Math.min(QUEUE_POLL_INTERVAL_MS, remaining));
|
|
11877
12413
|
}
|
|
11878
12414
|
throw new QueueWaitTimeout();
|
|
11879
12415
|
}
|
|
11880
|
-
function
|
|
12416
|
+
function sleep5(ms) {
|
|
11881
12417
|
return new Promise((r) => setTimeout(r, ms));
|
|
11882
12418
|
}
|
|
11883
12419
|
function parsePositiveInt3(raw, label) {
|
|
@@ -11927,7 +12463,8 @@ async function rehydrateFromState() {
|
|
|
11927
12463
|
cloudBackend: b.cloud?.backend,
|
|
11928
12464
|
relayPreviewUrl: b.cloud?.relayPreviewUrl,
|
|
11929
12465
|
relayPreviewToken: b.cloud?.relayPreviewToken,
|
|
11930
|
-
bridgeToken: b.cloud?.bridgeToken
|
|
12466
|
+
bridgeToken: b.cloud?.bridgeToken,
|
|
12467
|
+
autoApproveHostActions: b.autoApproveHostActions
|
|
11931
12468
|
}))
|
|
11932
12469
|
);
|
|
11933
12470
|
}
|
|
@@ -12149,6 +12686,36 @@ async function runDockerJob(job, log47, onBoxCreated) {
|
|
|
12149
12686
|
} else {
|
|
12150
12687
|
throw new Error(`unknown agent kind: ${String(job.agent)}`);
|
|
12151
12688
|
}
|
|
12689
|
+
await maybeOpenQueuedTerminal(job, result.record.name, log47);
|
|
12690
|
+
}
|
|
12691
|
+
function agentBinaryName(agent) {
|
|
12692
|
+
return agent === "claude-code" ? "claude" : agent;
|
|
12693
|
+
}
|
|
12694
|
+
async function maybeOpenQueuedTerminal(job, boxName, log47) {
|
|
12695
|
+
const ctx = job.openTerminal;
|
|
12696
|
+
if (!ctx) return;
|
|
12697
|
+
const cliEntry = process.env["AGENTBOX_CLI_ENTRY"];
|
|
12698
|
+
if (!cliEntry) {
|
|
12699
|
+
log47.write("queue.openIn: AGENTBOX_CLI_ENTRY unset; cannot open terminal");
|
|
12700
|
+
return;
|
|
12701
|
+
}
|
|
12702
|
+
const argv2 = [
|
|
12703
|
+
process.execPath,
|
|
12704
|
+
cliEntry,
|
|
12705
|
+
agentBinaryName(job.agent),
|
|
12706
|
+
"attach",
|
|
12707
|
+
boxName,
|
|
12708
|
+
"--attach-in",
|
|
12709
|
+
"same"
|
|
12710
|
+
];
|
|
12711
|
+
try {
|
|
12712
|
+
const r = await spawnQueuedOpenTerminal(ctx, argv2, boxName);
|
|
12713
|
+
log47.write(
|
|
12714
|
+
r.launched ? `queue.openIn: ${r.note}` : `queue.openIn: open failed: ${r.error ?? ""}`
|
|
12715
|
+
);
|
|
12716
|
+
} catch (err) {
|
|
12717
|
+
log47.write(`queue.openIn: open threw: ${err instanceof Error ? err.message : String(err)}`);
|
|
12718
|
+
}
|
|
12152
12719
|
}
|
|
12153
12720
|
async function runCloudJob(job, log47, onBoxCreated) {
|
|
12154
12721
|
const opts = job.createOpts;
|
|
@@ -12206,6 +12773,7 @@ async function runCloudJob(job, log47, onBoxCreated) {
|
|
|
12206
12773
|
sessionName,
|
|
12207
12774
|
extraArgs
|
|
12208
12775
|
});
|
|
12776
|
+
await maybeOpenQueuedTerminal(job, result.record.name, log47);
|
|
12209
12777
|
}
|
|
12210
12778
|
function buildOverridesFromJob(job) {
|
|
12211
12779
|
const opts = job.createOpts;
|
|
@@ -12757,12 +13325,12 @@ async function renderText(i) {
|
|
|
12757
13325
|
`env files ${i.record.withEnv ? "yes" : "no"}`,
|
|
12758
13326
|
"endpoints",
|
|
12759
13327
|
...renderEndpoints(i),
|
|
12760
|
-
`mem limit ${lim?.memoryBytes ?
|
|
13328
|
+
`mem limit ${lim?.memoryBytes ? fmtBytes2(lim.memoryBytes) : "unlimited"}`,
|
|
12761
13329
|
`cpu limit ${fmtLimit(lim?.cpus, "")}`,
|
|
12762
13330
|
`pids limit ${fmtLimit(lim?.pidsLimit, "")}`,
|
|
12763
13331
|
`disk limit ${lim?.disk ? `${lim.disk} (best-effort; no-op on overlay2/macOS)` : "unlimited"}`,
|
|
12764
13332
|
`snapshot dir ${i.record.snapshotDir ?? "(none)"}`,
|
|
12765
|
-
`snapshot size ${
|
|
13333
|
+
`snapshot size ${fmtBytes2(i.snapshotSizeBytes)}`,
|
|
12766
13334
|
`checkpoint ${renderCheckpoint(i, ckptBytes)}`,
|
|
12767
13335
|
`host export ${i.hostPaths.mergedExport} (run \`agentbox open\` to refresh)`,
|
|
12768
13336
|
`created ${i.record.createdAt}`
|
|
@@ -12772,7 +13340,7 @@ async function renderText(i) {
|
|
|
12772
13340
|
function renderCheckpoint(i, sizeBytes) {
|
|
12773
13341
|
const src = i.record.checkpointSource;
|
|
12774
13342
|
if (!src || !i.record.checkpointImage) return "(none)";
|
|
12775
|
-
const sizePart = sizeBytes !== null ? ` ${
|
|
13343
|
+
const sizePart = sizeBytes !== null ? ` ${fmtBytes2(sizeBytes)}` : "";
|
|
12776
13344
|
return `${src.ref} (${src.type}, chain ${src.chain.length}) \u2192 ${i.record.checkpointImage}${sizePart}`;
|
|
12777
13345
|
}
|
|
12778
13346
|
function renderClaudeSession(i) {
|
|
@@ -12843,7 +13411,7 @@ async function renderCloudText(box) {
|
|
|
12843
13411
|
`bridge token ${box.cloud?.bridgeToken ? "(set)" : "(unset)"}`,
|
|
12844
13412
|
`playwright ${box.withPlaywright ? "yes" : "no"}`,
|
|
12845
13413
|
`env files ${box.withEnv ? "yes" : "no"}`,
|
|
12846
|
-
`mem limit ${lim?.memoryBytes ?
|
|
13414
|
+
`mem limit ${lim?.memoryBytes ? fmtBytes2(lim.memoryBytes) : "unlimited"}`,
|
|
12847
13415
|
`cpu limit ${fmtLimit(lim?.cpus, "")}`,
|
|
12848
13416
|
`pids limit ${fmtLimit(lim?.pidsLimit, "")}`,
|
|
12849
13417
|
`persisted ${persisted ? `${persisted.timestamp} (${String(persisted.services.length)} svc, ${String(persisted.tasks.length)} tasks, ${String(persisted.ports.length)} ports)` : "(none)"}`,
|
|
@@ -13004,20 +13572,20 @@ function renderResources(s) {
|
|
|
13004
13572
|
if (s.live) {
|
|
13005
13573
|
seg.push(`cpu ${fmtPercent(s.cpuPercent)}${lim(s.limits.cpus)}`);
|
|
13006
13574
|
seg.push(
|
|
13007
|
-
`mem ${
|
|
13575
|
+
`mem ${fmtBytes2(s.memUsedBytes)} / ${fmtBytes2(s.memLimitBytes)} (${fmtPercent(s.memPercent)})${lim(s.limits.memoryBytes ? fmtBytes2(s.limits.memoryBytes) : null)}`
|
|
13008
13576
|
);
|
|
13009
13577
|
seg.push(`pids ${s.pids === null ? "\u2014" : String(s.pids)}${lim(s.limits.pidsLimit)}`);
|
|
13010
13578
|
} else {
|
|
13011
13579
|
seg.push("not running");
|
|
13012
|
-
if (s.limits.memoryBytes) seg.push(`mem limit ${
|
|
13580
|
+
if (s.limits.memoryBytes) seg.push(`mem limit ${fmtBytes2(s.limits.memoryBytes)}`);
|
|
13013
13581
|
if (s.limits.cpus) seg.push(`cpu limit ${String(s.limits.cpus)}`);
|
|
13014
13582
|
if (s.limits.pidsLimit) seg.push(`pids limit ${String(s.limits.pidsLimit)}`);
|
|
13015
13583
|
}
|
|
13016
13584
|
seg.push(
|
|
13017
|
-
`disk ${
|
|
13585
|
+
`disk ${fmtBytes2(s.diskUsedBytes)}${s.limits.disk ? ` (limit ${s.limits.disk}, no-op on overlay2/macOS)` : ""}`
|
|
13018
13586
|
);
|
|
13019
|
-
if (s.snapshotDiskBytes !== null) seg.push(`snapshot ${
|
|
13020
|
-
if (s.checkpointVolumeBytes !== null) seg.push(`ckpt ${
|
|
13587
|
+
if (s.snapshotDiskBytes !== null) seg.push(`snapshot ${fmtBytes2(s.snapshotDiskBytes)}`);
|
|
13588
|
+
if (s.checkpointVolumeBytes !== null) seg.push(`ckpt ${fmtBytes2(s.checkpointVolumeBytes)}`);
|
|
13021
13589
|
let line = ` ${seg.join(" ")}`;
|
|
13022
13590
|
for (const w of s.warnings) line += `
|
|
13023
13591
|
note: ${w}`;
|
|
@@ -13114,8 +13682,8 @@ restart with: agentbox start ${box.name}
|
|
|
13114
13682
|
import { Command as Command43 } from "commander";
|
|
13115
13683
|
var COLS = ["BOX", "STATE", "CPU%", "MEM USAGE / LIMIT", "MEM%", "PIDS", "DISK", "NET I/O"];
|
|
13116
13684
|
function row(name, state, s) {
|
|
13117
|
-
const mem = `${
|
|
13118
|
-
const net = s.netRxBytes === null && s.netTxBytes === null ? "\u2014" : `${
|
|
13685
|
+
const mem = `${fmtBytes2(s.memUsedBytes)} / ${fmtBytes2(s.memLimitBytes)}`;
|
|
13686
|
+
const net = s.netRxBytes === null && s.netTxBytes === null ? "\u2014" : `${fmtBytes2(s.netRxBytes)} / ${fmtBytes2(s.netTxBytes)}`;
|
|
13119
13687
|
return [
|
|
13120
13688
|
name,
|
|
13121
13689
|
state,
|
|
@@ -13123,7 +13691,7 @@ function row(name, state, s) {
|
|
|
13123
13691
|
s.live ? mem : "\u2014",
|
|
13124
13692
|
fmtPercent(s.memPercent),
|
|
13125
13693
|
s.pids === null ? "\u2014" : String(s.pids),
|
|
13126
|
-
|
|
13694
|
+
fmtBytes2(s.diskUsedBytes),
|
|
13127
13695
|
s.live ? net : "\u2014"
|
|
13128
13696
|
];
|
|
13129
13697
|
}
|
|
@@ -13179,8 +13747,8 @@ async function renderProjectFooters() {
|
|
|
13179
13747
|
allCheckpointImagesBytes(),
|
|
13180
13748
|
agentboxHomeBytes()
|
|
13181
13749
|
]);
|
|
13182
|
-
if (home !== null) parts.push(`~/.agentbox: ${
|
|
13183
|
-
if (ckpt !== null) parts.push(`checkpoints: ${
|
|
13750
|
+
if (home !== null) parts.push(`~/.agentbox: ${fmtBytes2(home)}`);
|
|
13751
|
+
if (ckpt !== null) parts.push(`checkpoints: ${fmtBytes2(ckpt)}`);
|
|
13184
13752
|
return parts.length > 0 ? `
|
|
13185
13753
|
|
|
13186
13754
|
SYSTEM: ${parts.join(" - ")}` : "";
|