@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.
Files changed (59) hide show
  1. package/dist/_cloud-attach-DMVH6GWO.js +12 -0
  2. package/dist/chunk-7KOEFGN2.js +1162 -0
  3. package/dist/chunk-7KOEFGN2.js.map +1 -0
  4. package/dist/chunk-I24B6AXR.js +600 -0
  5. package/dist/chunk-I24B6AXR.js.map +1 -0
  6. package/dist/chunk-NAVL4R34.js +7546 -0
  7. package/dist/chunk-NAVL4R34.js.map +1 -0
  8. package/dist/chunk-NW5NYTQM.js +1366 -0
  9. package/dist/chunk-NW5NYTQM.js.map +1 -0
  10. package/dist/chunk-UK72UQ5U.js +237 -0
  11. package/dist/chunk-UK72UQ5U.js.map +1 -0
  12. package/dist/chunk-V5KZGB5V.js +722 -0
  13. package/dist/chunk-V5KZGB5V.js.map +1 -0
  14. package/dist/cloud-poller-ZIWSADJB-JXFRJUEM.js +10 -0
  15. package/dist/dist-ETCFRVPA.js +423 -0
  16. package/dist/dist-QZGJIBT5.js +1339 -0
  17. package/dist/dist-QZGJIBT5.js.map +1 -0
  18. package/dist/dist-R67WMLCF.js +183 -0
  19. package/dist/dist-R67WMLCF.js.map +1 -0
  20. package/dist/index.js +3998 -1569
  21. package/dist/index.js.map +1 -1
  22. package/package.json +8 -3
  23. package/runtime/docker/Dockerfile.box +98 -14
  24. package/runtime/docker/apps/cli/share/agentbox-setup/SKILL.md +15 -8
  25. package/runtime/docker/packages/ctl/dist/bin.cjs +10220 -773
  26. package/runtime/docker/packages/sandbox-docker/scripts/agentbox-codex-hooks.json +37 -0
  27. package/runtime/docker/packages/sandbox-docker/scripts/agentbox-open +9 -9
  28. package/runtime/hetzner/agentbox-checkpoint-cleanup +52 -0
  29. package/runtime/hetzner/agentbox-codex-hooks.json +37 -0
  30. package/runtime/hetzner/agentbox-dockerd-start +132 -0
  31. package/runtime/hetzner/agentbox-open +28 -0
  32. package/runtime/hetzner/agentbox-setup-skill.md +196 -0
  33. package/runtime/hetzner/agentbox-vnc-start +77 -0
  34. package/runtime/hetzner/claude-managed-settings.json +54 -0
  35. package/runtime/hetzner/ctl.cjs +22350 -0
  36. package/runtime/hetzner/custom-system-CLAUDE.md +27 -0
  37. package/runtime/hetzner/scripts/install-box.sh +365 -0
  38. package/runtime/relay/bin.cjs +9118 -809
  39. package/share/agentbox-setup/SKILL.md +15 -8
  40. package/dist/chunk-BBZMA2K6.js +0 -238
  41. package/dist/chunk-BBZMA2K6.js.map +0 -1
  42. package/dist/chunk-HHMWQNLF.js +0 -1709
  43. package/dist/chunk-HHMWQNLF.js.map +0 -1
  44. package/dist/chunk-HPZMD5DE.js +0 -106
  45. package/dist/chunk-HPZMD5DE.js.map +0 -1
  46. package/dist/chunk-HTTKML3C.js +0 -2655
  47. package/dist/chunk-HTTKML3C.js.map +0 -1
  48. package/dist/chunk-KJNZP6I3.js +0 -586
  49. package/dist/chunk-KJNZP6I3.js.map +0 -1
  50. package/dist/chunk-M7I247BK.js +0 -525
  51. package/dist/chunk-M7I247BK.js.map +0 -1
  52. package/dist/create-6PWXI6HO-OWAMHBAK.js +0 -15
  53. package/dist/lifecycle-EMXR46DI-DUVBXNTV.js +0 -38
  54. package/dist/state-KD7M46ZP-KHFTHFUS.js +0 -26
  55. package/dist/stats-SZXOJE3D-N7OODCHW.js +0 -19
  56. package/dist/stats-SZXOJE3D-N7OODCHW.js.map +0 -1
  57. /package/dist/{create-6PWXI6HO-OWAMHBAK.js.map → _cloud-attach-DMVH6GWO.js.map} +0 -0
  58. /package/dist/{lifecycle-EMXR46DI-DUVBXNTV.js.map → cloud-poller-ZIWSADJB-JXFRJUEM.js.map} +0 -0
  59. /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 passwordless sudo to run commands as root.
10
+ Your user i `vscode` and you can use `sudo` to run commands as root.
11
11
 
12
- `/workspace` is the box's plain writable filesystem — 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). Anything you install or build into `/workspace` (incl. `node_modules`, `.next`, `target`, `.venv`) lives in the **container's writable layer** and is captured wholesale by `agentbox checkpoint` (`docker commit`) — so a setup task that runs the install once becomes a warm-start asset for every future box in the project. Everything is wiped on `agentbox destroy`.
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
- Three bind mounts wire the box back to the host:
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`** — a Docker named volume (`agentbox-claude-config`, shared across boxes by default) seeded from the host's `~/.claude` on each create so auth, skills, and plugins persist without leaking the host's home dir.
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
- - 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`.
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
- - Host-only CLI wrappers (portless, etc.) must be bypassed, eg some projects wrap the dev server with a host-side proxy (here: `portless projectname next dev --turbopack`). Override the service command: to call the underlying tool directly (`next dev --turbopack`)
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`.
@@ -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":[]}