@madarco/agentbox 0.7.0 → 0.9.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/_cloud-attach-ZXBCNWJX.js +13 -0
- package/dist/{chunk-NW5NYTQM.js → chunk-BXQMIEHC.js} +459 -110
- package/dist/chunk-BXQMIEHC.js.map +1 -0
- package/dist/{chunk-UK72UQ5U.js → chunk-G3H2L3O2.js} +55 -4
- package/dist/chunk-G3H2L3O2.js.map +1 -0
- package/dist/{chunk-7KOEFGN2.js → chunk-GU5LW4B5.js} +385 -31
- package/dist/chunk-GU5LW4B5.js.map +1 -0
- package/dist/chunk-KL36BRN4.js +455 -0
- package/dist/chunk-KL36BRN4.js.map +1 -0
- package/dist/{chunk-V5KZGB5V.js → chunk-LEV3KICD.js} +18 -2
- package/dist/chunk-LEV3KICD.js.map +1 -0
- package/dist/chunk-MTVI44DW.js +662 -0
- package/dist/chunk-MTVI44DW.js.map +1 -0
- package/dist/{chunk-NAVL4R34.js → chunk-NCJP5MTN.js} +1281 -556
- package/dist/chunk-NCJP5MTN.js.map +1 -0
- package/dist/{cloud-poller-ZIWSADJB-JXFRJUEM.js → cloud-poller-SUNA6ZQC-2RG5WPRN.js} +2 -2
- package/dist/{dist-ETCFRVPA.js → dist-32EZBYG4.js} +50 -20
- package/dist/{dist-R67WMLCF.js → dist-CX5CGVEB.js} +120 -10
- package/dist/dist-CX5CGVEB.js.map +1 -0
- package/dist/{dist-QZGJIBT5.js → dist-GDHP34ZK.js} +141 -75
- package/dist/dist-GDHP34ZK.js.map +1 -0
- package/dist/dist-XML54CNB.js +849 -0
- package/dist/dist-XML54CNB.js.map +1 -0
- package/dist/index.js +3881 -867
- package/dist/index.js.map +1 -1
- package/dist/prepared-state-CL4CWXQA-H5THETIM.js +18 -0
- package/dist/prepared-state-CL4CWXQA-H5THETIM.js.map +1 -0
- package/package.json +7 -5
- package/runtime/daytona/custom-system-CLAUDE.md +39 -0
- package/runtime/docker/Dockerfile.box +22 -0
- package/runtime/docker/apps/cli/share/agentbox-setup/SKILL.md +1 -1
- package/runtime/docker/packages/ctl/dist/bin.cjs +1214 -98
- package/runtime/docker/packages/sandbox-docker/scripts/agentbox-codex-hooks.json +66 -35
- package/runtime/docker/packages/sandbox-docker/scripts/agentbox-vnc-start +15 -1
- package/runtime/docker/packages/sandbox-docker/scripts/claude-managed-settings.json +62 -1
- package/runtime/docker/packages/sandbox-docker/scripts/custom-system-CLAUDE.md +15 -4
- package/runtime/docker/packages/sandbox-docker/scripts/gh-shim +263 -0
- package/runtime/docker/packages/sandbox-docker/scripts/git-shim +131 -0
- package/runtime/docker/packages/sandbox-docker/scripts/opencode-agentbox-plugin.js +76 -0
- package/runtime/hetzner/agentbox-codex-hooks.json +66 -35
- package/runtime/hetzner/agentbox-setup-skill.md +1 -1
- package/runtime/hetzner/agentbox-vnc-start +15 -1
- package/runtime/hetzner/claude-managed-settings.json +62 -1
- package/runtime/hetzner/ctl.cjs +1214 -98
- package/runtime/hetzner/custom-system-CLAUDE.md +26 -14
- package/runtime/hetzner/gh-shim +263 -0
- package/runtime/hetzner/git-shim +131 -0
- package/runtime/hetzner/opencode-agentbox-plugin.js +76 -0
- package/runtime/hetzner/scripts/install-box.sh +11 -2
- package/runtime/relay/bin.cjs +1146 -63
- package/runtime/vercel/agentbox-checkpoint-cleanup +52 -0
- package/runtime/vercel/agentbox-codex-hooks.json +68 -0
- package/runtime/vercel/agentbox-open +28 -0
- package/runtime/vercel/agentbox-setup-skill.md +196 -0
- package/runtime/vercel/agentbox-vnc-start +91 -0
- package/runtime/vercel/claude-managed-settings.json +115 -0
- package/runtime/vercel/ctl.cjs +23466 -0
- package/runtime/vercel/custom-system-CLAUDE.md +50 -0
- package/runtime/vercel/gh-shim +263 -0
- package/runtime/vercel/git-shim +131 -0
- package/runtime/vercel/scripts/provision.sh +274 -0
- package/share/agentbox-setup/SKILL.md +1 -1
- package/share/host-skills/agentbox/SKILL.md +29 -0
- package/share/host-skills/agentbox-info/SKILL.md +211 -0
- package/share/host-skills/codex/agentbox.md +35 -0
- package/share/host-skills/opencode/agentbox.md +26 -0
- package/dist/_cloud-attach-DMVH6GWO.js +0 -12
- package/dist/chunk-7KOEFGN2.js.map +0 -1
- package/dist/chunk-NAVL4R34.js.map +0 -1
- package/dist/chunk-NW5NYTQM.js.map +0 -1
- package/dist/chunk-UK72UQ5U.js.map +0 -1
- package/dist/chunk-V5KZGB5V.js.map +0 -1
- package/dist/dist-QZGJIBT5.js.map +0 -1
- package/dist/dist-R67WMLCF.js.map +0 -1
- /package/dist/{_cloud-attach-DMVH6GWO.js.map → _cloud-attach-ZXBCNWJX.js.map} +0 -0
- /package/dist/{cloud-poller-ZIWSADJB-JXFRJUEM.js.map → cloud-poller-SUNA6ZQC-2RG5WPRN.js.map} +0 -0
- /package/dist/{dist-ETCFRVPA.js.map → dist-32EZBYG4.js.map} +0 -0
package/runtime/hetzner/ctl.cjs
CHANGED
|
@@ -3033,10 +3033,16 @@ var require_commander = __commonJS({
|
|
|
3033
3033
|
}
|
|
3034
3034
|
});
|
|
3035
3035
|
|
|
3036
|
-
// ../relay/dist/chunk-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3036
|
+
// ../relay/dist/chunk-YVJLTAM3.js
|
|
3037
|
+
function isConnectionLevelError(err) {
|
|
3038
|
+
const code = err?.code;
|
|
3039
|
+
if (code && CONNECTION_LEVEL_CODES.has(code)) return true;
|
|
3040
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3041
|
+
return /\b(ECONNREFUSED|ENOTFOUND|EHOSTUNREACH|ECONNRESET|EPIPE)\b/.test(msg);
|
|
3042
|
+
}
|
|
3043
|
+
var import_http, import_https, import_promises, BACKOFF_BASE_MS, BACKOFF_MAX_MS, REQUEST_TIMEOUT_MS, FAST_REQUEST_TIMEOUT_MS, FAST_MODE_DECAY_POLLS, STOPPED_TICK_MS, CONNECTION_LEVEL_CODES, CloudBoxPoller, CloudBoxPollers;
|
|
3044
|
+
var init_chunk_YVJLTAM3 = __esm({
|
|
3045
|
+
"../relay/dist/chunk-YVJLTAM3.js"() {
|
|
3040
3046
|
"use strict";
|
|
3041
3047
|
import_http = require("http");
|
|
3042
3048
|
import_https = require("https");
|
|
@@ -3047,9 +3053,17 @@ var init_chunk_SJUIVWA3 = __esm({
|
|
|
3047
3053
|
FAST_REQUEST_TIMEOUT_MS = 8e3;
|
|
3048
3054
|
FAST_MODE_DECAY_POLLS = 5;
|
|
3049
3055
|
STOPPED_TICK_MS = 250;
|
|
3056
|
+
CONNECTION_LEVEL_CODES = /* @__PURE__ */ new Set([
|
|
3057
|
+
"ECONNREFUSED",
|
|
3058
|
+
"ENOTFOUND",
|
|
3059
|
+
"EHOSTUNREACH",
|
|
3060
|
+
"ECONNRESET",
|
|
3061
|
+
"EPIPE"
|
|
3062
|
+
]);
|
|
3050
3063
|
CloudBoxPoller = class {
|
|
3051
3064
|
constructor(deps) {
|
|
3052
3065
|
this.deps = deps;
|
|
3066
|
+
this.currentPreviewUrl = deps.previewUrl;
|
|
3053
3067
|
}
|
|
3054
3068
|
deps;
|
|
3055
3069
|
stopped = false;
|
|
@@ -3063,6 +3077,15 @@ var init_chunk_SJUIVWA3 = __esm({
|
|
|
3063
3077
|
* within ~5 successful round-trips.
|
|
3064
3078
|
*/
|
|
3065
3079
|
fastModePolls = 0;
|
|
3080
|
+
/**
|
|
3081
|
+
* Mutable copy of `deps.previewUrl`. We don't read `deps.previewUrl`
|
|
3082
|
+
* directly after construction because `recoverPreviewUrl` may hand us a
|
|
3083
|
+
* new URL (e.g. Hetzner reopens its SSH ControlMaster and the `-L`
|
|
3084
|
+
* forward gets a new ephemeral local port).
|
|
3085
|
+
*/
|
|
3086
|
+
currentPreviewUrl;
|
|
3087
|
+
/** Guards against recovery storms — at most one recovery attempt in flight. */
|
|
3088
|
+
recovering = null;
|
|
3066
3089
|
start() {
|
|
3067
3090
|
if (this.loopPromise) return;
|
|
3068
3091
|
this.loopPromise = this.run().catch((err) => {
|
|
@@ -3079,7 +3102,7 @@ var init_chunk_SJUIVWA3 = __esm({
|
|
|
3079
3102
|
* and the in-box agent finally sees the answer.
|
|
3080
3103
|
*/
|
|
3081
3104
|
async respond(actionId, result) {
|
|
3082
|
-
const base = this.
|
|
3105
|
+
const base = this.currentPreviewUrl.replace(/\/+$/, "");
|
|
3083
3106
|
const url = new URL(`${base}/bridge/action-result`);
|
|
3084
3107
|
const isHttps = url.protocol === "https:";
|
|
3085
3108
|
const transport = isHttps ? import_https.request : import_http.request;
|
|
@@ -3169,6 +3192,9 @@ var init_chunk_SJUIVWA3 = __esm({
|
|
|
3169
3192
|
this.fastModePolls = FAST_MODE_DECAY_POLLS;
|
|
3170
3193
|
}
|
|
3171
3194
|
this.log(`poll error: ${msg}`);
|
|
3195
|
+
if (this.deps.recoverPreviewUrl && isConnectionLevelError(err)) {
|
|
3196
|
+
await this.tryRecoverPreviewUrl();
|
|
3197
|
+
}
|
|
3172
3198
|
await this.backoff();
|
|
3173
3199
|
}
|
|
3174
3200
|
if (this.currentBackoffMs === 0 && this.fastModePolls > 0) {
|
|
@@ -3181,8 +3207,33 @@ var init_chunk_SJUIVWA3 = __esm({
|
|
|
3181
3207
|
this.currentBackoffMs = this.currentBackoffMs === 0 ? BACKOFF_BASE_MS : Math.min(this.currentBackoffMs * 2, BACKOFF_MAX_MS);
|
|
3182
3208
|
await (0, import_promises.setTimeout)(this.currentBackoffMs);
|
|
3183
3209
|
}
|
|
3210
|
+
async tryRecoverPreviewUrl() {
|
|
3211
|
+
if (!this.deps.recoverPreviewUrl) return;
|
|
3212
|
+
if (this.recovering) {
|
|
3213
|
+
await this.recovering;
|
|
3214
|
+
return;
|
|
3215
|
+
}
|
|
3216
|
+
this.recovering = (async () => {
|
|
3217
|
+
try {
|
|
3218
|
+
const next = await this.deps.recoverPreviewUrl();
|
|
3219
|
+
if (typeof next === "string" && next.length > 0 && next !== this.currentPreviewUrl) {
|
|
3220
|
+
this.log(`preview URL recovered: ${this.currentPreviewUrl} \u2192 ${next}`);
|
|
3221
|
+
this.currentPreviewUrl = next;
|
|
3222
|
+
this.currentBackoffMs = 0;
|
|
3223
|
+
} else if (typeof next === "string" && next === this.currentPreviewUrl) {
|
|
3224
|
+
this.log("preview URL recovered (unchanged)");
|
|
3225
|
+
this.currentBackoffMs = 0;
|
|
3226
|
+
}
|
|
3227
|
+
} catch (err) {
|
|
3228
|
+
this.log(`preview URL recover failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
3229
|
+
} finally {
|
|
3230
|
+
this.recovering = null;
|
|
3231
|
+
}
|
|
3232
|
+
})();
|
|
3233
|
+
await this.recovering;
|
|
3234
|
+
}
|
|
3184
3235
|
async pollOnce() {
|
|
3185
|
-
const base = this.
|
|
3236
|
+
const base = this.currentPreviewUrl.replace(/\/+$/, "");
|
|
3186
3237
|
const url = new URL(`${base}/bridge/poll?since=${String(this.cursor)}`);
|
|
3187
3238
|
const isHttps = url.protocol === "https:";
|
|
3188
3239
|
const transport = isHttps ? import_https.request : import_http.request;
|
|
@@ -3750,7 +3801,7 @@ var require_cross_spawn = __commonJS({
|
|
|
3750
3801
|
var cp = require("child_process");
|
|
3751
3802
|
var parse = require_parse();
|
|
3752
3803
|
var enoent = require_enoent();
|
|
3753
|
-
function
|
|
3804
|
+
function spawn10(command, args, options) {
|
|
3754
3805
|
const parsed = parse(command, args, options);
|
|
3755
3806
|
const spawned = cp.spawn(parsed.command, parsed.args, parsed.options);
|
|
3756
3807
|
enoent.hookChildProcess(spawned, parsed);
|
|
@@ -3762,8 +3813,8 @@ var require_cross_spawn = __commonJS({
|
|
|
3762
3813
|
result.error = result.error || enoent.verifyENOENTSync(result.status, parsed);
|
|
3763
3814
|
return result;
|
|
3764
3815
|
}
|
|
3765
|
-
module2.exports =
|
|
3766
|
-
module2.exports.spawn =
|
|
3816
|
+
module2.exports = spawn10;
|
|
3817
|
+
module2.exports.spawn = spawn10;
|
|
3767
3818
|
module2.exports.sync = spawnSync2;
|
|
3768
3819
|
module2.exports._parse = parse;
|
|
3769
3820
|
module2.exports._enoent = enoent;
|
|
@@ -11097,16 +11148,16 @@ var require_dist = __commonJS({
|
|
|
11097
11148
|
}
|
|
11098
11149
|
});
|
|
11099
11150
|
|
|
11100
|
-
// ../relay/dist/cloud-poller-
|
|
11101
|
-
var
|
|
11102
|
-
__export(
|
|
11151
|
+
// ../relay/dist/cloud-poller-SUNA6ZQC.js
|
|
11152
|
+
var cloud_poller_SUNA6ZQC_exports = {};
|
|
11153
|
+
__export(cloud_poller_SUNA6ZQC_exports, {
|
|
11103
11154
|
CloudBoxPoller: () => CloudBoxPoller,
|
|
11104
11155
|
CloudBoxPollers: () => CloudBoxPollers
|
|
11105
11156
|
});
|
|
11106
|
-
var
|
|
11107
|
-
"../relay/dist/cloud-poller-
|
|
11157
|
+
var init_cloud_poller_SUNA6ZQC = __esm({
|
|
11158
|
+
"../relay/dist/cloud-poller-SUNA6ZQC.js"() {
|
|
11108
11159
|
"use strict";
|
|
11109
|
-
|
|
11160
|
+
init_chunk_YVJLTAM3();
|
|
11110
11161
|
}
|
|
11111
11162
|
});
|
|
11112
11163
|
|
|
@@ -11231,12 +11282,21 @@ async function claudeSession(opts) {
|
|
|
11231
11282
|
sessionName: opts.sessionName
|
|
11232
11283
|
});
|
|
11233
11284
|
}
|
|
11234
|
-
async function claudeState(opts, state) {
|
|
11235
|
-
return sendOneShot(opts, {
|
|
11285
|
+
async function claudeState(opts, state, payload) {
|
|
11286
|
+
return sendOneShot(opts, {
|
|
11287
|
+
op: "claude-state",
|
|
11288
|
+
state,
|
|
11289
|
+
...payload?.plan ? { plan: payload.plan } : {},
|
|
11290
|
+
...payload?.question ? { question: payload.question } : {},
|
|
11291
|
+
...payload?.clearPending ? { clearPending: true } : {}
|
|
11292
|
+
});
|
|
11236
11293
|
}
|
|
11237
11294
|
async function codexState(opts, state) {
|
|
11238
11295
|
return sendOneShot(opts, { op: "codex-state", state });
|
|
11239
11296
|
}
|
|
11297
|
+
async function opencodeState(opts, state) {
|
|
11298
|
+
return sendOneShot(opts, { op: "opencode-state", state });
|
|
11299
|
+
}
|
|
11240
11300
|
async function logs(opts, args) {
|
|
11241
11301
|
const sock = await connect(opts);
|
|
11242
11302
|
sock.write(`${JSON.stringify({ op: "logs", ...args })}
|
|
@@ -11291,6 +11351,10 @@ var CLAUDE_ACTIVITY_STATES = [
|
|
|
11291
11351
|
"working",
|
|
11292
11352
|
"idle",
|
|
11293
11353
|
"waiting",
|
|
11354
|
+
"end-plan",
|
|
11355
|
+
"question",
|
|
11356
|
+
"compacting",
|
|
11357
|
+
"error",
|
|
11294
11358
|
"unknown"
|
|
11295
11359
|
];
|
|
11296
11360
|
var BOX_STATUS_SCHEMA = 1;
|
|
@@ -11324,18 +11388,90 @@ var claudeSessionCommand = new Command("claude-session").description("Report whe
|
|
|
11324
11388
|
});
|
|
11325
11389
|
|
|
11326
11390
|
// src/commands/claude-state.ts
|
|
11327
|
-
var claudeStateCommand = new Command("claude-state").description("Report Claude activity state to the box supervisor (used by hooks)").argument("<state>", `one of: ${CLAUDE_ACTIVITY_STATES.join(", ")}`).option("--socket <path>", "unix socket path", DEFAULT_SOCKET_PATH).action(async (state, opts) => {
|
|
11391
|
+
var claudeStateCommand = new Command("claude-state").description("Report Claude activity state to the box supervisor (used by hooks)").argument("<state>", `one of: ${CLAUDE_ACTIVITY_STATES.join(", ")}`).option("--socket <path>", "unix socket path", DEFAULT_SOCKET_PATH).option("--payload-stdin", "parse Claude Code's hook JSON from stdin (PreToolUse plan/question)").option("--clear-pending", "force-clear a sticky end-plan/question state (PostToolUse cleanup)").action(async (state, opts) => {
|
|
11328
11392
|
try {
|
|
11329
|
-
if (CLAUDE_ACTIVITY_STATES.includes(state)) {
|
|
11330
|
-
|
|
11331
|
-
|
|
11332
|
-
|
|
11333
|
-
|
|
11334
|
-
}
|
|
11393
|
+
if (!CLAUDE_ACTIVITY_STATES.includes(state)) {
|
|
11394
|
+
process.exit(0);
|
|
11395
|
+
}
|
|
11396
|
+
const typedState = state;
|
|
11397
|
+
const extracted = opts.payloadStdin ? await extractPayload(typedState, await readStdinJson()) : void 0;
|
|
11398
|
+
const payload = { ...extracted ?? {} };
|
|
11399
|
+
if (opts.clearPending) payload.clearPending = true;
|
|
11400
|
+
const hasField = payload.plan !== void 0 || payload.question !== void 0 || payload.clearPending !== void 0;
|
|
11401
|
+
await claudeState(
|
|
11402
|
+
{ socketPath: opts.socket, timeoutMs: 1500 },
|
|
11403
|
+
typedState,
|
|
11404
|
+
hasField ? payload : void 0
|
|
11405
|
+
);
|
|
11335
11406
|
} catch {
|
|
11336
11407
|
}
|
|
11337
11408
|
process.exit(0);
|
|
11338
11409
|
});
|
|
11410
|
+
function extractPayload(state, raw) {
|
|
11411
|
+
if (!raw || typeof raw !== "object") return void 0;
|
|
11412
|
+
const tool = raw.tool_input ?? {};
|
|
11413
|
+
const capturedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
11414
|
+
if (state === "end-plan" && typeof tool.plan === "string") {
|
|
11415
|
+
const plan = { plan: tool.plan, capturedAt };
|
|
11416
|
+
return { plan };
|
|
11417
|
+
}
|
|
11418
|
+
if (state === "question" && Array.isArray(tool.questions)) {
|
|
11419
|
+
const questions = tool.questions.map((q) => normalizeQuestion(q)).filter((q) => q !== null);
|
|
11420
|
+
if (questions.length === 0) return void 0;
|
|
11421
|
+
const question = { questions, capturedAt };
|
|
11422
|
+
return { question };
|
|
11423
|
+
}
|
|
11424
|
+
return void 0;
|
|
11425
|
+
}
|
|
11426
|
+
function normalizeQuestion(raw) {
|
|
11427
|
+
if (!raw || typeof raw !== "object") return null;
|
|
11428
|
+
const q = raw;
|
|
11429
|
+
if (typeof q.question !== "string") return null;
|
|
11430
|
+
const opts = Array.isArray(q.options) ? q.options : [];
|
|
11431
|
+
const options = opts.map((o2) => {
|
|
11432
|
+
if (!o2 || typeof o2 !== "object") return null;
|
|
11433
|
+
const oo = o2;
|
|
11434
|
+
if (typeof oo.label !== "string") return null;
|
|
11435
|
+
const entry = { label: oo.label };
|
|
11436
|
+
if (typeof oo.description === "string") entry.description = oo.description;
|
|
11437
|
+
return entry;
|
|
11438
|
+
}).filter((o2) => o2 !== null);
|
|
11439
|
+
const out = { question: q.question, options };
|
|
11440
|
+
if (typeof q.header === "string") out.header = q.header;
|
|
11441
|
+
if (typeof q.multiSelect === "boolean") out.multiSelect = q.multiSelect;
|
|
11442
|
+
return out;
|
|
11443
|
+
}
|
|
11444
|
+
function readStdinJson() {
|
|
11445
|
+
return new Promise((resolve2) => {
|
|
11446
|
+
if (process.stdin.isTTY) {
|
|
11447
|
+
resolve2(null);
|
|
11448
|
+
return;
|
|
11449
|
+
}
|
|
11450
|
+
const chunks = [];
|
|
11451
|
+
const cap = setTimeout(() => {
|
|
11452
|
+
process.stdin.removeAllListeners();
|
|
11453
|
+
resolve2(null);
|
|
11454
|
+
}, 1e3);
|
|
11455
|
+
cap.unref();
|
|
11456
|
+
process.stdin.on("data", (b) => chunks.push(b));
|
|
11457
|
+
process.stdin.on("end", () => {
|
|
11458
|
+
clearTimeout(cap);
|
|
11459
|
+
if (chunks.length === 0) {
|
|
11460
|
+
resolve2(null);
|
|
11461
|
+
return;
|
|
11462
|
+
}
|
|
11463
|
+
try {
|
|
11464
|
+
resolve2(JSON.parse(Buffer.concat(chunks).toString("utf8")));
|
|
11465
|
+
} catch {
|
|
11466
|
+
resolve2(null);
|
|
11467
|
+
}
|
|
11468
|
+
});
|
|
11469
|
+
process.stdin.on("error", () => {
|
|
11470
|
+
clearTimeout(cap);
|
|
11471
|
+
resolve2(null);
|
|
11472
|
+
});
|
|
11473
|
+
});
|
|
11474
|
+
}
|
|
11339
11475
|
|
|
11340
11476
|
// src/commands/codex-state.ts
|
|
11341
11477
|
var codexStateCommand = new Command("codex-state").description("Report Codex activity state to the box supervisor (used by hooks)").argument("<state>", `one of: ${CLAUDE_ACTIVITY_STATES.join(", ")}`).option("--socket <path>", "unix socket path", DEFAULT_SOCKET_PATH).action(async (state, opts) => {
|
|
@@ -11450,15 +11586,31 @@ var cpCommand = new Command("cp").description("Copy a file/dir between this box
|
|
|
11450
11586
|
})
|
|
11451
11587
|
);
|
|
11452
11588
|
|
|
11589
|
+
// src/commands/opencode-state.ts
|
|
11590
|
+
var opencodeStateCommand = new Command("opencode-state").description("Report OpenCode activity state to the box supervisor (used by the plugin)").argument("<state>", `one of: ${CLAUDE_ACTIVITY_STATES.join(", ")}`).option("--socket <path>", "unix socket path", DEFAULT_SOCKET_PATH).action(async (state, opts) => {
|
|
11591
|
+
try {
|
|
11592
|
+
if (CLAUDE_ACTIVITY_STATES.includes(state)) {
|
|
11593
|
+
await opencodeState(
|
|
11594
|
+
{ socketPath: opts.socket, timeoutMs: 1500 },
|
|
11595
|
+
state
|
|
11596
|
+
);
|
|
11597
|
+
}
|
|
11598
|
+
} catch {
|
|
11599
|
+
}
|
|
11600
|
+
process.exit(0);
|
|
11601
|
+
});
|
|
11602
|
+
|
|
11453
11603
|
// ../relay/dist/index.js
|
|
11454
|
-
|
|
11604
|
+
init_chunk_YVJLTAM3();
|
|
11455
11605
|
var import_crypto = require("crypto");
|
|
11456
11606
|
var import_crypto2 = require("crypto");
|
|
11457
11607
|
var import_crypto3 = require("crypto");
|
|
11608
|
+
var import_crypto4 = require("crypto");
|
|
11609
|
+
var import_child_process = require("child_process");
|
|
11458
11610
|
var import_promises14 = require("fs/promises");
|
|
11459
11611
|
var import_os3 = require("os");
|
|
11460
11612
|
var import_path4 = require("path");
|
|
11461
|
-
var
|
|
11613
|
+
var import_child_process2 = require("child_process");
|
|
11462
11614
|
var import_http2 = require("http");
|
|
11463
11615
|
|
|
11464
11616
|
// ../../node_modules/.pnpm/is-plain-obj@4.1.0/node_modules/is-plain-obj/index.js
|
|
@@ -18284,6 +18436,8 @@ function projectDockerFields(box) {
|
|
|
18284
18436
|
webHostPort: box.webHostPort,
|
|
18285
18437
|
portlessAlias: box.portlessAlias,
|
|
18286
18438
|
portlessUrl: box.portlessUrl,
|
|
18439
|
+
portlessVncAlias: box.portlessVncAlias,
|
|
18440
|
+
portlessVncUrl: box.portlessVncUrl,
|
|
18287
18441
|
dockerVolume: box.dockerVolume,
|
|
18288
18442
|
dockerCacheShared: box.dockerCacheShared,
|
|
18289
18443
|
checkpointImage: box.checkpointImage
|
|
@@ -18315,8 +18469,8 @@ var KEY_REGISTRY = [
|
|
|
18315
18469
|
{
|
|
18316
18470
|
key: "box.provider",
|
|
18317
18471
|
type: "enum",
|
|
18318
|
-
enumValues: ["docker", "daytona", "hetzner"],
|
|
18319
|
-
description: "Sandbox backend new boxes are created on: local Docker containers, Daytona Cloud sandboxes,
|
|
18472
|
+
enumValues: ["docker", "daytona", "hetzner", "vercel"],
|
|
18473
|
+
description: "Sandbox backend new boxes are created on: local Docker containers, Daytona Cloud sandboxes, Hetzner Cloud VPSes, or Vercel Sandboxes."
|
|
18320
18474
|
},
|
|
18321
18475
|
{
|
|
18322
18476
|
key: "box.hostSnapshot",
|
|
@@ -18346,6 +18500,12 @@ var KEY_REGISTRY = [
|
|
|
18346
18500
|
description: "Per-provider override of `box.defaultCheckpoint` for hetzner. Wins over the global when set; set via `agentbox checkpoint set-default --provider hetzner`.",
|
|
18347
18501
|
advanced: true
|
|
18348
18502
|
},
|
|
18503
|
+
{
|
|
18504
|
+
key: "box.defaultCheckpointVercel",
|
|
18505
|
+
type: "string",
|
|
18506
|
+
description: "Per-provider override of `box.defaultCheckpoint` for vercel. Wins over the global when set; set via `agentbox checkpoint set-default --provider vercel`.",
|
|
18507
|
+
advanced: true
|
|
18508
|
+
},
|
|
18349
18509
|
{
|
|
18350
18510
|
key: "checkpoint.maxLayers",
|
|
18351
18511
|
type: "int",
|
|
@@ -18414,6 +18574,26 @@ var KEY_REGISTRY = [
|
|
|
18414
18574
|
description: "Best-effort writable-layer size for new boxes, e.g. '10G'. No-op on overlay2 / the macOS engines.",
|
|
18415
18575
|
advanced: true
|
|
18416
18576
|
},
|
|
18577
|
+
{
|
|
18578
|
+
key: "box.bundleDepth",
|
|
18579
|
+
type: "int",
|
|
18580
|
+
description: "Cap git bundle history shipped to cloud sandboxes (daytona, hetzner). 0 = full history. Unset = adaptive default (last 200 commits; re-bundle at 100 if the bundle exceeds 20 MB). Ignored for docker (which bind-mounts .git/)."
|
|
18581
|
+
},
|
|
18582
|
+
{
|
|
18583
|
+
key: "box.vercelVcpus",
|
|
18584
|
+
type: "int",
|
|
18585
|
+
description: "vCPUs for new --provider vercel boxes (Vercel couples RAM at 2048 MB/vCPU). Default 2. Vercel only accepts specific counts (e.g. 1, 2, 4, 8) \u2014 an unsupported value fails create with a 400. Vercel-only; ignored by other providers."
|
|
18586
|
+
},
|
|
18587
|
+
{
|
|
18588
|
+
key: "box.vercelTimeoutMs",
|
|
18589
|
+
type: "int",
|
|
18590
|
+
description: "Max session length (ms) for new --provider vercel boxes before the VM auto-snapshots; persistent mode auto-resumes on the next call. Default 2700000 (45 min, the Hobby ceiling). Vercel-only."
|
|
18591
|
+
},
|
|
18592
|
+
{
|
|
18593
|
+
key: "box.vercelNetworkPolicy",
|
|
18594
|
+
type: "string",
|
|
18595
|
+
description: "Egress lock for new --provider vercel boxes: 'allow-all' (default, unset), 'deny-all', or a comma-separated domain allowlist (e.g. 'github.com,*.npmjs.org') that denies everything else. Vercel-only; ignored by other providers."
|
|
18596
|
+
},
|
|
18417
18597
|
{
|
|
18418
18598
|
key: "claude.sessionName",
|
|
18419
18599
|
type: "string",
|
|
@@ -18521,6 +18701,31 @@ var KEY_REGISTRY = [
|
|
|
18521
18701
|
type: "int",
|
|
18522
18702
|
description: "Minutes a box must be continuously idle (claude state) before it is eligible for auto-pause."
|
|
18523
18703
|
},
|
|
18704
|
+
{
|
|
18705
|
+
key: "queue.enabled",
|
|
18706
|
+
type: "bool",
|
|
18707
|
+
description: "Run `agentbox claude|codex|opencode -i <prompt>` jobs through the host-wide background queue (FIFO, capped by queue.maxConcurrent)."
|
|
18708
|
+
},
|
|
18709
|
+
{
|
|
18710
|
+
key: "queue.maxConcurrent",
|
|
18711
|
+
type: "int",
|
|
18712
|
+
description: "Max number of simultaneously-running boxes (across providers) before background `-i` jobs queue up instead of starting immediately. Per-invocation override: `--max-running <n>`."
|
|
18713
|
+
},
|
|
18714
|
+
{
|
|
18715
|
+
key: "queue.maxWorking",
|
|
18716
|
+
type: "int",
|
|
18717
|
+
description: "Max agents actively working/thinking (quota-consuming) at once before background `-i` jobs queue. 0 = disabled (use the queue.maxConcurrent running-box gate). Counts all boxes, foreground + queued. Per-invocation override: `--max-working <n>`."
|
|
18718
|
+
},
|
|
18719
|
+
{
|
|
18720
|
+
key: "queue.idleGraceSeconds",
|
|
18721
|
+
type: "int",
|
|
18722
|
+
description: "Seconds an agent must stay non-working before it frees its working slot (debounce against brief idle flaps between turns). Only used when queue.maxWorking > 0."
|
|
18723
|
+
},
|
|
18724
|
+
{
|
|
18725
|
+
key: "cloud.useCurrentBranch",
|
|
18726
|
+
type: "bool",
|
|
18727
|
+
description: "On cloud providers (daytona/hetzner), start new boxes on the host's current branch instead of forking a new agentbox/<box-name> branch. Overridden by an explicit --use-branch / --from-branch."
|
|
18728
|
+
},
|
|
18524
18729
|
{
|
|
18525
18730
|
key: "maintenance.pruneProjectConfigs",
|
|
18526
18731
|
type: "bool",
|
|
@@ -18557,7 +18762,8 @@ var PROJECTS_DIR = (0, import_path2.join)(STATE_DIR2, "projects");
|
|
|
18557
18762
|
var PROJECT_GC_COUNTER_FILE = (0, import_path3.join)(PROJECTS_DIR, ".gc.json");
|
|
18558
18763
|
|
|
18559
18764
|
// ../relay/dist/index.js
|
|
18560
|
-
var
|
|
18765
|
+
var import_path6 = require("path");
|
|
18766
|
+
var DEFAULT_BOX_RELAY_PORT = 8788;
|
|
18561
18767
|
var RELAY_EVENT_RING_SIZE = 1e3;
|
|
18562
18768
|
var DEFAULT_HOST_ACTION_MAX_AGE_MS = 15 * 60 * 1e3;
|
|
18563
18769
|
var HostActionQueue = class {
|
|
@@ -18870,6 +19076,254 @@ var BoxNotices = class {
|
|
|
18870
19076
|
return this.entries.size;
|
|
18871
19077
|
}
|
|
18872
19078
|
};
|
|
19079
|
+
var DEFAULT_TTL_MS = 12e4;
|
|
19080
|
+
function hashRpcParams(params) {
|
|
19081
|
+
return (0, import_crypto4.createHash)("sha256").update(canonicalJson(params)).digest("hex");
|
|
19082
|
+
}
|
|
19083
|
+
function canonicalJson(v) {
|
|
19084
|
+
if (v === null) return "null";
|
|
19085
|
+
if (typeof v === "undefined") return "null";
|
|
19086
|
+
if (typeof v === "number") return Number.isFinite(v) ? String(v) : "null";
|
|
19087
|
+
if (typeof v === "boolean") return v ? "true" : "false";
|
|
19088
|
+
if (typeof v === "string") return JSON.stringify(v);
|
|
19089
|
+
if (Array.isArray(v)) return "[" + v.map(canonicalJson).join(",") + "]";
|
|
19090
|
+
if (typeof v === "object") {
|
|
19091
|
+
const entries = Object.entries(v).filter(([k]) => k !== "hostInitiated").filter(([, val]) => val !== void 0).sort(([a2], [b]) => a2 < b ? -1 : a2 > b ? 1 : 0);
|
|
19092
|
+
return "{" + entries.map(([k, val]) => JSON.stringify(k) + ":" + canonicalJson(val)).join(",") + "}";
|
|
19093
|
+
}
|
|
19094
|
+
return "null";
|
|
19095
|
+
}
|
|
19096
|
+
var HostInitiatedTokens = class {
|
|
19097
|
+
store = /* @__PURE__ */ new Map();
|
|
19098
|
+
/**
|
|
19099
|
+
* Mint a fresh one-time token scoped to (boxId, method, paramsHash).
|
|
19100
|
+
* `paramsHash` MUST be supplied for any call surface where the box can
|
|
19101
|
+
* influence the eventual RPC params. Pass `null` only when there are no
|
|
19102
|
+
* params (no current call sites use this).
|
|
19103
|
+
*/
|
|
19104
|
+
mint(boxId, method, paramsHash, ttlMs = DEFAULT_TTL_MS) {
|
|
19105
|
+
const token = (0, import_crypto4.randomBytes)(32).toString("hex");
|
|
19106
|
+
this.store.set(token, { boxId, method, paramsHash, expiresAt: Date.now() + ttlMs });
|
|
19107
|
+
return token;
|
|
19108
|
+
}
|
|
19109
|
+
/**
|
|
19110
|
+
* Returns true exactly once if `token` is a valid, unexpired token for the
|
|
19111
|
+
* given `(boxId, method)` AND the supplied `incomingParamsHash` matches
|
|
19112
|
+
* the hash bound at mint time. The token is removed on a successful match
|
|
19113
|
+
* (one-shot semantics). All failure modes return false — callers fall back
|
|
19114
|
+
* to the normal prompt path.
|
|
19115
|
+
*/
|
|
19116
|
+
consume(token, boxId, method, incomingParamsHash) {
|
|
19117
|
+
if (!token || typeof token !== "string") return false;
|
|
19118
|
+
const record = this.store.get(token);
|
|
19119
|
+
if (!record) return false;
|
|
19120
|
+
if (record.expiresAt < Date.now()) {
|
|
19121
|
+
this.store.delete(token);
|
|
19122
|
+
return false;
|
|
19123
|
+
}
|
|
19124
|
+
if (record.boxId !== boxId || record.method !== method) return false;
|
|
19125
|
+
if (record.paramsHash !== null && record.paramsHash !== incomingParamsHash) {
|
|
19126
|
+
return false;
|
|
19127
|
+
}
|
|
19128
|
+
this.store.delete(token);
|
|
19129
|
+
return true;
|
|
19130
|
+
}
|
|
19131
|
+
/** Drop expired entries. Cheap; safe to call periodically. */
|
|
19132
|
+
gc() {
|
|
19133
|
+
const now = Date.now();
|
|
19134
|
+
for (const [token, record] of this.store) {
|
|
19135
|
+
if (record.expiresAt < now) this.store.delete(token);
|
|
19136
|
+
}
|
|
19137
|
+
}
|
|
19138
|
+
/** Test-only: number of live tokens. */
|
|
19139
|
+
size() {
|
|
19140
|
+
return this.store.size;
|
|
19141
|
+
}
|
|
19142
|
+
};
|
|
19143
|
+
var GH_PR_OPS = [
|
|
19144
|
+
"create",
|
|
19145
|
+
"view",
|
|
19146
|
+
"list",
|
|
19147
|
+
"comment",
|
|
19148
|
+
"review",
|
|
19149
|
+
"merge",
|
|
19150
|
+
"checkout",
|
|
19151
|
+
"close",
|
|
19152
|
+
"reopen"
|
|
19153
|
+
];
|
|
19154
|
+
function isGhPrOp(value) {
|
|
19155
|
+
return GH_PR_OPS.includes(value);
|
|
19156
|
+
}
|
|
19157
|
+
var GH_PR_READ_ONLY_OPS = /* @__PURE__ */ new Set(["view", "list"]);
|
|
19158
|
+
function injectPrCreateHead(op, branch, args) {
|
|
19159
|
+
if (op !== "create") return args;
|
|
19160
|
+
if (!branch || branch === "HEAD") return args;
|
|
19161
|
+
if (hasHeadArg(args)) return args;
|
|
19162
|
+
return ["--head", branch, ...args];
|
|
19163
|
+
}
|
|
19164
|
+
function hasHeadArg(args) {
|
|
19165
|
+
return args.some((a2) => a2 === "--head" || a2.startsWith("--head=") || a2.startsWith("-H"));
|
|
19166
|
+
}
|
|
19167
|
+
function prCreateNeedsHead(op, args) {
|
|
19168
|
+
return op === "create" && !hasHeadArg(args);
|
|
19169
|
+
}
|
|
19170
|
+
var PR_CREATE_NO_HEAD_REFUSAL = {
|
|
19171
|
+
exitCode: 65,
|
|
19172
|
+
stdout: "",
|
|
19173
|
+
stderr: "gh pr create: refusing to run without --head \u2014 could not resolve this box's branch, and falling back to the host repo's checked-out branch would open a PR for the wrong branch. Ensure the box branch is pushed, or pass --head <branch> explicitly.\n"
|
|
19174
|
+
};
|
|
19175
|
+
var GH_RPC_TIMEOUT_MS = 12e4;
|
|
19176
|
+
var GH_READY_CACHE_TTL_MS = 6e4;
|
|
19177
|
+
var ghReadyCache;
|
|
19178
|
+
async function assertGhReady() {
|
|
19179
|
+
const now = Date.now();
|
|
19180
|
+
if (ghReadyCache && ghReadyCache.expiresAt > now) {
|
|
19181
|
+
return ghReadyCache.result;
|
|
19182
|
+
}
|
|
19183
|
+
const result = await probeGh();
|
|
19184
|
+
ghReadyCache = { result, expiresAt: now + GH_READY_CACHE_TTL_MS };
|
|
19185
|
+
return result;
|
|
19186
|
+
}
|
|
19187
|
+
async function probeGh() {
|
|
19188
|
+
const version = await runHostGh(["--version"], process.cwd(), 1e4);
|
|
19189
|
+
if (version.exitCode === 127 || /ENOENT/.test(version.stderr)) {
|
|
19190
|
+
return {
|
|
19191
|
+
exitCode: 127,
|
|
19192
|
+
stdout: "",
|
|
19193
|
+
stderr: "gh not installed on host (https://cli.github.com)\n"
|
|
19194
|
+
};
|
|
19195
|
+
}
|
|
19196
|
+
if (version.exitCode !== 0) {
|
|
19197
|
+
return {
|
|
19198
|
+
exitCode: version.exitCode,
|
|
19199
|
+
stdout: "",
|
|
19200
|
+
stderr: `gh --version failed: ${version.stderr || version.stdout}`.trimEnd() + "\n"
|
|
19201
|
+
};
|
|
19202
|
+
}
|
|
19203
|
+
const auth = await runHostGh(["auth", "status"], process.cwd(), 15e3);
|
|
19204
|
+
if (auth.exitCode !== 0) {
|
|
19205
|
+
return {
|
|
19206
|
+
exitCode: 4,
|
|
19207
|
+
stdout: "",
|
|
19208
|
+
stderr: "gh not authenticated on host (run `gh auth login`)\n"
|
|
19209
|
+
};
|
|
19210
|
+
}
|
|
19211
|
+
return null;
|
|
19212
|
+
}
|
|
19213
|
+
function runHostGh(args, cwd, timeoutMs = GH_RPC_TIMEOUT_MS) {
|
|
19214
|
+
return new Promise((resolve2) => {
|
|
19215
|
+
const child = (0, import_child_process.spawn)("gh", args, {
|
|
19216
|
+
cwd,
|
|
19217
|
+
env: process.env,
|
|
19218
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
19219
|
+
});
|
|
19220
|
+
let stdout = "";
|
|
19221
|
+
let stderr = "";
|
|
19222
|
+
let settled = false;
|
|
19223
|
+
const finish = (exitCode) => {
|
|
19224
|
+
if (settled) return;
|
|
19225
|
+
settled = true;
|
|
19226
|
+
resolve2({ exitCode, stdout, stderr });
|
|
19227
|
+
};
|
|
19228
|
+
const timer = setTimeout(() => {
|
|
19229
|
+
child.kill("SIGTERM");
|
|
19230
|
+
stderr += `
|
|
19231
|
+
relay: gh command timed out after ${String(timeoutMs)}ms
|
|
19232
|
+
`;
|
|
19233
|
+
finish(124);
|
|
19234
|
+
}, timeoutMs);
|
|
19235
|
+
child.stdout?.on("data", (chunk) => {
|
|
19236
|
+
stdout += chunk.toString("utf8");
|
|
19237
|
+
});
|
|
19238
|
+
child.stderr?.on("data", (chunk) => {
|
|
19239
|
+
stderr += chunk.toString("utf8");
|
|
19240
|
+
});
|
|
19241
|
+
child.on("error", (err) => {
|
|
19242
|
+
clearTimeout(timer);
|
|
19243
|
+
const code = err.code;
|
|
19244
|
+
stderr += String(err.message ?? err);
|
|
19245
|
+
finish(code === "ENOENT" ? 127 : 1);
|
|
19246
|
+
});
|
|
19247
|
+
child.on("close", (code) => {
|
|
19248
|
+
clearTimeout(timer);
|
|
19249
|
+
finish(code ?? -1);
|
|
19250
|
+
});
|
|
19251
|
+
});
|
|
19252
|
+
}
|
|
19253
|
+
async function checkoutGuards(hostMainRepo, registeredBranches) {
|
|
19254
|
+
const status2 = await runGitProbe(["-C", hostMainRepo, "status", "--porcelain"]);
|
|
19255
|
+
if (status2.exitCode !== 0) {
|
|
19256
|
+
return {
|
|
19257
|
+
exitCode: status2.exitCode,
|
|
19258
|
+
stdout: "",
|
|
19259
|
+
stderr: `gh pr checkout: failed to inspect host repo: ${status2.stderr || status2.stdout}`.trimEnd() + "\n"
|
|
19260
|
+
};
|
|
19261
|
+
}
|
|
19262
|
+
if (status2.stdout.trim().length > 0) {
|
|
19263
|
+
return {
|
|
19264
|
+
exitCode: 12,
|
|
19265
|
+
stdout: "",
|
|
19266
|
+
stderr: `gh pr checkout: ${hostMainRepo} has uncommitted changes; refusing to switch branches
|
|
19267
|
+
`
|
|
19268
|
+
};
|
|
19269
|
+
}
|
|
19270
|
+
const head = await runGitProbe(["-C", hostMainRepo, "rev-parse", "--abbrev-ref", "HEAD"]);
|
|
19271
|
+
if (head.exitCode !== 0) {
|
|
19272
|
+
return {
|
|
19273
|
+
exitCode: head.exitCode,
|
|
19274
|
+
stdout: "",
|
|
19275
|
+
stderr: `gh pr checkout: failed to resolve HEAD: ${head.stderr || head.stdout}`.trimEnd() + "\n"
|
|
19276
|
+
};
|
|
19277
|
+
}
|
|
19278
|
+
const currentBranch = head.stdout.trim();
|
|
19279
|
+
if (registeredBranches.includes(currentBranch)) {
|
|
19280
|
+
return {
|
|
19281
|
+
exitCode: 12,
|
|
19282
|
+
stdout: "",
|
|
19283
|
+
stderr: `gh pr checkout: ${hostMainRepo} is on registered box branch ${currentBranch}; refusing (would corrupt the bind-mounted box HEAD)
|
|
19284
|
+
`
|
|
19285
|
+
};
|
|
19286
|
+
}
|
|
19287
|
+
return null;
|
|
19288
|
+
}
|
|
19289
|
+
function runGitProbe(args) {
|
|
19290
|
+
return new Promise((resolve2) => {
|
|
19291
|
+
const child = (0, import_child_process.spawn)("git", args, { env: process.env, stdio: ["ignore", "pipe", "pipe"] });
|
|
19292
|
+
let stdout = "";
|
|
19293
|
+
let stderr = "";
|
|
19294
|
+
child.stdout?.on("data", (c3) => {
|
|
19295
|
+
stdout += c3.toString("utf8");
|
|
19296
|
+
});
|
|
19297
|
+
child.stderr?.on("data", (c3) => {
|
|
19298
|
+
stderr += c3.toString("utf8");
|
|
19299
|
+
});
|
|
19300
|
+
child.on("error", (err) => {
|
|
19301
|
+
resolve2({ exitCode: 127, stdout, stderr: stderr + String(err.message ?? err) });
|
|
19302
|
+
});
|
|
19303
|
+
child.on("close", (code) => {
|
|
19304
|
+
resolve2({ exitCode: code ?? -1, stdout, stderr });
|
|
19305
|
+
});
|
|
19306
|
+
});
|
|
19307
|
+
}
|
|
19308
|
+
function refuseMergeBypass(op) {
|
|
19309
|
+
if (op !== "merge") return null;
|
|
19310
|
+
if (process.env["AGENTBOX_PROMPT"] !== "off") return null;
|
|
19311
|
+
if (process.env["AGENTBOX_GH_FORCE"] === "1") return null;
|
|
19312
|
+
return {
|
|
19313
|
+
exitCode: 10,
|
|
19314
|
+
stdout: "",
|
|
19315
|
+
stderr: "gh pr merge: AGENTBOX_PROMPT=off bypass requires AGENTBOX_GH_FORCE=1 (merge is irreversible)\n"
|
|
19316
|
+
};
|
|
19317
|
+
}
|
|
19318
|
+
function refuseCheckoutByDefault(op) {
|
|
19319
|
+
if (op !== "checkout") return null;
|
|
19320
|
+
if (process.env["AGENTBOX_GH_PR_CHECKOUT"] === "allow") return null;
|
|
19321
|
+
return {
|
|
19322
|
+
exitCode: 13,
|
|
19323
|
+
stdout: "",
|
|
19324
|
+
stderr: "gh pr checkout: disabled by default; set AGENTBOX_GH_PR_CHECKOUT=allow to enable\n"
|
|
19325
|
+
};
|
|
19326
|
+
}
|
|
18873
19327
|
function sanitizeMnemonic(raw) {
|
|
18874
19328
|
return raw.toLowerCase().replace(/-/g, "_").replace(/[^a-z0-9_]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "").slice(0, 32) || "unnamed";
|
|
18875
19329
|
}
|
|
@@ -18919,36 +19373,43 @@ var BoxStatusStore = class {
|
|
|
18919
19373
|
async function resolveCloudBackend(name) {
|
|
18920
19374
|
if (name === "daytona") {
|
|
18921
19375
|
const pkg = "@agentbox/sandbox-daytona";
|
|
18922
|
-
|
|
18923
|
-
const mod = await import(pkg);
|
|
18924
|
-
return mod.daytonaBackend;
|
|
18925
|
-
} catch (err) {
|
|
18926
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
18927
|
-
if (/cannot find module|MODULE_NOT_FOUND/i.test(msg)) {
|
|
18928
|
-
throw new Error(
|
|
18929
|
-
`relay: cannot load '${pkg}' at runtime \u2014 install it alongside @agentbox/relay (the @madarco/agentbox CLI normally provides this dependency). Original: ${msg}`
|
|
18930
|
-
);
|
|
18931
|
-
}
|
|
18932
|
-
throw err;
|
|
18933
|
-
}
|
|
19376
|
+
return loadCloudBackend(pkg, async () => (await import(pkg)).daytonaBackend);
|
|
18934
19377
|
}
|
|
18935
19378
|
if (name === "hetzner") {
|
|
18936
19379
|
const pkg = "@agentbox/sandbox-hetzner";
|
|
18937
|
-
|
|
18938
|
-
|
|
18939
|
-
|
|
18940
|
-
|
|
18941
|
-
|
|
18942
|
-
if (/cannot find module|MODULE_NOT_FOUND/i.test(msg)) {
|
|
18943
|
-
throw new Error(
|
|
18944
|
-
`relay: cannot load '${pkg}' at runtime \u2014 install it alongside @agentbox/relay (the @madarco/agentbox CLI normally provides this dependency). Original: ${msg}`
|
|
18945
|
-
);
|
|
18946
|
-
}
|
|
18947
|
-
throw err;
|
|
18948
|
-
}
|
|
19380
|
+
return loadCloudBackend(pkg, async () => (await import(pkg)).hetznerBackend);
|
|
19381
|
+
}
|
|
19382
|
+
if (name === "vercel") {
|
|
19383
|
+
const pkg = "@agentbox/sandbox-vercel";
|
|
19384
|
+
return loadCloudBackend(pkg, async () => (await import(pkg)).vercelBackend);
|
|
18949
19385
|
}
|
|
18950
19386
|
throw new Error(`no host executor for cloud backend '${name}'`);
|
|
18951
19387
|
}
|
|
19388
|
+
async function loadCloudBackend(pkg, load2) {
|
|
19389
|
+
try {
|
|
19390
|
+
return await load2();
|
|
19391
|
+
} catch (err) {
|
|
19392
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
19393
|
+
if (/cannot find module|MODULE_NOT_FOUND/i.test(msg)) {
|
|
19394
|
+
throw new Error(
|
|
19395
|
+
`relay: cannot load '${pkg}' at runtime \u2014 install it alongside @agentbox/relay (the @madarco/agentbox CLI normally provides this dependency). Original: ${msg}`
|
|
19396
|
+
);
|
|
19397
|
+
}
|
|
19398
|
+
throw err;
|
|
19399
|
+
}
|
|
19400
|
+
}
|
|
19401
|
+
async function refreshCloudPreviewUrl(backendName, boxId, port) {
|
|
19402
|
+
try {
|
|
19403
|
+
const backend = await resolveCloudBackend(backendName);
|
|
19404
|
+
if (!backend.refreshPreviewUrl) return null;
|
|
19405
|
+
const lookup = await lookupCloudBox(boxId);
|
|
19406
|
+
const handle = { sandboxId: lookup.cloudSandboxId };
|
|
19407
|
+
const url = await backend.refreshPreviewUrl(handle, port);
|
|
19408
|
+
return url.url;
|
|
19409
|
+
} catch {
|
|
19410
|
+
return null;
|
|
19411
|
+
}
|
|
19412
|
+
}
|
|
18952
19413
|
async function executeCloudAction(action, deps) {
|
|
18953
19414
|
const log = deps.log ?? (() => {
|
|
18954
19415
|
});
|
|
@@ -18968,6 +19429,17 @@ async function executeCloudAction(action, deps) {
|
|
|
18968
19429
|
if (action.method === "browser.open.mirror") {
|
|
18969
19430
|
return runBrowserOpenMirror(action, deps);
|
|
18970
19431
|
}
|
|
19432
|
+
if (action.method.startsWith("gh.pr.")) {
|
|
19433
|
+
return runGhPrRpc(action, deps);
|
|
19434
|
+
}
|
|
19435
|
+
if (action.method === "git.clone" || action.method === "gh.repo.clone") {
|
|
19436
|
+
return {
|
|
19437
|
+
exitCode: 64,
|
|
19438
|
+
stdout: "",
|
|
19439
|
+
stderr: `${action.method}: not yet implemented (deferred; see docs/plans/gh-and-git-shims-host-only.md). Run \`gh\` / \`git\` on the host directly for now.
|
|
19440
|
+
`
|
|
19441
|
+
};
|
|
19442
|
+
}
|
|
18971
19443
|
return {
|
|
18972
19444
|
exitCode: 1,
|
|
18973
19445
|
stdout: "",
|
|
@@ -18975,6 +19447,99 @@ async function executeCloudAction(action, deps) {
|
|
|
18975
19447
|
`
|
|
18976
19448
|
};
|
|
18977
19449
|
}
|
|
19450
|
+
async function runGhPrRpc(action, deps) {
|
|
19451
|
+
const op = action.method.slice("gh.pr.".length);
|
|
19452
|
+
if (!isGhPrOp(op)) {
|
|
19453
|
+
return {
|
|
19454
|
+
exitCode: 64,
|
|
19455
|
+
stdout: "",
|
|
19456
|
+
stderr: `unknown gh.pr.* op: ${op}
|
|
19457
|
+
`
|
|
19458
|
+
};
|
|
19459
|
+
}
|
|
19460
|
+
const mergeBypass = refuseMergeBypass(op);
|
|
19461
|
+
if (mergeBypass) return mergeBypass;
|
|
19462
|
+
const checkoutOptIn = refuseCheckoutByDefault(op);
|
|
19463
|
+
if (checkoutOptIn) return checkoutOptIn;
|
|
19464
|
+
const params = action.params ?? {};
|
|
19465
|
+
const args = Array.isArray(params.args) ? params.args.filter((a2) => typeof a2 === "string") : [];
|
|
19466
|
+
const ghReady = await assertGhReady();
|
|
19467
|
+
if (ghReady) return ghReady;
|
|
19468
|
+
const lookup = await lookupCloudBox(deps.boxId);
|
|
19469
|
+
if (op === "checkout") {
|
|
19470
|
+
const guard = await checkoutGuards(lookup.workspacePath, []);
|
|
19471
|
+
if (guard) return guard;
|
|
19472
|
+
}
|
|
19473
|
+
const tokenClaimedGhCloud = typeof params.hostInitiated === "string";
|
|
19474
|
+
const incomingHashGhCloud = hashRpcParams(params);
|
|
19475
|
+
const hostInitiatedGhOk = !GH_PR_READ_ONLY_OPS.has(op) && tokenClaimedGhCloud && (deps.hostInitiatedTokens?.consume(
|
|
19476
|
+
params.hostInitiated,
|
|
19477
|
+
deps.boxId,
|
|
19478
|
+
`gh.pr.${op}`,
|
|
19479
|
+
incomingHashGhCloud
|
|
19480
|
+
) ?? false);
|
|
19481
|
+
if (!GH_PR_READ_ONLY_OPS.has(op) && tokenClaimedGhCloud && !hostInitiatedGhOk) {
|
|
19482
|
+
return {
|
|
19483
|
+
exitCode: 10,
|
|
19484
|
+
stdout: "",
|
|
19485
|
+
stderr: "host-initiated token rejected: invalid, expired, or bound to different params\n"
|
|
19486
|
+
};
|
|
19487
|
+
}
|
|
19488
|
+
if (!GH_PR_READ_ONLY_OPS.has(op) && !hostInitiatedGhOk && deps.prompts && deps.subscribers) {
|
|
19489
|
+
const detail = args.join(" ").slice(0, 200);
|
|
19490
|
+
const ctx = {
|
|
19491
|
+
kind: "confirm",
|
|
19492
|
+
message: `Allow gh pr ${op} from cloud box ${deps.boxName ?? deps.boxId}?`,
|
|
19493
|
+
detail,
|
|
19494
|
+
defaultAnswer: "n",
|
|
19495
|
+
context: {
|
|
19496
|
+
command: `gh pr ${op}`,
|
|
19497
|
+
cwd: params.path,
|
|
19498
|
+
argv: args
|
|
19499
|
+
}
|
|
19500
|
+
};
|
|
19501
|
+
const hasSubscriber = deps.subscribers.forBox(deps.boxId).length > 0;
|
|
19502
|
+
if (!hasSubscriber && process.env["AGENTBOX_PROMPT"] !== "off") {
|
|
19503
|
+
const noSubMode = (process.env["AGENTBOX_GH_NO_SUB"] ?? "deny").toLowerCase();
|
|
19504
|
+
if (noSubMode === "deny") {
|
|
19505
|
+
return {
|
|
19506
|
+
exitCode: 10,
|
|
19507
|
+
stdout: "",
|
|
19508
|
+
stderr: "denied automatically \u2014 no attached wrapper to confirm. Attach `agentbox claude` (or similar) and retry, or set AGENTBOX_GH_NO_SUB=allow.\n"
|
|
19509
|
+
};
|
|
19510
|
+
}
|
|
19511
|
+
if (noSubMode === "allow") {
|
|
19512
|
+
deps.log?.(`gh.pr.${op} auto-approved (no subscribers, AGENTBOX_GH_NO_SUB=allow)`);
|
|
19513
|
+
} else {
|
|
19514
|
+
const verdict = await askPrompt(deps.prompts, deps.subscribers, deps.boxId, ctx, {
|
|
19515
|
+
ttlMs: 5 * 60 * 1e3
|
|
19516
|
+
});
|
|
19517
|
+
if (verdict.answer !== "y") {
|
|
19518
|
+
return { exitCode: 10, stdout: "", stderr: "denied by user\n" };
|
|
19519
|
+
}
|
|
19520
|
+
}
|
|
19521
|
+
} else {
|
|
19522
|
+
const verdict = await askPrompt(deps.prompts, deps.subscribers, deps.boxId, ctx);
|
|
19523
|
+
if (verdict.answer !== "y") {
|
|
19524
|
+
return { exitCode: 10, stdout: "", stderr: "denied by user\n" };
|
|
19525
|
+
}
|
|
19526
|
+
}
|
|
19527
|
+
}
|
|
19528
|
+
let finalArgs = args;
|
|
19529
|
+
if (op === "create" && !args.some((a2) => a2 === "--head" || a2.startsWith("--head="))) {
|
|
19530
|
+
const backend = await resolveCloudBackend(deps.backendName);
|
|
19531
|
+
const handle = { sandboxId: lookup.cloudSandboxId };
|
|
19532
|
+
const containerPath = params.path ?? "/workspace";
|
|
19533
|
+
const branchProbe = await backend.exec(
|
|
19534
|
+
handle,
|
|
19535
|
+
`git -C ${shellQuote(containerPath)} rev-parse --abbrev-ref HEAD`
|
|
19536
|
+
);
|
|
19537
|
+
const branch = branchProbe.exitCode === 0 ? (branchProbe.stdout ?? "").trim() : "";
|
|
19538
|
+
finalArgs = injectPrCreateHead(op, branch, args);
|
|
19539
|
+
}
|
|
19540
|
+
if (prCreateNeedsHead(op, finalArgs)) return PR_CREATE_NO_HEAD_REFUSAL;
|
|
19541
|
+
return runHostGh(["pr", op, ...finalArgs], lookup.workspacePath);
|
|
19542
|
+
}
|
|
18978
19543
|
async function runBrowserOpenMirror(action, deps) {
|
|
18979
19544
|
const params = action.params ?? {};
|
|
18980
19545
|
const url = typeof params.url === "string" ? params.url.trim() : "";
|
|
@@ -19001,8 +19566,8 @@ async function runBrowserOpenMirror(action, deps) {
|
|
|
19001
19566
|
{ ttlMs: TTL_MS }
|
|
19002
19567
|
);
|
|
19003
19568
|
if (verdict.answer === "y" && !verdict.cancelled) {
|
|
19004
|
-
const { spawn:
|
|
19005
|
-
const child =
|
|
19569
|
+
const { spawn: spawn52 } = await import("child_process");
|
|
19570
|
+
const child = spawn52("open", [url], { stdio: "ignore", detached: true });
|
|
19006
19571
|
child.unref();
|
|
19007
19572
|
}
|
|
19008
19573
|
} catch (err) {
|
|
@@ -19173,7 +19738,23 @@ async function runGitRpc(action, deps) {
|
|
|
19173
19738
|
stderr: `failed to resolve branch in sandbox ${containerPath}: ${branchProbe.stderr || branch}`
|
|
19174
19739
|
};
|
|
19175
19740
|
}
|
|
19176
|
-
|
|
19741
|
+
const isAgentboxBranch = branch.startsWith("agentbox/");
|
|
19742
|
+
const tokenClaimedGit = typeof params.hostInitiated === "string";
|
|
19743
|
+
const incomingHashGit = hashRpcParams(params);
|
|
19744
|
+
const hostInitiatedOk = !isAgentboxBranch && tokenClaimedGit && (deps.hostInitiatedTokens?.consume(
|
|
19745
|
+
params.hostInitiated,
|
|
19746
|
+
deps.boxId,
|
|
19747
|
+
"git.push",
|
|
19748
|
+
incomingHashGit
|
|
19749
|
+
) ?? false);
|
|
19750
|
+
if (action.method === "git.push" && !isAgentboxBranch && tokenClaimedGit && !hostInitiatedOk) {
|
|
19751
|
+
return {
|
|
19752
|
+
exitCode: 10,
|
|
19753
|
+
stdout: "",
|
|
19754
|
+
stderr: "host-initiated token rejected: invalid, expired, or bound to different params\n"
|
|
19755
|
+
};
|
|
19756
|
+
}
|
|
19757
|
+
if (action.method === "git.push" && !isAgentboxBranch && !hostInitiatedOk && deps.prompts && deps.subscribers) {
|
|
19177
19758
|
const hasSubscriber = deps.subscribers.forBox(deps.boxId).length > 0;
|
|
19178
19759
|
if (!hasSubscriber && process.env["AGENTBOX_PROMPT"] !== "off") {
|
|
19179
19760
|
const noSubMode = (process.env["AGENTBOX_GIT_PUSH_NO_SUB"] ?? "deny").toLowerCase();
|
|
@@ -19248,10 +19829,45 @@ async function runGitRpc(action, deps) {
|
|
|
19248
19829
|
for (const a2 of params.args) if (typeof a2 === "string") argv.push(a2);
|
|
19249
19830
|
}
|
|
19250
19831
|
const push = await execa("git", argv, { reject: false });
|
|
19832
|
+
let pushStderr = push.stderr ?? "";
|
|
19833
|
+
if ((push.exitCode ?? 1) === 0 && !branch.startsWith("agentbox/")) {
|
|
19834
|
+
try {
|
|
19835
|
+
const sha = await execa(
|
|
19836
|
+
"git",
|
|
19837
|
+
["-C", lookup.workspacePath, "rev-parse", branch],
|
|
19838
|
+
{ reject: false }
|
|
19839
|
+
);
|
|
19840
|
+
const shaText = (sha.stdout ?? "").trim();
|
|
19841
|
+
if (sha.exitCode === 0 && shaText.length > 0) {
|
|
19842
|
+
const updateRef = await backend.exec(
|
|
19843
|
+
handle,
|
|
19844
|
+
`git -C ${shellQuote(containerPath)} update-ref refs/remotes/${remote2}/${branch} ${shellQuote(shaText)}`
|
|
19845
|
+
);
|
|
19846
|
+
if (updateRef.exitCode !== 0) {
|
|
19847
|
+
pushStderr += `
|
|
19848
|
+
relay: post-push in-box update-ref refs/remotes/${remote2}/${branch} failed: ${updateRef.stderr || updateRef.stdout}`;
|
|
19849
|
+
}
|
|
19850
|
+
const setUpstream = await backend.exec(
|
|
19851
|
+
handle,
|
|
19852
|
+
`git -C ${shellQuote(containerPath)} branch --set-upstream-to=${remote2}/${branch} ${shellQuote(branch)}`
|
|
19853
|
+
);
|
|
19854
|
+
if (setUpstream.exitCode !== 0) {
|
|
19855
|
+
pushStderr += `
|
|
19856
|
+
relay: post-push in-box --set-upstream-to=${remote2}/${branch} failed: ${setUpstream.stderr || setUpstream.stdout}`;
|
|
19857
|
+
}
|
|
19858
|
+
} else {
|
|
19859
|
+
pushStderr += `
|
|
19860
|
+
relay: post-push rev-parse ${branch} failed on host; skipping in-box origin/upstream sync`;
|
|
19861
|
+
}
|
|
19862
|
+
} catch (err) {
|
|
19863
|
+
pushStderr += `
|
|
19864
|
+
relay: post-push in-box origin/upstream sync threw: ${err instanceof Error ? err.message : String(err)}`;
|
|
19865
|
+
}
|
|
19866
|
+
}
|
|
19251
19867
|
return {
|
|
19252
19868
|
exitCode: push.exitCode ?? 1,
|
|
19253
19869
|
stdout: push.stdout ?? "",
|
|
19254
|
-
stderr:
|
|
19870
|
+
stderr: pushStderr
|
|
19255
19871
|
};
|
|
19256
19872
|
}
|
|
19257
19873
|
const remote = params.remote ?? "origin";
|
|
@@ -19363,6 +19979,8 @@ function createRelayServer(opts) {
|
|
|
19363
19979
|
const prompts = new PendingPrompts();
|
|
19364
19980
|
const subscribers = new PromptSubscribers();
|
|
19365
19981
|
const notices = new BoxNotices(subscribers);
|
|
19982
|
+
const hostInitiatedTokens = new HostInitiatedTokens();
|
|
19983
|
+
let queuePoke = null;
|
|
19366
19984
|
const host = opts.host ?? "0.0.0.0";
|
|
19367
19985
|
const mode = opts.mode ?? "host";
|
|
19368
19986
|
const hostActions = mode === "box" ? new HostActionQueue() : null;
|
|
@@ -19373,7 +19991,7 @@ function createRelayServer(opts) {
|
|
|
19373
19991
|
let pollers = null;
|
|
19374
19992
|
async function getPollers() {
|
|
19375
19993
|
if (!pollers) {
|
|
19376
|
-
const mod = await Promise.resolve().then(() => (
|
|
19994
|
+
const mod = await Promise.resolve().then(() => (init_cloud_poller_SUNA6ZQC(), cloud_poller_SUNA6ZQC_exports));
|
|
19377
19995
|
pollers = new mod.CloudBoxPollers();
|
|
19378
19996
|
}
|
|
19379
19997
|
return pollers;
|
|
@@ -19390,7 +20008,13 @@ function createRelayServer(opts) {
|
|
|
19390
20008
|
const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "relay"}`);
|
|
19391
20009
|
const route = `${req.method ?? "GET"} ${url.pathname}`;
|
|
19392
20010
|
if (route === "GET /healthz") {
|
|
19393
|
-
send(res, 200, {
|
|
20011
|
+
send(res, 200, {
|
|
20012
|
+
ok: true,
|
|
20013
|
+
boxes: registry.size(),
|
|
20014
|
+
events: events.size(),
|
|
20015
|
+
pid: process.pid,
|
|
20016
|
+
cliEntry: Boolean(process.env.AGENTBOX_CLI_ENTRY)
|
|
20017
|
+
});
|
|
19394
20018
|
return;
|
|
19395
20019
|
}
|
|
19396
20020
|
if (url.pathname.startsWith("/bridge/")) {
|
|
@@ -19495,21 +20119,36 @@ function createRelayServer(opts) {
|
|
|
19495
20119
|
if (body.method === "git.push" || body.method === "git.fetch") {
|
|
19496
20120
|
if (body.method === "git.push") {
|
|
19497
20121
|
const params = body.params;
|
|
19498
|
-
const
|
|
19499
|
-
|
|
19500
|
-
|
|
19501
|
-
|
|
19502
|
-
|
|
19503
|
-
|
|
19504
|
-
|
|
19505
|
-
|
|
19506
|
-
|
|
19507
|
-
|
|
19508
|
-
|
|
19509
|
-
if (verdict.answer !== "y") {
|
|
19510
|
-
send(res, 500, { exitCode: 10, stdout: "", stderr: "denied by user\n" });
|
|
20122
|
+
const worktree = resolveWorktree(reg, params?.path ?? "/workspace");
|
|
20123
|
+
const isAgentboxBranch = worktree?.branch.startsWith("agentbox/") ?? false;
|
|
20124
|
+
const tokenClaimed = typeof params?.hostInitiated === "string";
|
|
20125
|
+
const incomingHash = hashRpcParams(params);
|
|
20126
|
+
const hostInitiatedOk = !isAgentboxBranch && tokenClaimed && hostInitiatedTokens.consume(params?.hostInitiated, reg.boxId, "git.push", incomingHash);
|
|
20127
|
+
if (!isAgentboxBranch && tokenClaimed && !hostInitiatedOk) {
|
|
20128
|
+
send(res, 500, {
|
|
20129
|
+
exitCode: 10,
|
|
20130
|
+
stdout: "",
|
|
20131
|
+
stderr: "host-initiated token rejected: invalid, expired, or bound to different params\n"
|
|
20132
|
+
});
|
|
19511
20133
|
return;
|
|
19512
20134
|
}
|
|
20135
|
+
if (!isAgentboxBranch && !hostInitiatedOk) {
|
|
20136
|
+
const verdict = await askPrompt(prompts, subscribers, reg.boxId, {
|
|
20137
|
+
kind: "confirm",
|
|
20138
|
+
message: `Allow git push from box ${reg.name}?`,
|
|
20139
|
+
detail: `${params?.remote ?? "origin"} ${(params?.args ?? []).join(" ")}`.trim(),
|
|
20140
|
+
defaultAnswer: "n",
|
|
20141
|
+
context: {
|
|
20142
|
+
command: "git push",
|
|
20143
|
+
cwd: params?.path,
|
|
20144
|
+
argv: params?.args
|
|
20145
|
+
}
|
|
20146
|
+
});
|
|
20147
|
+
if (verdict.answer !== "y") {
|
|
20148
|
+
send(res, 500, { exitCode: 10, stdout: "", stderr: "denied by user\n" });
|
|
20149
|
+
return;
|
|
20150
|
+
}
|
|
20151
|
+
}
|
|
19513
20152
|
}
|
|
19514
20153
|
const result = await handleGitRpc(reg, body.method, body.params);
|
|
19515
20154
|
const status2 = result.exitCode === 0 ? 200 : 500;
|
|
@@ -19542,6 +20181,33 @@ function createRelayServer(opts) {
|
|
|
19542
20181
|
send(res, status2, result);
|
|
19543
20182
|
return;
|
|
19544
20183
|
}
|
|
20184
|
+
if (body.method.startsWith("gh.pr.")) {
|
|
20185
|
+
const op = body.method.slice("gh.pr.".length);
|
|
20186
|
+
if (!isGhPrOp(op)) {
|
|
20187
|
+
send(res, 400, { error: `unknown gh.pr.* op: ${op}` });
|
|
20188
|
+
return;
|
|
20189
|
+
}
|
|
20190
|
+
const result = await handleGhPrRpc(
|
|
20191
|
+
op,
|
|
20192
|
+
reg,
|
|
20193
|
+
body.params,
|
|
20194
|
+
prompts,
|
|
20195
|
+
subscribers,
|
|
20196
|
+
hostInitiatedTokens
|
|
20197
|
+
);
|
|
20198
|
+
const status2 = result.exitCode === 0 ? 200 : 500;
|
|
20199
|
+
send(res, status2, result);
|
|
20200
|
+
return;
|
|
20201
|
+
}
|
|
20202
|
+
if (body.method === "git.clone" || body.method === "gh.repo.clone") {
|
|
20203
|
+
send(res, 501, {
|
|
20204
|
+
exitCode: 64,
|
|
20205
|
+
stdout: "",
|
|
20206
|
+
stderr: `${body.method}: not yet implemented (deferred; see docs/plans/gh-and-git-shims-host-only.md). Run \`gh\` / \`git\` on the host directly for now.
|
|
20207
|
+
`
|
|
20208
|
+
});
|
|
20209
|
+
return;
|
|
20210
|
+
}
|
|
19545
20211
|
if (body.method === "download.workspace" || body.method === "download.env" || body.method === "download.config" || body.method === "download.claude") {
|
|
19546
20212
|
const params = body.params;
|
|
19547
20213
|
const kind = body.method.split(".")[1] ?? "workspace";
|
|
@@ -19678,6 +20344,7 @@ function createRelayServer(opts) {
|
|
|
19678
20344
|
boxName: reg.name,
|
|
19679
20345
|
prompts,
|
|
19680
20346
|
subscribers,
|
|
20347
|
+
hostInitiatedTokens,
|
|
19681
20348
|
log
|
|
19682
20349
|
});
|
|
19683
20350
|
await respond(result);
|
|
@@ -19690,6 +20357,11 @@ function createRelayServer(opts) {
|
|
|
19690
20357
|
});
|
|
19691
20358
|
}
|
|
19692
20359
|
} : void 0,
|
|
20360
|
+
// Self-heal a dead preview transport (hetzner SSH `-L` after a
|
|
20361
|
+
// ControlMaster death). The relay strips the `cloud:` prefix
|
|
20362
|
+
// the cloud-provider tags onto BoxRecord.container — what the
|
|
20363
|
+
// backend's `get(sandboxId)` expects is the bare sandbox id.
|
|
20364
|
+
recoverPreviewUrl: reg.backend ? async () => refreshCloudPreviewUrl(reg.backend, reg.boxId, DEFAULT_BOX_RELAY_PORT) : void 0,
|
|
19693
20365
|
logger: log
|
|
19694
20366
|
});
|
|
19695
20367
|
} catch (err) {
|
|
@@ -19804,6 +20476,27 @@ data: {"ts":"${(/* @__PURE__ */ new Date()).toISOString()}"}
|
|
|
19804
20476
|
send(res, 204, null);
|
|
19805
20477
|
return;
|
|
19806
20478
|
}
|
|
20479
|
+
if (route === "POST /admin/host-initiated/mint") {
|
|
20480
|
+
const body = await readJsonBody(req);
|
|
20481
|
+
if (!body || typeof body.boxId !== "string" || body.boxId.length === 0 || typeof body.method !== "string" || body.method.length === 0) {
|
|
20482
|
+
send(res, 400, { error: "expected {boxId, method, paramsHash, ttlMs?}" });
|
|
20483
|
+
return;
|
|
20484
|
+
}
|
|
20485
|
+
let paramsHash;
|
|
20486
|
+
if (body.paramsHash === null || body.paramsHash === void 0) {
|
|
20487
|
+
paramsHash = null;
|
|
20488
|
+
} else if (typeof body.paramsHash === "string" && /^[0-9a-f]{64}$/.test(body.paramsHash)) {
|
|
20489
|
+
paramsHash = body.paramsHash;
|
|
20490
|
+
} else {
|
|
20491
|
+
send(res, 400, { error: "paramsHash must be a 64-hex sha256 string or null" });
|
|
20492
|
+
return;
|
|
20493
|
+
}
|
|
20494
|
+
const ttlMs = typeof body.ttlMs === "number" && Number.isFinite(body.ttlMs) && body.ttlMs > 0 ? body.ttlMs : void 0;
|
|
20495
|
+
const token = hostInitiatedTokens.mint(body.boxId, body.method, paramsHash, ttlMs);
|
|
20496
|
+
log(`host-initiated-mint box=${body.boxId} method=${body.method} paramsBound=${paramsHash !== null}`);
|
|
20497
|
+
send(res, 200, { token });
|
|
20498
|
+
return;
|
|
20499
|
+
}
|
|
19807
20500
|
if (route === "POST /admin/notices/set") {
|
|
19808
20501
|
const body = await readJsonBody(req);
|
|
19809
20502
|
if (!body || typeof body.boxId !== "string" || body.boxId.length === 0 || typeof body.kind !== "string" || body.kind.length === 0 || typeof body.message !== "string" || body.message.length === 0) {
|
|
@@ -19816,6 +20509,17 @@ data: {"ts":"${(/* @__PURE__ */ new Date()).toISOString()}"}
|
|
|
19816
20509
|
send(res, 200, { id });
|
|
19817
20510
|
return;
|
|
19818
20511
|
}
|
|
20512
|
+
if (route === "POST /admin/queue/enqueue") {
|
|
20513
|
+
const body = await readJsonBody(req);
|
|
20514
|
+
if (!body || typeof body.id !== "string" || body.id.length === 0) {
|
|
20515
|
+
send(res, 400, { error: "expected {id}" });
|
|
20516
|
+
return;
|
|
20517
|
+
}
|
|
20518
|
+
log(`queue-enqueue id=${body.id}`);
|
|
20519
|
+
queuePoke?.();
|
|
20520
|
+
send(res, 204, null);
|
|
20521
|
+
return;
|
|
20522
|
+
}
|
|
19819
20523
|
if (route === "POST /admin/notices/clear") {
|
|
19820
20524
|
const body = await readJsonBody(req);
|
|
19821
20525
|
if (!body || typeof body.id !== "string" || body.id.length === 0) {
|
|
@@ -19852,6 +20556,9 @@ data: {"ts":"${(/* @__PURE__ */ new Date()).toISOString()}"}
|
|
|
19852
20556
|
notices,
|
|
19853
20557
|
hostActions: hostActions ?? void 0,
|
|
19854
20558
|
url: `http://${host}:${String(opts.port)}`,
|
|
20559
|
+
setQueuePoke: (fn) => {
|
|
20560
|
+
queuePoke = fn;
|
|
20561
|
+
},
|
|
19855
20562
|
close: async () => {
|
|
19856
20563
|
if (pollers) await pollers.stopAll();
|
|
19857
20564
|
await new Promise((resolve2, reject) => {
|
|
@@ -19903,7 +20610,71 @@ async function handleGitRpc(reg, method, params) {
|
|
|
19903
20610
|
if (typeof a2 === "string") argv.push(a2);
|
|
19904
20611
|
}
|
|
19905
20612
|
}
|
|
19906
|
-
|
|
20613
|
+
const result = await runHostCommand(argv);
|
|
20614
|
+
if (method === "git.push" && result.exitCode === 0 && !worktree.branch.startsWith("agentbox/")) {
|
|
20615
|
+
await runHostCommand([
|
|
20616
|
+
"git",
|
|
20617
|
+
"-C",
|
|
20618
|
+
worktree.hostMainRepo,
|
|
20619
|
+
"branch",
|
|
20620
|
+
`--set-upstream-to=${remote}/${worktree.branch}`,
|
|
20621
|
+
worktree.branch
|
|
20622
|
+
]);
|
|
20623
|
+
}
|
|
20624
|
+
return result;
|
|
20625
|
+
}
|
|
20626
|
+
async function handleGhPrRpc(op, reg, params, prompts, subscribers, hostInitiatedTokens) {
|
|
20627
|
+
const mergeBypass = refuseMergeBypass(op);
|
|
20628
|
+
if (mergeBypass) return mergeBypass;
|
|
20629
|
+
const checkoutOptIn = refuseCheckoutByDefault(op);
|
|
20630
|
+
if (checkoutOptIn) return checkoutOptIn;
|
|
20631
|
+
const containerPath = params?.path ?? "/workspace";
|
|
20632
|
+
const worktree = resolveWorktree(reg, containerPath);
|
|
20633
|
+
if (!worktree) {
|
|
20634
|
+
return {
|
|
20635
|
+
exitCode: 64,
|
|
20636
|
+
stdout: "",
|
|
20637
|
+
stderr: `no worktree registered for box ${reg.boxId} matching ${containerPath}`
|
|
20638
|
+
};
|
|
20639
|
+
}
|
|
20640
|
+
const ghReady = await assertGhReady();
|
|
20641
|
+
if (ghReady) return ghReady;
|
|
20642
|
+
const args = Array.isArray(params?.args) ? params.args.filter((a2) => typeof a2 === "string") : [];
|
|
20643
|
+
if (op === "checkout") {
|
|
20644
|
+
const branches = (reg.worktrees ?? []).map((w) => w.branch);
|
|
20645
|
+
const guard = await checkoutGuards(worktree.hostMainRepo, branches);
|
|
20646
|
+
if (guard) return guard;
|
|
20647
|
+
}
|
|
20648
|
+
const tokenClaimedGh = typeof params?.hostInitiated === "string";
|
|
20649
|
+
const incomingHashGh = hashRpcParams(params);
|
|
20650
|
+
const hostInitiatedOk = !GH_PR_READ_ONLY_OPS.has(op) && tokenClaimedGh && hostInitiatedTokens.consume(params?.hostInitiated, reg.boxId, `gh.pr.${op}`, incomingHashGh);
|
|
20651
|
+
if (!GH_PR_READ_ONLY_OPS.has(op) && tokenClaimedGh && !hostInitiatedOk) {
|
|
20652
|
+
return {
|
|
20653
|
+
exitCode: 10,
|
|
20654
|
+
stdout: "",
|
|
20655
|
+
stderr: "host-initiated token rejected: invalid, expired, or bound to different params\n"
|
|
20656
|
+
};
|
|
20657
|
+
}
|
|
20658
|
+
if (!GH_PR_READ_ONLY_OPS.has(op) && !hostInitiatedOk) {
|
|
20659
|
+
const detail = args.join(" ").slice(0, 200);
|
|
20660
|
+
const verdict = await askPrompt(prompts, subscribers, reg.boxId, {
|
|
20661
|
+
kind: "confirm",
|
|
20662
|
+
message: `Allow gh pr ${op} from box ${reg.name}?`,
|
|
20663
|
+
detail,
|
|
20664
|
+
defaultAnswer: "n",
|
|
20665
|
+
context: {
|
|
20666
|
+
command: `gh pr ${op}`,
|
|
20667
|
+
cwd: containerPath,
|
|
20668
|
+
argv: args
|
|
20669
|
+
}
|
|
20670
|
+
});
|
|
20671
|
+
if (verdict.answer !== "y") {
|
|
20672
|
+
return { exitCode: 10, stdout: "", stderr: "denied by user\n" };
|
|
20673
|
+
}
|
|
20674
|
+
}
|
|
20675
|
+
const finalArgs = injectPrCreateHead(op, worktree.branch, args);
|
|
20676
|
+
if (prCreateNeedsHead(op, finalArgs)) return PR_CREATE_NO_HEAD_REFUSAL;
|
|
20677
|
+
return runHostGh(["pr", op, ...finalArgs], worktree.hostMainRepo);
|
|
19907
20678
|
}
|
|
19908
20679
|
async function handleCpRpc(reg, method, params) {
|
|
19909
20680
|
const entry = process.env.AGENTBOX_CLI_ENTRY;
|
|
@@ -19964,7 +20735,7 @@ function runHostCommand(argv, timeoutMs = GIT_RPC_TIMEOUT_MS) {
|
|
|
19964
20735
|
resolve2({ exitCode: 64, stdout: "", stderr: "empty command" });
|
|
19965
20736
|
return;
|
|
19966
20737
|
}
|
|
19967
|
-
const child = (0,
|
|
20738
|
+
const child = (0, import_child_process2.spawn)(cmd, rest, {
|
|
19968
20739
|
env: process.env,
|
|
19969
20740
|
stdio: ["ignore", "pipe", "pipe"]
|
|
19970
20741
|
});
|
|
@@ -20011,6 +20782,8 @@ async function startRelayServer(opts) {
|
|
|
20011
20782
|
});
|
|
20012
20783
|
return handle;
|
|
20013
20784
|
}
|
|
20785
|
+
var QUEUE_DIR = (0, import_path6.join)(STATE_DIR, "queue");
|
|
20786
|
+
var QUEUE_LOGS_DIR = (0, import_path6.join)(STATE_DIR, "logs");
|
|
20014
20787
|
|
|
20015
20788
|
// src/config.ts
|
|
20016
20789
|
var import_promises16 = require("fs/promises");
|
|
@@ -20321,7 +21094,7 @@ function assertBool(raw, where) {
|
|
|
20321
21094
|
if (typeof raw !== "boolean") throw new ConfigError(`${where} must be a boolean`);
|
|
20322
21095
|
return raw;
|
|
20323
21096
|
}
|
|
20324
|
-
var TOP_LEVEL_KEYS = /* @__PURE__ */ new Set(["services", "tasks", "ide", "defaults"]);
|
|
21097
|
+
var TOP_LEVEL_KEYS = /* @__PURE__ */ new Set(["services", "tasks", "ide", "defaults", "carry"]);
|
|
20325
21098
|
function validateUnitGraph(tasks, services) {
|
|
20326
21099
|
const names = /* @__PURE__ */ new Set();
|
|
20327
21100
|
for (const t of tasks) {
|
|
@@ -20441,8 +21214,95 @@ function describeCommand(cmd) {
|
|
|
20441
21214
|
return Array.isArray(cmd) ? cmd.join(" ") : cmd;
|
|
20442
21215
|
}
|
|
20443
21216
|
|
|
20444
|
-
// src/
|
|
21217
|
+
// src/codex-scraper.ts
|
|
20445
21218
|
var import_node_child_process7 = require("child_process");
|
|
21219
|
+
var DEFAULT_INTERVAL_MS = 1e3;
|
|
21220
|
+
var DEFAULT_SESSION = "codex";
|
|
21221
|
+
var PATTERNS = [
|
|
21222
|
+
// Permission / approval prompts (highest priority — these mean codex is blocked).
|
|
21223
|
+
{ re: /Hooks need review|Trust all and continue/i, state: "waiting" },
|
|
21224
|
+
{ re: /Do you trust the contents of this directory/i, state: "waiting" },
|
|
21225
|
+
{ re: /Allow this command\?|Approve this (command|tool)\?|Press y\/n|\[Y\/n\]/m, state: "waiting" },
|
|
21226
|
+
{ re: /Waiting for (your |user )?(response|input|approval|permission)/i, state: "waiting" },
|
|
21227
|
+
// Compaction (codex's `/compact` command and auto-compaction).
|
|
21228
|
+
{ re: /Compacting (conversation|context)|Summariz(e|ing) (the )?conversation/i, state: "compacting" },
|
|
21229
|
+
// Failure / fatal-error frames.
|
|
21230
|
+
{ re: /\bError:|\bFailed:|^Traceback /m, state: "error" },
|
|
21231
|
+
// Active work signals — pinned to specific codex TUI fragments to avoid
|
|
21232
|
+
// matching every line of english that contains "working" or "running"
|
|
21233
|
+
// (e.g. the directory-trust prompt's "Working with untrusted contents"
|
|
21234
|
+
// warning is NOT a working state).
|
|
21235
|
+
{
|
|
21236
|
+
re: /\b(Thinking\.\.\.|Worked for \d|Streaming response|tool call \w|Running command|Generating response|Reasoning\.\.\.|Editing \w)/m,
|
|
21237
|
+
state: "working"
|
|
21238
|
+
},
|
|
21239
|
+
// Idle: codex shows a status line `gpt-5.5 high · /workspace` (or similar
|
|
21240
|
+
// model · cwd footer) at the bottom of the input prompt when ready for
|
|
21241
|
+
// input. Lower priority than every "busy" pattern above so an in-flight
|
|
21242
|
+
// turn that still shows the footer correctly registers as working.
|
|
21243
|
+
{ re: /gpt-\d+(\.\d+)?(-\w+)?\s+(low|medium|high|xhigh)\b|OpenAI Codex \(v\d/i, state: "idle" }
|
|
21244
|
+
];
|
|
21245
|
+
function startCodexScraper(opts) {
|
|
21246
|
+
const sessionName = opts.sessionName ?? DEFAULT_SESSION;
|
|
21247
|
+
const intervalMs = opts.intervalMs ?? DEFAULT_INTERVAL_MS;
|
|
21248
|
+
const capture = opts.capturePane ?? defaultCapturePane;
|
|
21249
|
+
let lastState = null;
|
|
21250
|
+
let lastSessionPresent = false;
|
|
21251
|
+
let stopped = false;
|
|
21252
|
+
const tick = async () => {
|
|
21253
|
+
if (stopped) return;
|
|
21254
|
+
try {
|
|
21255
|
+
const pane = await capture(sessionName);
|
|
21256
|
+
if (pane === null) {
|
|
21257
|
+
lastSessionPresent = false;
|
|
21258
|
+
return;
|
|
21259
|
+
}
|
|
21260
|
+
if (!lastSessionPresent) {
|
|
21261
|
+
opts.reporter.setCodexState("idle");
|
|
21262
|
+
lastState = "idle";
|
|
21263
|
+
lastSessionPresent = true;
|
|
21264
|
+
}
|
|
21265
|
+
const matched = matchState(pane);
|
|
21266
|
+
if (matched !== null && matched !== lastState) {
|
|
21267
|
+
opts.reporter.setCodexState(matched);
|
|
21268
|
+
lastState = matched;
|
|
21269
|
+
}
|
|
21270
|
+
} catch {
|
|
21271
|
+
}
|
|
21272
|
+
};
|
|
21273
|
+
const timer = setInterval(() => void tick(), intervalMs);
|
|
21274
|
+
timer.unref();
|
|
21275
|
+
void tick();
|
|
21276
|
+
return {
|
|
21277
|
+
stop() {
|
|
21278
|
+
stopped = true;
|
|
21279
|
+
clearInterval(timer);
|
|
21280
|
+
}
|
|
21281
|
+
};
|
|
21282
|
+
}
|
|
21283
|
+
function matchState(pane) {
|
|
21284
|
+
for (const { re, state } of PATTERNS) {
|
|
21285
|
+
if (re.test(pane)) return state;
|
|
21286
|
+
}
|
|
21287
|
+
return null;
|
|
21288
|
+
}
|
|
21289
|
+
function defaultCapturePane(sessionName) {
|
|
21290
|
+
return new Promise((resolve2) => {
|
|
21291
|
+
const child = (0, import_node_child_process7.spawn)("tmux", ["capture-pane", "-p", "-t", sessionName], {
|
|
21292
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
21293
|
+
});
|
|
21294
|
+
let stdout = "";
|
|
21295
|
+
child.stdout.on("data", (b) => stdout += b.toString("utf8"));
|
|
21296
|
+
child.on("error", () => resolve2(null));
|
|
21297
|
+
child.on("close", (code) => {
|
|
21298
|
+
if (code === 0) resolve2(stdout);
|
|
21299
|
+
else resolve2(null);
|
|
21300
|
+
});
|
|
21301
|
+
});
|
|
21302
|
+
}
|
|
21303
|
+
|
|
21304
|
+
// src/supervisor.ts
|
|
21305
|
+
var import_node_child_process8 = require("child_process");
|
|
20446
21306
|
var import_node_events15 = require("events");
|
|
20447
21307
|
var import_node_fs7 = require("fs");
|
|
20448
21308
|
var import_promises18 = require("fs/promises");
|
|
@@ -20708,7 +21568,7 @@ var cachedLoginPath;
|
|
|
20708
21568
|
function loginShellPath() {
|
|
20709
21569
|
if (cachedLoginPath !== void 0) return cachedLoginPath;
|
|
20710
21570
|
try {
|
|
20711
|
-
const out = (0,
|
|
21571
|
+
const out = (0, import_node_child_process8.execFileSync)("bash", ["-lc", 'printf %s "$PATH"'], {
|
|
20712
21572
|
encoding: "utf8",
|
|
20713
21573
|
timeout: 5e3
|
|
20714
21574
|
}).trim();
|
|
@@ -20723,7 +21583,7 @@ var ServiceRunner = class extends import_node_events15.EventEmitter {
|
|
|
20723
21583
|
super();
|
|
20724
21584
|
this.spec = spec;
|
|
20725
21585
|
this.opts = opts;
|
|
20726
|
-
this.spawnFn = opts.spawn ??
|
|
21586
|
+
this.spawnFn = opts.spawn ?? import_node_child_process8.spawn;
|
|
20727
21587
|
this.setTimer = opts.setTimer ?? ((fn, ms) => setTimeout(fn, ms));
|
|
20728
21588
|
this.clearTimer = opts.clearTimer ?? ((h2) => {
|
|
20729
21589
|
clearTimeout(h2);
|
|
@@ -20954,7 +21814,7 @@ var TaskRunner = class extends import_node_events15.EventEmitter {
|
|
|
20954
21814
|
super();
|
|
20955
21815
|
this.spec = spec;
|
|
20956
21816
|
this.opts = opts;
|
|
20957
|
-
this.spawnFn = opts.spawn ??
|
|
21817
|
+
this.spawnFn = opts.spawn ?? import_node_child_process8.spawn;
|
|
20958
21818
|
}
|
|
20959
21819
|
spec;
|
|
20960
21820
|
opts;
|
|
@@ -21502,10 +22362,10 @@ var import_promises19 = require("fs/promises");
|
|
|
21502
22362
|
var import_node_path7 = require("path");
|
|
21503
22363
|
|
|
21504
22364
|
// src/status-reporter.ts
|
|
21505
|
-
var
|
|
22365
|
+
var import_node_child_process10 = require("child_process");
|
|
21506
22366
|
|
|
21507
22367
|
// src/tmux.ts
|
|
21508
|
-
var
|
|
22368
|
+
var import_node_child_process9 = require("child_process");
|
|
21509
22369
|
var import_node_os4 = require("os");
|
|
21510
22370
|
var MAX_TITLE_LEN = 120;
|
|
21511
22371
|
function sanitizePaneTitle(raw, ctx) {
|
|
@@ -21519,7 +22379,7 @@ function sanitizePaneTitle(raw, ctx) {
|
|
|
21519
22379
|
}
|
|
21520
22380
|
function runTool(cmd, args) {
|
|
21521
22381
|
return new Promise((resolve2) => {
|
|
21522
|
-
const child = (0,
|
|
22382
|
+
const child = (0, import_node_child_process9.spawn)(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
21523
22383
|
let stdout = "";
|
|
21524
22384
|
let stderr = "";
|
|
21525
22385
|
child.stdout.on("data", (b) => stdout += b.toString("utf8"));
|
|
@@ -21565,8 +22425,12 @@ var StatusReporter = class {
|
|
|
21565
22425
|
periodicMs;
|
|
21566
22426
|
claudeState = "unknown";
|
|
21567
22427
|
claudeUpdatedAt = null;
|
|
22428
|
+
claudePlan;
|
|
22429
|
+
claudeQuestion;
|
|
21568
22430
|
codexState = "unknown";
|
|
21569
22431
|
codexUpdatedAt = null;
|
|
22432
|
+
opencodeState = "unknown";
|
|
22433
|
+
opencodeUpdatedAt = null;
|
|
21570
22434
|
debounceTimer = null;
|
|
21571
22435
|
periodicTimer = null;
|
|
21572
22436
|
onChange = () => this.schedulePush();
|
|
@@ -21595,9 +22459,21 @@ var StatusReporter = class {
|
|
|
21595
22459
|
this.periodicTimer = null;
|
|
21596
22460
|
}
|
|
21597
22461
|
}
|
|
21598
|
-
setClaudeState(state) {
|
|
22462
|
+
setClaudeState(state, payload) {
|
|
22463
|
+
const sticky = this.claudeState === "end-plan" || this.claudeState === "question";
|
|
22464
|
+
if (state === "working" && sticky && !payload?.clearPending) return;
|
|
21599
22465
|
this.claudeState = state;
|
|
21600
22466
|
this.claudeUpdatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
22467
|
+
if (payload?.clearPending) {
|
|
22468
|
+
this.claudePlan = void 0;
|
|
22469
|
+
this.claudeQuestion = void 0;
|
|
22470
|
+
}
|
|
22471
|
+
if (state === "end-plan" && payload?.plan) {
|
|
22472
|
+
this.claudePlan = payload.plan;
|
|
22473
|
+
}
|
|
22474
|
+
if (state === "question" && payload?.question) {
|
|
22475
|
+
this.claudeQuestion = payload.question;
|
|
22476
|
+
}
|
|
21601
22477
|
this.schedulePush();
|
|
21602
22478
|
}
|
|
21603
22479
|
setCodexState(state) {
|
|
@@ -21605,6 +22481,11 @@ var StatusReporter = class {
|
|
|
21605
22481
|
this.codexUpdatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
21606
22482
|
this.schedulePush();
|
|
21607
22483
|
}
|
|
22484
|
+
setOpencodeState(state) {
|
|
22485
|
+
this.opencodeState = state;
|
|
22486
|
+
this.opencodeUpdatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
22487
|
+
this.schedulePush();
|
|
22488
|
+
}
|
|
21608
22489
|
/** Forced immediate push (used on shutdown). */
|
|
21609
22490
|
flush() {
|
|
21610
22491
|
if (this.debounceTimer) {
|
|
@@ -21654,7 +22535,9 @@ var StatusReporter = class {
|
|
|
21654
22535
|
state: this.claudeState,
|
|
21655
22536
|
updatedAt: this.claudeUpdatedAt,
|
|
21656
22537
|
sessionRunning: claudeSession2.running,
|
|
21657
|
-
...claudeSession2.title ? { sessionTitle: claudeSession2.title } : {}
|
|
22538
|
+
...claudeSession2.title ? { sessionTitle: claudeSession2.title } : {},
|
|
22539
|
+
...this.claudePlan ? { plan: this.claudePlan } : {},
|
|
22540
|
+
...this.claudeQuestion ? { question: this.claudeQuestion } : {}
|
|
21658
22541
|
}
|
|
21659
22542
|
};
|
|
21660
22543
|
if (codexSession.running || this.codexState !== "unknown") {
|
|
@@ -21665,9 +22548,11 @@ var StatusReporter = class {
|
|
|
21665
22548
|
...codexSession.title ? { sessionTitle: codexSession.title } : {}
|
|
21666
22549
|
};
|
|
21667
22550
|
}
|
|
21668
|
-
if (opencodeSession.running) {
|
|
22551
|
+
if (opencodeSession.running || this.opencodeState !== "unknown") {
|
|
21669
22552
|
status2.opencode = {
|
|
21670
|
-
|
|
22553
|
+
state: this.opencodeState,
|
|
22554
|
+
updatedAt: this.opencodeUpdatedAt,
|
|
22555
|
+
sessionRunning: opencodeSession.running,
|
|
21671
22556
|
...opencodeSession.title ? { sessionTitle: opencodeSession.title } : {}
|
|
21672
22557
|
};
|
|
21673
22558
|
}
|
|
@@ -21687,7 +22572,7 @@ async function collectPorts(supervisor) {
|
|
|
21687
22572
|
}
|
|
21688
22573
|
function run(cmd, args) {
|
|
21689
22574
|
return new Promise((resolve2) => {
|
|
21690
|
-
const child = (0,
|
|
22575
|
+
const child = (0, import_node_child_process10.spawn)(cmd, args, { stdio: ["ignore", "pipe", "ignore"] });
|
|
21691
22576
|
let stdout = "";
|
|
21692
22577
|
child.stdout.on("data", (b) => stdout += b.toString("utf8"));
|
|
21693
22578
|
child.on("error", () => resolve2({ exitCode: 127, stdout }));
|
|
@@ -21844,7 +22729,11 @@ async function handleConnection(sock, opts) {
|
|
|
21844
22729
|
if (!CLAUDE_ACTIVITY_STATES.includes(req.state)) {
|
|
21845
22730
|
writeLine(sock, { ok: false, error: `invalid claude state: ${String(req.state)}` });
|
|
21846
22731
|
} else {
|
|
21847
|
-
opts.reporter?.setClaudeState(req.state
|
|
22732
|
+
opts.reporter?.setClaudeState(req.state, {
|
|
22733
|
+
plan: req.plan,
|
|
22734
|
+
question: req.question,
|
|
22735
|
+
clearPending: req.clearPending
|
|
22736
|
+
});
|
|
21848
22737
|
writeLine(sock, { ok: true, data: "ok" });
|
|
21849
22738
|
}
|
|
21850
22739
|
sock.end();
|
|
@@ -21860,6 +22749,16 @@ async function handleConnection(sock, opts) {
|
|
|
21860
22749
|
sock.end();
|
|
21861
22750
|
return;
|
|
21862
22751
|
}
|
|
22752
|
+
case "opencode-state": {
|
|
22753
|
+
if (!CLAUDE_ACTIVITY_STATES.includes(req.state)) {
|
|
22754
|
+
writeLine(sock, { ok: false, error: `invalid opencode state: ${String(req.state)}` });
|
|
22755
|
+
} else {
|
|
22756
|
+
opts.reporter?.setOpencodeState(req.state);
|
|
22757
|
+
writeLine(sock, { ok: true, data: "ok" });
|
|
22758
|
+
}
|
|
22759
|
+
sock.end();
|
|
22760
|
+
return;
|
|
22761
|
+
}
|
|
21863
22762
|
default: {
|
|
21864
22763
|
writeLine(sock, { ok: false, error: `unknown op` });
|
|
21865
22764
|
sock.end();
|
|
@@ -21913,7 +22812,85 @@ async function* createLineReader(sock) {
|
|
|
21913
22812
|
if (buf.length > 0) yield buf;
|
|
21914
22813
|
}
|
|
21915
22814
|
|
|
22815
|
+
// src/box-relay-forwarder.ts
|
|
22816
|
+
var import_node_http3 = require("http");
|
|
22817
|
+
var ALLOWED_PATHS = /* @__PURE__ */ new Set(["/rpc", "/events"]);
|
|
22818
|
+
function startBoxRelayForwarder(opts) {
|
|
22819
|
+
const log = opts.logger ?? (() => {
|
|
22820
|
+
});
|
|
22821
|
+
const upstream = opts.upstream;
|
|
22822
|
+
const upstreamPort = upstream.port.length > 0 ? Number.parseInt(upstream.port, 10) : upstream.protocol === "https:" ? 443 : 80;
|
|
22823
|
+
const server = (0, import_node_http3.createServer)((req, res) => {
|
|
22824
|
+
const path6 = (req.url ?? "").split("?")[0] ?? "";
|
|
22825
|
+
if (req.method !== "POST" || !ALLOWED_PATHS.has(path6)) {
|
|
22826
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
22827
|
+
res.end("not found");
|
|
22828
|
+
return;
|
|
22829
|
+
}
|
|
22830
|
+
const headers = { ...req.headers };
|
|
22831
|
+
delete headers.host;
|
|
22832
|
+
delete headers.connection;
|
|
22833
|
+
const upstreamReq = (0, import_node_http3.request)(
|
|
22834
|
+
{
|
|
22835
|
+
host: upstream.hostname,
|
|
22836
|
+
port: upstreamPort,
|
|
22837
|
+
method: "POST",
|
|
22838
|
+
path: `${upstream.pathname.replace(/\/$/, "")}${path6}`,
|
|
22839
|
+
headers,
|
|
22840
|
+
// No keep-alive: the relay holds /rpc open for the lifetime of a
|
|
22841
|
+
// host prompt (potentially many seconds). Reusing sockets across
|
|
22842
|
+
// such calls invites mid-stream resets on Node version drift.
|
|
22843
|
+
agent: false
|
|
22844
|
+
},
|
|
22845
|
+
(upstreamRes) => {
|
|
22846
|
+
res.writeHead(upstreamRes.statusCode ?? 502, upstreamRes.headers);
|
|
22847
|
+
upstreamRes.pipe(res);
|
|
22848
|
+
}
|
|
22849
|
+
);
|
|
22850
|
+
upstreamReq.on("error", (err) => {
|
|
22851
|
+
log(`upstream error on ${path6}: ${err.message}`);
|
|
22852
|
+
if (!res.headersSent) {
|
|
22853
|
+
res.writeHead(502, { "Content-Type": "text/plain" });
|
|
22854
|
+
}
|
|
22855
|
+
res.end();
|
|
22856
|
+
});
|
|
22857
|
+
req.on("error", (err) => {
|
|
22858
|
+
log(`client error on ${path6}: ${err.message}`);
|
|
22859
|
+
upstreamReq.destroy();
|
|
22860
|
+
});
|
|
22861
|
+
req.pipe(upstreamReq);
|
|
22862
|
+
});
|
|
22863
|
+
return new Promise((resolve2, reject) => {
|
|
22864
|
+
const onError = (err) => {
|
|
22865
|
+
reject(err);
|
|
22866
|
+
};
|
|
22867
|
+
server.once("error", onError);
|
|
22868
|
+
server.listen(opts.port, "127.0.0.1", () => {
|
|
22869
|
+
server.removeListener("error", onError);
|
|
22870
|
+
resolve2({
|
|
22871
|
+
url: `http://127.0.0.1:${String(opts.port)}`,
|
|
22872
|
+
close: () => new Promise((res) => {
|
|
22873
|
+
server.close(() => res());
|
|
22874
|
+
})
|
|
22875
|
+
});
|
|
22876
|
+
});
|
|
22877
|
+
});
|
|
22878
|
+
}
|
|
22879
|
+
|
|
21916
22880
|
// src/commands/daemon.ts
|
|
22881
|
+
function resolveBoxRelayPort() {
|
|
22882
|
+
const raw = process.env.AGENTBOX_BOX_RELAY_PORT;
|
|
22883
|
+
if (raw === void 0 || raw.length === 0) return DEFAULT_BOX_RELAY_PORT;
|
|
22884
|
+
const n2 = Number.parseInt(raw, 10);
|
|
22885
|
+
if (!Number.isFinite(n2) || n2 < 1 || n2 > 65535) {
|
|
22886
|
+
process.stderr.write(
|
|
22887
|
+
`agentbox-ctl: AGENTBOX_BOX_RELAY_PORT=${raw} is not a valid port; falling back to ${String(DEFAULT_BOX_RELAY_PORT)}
|
|
22888
|
+
`
|
|
22889
|
+
);
|
|
22890
|
+
return DEFAULT_BOX_RELAY_PORT;
|
|
22891
|
+
}
|
|
22892
|
+
return n2;
|
|
22893
|
+
}
|
|
21917
22894
|
var daemonCommand = new Command("daemon").description("Run the agentbox-ctl supervisor in the foreground").option("--socket <path>", "unix socket path", DEFAULT_SOCKET_PATH).option("--config <path>", "path to agentbox.yaml", DEFAULT_CONFIG_PATH).option("--log-dir <path>", "where per-service log files are written", DEFAULT_LOG_DIR).option("--workspace <path>", "cwd for service processes", "/workspace").action(async (opts) => {
|
|
21918
22895
|
const cfg = await loadConfig(opts.config);
|
|
21919
22896
|
const sup = new Supervisor({ workspace: opts.workspace, logDir: opts.logDir });
|
|
@@ -21925,6 +22902,14 @@ var daemonCommand = new Command("daemon").description("Run the agentbox-ctl supe
|
|
|
21925
22902
|
sessionName: DEFAULT_CLAUDE_SESSION_NAME
|
|
21926
22903
|
});
|
|
21927
22904
|
reporter.start();
|
|
22905
|
+
let codexScraper = null;
|
|
22906
|
+
try {
|
|
22907
|
+
codexScraper = startCodexScraper({ reporter });
|
|
22908
|
+
} catch (err) {
|
|
22909
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
22910
|
+
process.stderr.write(`agentbox-ctl: codex scraper failed to start: ${msg}
|
|
22911
|
+
`);
|
|
22912
|
+
}
|
|
21928
22913
|
const server = await startServer({
|
|
21929
22914
|
socketPath: opts.socket,
|
|
21930
22915
|
supervisor: sup,
|
|
@@ -21938,7 +22923,9 @@ var daemonCommand = new Command("daemon").description("Run the agentbox-ctl supe
|
|
|
21938
22923
|
`agentbox-ctl: ${String(cfg.services.length)} service(s), ${String(cfg.tasks.length)} task(s) configured
|
|
21939
22924
|
`
|
|
21940
22925
|
);
|
|
22926
|
+
const boxRelayPort = resolveBoxRelayPort();
|
|
21941
22927
|
let inBoxRelay = null;
|
|
22928
|
+
let inBoxForwarder = null;
|
|
21942
22929
|
if (process.env.AGENTBOX_BOX_KIND === "cloud") {
|
|
21943
22930
|
const bridgeToken = process.env.AGENTBOX_BRIDGE_TOKEN ?? "";
|
|
21944
22931
|
const boxId = process.env.AGENTBOX_BOX_ID ?? "";
|
|
@@ -21951,7 +22938,7 @@ var daemonCommand = new Command("daemon").description("Run the agentbox-ctl supe
|
|
|
21951
22938
|
} else {
|
|
21952
22939
|
try {
|
|
21953
22940
|
inBoxRelay = await startRelayServer({
|
|
21954
|
-
port:
|
|
22941
|
+
port: boxRelayPort,
|
|
21955
22942
|
host: "0.0.0.0",
|
|
21956
22943
|
mode: "box",
|
|
21957
22944
|
bridgeToken,
|
|
@@ -21966,7 +22953,7 @@ var daemonCommand = new Command("daemon").description("Run the agentbox-ctl supe
|
|
|
21966
22953
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
21967
22954
|
});
|
|
21968
22955
|
process.stdout.write(
|
|
21969
|
-
`agentbox-ctl: in-sandbox relay (mode=box) listening on :${String(
|
|
22956
|
+
`agentbox-ctl: in-sandbox relay (mode=box) listening on :${String(boxRelayPort)}
|
|
21970
22957
|
`
|
|
21971
22958
|
);
|
|
21972
22959
|
} catch (err) {
|
|
@@ -21975,15 +22962,35 @@ var daemonCommand = new Command("daemon").description("Run the agentbox-ctl supe
|
|
|
21975
22962
|
`);
|
|
21976
22963
|
}
|
|
21977
22964
|
}
|
|
22965
|
+
} else {
|
|
22966
|
+
const upstreamUrl = process.env.AGENTBOX_HOST_RELAY_URL ?? "http://host.docker.internal:8787";
|
|
22967
|
+
try {
|
|
22968
|
+
inBoxForwarder = await startBoxRelayForwarder({
|
|
22969
|
+
port: boxRelayPort,
|
|
22970
|
+
upstream: new URL(upstreamUrl),
|
|
22971
|
+
logger: (line) => process.stdout.write(`relay(fwd): ${line}
|
|
22972
|
+
`)
|
|
22973
|
+
});
|
|
22974
|
+
process.stdout.write(
|
|
22975
|
+
`agentbox-ctl: in-box relay forwarder listening on :${String(boxRelayPort)} -> ${upstreamUrl}
|
|
22976
|
+
`
|
|
22977
|
+
);
|
|
22978
|
+
} catch (err) {
|
|
22979
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
22980
|
+
process.stderr.write(`agentbox-ctl: in-box relay forwarder failed to start: ${msg}
|
|
22981
|
+
`);
|
|
22982
|
+
}
|
|
21978
22983
|
}
|
|
21979
22984
|
const shutdown = async (signal) => {
|
|
21980
22985
|
process.stdout.write(`agentbox-ctl: ${signal} \u2014 shutting down
|
|
21981
22986
|
`);
|
|
22987
|
+
if (codexScraper) codexScraper.stop();
|
|
21982
22988
|
reporter.stop();
|
|
21983
22989
|
reporter.flush();
|
|
21984
22990
|
server.close();
|
|
21985
22991
|
await sup.stopAll();
|
|
21986
22992
|
if (inBoxRelay) await inBoxRelay.close();
|
|
22993
|
+
if (inBoxForwarder) await inBoxForwarder.close();
|
|
21987
22994
|
process.exit(0);
|
|
21988
22995
|
};
|
|
21989
22996
|
process.on("SIGTERM", () => void shutdown("SIGTERM"));
|
|
@@ -22030,17 +23037,95 @@ var checkpointCommand = new Command("checkpoint").description("Capture this box
|
|
|
22030
23037
|
}
|
|
22031
23038
|
);
|
|
22032
23039
|
|
|
23040
|
+
// src/commands/pr-subcommands.ts
|
|
23041
|
+
var PR_SUBCOMMANDS = [
|
|
23042
|
+
{
|
|
23043
|
+
op: "create",
|
|
23044
|
+
description: "Run `gh pr create` on the host (creates a PR for this box's branch). User is prompted on the host wrapper."
|
|
23045
|
+
},
|
|
23046
|
+
{ op: "view", description: "Run `gh pr view` on the host (read-only; no prompt)." },
|
|
23047
|
+
{ op: "list", description: "Run `gh pr list` on the host (read-only; no prompt)." },
|
|
23048
|
+
{ op: "comment", description: "Run `gh pr comment` on the host (prompted; visible to others)." },
|
|
23049
|
+
{ op: "review", description: "Run `gh pr review` on the host (prompted; visible to others)." },
|
|
23050
|
+
{
|
|
23051
|
+
op: "merge",
|
|
23052
|
+
description: "Run `gh pr merge` on the host (prompted; destructive \u2014 AGENTBOX_PROMPT=off bypass requires AGENTBOX_GH_FORCE=1)."
|
|
23053
|
+
},
|
|
23054
|
+
{
|
|
23055
|
+
op: "checkout",
|
|
23056
|
+
description: "Run `gh pr checkout` on the host (prompted + clean-tree guard; opt-in via AGENTBOX_GH_PR_CHECKOUT=allow because it switches the host main repo branch)."
|
|
23057
|
+
},
|
|
23058
|
+
{ op: "close", description: "Run `gh pr close` on the host (prompted)." },
|
|
23059
|
+
{ op: "reopen", description: "Run `gh pr reopen` on the host (prompted)." }
|
|
23060
|
+
];
|
|
23061
|
+
function buildPrCommand(errorPrefix) {
|
|
23062
|
+
const prCommand = new Command("pr").description(
|
|
23063
|
+
"PR operations via the host `gh` CLI (requires `gh` installed and `gh auth login` on the host)"
|
|
23064
|
+
);
|
|
23065
|
+
for (const spec of PR_SUBCOMMANDS) {
|
|
23066
|
+
prCommand.addCommand(
|
|
23067
|
+
new Command(spec.op).description(spec.description).option("--cwd <path>", "container path identifying which registered worktree to use").addOption(
|
|
23068
|
+
new Option(
|
|
23069
|
+
"--host-initiated-token <token>",
|
|
23070
|
+
"internal: one-time token from the host CLI; skips relay confirm prompt when valid"
|
|
23071
|
+
).hideHelp()
|
|
23072
|
+
).allowExcessArguments(true).allowUnknownOption(true).argument(
|
|
23073
|
+
"[args...]",
|
|
23074
|
+
"extra flags forwarded to `gh pr <op>` verbatim (e.g. `--title`, `--body`, `--label`, `--draft`, `--json`)."
|
|
23075
|
+
).action(async (args, opts) => {
|
|
23076
|
+
const params = { path: opts.cwd ?? process.cwd() };
|
|
23077
|
+
if (args.length > 0) params.args = args;
|
|
23078
|
+
if (opts.hostInitiatedToken) params.hostInitiated = opts.hostInitiatedToken;
|
|
23079
|
+
const code = await postRpcAndExit(`gh.pr.${spec.op}`, params, { errorPrefix });
|
|
23080
|
+
process.exit(code);
|
|
23081
|
+
})
|
|
23082
|
+
);
|
|
23083
|
+
}
|
|
23084
|
+
return prCommand;
|
|
23085
|
+
}
|
|
23086
|
+
|
|
23087
|
+
// src/commands/gh.ts
|
|
23088
|
+
var repoCommand = new Command("repo").description("GitHub repo operations via the host `gh` CLI (host runs `gh repo \u2026` then ships results to the box)").addCommand(
|
|
23089
|
+
new Command("clone").description(
|
|
23090
|
+
"Clone a github repo into the box via host `gh repo clone`. The host clones into a tmpdir with its creds, bundles, and ships the bundle back; the box materialises the working copy and resets origin to the original URL."
|
|
23091
|
+
).option("--cwd <path>", "container path identifying which registered worktree to use (default: cwd)").option("--branch <name>", "pass --branch <name> to host gh repo clone").option("--depth <n>", "pass --depth <n> to host gh repo clone").argument("<repo>", "github repo: owner/name shorthand or full URL").argument("[dir]", "target directory inside the box (default: derived from repo)").action(
|
|
23092
|
+
async (repo, dir, opts) => {
|
|
23093
|
+
const params = {
|
|
23094
|
+
path: opts.cwd ?? process.cwd(),
|
|
23095
|
+
repo
|
|
23096
|
+
};
|
|
23097
|
+
if (dir) params.targetPath = dir;
|
|
23098
|
+
const extra = [];
|
|
23099
|
+
if (opts.branch) extra.push("--branch", opts.branch);
|
|
23100
|
+
if (opts.depth) extra.push("--depth", opts.depth);
|
|
23101
|
+
if (extra.length > 0) params.args = extra;
|
|
23102
|
+
const code = await postRpcAndExit("gh.repo.clone", params, {
|
|
23103
|
+
errorPrefix: "agentbox-ctl gh repo clone"
|
|
23104
|
+
});
|
|
23105
|
+
process.exit(code);
|
|
23106
|
+
}
|
|
23107
|
+
)
|
|
23108
|
+
);
|
|
23109
|
+
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(repoCommand);
|
|
23110
|
+
|
|
22033
23111
|
// src/commands/git.ts
|
|
22034
|
-
var
|
|
23112
|
+
var import_node_child_process11 = require("child_process");
|
|
23113
|
+
function hostInitiatedOption() {
|
|
23114
|
+
return new Option(
|
|
23115
|
+
"--host-initiated-token <token>",
|
|
23116
|
+
"internal: one-time token from the host CLI; skips relay confirm prompt when valid"
|
|
23117
|
+
).hideHelp();
|
|
23118
|
+
}
|
|
22035
23119
|
function buildParams(opts, extra) {
|
|
22036
23120
|
const params = { path: opts.cwd ?? process.cwd() };
|
|
22037
23121
|
if (opts.remote) params.remote = opts.remote;
|
|
22038
23122
|
if (extra.length > 0) params.args = extra;
|
|
23123
|
+
if (opts.hostInitiatedToken) params.hostInitiated = opts.hostInitiatedToken;
|
|
22039
23124
|
return params;
|
|
22040
23125
|
}
|
|
22041
23126
|
function runLocalGit(args, cwd) {
|
|
22042
23127
|
return new Promise((resolve2) => {
|
|
22043
|
-
const child = (0,
|
|
23128
|
+
const child = (0, import_node_child_process11.spawn)("git", args, { cwd, stdio: "inherit" });
|
|
22044
23129
|
child.on("close", (code) => resolve2(code ?? 1));
|
|
22045
23130
|
child.on("error", (err) => {
|
|
22046
23131
|
process.stderr.write(`agentbox-ctl git: ${String(err.message ?? err)}
|
|
@@ -22050,14 +23135,20 @@ function runLocalGit(args, cwd) {
|
|
|
22050
23135
|
});
|
|
22051
23136
|
}
|
|
22052
23137
|
var gitCommand = new Command("git").description("Git operations that need host credentials (routed through the agentbox relay)").addCommand(
|
|
22053
|
-
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(
|
|
23138
|
+
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(
|
|
23139
|
+
"[args...]",
|
|
23140
|
+
"extra flags appended to the host-built `git push <remote> <branch>` (e.g. `--force-with-lease`, `--tags`). Do NOT re-pass the remote or branch \u2014 they are taken from --remote and the registered worktree; appending them as positionals makes git treat them as refspecs and fail with `refs/remotes/origin/HEAD cannot be resolved to branch`. Use --remote to change the remote."
|
|
23141
|
+
).action(async (args, opts) => {
|
|
22054
23142
|
const code = await postRpcAndExit("git.push", buildParams(opts, args), {
|
|
22055
23143
|
errorPrefix: "agentbox-ctl git"
|
|
22056
23144
|
});
|
|
22057
23145
|
process.exit(code);
|
|
22058
23146
|
})
|
|
22059
23147
|
).addCommand(
|
|
22060
|
-
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(
|
|
23148
|
+
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").addOption(hostInitiatedOption()).allowExcessArguments(true).allowUnknownOption(true).argument(
|
|
23149
|
+
"[args...]",
|
|
23150
|
+
"extra flags appended to the host-built `git fetch <remote> <branch>` (e.g. `--prune`, `--tags`). Do NOT re-pass the remote or branch; they come from --remote and the registered worktree (same gotcha as `push`)."
|
|
23151
|
+
).action(async (args, opts) => {
|
|
22061
23152
|
const code = await postRpcAndExit("git.fetch", buildParams(opts, args), {
|
|
22062
23153
|
errorPrefix: "agentbox-ctl git"
|
|
22063
23154
|
});
|
|
@@ -22066,7 +23157,10 @@ var gitCommand = new Command("git").description("Git operations that need host c
|
|
|
22066
23157
|
).addCommand(
|
|
22067
23158
|
new Command("pull").description(
|
|
22068
23159
|
"Fetch via the relay (host creds), then merge into the in-container working tree locally"
|
|
22069
|
-
).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(
|
|
23160
|
+
).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").addOption(hostInitiatedOption()).allowExcessArguments(true).allowUnknownOption(true).argument(
|
|
23161
|
+
"[args...]",
|
|
23162
|
+
"extra flags appended to the host-built `git fetch <remote> <branch>` (e.g. `--prune`). Do NOT re-pass the remote or branch; they come from --remote and the registered worktree (same gotcha as `push`)."
|
|
23163
|
+
).action(
|
|
22070
23164
|
async (args, opts) => {
|
|
22071
23165
|
const fetchCode = await postRpcAndExit("git.fetch", buildParams(opts, args), {
|
|
22072
23166
|
errorPrefix: "agentbox-ctl git"
|
|
@@ -22081,7 +23175,27 @@ var gitCommand = new Command("git").description("Git operations that need host c
|
|
|
22081
23175
|
process.exit(mergeCode);
|
|
22082
23176
|
}
|
|
22083
23177
|
)
|
|
22084
|
-
)
|
|
23178
|
+
).addCommand(
|
|
23179
|
+
new Command("clone").description(
|
|
23180
|
+
"Clone a github repo into the box. Host runs `git clone` with its creds into a tmpdir, bundles, and ships the bundle back; the box materialises the working copy and resets origin to the original URL."
|
|
23181
|
+
).option("--cwd <path>", "container path identifying which registered worktree to use (default: cwd)").option("--branch <name>", "pass --branch <name> to host git clone").option("--depth <n>", "pass --depth <n> to host git clone").argument("<url>", "github URL or owner/name shorthand").argument("[dir]", "target directory inside the box (default: derived from url)").action(
|
|
23182
|
+
async (url, dir, opts) => {
|
|
23183
|
+
const params = {
|
|
23184
|
+
path: opts.cwd ?? process.cwd(),
|
|
23185
|
+
url
|
|
23186
|
+
};
|
|
23187
|
+
if (dir) params.targetPath = dir;
|
|
23188
|
+
const extra = [];
|
|
23189
|
+
if (opts.branch) extra.push("--branch", opts.branch);
|
|
23190
|
+
if (opts.depth) extra.push("--depth", opts.depth);
|
|
23191
|
+
if (extra.length > 0) params.args = extra;
|
|
23192
|
+
const code = await postRpcAndExit("git.clone", params, {
|
|
23193
|
+
errorPrefix: "agentbox-ctl git clone"
|
|
23194
|
+
});
|
|
23195
|
+
process.exit(code);
|
|
23196
|
+
}
|
|
23197
|
+
)
|
|
23198
|
+
).addCommand(buildPrCommand("agentbox-ctl git pr"));
|
|
22085
23199
|
|
|
22086
23200
|
// src/commands/notify.ts
|
|
22087
23201
|
async function reportState(opts, state) {
|
|
@@ -22103,7 +23217,7 @@ var notifyCommand = new Command("notify").description(
|
|
|
22103
23217
|
);
|
|
22104
23218
|
|
|
22105
23219
|
// src/commands/open.ts
|
|
22106
|
-
var
|
|
23220
|
+
var import_node_child_process12 = require("child_process");
|
|
22107
23221
|
var OPEN_TIMEOUT_MS = 3e4;
|
|
22108
23222
|
function isHttpUrl(value) {
|
|
22109
23223
|
try {
|
|
@@ -22115,7 +23229,7 @@ function isHttpUrl(value) {
|
|
|
22115
23229
|
}
|
|
22116
23230
|
function openInBoxBrowser(url) {
|
|
22117
23231
|
return new Promise((resolve2) => {
|
|
22118
|
-
const child = (0,
|
|
23232
|
+
const child = (0, import_node_child_process12.spawn)("agent-browser", ["open", "--headed", url], { stdio: "inherit" });
|
|
22119
23233
|
const timer = setTimeout(() => {
|
|
22120
23234
|
child.kill("SIGTERM");
|
|
22121
23235
|
process.stderr.write(
|
|
@@ -22334,9 +23448,11 @@ program2.addCommand(reloadCommand);
|
|
|
22334
23448
|
program2.addCommand(claudeSessionCommand);
|
|
22335
23449
|
program2.addCommand(claudeStateCommand);
|
|
22336
23450
|
program2.addCommand(codexStateCommand);
|
|
23451
|
+
program2.addCommand(opencodeStateCommand);
|
|
22337
23452
|
program2.addCommand(waitReadyCommand);
|
|
22338
23453
|
program2.addCommand(runTaskCommand);
|
|
22339
23454
|
program2.addCommand(gitCommand);
|
|
23455
|
+
program2.addCommand(ghCommand);
|
|
22340
23456
|
program2.addCommand(checkpointCommand);
|
|
22341
23457
|
program2.addCommand(cpCommand);
|
|
22342
23458
|
program2.addCommand(downloadCommand);
|