@madarco/agentbox 0.11.3 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +90 -0
- package/README.md +11 -0
- package/dist/{_cloud-attach-XWCVLO5V.js → _cloud-attach-HJC672UR.js} +3 -3
- package/dist/{chunk-ZGVMN54V.js → chunk-2LF5YILI.js} +21 -3
- package/dist/chunk-2LF5YILI.js.map +1 -0
- package/dist/{chunk-MXXXKJYS.js → chunk-4NQXNQ53.js} +234 -83
- package/dist/chunk-4NQXNQ53.js.map +1 -0
- package/dist/{chunk-ZJXTIH6C.js → chunk-B4QG2MCW.js} +1352 -851
- package/dist/chunk-B4QG2MCW.js.map +1 -0
- package/dist/{chunk-GYJ62GFL.js → chunk-QYRK5H6Q.js} +297 -33
- package/dist/chunk-QYRK5H6Q.js.map +1 -0
- package/dist/{dist-WMQDMTWS.js → dist-7KVUIKJX.js} +8 -5
- package/dist/dist-7KVUIKJX.js.map +1 -0
- package/dist/{dist-RAZP76VX.js → dist-JAN5VABY.js} +3 -3
- package/dist/{dist-ASLPRUQR.js → dist-OG6NW6SM.js} +28 -2
- package/dist/{dist-PTJ6CEQY.js → dist-OPIBZ7XM.js} +4 -4
- package/dist/index.js +1720 -876
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/runtime/docker/packages/ctl/dist/bin.cjs +341 -5
- package/runtime/docker/packages/sandbox-docker/scripts/gh-shim +86 -5
- package/runtime/hetzner/ctl.cjs +341 -5
- package/runtime/hetzner/gh-shim +86 -5
- package/runtime/relay/bin.cjs +293 -4
- package/runtime/vercel/ctl.cjs +341 -5
- package/runtime/vercel/gh-shim +86 -5
- package/share/host-skills/agentbox/SKILL.md +16 -5
- package/share/host-skills/agentbox-info/SKILL.md +3 -1
- package/dist/chunk-GYJ62GFL.js.map +0 -1
- package/dist/chunk-MXXXKJYS.js.map +0 -1
- package/dist/chunk-ZGVMN54V.js.map +0 -1
- package/dist/chunk-ZJXTIH6C.js.map +0 -1
- package/dist/dist-WMQDMTWS.js.map +0 -1
- /package/dist/{_cloud-attach-XWCVLO5V.js.map → _cloud-attach-HJC672UR.js.map} +0 -0
- /package/dist/{dist-RAZP76VX.js.map → dist-JAN5VABY.js.map} +0 -0
- /package/dist/{dist-ASLPRUQR.js.map → dist-OG6NW6SM.js.map} +0 -0
- /package/dist/{dist-PTJ6CEQY.js.map → dist-OPIBZ7XM.js.map} +0 -0
package/runtime/vercel/ctl.cjs
CHANGED
|
@@ -18509,6 +18509,35 @@ var KEY_REGISTRY = [
|
|
|
18509
18509
|
description: "Per-provider override of `box.defaultCheckpoint` for vercel. Wins over the global when set; set via `agentbox checkpoint set-default --provider vercel`.",
|
|
18510
18510
|
advanced: true
|
|
18511
18511
|
},
|
|
18512
|
+
{
|
|
18513
|
+
key: "box.size",
|
|
18514
|
+
type: "string",
|
|
18515
|
+
description: "Default VM size for cloud providers. Provider-interpreted: hetzner = server type (e.g. `cx33`); daytona = `cpu-memory-disk` GB (e.g. `4-8-20`). Used as fallback when no per-provider override is set. Docker/Vercel ignore it."
|
|
18516
|
+
},
|
|
18517
|
+
{
|
|
18518
|
+
key: "box.sizeDocker",
|
|
18519
|
+
type: "string",
|
|
18520
|
+
description: "Per-provider override of `box.size` for docker. Reserved \u2014 docker sizing is controlled via `box.memory` / `box.cpus` / `box.disk`.",
|
|
18521
|
+
advanced: true
|
|
18522
|
+
},
|
|
18523
|
+
{
|
|
18524
|
+
key: "box.sizeDaytona",
|
|
18525
|
+
type: "string",
|
|
18526
|
+
description: "Per-provider override of `box.size` for daytona. `cpu-memory-disk` GB spec (e.g. `4-8-20`). Only honored on the image/Dockerfile create path; Daytona rejects custom resources on snapshot-resume.",
|
|
18527
|
+
advanced: true
|
|
18528
|
+
},
|
|
18529
|
+
{
|
|
18530
|
+
key: "box.sizeHetzner",
|
|
18531
|
+
type: "string",
|
|
18532
|
+
description: "Per-provider override of `box.size` for hetzner. Server type string (e.g. `cx23`, `cx33`, `cx43`).",
|
|
18533
|
+
advanced: true
|
|
18534
|
+
},
|
|
18535
|
+
{
|
|
18536
|
+
key: "box.sizeVercel",
|
|
18537
|
+
type: "string",
|
|
18538
|
+
description: "Per-provider override of `box.size` for vercel. Reserved \u2014 vercel sizing is controlled via `box.vercelVcpus`.",
|
|
18539
|
+
advanced: true
|
|
18540
|
+
},
|
|
18512
18541
|
{
|
|
18513
18542
|
key: "checkpoint.maxLayers",
|
|
18514
18543
|
type: "int",
|
|
@@ -18525,6 +18554,11 @@ var KEY_REGISTRY = [
|
|
|
18525
18554
|
type: "bool",
|
|
18526
18555
|
description: "Copy host env/config files (.env*, secrets.toml, agentbox.yaml, ...) into /workspace at box create time (gitignore-bypassing)."
|
|
18527
18556
|
},
|
|
18557
|
+
{
|
|
18558
|
+
key: "box.resyncOnStart",
|
|
18559
|
+
type: "bool",
|
|
18560
|
+
description: "Merge the host's current branch into the box and overlay the host's uncommitted/untracked changes when starting an agent session (keeps the box's version on conflict and warns the agent)."
|
|
18561
|
+
},
|
|
18528
18562
|
{
|
|
18529
18563
|
key: "box.vnc",
|
|
18530
18564
|
type: "bool",
|
|
@@ -18548,7 +18582,31 @@ var KEY_REGISTRY = [
|
|
|
18548
18582
|
{
|
|
18549
18583
|
key: "box.image",
|
|
18550
18584
|
type: "string",
|
|
18551
|
-
description: "
|
|
18585
|
+
description: "Generic box image ref (fallback). Used as fallback when no per-provider override is set; the default `agentbox/box:dev` is treated as a sentinel by cloud backends (boot from their prepared base snapshot instead).",
|
|
18586
|
+
advanced: true
|
|
18587
|
+
},
|
|
18588
|
+
{
|
|
18589
|
+
key: "box.imageDocker",
|
|
18590
|
+
type: "string",
|
|
18591
|
+
description: "Per-provider override of `box.image` for docker (local docker image ref, e.g. `agentbox/box:dev`). Wins over the generic when set.",
|
|
18592
|
+
advanced: true
|
|
18593
|
+
},
|
|
18594
|
+
{
|
|
18595
|
+
key: "box.imageDaytona",
|
|
18596
|
+
type: "string",
|
|
18597
|
+
description: "Per-provider override of `box.image` for daytona (named snapshot, e.g. `agentbox-base-<fingerprint>`). Written by `agentbox prepare --provider daytona`.",
|
|
18598
|
+
advanced: true
|
|
18599
|
+
},
|
|
18600
|
+
{
|
|
18601
|
+
key: "box.imageHetzner",
|
|
18602
|
+
type: "string",
|
|
18603
|
+
description: "Per-provider override of `box.image` for hetzner (image description, e.g. `agentbox-base-<fingerprint>`). Written by `agentbox prepare --provider hetzner`.",
|
|
18604
|
+
advanced: true
|
|
18605
|
+
},
|
|
18606
|
+
{
|
|
18607
|
+
key: "box.imageVercel",
|
|
18608
|
+
type: "string",
|
|
18609
|
+
description: "Per-provider override of `box.image` for vercel (snapshot id, e.g. `snap_\u2026`). Written by `agentbox prepare --provider vercel`.",
|
|
18552
18610
|
advanced: true
|
|
18553
18611
|
},
|
|
18554
18612
|
{
|
|
@@ -18632,7 +18690,12 @@ var KEY_REGISTRY = [
|
|
|
18632
18690
|
key: "attach.openIn",
|
|
18633
18691
|
type: "enum",
|
|
18634
18692
|
enumValues: ["split", "window", "tab", "same"],
|
|
18635
|
-
description: "Where `agentbox claude|codex|opencode` opens the attached session when run from tmux or iTerm2: `split` (tmux split-window / iTerm2 vertical split, default), `window` (tmux new-window / new iTerm2 window), `tab` (tmux new-window / new iTerm2 tab), or `same` (attach inline in the current terminal). Outside tmux/iTerm2 every value behaves like `same`."
|
|
18693
|
+
description: "Where `agentbox claude|codex|opencode` opens the attached session when run from tmux, cmux, or iTerm2: `split` (tmux split-window / cmux new-split / iTerm2 vertical split, default \u2014 same workspace), `window` (tmux new-window / cmux new-workspace / new iTerm2 window), `tab` (tmux new-window / cmux new-surface tab in the current pane, same workspace / new iTerm2 tab), or `same` (attach inline in the current terminal). Outside tmux/cmux/iTerm2 every value behaves like `same`."
|
|
18694
|
+
},
|
|
18695
|
+
{
|
|
18696
|
+
key: "attach.cmuxStatus",
|
|
18697
|
+
type: "bool",
|
|
18698
|
+
description: "When attached inside cmux, reflect the box agent's live activity on its cmux workspace (colour + description: blue=working, amber=needs input, idle clears; restored on detach) and, when the agent needs input, flag the box's own tab via a cmux notification (tab badge + reorder + desktop notification) so it stands out among sibling tabs. cmux only; no-op in other terminals."
|
|
18636
18699
|
},
|
|
18637
18700
|
{
|
|
18638
18701
|
key: "code.ide",
|
|
@@ -19163,6 +19226,8 @@ var GH_PR_OPS = [
|
|
|
19163
19226
|
"create",
|
|
19164
19227
|
"view",
|
|
19165
19228
|
"list",
|
|
19229
|
+
"diff",
|
|
19230
|
+
"checks",
|
|
19166
19231
|
"comment",
|
|
19167
19232
|
"review",
|
|
19168
19233
|
"merge",
|
|
@@ -19173,7 +19238,84 @@ var GH_PR_OPS = [
|
|
|
19173
19238
|
function isGhPrOp(value) {
|
|
19174
19239
|
return GH_PR_OPS.includes(value);
|
|
19175
19240
|
}
|
|
19176
|
-
var GH_PR_READ_ONLY_OPS = /* @__PURE__ */ new Set([
|
|
19241
|
+
var GH_PR_READ_ONLY_OPS = /* @__PURE__ */ new Set([
|
|
19242
|
+
"view",
|
|
19243
|
+
"list",
|
|
19244
|
+
"diff",
|
|
19245
|
+
"checks"
|
|
19246
|
+
]);
|
|
19247
|
+
var GH_RUN_OPS = ["list", "view", "rerun"];
|
|
19248
|
+
function isGhRunOp(value) {
|
|
19249
|
+
return GH_RUN_OPS.includes(value);
|
|
19250
|
+
}
|
|
19251
|
+
var GH_RUN_READ_ONLY_OPS = /* @__PURE__ */ new Set(["list", "view"]);
|
|
19252
|
+
var PR_REVIEW_COMMENT = /^repos\/[^/]+\/[^/]+\/pulls\/\d+\/comments(\?.*)?$/;
|
|
19253
|
+
var PR_REVIEW_COMMENT_REPLY = /^repos\/[^/]+\/[^/]+\/pulls\/\d+\/comments\/\d+\/replies(\?.*)?$/;
|
|
19254
|
+
var GH_API_WRITE_ALLOWED_ENDPOINTS = [
|
|
19255
|
+
PR_REVIEW_COMMENT,
|
|
19256
|
+
PR_REVIEW_COMMENT_REPLY
|
|
19257
|
+
];
|
|
19258
|
+
var GH_API_ALLOWED_ENDPOINTS = [...GH_API_WRITE_ALLOWED_ENDPOINTS];
|
|
19259
|
+
function isAllowedGhApiEndpoint(endpoint) {
|
|
19260
|
+
return GH_API_ALLOWED_ENDPOINTS.some((re) => re.test(normalizeGhApiEndpoint(endpoint)));
|
|
19261
|
+
}
|
|
19262
|
+
function isWriteAllowedGhApiEndpoint(endpoint) {
|
|
19263
|
+
return GH_API_WRITE_ALLOWED_ENDPOINTS.some((re) => re.test(normalizeGhApiEndpoint(endpoint)));
|
|
19264
|
+
}
|
|
19265
|
+
function normalizeGhApiEndpoint(endpoint) {
|
|
19266
|
+
return endpoint.replace(/^\/+/, "");
|
|
19267
|
+
}
|
|
19268
|
+
var GH_API_ENDPOINT_REFUSAL = {
|
|
19269
|
+
exitCode: 65,
|
|
19270
|
+
stdout: "",
|
|
19271
|
+
stderr: "gh api: endpoint not allowlisted. Proxied: GET on repos/:owner/:repo/pulls/:number/comments (and /:id/replies); POST to those endpoints to add a review comment.\n"
|
|
19272
|
+
};
|
|
19273
|
+
function refuseGhApiCall(endpoint, args) {
|
|
19274
|
+
const refuse = (reason) => ({
|
|
19275
|
+
exitCode: 65,
|
|
19276
|
+
stdout: "",
|
|
19277
|
+
stderr: `gh api: ${reason}
|
|
19278
|
+
`
|
|
19279
|
+
});
|
|
19280
|
+
let explicitMethod = null;
|
|
19281
|
+
let hasFieldFlag = false;
|
|
19282
|
+
for (let i2 = 0; i2 < args.length; i2++) {
|
|
19283
|
+
const arg = args[i2] ?? "";
|
|
19284
|
+
if (arg === "-X" || arg === "--method") {
|
|
19285
|
+
explicitMethod = args[i2 + 1] ?? "";
|
|
19286
|
+
i2++;
|
|
19287
|
+
continue;
|
|
19288
|
+
}
|
|
19289
|
+
if (arg.startsWith("--method=")) {
|
|
19290
|
+
explicitMethod = arg.slice("--method=".length);
|
|
19291
|
+
continue;
|
|
19292
|
+
}
|
|
19293
|
+
if (arg.startsWith("-X") && arg.length > 2) {
|
|
19294
|
+
explicitMethod = arg.slice(2).replace(/^=/, "");
|
|
19295
|
+
continue;
|
|
19296
|
+
}
|
|
19297
|
+
if (arg === "--input" || arg.startsWith("--input=")) {
|
|
19298
|
+
return refuse("'--input' (stdin/file body) isn't supported through the relay; use -f/-F fields");
|
|
19299
|
+
}
|
|
19300
|
+
if (arg === "-f" || arg === "-F" || arg === "--field" || arg === "--raw-field") {
|
|
19301
|
+
hasFieldFlag = true;
|
|
19302
|
+
i2++;
|
|
19303
|
+
continue;
|
|
19304
|
+
}
|
|
19305
|
+
if (arg.startsWith("-f") || arg.startsWith("-F") || arg.startsWith("--field=") || arg.startsWith("--raw-field=")) {
|
|
19306
|
+
hasFieldFlag = true;
|
|
19307
|
+
}
|
|
19308
|
+
}
|
|
19309
|
+
const method = (explicitMethod ?? (hasFieldFlag ? "POST" : "GET")).toUpperCase();
|
|
19310
|
+
if (method === "GET") return null;
|
|
19311
|
+
if (method === "POST") {
|
|
19312
|
+
if (isWriteAllowedGhApiEndpoint(endpoint)) return null;
|
|
19313
|
+
return refuse(
|
|
19314
|
+
`POST is only proxied to PR review-comment endpoints (repos/:o/:r/pulls/:n/comments[/:id/replies]), not '${endpoint}'`
|
|
19315
|
+
);
|
|
19316
|
+
}
|
|
19317
|
+
return refuse(`method '${method}' is not proxied \u2014 only GET, and POST to comment endpoints, are allowed`);
|
|
19318
|
+
}
|
|
19177
19319
|
function injectPrCreateHead(op, branch, args) {
|
|
19178
19320
|
if (op !== "create") return args;
|
|
19179
19321
|
if (!branch || branch === "HEAD") return args;
|
|
@@ -19451,6 +19593,12 @@ async function executeCloudAction(action, deps) {
|
|
|
19451
19593
|
if (action.method.startsWith("gh.pr.")) {
|
|
19452
19594
|
return runGhPrRpc(action, deps);
|
|
19453
19595
|
}
|
|
19596
|
+
if (action.method.startsWith("gh.run.")) {
|
|
19597
|
+
return runGhRunRpc(action, deps);
|
|
19598
|
+
}
|
|
19599
|
+
if (action.method === "gh.api") {
|
|
19600
|
+
return runGhApiRpc(action, deps);
|
|
19601
|
+
}
|
|
19454
19602
|
if (action.method === "git.clone" || action.method === "gh.repo.clone") {
|
|
19455
19603
|
return {
|
|
19456
19604
|
exitCode: 64,
|
|
@@ -19559,6 +19707,66 @@ async function runGhPrRpc(action, deps) {
|
|
|
19559
19707
|
if (prCreateNeedsHead(op, finalArgs)) return PR_CREATE_NO_HEAD_REFUSAL;
|
|
19560
19708
|
return runHostGh(["pr", op, ...finalArgs], lookup.workspacePath);
|
|
19561
19709
|
}
|
|
19710
|
+
async function cloudWriteConfirm(deps, command, cwd, args) {
|
|
19711
|
+
if (!deps.prompts || !deps.subscribers) return null;
|
|
19712
|
+
const ctx = {
|
|
19713
|
+
kind: "confirm",
|
|
19714
|
+
message: `Allow ${command} from cloud box ${deps.boxName ?? deps.boxId}?`,
|
|
19715
|
+
detail: args.join(" ").slice(0, 200),
|
|
19716
|
+
defaultAnswer: "n",
|
|
19717
|
+
context: { command, cwd, argv: args }
|
|
19718
|
+
};
|
|
19719
|
+
const hasSubscriber = deps.subscribers.forBox(deps.boxId).length > 0;
|
|
19720
|
+
if (!hasSubscriber && process.env["AGENTBOX_PROMPT"] !== "off") {
|
|
19721
|
+
const noSubMode = (process.env["AGENTBOX_GH_NO_SUB"] ?? "deny").toLowerCase();
|
|
19722
|
+
if (noSubMode === "deny") {
|
|
19723
|
+
return {
|
|
19724
|
+
exitCode: 10,
|
|
19725
|
+
stdout: "",
|
|
19726
|
+
stderr: "denied automatically \u2014 no attached wrapper to confirm. Attach `agentbox claude` (or similar) and retry, or set AGENTBOX_GH_NO_SUB=allow.\n"
|
|
19727
|
+
};
|
|
19728
|
+
}
|
|
19729
|
+
if (noSubMode === "allow") {
|
|
19730
|
+
deps.log?.(`${command} auto-approved (no subscribers, AGENTBOX_GH_NO_SUB=allow)`);
|
|
19731
|
+
return null;
|
|
19732
|
+
}
|
|
19733
|
+
const verdict2 = await askPrompt(deps.prompts, deps.subscribers, deps.boxId, ctx, {
|
|
19734
|
+
ttlMs: 5 * 60 * 1e3
|
|
19735
|
+
});
|
|
19736
|
+
return verdict2.answer === "y" ? null : { exitCode: 10, stdout: "", stderr: "denied by user\n" };
|
|
19737
|
+
}
|
|
19738
|
+
const verdict = await askPrompt(deps.prompts, deps.subscribers, deps.boxId, ctx);
|
|
19739
|
+
return verdict.answer === "y" ? null : { exitCode: 10, stdout: "", stderr: "denied by user\n" };
|
|
19740
|
+
}
|
|
19741
|
+
async function runGhRunRpc(action, deps) {
|
|
19742
|
+
const op = action.method.slice("gh.run.".length);
|
|
19743
|
+
if (!isGhRunOp(op)) {
|
|
19744
|
+
return { exitCode: 64, stdout: "", stderr: `unknown gh.run.* op: ${op}
|
|
19745
|
+
` };
|
|
19746
|
+
}
|
|
19747
|
+
const params = action.params ?? {};
|
|
19748
|
+
const args = Array.isArray(params.args) ? params.args.filter((a2) => typeof a2 === "string") : [];
|
|
19749
|
+
const ghReady = await assertGhReady();
|
|
19750
|
+
if (ghReady) return ghReady;
|
|
19751
|
+
const lookup = await lookupCloudBox(deps.boxId);
|
|
19752
|
+
if (!GH_RUN_READ_ONLY_OPS.has(op)) {
|
|
19753
|
+
const denied = await cloudWriteConfirm(deps, `gh run ${op}`, params.path, args);
|
|
19754
|
+
if (denied) return denied;
|
|
19755
|
+
}
|
|
19756
|
+
return runHostGh(["run", op, ...args], lookup.workspacePath);
|
|
19757
|
+
}
|
|
19758
|
+
async function runGhApiRpc(action, deps) {
|
|
19759
|
+
const params = action.params ?? {};
|
|
19760
|
+
const endpoint = typeof params.endpoint === "string" ? params.endpoint : "";
|
|
19761
|
+
if (!isAllowedGhApiEndpoint(endpoint)) return GH_API_ENDPOINT_REFUSAL;
|
|
19762
|
+
const args = Array.isArray(params.args) ? params.args.filter((a2) => typeof a2 === "string") : [];
|
|
19763
|
+
const callRefusal = refuseGhApiCall(endpoint, args);
|
|
19764
|
+
if (callRefusal) return callRefusal;
|
|
19765
|
+
const ghReady = await assertGhReady();
|
|
19766
|
+
if (ghReady) return ghReady;
|
|
19767
|
+
const lookup = await lookupCloudBox(deps.boxId);
|
|
19768
|
+
return runHostGh(["api", endpoint, ...args], lookup.workspacePath);
|
|
19769
|
+
}
|
|
19562
19770
|
async function runBrowserOpenMirror(action, deps) {
|
|
19563
19771
|
const params = action.params ?? {};
|
|
19564
19772
|
const url = typeof params.url === "string" ? params.url.trim() : "";
|
|
@@ -20235,6 +20443,29 @@ function createRelayServer(opts) {
|
|
|
20235
20443
|
send(res, status2, result);
|
|
20236
20444
|
return;
|
|
20237
20445
|
}
|
|
20446
|
+
if (body.method.startsWith("gh.run.")) {
|
|
20447
|
+
const op = body.method.slice("gh.run.".length);
|
|
20448
|
+
if (!isGhRunOp(op)) {
|
|
20449
|
+
send(res, 400, { error: `unknown gh.run.* op: ${op}` });
|
|
20450
|
+
return;
|
|
20451
|
+
}
|
|
20452
|
+
const result = await handleGhRunRpc(
|
|
20453
|
+
op,
|
|
20454
|
+
reg,
|
|
20455
|
+
body.params,
|
|
20456
|
+
prompts,
|
|
20457
|
+
subscribers
|
|
20458
|
+
);
|
|
20459
|
+
const status2 = result.exitCode === 0 ? 200 : 500;
|
|
20460
|
+
send(res, status2, result);
|
|
20461
|
+
return;
|
|
20462
|
+
}
|
|
20463
|
+
if (body.method === "gh.api") {
|
|
20464
|
+
const result = await handleGhApiRpc(reg, body.params);
|
|
20465
|
+
const status2 = result.exitCode === 0 ? 200 : 500;
|
|
20466
|
+
send(res, status2, result);
|
|
20467
|
+
return;
|
|
20468
|
+
}
|
|
20238
20469
|
if (body.method === "git.clone" || body.method === "gh.repo.clone") {
|
|
20239
20470
|
send(res, 501, {
|
|
20240
20471
|
exitCode: 64,
|
|
@@ -20712,6 +20943,53 @@ async function handleGhPrRpc(op, reg, params, prompts, subscribers, hostInitiate
|
|
|
20712
20943
|
if (prCreateNeedsHead(op, finalArgs)) return PR_CREATE_NO_HEAD_REFUSAL;
|
|
20713
20944
|
return runHostGh(["pr", op, ...finalArgs], worktree.hostMainRepo);
|
|
20714
20945
|
}
|
|
20946
|
+
async function handleGhRunRpc(op, reg, params, prompts, subscribers) {
|
|
20947
|
+
const containerPath = params?.path ?? "/workspace";
|
|
20948
|
+
const worktree = resolveWorktree(reg, containerPath);
|
|
20949
|
+
if (!worktree) {
|
|
20950
|
+
return {
|
|
20951
|
+
exitCode: 64,
|
|
20952
|
+
stdout: "",
|
|
20953
|
+
stderr: `no worktree registered for box ${reg.boxId} matching ${containerPath}`
|
|
20954
|
+
};
|
|
20955
|
+
}
|
|
20956
|
+
const ghReady = await assertGhReady();
|
|
20957
|
+
if (ghReady) return ghReady;
|
|
20958
|
+
const args = Array.isArray(params?.args) ? params.args.filter((a2) => typeof a2 === "string") : [];
|
|
20959
|
+
if (!GH_RUN_READ_ONLY_OPS.has(op)) {
|
|
20960
|
+
const detail = args.join(" ").slice(0, 200);
|
|
20961
|
+
const verdict = await askPrompt(prompts, subscribers, reg.boxId, {
|
|
20962
|
+
kind: "confirm",
|
|
20963
|
+
message: `Allow gh run ${op} from box ${reg.name}?`,
|
|
20964
|
+
detail,
|
|
20965
|
+
defaultAnswer: "n",
|
|
20966
|
+
context: { command: `gh run ${op}`, cwd: containerPath, argv: args }
|
|
20967
|
+
});
|
|
20968
|
+
if (verdict.answer !== "y") {
|
|
20969
|
+
return { exitCode: 10, stdout: "", stderr: "denied by user\n" };
|
|
20970
|
+
}
|
|
20971
|
+
}
|
|
20972
|
+
return runHostGh(["run", op, ...args], worktree.hostMainRepo);
|
|
20973
|
+
}
|
|
20974
|
+
async function handleGhApiRpc(reg, params) {
|
|
20975
|
+
const containerPath = params?.path ?? "/workspace";
|
|
20976
|
+
const worktree = resolveWorktree(reg, containerPath);
|
|
20977
|
+
if (!worktree) {
|
|
20978
|
+
return {
|
|
20979
|
+
exitCode: 64,
|
|
20980
|
+
stdout: "",
|
|
20981
|
+
stderr: `no worktree registered for box ${reg.boxId} matching ${containerPath}`
|
|
20982
|
+
};
|
|
20983
|
+
}
|
|
20984
|
+
const endpoint = typeof params?.endpoint === "string" ? params.endpoint : "";
|
|
20985
|
+
if (!isAllowedGhApiEndpoint(endpoint)) return GH_API_ENDPOINT_REFUSAL;
|
|
20986
|
+
const args = Array.isArray(params?.args) ? params.args.filter((a2) => typeof a2 === "string") : [];
|
|
20987
|
+
const callRefusal = refuseGhApiCall(endpoint, args);
|
|
20988
|
+
if (callRefusal) return callRefusal;
|
|
20989
|
+
const ghReady = await assertGhReady();
|
|
20990
|
+
if (ghReady) return ghReady;
|
|
20991
|
+
return runHostGh(["api", endpoint, ...args], worktree.hostMainRepo);
|
|
20992
|
+
}
|
|
20715
20993
|
async function handleCpRpc(reg, method, params) {
|
|
20716
20994
|
const entry = process.env.AGENTBOX_CLI_ENTRY;
|
|
20717
20995
|
if (!entry) {
|
|
@@ -23083,6 +23361,8 @@ var PR_SUBCOMMANDS = [
|
|
|
23083
23361
|
},
|
|
23084
23362
|
{ op: "view", description: "Run `gh pr view` on the host (read-only; no prompt)." },
|
|
23085
23363
|
{ op: "list", description: "Run `gh pr list` on the host (read-only; no prompt)." },
|
|
23364
|
+
{ op: "diff", description: "Run `gh pr diff` on the host (read-only; no prompt)." },
|
|
23365
|
+
{ op: "checks", description: "Run `gh pr checks` on the host (read-only; no prompt)." },
|
|
23086
23366
|
{ op: "comment", description: "Run `gh pr comment` on the host (prompted; visible to others)." },
|
|
23087
23367
|
{ op: "review", description: "Run `gh pr review` on the host (prompted; visible to others)." },
|
|
23088
23368
|
{
|
|
@@ -23144,7 +23424,42 @@ var repoCommand = new Command("repo").description("GitHub repo operations via th
|
|
|
23144
23424
|
}
|
|
23145
23425
|
)
|
|
23146
23426
|
);
|
|
23147
|
-
var
|
|
23427
|
+
var RUN_SUBCOMMANDS = [
|
|
23428
|
+
{ op: "list", description: "Run `gh run list` on the host (read-only; no prompt)." },
|
|
23429
|
+
{ op: "view", description: "Run `gh run view` on the host (read-only; no prompt)." },
|
|
23430
|
+
{
|
|
23431
|
+
op: "rerun",
|
|
23432
|
+
description: "Run `gh run rerun` on the host (prompted; re-triggers CI)."
|
|
23433
|
+
}
|
|
23434
|
+
];
|
|
23435
|
+
function buildRunCommand(errorPrefix) {
|
|
23436
|
+
const runCommand = new Command("run").description(
|
|
23437
|
+
"GitHub Actions run operations via the host `gh` CLI (requires `gh` installed and `gh auth login` on the host)"
|
|
23438
|
+
);
|
|
23439
|
+
for (const spec of RUN_SUBCOMMANDS) {
|
|
23440
|
+
runCommand.addCommand(
|
|
23441
|
+
new Command(spec.op).description(spec.description).option("--cwd <path>", "container path identifying which registered worktree to use").allowExcessArguments(true).allowUnknownOption(true).argument(
|
|
23442
|
+
"[args...]",
|
|
23443
|
+
"extra flags forwarded to `gh run <op>` verbatim (e.g. `--json`, `--limit`, `<run-id>`)."
|
|
23444
|
+
).action(async (args, opts) => {
|
|
23445
|
+
const params = { path: opts.cwd ?? process.cwd() };
|
|
23446
|
+
if (args.length > 0) params.args = args;
|
|
23447
|
+
const code = await postRpcAndExit(`gh.run.${spec.op}`, params, { errorPrefix });
|
|
23448
|
+
process.exit(code);
|
|
23449
|
+
})
|
|
23450
|
+
);
|
|
23451
|
+
}
|
|
23452
|
+
return runCommand;
|
|
23453
|
+
}
|
|
23454
|
+
var apiCommand = new Command("api").description(
|
|
23455
|
+
"Allowlisted `gh api` (host runs `gh api`): GET on proxied endpoints, plus POST to add a PR review comment. Other methods are rejected."
|
|
23456
|
+
).option("--cwd <path>", "container path identifying which registered worktree to use").allowExcessArguments(true).allowUnknownOption(true).argument("<endpoint>", "REST endpoint, e.g. repos/:owner/:repo/pulls/:number/comments").argument("[args...]", "extra flags forwarded to `gh api` verbatim (e.g. `--jq`, `-f body=\u2026`).").action(async (endpoint, args, opts) => {
|
|
23457
|
+
const params = { path: opts.cwd ?? process.cwd(), endpoint };
|
|
23458
|
+
if (args.length > 0) params.args = args;
|
|
23459
|
+
const code = await postRpcAndExit("gh.api", params, { errorPrefix: "agentbox-ctl gh api" });
|
|
23460
|
+
process.exit(code);
|
|
23461
|
+
});
|
|
23462
|
+
var ghCommand = new Command("gh").description("GitHub CLI operations routed through the relay (host `gh` runs with host creds; box never sees a token)").addCommand(buildPrCommand("agentbox-ctl gh pr")).addCommand(buildRunCommand("agentbox-ctl gh run")).addCommand(apiCommand).addCommand(repoCommand);
|
|
23148
23463
|
|
|
23149
23464
|
// src/commands/git.ts
|
|
23150
23465
|
var import_node_child_process11 = require("child_process");
|
|
@@ -23172,6 +23487,18 @@ function runLocalGit(args, cwd) {
|
|
|
23172
23487
|
});
|
|
23173
23488
|
});
|
|
23174
23489
|
}
|
|
23490
|
+
function hasGitIdentity(cwd) {
|
|
23491
|
+
return new Promise((resolve2) => {
|
|
23492
|
+
const child = (0, import_node_child_process11.spawn)("git", ["config", "user.email"], {
|
|
23493
|
+
cwd,
|
|
23494
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
23495
|
+
});
|
|
23496
|
+
let out = "";
|
|
23497
|
+
child.stdout.on("data", (c3) => out += c3.toString("utf8"));
|
|
23498
|
+
child.on("close", (code) => resolve2(code === 0 && out.trim().length > 0));
|
|
23499
|
+
child.on("error", () => resolve2(false));
|
|
23500
|
+
});
|
|
23501
|
+
}
|
|
23175
23502
|
var gitCommand = new Command("git").description("Git operations that need host credentials (routed through the agentbox relay)").addCommand(
|
|
23176
23503
|
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").addOption(hostInitiatedOption()).allowExcessArguments(true).allowUnknownOption(true).argument(
|
|
23177
23504
|
"[args...]",
|
|
@@ -23206,7 +23533,16 @@ var gitCommand = new Command("git").description("Git operations that need host c
|
|
|
23206
23533
|
if (fetchCode !== 0) process.exit(fetchCode);
|
|
23207
23534
|
const remote = opts.remote ?? "origin";
|
|
23208
23535
|
const cwd = opts.cwd ?? process.cwd();
|
|
23209
|
-
const mergeArgs = [
|
|
23536
|
+
const mergeArgs = [];
|
|
23537
|
+
if (!await hasGitIdentity(cwd)) {
|
|
23538
|
+
mergeArgs.push(
|
|
23539
|
+
"-c",
|
|
23540
|
+
"user.name=agentbox",
|
|
23541
|
+
"-c",
|
|
23542
|
+
"user.email=agentbox@users.noreply.github.com"
|
|
23543
|
+
);
|
|
23544
|
+
}
|
|
23545
|
+
mergeArgs.push("merge");
|
|
23210
23546
|
if (opts.ffOnly) mergeArgs.push("--ff-only");
|
|
23211
23547
|
mergeArgs.push(`${remote}/HEAD`);
|
|
23212
23548
|
const mergeCode = await runLocalGit(mergeArgs, cwd);
|
package/runtime/vercel/gh-shim
CHANGED
|
@@ -89,7 +89,7 @@ first_positional() {
|
|
|
89
89
|
handle_pr() {
|
|
90
90
|
local op="${1-}"; shift || true
|
|
91
91
|
if [ -z "$op" ]; then
|
|
92
|
-
die "missing subcommand for 'gh pr'. Supported: view, list, create, comment, review, merge, checkout, close, reopen"
|
|
92
|
+
die "missing subcommand for 'gh pr'. Supported: view, list, diff, checks, create, comment, review, merge, checkout, close, reopen"
|
|
93
93
|
fi
|
|
94
94
|
local branch
|
|
95
95
|
branch="$(box_branch)"
|
|
@@ -109,6 +109,20 @@ handle_pr() {
|
|
|
109
109
|
fi
|
|
110
110
|
exec "$CTL" gh pr list -- "$@"
|
|
111
111
|
;;
|
|
112
|
+
diff)
|
|
113
|
+
strict_flags "gh pr diff" "--color|--name-only|--patch" "$@"
|
|
114
|
+
if [ -z "$(first_positional "--color" "$@")" ] && [ -n "$branch" ]; then
|
|
115
|
+
set -- "$branch" "$@"
|
|
116
|
+
fi
|
|
117
|
+
exec "$CTL" gh pr diff -- "$@"
|
|
118
|
+
;;
|
|
119
|
+
checks)
|
|
120
|
+
strict_flags "gh pr checks" "--json|--required" "$@"
|
|
121
|
+
if [ -z "$(first_positional "--json" "$@")" ] && [ -n "$branch" ]; then
|
|
122
|
+
set -- "$branch" "$@"
|
|
123
|
+
fi
|
|
124
|
+
exec "$CTL" gh pr checks -- "$@"
|
|
125
|
+
;;
|
|
112
126
|
create)
|
|
113
127
|
strict_flags "gh pr create" "--fill|--draft|--title|--body|--base" "$@"
|
|
114
128
|
if ! needle_present "--head" "$@" && [ -n "$branch" ]; then
|
|
@@ -162,11 +176,70 @@ handle_pr() {
|
|
|
162
176
|
exec "$CTL" gh pr checkout -- "$@"
|
|
163
177
|
;;
|
|
164
178
|
*)
|
|
165
|
-
die "'gh pr $op' is not proxied (supported: view, list, create, comment, review, merge, checkout, close, reopen)"
|
|
179
|
+
die "'gh pr $op' is not proxied (supported: view, list, diff, checks, create, comment, review, merge, checkout, close, reopen)"
|
|
166
180
|
;;
|
|
167
181
|
esac
|
|
168
182
|
}
|
|
169
183
|
|
|
184
|
+
handle_run() {
|
|
185
|
+
local op="${1-}"; shift || true
|
|
186
|
+
if [ -z "$op" ]; then
|
|
187
|
+
die "missing subcommand for 'gh run'. Supported: list, view, rerun"
|
|
188
|
+
fi
|
|
189
|
+
case "$op" in
|
|
190
|
+
list)
|
|
191
|
+
strict_flags "gh run list" "--json|--limit|-L|--workflow|-w|--branch|-b|--status|--user" "$@"
|
|
192
|
+
exec "$CTL" gh run list -- "$@"
|
|
193
|
+
;;
|
|
194
|
+
view)
|
|
195
|
+
strict_flags "gh run view" "--json|--log|--log-failed|--job" "$@"
|
|
196
|
+
# gh run view needs a run-id OR a --job <id>; refuse when neither is
|
|
197
|
+
# present (host-side gh would otherwise try an interactive picker and
|
|
198
|
+
# fail without a TTY).
|
|
199
|
+
if [ -z "$(first_positional "--json|--job" "$@")" ] && ! needle_present "--job" "$@"; then
|
|
200
|
+
die "'gh run view' requires a positional <run-id> (or --job <job-id>)"
|
|
201
|
+
fi
|
|
202
|
+
exec "$CTL" gh run view -- "$@"
|
|
203
|
+
;;
|
|
204
|
+
rerun)
|
|
205
|
+
strict_flags "gh run rerun" "--failed|--job" "$@"
|
|
206
|
+
if [ -z "$(first_positional "--job" "$@")" ] && ! needle_present "--job" "$@"; then
|
|
207
|
+
die "'gh run rerun' requires a positional <run-id> (or --job <job-id>)"
|
|
208
|
+
fi
|
|
209
|
+
exec "$CTL" gh run rerun -- "$@"
|
|
210
|
+
;;
|
|
211
|
+
watch)
|
|
212
|
+
die "'gh run watch' is not proxied (it blocks until CI finishes). Poll status with 'gh run view <run-id>' instead."
|
|
213
|
+
;;
|
|
214
|
+
*)
|
|
215
|
+
die "'gh run $op' is not proxied (supported: list, view, rerun)"
|
|
216
|
+
;;
|
|
217
|
+
esac
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
handle_api() {
|
|
221
|
+
local endpoint="${1-}"
|
|
222
|
+
if [ -z "$endpoint" ] || [ "${endpoint:0:1}" = "-" ]; then
|
|
223
|
+
die "'gh api' requires a positional <endpoint> (e.g. repos/:owner/:repo/pulls/:number/comments)"
|
|
224
|
+
fi
|
|
225
|
+
shift
|
|
226
|
+
# GET reads are proxied for any allowlisted endpoint; POST is proxied only to
|
|
227
|
+
# the PR review-comment endpoints (the relay enforces the endpoint + method
|
|
228
|
+
# policy). `--input` (stdin/file body) can't cross the relay — the host `gh`
|
|
229
|
+
# runs with stdin ignored — so reject it locally and point at -f/-F fields.
|
|
230
|
+
local arg
|
|
231
|
+
for arg in "$@"; do
|
|
232
|
+
case "$arg" in
|
|
233
|
+
--input|--input=*)
|
|
234
|
+
die "'gh api --input' (stdin/file body) isn't supported through the relay; use -f/-F fields"
|
|
235
|
+
;;
|
|
236
|
+
esac
|
|
237
|
+
done
|
|
238
|
+
strict_flags "gh api" \
|
|
239
|
+
"--method|-X|-f|-F|--field|--raw-field|--jq|-q|--paginate|--cache|--hostname|-H|--header" "$@"
|
|
240
|
+
exec "$CTL" gh api "$endpoint" -- "$@"
|
|
241
|
+
}
|
|
242
|
+
|
|
170
243
|
handle_repo() {
|
|
171
244
|
local op="${1-}"; shift || true
|
|
172
245
|
if [ "$op" != "clone" ]; then
|
|
@@ -218,7 +291,7 @@ handle_repo() {
|
|
|
218
291
|
|
|
219
292
|
# Top-level dispatch.
|
|
220
293
|
if [ $# -eq 0 ]; then
|
|
221
|
-
die "no subcommand. Supported: pr {view,list,create,comment,review,merge,checkout,close,reopen}, repo clone, auth status, --version"
|
|
294
|
+
die "no subcommand. Supported: pr {view,list,diff,checks,create,comment,review,merge,checkout,close,reopen}, run {list,view,rerun}, api <endpoint>, repo clone, auth status, --version"
|
|
222
295
|
fi
|
|
223
296
|
|
|
224
297
|
case "$1" in
|
|
@@ -232,7 +305,7 @@ case "$1" in
|
|
|
232
305
|
;;
|
|
233
306
|
--help|-h)
|
|
234
307
|
printf 'agentbox gh shim — strict subset.\n' >&2
|
|
235
|
-
printf 'Supported: pr {view,list,create,comment,review,merge,checkout,close,reopen}, repo clone, auth status, --version\n' >&2
|
|
308
|
+
printf 'Supported: pr {view,list,diff,checks,create,comment,review,merge,checkout,close,reopen}, run {list,view,rerun}, api <endpoint>, repo clone, auth status, --version\n' >&2
|
|
236
309
|
printf 'Anything else is rejected. Run host `gh --help` for full upstream docs.\n' >&2
|
|
237
310
|
;;
|
|
238
311
|
auth)
|
|
@@ -253,11 +326,19 @@ case "$1" in
|
|
|
253
326
|
shift
|
|
254
327
|
handle_pr "$@"
|
|
255
328
|
;;
|
|
329
|
+
run)
|
|
330
|
+
shift
|
|
331
|
+
handle_run "$@"
|
|
332
|
+
;;
|
|
333
|
+
api)
|
|
334
|
+
shift
|
|
335
|
+
handle_api "$@"
|
|
336
|
+
;;
|
|
256
337
|
repo)
|
|
257
338
|
shift
|
|
258
339
|
handle_repo "$@"
|
|
259
340
|
;;
|
|
260
341
|
*)
|
|
261
|
-
die "'gh $1' is not proxied (supported: pr {…}, repo clone, auth status, --version)"
|
|
342
|
+
die "'gh $1' is not proxied (supported: pr {…}, run {…}, api <endpoint>, repo clone, auth status, --version)"
|
|
262
343
|
;;
|
|
263
344
|
esac
|
|
@@ -12,16 +12,27 @@ Fork the current Claude Code session into a fresh AgentBox box.
|
|
|
12
12
|
|
|
13
13
|
1. **Resolve the provider flag from `$ARGUMENTS`:**
|
|
14
14
|
- empty → no flag (uses the default docker provider)
|
|
15
|
-
- `docker` | `daytona` | `hetzner` → pass `--provider $ARGUMENTS`
|
|
16
|
-
- anything else → stop and tell the user the valid values are `docker`, `daytona`, `hetzner`
|
|
15
|
+
- `docker` | `daytona` | `hetzner` | `vercel` → pass `--provider $ARGUMENTS`
|
|
16
|
+
- anything else → stop and tell the user the valid values are `docker`, `daytona`, `hetzner`, `vercel`
|
|
17
17
|
|
|
18
|
-
2. **
|
|
18
|
+
2. **Detect an active plan (optional `--plan`).** If this session was just working on a Claude Code plan, carry it into the box so the fork resumes in plan mode.
|
|
19
|
+
- **If you know the plan file path** for this session (plan mode writes it to `~/.claude/plans/<slug>.md`, and you have it from the plan you just produced in this conversation), use that path.
|
|
20
|
+
- **Otherwise**, find the most recent plan only if it was touched in the last 15 minutes (a stale plan from another task should not be resurrected). Run via the Bash tool:
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
find "$HOME/.claude/plans" -name '*.md' -mmin -15 -type f 2>/dev/null \
|
|
24
|
+
| xargs -r ls -t 2>/dev/null | head -1
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
If it prints a path, that's the current plan; if it prints nothing, there is no active plan — skip `--plan`.
|
|
28
|
+
|
|
29
|
+
3. **Fork.** If you are in plan mode, exit it, then run, via the Bash tool, exactly one command (add `--plan "<path>"` only if step 2 found a plan):
|
|
19
30
|
|
|
20
31
|
```
|
|
21
|
-
agentbox fork --session ${CLAUDE_SESSION_ID} [--provider $ARGUMENTS]
|
|
32
|
+
agentbox fork --session ${CLAUDE_SESSION_ID} [--provider $ARGUMENTS] [--plan "<plan path>"]
|
|
22
33
|
```
|
|
23
34
|
|
|
24
|
-
|
|
35
|
+
4. **Report.** In one line, give the user the new box name (parse it from the command output) and confirm their host session is unaffected. If you passed `--plan`, mention the box opens in plan mode ready to resume. Do not summarize the conversation — the fork already carries it.
|
|
25
36
|
|
|
26
37
|
## Troubleshooting
|
|
27
38
|
|
|
@@ -68,9 +68,11 @@ agentbox claude attach <name|n> # reattach to a specific box
|
|
|
68
68
|
|
|
69
69
|
`-i` works on every provider — pass `--provider daytona|hetzner|vercel` (or set `box.provider`) and the queued job creates a cloud box and pre-starts the seeded agent session detached, same as docker. The host must have valid agent credentials. Extra args after `--` are forwarded to the in-box agent (e.g. `agentbox claude -i "<prompt>" --provider vercel -- --permission-mode=plan`).
|
|
70
70
|
|
|
71
|
+
`-i` honors the project's `carry:` block: the carry gate runs on the host when you submit (it prompts there, since you're at the terminal), and the approved files ride the queued job and land in the box at create time. Auto-approve non-interactively with `--carry-yes` (or `AGENTBOX_CARRY_YES=1`); skip with `--carry skip` (or `AGENTBOX_CARRY=skip`).
|
|
72
|
+
|
|
71
73
|
## Forking the current session into a box
|
|
72
74
|
|
|
73
|
-
From host Claude, run the **`/agentbox`** slash command (optional arg: `docker` | `daytona` | `hetzner`) to snapshot the *current* Claude Code session into a brand-new box that resumes it. With tmux or iTerm it opens in a new terminal tab; otherwise it starts in the background. The host session is unaffected — you get two parallel timelines. The underlying CLI is `agentbox fork` (`agentbox fork --help`); `/agentbox` requires `agentbox install` to have been run once. This is distinct from `-i`, which seeds a *new* prompt rather than resuming the live conversation.
|
|
75
|
+
From host Claude, run the **`/agentbox`** slash command (optional arg: `docker` | `daytona` | `hetzner`) to snapshot the *current* Claude Code session into a brand-new box that resumes it. With tmux or iTerm it opens in a new terminal tab; otherwise it starts in the background. The host session is unaffected — you get two parallel timelines. The underlying CLI is `agentbox fork` (`agentbox fork --help`); `/agentbox` requires `agentbox install` to have been run once. This is distinct from `-i`, which seeds a *new* prompt rather than resuming the live conversation. Fork **sends** the project's `carry:` block by default (the host is trusted; the box is the untrusted side, so host→box copy is safe) — opt out with `agentbox fork --carry skip`.
|
|
74
76
|
|
|
75
77
|
## Driving one agent from another (`drive`, `agent`, `queue wait-for`)
|
|
76
78
|
|