@madarco/agentbox 0.4.0 → 0.5.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/dist/{chunk-3NCUES35.js → chunk-6VTAPD4H.js} +123 -112
- package/dist/chunk-6VTAPD4H.js.map +1 -0
- package/dist/{chunk-J35IH7W5.js → chunk-7J5AJLWG.js} +61 -23
- package/dist/chunk-7J5AJLWG.js.map +1 -0
- package/dist/{chunk-3JKQNOXP.js → chunk-FJNIFTWK.js} +66 -65
- package/dist/chunk-FJNIFTWK.js.map +1 -0
- package/dist/{chunk-IDR4HVIC.js → chunk-HPZMD5DE.js} +2 -2
- package/dist/chunk-HPZMD5DE.js.map +1 -0
- package/dist/{chunk-MOC54XL6.js → chunk-PXUBE5KS.js} +376 -245
- package/dist/chunk-PXUBE5KS.js.map +1 -0
- package/dist/{chunk-SOMIKEN2.js → chunk-RFC5F5HR.js} +272 -214
- package/dist/chunk-RFC5F5HR.js.map +1 -0
- package/dist/create-AHZ3GVEZ-TGEDL7UX.js +15 -0
- package/dist/index.js +2760 -1857
- package/dist/index.js.map +1 -1
- package/dist/{lifecycle-YTMZYKOE-TD5S5FTS.js → lifecycle-LFOL6YFM-TCHDX3J5.js} +5 -5
- package/dist/{state-ZSP3ORXW-WI6KOIG3.js → state-KD7M46ZP-KHFTHFUS.js} +2 -2
- package/dist/stats-Z4BVJODD-HEC4TMUZ.js +19 -0
- package/package.json +3 -2
- package/runtime/docker/Dockerfile.box +53 -20
- package/runtime/docker/apps/cli/share/agentbox-setup/SKILL.md +39 -50
- package/runtime/docker/packages/ctl/dist/bin.cjs +219 -148
- package/runtime/docker/packages/sandbox-docker/scripts/agentbox-checkpoint-cleanup +42 -0
- package/runtime/docker/packages/sandbox-docker/scripts/custom-system-CLAUDE.md +26 -15
- package/runtime/relay/bin.cjs +288 -12
- package/share/agentbox-setup/SKILL.md +39 -50
- package/dist/chunk-3JKQNOXP.js.map +0 -1
- package/dist/chunk-3NCUES35.js.map +0 -1
- package/dist/chunk-IDR4HVIC.js.map +0 -1
- package/dist/chunk-J35IH7W5.js.map +0 -1
- package/dist/chunk-MOC54XL6.js.map +0 -1
- package/dist/chunk-SOMIKEN2.js.map +0 -1
- package/dist/create-SE6H4B5U-IWAZHJHV.js +0 -15
- package/dist/stats-GZFLPYTU-DBJ2DVBJ.js +0 -19
- /package/dist/{create-SE6H4B5U-IWAZHJHV.js.map → create-AHZ3GVEZ-TGEDL7UX.js.map} +0 -0
- /package/dist/{lifecycle-YTMZYKOE-TD5S5FTS.js.map → lifecycle-LFOL6YFM-TCHDX3J5.js.map} +0 -0
- /package/dist/{state-ZSP3ORXW-WI6KOIG3.js.map → state-KD7M46ZP-KHFTHFUS.js.map} +0 -0
- /package/dist/{stats-GZFLPYTU-DBJ2DVBJ.js.map → stats-Z4BVJODD-HEC4TMUZ.js.map} +0 -0
|
@@ -10545,6 +10545,108 @@ var claudeStateCommand = new Command("claude-state").description("Report Claude
|
|
|
10545
10545
|
process.exit(0);
|
|
10546
10546
|
});
|
|
10547
10547
|
|
|
10548
|
+
// src/relay-rpc.ts
|
|
10549
|
+
var import_node_http = require("http");
|
|
10550
|
+
var import_node_https = require("https");
|
|
10551
|
+
function postRpc(method, params, opts = {}) {
|
|
10552
|
+
const prefix = opts.errorPrefix ?? "agentbox-ctl rpc";
|
|
10553
|
+
const urlStr = process.env.AGENTBOX_RELAY_URL;
|
|
10554
|
+
const token = process.env.AGENTBOX_RELAY_TOKEN;
|
|
10555
|
+
if (!urlStr || !token) {
|
|
10556
|
+
process.stderr.write(
|
|
10557
|
+
`${prefix}: AGENTBOX_RELAY_URL / AGENTBOX_RELAY_TOKEN not set; no relay configured for this box.
|
|
10558
|
+
`
|
|
10559
|
+
);
|
|
10560
|
+
return Promise.resolve({ status: 0, parsed: null, raw: "", internalExitCode: 65 });
|
|
10561
|
+
}
|
|
10562
|
+
let url;
|
|
10563
|
+
try {
|
|
10564
|
+
url = new URL(urlStr);
|
|
10565
|
+
} catch {
|
|
10566
|
+
process.stderr.write(`${prefix}: invalid AGENTBOX_RELAY_URL: ${urlStr}
|
|
10567
|
+
`);
|
|
10568
|
+
return Promise.resolve({ status: 0, parsed: null, raw: "", internalExitCode: 65 });
|
|
10569
|
+
}
|
|
10570
|
+
const body = JSON.stringify({ method, params });
|
|
10571
|
+
const isHttps = url.protocol === "https:";
|
|
10572
|
+
const transport = isHttps ? import_node_https.request : import_node_http.request;
|
|
10573
|
+
const port = url.port.length > 0 ? Number.parseInt(url.port, 10) : isHttps ? 443 : 80;
|
|
10574
|
+
return new Promise((resolve) => {
|
|
10575
|
+
const req = transport(
|
|
10576
|
+
{
|
|
10577
|
+
host: url.hostname,
|
|
10578
|
+
port,
|
|
10579
|
+
method: "POST",
|
|
10580
|
+
path: `${url.pathname.replace(/\/$/, "")}/rpc`,
|
|
10581
|
+
headers: {
|
|
10582
|
+
"Content-Type": "application/json",
|
|
10583
|
+
"Content-Length": Buffer.byteLength(body).toString(),
|
|
10584
|
+
Authorization: `Bearer ${token}`
|
|
10585
|
+
}
|
|
10586
|
+
},
|
|
10587
|
+
(res) => {
|
|
10588
|
+
const chunks = [];
|
|
10589
|
+
res.on("data", (c) => chunks.push(c));
|
|
10590
|
+
res.on("end", () => {
|
|
10591
|
+
const status2 = res.statusCode ?? 0;
|
|
10592
|
+
const text = Buffer.concat(chunks).toString("utf8");
|
|
10593
|
+
let parsed = null;
|
|
10594
|
+
try {
|
|
10595
|
+
const v = JSON.parse(text);
|
|
10596
|
+
if (v && typeof v === "object" && typeof v.exitCode === "number") {
|
|
10597
|
+
parsed = v;
|
|
10598
|
+
}
|
|
10599
|
+
} catch {
|
|
10600
|
+
parsed = null;
|
|
10601
|
+
}
|
|
10602
|
+
resolve({ status: status2, parsed, raw: text, internalExitCode: null });
|
|
10603
|
+
});
|
|
10604
|
+
}
|
|
10605
|
+
);
|
|
10606
|
+
req.on("error", (err) => {
|
|
10607
|
+
process.stderr.write(`${prefix}: ${String(err.message ?? err)}
|
|
10608
|
+
`);
|
|
10609
|
+
resolve({ status: 0, parsed: null, raw: "", internalExitCode: 126 });
|
|
10610
|
+
});
|
|
10611
|
+
req.write(body);
|
|
10612
|
+
req.end();
|
|
10613
|
+
});
|
|
10614
|
+
}
|
|
10615
|
+
async function postRpcAndExit(method, params, opts = {}) {
|
|
10616
|
+
const prefix = opts.errorPrefix ?? "agentbox-ctl rpc";
|
|
10617
|
+
const out = await postRpc(method, params, opts);
|
|
10618
|
+
if (out.internalExitCode !== null) return out.internalExitCode;
|
|
10619
|
+
if (out.parsed) {
|
|
10620
|
+
if (out.parsed.stdout) process.stdout.write(out.parsed.stdout);
|
|
10621
|
+
if (out.parsed.stderr) process.stderr.write(out.parsed.stderr);
|
|
10622
|
+
return out.parsed.exitCode;
|
|
10623
|
+
}
|
|
10624
|
+
process.stderr.write(`${prefix}: relay returned ${String(out.status)}: ${out.raw}
|
|
10625
|
+
`);
|
|
10626
|
+
return out.status >= 200 && out.status < 300 ? 0 : 1;
|
|
10627
|
+
}
|
|
10628
|
+
|
|
10629
|
+
// src/commands/cp.ts
|
|
10630
|
+
var cpCommand = new Command("cp").description("Copy a file/dir between this box and the host (gated by user prompt on the host wrapper)").addCommand(
|
|
10631
|
+
new Command("toHost").description("Copy box:<boxPath> -> host:<hostPath>").argument("<boxPath>", "source path inside the container").argument("<hostPath>", "destination path on the host").option("--no-recursive", "reserved; current implementation is always recursive (docker cp -a)").action(async (boxPath, hostPath, opts) => {
|
|
10632
|
+
const params = { boxPath, hostPath };
|
|
10633
|
+
if (opts.recursive === false) params.recursive = false;
|
|
10634
|
+
const code = await postRpcAndExit("cp.toHost", params, {
|
|
10635
|
+
errorPrefix: "agentbox-ctl cp"
|
|
10636
|
+
});
|
|
10637
|
+
process.exit(code);
|
|
10638
|
+
})
|
|
10639
|
+
).addCommand(
|
|
10640
|
+
new Command("fromHost").description("Copy host:<hostPath> -> box:<boxPath>").argument("<hostPath>", "source path on the host").argument("<boxPath>", "destination path inside the container").option("--no-recursive", "reserved; current implementation is always recursive (docker cp -a)").action(async (hostPath, boxPath, opts) => {
|
|
10641
|
+
const params = { boxPath, hostPath };
|
|
10642
|
+
if (opts.recursive === false) params.recursive = false;
|
|
10643
|
+
const code = await postRpcAndExit("cp.fromHost", params, {
|
|
10644
|
+
errorPrefix: "agentbox-ctl cp"
|
|
10645
|
+
});
|
|
10646
|
+
process.exit(code);
|
|
10647
|
+
})
|
|
10648
|
+
);
|
|
10649
|
+
|
|
10548
10650
|
// src/config.ts
|
|
10549
10651
|
var import_promises = require("fs/promises");
|
|
10550
10652
|
var import_yaml = __toESM(require_dist(), 1);
|
|
@@ -11088,8 +11190,8 @@ function startProbe(probe, ctx) {
|
|
|
11088
11190
|
}
|
|
11089
11191
|
|
|
11090
11192
|
// src/relay-client.ts
|
|
11091
|
-
var
|
|
11092
|
-
var
|
|
11193
|
+
var import_node_http2 = require("http");
|
|
11194
|
+
var import_node_https2 = require("https");
|
|
11093
11195
|
var RelayClient = class {
|
|
11094
11196
|
url;
|
|
11095
11197
|
token;
|
|
@@ -11115,7 +11217,7 @@ var RelayClient = class {
|
|
|
11115
11217
|
const url = this.url;
|
|
11116
11218
|
const body = JSON.stringify({ type, ts: (/* @__PURE__ */ new Date()).toISOString(), payload });
|
|
11117
11219
|
const isHttps = url.protocol === "https:";
|
|
11118
|
-
const transport = isHttps ?
|
|
11220
|
+
const transport = isHttps ? import_node_https2.request : import_node_http2.request;
|
|
11119
11221
|
const port = url.port.length > 0 ? Number.parseInt(url.port, 10) : isHttps ? 443 : 80;
|
|
11120
11222
|
const req = transport(
|
|
11121
11223
|
{
|
|
@@ -11237,6 +11339,20 @@ function spawnArgs(cmd) {
|
|
|
11237
11339
|
if (typeof cmd === "string") return { bin: "bash", args: ["-c", cmd] };
|
|
11238
11340
|
return { bin: cmd[0], args: cmd.slice(1) };
|
|
11239
11341
|
}
|
|
11342
|
+
var cachedLoginPath;
|
|
11343
|
+
function loginShellPath() {
|
|
11344
|
+
if (cachedLoginPath !== void 0) return cachedLoginPath;
|
|
11345
|
+
try {
|
|
11346
|
+
const out = (0, import_node_child_process.execFileSync)("bash", ["-lc", 'printf %s "$PATH"'], {
|
|
11347
|
+
encoding: "utf8",
|
|
11348
|
+
timeout: 5e3
|
|
11349
|
+
}).trim();
|
|
11350
|
+
cachedLoginPath = out || (process.env.PATH ?? "");
|
|
11351
|
+
} catch {
|
|
11352
|
+
cachedLoginPath = process.env.PATH ?? "";
|
|
11353
|
+
}
|
|
11354
|
+
return cachedLoginPath;
|
|
11355
|
+
}
|
|
11240
11356
|
var ServiceRunner = class extends import_node_events.EventEmitter {
|
|
11241
11357
|
constructor(spec, opts) {
|
|
11242
11358
|
super();
|
|
@@ -11351,7 +11467,7 @@ var ServiceRunner = class extends import_node_events.EventEmitter {
|
|
|
11351
11467
|
try {
|
|
11352
11468
|
child = this.spawnFn(bin, args, {
|
|
11353
11469
|
cwd,
|
|
11354
|
-
env: { ...process.env, ...spec.env ?? {} },
|
|
11470
|
+
env: { ...process.env, PATH: loginShellPath(), ...spec.env ?? {} },
|
|
11355
11471
|
stdio: ["ignore", "pipe", "pipe"]
|
|
11356
11472
|
});
|
|
11357
11473
|
} catch (err) {
|
|
@@ -11549,7 +11665,7 @@ var TaskRunner = class extends import_node_events.EventEmitter {
|
|
|
11549
11665
|
try {
|
|
11550
11666
|
child = this.spawnFn(bin, args, {
|
|
11551
11667
|
cwd,
|
|
11552
|
-
env: { ...process.env, ...spec.env ?? {} },
|
|
11668
|
+
env: { ...process.env, PATH: loginShellPath(), ...spec.env ?? {} },
|
|
11553
11669
|
stdio: ["ignore", "pipe", "pipe"]
|
|
11554
11670
|
});
|
|
11555
11671
|
} catch (err) {
|
|
@@ -11853,6 +11969,14 @@ var Supervisor = class extends import_node_events.EventEmitter {
|
|
|
11853
11969
|
changed.push(spec.name);
|
|
11854
11970
|
}
|
|
11855
11971
|
}
|
|
11972
|
+
for (const [name, unit] of this.units) {
|
|
11973
|
+
if (unit.kind !== "task") continue;
|
|
11974
|
+
const task = unit;
|
|
11975
|
+
if (task.getState() === "skipped") {
|
|
11976
|
+
this.failed.delete(name);
|
|
11977
|
+
task.resetForRerun();
|
|
11978
|
+
}
|
|
11979
|
+
}
|
|
11856
11980
|
this.applyWebProxy();
|
|
11857
11981
|
this.schedule();
|
|
11858
11982
|
return { added, removed, changed };
|
|
@@ -12427,172 +12551,116 @@ var daemonCommand = new Command("daemon").description("Run the agentbox-ctl supe
|
|
|
12427
12551
|
process.on("SIGINT", () => void shutdown("SIGINT"));
|
|
12428
12552
|
});
|
|
12429
12553
|
|
|
12430
|
-
// src/commands/
|
|
12431
|
-
var
|
|
12432
|
-
|
|
12433
|
-
|
|
12434
|
-
|
|
12435
|
-
|
|
12436
|
-
|
|
12554
|
+
// src/commands/download.ts
|
|
12555
|
+
var KINDS = ["workspace", "env", "config", "claude"];
|
|
12556
|
+
function isKind(v) {
|
|
12557
|
+
return KINDS.includes(v);
|
|
12558
|
+
}
|
|
12559
|
+
var downloadCommand = new Command("download").description(
|
|
12560
|
+
"Download box contents to the host (gated by user prompt). Kinds: workspace (default), env, config, claude"
|
|
12561
|
+
).argument("[kind]", `one of: ${KINDS.join(", ")}`, "workspace").action(async (kindArg) => {
|
|
12562
|
+
if (!isKind(kindArg)) {
|
|
12437
12563
|
process.stderr.write(
|
|
12438
|
-
|
|
12439
|
-
);
|
|
12440
|
-
return 65;
|
|
12441
|
-
}
|
|
12442
|
-
let url;
|
|
12443
|
-
try {
|
|
12444
|
-
url = new URL(urlStr);
|
|
12445
|
-
} catch {
|
|
12446
|
-
process.stderr.write(`agentbox-ctl checkpoint: invalid AGENTBOX_RELAY_URL: ${urlStr}
|
|
12447
|
-
`);
|
|
12448
|
-
return 65;
|
|
12449
|
-
}
|
|
12450
|
-
const body = JSON.stringify({ method: "checkpoint.create", params });
|
|
12451
|
-
const isHttps = url.protocol === "https:";
|
|
12452
|
-
const transport = isHttps ? import_node_https2.request : import_node_http2.request;
|
|
12453
|
-
const port = url.port.length > 0 ? Number.parseInt(url.port, 10) : isHttps ? 443 : 80;
|
|
12454
|
-
return new Promise((resolve) => {
|
|
12455
|
-
const req = transport(
|
|
12456
|
-
{
|
|
12457
|
-
host: url.hostname,
|
|
12458
|
-
port,
|
|
12459
|
-
method: "POST",
|
|
12460
|
-
path: `${url.pathname.replace(/\/$/, "")}/rpc`,
|
|
12461
|
-
headers: {
|
|
12462
|
-
"Content-Type": "application/json",
|
|
12463
|
-
"Content-Length": Buffer.byteLength(body).toString(),
|
|
12464
|
-
Authorization: `Bearer ${token}`
|
|
12465
|
-
}
|
|
12466
|
-
},
|
|
12467
|
-
(res) => {
|
|
12468
|
-
const chunks = [];
|
|
12469
|
-
res.on("data", (c) => chunks.push(c));
|
|
12470
|
-
res.on("end", () => {
|
|
12471
|
-
const status2 = res.statusCode ?? 0;
|
|
12472
|
-
const text = Buffer.concat(chunks).toString("utf8");
|
|
12473
|
-
let parsed = null;
|
|
12474
|
-
try {
|
|
12475
|
-
parsed = JSON.parse(text);
|
|
12476
|
-
} catch {
|
|
12477
|
-
parsed = null;
|
|
12478
|
-
}
|
|
12479
|
-
if (parsed && typeof parsed.exitCode === "number") {
|
|
12480
|
-
if (parsed.stdout) process.stdout.write(parsed.stdout);
|
|
12481
|
-
if (parsed.stderr) process.stderr.write(parsed.stderr);
|
|
12482
|
-
resolve(parsed.exitCode);
|
|
12483
|
-
return;
|
|
12484
|
-
}
|
|
12485
|
-
process.stderr.write(
|
|
12486
|
-
`agentbox-ctl checkpoint: relay returned ${String(status2)}: ${text}
|
|
12564
|
+
`agentbox-ctl download: unknown kind "${kindArg}"; expected one of: ${KINDS.join(", ")}
|
|
12487
12565
|
`
|
|
12488
|
-
);
|
|
12489
|
-
resolve(status2 >= 200 && status2 < 300 ? 0 : 1);
|
|
12490
|
-
});
|
|
12491
|
-
}
|
|
12492
12566
|
);
|
|
12493
|
-
|
|
12494
|
-
|
|
12495
|
-
|
|
12496
|
-
|
|
12497
|
-
|
|
12498
|
-
req.write(body);
|
|
12499
|
-
req.end();
|
|
12567
|
+
process.exit(64);
|
|
12568
|
+
}
|
|
12569
|
+
const params = { kind: kindArg };
|
|
12570
|
+
const code = await postRpcAndExit(`download.${kindArg}`, params, {
|
|
12571
|
+
errorPrefix: "agentbox-ctl download"
|
|
12500
12572
|
});
|
|
12501
|
-
}
|
|
12502
|
-
var checkpointCommand = new Command("checkpoint").description("Capture this box as a project checkpoint (host-side, via the agentbox relay)").option("--name <name>", "checkpoint name (default: <box-name>-<next>)").option("--merged", "flatten lower+upper into one tree instead of a layered delta").option("--set-default", "mark this checkpoint as the project default for new boxes").action(async (opts) => {
|
|
12503
|
-
const params = {};
|
|
12504
|
-
if (opts.name) params.name = opts.name;
|
|
12505
|
-
if (opts.merged === true) params.merged = true;
|
|
12506
|
-
if (opts.setDefault === true) params.setDefault = true;
|
|
12507
|
-
const code = await rpc(params);
|
|
12508
12573
|
process.exit(code);
|
|
12509
12574
|
});
|
|
12510
12575
|
|
|
12511
|
-
// src/commands/
|
|
12512
|
-
var
|
|
12513
|
-
|
|
12514
|
-
|
|
12515
|
-
|
|
12516
|
-
|
|
12517
|
-
|
|
12518
|
-
|
|
12519
|
-
|
|
12520
|
-
);
|
|
12521
|
-
|
|
12522
|
-
|
|
12523
|
-
|
|
12524
|
-
|
|
12525
|
-
|
|
12526
|
-
} catch {
|
|
12527
|
-
process.stderr.write(`agentbox-ctl git: invalid AGENTBOX_RELAY_URL: ${urlStr}
|
|
12528
|
-
`);
|
|
12529
|
-
return 65;
|
|
12576
|
+
// src/commands/checkpoint.ts
|
|
12577
|
+
var checkpointCommand = new Command("checkpoint").description("Capture this box as a project checkpoint (host-side, via the agentbox relay)").option("--name <name>", "checkpoint name (default: <box-name>-<next>)").option("--merged", "flatten lower+upper into one tree instead of a layered delta").option("--set-default", "mark this checkpoint as the project default for new boxes").option(
|
|
12578
|
+
"--replace",
|
|
12579
|
+
"if a checkpoint with the same name exists, rm it first (idempotent recapture; safe to retry when the previous run's stdout was lost)"
|
|
12580
|
+
).action(
|
|
12581
|
+
async (opts) => {
|
|
12582
|
+
const params = {};
|
|
12583
|
+
if (opts.name) params.name = opts.name;
|
|
12584
|
+
if (opts.merged === true) params.merged = true;
|
|
12585
|
+
if (opts.setDefault === true) params.setDefault = true;
|
|
12586
|
+
if (opts.replace === true) params.replace = true;
|
|
12587
|
+
const code = await postRpcAndExit("checkpoint.create", params, {
|
|
12588
|
+
errorPrefix: "agentbox-ctl checkpoint"
|
|
12589
|
+
});
|
|
12590
|
+
process.exit(code);
|
|
12530
12591
|
}
|
|
12531
|
-
|
|
12532
|
-
|
|
12533
|
-
|
|
12592
|
+
);
|
|
12593
|
+
|
|
12594
|
+
// src/commands/git.ts
|
|
12595
|
+
var import_node_child_process4 = require("child_process");
|
|
12596
|
+
function buildParams(opts, extra) {
|
|
12597
|
+
const params = { path: opts.cwd ?? process.cwd() };
|
|
12534
12598
|
if (opts.remote) params.remote = opts.remote;
|
|
12535
12599
|
if (extra.length > 0) params.args = extra;
|
|
12536
|
-
|
|
12537
|
-
|
|
12538
|
-
|
|
12539
|
-
const port = url.port.length > 0 ? Number.parseInt(url.port, 10) : isHttps ? 443 : 80;
|
|
12600
|
+
return params;
|
|
12601
|
+
}
|
|
12602
|
+
function runLocalGit(args, cwd) {
|
|
12540
12603
|
return new Promise((resolve) => {
|
|
12541
|
-
const
|
|
12542
|
-
|
|
12543
|
-
|
|
12544
|
-
port,
|
|
12545
|
-
method: "POST",
|
|
12546
|
-
path: `${url.pathname.replace(/\/$/, "")}/rpc`,
|
|
12547
|
-
headers: {
|
|
12548
|
-
"Content-Type": "application/json",
|
|
12549
|
-
"Content-Length": Buffer.byteLength(body).toString(),
|
|
12550
|
-
Authorization: `Bearer ${token}`
|
|
12551
|
-
}
|
|
12552
|
-
},
|
|
12553
|
-
(res) => {
|
|
12554
|
-
const chunks = [];
|
|
12555
|
-
res.on("data", (c) => chunks.push(c));
|
|
12556
|
-
res.on("end", () => {
|
|
12557
|
-
const status2 = res.statusCode ?? 0;
|
|
12558
|
-
const text = Buffer.concat(chunks).toString("utf8");
|
|
12559
|
-
let parsed = null;
|
|
12560
|
-
try {
|
|
12561
|
-
parsed = JSON.parse(text);
|
|
12562
|
-
} catch {
|
|
12563
|
-
parsed = null;
|
|
12564
|
-
}
|
|
12565
|
-
if (parsed && typeof parsed.exitCode === "number") {
|
|
12566
|
-
if (parsed.stdout) process.stdout.write(parsed.stdout);
|
|
12567
|
-
if (parsed.stderr) process.stderr.write(parsed.stderr);
|
|
12568
|
-
resolve(parsed.exitCode);
|
|
12569
|
-
return;
|
|
12570
|
-
}
|
|
12571
|
-
process.stderr.write(`agentbox-ctl git: relay returned ${String(status2)}: ${text}
|
|
12572
|
-
`);
|
|
12573
|
-
resolve(status2 >= 200 && status2 < 300 ? 0 : 1);
|
|
12574
|
-
});
|
|
12575
|
-
}
|
|
12576
|
-
);
|
|
12577
|
-
req.on("error", (err) => {
|
|
12604
|
+
const child = (0, import_node_child_process4.spawn)("git", args, { cwd, stdio: "inherit" });
|
|
12605
|
+
child.on("close", (code) => resolve(code ?? 1));
|
|
12606
|
+
child.on("error", (err) => {
|
|
12578
12607
|
process.stderr.write(`agentbox-ctl git: ${String(err.message ?? err)}
|
|
12579
12608
|
`);
|
|
12580
12609
|
resolve(126);
|
|
12581
12610
|
});
|
|
12582
|
-
req.write(body);
|
|
12583
|
-
req.end();
|
|
12584
12611
|
});
|
|
12585
12612
|
}
|
|
12586
12613
|
var gitCommand = new Command("git").description("Git operations that need host credentials (routed through the agentbox relay)").addCommand(
|
|
12587
|
-
new Command("
|
|
12588
|
-
const code = await
|
|
12614
|
+
new Command("push").description("Run `git push` on the host main repo against this box's branch (user is prompted on the host wrapper to confirm)").option("--remote <name>", "remote name (default: origin)").option("--cwd <path>", "container path identifying which registered worktree to use").allowExcessArguments(true).allowUnknownOption(true).argument("[args...]", "additional args forwarded to git push").action(async (args, opts) => {
|
|
12615
|
+
const code = await postRpcAndExit("git.push", buildParams(opts, args), {
|
|
12616
|
+
errorPrefix: "agentbox-ctl git"
|
|
12617
|
+
});
|
|
12589
12618
|
process.exit(code);
|
|
12590
12619
|
})
|
|
12591
12620
|
).addCommand(
|
|
12592
|
-
new Command("
|
|
12593
|
-
const code = await
|
|
12621
|
+
new Command("fetch").description("Run `git fetch` on the host main repo (refs land in the shared .git)").option("--remote <name>", "remote name (default: origin)").option("--cwd <path>", "container path identifying which registered worktree to use").allowExcessArguments(true).allowUnknownOption(true).argument("[args...]", "additional args forwarded to git fetch").action(async (args, opts) => {
|
|
12622
|
+
const code = await postRpcAndExit("git.fetch", buildParams(opts, args), {
|
|
12623
|
+
errorPrefix: "agentbox-ctl git"
|
|
12624
|
+
});
|
|
12594
12625
|
process.exit(code);
|
|
12595
12626
|
})
|
|
12627
|
+
).addCommand(
|
|
12628
|
+
new Command("pull").description(
|
|
12629
|
+
"Fetch via the relay (host creds), then merge into the in-container working tree locally"
|
|
12630
|
+
).option("--remote <name>", "remote name (default: origin)").option("--cwd <path>", "container path identifying which registered worktree to use").option("--ff-only", "pass --ff-only to the local merge").allowExcessArguments(true).allowUnknownOption(true).argument("[args...]", "additional args forwarded to git fetch").action(
|
|
12631
|
+
async (args, opts) => {
|
|
12632
|
+
const fetchCode = await postRpcAndExit("git.fetch", buildParams(opts, args), {
|
|
12633
|
+
errorPrefix: "agentbox-ctl git"
|
|
12634
|
+
});
|
|
12635
|
+
if (fetchCode !== 0) process.exit(fetchCode);
|
|
12636
|
+
const remote = opts.remote ?? "origin";
|
|
12637
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
12638
|
+
const mergeArgs = ["merge"];
|
|
12639
|
+
if (opts.ffOnly) mergeArgs.push("--ff-only");
|
|
12640
|
+
mergeArgs.push(`${remote}/HEAD`);
|
|
12641
|
+
const mergeCode = await runLocalGit(mergeArgs, cwd);
|
|
12642
|
+
process.exit(mergeCode);
|
|
12643
|
+
}
|
|
12644
|
+
)
|
|
12645
|
+
);
|
|
12646
|
+
|
|
12647
|
+
// src/commands/notify.ts
|
|
12648
|
+
async function reportState(opts, state) {
|
|
12649
|
+
try {
|
|
12650
|
+
await claudeState({ socketPath: opts.socket, timeoutMs: 1500 }, state);
|
|
12651
|
+
} catch {
|
|
12652
|
+
}
|
|
12653
|
+
}
|
|
12654
|
+
var notifyCommand = new Command("notify").description(
|
|
12655
|
+
"Signal that the in-box agent is waiting for user input (highlights the box in the dashboard)"
|
|
12656
|
+
).option("--socket <path>", "unix socket path", DEFAULT_SOCKET_PATH).option("--message <text>", "reserved for future use; accepted but ignored in v1").action(async (opts) => {
|
|
12657
|
+
await reportState(opts, "waiting");
|
|
12658
|
+
process.exit(0);
|
|
12659
|
+
}).addCommand(
|
|
12660
|
+
new Command("clear").description("Clear the waiting state (alias for `claude-state idle`)").option("--socket <path>", "unix socket path", DEFAULT_SOCKET_PATH).action(async (opts) => {
|
|
12661
|
+
await reportState(opts, "idle");
|
|
12662
|
+
process.exit(0);
|
|
12663
|
+
})
|
|
12596
12664
|
);
|
|
12597
12665
|
|
|
12598
12666
|
// src/render.ts
|
|
@@ -12783,6 +12851,9 @@ program2.addCommand(waitReadyCommand);
|
|
|
12783
12851
|
program2.addCommand(runTaskCommand);
|
|
12784
12852
|
program2.addCommand(gitCommand);
|
|
12785
12853
|
program2.addCommand(checkpointCommand);
|
|
12854
|
+
program2.addCommand(cpCommand);
|
|
12855
|
+
program2.addCommand(downloadCommand);
|
|
12856
|
+
program2.addCommand(notifyCommand);
|
|
12786
12857
|
program2.parseAsync(process.argv).catch((err) => {
|
|
12787
12858
|
const msg = err instanceof Error ? err.message : String(err);
|
|
12788
12859
|
process.stderr.write(`agentbox-ctl: ${msg}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Pre-`docker commit` cleanup: strip ephemeral / disposable state so the
|
|
3
|
+
# captured checkpoint image is closer to "warm project state, nothing else".
|
|
4
|
+
#
|
|
5
|
+
# Invoked by the host via `docker exec --user root <container>
|
|
6
|
+
# /usr/local/bin/agentbox-checkpoint-cleanup` right before
|
|
7
|
+
# `docker commit`. Best-effort: every step is allowed to fail (a checkpoint
|
|
8
|
+
# capture should never block on cleanup hiccups).
|
|
9
|
+
#
|
|
10
|
+
# What we DELIBERATELY keep:
|
|
11
|
+
# - /workspace the actual point of the checkpoint
|
|
12
|
+
# - /home/vscode/.npm warm npm cache (next install is fast)
|
|
13
|
+
# - /home/vscode/.cache pnpm/yarn/Cargo/etc. caches
|
|
14
|
+
# - /var/lib/docker in-box dockerd's data root
|
|
15
|
+
# - /home/vscode/.claude the named volume is bind-mounted; image
|
|
16
|
+
# layer never sees it anyway
|
|
17
|
+
set +e
|
|
18
|
+
|
|
19
|
+
# apt: drop downloaded .deb cache and the package index. The index is ~50MB
|
|
20
|
+
# and gets refreshed on the next `apt-get update`; the .deb cache is reusable
|
|
21
|
+
# only if we don't change versions, which we usually do.
|
|
22
|
+
apt-get clean 2>/dev/null
|
|
23
|
+
rm -rf /var/lib/apt/lists/* 2>/dev/null
|
|
24
|
+
|
|
25
|
+
# Throwaway scratch dirs.
|
|
26
|
+
rm -rf /tmp/* /tmp/.[!.]* /var/tmp/* /var/tmp/.[!.]* 2>/dev/null
|
|
27
|
+
|
|
28
|
+
# Logs: truncate (don't delete) so the original file modes / ownerships stay
|
|
29
|
+
# intact for the next run. Targets common rotated archives too.
|
|
30
|
+
find /var/log -type f \( -name '*.log' -o -name '*.gz' -o -name '*.1' \) \
|
|
31
|
+
-exec truncate -s0 {} + 2>/dev/null
|
|
32
|
+
find /var/log/agentbox -type f -exec truncate -s0 {} + 2>/dev/null
|
|
33
|
+
|
|
34
|
+
# Bash history (root + vscode).
|
|
35
|
+
: > /root/.bash_history 2>/dev/null
|
|
36
|
+
: > /home/vscode/.bash_history 2>/dev/null
|
|
37
|
+
|
|
38
|
+
# Anthropic's installer writes a transient marker; redundant once the binary
|
|
39
|
+
# is in place. Safe to wipe.
|
|
40
|
+
rm -rf /home/vscode/.claude-installer 2>/dev/null
|
|
41
|
+
|
|
42
|
+
exit 0
|
|
@@ -1,21 +1,32 @@
|
|
|
1
1
|
# AgentBox sandbox
|
|
2
2
|
|
|
3
3
|
You are running inside an AgentBox sandbox: a Linux Docker container with
|
|
4
|
-
docker-in-docker (run `docker` directly, no sudo).
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
docker-in-docker (run `docker` directly, no sudo).
|
|
5
|
+
Your user is `vscode` and you can use passwordless sudo to run commands as root.
|
|
6
|
+
`/workspace` is your own per-box git worktree on branch `agentbox/<box-name>`:
|
|
7
|
+
writes there stay in the container's writable layer and don't touch the host's working
|
|
8
|
+
tree.
|
|
7
9
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
use `agentbox-ctl git pull|push -- <args>` — it RPCs to the host, which
|
|
11
|
-
runs git with the real SSH agent and gitconfig. Local commits work as
|
|
12
|
-
normal because the main `.git/` is bind-mounted at the same absolute
|
|
13
|
-
path as on the host.
|
|
10
|
+
You can save the current filesystem state to be reused by future boxes by
|
|
11
|
+
running `agentbox-ctl checkpoint --set-default`.
|
|
14
12
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
13
|
+
The main `.git/` is bind-mounted at the same absolute path as on
|
|
14
|
+
the host, so local commits show up in the host's `git log` immediately.
|
|
15
|
+
No SSH creds, no host gitconfig identity. For ops that need the user
|
|
16
|
+
(push, fetch from private remotes), use `agentbox-ctl git push|fetch|pull
|
|
17
|
+
-- <args>` — it RPCs to the host, which runs git with the real SSH agent.
|
|
20
18
|
|
|
21
|
-
|
|
19
|
+
For ad-hoc file transfers between this box and the host, use
|
|
20
|
+
`agentbox-ctl cp toHost <boxPath> <hostPath>` and
|
|
21
|
+
`agentbox-ctl cp fromHost <hostPath> <boxPath>`. They RPC to the host and
|
|
22
|
+
ask the user for confirmation on the wrapper that runs `agentbox claude`;
|
|
23
|
+
deny returns exit 10 (`denied by user`).
|
|
24
|
+
Don't put any timeout on the command, it will run forever and the user will be notified through multiple channels.
|
|
25
|
+
|
|
26
|
+
If you install a skill/plugin, change `~/.claude`, or write
|
|
27
|
+
`.env`/`.envrc`/secrets/`agentbox.yaml`, you can pull those onto the host
|
|
28
|
+
yourself with `agentbox-ctl download claude` / `download env` /
|
|
29
|
+
`download config` (also user-confirmed; additive; never overwrites host
|
|
30
|
+
files, don't put any timeout on the command).
|
|
31
|
+
|
|
32
|
+
Box identity: /etc/agentbox/box.env and the AGENTBOX_* env vars.
|