@madarco/agentbox 0.6.0 → 0.7.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-DMVH6GWO.js +12 -0
- package/dist/chunk-7KOEFGN2.js +1162 -0
- package/dist/chunk-7KOEFGN2.js.map +1 -0
- package/dist/chunk-I24B6AXR.js +600 -0
- package/dist/chunk-I24B6AXR.js.map +1 -0
- package/dist/chunk-NAVL4R34.js +7546 -0
- package/dist/chunk-NAVL4R34.js.map +1 -0
- package/dist/chunk-NW5NYTQM.js +1366 -0
- package/dist/chunk-NW5NYTQM.js.map +1 -0
- package/dist/chunk-UK72UQ5U.js +237 -0
- package/dist/chunk-UK72UQ5U.js.map +1 -0
- package/dist/chunk-V5KZGB5V.js +722 -0
- package/dist/chunk-V5KZGB5V.js.map +1 -0
- package/dist/cloud-poller-ZIWSADJB-JXFRJUEM.js +10 -0
- package/dist/dist-ETCFRVPA.js +423 -0
- package/dist/dist-QZGJIBT5.js +1339 -0
- package/dist/dist-QZGJIBT5.js.map +1 -0
- package/dist/dist-R67WMLCF.js +183 -0
- package/dist/dist-R67WMLCF.js.map +1 -0
- package/dist/index.js +3998 -1569
- package/dist/index.js.map +1 -1
- package/package.json +8 -3
- package/runtime/docker/Dockerfile.box +98 -14
- package/runtime/docker/apps/cli/share/agentbox-setup/SKILL.md +15 -8
- package/runtime/docker/packages/ctl/dist/bin.cjs +10220 -773
- package/runtime/docker/packages/sandbox-docker/scripts/agentbox-codex-hooks.json +37 -0
- package/runtime/docker/packages/sandbox-docker/scripts/agentbox-open +9 -9
- package/runtime/hetzner/agentbox-checkpoint-cleanup +52 -0
- package/runtime/hetzner/agentbox-codex-hooks.json +37 -0
- package/runtime/hetzner/agentbox-dockerd-start +132 -0
- package/runtime/hetzner/agentbox-open +28 -0
- package/runtime/hetzner/agentbox-setup-skill.md +196 -0
- package/runtime/hetzner/agentbox-vnc-start +77 -0
- package/runtime/hetzner/claude-managed-settings.json +54 -0
- package/runtime/hetzner/ctl.cjs +22350 -0
- package/runtime/hetzner/custom-system-CLAUDE.md +27 -0
- package/runtime/hetzner/scripts/install-box.sh +365 -0
- package/runtime/relay/bin.cjs +9118 -809
- package/share/agentbox-setup/SKILL.md +15 -8
- package/dist/chunk-BBZMA2K6.js +0 -238
- package/dist/chunk-BBZMA2K6.js.map +0 -1
- package/dist/chunk-HHMWQNLF.js +0 -1709
- package/dist/chunk-HHMWQNLF.js.map +0 -1
- package/dist/chunk-HPZMD5DE.js +0 -106
- package/dist/chunk-HPZMD5DE.js.map +0 -1
- package/dist/chunk-HTTKML3C.js +0 -2655
- package/dist/chunk-HTTKML3C.js.map +0 -1
- package/dist/chunk-KJNZP6I3.js +0 -586
- package/dist/chunk-KJNZP6I3.js.map +0 -1
- package/dist/chunk-M7I247BK.js +0 -525
- package/dist/chunk-M7I247BK.js.map +0 -1
- package/dist/create-6PWXI6HO-OWAMHBAK.js +0 -15
- package/dist/lifecycle-EMXR46DI-DUVBXNTV.js +0 -38
- package/dist/state-KD7M46ZP-KHFTHFUS.js +0 -26
- package/dist/stats-SZXOJE3D-N7OODCHW.js +0 -19
- package/dist/stats-SZXOJE3D-N7OODCHW.js.map +0 -1
- /package/dist/{create-6PWXI6HO-OWAMHBAK.js.map → _cloud-attach-DMVH6GWO.js.map} +0 -0
- /package/dist/{lifecycle-EMXR46DI-DUVBXNTV.js.map → cloud-poller-ZIWSADJB-JXFRJUEM.js.map} +0 -0
- /package/dist/{state-KD7M46ZP-KHFTHFUS.js.map → dist-ETCFRVPA.js.map} +0 -0
|
@@ -7,16 +7,22 @@ description: Generate an agentbox.yaml for the current AgentBox workspace. Invok
|
|
|
7
7
|
|
|
8
8
|
## Box layout (what you're configuring against)
|
|
9
9
|
|
|
10
|
-
Your user i `vscode` and you can use
|
|
10
|
+
Your user i `vscode` and you can use `sudo` to run commands as root.
|
|
11
11
|
|
|
12
|
-
`/workspace` is the
|
|
12
|
+
`/workspace` is where the user code lives, a per-box git worktree on a fresh `agentbox/<box-name>` branch (or a tar-piped copy of the host workspace for non-git projects).
|
|
13
|
+
Run `agentbox checkpoint --set-default` (similar to `docker commit`) to save any changes make to the system and workspace so that new boxes will start from a warm state. Everything is wiped on `agentbox destroy`.
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
Some special folders:
|
|
15
16
|
|
|
16
|
-
- **Host main repo's `.git/`** — bind-mounted RW at its identical absolute host path. In-box commits land on the host's branch refs (visible to `git log` on the host immediately); the box itself carries no SSH/git creds, so `git push` goes through the host relay (`agentbox-ctl git push`). The host's **working tree is never written to** — only refs/objects under `.git/`.
|
|
17
|
-
- **`~/.claude`** —
|
|
17
|
+
- **Host main repo's `.git/`** — If the box bind-mounted RW at its identical absolute host path. In-box commits land on the host's branch refs (visible to `git log` on the host immediately); the box itself carries no SSH/git creds, so `git push` goes through the host relay (`agentbox-ctl git push`). The host's **working tree is never written to** — only refs/objects under `.git/`.
|
|
18
|
+
- **`~/.claude`** — and similar home folders for coding agents are seeded from the host's `~/.claude` on each create so auth, skills, and plugins persist without leaking the host's home dir.
|
|
18
19
|
- **`agentbox.yaml`** — read by `agentbox-ctl` from `/workspace`. Tasks and services declared here are what the supervisor will run.
|
|
19
20
|
|
|
21
|
+
Exposed ports and services:
|
|
22
|
+
- **portless** - every port with `expose:` setting in agentbox.yaml, will be exposed not only as a local port but also as a special domain name `https://<name>.localhost` (so on https) using `portless` cli and proxy. This will be also mapped to the host where also `portless` proxy is running so users can access the same service on the same looking url.
|
|
23
|
+
- **vnc** - the webVNC server exposed on 6080 will be proxies to the host on a random port.
|
|
24
|
+
- **vscode** - the vscode server is proxied to the host on a random port.
|
|
25
|
+
|
|
20
26
|
## Goal
|
|
21
27
|
|
|
22
28
|
Produce a `/workspace/agentbox.yaml` that captures this project's services, tasks, and box defaults so the in-box supervisor (`agentbox-ctl`) can boot the workspace deterministically.
|
|
@@ -64,7 +70,7 @@ The box's primary web app (the dev server / Next.js / API the user opens in a br
|
|
|
64
70
|
as: 80 # must be 80 — the container port AgentBox publishes
|
|
65
71
|
```
|
|
66
72
|
|
|
67
|
-
At most **one** service may set `expose:`. AgentBox forwards container `:80` to `127.0.0.1:<port>` and publishes it on the host, so `agentbox list`/`status` show it as the box's main URL on every engine (no OrbStack dependency). Set this on the same service whose `ready_when:` you just wrote (a DB or worker should **not** get `expose:`).
|
|
73
|
+
At most **one** service may set `expose:`. AgentBox forwards container `:80` to `127.0.0.1:<port>` and publishes it on the host with `portless` proxy to a <boxname>.localhost url, so `agentbox list`/`status` show it as the box's main URL on every engine (no OrbStack dependency). Set this on the same service whose `ready_when:` you just wrote (a DB or worker should **not** get `expose:`).
|
|
68
74
|
|
|
69
75
|
## 4. Restart + backoff
|
|
70
76
|
|
|
@@ -179,11 +185,12 @@ Tell the user (verbatim):
|
|
|
179
185
|
```
|
|
180
186
|
|
|
181
187
|
your box is ready, you can start more sessions with `agentbox claude`
|
|
188
|
+
you can access the web app at https://<boxname>.localhost
|
|
182
189
|
|
|
183
190
|
## 10. Known issues
|
|
184
191
|
|
|
185
192
|
- For Nextjs/Vite/Tasnstack projects, makes sure to forward also websocket for hot reload.
|
|
186
193
|
|
|
187
|
-
-
|
|
194
|
+
- Service like flask, nextjs, BETTER_AUTH_URL, NEXT_PUBLIC_APP_URL should use the <boxname>.localhost url for the local development so that on the host it will use the same url as the box.
|
|
188
195
|
|
|
189
|
-
-
|
|
196
|
+
- The `install` task is intentionally a no-op once `node_modules/.agentbox-installed` exists. Do **not** remove the marker guard to "force a fresh install" — that reinstalls on every box start. To force a one-off rebuild, delete `node_modules` (or just the marker) then run `agentbox-ctl reload`.
|
package/dist/chunk-BBZMA2K6.js
DELETED
|
@@ -1,238 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
CHECKPOINT_IMAGE_PREFIX,
|
|
4
|
-
checkpointImageTag,
|
|
5
|
-
detectEngine,
|
|
6
|
-
inspectContainer,
|
|
7
|
-
inspectContainerStatus,
|
|
8
|
-
inspectVolumeMountpoint
|
|
9
|
-
} from "./chunk-HHMWQNLF.js";
|
|
10
|
-
|
|
11
|
-
// ../../packages/sandbox-docker/dist/chunk-RKWE53VF.js
|
|
12
|
-
import { homedir } from "os";
|
|
13
|
-
import { join } from "path";
|
|
14
|
-
import { execa } from "execa";
|
|
15
|
-
function parseDockerSize(raw) {
|
|
16
|
-
const s = raw.trim();
|
|
17
|
-
if (!s || s === "--" || s === "N/A") return null;
|
|
18
|
-
const m = /^([\d.]+)\s*([A-Za-z]*)$/.exec(s);
|
|
19
|
-
if (!m) return null;
|
|
20
|
-
const n = Number(m[1]);
|
|
21
|
-
if (!Number.isFinite(n)) return null;
|
|
22
|
-
const unit = (m[2] ?? "").toLowerCase();
|
|
23
|
-
const mult = {
|
|
24
|
-
"": 1,
|
|
25
|
-
b: 1,
|
|
26
|
-
kb: 1e3,
|
|
27
|
-
mb: 1e6,
|
|
28
|
-
gb: 1e9,
|
|
29
|
-
tb: 1e12,
|
|
30
|
-
kib: 1024,
|
|
31
|
-
mib: 1024 ** 2,
|
|
32
|
-
gib: 1024 ** 3,
|
|
33
|
-
tib: 1024 ** 4
|
|
34
|
-
};
|
|
35
|
-
const factor = mult[unit];
|
|
36
|
-
return factor === void 0 ? null : n * factor;
|
|
37
|
-
}
|
|
38
|
-
function parsePercent(raw) {
|
|
39
|
-
if (!raw) return null;
|
|
40
|
-
const n = Number(raw.replace("%", "").trim());
|
|
41
|
-
return Number.isFinite(n) ? n : null;
|
|
42
|
-
}
|
|
43
|
-
function splitPair(raw) {
|
|
44
|
-
if (!raw) return null;
|
|
45
|
-
const parts = raw.split("/");
|
|
46
|
-
if (parts.length !== 2) return null;
|
|
47
|
-
return [parts[0].trim(), parts[1].trim()];
|
|
48
|
-
}
|
|
49
|
-
async function duBytes(path) {
|
|
50
|
-
const result = await execa("du", ["-sk", path], { reject: false });
|
|
51
|
-
if (result.exitCode !== 0) return null;
|
|
52
|
-
const kb = Number.parseInt((result.stdout ?? "").split(/\s+/)[0] ?? "", 10);
|
|
53
|
-
return Number.isNaN(kb) ? null : kb * 1024;
|
|
54
|
-
}
|
|
55
|
-
async function volumeSizeBytes(name) {
|
|
56
|
-
if (!name) return null;
|
|
57
|
-
const engine = await detectEngine();
|
|
58
|
-
if (engine === "orbstack") {
|
|
59
|
-
const live = join(homedir(), "OrbStack", "docker", "volumes", name);
|
|
60
|
-
const sz = await duBytes(live);
|
|
61
|
-
if (sz !== null) return sz;
|
|
62
|
-
}
|
|
63
|
-
const df = await execa(
|
|
64
|
-
"docker",
|
|
65
|
-
["system", "df", "-v", "--format", "{{json .Volumes}}"],
|
|
66
|
-
{ reject: false }
|
|
67
|
-
);
|
|
68
|
-
if (df.exitCode === 0) {
|
|
69
|
-
try {
|
|
70
|
-
const vols = JSON.parse(df.stdout || "[]");
|
|
71
|
-
const hit = vols.find((v) => v.Name === name);
|
|
72
|
-
const parsed = hit?.Size ? parseDockerSize(hit.Size) : null;
|
|
73
|
-
if (parsed !== null) return parsed;
|
|
74
|
-
} catch {
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
const mp = await inspectVolumeMountpoint(name);
|
|
78
|
-
if (mp && !mp.startsWith("/var/lib/docker")) {
|
|
79
|
-
return duBytes(mp);
|
|
80
|
-
}
|
|
81
|
-
return null;
|
|
82
|
-
}
|
|
83
|
-
async function imageBytes(tag) {
|
|
84
|
-
const r = await execa("docker", ["image", "inspect", tag, "--format", "{{.Size}}"], {
|
|
85
|
-
reject: false
|
|
86
|
-
});
|
|
87
|
-
if (r.exitCode !== 0) return null;
|
|
88
|
-
const n = Number.parseInt((r.stdout ?? "").trim(), 10);
|
|
89
|
-
return Number.isFinite(n) ? n : null;
|
|
90
|
-
}
|
|
91
|
-
async function projectCheckpointImageBytes(projectRoot, name) {
|
|
92
|
-
return imageBytes(checkpointImageTag(projectRoot, name));
|
|
93
|
-
}
|
|
94
|
-
async function allCheckpointImagesBytes() {
|
|
95
|
-
const r = await execa(
|
|
96
|
-
"docker",
|
|
97
|
-
[
|
|
98
|
-
"image",
|
|
99
|
-
"ls",
|
|
100
|
-
"--format",
|
|
101
|
-
"{{.Repository}}:{{.Tag}} {{.Size}}",
|
|
102
|
-
`${CHECKPOINT_IMAGE_PREFIX}*`
|
|
103
|
-
],
|
|
104
|
-
{ reject: false }
|
|
105
|
-
);
|
|
106
|
-
if (r.exitCode !== 0) return null;
|
|
107
|
-
const lines = (r.stdout ?? "").split("\n").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
108
|
-
if (lines.length === 0) return null;
|
|
109
|
-
let total = 0;
|
|
110
|
-
let any = false;
|
|
111
|
-
for (const line of lines) {
|
|
112
|
-
const [, size] = line.split(" ");
|
|
113
|
-
const n = size ? parseDockerSize(size) : null;
|
|
114
|
-
if (n !== null) {
|
|
115
|
-
total += n;
|
|
116
|
-
any = true;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
return any ? total : null;
|
|
120
|
-
}
|
|
121
|
-
async function agentboxHomeBytes() {
|
|
122
|
-
return duBytes(join(homedir(), ".agentbox"));
|
|
123
|
-
}
|
|
124
|
-
function limitsFromRecord(record) {
|
|
125
|
-
const r = record.resourceLimits;
|
|
126
|
-
return {
|
|
127
|
-
memoryBytes: r?.memoryBytes && r.memoryBytes > 0 ? r.memoryBytes : null,
|
|
128
|
-
cpus: r?.cpus && r.cpus > 0 ? r.cpus : null,
|
|
129
|
-
pidsLimit: r?.pidsLimit && r.pidsLimit > 0 ? r.pidsLimit : null,
|
|
130
|
-
disk: r?.disk || null
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
function reconcileLimits(persisted, dockerJson) {
|
|
134
|
-
const hc = dockerJson?.HostConfig;
|
|
135
|
-
if (!hc) return persisted;
|
|
136
|
-
const mem = typeof hc.Memory === "number" && hc.Memory > 0 ? hc.Memory : null;
|
|
137
|
-
const nano = typeof hc.NanoCpus === "number" && hc.NanoCpus > 0 ? hc.NanoCpus : null;
|
|
138
|
-
const pids = typeof hc.PidsLimit === "number" && hc.PidsLimit > 0 ? hc.PidsLimit : null;
|
|
139
|
-
return {
|
|
140
|
-
memoryBytes: mem ?? persisted.memoryBytes,
|
|
141
|
-
cpus: nano ? nano / 1e9 : persisted.cpus,
|
|
142
|
-
pidsLimit: pids ?? persisted.pidsLimit,
|
|
143
|
-
disk: persisted.disk
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
async function containerWritableBytes(container) {
|
|
147
|
-
const r = await execa(
|
|
148
|
-
"docker",
|
|
149
|
-
["ps", "-a", "--filter", `name=^${container}$`, "--format", "{{.Size}}", "--size"],
|
|
150
|
-
{ reject: false }
|
|
151
|
-
);
|
|
152
|
-
if (r.exitCode !== 0) return null;
|
|
153
|
-
const first = (r.stdout ?? "").split("\n")[0]?.trim();
|
|
154
|
-
if (!first) return null;
|
|
155
|
-
const m = /^([^()]+?)(?:\s*\(.*\))?$/.exec(first);
|
|
156
|
-
const sz = m ? m[1].trim() : first;
|
|
157
|
-
return parseDockerSize(sz);
|
|
158
|
-
}
|
|
159
|
-
async function boxResourceStats(record) {
|
|
160
|
-
const warnings = [];
|
|
161
|
-
const dockerJson = await inspectContainer(record.container);
|
|
162
|
-
const limits = reconcileLimits(limitsFromRecord(record), dockerJson);
|
|
163
|
-
const [diskContainer, diskDocker, snapshotDiskBytes, checkpointImageBytesValue] = await Promise.all([
|
|
164
|
-
containerWritableBytes(record.container),
|
|
165
|
-
record.dockerVolume ? volumeSizeBytes(record.dockerVolume) : Promise.resolve(null),
|
|
166
|
-
record.snapshotDir ? duBytes(record.snapshotDir) : Promise.resolve(null),
|
|
167
|
-
record.checkpointImage ? imageBytes(record.checkpointImage) : Promise.resolve(null)
|
|
168
|
-
]);
|
|
169
|
-
const diskUsedBytes = diskContainer === null && diskDocker === null ? null : (diskContainer ?? 0) + (diskDocker ?? 0);
|
|
170
|
-
if (diskUsedBytes === null) {
|
|
171
|
-
warnings.push("disk usage unavailable on this engine");
|
|
172
|
-
}
|
|
173
|
-
const base = {
|
|
174
|
-
source: "docker",
|
|
175
|
-
live: false,
|
|
176
|
-
cpuPercent: null,
|
|
177
|
-
memUsedBytes: null,
|
|
178
|
-
memLimitBytes: limits.memoryBytes,
|
|
179
|
-
memPercent: null,
|
|
180
|
-
pids: null,
|
|
181
|
-
diskUsedBytes,
|
|
182
|
-
snapshotDiskBytes,
|
|
183
|
-
checkpointVolumeBytes: checkpointImageBytesValue,
|
|
184
|
-
netRxBytes: null,
|
|
185
|
-
netTxBytes: null,
|
|
186
|
-
blockReadBytes: null,
|
|
187
|
-
blockWriteBytes: null,
|
|
188
|
-
limits,
|
|
189
|
-
warnings
|
|
190
|
-
};
|
|
191
|
-
if (await inspectContainerStatus(record.container) !== "running") {
|
|
192
|
-
return base;
|
|
193
|
-
}
|
|
194
|
-
const proc = await execa(
|
|
195
|
-
"docker",
|
|
196
|
-
["stats", "--no-stream", "--format", "{{json .}}", record.container],
|
|
197
|
-
{ reject: false }
|
|
198
|
-
);
|
|
199
|
-
if (proc.exitCode !== 0 || !proc.stdout.trim()) {
|
|
200
|
-
return base;
|
|
201
|
-
}
|
|
202
|
-
let line;
|
|
203
|
-
try {
|
|
204
|
-
line = JSON.parse(proc.stdout.trim().split("\n")[0]);
|
|
205
|
-
} catch {
|
|
206
|
-
return base;
|
|
207
|
-
}
|
|
208
|
-
const memPair = splitPair(line.MemUsage);
|
|
209
|
-
const memUsedBytes = memPair ? parseDockerSize(memPair[0]) : null;
|
|
210
|
-
const memEngineTotal = memPair ? parseDockerSize(memPair[1]) : null;
|
|
211
|
-
const netPair = splitPair(line.NetIO);
|
|
212
|
-
const blkPair = splitPair(line.BlockIO);
|
|
213
|
-
return {
|
|
214
|
-
...base,
|
|
215
|
-
live: true,
|
|
216
|
-
cpuPercent: parsePercent(line.CPUPerc),
|
|
217
|
-
memUsedBytes,
|
|
218
|
-
// The applied limit when set; otherwise docker stats' own denominator
|
|
219
|
-
// (the engine/host total).
|
|
220
|
-
memLimitBytes: limits.memoryBytes ?? memEngineTotal,
|
|
221
|
-
memPercent: parsePercent(line.MemPerc),
|
|
222
|
-
pids: line.PIDs ? Number.parseInt(line.PIDs, 10) || null : null,
|
|
223
|
-
netRxBytes: netPair ? parseDockerSize(netPair[0]) : null,
|
|
224
|
-
netTxBytes: netPair ? parseDockerSize(netPair[1]) : null,
|
|
225
|
-
blockReadBytes: blkPair ? parseDockerSize(blkPair[0]) : null,
|
|
226
|
-
blockWriteBytes: blkPair ? parseDockerSize(blkPair[1]) : null
|
|
227
|
-
};
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
export {
|
|
231
|
-
parseDockerSize,
|
|
232
|
-
volumeSizeBytes,
|
|
233
|
-
projectCheckpointImageBytes,
|
|
234
|
-
allCheckpointImagesBytes,
|
|
235
|
-
agentboxHomeBytes,
|
|
236
|
-
boxResourceStats
|
|
237
|
-
};
|
|
238
|
-
//# sourceMappingURL=chunk-BBZMA2K6.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../packages/sandbox-docker/src/stats.ts"],"sourcesContent":["import { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { execa } from 'execa';\nimport type { BoxResourceLimits, BoxResourceStats } from '@agentbox/core';\nimport { CHECKPOINT_IMAGE_PREFIX, checkpointImageTag } from './checkpoint.js';\nimport {\n inspectContainer,\n inspectContainerStatus,\n inspectVolumeMountpoint,\n} from './docker.js';\nimport { detectEngine } from './host-export.js';\nimport type { BoxRecord } from './state.js';\n\n/**\n * Parse one of Docker's human-formatted size tokens (`512MiB`, `1.2kB`,\n * `3.4GB`, `0B`, `--`). Returns null when unparseable. Docker mixes binary\n * (`KiB/MiB/GiB`) and decimal (`kB/MB/GB`) suffixes depending on the column, so\n * we handle both.\n */\nexport function parseDockerSize(raw: string): number | null {\n const s = raw.trim();\n if (!s || s === '--' || s === 'N/A') return null;\n const m = /^([\\d.]+)\\s*([A-Za-z]*)$/.exec(s);\n if (!m) return null;\n const n = Number(m[1]);\n if (!Number.isFinite(n)) return null;\n const unit = (m[2] ?? '').toLowerCase();\n const mult: Record<string, number> = {\n '': 1,\n b: 1,\n kb: 1e3,\n mb: 1e6,\n gb: 1e9,\n tb: 1e12,\n kib: 1024,\n mib: 1024 ** 2,\n gib: 1024 ** 3,\n tib: 1024 ** 4,\n };\n const factor = mult[unit];\n return factor === undefined ? null : n * factor;\n}\n\nfunction parsePercent(raw: string | undefined): number | null {\n if (!raw) return null;\n const n = Number(raw.replace('%', '').trim());\n return Number.isFinite(n) ? n : null;\n}\n\n/** Split Docker's \"<a> / <b>\" pair columns (MemUsage, NetIO, BlockIO). */\nfunction splitPair(raw: string | undefined): [string, string] | null {\n if (!raw) return null;\n const parts = raw.split('/');\n if (parts.length !== 2) return null;\n return [parts[0]!.trim(), parts[1]!.trim()];\n}\n\nasync function duBytes(path: string): Promise<number | null> {\n const result = await execa('du', ['-sk', path], { reject: false });\n if (result.exitCode !== 0) return null;\n const kb = Number.parseInt((result.stdout ?? '').split(/\\s+/)[0] ?? '', 10);\n return Number.isNaN(kb) ? null : kb * 1024;\n}\n\n/**\n * Best-effort on-host byte size of a Docker named volume. Fastest path first:\n * 1. OrbStack exposes volumes live at ~/OrbStack/docker/volumes/<name>/.\n * 2. `docker system df -v` (cheap-walked once; may report \"N/A\").\n * 3. The reported mountpoint, only when host-readable (Linux native Docker).\n * Returns null when no path is reachable from the host (the macOS VM case\n * where `system df` also has no number).\n */\nexport async function volumeSizeBytes(name: string): Promise<number | null> {\n if (!name) return null;\n const engine = await detectEngine();\n if (engine === 'orbstack') {\n const live = join(homedir(), 'OrbStack', 'docker', 'volumes', name);\n const sz = await duBytes(live);\n if (sz !== null) return sz;\n }\n const df = await execa(\n 'docker',\n ['system', 'df', '-v', '--format', '{{json .Volumes}}'],\n { reject: false },\n );\n if (df.exitCode === 0) {\n try {\n const vols = JSON.parse(df.stdout || '[]') as Array<{ Name?: string; Size?: string }>;\n const hit = vols.find((v) => v.Name === name);\n const parsed = hit?.Size ? parseDockerSize(hit.Size) : null;\n if (parsed !== null) return parsed;\n } catch {\n // fall through to mountpoint\n }\n }\n const mp = await inspectVolumeMountpoint(name);\n if (mp && !mp.startsWith('/var/lib/docker')) {\n return duBytes(mp);\n }\n return null;\n}\n\n/**\n * On-host byte size of a Docker image (sum of its own layer sizes — what\n * `docker images` reports). Null on docker errors.\n */\nasync function imageBytes(tag: string): Promise<number | null> {\n const r = await execa('docker', ['image', 'inspect', tag, '--format', '{{.Size}}'], {\n reject: false,\n });\n if (r.exitCode !== 0) return null;\n const n = Number.parseInt((r.stdout ?? '').trim(), 10);\n return Number.isFinite(n) ? n : null;\n}\n\n/**\n * Size of a project's most-recent checkpoint image (the head of the lineage,\n * resolved via the `checkpointImageTag` helper from a checkpoint *name*). The\n * caller passes the name because we don't enumerate manifests from this\n * module — that lives in checkpoint.ts. Null when the image isn't present.\n */\nexport async function projectCheckpointImageBytes(\n projectRoot: string,\n name: string,\n): Promise<number | null> {\n return imageBytes(checkpointImageTag(projectRoot, name));\n}\n\n/**\n * Total on-host bytes of every checkpoint image (the durable, cross-box\n * warm-state assets). Walks every image tag under `CHECKPOINT_IMAGE_PREFIX`.\n * Null when none exist.\n */\nexport async function allCheckpointImagesBytes(): Promise<number | null> {\n const r = await execa(\n 'docker',\n [\n 'image',\n 'ls',\n '--format',\n '{{.Repository}}:{{.Tag}}\\t{{.Size}}',\n `${CHECKPOINT_IMAGE_PREFIX}*`,\n ],\n { reject: false },\n );\n if (r.exitCode !== 0) return null;\n const lines = (r.stdout ?? '')\n .split('\\n')\n .map((s) => s.trim())\n .filter((s) => s.length > 0);\n if (lines.length === 0) return null;\n let total = 0;\n let any = false;\n for (const line of lines) {\n const [, size] = line.split('\\t');\n const n = size ? parseDockerSize(size) : null;\n if (n !== null) {\n total += n;\n any = true;\n }\n }\n return any ? total : null;\n}\n\n/** On-host byte size of the whole ~/.agentbox state/runtime directory. */\nexport async function agentboxHomeBytes(): Promise<number | null> {\n return duBytes(join(homedir(), '.agentbox'));\n}\n\nfunction limitsFromRecord(record: BoxRecord): BoxResourceLimits {\n const r = record.resourceLimits;\n return {\n memoryBytes: r?.memoryBytes && r.memoryBytes > 0 ? r.memoryBytes : null,\n cpus: r?.cpus && r.cpus > 0 ? r.cpus : null,\n pidsLimit: r?.pidsLimit && r.pidsLimit > 0 ? r.pidsLimit : null,\n disk: r?.disk || null,\n };\n}\n\n/**\n * Cross-check persisted limits against the live container's HostConfig so an\n * externally `docker update`d box still reports the truth. The persisted\n * record stays the fallback when the container is gone.\n */\nfunction reconcileLimits(persisted: BoxResourceLimits, dockerJson: unknown): BoxResourceLimits {\n const hc = (dockerJson as { HostConfig?: Record<string, unknown> } | null)?.HostConfig;\n if (!hc) return persisted;\n const mem = typeof hc.Memory === 'number' && hc.Memory > 0 ? hc.Memory : null;\n const nano = typeof hc.NanoCpus === 'number' && hc.NanoCpus > 0 ? hc.NanoCpus : null;\n const pids = typeof hc.PidsLimit === 'number' && hc.PidsLimit > 0 ? hc.PidsLimit : null;\n return {\n memoryBytes: mem ?? persisted.memoryBytes,\n cpus: nano ? nano / 1e9 : persisted.cpus,\n pidsLimit: pids ?? persisted.pidsLimit,\n disk: persisted.disk,\n };\n}\n\ninterface DockerStatsLine {\n CPUPerc?: string;\n MemUsage?: string;\n MemPerc?: string;\n PIDs?: string;\n NetIO?: string;\n BlockIO?: string;\n}\n\n/**\n * Container writable-layer size from `docker ps --size`. With the overlay\n * gone, `/workspace` lives here (not in a named volume), so this is the\n * box's primary writable-surface number.\n */\nasync function containerWritableBytes(container: string): Promise<number | null> {\n const r = await execa(\n 'docker',\n ['ps', '-a', '--filter', `name=^${container}$`, '--format', '{{.Size}}', '--size'],\n { reject: false },\n );\n if (r.exitCode !== 0) return null;\n // `--size` produces `<rw> (virtual <total>)`; we want the first number.\n const first = (r.stdout ?? '').split('\\n')[0]?.trim();\n if (!first) return null;\n const m = /^([^()]+?)(?:\\s*\\(.*\\))?$/.exec(first);\n const sz = m ? m[1]!.trim() : first;\n return parseDockerSize(sz);\n}\n\n/**\n * Provider-agnostic resource snapshot for a box. CPU/mem/pids/IO come from\n * `docker stats --no-stream` (point-in-time sample; only when the container is\n * running). Disk is the container's writable layer (where `/workspace` lives\n * now that the overlay is gone) plus the in-box dockerd's data-root volume;\n * the per-box host snapshot dir and the checkpoint image lineage are\n * reported on their own fields.\n */\nexport async function boxResourceStats(record: BoxRecord): Promise<BoxResourceStats> {\n const warnings: string[] = [];\n const dockerJson = await inspectContainer(record.container);\n const limits = reconcileLimits(limitsFromRecord(record), dockerJson);\n\n const [diskContainer, diskDocker, snapshotDiskBytes, checkpointImageBytesValue] =\n await Promise.all([\n containerWritableBytes(record.container),\n record.dockerVolume ? volumeSizeBytes(record.dockerVolume) : Promise.resolve(null),\n record.snapshotDir ? duBytes(record.snapshotDir) : Promise.resolve(null),\n record.checkpointImage ? imageBytes(record.checkpointImage) : Promise.resolve(null),\n ]);\n const diskUsedBytes =\n diskContainer === null && diskDocker === null\n ? null\n : (diskContainer ?? 0) + (diskDocker ?? 0);\n if (diskUsedBytes === null) {\n warnings.push('disk usage unavailable on this engine');\n }\n\n const base: BoxResourceStats = {\n source: 'docker',\n live: false,\n cpuPercent: null,\n memUsedBytes: null,\n memLimitBytes: limits.memoryBytes,\n memPercent: null,\n pids: null,\n diskUsedBytes,\n snapshotDiskBytes,\n checkpointVolumeBytes: checkpointImageBytesValue,\n netRxBytes: null,\n netTxBytes: null,\n blockReadBytes: null,\n blockWriteBytes: null,\n limits,\n warnings,\n };\n\n if ((await inspectContainerStatus(record.container)) !== 'running') {\n return base;\n }\n\n const proc = await execa(\n 'docker',\n ['stats', '--no-stream', '--format', '{{json .}}', record.container],\n { reject: false },\n );\n if (proc.exitCode !== 0 || !proc.stdout.trim()) {\n return base;\n }\n let line: DockerStatsLine;\n try {\n line = JSON.parse(proc.stdout.trim().split('\\n')[0]!) as DockerStatsLine;\n } catch {\n return base;\n }\n\n const memPair = splitPair(line.MemUsage);\n const memUsedBytes = memPair ? parseDockerSize(memPair[0]) : null;\n const memEngineTotal = memPair ? parseDockerSize(memPair[1]) : null;\n const netPair = splitPair(line.NetIO);\n const blkPair = splitPair(line.BlockIO);\n\n return {\n ...base,\n live: true,\n cpuPercent: parsePercent(line.CPUPerc),\n memUsedBytes,\n // The applied limit when set; otherwise docker stats' own denominator\n // (the engine/host total).\n memLimitBytes: limits.memoryBytes ?? memEngineTotal,\n memPercent: parsePercent(line.MemPerc),\n pids: line.PIDs ? Number.parseInt(line.PIDs, 10) || null : null,\n netRxBytes: netPair ? parseDockerSize(netPair[0]) : null,\n netTxBytes: netPair ? parseDockerSize(netPair[1]) : null,\n blockReadBytes: blkPair ? parseDockerSize(blkPair[0]) : null,\n blockWriteBytes: blkPair ? parseDockerSize(blkPair[1]) : null,\n };\n}\n"],"mappings":"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","names":[]}
|