@madarco/agentbox 0.4.1 → 0.6.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/{chunk-J35IH7W5.js → chunk-BBZMA2K6.js} +61 -23
- package/dist/chunk-BBZMA2K6.js.map +1 -0
- package/dist/{chunk-SOMIKEN2.js → chunk-HHMWQNLF.js} +272 -214
- package/dist/chunk-HHMWQNLF.js.map +1 -0
- package/dist/{chunk-IDR4HVIC.js → chunk-HPZMD5DE.js} +2 -2
- package/dist/chunk-HPZMD5DE.js.map +1 -0
- package/dist/{chunk-NSIECUCS.js → chunk-HTTKML3C.js} +705 -289
- package/dist/chunk-HTTKML3C.js.map +1 -0
- package/dist/{chunk-WR5FFGE5.js → chunk-KJNZP6I3.js} +218 -128
- package/dist/chunk-KJNZP6I3.js.map +1 -0
- package/dist/{chunk-FQD6ZWYW.js → chunk-M7I247BK.js} +68 -65
- package/dist/chunk-M7I247BK.js.map +1 -0
- package/dist/create-6PWXI6HO-OWAMHBAK.js +15 -0
- package/dist/index.js +2394 -1283
- package/dist/index.js.map +1 -1
- package/dist/{lifecycle-LURNDNYO-UWQYPNPX.js → lifecycle-EMXR46DI-DUVBXNTV.js} +5 -5
- package/dist/{state-ZSP3ORXW-WI6KOIG3.js → state-KD7M46ZP-KHFTHFUS.js} +2 -2
- package/dist/stats-SZXOJE3D-N7OODCHW.js +19 -0
- package/package.json +3 -2
- package/runtime/docker/Dockerfile.box +65 -25
- package/runtime/docker/apps/cli/share/agentbox-setup/SKILL.md +52 -55
- package/runtime/docker/packages/ctl/dist/bin.cjs +272 -160
- package/runtime/docker/packages/sandbox-docker/scripts/agentbox-checkpoint-cleanup +52 -0
- package/runtime/docker/packages/sandbox-docker/scripts/agentbox-dockerd-start +87 -7
- package/runtime/docker/packages/sandbox-docker/scripts/agentbox-open +28 -0
- package/runtime/docker/packages/sandbox-docker/scripts/custom-system-CLAUDE.md +21 -15
- package/runtime/relay/bin.cjs +407 -12
- package/share/agentbox-setup/SKILL.md +52 -55
- package/dist/chunk-FQD6ZWYW.js.map +0 -1
- package/dist/chunk-IDR4HVIC.js.map +0 -1
- package/dist/chunk-J35IH7W5.js.map +0 -1
- package/dist/chunk-NSIECUCS.js.map +0 -1
- package/dist/chunk-SOMIKEN2.js.map +0 -1
- package/dist/chunk-WR5FFGE5.js.map +0 -1
- package/dist/create-4BQY2UYU-CGSW3RGE.js +0 -15
- package/dist/stats-GZFLPYTU-DBJ2DVBJ.js +0 -19
- /package/dist/{create-4BQY2UYU-CGSW3RGE.js.map → create-6PWXI6HO-OWAMHBAK.js.map} +0 -0
- /package/dist/{lifecycle-LURNDNYO-UWQYPNPX.js.map → lifecycle-EMXR46DI-DUVBXNTV.js.map} +0 -0
- /package/dist/{state-ZSP3ORXW-WI6KOIG3.js.map → state-KD7M46ZP-KHFTHFUS.js.map} +0 -0
- /package/dist/{stats-GZFLPYTU-DBJ2DVBJ.js.map → stats-SZXOJE3D-N7OODCHW.js.map} +0 -0
|
@@ -5,7 +5,21 @@ description: Generate an agentbox.yaml for the current AgentBox workspace. Invok
|
|
|
5
5
|
|
|
6
6
|
# /agentbox-setup
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
## Box layout (what you're configuring against)
|
|
9
|
+
|
|
10
|
+
Your user i `vscode` and you can use passwordless sudo to run commands as root.
|
|
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`.
|
|
13
|
+
|
|
14
|
+
Three bind mounts wire the box back to the host:
|
|
15
|
+
|
|
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.
|
|
18
|
+
- **`agentbox.yaml`** — read by `agentbox-ctl` from `/workspace`. Tasks and services declared here are what the supervisor will run.
|
|
19
|
+
|
|
20
|
+
## Goal
|
|
21
|
+
|
|
22
|
+
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.
|
|
9
23
|
|
|
10
24
|
`agentbox.yaml` is **declarative**. The supervisor reads it on box start, but you don't have to restart the box: after you write the file, `agentbox-ctl reload` (run from inside the box) makes the already-running supervisor re-read it and immediately run the declared tasks and autostart the services. See step 8.
|
|
11
25
|
|
|
@@ -23,10 +37,12 @@ Look at `/workspace`:
|
|
|
23
37
|
## 2. Pick services and tasks
|
|
24
38
|
|
|
25
39
|
- **Services** = long-running. Web servers, watchers, queue workers, databases. `restart: on-failure` by default.
|
|
26
|
-
- **Tasks** = one-shot. `pnpm install`, DB migrations, codegen, fixture loaders. Wire dependent services with `needs:` so they wait for the task to finish successfully.
|
|
40
|
+
- **Tasks** = one-shot. `pnpm install`, DB migrations, codegen, fixture loaders, install apt packages. Wire dependent services with `needs:` so they wait for the task to finish successfully.
|
|
27
41
|
- Names: must match `[A-Za-z0-9_-]+`. Task names and service names share a namespace — no collisions.
|
|
28
42
|
- No cycles in `needs:`.
|
|
29
|
-
- **Always generate a dependency-install task** and make it the root of the `needs:` graph (every service that needs deps gets `needs: [install, …]`).
|
|
43
|
+
- **Always generate a dependency-install task** and make it the root of the `needs:` graph (every service that needs deps gets `needs: [install, …]`). Future boxes start from a snapshot of the final filesystem so they won't need this, but updates or moving to a cloud provider might need to rebuild the container from scratch. The filesystem can be then later captured by `agentbox-ctl checkpoint --set-default`. The task must be **idempotent and self-healing**: `agentbox-ctl` re-runs pending tasks on every box stop/start (the daemon dies with the container and is relaunched), so a plain `rm -rf node_modules && install` would wipe + reinstall on every start. Guard the rebuild with a marker file *inside* `node_modules` (the `.agentbox-installed` convention AgentBox uses internally): rebuild only when the marker is absent (fresh box), and be a fast no-op once it exists. Detect the package manager from the lockfile — never hardcode `pnpm`. See the worked example below.
|
|
44
|
+
- **Add a comment to the beginning** of the file to explain what you did and what issues you encountered, so that future run might use this information in case the project evolves and you need to update the agentbox.yaml file.
|
|
45
|
+
-
|
|
30
46
|
|
|
31
47
|
## 3. Wire readiness probes (services only)
|
|
32
48
|
|
|
@@ -61,7 +77,7 @@ Per service:
|
|
|
61
77
|
|
|
62
78
|
Sets per-project defaults for `agentbox create`/`claude`/`code`/`shell` — same shape as `~/.agentbox/config.yaml`. CLI flags still override. Common keys:
|
|
63
79
|
|
|
64
|
-
- `box.hostSnapshot` (bool) —
|
|
80
|
+
- `box.hostSnapshot` (bool) — APFS-clone the *host* workspace into a per-box scratch dir before seeding `/workspace` (stabilizes the tar-pipe source).
|
|
65
81
|
- `box.defaultCheckpoint` (string) — checkpoint new boxes start from (normally you set this via `agentbox-ctl checkpoint --set-default` at the end of setup — see section 9, not by hand).
|
|
66
82
|
- `box.withPlaywright` (bool) — install `@playwright/cli` globally inside the box.
|
|
67
83
|
- `box.vnc` (bool) — run Xvnc + noVNC on container port 6080.
|
|
@@ -76,6 +92,11 @@ Full key list (run on the host): `agentbox config list --keys`.
|
|
|
76
92
|
|
|
77
93
|
```yaml
|
|
78
94
|
# yaml-language-server: $schema=https://agentbox.dev/schema/agentbox.schema.json
|
|
95
|
+
# This agentbox.yaml setup this Next.js project, and includes:
|
|
96
|
+
# - a postgres database because it's used in the project
|
|
97
|
+
# - an inngest server for queues
|
|
98
|
+
# - a fix to move .turbo/cache folder to the workspace to avoid a permission error during setup
|
|
99
|
+
# - ...
|
|
79
100
|
defaults:
|
|
80
101
|
box:
|
|
81
102
|
withPlaywright: true
|
|
@@ -83,30 +104,23 @@ defaults:
|
|
|
83
104
|
ide: cursor
|
|
84
105
|
|
|
85
106
|
tasks:
|
|
86
|
-
# Idempotent install.
|
|
87
|
-
#
|
|
88
|
-
# node_modules is macOS-native
|
|
89
|
-
#
|
|
90
|
-
# (agentbox-ctl re-runs pending tasks after
|
|
91
|
-
# lockfile detection to the project's package
|
|
107
|
+
# Idempotent install. /workspace is the container's writable filesystem, so
|
|
108
|
+
# node_modules persists across pause/stop/start and is captured by
|
|
109
|
+
# `agentbox checkpoint`. The host's node_modules is macOS-native and is
|
|
110
|
+
# never copied in, so force a clean Linux build the first time — but skip
|
|
111
|
+
# on every subsequent box start (agentbox-ctl re-runs pending tasks after
|
|
112
|
+
# stop/start). Adjust the lockfile detection to the project's package
|
|
113
|
+
# manager.
|
|
92
114
|
install:
|
|
93
115
|
command: |
|
|
94
116
|
set -e
|
|
95
117
|
MARKER=node_modules/.agentbox-installed
|
|
96
118
|
[ -f "$MARKER" ] && { echo "deps installed (marker present) — skip"; exit 0; }
|
|
119
|
+
apt-get update && apt-get install -y postgresql-client
|
|
97
120
|
rm -rf node_modules
|
|
98
121
|
if [ -f pnpm-lock.yaml ]; then
|
|
99
122
|
corepack enable >/dev/null 2>&1 || true
|
|
100
123
|
pnpm install --frozen-lockfile || pnpm install
|
|
101
|
-
elif [ -f yarn.lock ]; then
|
|
102
|
-
corepack enable >/dev/null 2>&1 || true
|
|
103
|
-
yarn install --frozen-lockfile || yarn install
|
|
104
|
-
elif [ -f bun.lockb ] || [ -f bun.lock ]; then
|
|
105
|
-
bun install
|
|
106
|
-
elif [ -f package-lock.json ]; then
|
|
107
|
-
npm ci || npm install
|
|
108
|
-
else
|
|
109
|
-
npm install
|
|
110
124
|
fi
|
|
111
125
|
touch "$MARKER"
|
|
112
126
|
|
|
@@ -144,49 +158,32 @@ services:
|
|
|
144
158
|
- A service with `restart: never` and an autostart dependency will block the dependent forever after one failed run — usually a mistake.
|
|
145
159
|
- `command:` is either a shell string (run via `bash -c`) or an argv array. Use the argv form if you need to avoid shell quoting.
|
|
146
160
|
|
|
147
|
-
## 8.
|
|
148
|
-
|
|
149
|
-
1. Write the file to `/workspace/agentbox.yaml`.
|
|
150
|
-
2. **Apply it live**: from inside the box run `agentbox-ctl reload`. The already-running supervisor re-reads the config and immediately runs the declared tasks and autostarts the services — no box restart needed. It prints the `added` / `removed` / `changed` diff. If it errors because the daemon isn't running, the config is still valid: the next `agentbox start` (or `agentbox create` in this workspace) picks it up automatically.
|
|
151
|
-
3. Confirm with `agentbox-ctl status`: tasks should be `running` or `done`, autostart services `starting` or `ready`. If something failed, tail it with `agentbox-ctl logs <service>` and fix the config, then `agentbox-ctl reload` again.
|
|
152
|
-
|
|
153
|
-
4. **Then do section 9** — once the box is warmed up (deps installed, services ready), checkpoint it with `agentbox-ctl checkpoint --set-default` so future boxes start ready. This is the real final step; don't stop at hand-off.
|
|
154
|
-
|
|
155
|
-
5. Tell the user:
|
|
161
|
+
## 8. Checkpoint the warm state (do this at the very end)
|
|
156
162
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
163
|
+
Checkpoint (snapshot) this box writable layer: once the box is warmed up (deps installed, services ready), checkpoint it with `agentbox-ctl checkpoint --name setup --replace --set-default` so future boxes start ready.
|
|
164
|
+
Run this command exactly once. The `--name setup --replace` makes it idempotent — if it ever needs to run again it overwrites the existing `setup` checkpoint instead of stacking duplicates.
|
|
165
|
+
This doesn't need to be confirmed by the user.
|
|
166
|
+
It will pause the container for several seconds so warn the user about it and write Done when it's done.
|
|
161
167
|
|
|
168
|
+
## 9. Hand-off
|
|
162
169
|
|
|
163
|
-
|
|
170
|
+
Tell the user (verbatim):
|
|
164
171
|
|
|
165
|
-
|
|
172
|
+
```
|
|
173
|
+
█████╗ ██████╗ ███████╗███╗ ██╗████████╗██████╗ ██████╗ ██╗ ██╗
|
|
174
|
+
██╔══██╗██╔════╝ ██╔════╝████╗ ██║╚══██╔══╝██╔══██╗██╔═══██╗╚██╗██╔╝
|
|
175
|
+
███████║██║ ███╗█████╗ ██╔██╗ ██║ ██║ ██████╔╝██║ ██║ ╚███╔╝
|
|
176
|
+
██╔══██║██║ ██║██╔══╝ ██║╚██╗██║ ██║ ██╔══██╗██║ ██║ ██╔██╗
|
|
177
|
+
██║ ██║╚██████╔╝███████╗██║ ╚████║ ██║ ██████╔╝╚██████╔╝██╔╝ ██╗
|
|
178
|
+
╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝
|
|
179
|
+
```
|
|
166
180
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
- `node_modules` (and `.next`, `target`, `.venv`, build caches) — the expensive Linux-native install,
|
|
170
|
-
- `.env` / `.env.*` / `secrets.toml` and any other gitignored config files present in `/workspace`,
|
|
171
|
-
- any other files written during setup.
|
|
172
|
-
|
|
173
|
-
A new box created from the checkpoint reuses all of it (the install task's marker is already present, so it no-ops), while git-tracked code still comes fresh from the host's current HEAD. Result: new boxes are ready in seconds with no reinstall and no missing env vars.
|
|
174
|
-
|
|
175
|
-
**Run this from inside the box, as the final step:**
|
|
176
|
-
|
|
177
|
-
```sh
|
|
178
|
-
agentbox-ctl checkpoint --set-default
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
This routes through the host relay (no host shell needed), captures `<box-name>-<n>`, and writes `box.defaultCheckpoint` into the project's config so **every future `agentbox create` / `claude` in this project starts warm automatically**. Pass `--name <name>` for a stable name, or omit `--set-default` to capture without changing the default. Re-run it whenever the warm state meaningfully changes (e.g. after a dependency bump you want every new box to inherit).
|
|
182
|
-
|
|
183
|
-
Then tell the user, e.g.:
|
|
184
|
-
|
|
185
|
-
> I checkpointed the warm box state (node_modules, env files, build caches) and set it as this project's default — `agentbox create` will now spin up new boxes from it in seconds, no reinstall.
|
|
181
|
+
your box is ready, you can start more sessions with `agentbox claude`
|
|
186
182
|
|
|
187
183
|
## 10. Known issues
|
|
188
184
|
|
|
189
185
|
- For Nextjs/Vite/Tasnstack projects, makes sure to forward also websocket for hot reload.
|
|
190
|
-
|
|
186
|
+
|
|
191
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`.
|
|
192
|
-
|
|
188
|
+
|
|
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`)
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../packages/sandbox-docker/src/lifecycle.ts","../../../packages/sandbox-docker/src/endpoints.ts"],"sourcesContent":["import { execa } from 'execa';\nimport { readdir, rm, stat } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { BoxState } from '@agentbox/core';\nimport type { BoxStatus, ClaudeActivityState } from '@agentbox/ctl';\nimport { claudeSessionInfo, SHARED_CLAUDE_VOLUME, type ClaudeSessionInfo } from './claude.js';\nimport { removeBoxWorktree } from './git-worktree.js';\nimport {\n cursorServerVolumeName,\n SHARED_CURSOR_EXTENSIONS_VOLUME,\n SHARED_VSCODE_EXTENSIONS_VOLUME,\n vscodeServerVolumeName,\n} from './vscode.js';\nimport {\n BOXES_ROOT,\n boxRunDirFor,\n detectEngine,\n getHostPaths,\n openInFinder,\n readBoxStatus,\n type HostPaths,\n type OpenOptions,\n type OpenResult,\n} from './host-export.js';\nimport {\n inspectContainer,\n inspectContainerStatus,\n inspectVolumeMountpoint,\n listAgentboxContainers,\n listAgentboxVolumes,\n pauseContainer,\n publishedHostPort,\n removeContainer,\n removeNetwork,\n removeVolume,\n startContainer,\n stopContainer,\n unpauseContainer,\n volumeExists,\n} from './docker.js';\nimport {\n DEFAULT_LOWER_DIRS,\n mountOverlay,\n verifyOverlay,\n type NestedWorktreeBind,\n type OverlayCheck,\n} from './overlay.js';\nimport { CHECKPOINT_VOLUME_PREFIX } from './checkpoint.js';\nimport { launchCtlDaemon } from './ctl.js';\nimport { launchDockerdDaemon, SHARED_DOCKER_CACHE_VOLUME } from './dockerd.js';\nimport { launchVncDaemon, VNC_CONTAINER_PORT } from './vnc.js';\nimport { WEB_CONTAINER_PORT } from './web.js';\nimport { getBoxEndpoints, type BoxEndpoints } from './endpoints.js';\nimport {\n ensureRelay,\n forgetBoxFromRelay,\n registerBoxWithRelay,\n RELAY_CONTAINER_NAME,\n RELAY_IMAGE_REF,\n RELAY_NETWORK_NAME,\n} from './relay.js';\nimport { SNAPSHOTS_ROOT } from './snapshot.js';\nimport {\n findBox,\n readState,\n recordBox,\n removeBoxRecord,\n type BoxRecord,\n type FindBoxResult,\n} from './state.js';\n\nexport interface ListedBox extends BoxRecord {\n state: BoxState;\n endpoints: BoxEndpoints;\n /** From the persisted status file; undefined for pre-feature/never-pushed boxes. */\n claudeActivity?: ClaudeActivityState;\n /** Sanitized in-box terminal title Claude set; undefined when none. */\n claudeSessionTitle?: string;\n}\n\nexport async function listBoxes(): Promise<ListedBox[]> {\n const { boxes } = await readState();\n const engine = await detectEngine();\n return Promise.all(\n boxes.map(async (b): Promise<ListedBox> => {\n const state = await inspectContainerStatus(b.container);\n const persisted = await readBoxStatus(b.id);\n const endpoints = await getBoxEndpoints(b, engine, persisted);\n return {\n ...b,\n state,\n endpoints,\n claudeActivity: persisted?.claude.state,\n claudeSessionTitle: persisted?.claude.sessionTitle,\n };\n }),\n );\n}\n\nexport class BoxNotFoundError extends Error {\n constructor(public readonly query: string) {\n super(`no agentbox matches \"${query}\"`);\n this.name = 'BoxNotFoundError';\n }\n}\n\nexport class AmbiguousBoxError extends Error {\n constructor(\n public readonly query: string,\n public readonly matches: BoxRecord[],\n ) {\n const ids = matches.map((m) => m.id).join(', ');\n super(`\"${query}\" matches multiple boxes: ${ids}`);\n this.name = 'AmbiguousBoxError';\n }\n}\n\nasync function pathExists(p: string): Promise<boolean> {\n try {\n await stat(p);\n return true;\n } catch {\n return false;\n }\n}\n\nasync function resolveBox(idOrName: string): Promise<BoxRecord> {\n const state = await readState();\n const result: FindBoxResult = findBox(idOrName, state);\n switch (result.kind) {\n case 'ok':\n return result.box;\n case 'none':\n throw new BoxNotFoundError(idOrName);\n case 'ambiguous':\n throw new AmbiguousBoxError(idOrName, result.matches);\n }\n}\n\nexport async function pauseBox(idOrName: string): Promise<BoxRecord> {\n const box = await resolveBox(idOrName);\n await pauseContainer(box.container);\n return box;\n}\n\nexport async function unpauseBox(idOrName: string): Promise<BoxRecord> {\n const box = await resolveBox(idOrName);\n await unpauseContainer(box.container);\n return box;\n}\n\nexport async function stopBox(idOrName: string): Promise<BoxRecord> {\n const box = await resolveBox(idOrName);\n await stopContainer(box.container);\n return box;\n}\n\nexport interface StartedBox {\n record: BoxRecord;\n overlayChecks: OverlayCheck[];\n}\n\nexport async function startBox(idOrName: string): Promise<StartedBox> {\n const box = await resolveBox(idOrName);\n // Bind mounts are baked into the container at create time; if a worktree\n // dir has been deleted out from under us we can't recover by restarting\n // (Docker just fails the start with an opaque mount error). Surface a clear\n // message up front so the user knows to recreate the box or restore the\n // worktree.\n for (const w of box.gitWorktrees ?? []) {\n if (!(await pathExists(w.hostWorktreeDir))) {\n throw new Error(`box worktree missing on host: ${w.hostWorktreeDir} (recreate the box)`);\n }\n if (!(await pathExists(join(w.hostMainRepo, '.git')))) {\n throw new Error(\n `main repo for box worktree missing: ${join(w.hostMainRepo, '.git')} (recreate the box)`,\n );\n }\n }\n // The per-project checkpoint volume is mounted read-only at create time,\n // same baked-in story as worktrees — if it was removed we can't recover by\n // restart, so fail loudly up front. (A removed checkpoint *subdir* instead\n // surfaces as a loud mountOverlay failure, same as a missing worktree dir.)\n if (box.checkpointVolume && !(await volumeExists(box.checkpointVolume))) {\n throw new Error(\n `box checkpoint volume missing: ${box.checkpointVolume} (recreate the box)`,\n );\n }\n await startContainer(box.container);\n const nestedWorktrees: NestedWorktreeBind[] = (box.gitWorktrees ?? [])\n .filter((w) => w.kind === 'nested')\n .map((w) => ({\n containerPath: w.containerPath,\n mountFromPath: `/agentbox-worktrees/${w.relPathFromWorkspace}`,\n }));\n const lowerDirs = box.lowerDirs && box.lowerDirs.length > 0 ? box.lowerDirs : undefined;\n await mountOverlay(box.container, { lowerDirs, nestedWorktrees });\n const overlayChecks = await verifyOverlay(box.container, lowerDirs ?? DEFAULT_LOWER_DIRS);\n if (box.socketPath) {\n // The daemon died with the container; relaunch it. Best-effort, same as\n // create.ts — a missing config or other startup issue shouldn't block\n // resumption of the box itself.\n await launchCtlDaemon(box.container, box.socketPath);\n }\n // dockerd dies with the container too; relaunch it. Records from before\n // DinD landed have no `dockerVolume`, so we skip them — those boxes were\n // created without the in-box dockerd and don't have the launch script\n // baked in either (image rebuild is required to pick it up).\n if (box.dockerVolume) {\n await launchDockerdDaemon(box.container);\n }\n if (box.vncEnabled) {\n // Xvnc + websockify both die with the container. The password is already\n // in the container env (set at `docker run` time and preserved across\n // start/stop), so we don't need to forward it here.\n await launchVncDaemon(box.container);\n // Docker re-allocates an ephemeral host port for `-p 0:6080` on every\n // `start`, so the loopback URL from create time is stale. Re-resolve and\n // persist; the orb.local URL is name-based and unaffected. Best-effort —\n // a failed resolve just leaves the record as-is.\n const freshHostPort = await publishedHostPort(box.container, VNC_CONTAINER_PORT);\n if (freshHostPort && freshHostPort !== box.vncHostPort) {\n box.vncHostPort = freshHostPort;\n await recordBox(box);\n }\n }\n // Same ephemeral-reallocation story for the reserved web port. Gated on\n // webContainerPort so pre-feature boxes (no `-p 0:80` mapping) are skipped.\n if (box.webContainerPort !== undefined) {\n const freshWebPort = await publishedHostPort(\n box.container,\n box.webContainerPort ?? WEB_CONTAINER_PORT,\n );\n if (freshWebPort && freshWebPort !== box.webHostPort) {\n box.webHostPort = freshWebPort;\n await recordBox(box);\n }\n }\n // Relay's in-memory registry may have been lost if the relay restarted\n // between create and now (or this is the first start after a host reboot).\n // Re-ensure + re-register so outbound push from the box keeps working.\n if (box.relayToken) {\n try {\n await ensureRelay();\n await registerBoxWithRelay({\n boxId: box.id,\n token: box.relayToken,\n name: box.name,\n containerName: box.container,\n createdAt: box.createdAt,\n worktrees: box.gitWorktrees,\n });\n } catch {\n // best-effort\n }\n }\n return { record: box, overlayChecks };\n}\n\nexport interface OpenedBox extends OpenResult {\n record: BoxRecord;\n}\n\nexport async function openBoxInFinder(idOrName: string, opts: OpenOptions): Promise<OpenedBox> {\n const box = await resolveBox(idOrName);\n const result = await openInFinder(box, opts);\n return { ...result, record: box };\n}\n\nexport async function getBoxHostPaths(\n idOrName: string,\n): Promise<{ record: BoxRecord; paths: HostPaths }> {\n const box = await resolveBox(idOrName);\n const paths = await getHostPaths(box);\n return { record: box, paths };\n}\n\nexport interface InspectedBox {\n record: BoxRecord;\n state: BoxState;\n upperVolume: { name: string; mountpoint: string | null };\n snapshotSizeBytes: number | null;\n overlayMounted: boolean;\n dockerInspect: unknown;\n /** Null when the container isn't running; otherwise best-effort probe of the tmux 'claude' session. */\n claudeSession: ClaudeSessionInfo | null;\n /** Persisted status snapshot (services/tasks/ports/claude); null when none. */\n persistedStatus: BoxStatus | null;\n /** Host paths for `agentbox open` / `agentbox path`. */\n hostPaths: HostPaths;\n /** Box network surface: domain + VNC + service ports. */\n endpoints: BoxEndpoints;\n}\n\nasync function dirSizeBytes(path: string): Promise<number | null> {\n try {\n const result = await execa('du', ['-sk', path], { reject: false });\n if (result.exitCode !== 0) return null;\n const sizeKb = Number.parseInt((result.stdout ?? '').split(/\\s+/)[0] ?? '', 10);\n if (Number.isNaN(sizeKb)) return null;\n return sizeKb * 1024;\n } catch {\n return null;\n }\n}\n\nexport async function inspectBox(idOrName: string): Promise<InspectedBox> {\n const record = await resolveBox(idOrName);\n const state = await inspectContainerStatus(record.container);\n const upperMountpoint = await inspectVolumeMountpoint(record.upperVolume);\n const snapshotSizeBytes = record.snapshotDir ? await dirSizeBytes(record.snapshotDir) : null;\n const dockerJson = await inspectContainer(record.container);\n\n let overlayMounted = false;\n if (state === 'running' || state === 'paused') {\n const probe = await execa(\n 'docker',\n ['exec', '--user', 'root', record.container, 'mountpoint', '-q', '/workspace'],\n { reject: false },\n );\n overlayMounted = probe.exitCode === 0;\n }\n\n let claudeSession: ClaudeSessionInfo | null = null;\n if (state === 'running') {\n try {\n claudeSession = await claudeSessionInfo(record.container);\n } catch {\n claudeSession = null;\n }\n }\n\n const hostPaths = await getHostPaths(record);\n const engine = await detectEngine();\n const persistedStatus = await readBoxStatus(record.id);\n const endpoints = await getBoxEndpoints(record, engine, persistedStatus);\n\n return {\n record,\n state,\n upperVolume: { name: record.upperVolume, mountpoint: upperMountpoint },\n snapshotSizeBytes,\n overlayMounted,\n dockerInspect: dockerJson,\n claudeSession,\n persistedStatus,\n hostPaths,\n endpoints,\n };\n}\n\nexport interface DestroyOptions {\n keepSnapshot?: boolean;\n}\n\nexport interface DestroyResult {\n record: BoxRecord;\n removedContainer: boolean;\n removedVolumes: string[];\n removedSnapshot: string | null;\n}\n\nexport async function destroyBox(\n idOrName: string,\n opts: DestroyOptions = {},\n): Promise<DestroyResult> {\n const box = await resolveBox(idOrName);\n\n // Each step is best-effort. We collect what actually went away so the CLI\n // can show a truthful summary even if e.g. the container was gone already.\n if (box.relayToken) {\n try {\n await forgetBoxFromRelay(box.id);\n } catch {\n // best-effort — relay may be down or already wiped the entry\n }\n }\n // Remove the git worktrees on the host before nuking the container. The\n // worktree dirs live under the per-box run dir (which is wiped further\n // down), but we also need to deregister them from the main repo's\n // .git/worktrees/ so subsequent `git worktree list` on the host doesn't\n // see stale entries.\n for (const w of box.gitWorktrees ?? []) {\n try {\n await removeBoxWorktree({ hostMainRepo: w.hostMainRepo, worktreeDir: w.hostWorktreeDir });\n } catch {\n // best-effort\n }\n }\n const beforeContainer = await inspectContainerStatus(box.container);\n await removeContainer(box.container);\n const afterContainer = await inspectContainerStatus(box.container);\n const removedContainer = beforeContainer !== 'missing' && afterContainer === 'missing';\n\n const removedVolumes: string[] = [];\n // The dedicated agentbox-nm-<id> volume was removed (node_modules now lives\n // in the per-box overlay upper). Boxes created before that change still have\n // the volume on disk; removeVolume is a no-op for newer boxes that lack it.\n const legacyNodeModulesVolume = `agentbox-nm-${box.id}`;\n for (const v of [box.upperVolume, legacyNodeModulesVolume]) {\n await removeVolume(v);\n removedVolumes.push(v);\n }\n // Per-box claude config volumes are box-private — safe to remove. The shared\n // SHARED_CLAUDE_VOLUME holds user identity (auth, skills, plugins) across\n // every box, so never auto-remove it; users delete it manually if they want.\n if (box.claudeConfigVolume && box.claudeConfigVolume !== SHARED_CLAUDE_VOLUME) {\n await removeVolume(box.claudeConfigVolume);\n removedVolumes.push(box.claudeConfigVolume);\n }\n // Per-box `.vscode-server` and `.cursor-server` volumes. The shared\n // SHARED_*_EXTENSIONS_VOLUMEs are never auto-removed (parallel reasoning to\n // the shared claude volume). Volume names default-derived from `box.id` for\n // boxes created before these fields were recorded.\n const perBoxIdeVolumes = [\n box.vscodeServerVolume ?? vscodeServerVolumeName(box.id),\n box.cursorServerVolume ?? cursorServerVolumeName(box.id),\n ];\n for (const v of perBoxIdeVolumes) {\n await removeVolume(v);\n removedVolumes.push(v);\n }\n // Per-box dockerd data root. Skip when this box used the shared cache —\n // wiping it would also remove image layers other boxes (or future ones)\n // depend on. The shared volume is allowlisted in `pruneBoxes --all` too.\n if (box.dockerVolume && !box.dockerCacheShared) {\n await removeVolume(box.dockerVolume);\n removedVolumes.push(box.dockerVolume);\n }\n\n let removedSnapshot: string | null = null;\n if (box.snapshotDir && !opts.keepSnapshot) {\n try {\n await rm(box.snapshotDir, { recursive: true, force: true });\n removedSnapshot = box.snapshotDir;\n } catch {\n removedSnapshot = null;\n }\n }\n\n // The per-box runtime dir holds the ctl socket plus the workspace/upper\n // export dirs used by `agentbox open`. Wipe the whole thing so destroy\n // leaves no residue under ~/.agentbox/boxes/.\n try {\n await rm(boxRunDirFor(box.id), { recursive: true, force: true });\n } catch {\n // best-effort\n }\n\n await removeBoxRecord(box.id);\n\n return { record: box, removedContainer, removedVolumes, removedSnapshot };\n}\n\nexport interface PruneOptions {\n dryRun?: boolean;\n all?: boolean;\n}\n\nexport interface PruneResult {\n removedRecords: string[];\n removedContainers: string[];\n removedVolumes: string[];\n removedSnapshotDirs: string[];\n removedBoxDirs: string[];\n dryRun: boolean;\n}\n\nasync function listSnapshotDirs(): Promise<string[]> {\n try {\n const entries = await readdir(SNAPSHOTS_ROOT, { withFileTypes: true });\n return entries.filter((e) => e.isDirectory()).map((e) => join(SNAPSHOTS_ROOT, e.name));\n } catch {\n return [];\n }\n}\n\nasync function listBoxDirs(): Promise<string[]> {\n try {\n const entries = await readdir(BOXES_ROOT, { withFileTypes: true });\n return entries.filter((e) => e.isDirectory()).map((e) => join(BOXES_ROOT, e.name));\n } catch {\n return [];\n }\n}\n\nexport async function pruneBoxes(opts: PruneOptions = {}): Promise<PruneResult> {\n const dryRun = opts.dryRun ?? false;\n const all = opts.all ?? false;\n\n const { boxes } = await readState();\n\n // Step 1: missing-state records.\n const stateChecks = await Promise.all(\n boxes.map(async (b) => ({ box: b, status: await inspectContainerStatus(b.container) })),\n );\n const missingRecords = stateChecks.filter((c) => c.status === 'missing').map((c) => c.box);\n\n // Step 2 (only with --all): orphan docker containers / volumes / snapshot dirs / per-box dirs.\n let orphanContainers: string[] = [];\n let orphanVolumes: string[] = [];\n let orphanSnapshots: string[] = [];\n let orphanBoxDirs: string[] = [];\n\n if (all) {\n const liveContainers = await listAgentboxContainers();\n const liveVolumes = await listAgentboxVolumes();\n const liveSnapshotDirs = await listSnapshotDirs();\n const liveBoxDirs = await listBoxDirs();\n // The state we'd have AFTER step 1 runs: missing-state records gone.\n const survivingBoxes = boxes.filter((b) => !missingRecords.some((m) => m.id === b.id));\n const expectedContainers = new Set<string>([\n ...survivingBoxes.map((b) => b.container),\n // The relay no longer runs as a container (it's a host node process\n // now). Any agentbox-relay container is a leftover from a previous\n // version of agentbox; it will be collected as an orphan below.\n ]);\n const expectedVolumes = new Set<string>([\n // agentbox-nm-<id> reconstructed for back-compat: a surviving box\n // created before the nm volume was removed still mounts it, so it must\n // stay allowlisted. Inert for newer boxes (no such volume exists).\n ...survivingBoxes.flatMap((b) => [b.upperVolume, `agentbox-nm-${b.id}`]),\n ...survivingBoxes\n .map((b) => b.claudeConfigVolume)\n .filter((v): v is string => typeof v === 'string'),\n ...survivingBoxes\n .map((b) => b.vscodeServerVolume)\n .filter((v): v is string => typeof v === 'string'),\n ...survivingBoxes\n .map((b) => b.cursorServerVolume)\n .filter((v): v is string => typeof v === 'string'),\n ...survivingBoxes\n .map((b) => b.dockerVolume)\n .filter((v): v is string => typeof v === 'string'),\n // The shared claude-config volume holds user identity across every box;\n // never reap it via prune even if no surviving box currently references it.\n SHARED_CLAUDE_VOLUME,\n // Shared across boxes: downloaded IDE extensions. Same reasoning.\n SHARED_VSCODE_EXTENSIONS_VOLUME,\n SHARED_CURSOR_EXTENSIONS_VOLUME,\n // Shared in-box docker image cache — opt-in via `box.dockerCacheShared`,\n // never auto-removed (image layers may be reused by future boxes).\n SHARED_DOCKER_CACHE_VOLUME,\n ]);\n const expectedSnapshots = new Set(\n survivingBoxes\n .filter((b): b is BoxRecord & { snapshotDir: string } => b.snapshotDir !== null)\n .map((b) => b.snapshotDir),\n );\n const expectedBoxDirs = new Set(survivingBoxes.map((b) => boxRunDirFor(b.id)));\n orphanContainers = liveContainers.filter((c) => !expectedContainers.has(c));\n // Per-project checkpoint volumes (`agentbox-ckpt-<project-hash>`) are\n // durable project assets — they outlive every box that referenced them\n // (the whole point of a checkpoint). Same reasoning as SHARED_CLAUDE_VOLUME\n // but prefix-scoped since there is one per project.\n orphanVolumes = liveVolumes.filter(\n (v) => !expectedVolumes.has(v) && !v.startsWith(CHECKPOINT_VOLUME_PREFIX),\n );\n orphanSnapshots = liveSnapshotDirs.filter((d) => !expectedSnapshots.has(d));\n orphanBoxDirs = liveBoxDirs.filter((d) => !expectedBoxDirs.has(d));\n }\n\n if (dryRun) {\n return {\n removedRecords: missingRecords.map((b) => b.id),\n removedContainers: orphanContainers,\n removedVolumes: orphanVolumes,\n removedSnapshotDirs: orphanSnapshots,\n removedBoxDirs: orphanBoxDirs,\n dryRun: true,\n };\n }\n\n for (const b of missingRecords) await removeBoxRecord(b.id);\n for (const c of orphanContainers) await removeContainer(c);\n for (const v of orphanVolumes) await removeVolume(v);\n for (const d of orphanSnapshots) {\n try {\n await rm(d, { recursive: true, force: true });\n } catch {\n // best-effort\n }\n }\n for (const d of orphanBoxDirs) {\n try {\n await rm(d, { recursive: true, force: true });\n } catch {\n // best-effort\n }\n }\n\n // Migration sweep: the relay used to be a docker container on a dedicated\n // network with its own image. None of those exist after this version of\n // agentbox; drop any leftovers from previous installs. Idempotent and\n // best-effort — these calls succeed silently if the objects are already\n // gone.\n if (all) {\n try {\n await removeContainer(RELAY_CONTAINER_NAME);\n } catch {\n // best-effort\n }\n try {\n await execa('docker', ['image', 'rm', RELAY_IMAGE_REF], { reject: false });\n } catch {\n // best-effort\n }\n try {\n await removeNetwork(RELAY_NETWORK_NAME);\n } catch {\n // best-effort\n }\n }\n\n return {\n removedRecords: missingRecords.map((b) => b.id),\n removedContainers: orphanContainers,\n removedVolumes: orphanVolumes,\n removedSnapshotDirs: orphanSnapshots,\n removedBoxDirs: orphanBoxDirs,\n dryRun: false,\n };\n}\n\n// Help vitest / unit tests get to the snapshot-root constant without pulling\n// the whole snapshot module surface.\nexport { SNAPSHOTS_ROOT };\n\n// Re-export the file existence helper for inspect output; useful guard for\n// callers that want to know if a snapshot dir was ever created.\nexport async function snapshotPresent(path: string | null): Promise<boolean> {\n if (!path) return false;\n try {\n const s = await stat(path);\n return s.isDirectory();\n } catch {\n return false;\n }\n}\n","import { join } from 'node:path';\nimport { loadConfig } from '@agentbox/ctl';\nimport type { BoxStatus } from '@agentbox/ctl';\nimport type { BoxRecord } from './state.js';\nimport type { DockerEngine } from './host-export.js';\nimport { buildVncUrls, VNC_CONTAINER_PORT } from './vnc.js';\nimport { WEB_CONTAINER_PORT } from './web.js';\n\nexport interface BoxEndpoint {\n kind: 'vnc' | 'service' | 'web';\n /** Service name (kind === 'service'/'web') or 'vnc' (kind === 'vnc'). */\n name: string;\n /** In-container port (6080 for VNC, the `ready_when.port` value for services). */\n containerPort: number;\n /**\n * Host-side URL the user can open. Undefined when the port isn't reachable\n * from the host (service ports on Docker Desktop, since we don't auto-publish\n * them today).\n */\n url?: string;\n /** Whether the URL is reachable from the host on the current engine. */\n reachable: boolean;\n}\n\nexport interface BoxEndpoints {\n /** Bare hostname/IP for the box — `<container>.orb.local` on OrbStack, `127.0.0.1` otherwise. */\n domain: string;\n /** True when domain is the OrbStack auto-DNS (any in-container port works). */\n domainIsOrb: boolean;\n /** Ordered list of endpoints: VNC first (if enabled), then services in agentbox.yaml order. */\n endpoints: BoxEndpoint[];\n}\n\n/**\n * Build the box's user-facing network surface. Pure host-side: no docker exec,\n * no network — safe to call from `agentbox list` in a tight loop.\n *\n * Service ports come from the persisted status snapshot when available\n * (`~/.agentbox/boxes/<id>/status.json`, pushed by the in-box supervisor via\n * the relay). That snapshot resolves `ready_when.port` *inside the box*, so it\n * works even when `agentbox.yaml` lives only in the box and was never pulled to\n * the host. Falls back to parsing the host's `agentbox.yaml` for pre-relay\n * boxes (or ones that never pushed a snapshot).\n *\n * Missing config + no snapshot is non-fatal: the VNC entry (if any) is still\n * returned. Engine drives reachability — OrbStack auto-routes\n * `<container>.orb.local:<port>` for any in-box port; other engines see only\n * what we explicitly publish via `docker run -p`, which today is just VNC.\n */\nexport async function getBoxEndpoints(\n record: BoxRecord,\n engine: DockerEngine,\n persisted?: BoxStatus | null,\n): Promise<BoxEndpoints> {\n const domainIsOrb = engine === 'orbstack';\n const domain = domainIsOrb ? `${record.container}.orb.local` : '127.0.0.1';\n\n const endpoints: BoxEndpoint[] = [];\n\n if (record.vncEnabled && record.vncPassword) {\n const vncUrls = buildVncUrls(record, engine);\n const url = vncUrls.orbUrl ?? vncUrls.loopbackUrl;\n endpoints.push({\n kind: 'vnc',\n name: 'vnc',\n containerPort: VNC_CONTAINER_PORT,\n url,\n reachable: Boolean(url),\n });\n }\n\n // The single `expose:`-flagged service, from the snapshot first (works when\n // agentbox.yaml lives only in the box), else the host yaml.\n let webServiceName: string | null = null;\n const persistedWeb = persisted?.services.find((s) => s.expose);\n if (persistedWeb) {\n webServiceName = persistedWeb.name;\n }\n\n const pushService = (name: string, port: number): void => {\n // The web service is surfaced as the dedicated `web` endpoint below;\n // don't also list it as a generic service.\n if (name === webServiceName) return;\n endpoints.push({\n kind: 'service',\n name,\n containerPort: port,\n // Only OrbStack auto-routes arbitrary in-box ports; on other engines we\n // don't publish service ports, so the URL isn't host-reachable.\n ...(domainIsOrb\n ? { url: `http://${domain}:${String(port)}`, reachable: true }\n : { reachable: false }),\n });\n };\n\n const persistedServices = persisted?.services.filter(\n (s): s is typeof s & { port: number } => typeof s.port === 'number',\n );\n if (persistedServices && persistedServices.length > 0) {\n for (const svc of persistedServices) pushService(svc.name, svc.port);\n } else {\n try {\n const cfg = await loadConfig(join(record.workspacePath, 'agentbox.yaml'));\n if (!webServiceName) {\n webServiceName = cfg.services.find((s) => s.expose)?.name ?? null;\n }\n for (const svc of cfg.services) {\n if (svc.readyWhen?.kind !== 'port') continue;\n pushService(svc.name, svc.readyWhen.port);\n }\n } catch {\n // No persisted snapshot and no host agentbox.yaml — skip service\n // endpoints. The VNC entry, if any, is unaffected.\n }\n }\n\n // Web endpoint: only for boxes that reserved container :80 at create. The\n // URL is the published loopback host port — uniform across engines, NOT\n // gated on OrbStack (requirement: don't rely on orb auto-DNS). No url until\n // both a service declares `expose:` and the host port is resolved; until\n // then it renders as \"reserved\".\n if (record.webContainerPort !== undefined) {\n const hasTarget = webServiceName !== null && record.webHostPort !== undefined;\n endpoints.push({\n kind: 'web',\n name: webServiceName ?? 'web',\n containerPort: record.webContainerPort ?? WEB_CONTAINER_PORT,\n ...(hasTarget\n ? { url: `http://127.0.0.1:${String(record.webHostPort)}`, reachable: true }\n : { reachable: false }),\n });\n }\n\n return { domain, domainIsOrb, endpoints };\n}\n"],"mappings":"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","names":["join","join"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../packages/sandbox-docker/src/state.ts"],"sourcesContent":["import { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { dirname, join } from 'node:path';\n\nexport const STATE_DIR = join(homedir(), '.agentbox');\nexport const STATE_FILE = join(STATE_DIR, 'state.json');\n\nexport interface BoxRecord {\n id: string;\n name: string;\n container: string;\n image: string;\n workspacePath: string;\n lowerPath: string;\n upperVolume: string;\n snapshotDir: string | null;\n /**\n * Host-side path to the agentbox-ctl unix socket bind-mounted into the\n * container at /run/agentbox/ctl.sock. Absent for boxes created before this\n * field existed (treated as \"ctl not available\").\n */\n socketPath?: string;\n /**\n * Docker volume mounted at /home/vscode/.claude inside the box. The default\n * shared volume (`agentbox-claude-config`) is reused across boxes; isolated\n * boxes get a per-box volume suffixed with the box id. Absent for boxes\n * created before this field existed.\n */\n claudeConfigVolume?: string;\n /**\n * Per-box volume holding `.vscode-server` (server binary + TS cache).\n * The shared `agentbox-vscode-extensions` volume layers over the `extensions`\n * subdir at run time and isn't recorded here (never auto-removed). Absent\n * for boxes created before this field existed.\n */\n vscodeServerVolume?: string;\n /**\n * Per-box volume holding `.cursor-server` (Cursor server binary + state).\n * Parallel to `vscodeServerVolume`. Absent for boxes created before this\n * field existed — lifecycle code falls back to deriving from `id`.\n */\n cursorServerVolume?: string;\n /**\n * Bearer token the in-box supervisor uses to authenticate with the host\n * relay. Generated at create time and forwarded as AGENTBOX_RELAY_TOKEN.\n * Absent for boxes created before the relay existed — those boxes simply\n * skip outbound push.\n */\n relayToken?: string;\n /**\n * Git worktrees mounted into the box. Empty/absent when the host workspace\n * is not a git checkout. The root entry (kind: 'root') replaces the box's\n * overlay lower; nested entries (kind: 'nested', from monorepo 1st-level\n * `.git` dirs) are bind-mounted at /workspace/<relPathFromWorkspace> after\n * the FUSE overlay is mounted.\n */\n gitWorktrees?: GitWorktreeRecord[];\n /**\n * True when the box was created with --with-playwright. The install happens\n * once at create time (npm install -g @playwright/cli@latest inside the\n * container); we record the choice for `agentbox inspect` visibility. Absent\n * on boxes created before this field existed (treated as false).\n */\n withPlaywright?: boolean;\n /**\n * True when the box was created with --with-env. The host's env/config files\n * (DEFAULT_ENV_PATTERNS) were copied into /workspace once at create time,\n * bypassing gitignore; recorded for `agentbox inspect` visibility. Absent on\n * boxes created before this field existed (treated as false).\n */\n withEnv?: boolean;\n /**\n * VNC stack (Xvnc + websockify + noVNC) is enabled for this box. Absent on\n * boxes created before VNC support landed → treated as disabled.\n */\n vncEnabled?: boolean;\n /** Container-side noVNC web port. Fixed to 6080 today; here for future-proofing. */\n vncContainerPort?: number;\n /** Random host port Docker assigned to the noVNC web server (resolved via `docker port`). */\n vncHostPort?: number;\n /** Per-box password baked into Xvnc's PasswordFile and embedded in the auto-connect URL. */\n vncPassword?: string;\n /**\n * Container port reserved for the web service `expose:` forward. Fixed to 80\n * today; the `-p` mapping is created unconditionally at `create`. Absent on\n * boxes created before web-port reservation landed → no web endpoint until\n * the box is recreated.\n */\n webContainerPort?: number;\n /** Random host port Docker assigned to container :80 (resolved via `docker port`). */\n webHostPort?: number;\n /**\n * Volume mounted at /var/lib/docker for the in-box dockerd. Per-box\n * (`agentbox-docker-<id>`) by default; the shared `agentbox-docker-cache`\n * volume when `dockerCacheShared` is true. Absent on boxes created before\n * DinD landed — those boxes have no in-box dockerd at all.\n */\n dockerVolume?: string;\n /**\n * True when this box's `dockerVolume` is the shared cache. Tells `destroyBox`\n * to skip removal (the shared volume holds image layers other boxes may\n * reuse) and `pruneBoxes --all` to allowlist it.\n */\n dockerCacheShared?: boolean;\n /**\n * Absolute host path of the project this box belongs to. Set by `createBox`\n * from the CLI-supplied `findProjectRoot(workspacePath)` (nearest ancestor\n * dir holding `agentbox.yaml`, else workspacePath itself). Used by\n * `resolveBoxRef` + `autoPickProjectBox` to scope numeric refs and auto-pick\n * to the cwd's project. Absent on boxes created before this field existed —\n * those boxes are never auto-picked or matched by numeric index.\n */\n projectRoot?: string;\n /**\n * Monotonic 1-based index within `projectRoot`. Allocated once at create via\n * `allocateProjectIndex` and never recycled — destroying box #2 leaves a gap\n * (next new box is #3, not #2). Lets `agentbox open 3` mean the same box for\n * that box's whole lifetime.\n */\n projectIndex?: number;\n /**\n * Ordered in-container lower directories (upper-most first) the FUSE overlay\n * was mounted with. Persisted so `startBox` re-mounts identically after a\n * stop/start. Absent on non-checkpoint boxes → `mountOverlay` defaults to\n * the single base bind `['/host-src']` (byte-identical to legacy boxes).\n */\n lowerDirs?: string[];\n /**\n * The per-project checkpoint Docker volume mounted read-only at\n * `/agentbox-checkpoints` when this box was created from a checkpoint (the\n * `lowerDirs` reference subdirs of that mount). Absent on non-checkpoint\n * boxes. `startBox` revalidates this volume still exists (Docker bakes the\n * mount at create time) before re-mounting.\n */\n checkpointVolume?: string;\n /**\n * Lineage of the checkpoint this box was created from. Drives chain-depth\n * (auto-merge threshold) and `agentbox inspect`. Absent when the box was not\n * created from a checkpoint.\n */\n checkpointSource?: {\n ref: string;\n type: 'layered' | 'merged';\n /** Checkpoint refs composing the chain, base-most last. */\n chain: string[];\n };\n /**\n * Resource ceilings actually applied at `docker run` (bytes/fractional/count;\n * the `disk` string only present when the engine's storage driver enforces\n * it — dropped + warned on overlay2/macOS). Absent on legacy boxes → treated\n * as unlimited. Surfaced by `agentbox inspect` and cross-checked by\n * `boxResourceStats`.\n */\n resourceLimits?: {\n memoryBytes?: number;\n cpus?: number;\n pidsLimit?: number;\n disk?: string;\n };\n createdAt: string; // ISO-8601\n}\n\nexport interface GitWorktreeRecord {\n kind: 'root' | 'nested';\n /** Host path to the main repo whose `.git/` is bind-mounted RW at the identical path inside the container. */\n hostMainRepo: string;\n /** Host path to the per-box worktree directory (under ~/.agentbox/boxes/<id>/worktrees/). */\n hostWorktreeDir: string;\n /** Container path that resolves to the worktree's working tree. /workspace for root, /workspace/<subpath> for nested. */\n containerPath: string;\n /** Branch the worktree was created on, e.g. `agentbox/<box-name>`. */\n branch: string;\n /** Workspace-relative path the repo was found at (empty string for root). */\n relPathFromWorkspace: string;\n}\n\nexport interface StateFile {\n version: 1;\n boxes: BoxRecord[];\n}\n\nconst EMPTY: StateFile = { version: 1, boxes: [] };\n\nexport async function readState(path: string = STATE_FILE): Promise<StateFile> {\n try {\n const raw = await readFile(path, 'utf8');\n const parsed = JSON.parse(raw) as StateFile;\n if (parsed.version !== 1 || !Array.isArray(parsed.boxes)) {\n throw new Error(`unrecognized state file shape at ${path}`);\n }\n return parsed;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n return { ...EMPTY };\n }\n throw err;\n }\n}\n\nexport async function writeState(state: StateFile, path: string = STATE_FILE): Promise<void> {\n await mkdir(dirname(path), { recursive: true });\n await writeFile(path, JSON.stringify(state, null, 2) + '\\n', 'utf8');\n}\n\nexport async function recordBox(box: BoxRecord, path: string = STATE_FILE): Promise<void> {\n const state = await readState(path);\n const next: StateFile = {\n version: 1,\n boxes: [...state.boxes.filter((b) => b.id !== box.id), box],\n };\n await writeState(next, path);\n}\n\nexport async function removeBoxRecord(id: string, path: string = STATE_FILE): Promise<boolean> {\n const state = await readState(path);\n const before = state.boxes.length;\n const next: StateFile = {\n version: 1,\n boxes: state.boxes.filter((b) => b.id !== id),\n };\n if (next.boxes.length === before) return false;\n await writeState(next, path);\n return true;\n}\n\nexport type FindBoxResult =\n | { kind: 'ok'; box: BoxRecord }\n | { kind: 'none' }\n | { kind: 'ambiguous'; matches: BoxRecord[] };\n\n/**\n * Resolve a user-supplied identifier against the state file. Matching\n * precedence mirrors `docker`'s container reference resolution:\n *\n * 1. exact id\n * 2. unique id prefix\n * 3. exact name\n * 4. exact container name\n *\n * Returns `'ambiguous'` if step 2 finds more than one match (steps 1, 3, 4\n * are exact-match so they cannot be ambiguous on their own).\n */\nexport function findBox(idOrName: string, state: StateFile): FindBoxResult {\n const q = idOrName.trim();\n if (q.length === 0) return { kind: 'none' };\n\n const exactId = state.boxes.find((b) => b.id === q);\n if (exactId) return { kind: 'ok', box: exactId };\n\n const prefixMatches = state.boxes.filter((b) => b.id.startsWith(q));\n if (prefixMatches.length === 1) return { kind: 'ok', box: prefixMatches[0]! };\n if (prefixMatches.length > 1) return { kind: 'ambiguous', matches: prefixMatches };\n\n const byName = state.boxes.find((b) => b.name === q);\n if (byName) return { kind: 'ok', box: byName };\n\n const byContainer = state.boxes.find((b) => b.container === q);\n if (byContainer) return { kind: 'ok', box: byContainer };\n\n return { kind: 'none' };\n}\n\n/**\n * Next monotonic 1-based index for the given project. Reads only `state.boxes`\n * — caller is responsible for persisting the assignment. Boxes without\n * `projectRoot` are ignored (legacy records); boxes in *other* projects are\n * also ignored. Indices are never recycled, so a destroyed #2 leaves a gap.\n */\nexport function allocateProjectIndex(state: StateFile, projectRoot: string): number {\n let max = 0;\n for (const b of state.boxes) {\n if (b.projectRoot !== projectRoot) continue;\n if (typeof b.projectIndex === 'number' && b.projectIndex > max) {\n max = b.projectIndex;\n }\n }\n return max + 1;\n}\n\n/**\n * Auto-pick when a command's `[box]` argument is omitted. Returns the unique\n * box for `projectRoot`, an `ambiguous` carrying all candidates so the CLI can\n * print a chooser, or `none`.\n */\nexport function autoPickProjectBox(state: StateFile, projectRoot: string): FindBoxResult {\n const matches = state.boxes.filter((b) => b.projectRoot === projectRoot);\n if (matches.length === 0) return { kind: 'none' };\n if (matches.length === 1) return { kind: 'ok', box: matches[0]! };\n return { kind: 'ambiguous', matches };\n}\n\n/**\n * Top-level resolver every CLI command goes through. Combines numeric-index\n * lookup with the legacy `findBox` matcher:\n *\n * - `ref === undefined` and `projectRoot` known → autoPickProjectBox.\n * - `ref` is a pure positive integer and `projectRoot` known → resolve as\n * project index. **Never** falls through to `findBox` on miss, so\n * `agentbox open 3` is reserved for the index and won't accidentally\n * match a hex id like `3abc…`.\n * - Otherwise → `findBox` (id → prefix → name → container).\n */\nexport function resolveBoxRef(\n ref: string | undefined,\n state: StateFile,\n projectRoot: string | undefined,\n): FindBoxResult {\n if (ref === undefined) {\n if (projectRoot === undefined) return { kind: 'none' };\n return autoPickProjectBox(state, projectRoot);\n }\n const trimmed = ref.trim();\n if (projectRoot !== undefined && /^[1-9][0-9]*$/.test(trimmed)) {\n const idx = Number.parseInt(trimmed, 10);\n const hit = state.boxes.find(\n (b) => b.projectRoot === projectRoot && b.projectIndex === idx,\n );\n return hit ? { kind: 'ok', box: hit } : { kind: 'none' };\n }\n return findBox(trimmed, state);\n}\n"],"mappings":";;;AAAA,SAAS,OAAO,UAAU,iBAAiB;AAC3C,SAAS,eAAe;AACxB,SAAS,SAAS,YAAY;AAEvB,IAAM,YAAY,KAAK,QAAQ,GAAG,WAAW;AAC7C,IAAM,aAAa,KAAK,WAAW,YAAY;AAgLtD,IAAM,QAAmB,EAAE,SAAS,GAAG,OAAO,CAAC,EAAE;AAEjD,eAAsB,UAAU,OAAe,YAAgC;AAC7E,MAAI;AACF,UAAM,MAAM,MAAM,SAAS,MAAM,MAAM;AACvC,UAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,QAAI,OAAO,YAAY,KAAK,CAAC,MAAM,QAAQ,OAAO,KAAK,GAAG;AACxD,YAAM,IAAI,MAAM,oCAAoC,IAAI,EAAE;IAC5D;AACA,WAAO;EACT,SAAS,KAAK;AACZ,QAAK,IAA8B,SAAS,UAAU;AACpD,aAAO,EAAE,GAAG,MAAM;IACpB;AACA,UAAM;EACR;AACF;AAEA,eAAsB,WAAW,OAAkB,OAAe,YAA2B;AAC3F,QAAM,MAAM,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAC9C,QAAM,UAAU,MAAM,KAAK,UAAU,OAAO,MAAM,CAAC,IAAI,MAAM,MAAM;AACrE;AAEA,eAAsB,UAAU,KAAgB,OAAe,YAA2B;AACxF,QAAM,QAAQ,MAAM,UAAU,IAAI;AAClC,QAAM,OAAkB;IACtB,SAAS;IACT,OAAO,CAAC,GAAG,MAAM,MAAM,OAAO,CAAC,MAAM,EAAE,OAAO,IAAI,EAAE,GAAG,GAAG;EAC5D;AACA,QAAM,WAAW,MAAM,IAAI;AAC7B;AAEA,eAAsB,gBAAgB,IAAY,OAAe,YAA8B;AAC7F,QAAM,QAAQ,MAAM,UAAU,IAAI;AAClC,QAAM,SAAS,MAAM,MAAM;AAC3B,QAAM,OAAkB;IACtB,SAAS;IACT,OAAO,MAAM,MAAM,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE;EAC9C;AACA,MAAI,KAAK,MAAM,WAAW,OAAQ,QAAO;AACzC,QAAM,WAAW,MAAM,IAAI;AAC3B,SAAO;AACT;AAmBO,SAAS,QAAQ,UAAkB,OAAiC;AACzE,QAAM,IAAI,SAAS,KAAK;AACxB,MAAI,EAAE,WAAW,EAAG,QAAO,EAAE,MAAM,OAAO;AAE1C,QAAM,UAAU,MAAM,MAAM,KAAK,CAAC,MAAM,EAAE,OAAO,CAAC;AAClD,MAAI,QAAS,QAAO,EAAE,MAAM,MAAM,KAAK,QAAQ;AAE/C,QAAM,gBAAgB,MAAM,MAAM,OAAO,CAAC,MAAM,EAAE,GAAG,WAAW,CAAC,CAAC;AAClE,MAAI,cAAc,WAAW,EAAG,QAAO,EAAE,MAAM,MAAM,KAAK,cAAc,CAAC,EAAG;AAC5E,MAAI,cAAc,SAAS,EAAG,QAAO,EAAE,MAAM,aAAa,SAAS,cAAc;AAEjF,QAAM,SAAS,MAAM,MAAM,KAAK,CAAC,MAAM,EAAE,SAAS,CAAC;AACnD,MAAI,OAAQ,QAAO,EAAE,MAAM,MAAM,KAAK,OAAO;AAE7C,QAAM,cAAc,MAAM,MAAM,KAAK,CAAC,MAAM,EAAE,cAAc,CAAC;AAC7D,MAAI,YAAa,QAAO,EAAE,MAAM,MAAM,KAAK,YAAY;AAEvD,SAAO,EAAE,MAAM,OAAO;AACxB;AAQO,SAAS,qBAAqB,OAAkB,aAA6B;AAClF,MAAI,MAAM;AACV,aAAW,KAAK,MAAM,OAAO;AAC3B,QAAI,EAAE,gBAAgB,YAAa;AACnC,QAAI,OAAO,EAAE,iBAAiB,YAAY,EAAE,eAAe,KAAK;AAC9D,YAAM,EAAE;IACV;EACF;AACA,SAAO,MAAM;AACf;AAOO,SAAS,mBAAmB,OAAkB,aAAoC;AACvF,QAAM,UAAU,MAAM,MAAM,OAAO,CAAC,MAAM,EAAE,gBAAgB,WAAW;AACvE,MAAI,QAAQ,WAAW,EAAG,QAAO,EAAE,MAAM,OAAO;AAChD,MAAI,QAAQ,WAAW,EAAG,QAAO,EAAE,MAAM,MAAM,KAAK,QAAQ,CAAC,EAAG;AAChE,SAAO,EAAE,MAAM,aAAa,QAAQ;AACtC;AAaO,SAAS,cACd,KACA,OACA,aACe;AACf,MAAI,QAAQ,QAAW;AACrB,QAAI,gBAAgB,OAAW,QAAO,EAAE,MAAM,OAAO;AACrD,WAAO,mBAAmB,OAAO,WAAW;EAC9C;AACA,QAAM,UAAU,IAAI,KAAK;AACzB,MAAI,gBAAgB,UAAa,gBAAgB,KAAK,OAAO,GAAG;AAC9D,UAAM,MAAM,OAAO,SAAS,SAAS,EAAE;AACvC,UAAM,MAAM,MAAM,MAAM;MACtB,CAAC,MAAM,EAAE,gBAAgB,eAAe,EAAE,iBAAiB;IAC7D;AACA,WAAO,MAAM,EAAE,MAAM,MAAM,KAAK,IAAI,IAAI,EAAE,MAAM,OAAO;EACzD;AACA,SAAO,QAAQ,SAAS,KAAK;AAC/B;","names":[]}
|
|
@@ -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_VOLUME_PREFIX, checkpointVolumeName } from './checkpoint.js';\nimport {\n inspectContainer,\n inspectContainerStatus,\n inspectVolumeMountpoint,\n listAgentboxVolumes,\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/** Size of the per-project shared checkpoint volume, or null when absent. */\nexport async function projectCheckpointVolumeBytes(\n projectRoot: string,\n): Promise<number | null> {\n return volumeSizeBytes(checkpointVolumeName(projectRoot));\n}\n\n/**\n * Total on-host bytes of every per-project checkpoint volume (the durable,\n * cross-box warm-state assets). Null when none exist or no size is reachable\n * from the host.\n */\nexport async function allCheckpointVolumesBytes(): Promise<number | null> {\n const vols = (await listAgentboxVolumes()).filter((v) =>\n v.startsWith(CHECKPOINT_VOLUME_PREFIX),\n );\n if (vols.length === 0) return null;\n const sizes = await Promise.all(vols.map((v) => volumeSizeBytes(v)));\n const known = sizes.filter((s): s is number => s !== null);\n return known.length === 0 ? null : known.reduce((a, b) => a + b, 0);\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 * 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 per-box writable surface (upper + docker data-root\n * volumes); the per-box host snapshot dir and the SHARED per-project\n * checkpoint volume are reported on their own fields and never summed into\n * `diskUsedBytes` (would double-count across a project's boxes).\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 [diskUpper, diskDocker, snapshotDiskBytes, checkpointVolumeBytes] = await Promise.all([\n volumeSizeBytes(record.upperVolume),\n record.dockerVolume ? volumeSizeBytes(record.dockerVolume) : Promise.resolve(null),\n record.snapshotDir ? duBytes(record.snapshotDir) : Promise.resolve(null),\n record.checkpointVolume\n ? volumeSizeBytes(record.checkpointVolume)\n : Promise.resolve(null),\n ]);\n const diskUsedBytes =\n diskUpper === null && diskDocker === null ? null : (diskUpper ?? 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,\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":[]}
|