@madarco/agentbox 0.12.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 +96 -0
- package/README.md +21 -7
- package/dist/{_cloud-attach-XKO4SHR3.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-HFV6THYG.js → chunk-BYCLD6D6.js} +308 -36
- 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-DHJ7OMIP.js → chunk-TBSIJVSN.js} +149 -47
- package/dist/chunk-TBSIJVSN.js.map +1 -0
- package/dist/{chunk-IZXPJPPV.js → chunk-TCS5HXJX.js} +389 -176
- 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-47LVLYUV.js → dist-3IMQNTTV.js} +14 -69
- package/dist/dist-3IMQNTTV.js.map +1 -0
- package/dist/{dist-RZZSSUNB.js → dist-4DPOL5A7.js} +5 -3
- package/dist/{dist-24PY2ZMO.js → dist-57M6ZA7H.js} +25 -177
- package/dist/dist-57M6ZA7H.js.map +1 -0
- package/dist/{dist-SWUOU34W.js → dist-J2IHD5T7.js} +37 -226
- package/dist/dist-J2IHD5T7.js.map +1 -0
- package/dist/index.js +1524 -921
- package/dist/index.js.map +1 -1
- package/dist/{prepared-state-MQHD3M5F-KE4DT3GX.js → prepared-state-MQHD3M5F-Q27AZU53.js} +2 -2
- package/package.json +9 -7
- 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 +46 -17
- 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 +46 -17
- package/runtime/relay/bin.cjs +305 -230
- 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 +46 -17
- 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-DHJ7OMIP.js.map +0 -1
- package/dist/chunk-ECLLV5JH.js.map +0 -1
- package/dist/chunk-HFV6THYG.js.map +0 -1
- package/dist/chunk-IZXPJPPV.js.map +0 -1
- package/dist/chunk-R5XIDQFR.js.map +0 -1
- package/dist/chunk-SNTHHWKY.js.map +0 -1
- package/dist/dist-24PY2ZMO.js.map +0 -1
- package/dist/dist-47LVLYUV.js.map +0 -1
- package/dist/dist-SWUOU34W.js.map +0 -1
- /package/dist/{_cloud-attach-XKO4SHR3.js.map → _cloud-attach-GUBB5RH2.js.map} +0 -0
- /package/dist/{dist-RZZSSUNB.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",
|
|
@@ -20370,7 +20377,8 @@ var BUILT_IN_DEFAULTS = {
|
|
|
20370
20377
|
sessionName: "opencode"
|
|
20371
20378
|
},
|
|
20372
20379
|
attach: {
|
|
20373
|
-
openIn: "split"
|
|
20380
|
+
openIn: "split",
|
|
20381
|
+
cmuxStatus: true
|
|
20374
20382
|
},
|
|
20375
20383
|
code: {
|
|
20376
20384
|
ide: "auto",
|
|
@@ -20422,8 +20430,8 @@ var KEY_REGISTRY = [
|
|
|
20422
20430
|
{
|
|
20423
20431
|
key: "box.provider",
|
|
20424
20432
|
type: "enum",
|
|
20425
|
-
enumValues: ["docker", "daytona", "hetzner", "vercel"],
|
|
20426
|
-
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."
|
|
20427
20435
|
},
|
|
20428
20436
|
{
|
|
20429
20437
|
key: "box.hostSnapshot",
|
|
@@ -20459,6 +20467,12 @@ var KEY_REGISTRY = [
|
|
|
20459
20467
|
description: "Per-provider override of `box.defaultCheckpoint` for vercel. Wins over the global when set; set via `agentbox checkpoint set-default --provider vercel`.",
|
|
20460
20468
|
advanced: true
|
|
20461
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
|
+
},
|
|
20462
20476
|
{
|
|
20463
20477
|
key: "box.size",
|
|
20464
20478
|
type: "string",
|
|
@@ -20488,6 +20502,12 @@ var KEY_REGISTRY = [
|
|
|
20488
20502
|
description: "Per-provider override of `box.size` for vercel. Reserved \u2014 vercel sizing is controlled via `box.vercelVcpus`.",
|
|
20489
20503
|
advanced: true
|
|
20490
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
|
+
},
|
|
20491
20511
|
{
|
|
20492
20512
|
key: "checkpoint.maxLayers",
|
|
20493
20513
|
type: "int",
|
|
@@ -20559,6 +20579,12 @@ var KEY_REGISTRY = [
|
|
|
20559
20579
|
description: "Per-provider override of `box.image` for vercel (snapshot id, e.g. `snap_\u2026`). Written by `agentbox prepare --provider vercel`.",
|
|
20560
20580
|
advanced: true
|
|
20561
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
|
+
},
|
|
20562
20588
|
{
|
|
20563
20589
|
key: "box.imageRegistry",
|
|
20564
20590
|
type: "string",
|
|
@@ -20640,7 +20666,12 @@ var KEY_REGISTRY = [
|
|
|
20640
20666
|
key: "attach.openIn",
|
|
20641
20667
|
type: "enum",
|
|
20642
20668
|
enumValues: ["split", "window", "tab", "same"],
|
|
20643
|
-
description: "Where `agentbox claude|codex|opencode` opens the attached session when run from tmux or iTerm2: `split` (tmux split-window / iTerm2 vertical split, default), `window` (tmux new-window / new iTerm2 window), `tab` (tmux new-window / new iTerm2 tab), or `same` (attach inline in the current terminal). Outside tmux/iTerm2 every value behaves like `same`."
|
|
20669
|
+
description: "Where `agentbox claude|codex|opencode` opens the attached session when run from tmux, cmux, or iTerm2: `split` (tmux split-window / cmux new-split / iTerm2 vertical split, default \u2014 same workspace), `window` (tmux new-window / cmux new-workspace / new iTerm2 window), `tab` (tmux new-window / cmux new-surface tab in the current pane, same workspace / new iTerm2 tab), or `same` (attach inline in the current terminal). Outside tmux/cmux/iTerm2 every value behaves like `same`."
|
|
20670
|
+
},
|
|
20671
|
+
{
|
|
20672
|
+
key: "attach.cmuxStatus",
|
|
20673
|
+
type: "bool",
|
|
20674
|
+
description: "When attached inside cmux, reflect the box agent's live activity on its cmux workspace (colour + description: blue=working, amber=needs input, idle clears; restored on detach) and, when the agent needs input, flag the box's own tab via a cmux notification (tab badge + reorder + desktop notification) so it stands out among sibling tabs. cmux only; no-op in other terminals."
|
|
20644
20675
|
},
|
|
20645
20676
|
{
|
|
20646
20677
|
key: "code.ide",
|
|
@@ -20894,210 +20925,18 @@ var GLOBAL_CONFIG_FILE = (0, import_path2.join)(STATE_DIR2, "config.yaml");
|
|
|
20894
20925
|
var PROJECTS_DIR = (0, import_path2.join)(STATE_DIR2, "projects");
|
|
20895
20926
|
var PROJECT_GC_COUNTER_FILE = (0, import_path3.join)(PROJECTS_DIR, ".gc.json");
|
|
20896
20927
|
|
|
20897
|
-
// src/autopause.ts
|
|
20898
|
-
function selectBoxesToPause(entries, cfg) {
|
|
20899
|
-
if (!cfg.enabled) return [];
|
|
20900
|
-
const runningCount = entries.reduce((n2, e) => e.running ? n2 + 1 : n2, 0);
|
|
20901
|
-
const excess = runningCount - cfg.maxRunningBoxes;
|
|
20902
|
-
if (excess <= 0) return [];
|
|
20903
|
-
const idleThresholdMs = cfg.idleMinutes * 6e4;
|
|
20904
|
-
const candidates = entries.filter(
|
|
20905
|
-
(e) => e.running && e.claudeState === "idle" && e.idleMs != null && e.idleMs >= idleThresholdMs
|
|
20906
|
-
);
|
|
20907
|
-
candidates.sort(
|
|
20908
|
-
(a2, b) => b.idleMs - a2.idleMs || a2.createdAt - b.createdAt || (a2.boxId < b.boxId ? -1 : a2.boxId > b.boxId ? 1 : 0)
|
|
20909
|
-
);
|
|
20910
|
-
return candidates.slice(0, excess).map((e) => e.boxId);
|
|
20911
|
-
}
|
|
20912
|
-
async function loadAutopauseConfig() {
|
|
20913
|
-
const d = BUILT_IN_DEFAULTS.autopause;
|
|
20914
|
-
let global3 = {};
|
|
20915
|
-
try {
|
|
20916
|
-
global3 = parseUserConfig(await (0, import_promises16.readFile)(GLOBAL_CONFIG_FILE, "utf8"), GLOBAL_CONFIG_FILE);
|
|
20917
|
-
} catch {
|
|
20918
|
-
}
|
|
20919
|
-
const a2 = global3.autopause ?? {};
|
|
20920
|
-
return {
|
|
20921
|
-
enabled: a2.enabled ?? d.enabled,
|
|
20922
|
-
maxRunningBoxes: a2.maxRunningBoxes ?? d.maxRunningBoxes,
|
|
20923
|
-
idleMinutes: a2.idleMinutes ?? d.idleMinutes
|
|
20924
|
-
};
|
|
20925
|
-
}
|
|
20926
|
-
var DEFAULT_INTERVAL_MS = 6e4;
|
|
20927
|
-
function startAutopauseLoop(deps) {
|
|
20928
|
-
const loadConfig = deps.loadConfig ?? loadAutopauseConfig;
|
|
20929
|
-
const inspectStatus = deps.inspectStatus ?? inspectContainerState;
|
|
20930
|
-
const pause = deps.pause ?? pauseContainer;
|
|
20931
|
-
const intervalMs = deps.intervalMs ?? DEFAULT_INTERVAL_MS;
|
|
20932
|
-
const { registry, statusStore, events, log } = deps;
|
|
20933
|
-
let ticking = false;
|
|
20934
|
-
let stopped = false;
|
|
20935
|
-
let inFlight = Promise.resolve();
|
|
20936
|
-
async function tick() {
|
|
20937
|
-
if (ticking) return;
|
|
20938
|
-
ticking = true;
|
|
20939
|
-
try {
|
|
20940
|
-
const cfg = await loadConfig();
|
|
20941
|
-
if (!cfg.enabled) return;
|
|
20942
|
-
const entries = [];
|
|
20943
|
-
for (const reg of registry.list()) {
|
|
20944
|
-
if (!reg.containerName) continue;
|
|
20945
|
-
const state = await inspectStatus(reg.containerName);
|
|
20946
|
-
const claude = readClaude(statusStore.get(reg.boxId));
|
|
20947
|
-
const idleMs = claude.state === "idle" && claude.updatedAt ? msSince(claude.updatedAt) : null;
|
|
20948
|
-
entries.push({
|
|
20949
|
-
boxId: reg.boxId,
|
|
20950
|
-
containerName: reg.containerName,
|
|
20951
|
-
running: state === "running",
|
|
20952
|
-
claudeState: claude.state,
|
|
20953
|
-
idleMs,
|
|
20954
|
-
createdAt: reg.createdAt ? toEpoch(reg.createdAt) : 0
|
|
20955
|
-
});
|
|
20956
|
-
}
|
|
20957
|
-
const toPause = selectBoxesToPause(entries, cfg);
|
|
20958
|
-
if (toPause.length === 0) return;
|
|
20959
|
-
const byId = new Map(entries.map((e) => [e.boxId, e]));
|
|
20960
|
-
const runningBefore = entries.reduce((n2, e) => e.running ? n2 + 1 : n2, 0);
|
|
20961
|
-
for (const boxId of toPause) {
|
|
20962
|
-
const e = byId.get(boxId);
|
|
20963
|
-
if (!e) continue;
|
|
20964
|
-
try {
|
|
20965
|
-
await pause(e.containerName);
|
|
20966
|
-
const mins = e.idleMs != null ? Math.round(e.idleMs / 6e4) : null;
|
|
20967
|
-
events.append({
|
|
20968
|
-
boxId,
|
|
20969
|
-
type: "autopause",
|
|
20970
|
-
payload: {
|
|
20971
|
-
containerName: e.containerName,
|
|
20972
|
-
action: "paused",
|
|
20973
|
-
idleMs: e.idleMs,
|
|
20974
|
-
runningBefore,
|
|
20975
|
-
max: cfg.maxRunningBoxes
|
|
20976
|
-
}
|
|
20977
|
-
});
|
|
20978
|
-
log(
|
|
20979
|
-
`autopause: paused box ${boxId} (${e.containerName})` + (mins != null ? ` after ~${String(mins)}m idle` : "") + `; running ${String(runningBefore)} -> target ${String(cfg.maxRunningBoxes)}`
|
|
20980
|
-
);
|
|
20981
|
-
} catch (err) {
|
|
20982
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
20983
|
-
log(`autopause: docker pause ${e.containerName} failed: ${msg}`);
|
|
20984
|
-
events.append({
|
|
20985
|
-
boxId,
|
|
20986
|
-
type: "autopause",
|
|
20987
|
-
payload: { containerName: e.containerName, action: "pause-failed", error: msg }
|
|
20988
|
-
});
|
|
20989
|
-
}
|
|
20990
|
-
}
|
|
20991
|
-
} catch (err) {
|
|
20992
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
20993
|
-
log(`autopause: tick error: ${msg}`);
|
|
20994
|
-
} finally {
|
|
20995
|
-
ticking = false;
|
|
20996
|
-
}
|
|
20997
|
-
}
|
|
20998
|
-
const timer = setInterval(() => {
|
|
20999
|
-
if (stopped) return;
|
|
21000
|
-
inFlight = tick();
|
|
21001
|
-
}, intervalMs);
|
|
21002
|
-
timer.unref();
|
|
21003
|
-
return {
|
|
21004
|
-
stop: async () => {
|
|
21005
|
-
stopped = true;
|
|
21006
|
-
clearInterval(timer);
|
|
21007
|
-
await inFlight.catch(() => {
|
|
21008
|
-
});
|
|
21009
|
-
}
|
|
21010
|
-
};
|
|
21011
|
-
}
|
|
21012
|
-
function readClaude(snap) {
|
|
21013
|
-
const c3 = snap && typeof snap === "object" ? snap.claude : void 0;
|
|
21014
|
-
if (!c3 || typeof c3 !== "object") return { state: null, updatedAt: null };
|
|
21015
|
-
const o2 = c3;
|
|
21016
|
-
const state = o2.state === "working" || o2.state === "idle" || o2.state === "waiting" || o2.state === "unknown" ? o2.state : null;
|
|
21017
|
-
return { state, updatedAt: typeof o2.updatedAt === "string" ? o2.updatedAt : null };
|
|
21018
|
-
}
|
|
21019
|
-
function msSince(iso) {
|
|
21020
|
-
const t = Date.parse(iso);
|
|
21021
|
-
return Number.isNaN(t) ? null : Date.now() - t;
|
|
21022
|
-
}
|
|
21023
|
-
function toEpoch(iso) {
|
|
21024
|
-
const t = Date.parse(iso);
|
|
21025
|
-
return Number.isNaN(t) ? 0 : t;
|
|
21026
|
-
}
|
|
21027
|
-
var INSPECT_TIMEOUT_MS = 15e3;
|
|
21028
|
-
var PAUSE_TIMEOUT_MS = 3e4;
|
|
21029
|
-
function runDocker(args, timeoutMs) {
|
|
21030
|
-
return new Promise((resolve2) => {
|
|
21031
|
-
const child = (0, import_node_child_process8.spawn)("docker", args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
21032
|
-
let stdout = "";
|
|
21033
|
-
let stderr = "";
|
|
21034
|
-
let settled = false;
|
|
21035
|
-
const finish = (exitCode) => {
|
|
21036
|
-
if (settled) return;
|
|
21037
|
-
settled = true;
|
|
21038
|
-
resolve2({ exitCode, stdout, stderr });
|
|
21039
|
-
};
|
|
21040
|
-
const timer = setTimeout(() => {
|
|
21041
|
-
child.kill("SIGTERM");
|
|
21042
|
-
stderr += `
|
|
21043
|
-
relay: docker ${args.join(" ")} timed out after ${String(timeoutMs)}ms
|
|
21044
|
-
`;
|
|
21045
|
-
finish(124);
|
|
21046
|
-
}, timeoutMs);
|
|
21047
|
-
child.stdout?.on("data", (c3) => {
|
|
21048
|
-
stdout += c3.toString("utf8");
|
|
21049
|
-
});
|
|
21050
|
-
child.stderr?.on("data", (c3) => {
|
|
21051
|
-
stderr += c3.toString("utf8");
|
|
21052
|
-
});
|
|
21053
|
-
child.on("error", (err) => {
|
|
21054
|
-
clearTimeout(timer);
|
|
21055
|
-
stderr += String(err.message ?? err);
|
|
21056
|
-
finish(127);
|
|
21057
|
-
});
|
|
21058
|
-
child.on("close", (code) => {
|
|
21059
|
-
clearTimeout(timer);
|
|
21060
|
-
finish(code ?? -1);
|
|
21061
|
-
});
|
|
21062
|
-
});
|
|
21063
|
-
}
|
|
21064
|
-
async function inspectContainerState(name) {
|
|
21065
|
-
const r = await runDocker(["inspect", "--format", "{{.State.Status}}", name], INSPECT_TIMEOUT_MS);
|
|
21066
|
-
if (r.exitCode !== 0) return "missing";
|
|
21067
|
-
switch (r.stdout.trim()) {
|
|
21068
|
-
case "running":
|
|
21069
|
-
return "running";
|
|
21070
|
-
case "paused":
|
|
21071
|
-
return "paused";
|
|
21072
|
-
case "created":
|
|
21073
|
-
case "exited":
|
|
21074
|
-
case "dead":
|
|
21075
|
-
case "restarting":
|
|
21076
|
-
case "removing":
|
|
21077
|
-
return "stopped";
|
|
21078
|
-
default:
|
|
21079
|
-
return "missing";
|
|
21080
|
-
}
|
|
21081
|
-
}
|
|
21082
|
-
async function pauseContainer(name) {
|
|
21083
|
-
const r = await runDocker(["pause", name], PAUSE_TIMEOUT_MS);
|
|
21084
|
-
if (r.exitCode !== 0) {
|
|
21085
|
-
throw new Error(r.stderr.trim() || `docker pause ${name} exited ${String(r.exitCode)}`);
|
|
21086
|
-
}
|
|
21087
|
-
}
|
|
21088
|
-
|
|
21089
20928
|
// src/queue.ts
|
|
21090
|
-
var
|
|
21091
|
-
var
|
|
20929
|
+
var import_node_child_process8 = require("child_process");
|
|
20930
|
+
var import_promises16 = require("fs/promises");
|
|
21092
20931
|
var import_node_fs6 = require("fs");
|
|
21093
20932
|
var import_node_path8 = require("path");
|
|
21094
|
-
var
|
|
20933
|
+
var import_promises17 = require("timers/promises");
|
|
21095
20934
|
var QUEUE_DIR = (0, import_node_path8.join)(STATE_DIR, "queue");
|
|
21096
20935
|
async function loadQueueConfig() {
|
|
21097
20936
|
const d = BUILT_IN_DEFAULTS.queue;
|
|
21098
20937
|
let global3 = {};
|
|
21099
20938
|
try {
|
|
21100
|
-
global3 = parseUserConfig(await (0,
|
|
20939
|
+
global3 = parseUserConfig(await (0, import_promises16.readFile)(GLOBAL_CONFIG_FILE, "utf8"), GLOBAL_CONFIG_FILE);
|
|
21101
20940
|
} catch {
|
|
21102
20941
|
}
|
|
21103
20942
|
const q = global3.queue ?? {};
|
|
@@ -21109,25 +20948,32 @@ async function loadQueueConfig() {
|
|
|
21109
20948
|
};
|
|
21110
20949
|
}
|
|
21111
20950
|
async function writeJob(job) {
|
|
21112
|
-
await (0,
|
|
20951
|
+
await (0, import_promises16.mkdir)(QUEUE_DIR, { recursive: true });
|
|
21113
20952
|
const final = (0, import_node_path8.join)(QUEUE_DIR, `${job.id}.json`);
|
|
21114
20953
|
const tmp = `${final}.tmp.${String(process.pid)}.${String(Date.now())}`;
|
|
21115
|
-
await (0,
|
|
21116
|
-
await (0,
|
|
20954
|
+
await (0, import_promises16.writeFile)(tmp, JSON.stringify(job, null, 2) + "\n", "utf8");
|
|
20955
|
+
await (0, import_promises16.rename)(tmp, final);
|
|
21117
20956
|
}
|
|
21118
20957
|
async function readJob(id) {
|
|
21119
20958
|
try {
|
|
21120
|
-
const raw = await (0,
|
|
20959
|
+
const raw = await (0, import_promises16.readFile)((0, import_node_path8.join)(QUEUE_DIR, `${id}.json`), "utf8");
|
|
21121
20960
|
return JSON.parse(raw);
|
|
21122
20961
|
} catch (err) {
|
|
21123
20962
|
if (err.code === "ENOENT") return null;
|
|
21124
20963
|
throw err;
|
|
21125
20964
|
}
|
|
21126
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
|
+
}
|
|
21127
20973
|
async function loadQueue() {
|
|
21128
20974
|
let entries;
|
|
21129
20975
|
try {
|
|
21130
|
-
entries = await (0,
|
|
20976
|
+
entries = await (0, import_promises16.readdir)(QUEUE_DIR);
|
|
21131
20977
|
} catch (err) {
|
|
21132
20978
|
if (err.code === "ENOENT") return [];
|
|
21133
20979
|
throw err;
|
|
@@ -21136,7 +20982,7 @@ async function loadQueue() {
|
|
|
21136
20982
|
for (const name of entries) {
|
|
21137
20983
|
if (!name.endsWith(".json")) continue;
|
|
21138
20984
|
try {
|
|
21139
|
-
const raw = await (0,
|
|
20985
|
+
const raw = await (0, import_promises16.readFile)((0, import_node_path8.join)(QUEUE_DIR, name), "utf8");
|
|
21140
20986
|
out.push(JSON.parse(raw));
|
|
21141
20987
|
} catch {
|
|
21142
20988
|
}
|
|
@@ -21210,22 +21056,23 @@ function parseTime(iso) {
|
|
|
21210
21056
|
const t = Date.parse(iso);
|
|
21211
21057
|
return Number.isNaN(t) ? 0 : t;
|
|
21212
21058
|
}
|
|
21213
|
-
function
|
|
21059
|
+
function msSince(iso) {
|
|
21214
21060
|
if (!iso) return null;
|
|
21215
21061
|
const t = Date.parse(iso);
|
|
21216
21062
|
return Number.isNaN(t) ? null : Date.now() - t;
|
|
21217
21063
|
}
|
|
21218
|
-
var
|
|
21064
|
+
var DEFAULT_INTERVAL_MS = 2e3;
|
|
21219
21065
|
function startQueueLoop(deps) {
|
|
21220
21066
|
const loadConfig = deps.loadConfig ?? loadQueueConfig;
|
|
21221
21067
|
const countRunning = deps.countRunning ?? defaultCountRunningBoxes;
|
|
21222
21068
|
const spawnWorker = deps.spawnWorker ?? defaultSpawnWorker;
|
|
21223
|
-
const intervalMs = deps.intervalMs ??
|
|
21069
|
+
const intervalMs = deps.intervalMs ?? DEFAULT_INTERVAL_MS;
|
|
21224
21070
|
const { log, onStatusChange } = deps;
|
|
21225
21071
|
const countWorking = deps.countWorking ?? (deps.registry && deps.statusStore ? (idleGraceMs) => defaultCountWorkingBoxes(deps.registry, deps.statusStore, idleGraceMs) : null);
|
|
21226
21072
|
let ticking = false;
|
|
21227
21073
|
let stopped = false;
|
|
21228
21074
|
let warnedNoWorkingDeps = false;
|
|
21075
|
+
let lastSweepAt = 0;
|
|
21229
21076
|
let inFlight = recoverOrphanedWorkers(log, onStatusChange).catch((err) => {
|
|
21230
21077
|
log(`queue: orphan recovery failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
21231
21078
|
});
|
|
@@ -21235,6 +21082,13 @@ function startQueueLoop(deps) {
|
|
|
21235
21082
|
try {
|
|
21236
21083
|
const cfg = await loadConfig();
|
|
21237
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
|
+
}
|
|
21238
21092
|
const jobs = await loadQueue();
|
|
21239
21093
|
const hasQueued = jobs.some((j) => j.status === "queued");
|
|
21240
21094
|
if (!hasQueued) return;
|
|
@@ -21337,6 +21191,21 @@ async function recoverOrphanedWorkers(log, onChange) {
|
|
|
21337
21191
|
log(`queue: recovered orphan job ${j.id} (pid ${String(j.pid ?? "?")} not alive) -> failed`);
|
|
21338
21192
|
}
|
|
21339
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
|
+
}
|
|
21340
21209
|
function processAlive(pid) {
|
|
21341
21210
|
try {
|
|
21342
21211
|
process.kill(pid, 0);
|
|
@@ -21353,8 +21222,8 @@ async function defaultCountWorkingBoxes(registry, statusStore, idleGraceMs) {
|
|
|
21353
21222
|
return {
|
|
21354
21223
|
key: b.boxId,
|
|
21355
21224
|
agentState: active.state,
|
|
21356
|
-
sinceUpdateMs:
|
|
21357
|
-
sinceCreateMs:
|
|
21225
|
+
sinceUpdateMs: msSince(active.updatedAt),
|
|
21226
|
+
sinceCreateMs: msSince(b.createdAt)
|
|
21358
21227
|
};
|
|
21359
21228
|
});
|
|
21360
21229
|
const count2 = countWorkingSlots(entries, idleGraceMs);
|
|
@@ -21426,7 +21295,7 @@ async function uncachedBoxStateCount() {
|
|
|
21426
21295
|
}
|
|
21427
21296
|
function inspectDockerState(containerName) {
|
|
21428
21297
|
return new Promise((resolveP) => {
|
|
21429
|
-
const child = (0,
|
|
21298
|
+
const child = (0, import_node_child_process8.spawn)("docker", ["inspect", "--format", "{{.State.Status}}", containerName], {
|
|
21430
21299
|
stdio: ["ignore", "pipe", "pipe"]
|
|
21431
21300
|
});
|
|
21432
21301
|
let out = "";
|
|
@@ -21460,9 +21329,9 @@ async function defaultSpawnWorker(job) {
|
|
|
21460
21329
|
`AGENTBOX_CLI_ENTRY not set or missing (${String(entry)}); cannot spawn queue worker`
|
|
21461
21330
|
);
|
|
21462
21331
|
}
|
|
21463
|
-
await (0,
|
|
21332
|
+
await (0, import_promises16.mkdir)((0, import_node_path8.join)(STATE_DIR, "logs"), { recursive: true });
|
|
21464
21333
|
const fd = (0, import_node_fs6.openSync)(job.logPath, "a");
|
|
21465
|
-
const child = (0,
|
|
21334
|
+
const child = (0, import_node_child_process8.spawn)(process.execPath, [entry, "_run-queued-job", job.id], {
|
|
21466
21335
|
detached: true,
|
|
21467
21336
|
stdio: ["ignore", fd, fd],
|
|
21468
21337
|
env: process.env
|
|
@@ -21476,6 +21345,212 @@ async function defaultSpawnWorker(job) {
|
|
|
21476
21345
|
}
|
|
21477
21346
|
var QUEUE_LOGS_DIR = (0, import_node_path8.join)(STATE_DIR, "logs");
|
|
21478
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
|
+
|
|
21479
21554
|
// src/bin.ts
|
|
21480
21555
|
var program2 = new Command();
|
|
21481
21556
|
program2.name("agentbox-relay").description("Host-side HTTP relay for box\u2192host events and RPCs").version("0.0.0");
|