@madarco/agentbox 0.13.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +65 -0
- package/README.md +11 -8
- package/dist/{_cloud-attach-HJC672UR.js → _cloud-attach-GUBB5RH2.js} +4 -4
- package/dist/{chunk-R5XIDQFR.js → chunk-BKU34KYY.js} +170 -6
- package/dist/chunk-BKU34KYY.js.map +1 -0
- package/dist/{chunk-QYRK5H6Q.js → chunk-BYCLD6D6.js} +17 -9
- package/dist/chunk-BYCLD6D6.js.map +1 -0
- package/dist/chunk-LDMYHWUS.js +346 -0
- package/dist/chunk-LDMYHWUS.js.map +1 -0
- package/dist/{chunk-2LF5YILI.js → chunk-RSKG7AFU.js} +80 -6
- package/dist/chunk-RSKG7AFU.js.map +1 -0
- package/dist/{chunk-4NQXNQ53.js → chunk-TBSIJVSN.js} +149 -47
- package/dist/chunk-TBSIJVSN.js.map +1 -0
- package/dist/{chunk-B4QG2MCW.js → chunk-TCS5HXJX.js} +381 -174
- package/dist/chunk-TCS5HXJX.js.map +1 -0
- package/dist/{chunk-ECLLV5JH.js → chunk-VATTS2MR.js} +156 -5
- package/dist/chunk-VATTS2MR.js.map +1 -0
- package/dist/{chunk-SNTHHWKY.js → chunk-XKH7NTT7.js} +80 -22
- package/dist/chunk-XKH7NTT7.js.map +1 -0
- package/dist/dist-34RKQ74M.js +662 -0
- package/dist/dist-34RKQ74M.js.map +1 -0
- package/dist/{dist-OPIBZ7XM.js → dist-3IMQNTTV.js} +14 -69
- package/dist/dist-3IMQNTTV.js.map +1 -0
- package/dist/{dist-OG6NW6SM.js → dist-4DPOL5A7.js} +5 -3
- package/dist/{dist-JAN5VABY.js → dist-57M6ZA7H.js} +25 -177
- package/dist/dist-57M6ZA7H.js.map +1 -0
- package/dist/{dist-7KVUIKJX.js → dist-J2IHD5T7.js} +37 -226
- package/dist/dist-J2IHD5T7.js.map +1 -0
- package/dist/index.js +1376 -1029
- package/dist/index.js.map +1 -1
- package/dist/{prepared-state-MQHD3M5F-KE4DT3GX.js → prepared-state-MQHD3M5F-Q27AZU53.js} +2 -2
- package/package.json +8 -6
- package/runtime/docker/Dockerfile.box +21 -26
- package/runtime/docker/apps/cli/share/agentbox-setup/SKILL.md +37 -1
- package/runtime/docker/packages/ctl/dist/bin.cjs +40 -16
- package/runtime/docker/packages/sandbox-docker/scripts/agentbox-vnc-start +17 -6
- package/runtime/docker/packages/sandbox-docker/scripts/chromium-resolver +57 -0
- package/runtime/docker/packages/sandbox-docker/scripts/claude-managed-settings.json +2 -1
- package/runtime/e2b/agentbox-checkpoint-cleanup +52 -0
- package/runtime/e2b/agentbox-codex-hooks.json +68 -0
- package/runtime/e2b/agentbox-open +28 -0
- package/runtime/e2b/agentbox-setup-skill.md +233 -0
- package/runtime/e2b/agentbox-vnc-start +102 -0
- package/runtime/e2b/attach-helper.cjs +167 -0
- package/runtime/e2b/claude-managed-settings.json +116 -0
- package/runtime/e2b/ctl.cjs +23864 -0
- package/runtime/e2b/custom-system-CLAUDE.md +46 -0
- package/runtime/e2b/gh-shim +344 -0
- package/runtime/e2b/git-shim +131 -0
- package/runtime/e2b/scripts/build-template.sh +295 -0
- package/runtime/hetzner/agentbox-setup-skill.md +37 -1
- package/runtime/hetzner/agentbox-vnc-start +17 -6
- package/runtime/hetzner/claude-managed-settings.json +2 -1
- package/runtime/hetzner/ctl.cjs +40 -16
- package/runtime/relay/bin.cjs +297 -228
- package/runtime/vercel/agentbox-setup-skill.md +37 -1
- package/runtime/vercel/agentbox-vnc-start +17 -6
- package/runtime/vercel/claude-managed-settings.json +2 -1
- package/runtime/vercel/ctl.cjs +40 -16
- package/share/agentbox-setup/SKILL.md +37 -1
- package/share/host-skills/agentbox-info/SKILL.md +26 -34
- package/dist/chunk-2LF5YILI.js.map +0 -1
- package/dist/chunk-4NQXNQ53.js.map +0 -1
- package/dist/chunk-B4QG2MCW.js.map +0 -1
- package/dist/chunk-ECLLV5JH.js.map +0 -1
- package/dist/chunk-QYRK5H6Q.js.map +0 -1
- package/dist/chunk-R5XIDQFR.js.map +0 -1
- package/dist/chunk-SNTHHWKY.js.map +0 -1
- package/dist/dist-7KVUIKJX.js.map +0 -1
- package/dist/dist-JAN5VABY.js.map +0 -1
- package/dist/dist-OPIBZ7XM.js.map +0 -1
- /package/dist/{_cloud-attach-HJC672UR.js.map → _cloud-attach-GUBB5RH2.js.map} +0 -0
- /package/dist/{dist-OG6NW6SM.js.map → dist-4DPOL5A7.js.map} +0 -0
- /package/dist/{prepared-state-MQHD3M5F-KE4DT3GX.js.map → prepared-state-MQHD3M5F-Q27AZU53.js.map} +0 -0
package/runtime/relay/bin.cjs
CHANGED
|
@@ -3057,15 +3057,15 @@ var require_windows = __commonJS({
|
|
|
3057
3057
|
}
|
|
3058
3058
|
return false;
|
|
3059
3059
|
}
|
|
3060
|
-
function checkStat(
|
|
3061
|
-
if (!
|
|
3060
|
+
function checkStat(stat2, path6, options) {
|
|
3061
|
+
if (!stat2.isSymbolicLink() && !stat2.isFile()) {
|
|
3062
3062
|
return false;
|
|
3063
3063
|
}
|
|
3064
3064
|
return checkPathExt(path6, options);
|
|
3065
3065
|
}
|
|
3066
3066
|
function isexe(path6, options, cb) {
|
|
3067
|
-
fs.stat(path6, function(er,
|
|
3068
|
-
cb(er, er ? false : checkStat(
|
|
3067
|
+
fs.stat(path6, function(er, stat2) {
|
|
3068
|
+
cb(er, er ? false : checkStat(stat2, path6, options));
|
|
3069
3069
|
});
|
|
3070
3070
|
}
|
|
3071
3071
|
function sync(path6, options) {
|
|
@@ -3082,20 +3082,20 @@ var require_mode = __commonJS({
|
|
|
3082
3082
|
isexe.sync = sync;
|
|
3083
3083
|
var fs = require("fs");
|
|
3084
3084
|
function isexe(path6, options, cb) {
|
|
3085
|
-
fs.stat(path6, function(er,
|
|
3086
|
-
cb(er, er ? false : checkStat(
|
|
3085
|
+
fs.stat(path6, function(er, stat2) {
|
|
3086
|
+
cb(er, er ? false : checkStat(stat2, options));
|
|
3087
3087
|
});
|
|
3088
3088
|
}
|
|
3089
3089
|
function sync(path6, options) {
|
|
3090
3090
|
return checkStat(fs.statSync(path6), options);
|
|
3091
3091
|
}
|
|
3092
|
-
function checkStat(
|
|
3093
|
-
return
|
|
3092
|
+
function checkStat(stat2, options) {
|
|
3093
|
+
return stat2.isFile() && checkMode(stat2, options);
|
|
3094
3094
|
}
|
|
3095
|
-
function checkMode(
|
|
3096
|
-
var mod =
|
|
3097
|
-
var uid =
|
|
3098
|
-
var gid =
|
|
3095
|
+
function checkMode(stat2, options) {
|
|
3096
|
+
var mod = stat2.mode;
|
|
3097
|
+
var uid = stat2.uid;
|
|
3098
|
+
var gid = stat2.gid;
|
|
3099
3099
|
var myUid = options.uid !== void 0 ? options.uid : process.getuid && process.getuid();
|
|
3100
3100
|
var myGid = options.gid !== void 0 ? options.gid : process.getgid && process.getgid();
|
|
3101
3101
|
var u2 = parseInt("100", 8);
|
|
@@ -11808,11 +11808,11 @@ var replacements = Object.entries(specialMainSymbols);
|
|
|
11808
11808
|
// ../../node_modules/.pnpm/yoctocolors@2.1.2/node_modules/yoctocolors/base.js
|
|
11809
11809
|
var import_node_tty = __toESM(require("tty"), 1);
|
|
11810
11810
|
var hasColors = import_node_tty.default?.WriteStream?.prototype?.hasColors?.() ?? false;
|
|
11811
|
-
var format = (
|
|
11811
|
+
var format = (open2, close) => {
|
|
11812
11812
|
if (!hasColors) {
|
|
11813
11813
|
return (input) => input;
|
|
11814
11814
|
}
|
|
11815
|
-
const openCode = `\x1B[${
|
|
11815
|
+
const openCode = `\x1B[${open2}m`;
|
|
11816
11816
|
const closeCode = `\x1B[${close}m`;
|
|
11817
11817
|
return (input) => {
|
|
11818
11818
|
const string = input + "";
|
|
@@ -18492,6 +18492,10 @@ async function resolveCloudBackend(name) {
|
|
|
18492
18492
|
const pkg = "@agentbox/sandbox-vercel";
|
|
18493
18493
|
return loadCloudBackend(pkg, async () => (await import(pkg)).vercelBackend);
|
|
18494
18494
|
}
|
|
18495
|
+
if (name === "e2b") {
|
|
18496
|
+
const pkg = "@agentbox/sandbox-e2b";
|
|
18497
|
+
return loadCloudBackend(pkg, async () => (await import(pkg)).e2bBackend);
|
|
18498
|
+
}
|
|
18495
18499
|
throw new Error(`no host executor for cloud backend '${name}'`);
|
|
18496
18500
|
}
|
|
18497
18501
|
async function loadCloudBackend(pkg, load2) {
|
|
@@ -20306,8 +20310,8 @@ async function startRelayServer(opts) {
|
|
|
20306
20310
|
}
|
|
20307
20311
|
|
|
20308
20312
|
// src/autopause.ts
|
|
20309
|
-
var
|
|
20310
|
-
var
|
|
20313
|
+
var import_node_child_process9 = require("child_process");
|
|
20314
|
+
var import_promises18 = require("fs/promises");
|
|
20311
20315
|
|
|
20312
20316
|
// ../config/dist/index.js
|
|
20313
20317
|
var import_yaml = __toESM(require_dist(), 1);
|
|
@@ -20325,11 +20329,13 @@ var BUILT_IN_DEFAULTS = {
|
|
|
20325
20329
|
defaultCheckpointDaytona: "",
|
|
20326
20330
|
defaultCheckpointHetzner: "",
|
|
20327
20331
|
defaultCheckpointVercel: "",
|
|
20332
|
+
defaultCheckpointE2b: "",
|
|
20328
20333
|
size: "",
|
|
20329
20334
|
sizeDocker: "",
|
|
20330
20335
|
sizeDaytona: "",
|
|
20331
20336
|
sizeHetzner: "",
|
|
20332
20337
|
sizeVercel: "",
|
|
20338
|
+
sizeE2b: "",
|
|
20333
20339
|
withPlaywright: false,
|
|
20334
20340
|
withEnv: false,
|
|
20335
20341
|
resyncOnStart: true,
|
|
@@ -20342,6 +20348,7 @@ var BUILT_IN_DEFAULTS = {
|
|
|
20342
20348
|
imageDaytona: "",
|
|
20343
20349
|
imageHetzner: "",
|
|
20344
20350
|
imageVercel: "",
|
|
20351
|
+
imageE2b: "",
|
|
20345
20352
|
// Mirrors BOX_IMAGE_REGISTRY in @agentbox/sandbox-docker. Empty disables the
|
|
20346
20353
|
// registry pull (always build the docker base image locally).
|
|
20347
20354
|
imageRegistry: "ghcr.io/madarco/agentbox/box",
|
|
@@ -20423,8 +20430,8 @@ var KEY_REGISTRY = [
|
|
|
20423
20430
|
{
|
|
20424
20431
|
key: "box.provider",
|
|
20425
20432
|
type: "enum",
|
|
20426
|
-
enumValues: ["docker", "daytona", "hetzner", "vercel"],
|
|
20427
|
-
description: "Sandbox backend new boxes are created on: local Docker containers, Daytona Cloud sandboxes, Hetzner Cloud VPSes, or
|
|
20433
|
+
enumValues: ["docker", "daytona", "hetzner", "vercel", "e2b"],
|
|
20434
|
+
description: "Sandbox backend new boxes are created on: local Docker containers, Daytona Cloud sandboxes, Hetzner Cloud VPSes, Vercel Sandboxes, or E2B microVMs."
|
|
20428
20435
|
},
|
|
20429
20436
|
{
|
|
20430
20437
|
key: "box.hostSnapshot",
|
|
@@ -20460,6 +20467,12 @@ var KEY_REGISTRY = [
|
|
|
20460
20467
|
description: "Per-provider override of `box.defaultCheckpoint` for vercel. Wins over the global when set; set via `agentbox checkpoint set-default --provider vercel`.",
|
|
20461
20468
|
advanced: true
|
|
20462
20469
|
},
|
|
20470
|
+
{
|
|
20471
|
+
key: "box.defaultCheckpointE2b",
|
|
20472
|
+
type: "string",
|
|
20473
|
+
description: "Per-provider override of `box.defaultCheckpoint` for e2b. Wins over the global when set; set via `agentbox checkpoint set-default --provider e2b`.",
|
|
20474
|
+
advanced: true
|
|
20475
|
+
},
|
|
20463
20476
|
{
|
|
20464
20477
|
key: "box.size",
|
|
20465
20478
|
type: "string",
|
|
@@ -20489,6 +20502,12 @@ var KEY_REGISTRY = [
|
|
|
20489
20502
|
description: "Per-provider override of `box.size` for vercel. Reserved \u2014 vercel sizing is controlled via `box.vercelVcpus`.",
|
|
20490
20503
|
advanced: true
|
|
20491
20504
|
},
|
|
20505
|
+
{
|
|
20506
|
+
key: "box.sizeE2b",
|
|
20507
|
+
type: "string",
|
|
20508
|
+
description: "Per-provider override of `box.size` for e2b. Reserved \u2014 e2b sizing is template-level (set at `agentbox prepare --provider e2b` time via --vcpus / --memory).",
|
|
20509
|
+
advanced: true
|
|
20510
|
+
},
|
|
20492
20511
|
{
|
|
20493
20512
|
key: "checkpoint.maxLayers",
|
|
20494
20513
|
type: "int",
|
|
@@ -20560,6 +20579,12 @@ var KEY_REGISTRY = [
|
|
|
20560
20579
|
description: "Per-provider override of `box.image` for vercel (snapshot id, e.g. `snap_\u2026`). Written by `agentbox prepare --provider vercel`.",
|
|
20561
20580
|
advanced: true
|
|
20562
20581
|
},
|
|
20582
|
+
{
|
|
20583
|
+
key: "box.imageE2b",
|
|
20584
|
+
type: "string",
|
|
20585
|
+
description: "Per-provider override of `box.image` for e2b (template id or `name:tag`, e.g. `agentbox-base:latest`). Written by `agentbox prepare --provider e2b`.",
|
|
20586
|
+
advanced: true
|
|
20587
|
+
},
|
|
20563
20588
|
{
|
|
20564
20589
|
key: "box.imageRegistry",
|
|
20565
20590
|
type: "string",
|
|
@@ -20900,210 +20925,18 @@ var GLOBAL_CONFIG_FILE = (0, import_path2.join)(STATE_DIR2, "config.yaml");
|
|
|
20900
20925
|
var PROJECTS_DIR = (0, import_path2.join)(STATE_DIR2, "projects");
|
|
20901
20926
|
var PROJECT_GC_COUNTER_FILE = (0, import_path3.join)(PROJECTS_DIR, ".gc.json");
|
|
20902
20927
|
|
|
20903
|
-
// src/autopause.ts
|
|
20904
|
-
function selectBoxesToPause(entries, cfg) {
|
|
20905
|
-
if (!cfg.enabled) return [];
|
|
20906
|
-
const runningCount = entries.reduce((n2, e) => e.running ? n2 + 1 : n2, 0);
|
|
20907
|
-
const excess = runningCount - cfg.maxRunningBoxes;
|
|
20908
|
-
if (excess <= 0) return [];
|
|
20909
|
-
const idleThresholdMs = cfg.idleMinutes * 6e4;
|
|
20910
|
-
const candidates = entries.filter(
|
|
20911
|
-
(e) => e.running && e.claudeState === "idle" && e.idleMs != null && e.idleMs >= idleThresholdMs
|
|
20912
|
-
);
|
|
20913
|
-
candidates.sort(
|
|
20914
|
-
(a2, b) => b.idleMs - a2.idleMs || a2.createdAt - b.createdAt || (a2.boxId < b.boxId ? -1 : a2.boxId > b.boxId ? 1 : 0)
|
|
20915
|
-
);
|
|
20916
|
-
return candidates.slice(0, excess).map((e) => e.boxId);
|
|
20917
|
-
}
|
|
20918
|
-
async function loadAutopauseConfig() {
|
|
20919
|
-
const d = BUILT_IN_DEFAULTS.autopause;
|
|
20920
|
-
let global3 = {};
|
|
20921
|
-
try {
|
|
20922
|
-
global3 = parseUserConfig(await (0, import_promises16.readFile)(GLOBAL_CONFIG_FILE, "utf8"), GLOBAL_CONFIG_FILE);
|
|
20923
|
-
} catch {
|
|
20924
|
-
}
|
|
20925
|
-
const a2 = global3.autopause ?? {};
|
|
20926
|
-
return {
|
|
20927
|
-
enabled: a2.enabled ?? d.enabled,
|
|
20928
|
-
maxRunningBoxes: a2.maxRunningBoxes ?? d.maxRunningBoxes,
|
|
20929
|
-
idleMinutes: a2.idleMinutes ?? d.idleMinutes
|
|
20930
|
-
};
|
|
20931
|
-
}
|
|
20932
|
-
var DEFAULT_INTERVAL_MS = 6e4;
|
|
20933
|
-
function startAutopauseLoop(deps) {
|
|
20934
|
-
const loadConfig = deps.loadConfig ?? loadAutopauseConfig;
|
|
20935
|
-
const inspectStatus = deps.inspectStatus ?? inspectContainerState;
|
|
20936
|
-
const pause = deps.pause ?? pauseContainer;
|
|
20937
|
-
const intervalMs = deps.intervalMs ?? DEFAULT_INTERVAL_MS;
|
|
20938
|
-
const { registry, statusStore, events, log } = deps;
|
|
20939
|
-
let ticking = false;
|
|
20940
|
-
let stopped = false;
|
|
20941
|
-
let inFlight = Promise.resolve();
|
|
20942
|
-
async function tick() {
|
|
20943
|
-
if (ticking) return;
|
|
20944
|
-
ticking = true;
|
|
20945
|
-
try {
|
|
20946
|
-
const cfg = await loadConfig();
|
|
20947
|
-
if (!cfg.enabled) return;
|
|
20948
|
-
const entries = [];
|
|
20949
|
-
for (const reg of registry.list()) {
|
|
20950
|
-
if (!reg.containerName) continue;
|
|
20951
|
-
const state = await inspectStatus(reg.containerName);
|
|
20952
|
-
const claude = readClaude(statusStore.get(reg.boxId));
|
|
20953
|
-
const idleMs = claude.state === "idle" && claude.updatedAt ? msSince(claude.updatedAt) : null;
|
|
20954
|
-
entries.push({
|
|
20955
|
-
boxId: reg.boxId,
|
|
20956
|
-
containerName: reg.containerName,
|
|
20957
|
-
running: state === "running",
|
|
20958
|
-
claudeState: claude.state,
|
|
20959
|
-
idleMs,
|
|
20960
|
-
createdAt: reg.createdAt ? toEpoch(reg.createdAt) : 0
|
|
20961
|
-
});
|
|
20962
|
-
}
|
|
20963
|
-
const toPause = selectBoxesToPause(entries, cfg);
|
|
20964
|
-
if (toPause.length === 0) return;
|
|
20965
|
-
const byId = new Map(entries.map((e) => [e.boxId, e]));
|
|
20966
|
-
const runningBefore = entries.reduce((n2, e) => e.running ? n2 + 1 : n2, 0);
|
|
20967
|
-
for (const boxId of toPause) {
|
|
20968
|
-
const e = byId.get(boxId);
|
|
20969
|
-
if (!e) continue;
|
|
20970
|
-
try {
|
|
20971
|
-
await pause(e.containerName);
|
|
20972
|
-
const mins = e.idleMs != null ? Math.round(e.idleMs / 6e4) : null;
|
|
20973
|
-
events.append({
|
|
20974
|
-
boxId,
|
|
20975
|
-
type: "autopause",
|
|
20976
|
-
payload: {
|
|
20977
|
-
containerName: e.containerName,
|
|
20978
|
-
action: "paused",
|
|
20979
|
-
idleMs: e.idleMs,
|
|
20980
|
-
runningBefore,
|
|
20981
|
-
max: cfg.maxRunningBoxes
|
|
20982
|
-
}
|
|
20983
|
-
});
|
|
20984
|
-
log(
|
|
20985
|
-
`autopause: paused box ${boxId} (${e.containerName})` + (mins != null ? ` after ~${String(mins)}m idle` : "") + `; running ${String(runningBefore)} -> target ${String(cfg.maxRunningBoxes)}`
|
|
20986
|
-
);
|
|
20987
|
-
} catch (err) {
|
|
20988
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
20989
|
-
log(`autopause: docker pause ${e.containerName} failed: ${msg}`);
|
|
20990
|
-
events.append({
|
|
20991
|
-
boxId,
|
|
20992
|
-
type: "autopause",
|
|
20993
|
-
payload: { containerName: e.containerName, action: "pause-failed", error: msg }
|
|
20994
|
-
});
|
|
20995
|
-
}
|
|
20996
|
-
}
|
|
20997
|
-
} catch (err) {
|
|
20998
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
20999
|
-
log(`autopause: tick error: ${msg}`);
|
|
21000
|
-
} finally {
|
|
21001
|
-
ticking = false;
|
|
21002
|
-
}
|
|
21003
|
-
}
|
|
21004
|
-
const timer = setInterval(() => {
|
|
21005
|
-
if (stopped) return;
|
|
21006
|
-
inFlight = tick();
|
|
21007
|
-
}, intervalMs);
|
|
21008
|
-
timer.unref();
|
|
21009
|
-
return {
|
|
21010
|
-
stop: async () => {
|
|
21011
|
-
stopped = true;
|
|
21012
|
-
clearInterval(timer);
|
|
21013
|
-
await inFlight.catch(() => {
|
|
21014
|
-
});
|
|
21015
|
-
}
|
|
21016
|
-
};
|
|
21017
|
-
}
|
|
21018
|
-
function readClaude(snap) {
|
|
21019
|
-
const c3 = snap && typeof snap === "object" ? snap.claude : void 0;
|
|
21020
|
-
if (!c3 || typeof c3 !== "object") return { state: null, updatedAt: null };
|
|
21021
|
-
const o2 = c3;
|
|
21022
|
-
const state = o2.state === "working" || o2.state === "idle" || o2.state === "waiting" || o2.state === "unknown" ? o2.state : null;
|
|
21023
|
-
return { state, updatedAt: typeof o2.updatedAt === "string" ? o2.updatedAt : null };
|
|
21024
|
-
}
|
|
21025
|
-
function msSince(iso) {
|
|
21026
|
-
const t = Date.parse(iso);
|
|
21027
|
-
return Number.isNaN(t) ? null : Date.now() - t;
|
|
21028
|
-
}
|
|
21029
|
-
function toEpoch(iso) {
|
|
21030
|
-
const t = Date.parse(iso);
|
|
21031
|
-
return Number.isNaN(t) ? 0 : t;
|
|
21032
|
-
}
|
|
21033
|
-
var INSPECT_TIMEOUT_MS = 15e3;
|
|
21034
|
-
var PAUSE_TIMEOUT_MS = 3e4;
|
|
21035
|
-
function runDocker(args, timeoutMs) {
|
|
21036
|
-
return new Promise((resolve2) => {
|
|
21037
|
-
const child = (0, import_node_child_process8.spawn)("docker", args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
21038
|
-
let stdout = "";
|
|
21039
|
-
let stderr = "";
|
|
21040
|
-
let settled = false;
|
|
21041
|
-
const finish = (exitCode) => {
|
|
21042
|
-
if (settled) return;
|
|
21043
|
-
settled = true;
|
|
21044
|
-
resolve2({ exitCode, stdout, stderr });
|
|
21045
|
-
};
|
|
21046
|
-
const timer = setTimeout(() => {
|
|
21047
|
-
child.kill("SIGTERM");
|
|
21048
|
-
stderr += `
|
|
21049
|
-
relay: docker ${args.join(" ")} timed out after ${String(timeoutMs)}ms
|
|
21050
|
-
`;
|
|
21051
|
-
finish(124);
|
|
21052
|
-
}, timeoutMs);
|
|
21053
|
-
child.stdout?.on("data", (c3) => {
|
|
21054
|
-
stdout += c3.toString("utf8");
|
|
21055
|
-
});
|
|
21056
|
-
child.stderr?.on("data", (c3) => {
|
|
21057
|
-
stderr += c3.toString("utf8");
|
|
21058
|
-
});
|
|
21059
|
-
child.on("error", (err) => {
|
|
21060
|
-
clearTimeout(timer);
|
|
21061
|
-
stderr += String(err.message ?? err);
|
|
21062
|
-
finish(127);
|
|
21063
|
-
});
|
|
21064
|
-
child.on("close", (code) => {
|
|
21065
|
-
clearTimeout(timer);
|
|
21066
|
-
finish(code ?? -1);
|
|
21067
|
-
});
|
|
21068
|
-
});
|
|
21069
|
-
}
|
|
21070
|
-
async function inspectContainerState(name) {
|
|
21071
|
-
const r = await runDocker(["inspect", "--format", "{{.State.Status}}", name], INSPECT_TIMEOUT_MS);
|
|
21072
|
-
if (r.exitCode !== 0) return "missing";
|
|
21073
|
-
switch (r.stdout.trim()) {
|
|
21074
|
-
case "running":
|
|
21075
|
-
return "running";
|
|
21076
|
-
case "paused":
|
|
21077
|
-
return "paused";
|
|
21078
|
-
case "created":
|
|
21079
|
-
case "exited":
|
|
21080
|
-
case "dead":
|
|
21081
|
-
case "restarting":
|
|
21082
|
-
case "removing":
|
|
21083
|
-
return "stopped";
|
|
21084
|
-
default:
|
|
21085
|
-
return "missing";
|
|
21086
|
-
}
|
|
21087
|
-
}
|
|
21088
|
-
async function pauseContainer(name) {
|
|
21089
|
-
const r = await runDocker(["pause", name], PAUSE_TIMEOUT_MS);
|
|
21090
|
-
if (r.exitCode !== 0) {
|
|
21091
|
-
throw new Error(r.stderr.trim() || `docker pause ${name} exited ${String(r.exitCode)}`);
|
|
21092
|
-
}
|
|
21093
|
-
}
|
|
21094
|
-
|
|
21095
20928
|
// src/queue.ts
|
|
21096
|
-
var
|
|
21097
|
-
var
|
|
20929
|
+
var import_node_child_process8 = require("child_process");
|
|
20930
|
+
var import_promises16 = require("fs/promises");
|
|
21098
20931
|
var import_node_fs6 = require("fs");
|
|
21099
20932
|
var import_node_path8 = require("path");
|
|
21100
|
-
var
|
|
20933
|
+
var import_promises17 = require("timers/promises");
|
|
21101
20934
|
var QUEUE_DIR = (0, import_node_path8.join)(STATE_DIR, "queue");
|
|
21102
20935
|
async function loadQueueConfig() {
|
|
21103
20936
|
const d = BUILT_IN_DEFAULTS.queue;
|
|
21104
20937
|
let global3 = {};
|
|
21105
20938
|
try {
|
|
21106
|
-
global3 = parseUserConfig(await (0,
|
|
20939
|
+
global3 = parseUserConfig(await (0, import_promises16.readFile)(GLOBAL_CONFIG_FILE, "utf8"), GLOBAL_CONFIG_FILE);
|
|
21107
20940
|
} catch {
|
|
21108
20941
|
}
|
|
21109
20942
|
const q = global3.queue ?? {};
|
|
@@ -21115,25 +20948,32 @@ async function loadQueueConfig() {
|
|
|
21115
20948
|
};
|
|
21116
20949
|
}
|
|
21117
20950
|
async function writeJob(job) {
|
|
21118
|
-
await (0,
|
|
20951
|
+
await (0, import_promises16.mkdir)(QUEUE_DIR, { recursive: true });
|
|
21119
20952
|
const final = (0, import_node_path8.join)(QUEUE_DIR, `${job.id}.json`);
|
|
21120
20953
|
const tmp = `${final}.tmp.${String(process.pid)}.${String(Date.now())}`;
|
|
21121
|
-
await (0,
|
|
21122
|
-
await (0,
|
|
20954
|
+
await (0, import_promises16.writeFile)(tmp, JSON.stringify(job, null, 2) + "\n", "utf8");
|
|
20955
|
+
await (0, import_promises16.rename)(tmp, final);
|
|
21123
20956
|
}
|
|
21124
20957
|
async function readJob(id) {
|
|
21125
20958
|
try {
|
|
21126
|
-
const raw = await (0,
|
|
20959
|
+
const raw = await (0, import_promises16.readFile)((0, import_node_path8.join)(QUEUE_DIR, `${id}.json`), "utf8");
|
|
21127
20960
|
return JSON.parse(raw);
|
|
21128
20961
|
} catch (err) {
|
|
21129
20962
|
if (err.code === "ENOENT") return null;
|
|
21130
20963
|
throw err;
|
|
21131
20964
|
}
|
|
21132
20965
|
}
|
|
20966
|
+
async function deleteJob(id) {
|
|
20967
|
+
try {
|
|
20968
|
+
await (0, import_promises16.unlink)((0, import_node_path8.join)(QUEUE_DIR, `${id}.json`));
|
|
20969
|
+
} catch (err) {
|
|
20970
|
+
if (err.code !== "ENOENT") throw err;
|
|
20971
|
+
}
|
|
20972
|
+
}
|
|
21133
20973
|
async function loadQueue() {
|
|
21134
20974
|
let entries;
|
|
21135
20975
|
try {
|
|
21136
|
-
entries = await (0,
|
|
20976
|
+
entries = await (0, import_promises16.readdir)(QUEUE_DIR);
|
|
21137
20977
|
} catch (err) {
|
|
21138
20978
|
if (err.code === "ENOENT") return [];
|
|
21139
20979
|
throw err;
|
|
@@ -21142,7 +20982,7 @@ async function loadQueue() {
|
|
|
21142
20982
|
for (const name of entries) {
|
|
21143
20983
|
if (!name.endsWith(".json")) continue;
|
|
21144
20984
|
try {
|
|
21145
|
-
const raw = await (0,
|
|
20985
|
+
const raw = await (0, import_promises16.readFile)((0, import_node_path8.join)(QUEUE_DIR, name), "utf8");
|
|
21146
20986
|
out.push(JSON.parse(raw));
|
|
21147
20987
|
} catch {
|
|
21148
20988
|
}
|
|
@@ -21216,22 +21056,23 @@ function parseTime(iso) {
|
|
|
21216
21056
|
const t = Date.parse(iso);
|
|
21217
21057
|
return Number.isNaN(t) ? 0 : t;
|
|
21218
21058
|
}
|
|
21219
|
-
function
|
|
21059
|
+
function msSince(iso) {
|
|
21220
21060
|
if (!iso) return null;
|
|
21221
21061
|
const t = Date.parse(iso);
|
|
21222
21062
|
return Number.isNaN(t) ? null : Date.now() - t;
|
|
21223
21063
|
}
|
|
21224
|
-
var
|
|
21064
|
+
var DEFAULT_INTERVAL_MS = 2e3;
|
|
21225
21065
|
function startQueueLoop(deps) {
|
|
21226
21066
|
const loadConfig = deps.loadConfig ?? loadQueueConfig;
|
|
21227
21067
|
const countRunning = deps.countRunning ?? defaultCountRunningBoxes;
|
|
21228
21068
|
const spawnWorker = deps.spawnWorker ?? defaultSpawnWorker;
|
|
21229
|
-
const intervalMs = deps.intervalMs ??
|
|
21069
|
+
const intervalMs = deps.intervalMs ?? DEFAULT_INTERVAL_MS;
|
|
21230
21070
|
const { log, onStatusChange } = deps;
|
|
21231
21071
|
const countWorking = deps.countWorking ?? (deps.registry && deps.statusStore ? (idleGraceMs) => defaultCountWorkingBoxes(deps.registry, deps.statusStore, idleGraceMs) : null);
|
|
21232
21072
|
let ticking = false;
|
|
21233
21073
|
let stopped = false;
|
|
21234
21074
|
let warnedNoWorkingDeps = false;
|
|
21075
|
+
let lastSweepAt = 0;
|
|
21235
21076
|
let inFlight = recoverOrphanedWorkers(log, onStatusChange).catch((err) => {
|
|
21236
21077
|
log(`queue: orphan recovery failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
21237
21078
|
});
|
|
@@ -21241,6 +21082,13 @@ function startQueueLoop(deps) {
|
|
|
21241
21082
|
try {
|
|
21242
21083
|
const cfg = await loadConfig();
|
|
21243
21084
|
if (!cfg.enabled) return;
|
|
21085
|
+
const now = Date.now();
|
|
21086
|
+
if (now - lastSweepAt >= SWEEP_INTERVAL_MS) {
|
|
21087
|
+
lastSweepAt = now;
|
|
21088
|
+
await sweepTerminalJobs(log, now).catch((err) => {
|
|
21089
|
+
log(`queue: sweep failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
21090
|
+
});
|
|
21091
|
+
}
|
|
21244
21092
|
const jobs = await loadQueue();
|
|
21245
21093
|
const hasQueued = jobs.some((j) => j.status === "queued");
|
|
21246
21094
|
if (!hasQueued) return;
|
|
@@ -21343,6 +21191,21 @@ async function recoverOrphanedWorkers(log, onChange) {
|
|
|
21343
21191
|
log(`queue: recovered orphan job ${j.id} (pid ${String(j.pid ?? "?")} not alive) -> failed`);
|
|
21344
21192
|
}
|
|
21345
21193
|
}
|
|
21194
|
+
var TERMINAL_RETENTION_MS = 60 * 60 * 1e3;
|
|
21195
|
+
var SWEEP_INTERVAL_MS = 60 * 1e3;
|
|
21196
|
+
var TERMINAL_STATUSES = ["done", "failed", "cancelled"];
|
|
21197
|
+
async function sweepTerminalJobs(log, now) {
|
|
21198
|
+
const jobs = await loadQueue();
|
|
21199
|
+
let swept = 0;
|
|
21200
|
+
for (const j of jobs) {
|
|
21201
|
+
if (!TERMINAL_STATUSES.includes(j.status)) continue;
|
|
21202
|
+
const since = Date.parse(j.finishedAt ?? j.createdAt);
|
|
21203
|
+
if (Number.isNaN(since) || now - since < TERMINAL_RETENTION_MS) continue;
|
|
21204
|
+
await deleteJob(j.id);
|
|
21205
|
+
swept += 1;
|
|
21206
|
+
}
|
|
21207
|
+
if (swept > 0) log(`queue: swept ${String(swept)} stale terminal manifest(s)`);
|
|
21208
|
+
}
|
|
21346
21209
|
function processAlive(pid) {
|
|
21347
21210
|
try {
|
|
21348
21211
|
process.kill(pid, 0);
|
|
@@ -21359,8 +21222,8 @@ async function defaultCountWorkingBoxes(registry, statusStore, idleGraceMs) {
|
|
|
21359
21222
|
return {
|
|
21360
21223
|
key: b.boxId,
|
|
21361
21224
|
agentState: active.state,
|
|
21362
|
-
sinceUpdateMs:
|
|
21363
|
-
sinceCreateMs:
|
|
21225
|
+
sinceUpdateMs: msSince(active.updatedAt),
|
|
21226
|
+
sinceCreateMs: msSince(b.createdAt)
|
|
21364
21227
|
};
|
|
21365
21228
|
});
|
|
21366
21229
|
const count2 = countWorkingSlots(entries, idleGraceMs);
|
|
@@ -21432,7 +21295,7 @@ async function uncachedBoxStateCount() {
|
|
|
21432
21295
|
}
|
|
21433
21296
|
function inspectDockerState(containerName) {
|
|
21434
21297
|
return new Promise((resolveP) => {
|
|
21435
|
-
const child = (0,
|
|
21298
|
+
const child = (0, import_node_child_process8.spawn)("docker", ["inspect", "--format", "{{.State.Status}}", containerName], {
|
|
21436
21299
|
stdio: ["ignore", "pipe", "pipe"]
|
|
21437
21300
|
});
|
|
21438
21301
|
let out = "";
|
|
@@ -21466,9 +21329,9 @@ async function defaultSpawnWorker(job) {
|
|
|
21466
21329
|
`AGENTBOX_CLI_ENTRY not set or missing (${String(entry)}); cannot spawn queue worker`
|
|
21467
21330
|
);
|
|
21468
21331
|
}
|
|
21469
|
-
await (0,
|
|
21332
|
+
await (0, import_promises16.mkdir)((0, import_node_path8.join)(STATE_DIR, "logs"), { recursive: true });
|
|
21470
21333
|
const fd = (0, import_node_fs6.openSync)(job.logPath, "a");
|
|
21471
|
-
const child = (0,
|
|
21334
|
+
const child = (0, import_node_child_process8.spawn)(process.execPath, [entry, "_run-queued-job", job.id], {
|
|
21472
21335
|
detached: true,
|
|
21473
21336
|
stdio: ["ignore", fd, fd],
|
|
21474
21337
|
env: process.env
|
|
@@ -21482,6 +21345,212 @@ async function defaultSpawnWorker(job) {
|
|
|
21482
21345
|
}
|
|
21483
21346
|
var QUEUE_LOGS_DIR = (0, import_node_path8.join)(STATE_DIR, "logs");
|
|
21484
21347
|
|
|
21348
|
+
// src/autopause.ts
|
|
21349
|
+
function selectBoxesToPause(entries, cfg) {
|
|
21350
|
+
if (!cfg.enabled) return [];
|
|
21351
|
+
const runningCount = entries.reduce((n2, e) => e.running ? n2 + 1 : n2, 0);
|
|
21352
|
+
const excess = runningCount - cfg.maxRunningBoxes;
|
|
21353
|
+
if (excess <= 0) return [];
|
|
21354
|
+
const idleThresholdMs = cfg.idleMinutes * 6e4;
|
|
21355
|
+
const candidates = entries.filter(
|
|
21356
|
+
(e) => e.running && e.claudeState === "idle" && e.idleMs != null && e.idleMs >= idleThresholdMs
|
|
21357
|
+
);
|
|
21358
|
+
candidates.sort(
|
|
21359
|
+
(a2, b) => b.idleMs - a2.idleMs || a2.createdAt - b.createdAt || (a2.boxId < b.boxId ? -1 : a2.boxId > b.boxId ? 1 : 0)
|
|
21360
|
+
);
|
|
21361
|
+
return candidates.slice(0, excess).map((e) => e.boxId);
|
|
21362
|
+
}
|
|
21363
|
+
async function loadAutopauseConfig() {
|
|
21364
|
+
const d = BUILT_IN_DEFAULTS.autopause;
|
|
21365
|
+
let global3 = {};
|
|
21366
|
+
try {
|
|
21367
|
+
global3 = parseUserConfig(await (0, import_promises18.readFile)(GLOBAL_CONFIG_FILE, "utf8"), GLOBAL_CONFIG_FILE);
|
|
21368
|
+
} catch {
|
|
21369
|
+
}
|
|
21370
|
+
const a2 = global3.autopause ?? {};
|
|
21371
|
+
return {
|
|
21372
|
+
enabled: a2.enabled ?? d.enabled,
|
|
21373
|
+
maxRunningBoxes: a2.maxRunningBoxes ?? d.maxRunningBoxes,
|
|
21374
|
+
idleMinutes: a2.idleMinutes ?? d.idleMinutes
|
|
21375
|
+
};
|
|
21376
|
+
}
|
|
21377
|
+
var DEFAULT_INTERVAL_MS2 = 6e4;
|
|
21378
|
+
function startAutopauseLoop(deps) {
|
|
21379
|
+
const loadConfig = deps.loadConfig ?? loadAutopauseConfig;
|
|
21380
|
+
const inspectStatus = deps.inspectStatus ?? inspectContainerState;
|
|
21381
|
+
const pause = deps.pause ?? pauseContainer;
|
|
21382
|
+
const intervalMs = deps.intervalMs ?? DEFAULT_INTERVAL_MS2;
|
|
21383
|
+
const { registry, statusStore, events, log } = deps;
|
|
21384
|
+
let ticking = false;
|
|
21385
|
+
let stopped = false;
|
|
21386
|
+
let inFlight = Promise.resolve();
|
|
21387
|
+
async function tick() {
|
|
21388
|
+
if (ticking) return;
|
|
21389
|
+
ticking = true;
|
|
21390
|
+
try {
|
|
21391
|
+
const cfg = await loadConfig();
|
|
21392
|
+
if (!cfg.enabled) return;
|
|
21393
|
+
const entries = [];
|
|
21394
|
+
for (const reg of registry.list()) {
|
|
21395
|
+
if (!reg.containerName) continue;
|
|
21396
|
+
const state = await inspectStatus(reg.containerName);
|
|
21397
|
+
const active = readPauseState(statusStore.get(reg.boxId));
|
|
21398
|
+
const idleMs = active.state === "idle" && active.updatedAt ? msSince2(active.updatedAt) : null;
|
|
21399
|
+
entries.push({
|
|
21400
|
+
boxId: reg.boxId,
|
|
21401
|
+
containerName: reg.containerName,
|
|
21402
|
+
running: state === "running",
|
|
21403
|
+
claudeState: active.state,
|
|
21404
|
+
idleMs,
|
|
21405
|
+
createdAt: reg.createdAt ? toEpoch(reg.createdAt) : 0
|
|
21406
|
+
});
|
|
21407
|
+
}
|
|
21408
|
+
const toPause = selectBoxesToPause(entries, cfg);
|
|
21409
|
+
if (toPause.length === 0) return;
|
|
21410
|
+
const byId = new Map(entries.map((e) => [e.boxId, e]));
|
|
21411
|
+
const runningBefore = entries.reduce((n2, e) => e.running ? n2 + 1 : n2, 0);
|
|
21412
|
+
for (const boxId of toPause) {
|
|
21413
|
+
const e = byId.get(boxId);
|
|
21414
|
+
if (!e) continue;
|
|
21415
|
+
try {
|
|
21416
|
+
await pause(e.containerName);
|
|
21417
|
+
const mins = e.idleMs != null ? Math.round(e.idleMs / 6e4) : null;
|
|
21418
|
+
events.append({
|
|
21419
|
+
boxId,
|
|
21420
|
+
type: "autopause",
|
|
21421
|
+
payload: {
|
|
21422
|
+
containerName: e.containerName,
|
|
21423
|
+
action: "paused",
|
|
21424
|
+
idleMs: e.idleMs,
|
|
21425
|
+
runningBefore,
|
|
21426
|
+
max: cfg.maxRunningBoxes
|
|
21427
|
+
}
|
|
21428
|
+
});
|
|
21429
|
+
log(
|
|
21430
|
+
`autopause: paused box ${boxId} (${e.containerName})` + (mins != null ? ` after ~${String(mins)}m idle` : "") + `; running ${String(runningBefore)} -> target ${String(cfg.maxRunningBoxes)}`
|
|
21431
|
+
);
|
|
21432
|
+
} catch (err) {
|
|
21433
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
21434
|
+
log(`autopause: docker pause ${e.containerName} failed: ${msg}`);
|
|
21435
|
+
events.append({
|
|
21436
|
+
boxId,
|
|
21437
|
+
type: "autopause",
|
|
21438
|
+
payload: { containerName: e.containerName, action: "pause-failed", error: msg }
|
|
21439
|
+
});
|
|
21440
|
+
}
|
|
21441
|
+
}
|
|
21442
|
+
} catch (err) {
|
|
21443
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
21444
|
+
log(`autopause: tick error: ${msg}`);
|
|
21445
|
+
} finally {
|
|
21446
|
+
ticking = false;
|
|
21447
|
+
}
|
|
21448
|
+
}
|
|
21449
|
+
const timer = setInterval(() => {
|
|
21450
|
+
if (stopped) return;
|
|
21451
|
+
inFlight = tick();
|
|
21452
|
+
}, intervalMs);
|
|
21453
|
+
timer.unref();
|
|
21454
|
+
return {
|
|
21455
|
+
stop: async () => {
|
|
21456
|
+
stopped = true;
|
|
21457
|
+
clearInterval(timer);
|
|
21458
|
+
await inFlight.catch(() => {
|
|
21459
|
+
});
|
|
21460
|
+
}
|
|
21461
|
+
};
|
|
21462
|
+
}
|
|
21463
|
+
function readPauseState(snap) {
|
|
21464
|
+
const active = readActiveAgent(snap);
|
|
21465
|
+
return { state: coarsePauseState(active.state), updatedAt: active.updatedAt };
|
|
21466
|
+
}
|
|
21467
|
+
function coarsePauseState(s) {
|
|
21468
|
+
switch (s) {
|
|
21469
|
+
case "idle":
|
|
21470
|
+
return "idle";
|
|
21471
|
+
case "waiting":
|
|
21472
|
+
return "waiting";
|
|
21473
|
+
case "working":
|
|
21474
|
+
case "compacting":
|
|
21475
|
+
return "working";
|
|
21476
|
+
case null:
|
|
21477
|
+
return null;
|
|
21478
|
+
// end-plan / question / error / unknown: a live session expecting attention
|
|
21479
|
+
// — never auto-pause it (maps to a non-idle, non-candidate state).
|
|
21480
|
+
default:
|
|
21481
|
+
return "unknown";
|
|
21482
|
+
}
|
|
21483
|
+
}
|
|
21484
|
+
function msSince2(iso) {
|
|
21485
|
+
const t = Date.parse(iso);
|
|
21486
|
+
return Number.isNaN(t) ? null : Date.now() - t;
|
|
21487
|
+
}
|
|
21488
|
+
function toEpoch(iso) {
|
|
21489
|
+
const t = Date.parse(iso);
|
|
21490
|
+
return Number.isNaN(t) ? 0 : t;
|
|
21491
|
+
}
|
|
21492
|
+
var INSPECT_TIMEOUT_MS = 15e3;
|
|
21493
|
+
var PAUSE_TIMEOUT_MS = 3e4;
|
|
21494
|
+
function runDocker(args, timeoutMs) {
|
|
21495
|
+
return new Promise((resolve2) => {
|
|
21496
|
+
const child = (0, import_node_child_process9.spawn)("docker", args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
21497
|
+
let stdout = "";
|
|
21498
|
+
let stderr = "";
|
|
21499
|
+
let settled = false;
|
|
21500
|
+
const finish = (exitCode) => {
|
|
21501
|
+
if (settled) return;
|
|
21502
|
+
settled = true;
|
|
21503
|
+
resolve2({ exitCode, stdout, stderr });
|
|
21504
|
+
};
|
|
21505
|
+
const timer = setTimeout(() => {
|
|
21506
|
+
child.kill("SIGTERM");
|
|
21507
|
+
stderr += `
|
|
21508
|
+
relay: docker ${args.join(" ")} timed out after ${String(timeoutMs)}ms
|
|
21509
|
+
`;
|
|
21510
|
+
finish(124);
|
|
21511
|
+
}, timeoutMs);
|
|
21512
|
+
child.stdout?.on("data", (c3) => {
|
|
21513
|
+
stdout += c3.toString("utf8");
|
|
21514
|
+
});
|
|
21515
|
+
child.stderr?.on("data", (c3) => {
|
|
21516
|
+
stderr += c3.toString("utf8");
|
|
21517
|
+
});
|
|
21518
|
+
child.on("error", (err) => {
|
|
21519
|
+
clearTimeout(timer);
|
|
21520
|
+
stderr += String(err.message ?? err);
|
|
21521
|
+
finish(127);
|
|
21522
|
+
});
|
|
21523
|
+
child.on("close", (code) => {
|
|
21524
|
+
clearTimeout(timer);
|
|
21525
|
+
finish(code ?? -1);
|
|
21526
|
+
});
|
|
21527
|
+
});
|
|
21528
|
+
}
|
|
21529
|
+
async function inspectContainerState(name) {
|
|
21530
|
+
const r = await runDocker(["inspect", "--format", "{{.State.Status}}", name], INSPECT_TIMEOUT_MS);
|
|
21531
|
+
if (r.exitCode !== 0) return "missing";
|
|
21532
|
+
switch (r.stdout.trim()) {
|
|
21533
|
+
case "running":
|
|
21534
|
+
return "running";
|
|
21535
|
+
case "paused":
|
|
21536
|
+
return "paused";
|
|
21537
|
+
case "created":
|
|
21538
|
+
case "exited":
|
|
21539
|
+
case "dead":
|
|
21540
|
+
case "restarting":
|
|
21541
|
+
case "removing":
|
|
21542
|
+
return "stopped";
|
|
21543
|
+
default:
|
|
21544
|
+
return "missing";
|
|
21545
|
+
}
|
|
21546
|
+
}
|
|
21547
|
+
async function pauseContainer(name) {
|
|
21548
|
+
const r = await runDocker(["pause", name], PAUSE_TIMEOUT_MS);
|
|
21549
|
+
if (r.exitCode !== 0) {
|
|
21550
|
+
throw new Error(r.stderr.trim() || `docker pause ${name} exited ${String(r.exitCode)}`);
|
|
21551
|
+
}
|
|
21552
|
+
}
|
|
21553
|
+
|
|
21485
21554
|
// src/bin.ts
|
|
21486
21555
|
var program2 = new Command();
|
|
21487
21556
|
program2.name("agentbox-relay").description("Host-side HTTP relay for box\u2192host events and RPCs").version("0.0.0");
|