Commit graph

287 commits

Author SHA1 Message Date
agent-vm
6e593379e8 agent-vm: pull progress now mirrors msb pull (per-layer bars, no ETA)
Rewrite crates/agent-vm/src/pull_progress.rs to render one progress bar
per layer instead of a two-phase aggregate, matching the upstream
PullProgressDisplay in vendor/microsandbox/crates/cli/lib/ui.rs.

While porting, fix seven defects flagged by code review:

  - Per-layer cache-EROFS fast path (registry.rs:701, :753) emits
    LayerMaterializeComplete with no prior Started — bar previously
    stayed magenta "downloading" then jumped straight to ✓. Introduce
    `materialize_styled: Vec<bool>` + ensure_materialize_style() and
    call it from all four materialize-phase arms so the bar always
    transitions through the correct style.

  - Bounded try_send progress channel can drop LayerMaterializeStarted
    under load; the same helper restyles on the next surviving
    materialize event rather than leaving the bar in download style.

  - `style("✓").green()` defaulted to for_stderr=false so the checkmark
    was rendered against stdout's TTY state. Add .for_stderr().

  - `render.await.ok()` silently swallowed JoinError. Add a small
    `await_render` helper that logs panics, and refactor pull.rs /
    setup.rs / run.rs to always await render before propagating pull
    errors so display.finish() runs mp.clear() to restore the terminal.

  - header.enable_steady_tick() fired even when the multi-progress
    draw target was hidden (non-TTY). Guard with `if is_tty`.

  - BRAILLE_TICKS had 11 entries; index 10 duplicated index 0,
    producing a one-frame stutter every full spinner rotation. Drop the
    trailing entry.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 22:32:35 +00:00
agent-vm
25d75e7181 ci(build-image): build rewrite-microsandbox on scheduled runs
Active development happens on `rewrite-microsandbox`; `main` lags
behind. The hourly schedule fires only from the default branch
(GitHub Actions hard constraint), so until now the cron was
checking out `main`, building its stale Dockerfile, and pushing
that as `:latest` — clobbering the zstd-compressed image
published from rewrite-microsandbox.

Two changes here:

1. The workflow file itself is brought in line with rewrite-
   microsandbox's version: zstd outputs, moving-tag gating
   (allowing both main and rewrite-microsandbox), and the
   `agent-vm-template` package name in the retain script.

2. On scheduled runs, `actions/checkout` is pointed at
   `rewrite-microsandbox` explicitly. workflow_dispatch and push
   events still build whatever ref triggered them. A new
   `Resolve build SHA` step captures the actually-checked-out
   commit so the `:sha-…` tag and the
   `org.opencontainers.image.revision` label reflect what was
   built, not main's stale tip.

Net effect: `:latest` stops oscillating between gzip (from main)
and zstd (from rewrite-microsandbox).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 22:00:54 +00:00
agent-vm
01f7b4b3bd ci(build-image): fix retain job — correct GHCR package name
The retain script queries package `agent-vm`, but the GHCR package
was renamed to `agent-vm-template` in ca6a2a3. The script has been
returning HttpError 404 ("Package not found") on every build since,
so 14-day pruning has not actually run.

Switch to the real name so the cleanup actually fires.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 20:14:08 +00:00
agent-vm
1f9d9ce1cd v0.1.7: bump for zstd-compressed OCI layers 2026-05-25 20:04:15 +00:00
agent-vm
8cc5dbaf13 Merge branch 'images-zstd-layers': zstd-compress OCI layers
Cuts the OCI→EROFS conversion on `agent-vm setup` by ~24× on
binary-heavy content (benched at 856 MB of /usr/lib: 143s → 5s).

- `images/build.sh`: docker build → buildx with
  `--output type=registry,compression=zstd,compression-level=3,
  force-compression=true`
- `.github/workflows/build-image.yml`: same flags via the
  `outputs:` input of docker/build-push-action@v7, and gate the
  moving `:latest` + `:YYYY-MM-DDTHH` tags to the integration
  branches (`main`, `rewrite-microsandbox`) so feature-branch
  workflow_dispatch can verify safely.

No microsandbox-side changes — `tar_ingest.rs:427` already
accepts the `application/vnd.oci.image.layer.v1.tar+zstd`
media type.

Verified end-to-end on the branch: build-image run #26417234380
pushed ghcr.io/.../agent-vm-template:sha-ce6b9ca with all 12
layers tagged `application/vnd.oci.image.layer.v1.tar+zstd`
(568 MB compressed).
2026-05-25 20:03:33 +00:00
agent-vm
484f967404 ci(build-image): also allow rewrite-microsandbox for moving tags
The previous commit gated `:latest` and `:YYYY-MM-DDTHH` to `main`
only, but `rewrite-microsandbox` is the active integration branch
for the microsandbox-rewrite work (release-npm.yml:96 documents
that release dispatches happen from there). Restricting moving
tags to `main` alone means a dispatch on rewrite-microsandbox
produces only `:sha-…` — fine for ad-hoc verification, wrong for
the canonical image build that the binary release pins to.

Allow both branches.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 20:02:34 +00:00
agent-vm
db55962bea Merge branch 'nested-virt': ship libkrunfw with CONFIG_KVM=y
Adds out-of-the-box nested KVM support (Docker-in-Docker, qemu inside
the guest) and reroutes msb's writable state off ~/.microsandbox/ into
$XDG_STATE_HOME/agent-vm/msb-home/. See 6b2fdea for the full commit
message.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 19:49:23 +00:00
agent-vm
ce6b9cacca ci(build-image): zstd-compress layers + gate moving tags to main
Mirror the gzip→zstd compression change from images/build.sh into
the GitHub Actions image build, so the published
ghcr.io/.../agent-vm-template image carries zstd layers that
microsandbox can ingest ~24× faster on a cold `agent-vm pull`.

`docker/build-push-action@v7` doesn't expose compression flags
directly, but it forwards `outputs:` straight to buildx. So:

  outputs: type=registry,push=true,compression=zstd,
           compression-level=3,force-compression=true

replaces the previous `push: true`. `force-compression=true` is
the important bit — without it, only the layers we add get
zstd-compressed and the base-image layers stay gzip.

Also gate `:latest` and `:YYYY-MM-DDTHH` (the moving tags) to
pushes on `main`. The immutable `:sha-<sha>` is still pushed
unconditionally. This lets `workflow_dispatch` on a feature
branch produce a verifiable image (inspect the `:sha-…` manifest)
without overwriting whatever's currently on `:latest`.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 19:49:10 +00:00
agent-vm
6b2fdea71a feat: ship libkrunfw with CONFIG_KVM=y; reroute msb state off ~/.microsandbox
Two coupled changes to make nested KVM (Docker-in-Docker, qemu, etc.)
work inside the agent-vm guest without manual setup:

1. CI rebuilds libkrunfw with CONFIG_KVM=y instead of pulling the
   upstream release. Upstream's config-libkrunfw_x86_64 has
   `# CONFIG_KVM is not set` — the guest kernel ships only the
   paravirt-guest helpers and /dev/kvm never appears. We clone
   libkrunfw at LIBKRUNFW_VERSION, apply
   libkrunfw-overrides/config-libkrunfw_x86_64.patch (enable KVM,
   KVM_INTEL, KVM_AMD; olddefconfig pulls in the rest), build, and
   bundle the resulting .so into the per-platform npm subpackage.
   Result cached on (version, arch, patch-hash, workflow-hash) so the
   +5min build only runs on a real change. CI sanity-checks
   `nm vmlinux | grep kvm_dev_ioctl` so a silent patch failure becomes
   a CI error instead of a runtime mystery.

2. agent-vm pins MSB_HOME to its own state dir at startup so msb's
   writable state (db, cache, sandboxes, logs, tls/CA) lives under
   $XDG_STATE_HOME/agent-vm/msb-home/ instead of ~/.microsandbox/.
   The bundled `msb` + `libkrunfw` are read-only and discovered via
   MSB_PATH + msb's existing "sibling-of-msb / ../lib/" resolution,
   so no copy or sync into MSB_HOME is needed. Side-effect: a user's
   stale upstream libkrunfw.so.X.Y.Z at ~/.microsandbox/lib/ can
   never shadow ours.

Drops the `ensure_runtime_installed` / `microsandbox::setup::install`
download path entirely — the npm bundle is the source of truth.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 19:49:00 +00:00
agent-vm
b9495a18bd agent-vm/images: switch image build to zstd-compressed layers
`agent-vm setup` calls `images/build.sh`, which built and pushed via
the legacy `docker build` + `docker push` path — leaving every layer
gzip-compressed. The OCI→EROFS conversion on the user's first launch
then spends ~95% of its CPU time in single-threaded gzip decode.

Bench against /usr/lib (856 MB) — see crates/agent-vm/examples/
erofs_bench.rs:

  gzip layers:  ingest = 143.08s  (6.0 MB/s effective uncompressed)
  zstd layers:  ingest =   5.01s  (171.0 MB/s effective uncompressed)

≈24× faster end-to-end pull pipeline with no microsandbox changes —
`tar_ingest.rs:427` already accepts `application/vnd.oci.image.layer
.v1.tar+zstd` automatically.

Switch the script to `docker buildx build --output type=registry,
compression=zstd,compression-level=3,force-compression=true,push=
true`:

- `--output type=registry,push=true` replaces the build + separate
  push, because buildx with `--push` cannot also carry `--output`
  attributes, but `type=registry,push=true` is equivalent and lets
  us attach the compression options to the same step.
- `force-compression=true` re-emits base-image layers as zstd too;
  without it, only the layers we add get re-compressed and the rest
  stay gzip, halving the win.
- `compression-level=3` is zstd's default — bench shows diminishing
  returns past that for binary-heavy layers.
- `registry.insecure=true` is needed because we push to a loopback
  HTTP registry; `--push`-via-buildx doesn't inherit the daemon's
  `insecure-registries` config.

Adds preflight checks: bail with a clear message if `docker` or
`docker buildx` aren't present (modern Docker bundles buildx; older
installs need an upgrade).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 19:34:44 +00:00
Evgeny Boger
2cb978809b v0.1.6: bump for agent-vm shell <cmd> fix
82d97fc fixed shell-passthrough so `agent-vm shell ls /tmp` runs
`bash -c 'ls /tmp'` instead of bash trying to exec ls as a script
(exit 126: "cannot execute binary file"). Bump for release.
2026-05-25 18:46:16 +03:00
agent-vm
82d97fc4c3 agent-vm: run shell trailing args via bash -c
Before: `agent-vm shell ls` invoked `bash -O histappend ls`. bash
treats the first non-option positional as a script filename and
PATH-searches for it, lands on `/usr/bin/ls`, hits the ELF magic,
and exits 126 with `/usr/bin/ls: cannot execute binary file`.

Now: when `Agent::Shell` has any trailing args, shell-escape and
join them into a single `-c` command. `agent-vm shell ls -la /tmp`
becomes `bash -O histappend -c "'ls' '-la' '/tmp'"`. No-arg
`agent-vm shell` keeps its interactive-bash path. Other agents
(claude/codex/opencode) are unchanged.

Cargo.lock: workspace agent-vm version bumped 0.1.4 -> 0.1.5 to
match Cargo.toml (stale lockfile entry refreshed on first build).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 14:08:26 +00:00
Evgeny Boger
3163a5961a Revert "ci(release-npm): restore source mtimes post-checkout so cargo cache works"
This reverts commit 90376ec.

git-restore-mtime ran cleanly but cargo still recompiled every
path-dep workspace crate on the next warm run. rust-cache's own
README documents the limitation: cargo's fingerprint check for
path-dep crates forces a rebuild regardless of mtime — likely
dep-info absolute-path canonicalization or build-script reruns.

The remaining wins (parallel jobs, relaxed LTO on msb,
Swatinem/rust-cache with workspace crates + save-if=true) still
get us 11m31s -> ~3m wall-clock cold and don't get hurt by warm
also being ~3m.
2026-05-25 16:47:44 +03:00
Evgeny Boger
cc506b7fff agent-vm: allow concurrent launches in the same project dir
Before: `ProjectSession::sandbox_name` was just `agent-vm-<hash>` and
`Sandbox::builder(...)` was called with `.replace()`. Launching a
second `agent-vm` in the same dir would SIGTERM/SIGKILL the first
one's VMM (the second create's "replace" path) — the first attach
saw "agent stream closed (VMM/agentd disconnected)" and died.

Now:

- session.rs: sandbox name carries the launcher PID
  (`agent-vm-<hash>-<pid>`) so two concurrent invocations boot
  independent VMs. State dir is still hash-keyed, so per-project
  bind-mounted state (claude/, codex/, opencode/, bash_history) is
  still shared across launches.

- run.rs: drop `.replace()` on the builder (no name collision left to
  override), and add `reap_stale_project_sandboxes` which scans
  `~/.microsandbox/sandboxes/agent-vm-<hash>-*` before each launch
  and `Sandbox::remove`s any entry whose PID-suffix is no longer in
  /proc/. That replaces the implicit GC the old deterministic name
  used to get from `.replace()`. It also pre-empts the new failure
  mode where a stale entry happens to match our own PID (PID reuse)
  and `Sandbox::create` would otherwise hard-error with "already
  exists".

- secrets.rs: `secrets::refresh` is now serialized across concurrent
  launchers in the same project via an exclusive flock on
  `<state_dir>/.refresh.lock` (RAII guard). Several files under
  state_dir (claude.json, claude/settings.json,
  opencode-config/opencode.json) are read-modify-write — without the
  lock, two simultaneous launches in the same project would both
  read the same baseline and the later write would silently clobber
  the earlier launch's mutation (lost project-trust flag, lost
  onboarding state, etc.).

- run.rs: `Agent::Shell::default_args` now carries `-O histappend`
  so two concurrent interactive shells *append* (not overwrite) the
  shared bind-mounted `~/.bash_history` on exit.

Adds three unit tests: sandbox-name format encodes PID; refresh
lock blocks a second LOCK_EX while the first guard is alive;
existing session/secrets suites still pass (87/87).

Adds `libc = "0.2"` as a direct dep for `flock(2)` (already present
transitively).
2026-05-25 16:40:40 +03:00
Evgeny Boger
90376ecb6c ci(release-npm): restore source mtimes post-checkout so cargo cache works
actions/checkout sets every source file's mtime to "now". cargo's
fingerprint fast-path compares source mtime to .fingerprint
timestamps in target/; with the rust-cache-restored target/, sources
look newer than fingerprints and cargo recompiles every path-dep
crate + the final binary. ~90s/job wasted.

git-restore-mtime (from MestreLion/git-tools) walks git log and sets
each file's mtime to its last-touching commit date, so fingerprints
match between runs.

Also bumps `fetch-depth: 0` on both checkouts so git-restore-mtime
has the full history it needs (default depth=1 only sees one commit).
2026-05-25 16:35:50 +03:00
Evgeny Boger
ff9d3bee28 ci(release-npm): cache path-dep crates so warm runs actually skip work
rust-cache@v2 defaults to cache-workspace-crates=false. It strips
target/release/{deps,build}/* entries belonging to path = "..."
crates before saving, so a warm-cache run still recompiles the
9 vendor/microsandbox workspace crates (~28s) and re-links the
agent-vm binary under LTO (~70s).

Set cache-all-crates and cache-workspace-crates to true so the
full target/ is preserved. Trades ~600 MB of cache space per
job for actually-warm builds.
2026-05-25 15:38:52 +03:00
Evgeny Boger
1d5a8c4b42 ci(release-npm): force rust-cache save on non-default refs
rust-cache@v2 defaults to save-if: github.ref == default_branch.
Releases fire from v*.*.* tags or workflow_dispatch on
rewrite-microsandbox; neither is main, so the cache never
persisted and every run was effectively cold. set save-if: true
so subsequent runs can restore.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 15:29:15 +03:00
Evgeny Boger
35bdce0cee ci(release-npm): mirror chmod-line-continuation fix 2026-05-25 15:11:35 +03:00
Evgeny Boger
94646ceead ci(release-npm): fix chmod line-continuation (YAML scalar folding) 2026-05-25 15:11:34 +03:00
Evgeny Boger
d36c945156 ci(release-npm): mirror parallel-jobs + rust-cache + relax-LTO from rewrite-microsandbox 2026-05-25 15:03:19 +03:00
Evgeny Boger
f7dc485472 ci(release-npm): parallel build + rust-cache + relax msb LTO
Three speedups stacked, expected to roughly halve wall time
(~10–11 min cold / ~5–7 warm → ~5 cold / ~2 warm):

1. **Split into parallel jobs.** `build-platform` was building
   agent-vm + msb sequentially on one runner; now `build-agent-vm`
   and `build-msb` are independent matrix jobs that run on
   separate runners. A new `package` job downloads both artifacts,
   fetches libkrunfw inline (~10s curl, not worth its own job),
   and assembles the per-platform npm subpackage.

2. **Swatinem/rust-cache@v2** replaces our hand-rolled
   actions/cache step. Independent caches per build job (workspace
   root vs vendor/microsandbox), keyed automatically on rustc
   version + Cargo.lock + a salt — drops the manual rustc-hash
   step we wired in earlier this session. msb job also adds
   `env-vars: CARGO_PROFILE_RELEASE_LTO ...` so a relax-LTO flip
   never reuses a fat-LTO build's stale rlibs.

3. **Relax msb LTO in CI.** vendor/microsandbox's release profile
   is `lto=true, codegen-units=1` (~3–4 min single-threaded link).
   For a TLS proxy the runtime perf gain is negligible — override
   via env at the job level:
     CARGO_PROFILE_RELEASE_LTO=thin
     CARGO_PROFILE_RELEASE_CODEGEN_UNITS=16
   Keep panic=abort and strip=true (those change behaviour /
   reduce size, worth keeping).

`publish` now depends on `package` instead of `build-platform`.
The final artifact path is unchanged (`agent-vm-${platform}/{bin,lib}`)
so the publish steps need no changes.

v0.1.5 cuts a release to exercise the new layout end-to-end.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 15:03:18 +03:00
Evgeny Boger
3ba31d8ec7 agent-vm: re-add ANTHROPIC_MCP_PROXY_HOST to allowed hosts
Restores the MCP relay endpoint to the Anthropic placeholder's
allowed_hosts list. Previously added in dbe9210 and inadvertently
reverted by 31b24cc's stale-working-tree commit-a; not picked back
up by the subsequent rename / version-bump commits.

Without it, Claude Code's MCP traffic trips the secrets-handler
violation scan (the Anthropic placeholder appearing on a host
that's not in this secret's allow_host list) and the conn drops —
breaking MCP entirely for the in-VM agent.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 14:49:36 +03:00
Evgeny Boger
6560748f84 vendor: bump msb to document set_sandbox_log_path
Picks up `de9a1b2` which adds the missing doc comment that was
tripping the crate-level `#![warn(missing_docs)]` and printing a
warning during every release build.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 14:46:22 +03:00
Evgeny Boger
169dfb9c16 v0.1.4: bump for image rename to agent-vm-template 2026-05-25 14:40:28 +03:00
Evgeny Boger
1d5f36c451 rename ghcr image: agent-vm → agent-vm-template + clarify it's the guest
The name `agent-vm` was overloaded: it's both the runtime tool
(Rust binary on npm) AND the OCI image the tool boots inside each
microVM. Users seeing `ghcr.io/wirenboard/agent-vm:latest` would
reasonably guess that's where the *runtime* lives, but it's
actually the guest rootfs.

Rename the ghcr package to `agent-vm-template` and add OCI labels
(`org.opencontainers.image.title` / `description`) that explicitly
say "guest template image — install the runtime via npm". The
description shows on the ghcr.io package page.

Also rename the local-registry tag (`localhost:5000/agent-vm-template:latest`)
for consistency with the dev workflow.

Touches:
- DEFAULT_IMAGE_REF and every help/doctring referencing the ghcr
  image (setup.rs, run.rs, pull.rs, image_api_version.rs,
  image_check.rs).
- images/Dockerfile — new header + OCI labels.
- images/build.sh — local registry tag.
- .github/workflows/build-image.yml — IMAGE env.
- README.md + npm-dist/README.md.
- image_check tests updated to assert the new name.

Bumps to v0.1.4 so npm + ghcr converge on the new name.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 14:39:57 +03:00
Evgeny Boger
ca6a2a3d2b rename ghcr image to agent-vm-template (mirror from rewrite-microsandbox) 2026-05-25 14:39:24 +03:00
Evgeny Boger
c87d9bfb99 ci: bump actions to Node-24 majors + dependabot (mirror from rewrite-microsandbox) 2026-05-25 14:27:42 +03:00
Evgeny Boger
31b24ccbdb ci: bump all GH actions to Node-24 majors + add Dependabot
The Node 20 → 24 cutover is coming (forced June 2 2026, removal
Sept 16 2026). Every action we use already has a Node-24
major released — we were pinned to stale ones, hence the deprecation
warnings in the prior runs:

  actions/cache@v4              → @v5
  actions/upload-artifact@v4    → @v7
  actions/download-artifact@v5  → @v8
  actions/github-script@v7      → @v9
  docker/build-push-action@v6   → @v7
  docker/login-action@v3        → @v4
  docker/setup-buildx-action@v3 → @v4

The big leaps (artifact actions v4→v7, github-script v7→v9) are
because the maintainers cut MULTIPLE majors between Node 20 and
Node 24; we're just catching up.

Also enable Dependabot for github-actions on a weekly cadence so
we don't drift again. Submodule + Rust deps explicitly NOT
auto-updated — they need careful audits.

Bumps to v0.1.3 to exercise the upgraded actions end-to-end.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 14:27:41 +03:00
Evgeny Boger
9a66b4bfa8 vendor: bump msb to apply review fixes (handler state machine + tee lifecycle)
Bumps the submodule pointer to fd1b31b, which is the tip of
`agent-vm-secret-file` after folding in two review-driven fix commits:

  c5e3004  secrets/handler: fix state machine bypasses + cross-chunk
           boundary + keep-alive reset
  fd1b31b  runtime: harden stderr tee lifecycle + idempotent exit-log

Closes 13 of the 15 findings the max-effort review produced on the
prior submodule tip (db98655) — including the four CONFIRMED-via-
probe state-machine bypasses that re-introduced the ECONNRESET
false-positive the original narrowing was meant to eliminate, the
keep-alive security regression where request 2's partial headers
skipped the violation scan entirely, and the stderr-tee deadlock vs
VMM thread when host stderr is wedged. Full rationale in the vendor
commits' messages.

Cargo.lock bump (0.1.0 → 0.1.2) absorbed from the previous
rewrite-microsandbox merge.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 14:25:01 +03:00
Evgeny Boger
af51c0e001 merge rewrite-microsandbox (v0.1.1/v0.1.2 + ci npm-release fixes) 2026-05-25 14:22:07 +03:00
Evgeny Boger
8b67e15304 v0.1.2: re-publish (v0.1.1 subpackage tarball blob missing on npm)
Workflow log shows v0.1.1 published cleanly with sigstore-signed
provenance + valid integrity, but the actual
agent-vm-linux-x64-0.1.1.tgz blob 404s on origin (Cloudflare
forwarded). 25 minutes post-publish, still missing. Likely an
npm-side upload glitch on the 36MB tarball. Re-publishing as a
fresh version is simpler than waiting on npm support.

v0.1.0: binaries lack execute bit. Deprecated.
v0.1.1: subpackage tarball missing on origin. Deprecated.
v0.1.2: ↑ this should be the first usable version.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 14:03:01 +03:00
Evgeny Boger
f5bacb5f41 ci(release-npm): mirror chmod-after-download fix from rewrite-microsandbox 2026-05-25 13:46:53 +03:00
Evgeny Boger
6bee1f5626 v0.1.1: bump + ci chmod fix
v0.1.0 was published but the binaries inside the linux-x64
subpackage tarball landed at 0644 (no execute) — actions/
upload-artifact@v4 zips up the files without preserving POSIX
permissions, and npm publishes the round-tripped result as-is.
Users on v0.1.0 hit EACCES from the launcher's spawnSync.

Fix: add an explicit `chmod +x` step in the publish job after the
artifact download. Bump to v0.1.1 since npm versions are immutable.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 13:46:52 +03:00
Evgeny Boger
79d2458d64 ci(release-npm): mirror require-path fix from rewrite-microsandbox 2026-05-25 13:12:15 +03:00
Evgeny Boger
953fb4aa47 ci(release-npm): fix node require path in idempotent-skip helper
Node's `require()` only treats a path as relative when prefixed with
`./`. Bare `npm-dist/...` is interpreted as a node_modules /
built-in module spec and throws 'Cannot find module'. The
version-rewrite step above already used `fs.readFileSync` (which IS
cwd-relative) so it worked; this step used `require` and didn't.

Add the `./` prefix.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 13:12:13 +03:00
Evgeny Boger
cb692b9e4a ci(release-npm): mirror fix for actions/download-artifact@v5 behaviour
Same change as rewrite-microsandbox@643b249. Keep main's
workflow file in sync so GH Actions has a current version on the
default branch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 13:05:25 +03:00
Evgeny Boger
643b24953b ci(release-npm): download each platform artifact by exact name
actions/download-artifact@v5 with `pattern:` + a single matched
artifact does NOT wrap into a per-artifact subdir (behaviour change
from v4) — files land flat at `path:`. With one platform that meant
bin/ + lib/ ended up at npm-dist/{bin,lib} instead of
npm-dist/agent-vm-linux-x64/{bin,lib}, and the verify step
short-circuited the whole publish.

Switch to one download step per platform with explicit `name:` +
`path:`. Robust to v5's single-vs-multiple-artifact behaviour, and
forces an explicit step per added platform (a soft reminder when
extending the matrix).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 13:05:15 +03:00
Evgeny Boger
fa839320cf ci: add release-npm + build-image workflows
Land the workflow files on main so GitHub indexes them. The actual
release work lives on rewrite-microsandbox; main carries only the
CI plumbing so tag pushes / hourly crons / workflow_dispatch all
find a registered workflow to fire.

- release-npm.yml: fires on v*.*.* tag push (or manual dispatch).
- build-image.yml: hourly cron + on push to images/** (only fires
  here once images/ exists on main; until then, only manual
  dispatch from a feature branch).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 12:46:38 +03:00
Evgeny Boger
9e5d328565 merge rewrite-microsandbox (post-review-2 + docs)
# Conflicts:
#	crates/agent-vm/src/run.rs
2026-05-25 12:35:46 +03:00
Evgeny Boger
0ad086e12f merge rewrite-microsandbox-3: npm/ghcr distribution + image-API contract 2026-05-25 12:32:49 +03:00
Evgeny Boger
931ba12eb7 docs: bring PLAN/ARCHITECTURE/README in sync with Phase 7 chrome MCP rewrite
The four recent chrome-MCP commits (f40e6df / ce745ec / 43203fe and
this one's predecessor) left three documents describing an obsolete
shape:

- **PLAN.md Phase 7** still showed the original naïve MCP entry
  (`command: "npx"`, args `[..., "chrome-devtools-mcp@latest", ...]`)
  and a sketchy "in the rewrite" bullet list. Replaced with what
  actually shipped: the two failure modes (sandbox-as-root + MITM CA
  not in chrome's NSS DB), the three layers of fix (image:
  chrome-user/NSS DB/sudo/wrapper/pre-warm; launcher: certutil
  prelude with on-failure warning; secrets: pinned `@1.0.1` MCP
  entry under the wrapper), and the AGENT_VM_NO_CHROME_MCP opt-out
  semantics (gates both halves, cleans up empty mcpServers).
- **ARCHITECTURE.md "Image content"** still listed Chromium and
  Chrome DevTools MCP as "explicitly skipped in v1". Moved them
  into the in-scope list with a paragraph noting the chrome-user /
  NSS DB / wrapper / pre-warm setup. Also added gh (Phase 6, was
  missing) and rewrote the misleading image-size figures (chromium
  blew past the prior ~1.5 GB estimate).
- **README.md** had no mention of chrome MCP, AGENT_VM_NO_CHROME_MCP,
  or any of the env-var family (RUST_LOG / AGENT_VM_PROFILE /
  AGENT_VM_DEBUG_CONFIG). Added a small env-var table and a "Chrome
  DevTools MCP" section explaining the wrapper / NSS-DB design and
  the opt-out — enough that a user doesn't have to grep the source
  to understand what the chrome user in their VM is doing.

No code changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 12:21:40 +03:00
Evgeny Boger
d1ceca48c8 main: dedupe the !matches!(...) check via a local bool
Pure refactor. The same predicate was evaluated twice — once before
constructing the tokio runtime (to gate `point_at_msb` and thus the
unsafe set_var) and once inside the async block (to gate
`ensure_runtime_installed`). Same answer both times since `cli` is
not mutated. Capture the bool once.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 12:04:54 +03:00
Evgeny Boger
43203fe3a9 post-review-2: tighten chrome MCP wrapper + log hints + opt-out cleanup + JWT sig length
Folds in 13 findings from the second-pass max-effort review of
ce745ec. (The 14th — "var_os().is_some() opt-out semantics flipped" —
is actually a false positive: both old and new code treat
AGENT_VM_NO_CHROME_MCP=anything as opt-out. The unconventional
"empty value also opts out" behavior is consistent with
AGENT_VM_PROFILE / AGENT_VM_DEBUG_CONFIG elsewhere; documented
inline so the next reviewer doesn't re-flag it.)

**run.rs**

- `sandbox_log_dir()` replaces `runtime_log_path()`. Returns the log
  *directory* (containing `runtime.log` + `kernel.log` +
  `boot-error.json` — verified all three are written by vendor's
  `vm.rs::setup_kernel_log` and `boot_error.rs`). The previous helper
  pointed at `runtime.log` only, which is zero bytes for the most
  common boot-stage failures where the cause sits in kernel.log or
  boot-error.json instead. The doc comment now lists all three files
  and acknowledges that the helper deliberately diverges from
  upstream's `microsandbox_utils::resolve_home()` in one place: when
  `MSB_HOME` and `HOME` are both unset, we canonicalize the relative
  `./.microsandbox` fallback against the current working directory
  so the hint string is absolute (upstream's fallback ships into a
  subprocess where CWD is more controlled; embedding `.`-prefixed
  paths into launcher-side error messages is confusing).

- Error-context wording changed from `(see <path>/runtime.log for the
  in-VM tail)` to `(full logs: <dir>)`. Vendor's `Sandbox::create`
  already inlines the runtime.log tail into BootError messages via
  `tail_runtime_log`; the prior wording duplicated the suggestion
  ("look at the file you just saw the tail of"). The new wording
  positions the directory pointer as the *full* log location, useful
  in both the boot-failure case (vendor already showed you a tail)
  and the post-boot attach failure case (vendor didn't).

- Certutil prelude no longer silently swallows failures. Sudo /
  certutil stderr is captured to a per-invocation tempfile; on
  non-zero exit the launcher prints a 4-line warning that names the
  user-visible symptom (`HTTPS in chrome-devtools MCP will fail with
  ERR_CERT_AUTHORITY_INVALID`) and tail-prefixes the stderr so the
  user can see the actual sudoers / NSS error. Without this, every
  failure mode (dropped sudoers, world-writable sudoers, locked NSS
  DB, missing chrome user, unreadable CA file) was invisible from
  the launcher — chrome MCP boots normally, then every navigate
  silently fails with an opaque CDP error.

- AGENT_VM_NO_CHROME_MCP opt-out semantics commented inline at the
  point where the env-var is consulted, so the "set with any value
  including empty = opt out" rule (consistent with the rest of the
  agent-vm env-var family) doesn't re-trip future reviewers.

**secrets.rs**

- `OPENCODE_OPENAI_ACCESS_PLACEHOLDER` JWT signature length fixed:
  was `msb-opencode-placeholder-a-v2` (29 chars, `len % 4 == 1`
  which is structurally impossible for unpadded base64url and would
  trip any strict JWT parser); now `msb-opencode-placeholder-av2`
  (28 chars, `% 4 == 0`, valid). Sibling `OPENAI_ID_PLACEHOLDER` sig
  is already 28 chars — the rename in the previous commit produced a
  29-char OPENCODE sig by accident. `placeholders_are_pairwise_distinct`
  test continues to pass.

- Always-own-the-mcpServers-key block now tolerates a pre-existing
  `mcpServers: null` (resets to `{}` rather than bailing) and cleans
  up an empty `mcpServers: {}` left over after opt-out. With this,
  `AGENT_VM_NO_CHROME_MCP=1` from a fresh state dir produces a
  `claude.json` with the `mcpServers` key absent (the same shape it
  had pre-Phase-7), rather than the empty placeholder map that
  surprised users opting out from day one.

- Pinned `chrome-devtools-mcp@1.0.1` in the MCP config (was
  `@latest`). The image's pre-warm step now also pins the same
  version; without coupled pins, `npx -y @latest` re-resolves
  against the registry on every launch and invalidates the pre-warm
  cache as soon as upstream cuts a release. Bump both together when
  you want to track a newer release.

**Dockerfile**

- Wrapper script: replace `cd /home/chrome` with `cd /home/chrome
  2>/dev/null || { echo "...image misconfigured?" >&2; exit 1; }`.
  Without this, a missing /home/chrome (derivative image strips it,
  runtime mount fails) silently exits the wrapper from claude's MCP
  transport perspective with no actionable breadcrumb.

- Wrapper script doc: drop the false claim that agentd boot-sets
  `HTTP_PROXY` / `HTTPS_PROXY` / `NO_PROXY` (it doesn't — vendor
  agentd/lib/tls.rs sets only the CA env vars). The proxy env vars
  remain in the preserve list because user MCP `env:` blocks or
  `.agent-vm.runtime.sh` may set them, but the *reason* is
  documented honestly.

- Wrapper script doc: explicitly note that USER and LOGNAME are NOT
  in the preserve list, because sudo's default `env_reset` policy
  re-derives them from the target passwd entry; adding them would
  forward root's USER into the chrome session.

- Pre-warm RUN now `chrome-devtools-mcp@1.0.1 ... 2>&1 || echo
  "==> pre-warm skipped"`. Was an unguarded `npx -y @latest --help
  >/dev/null` that hard-failed the entire `agent-vm setup` on any
  npm registry blip — the opposite of its stated intent of
  insulating launches from registry availability.

Verified e2e in a fresh sandbox:
- pre-warm cache at /home/chrome/.npm/_npx/... is hit by the runtime
  npx invocation (same hash for matching @1.0.1 pin); reports
  app-version=1.0.1
- chromium runs as chrome (UID 9999) with its sandbox active; NSS
  DB trusts only the microsandbox CA with attributes `C,,`
- navigate_page https://example.com + take_snapshot return real
  "Example Domain" content
- AGENT_VM_NO_CHROME_MCP=1: jq 'has("mcpServers")' returns false
  (key entirely absent, not empty {})
- 73 cargo unit tests pass (placeholders_are_pairwise_distinct
  green with the new 28-char OPENCODE sig)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 12:03:49 +03:00
Evgeny Boger
494b8dd0aa distribution: address code-review findings before v0.1.0
Top fixes from the max-effort review:

**Correctness:**

1. setup.rs::verify_image now calls image_api_version::check() and
   runs each agent --version separately. Old code did
   `cat ... && claude --version && ...` so a non-zero exit could
   mean any of four causes; error message blamed only the API file.
   New code names which check failed.

2. main.rs runs `point_at_msb` BEFORE constructing the tokio
   runtime. setenv() is not thread-safe; the previous code ran
   `unsafe { set_var }` after `#[tokio::main]` had already spawned
   workers, technically UB if a worker read env concurrently
   (reqwest / sea-orm do this on first use). Switched to plain
   `fn main` + manual Runtime::new() so the env mutation happens
   single-threaded.

3. release-npm.yml cache key now includes a hash of `rustc -V`.
   Without this, an ubuntu-latest rustc rollover (we hit this
   exact bug locally this session) reused cached rlibs from the
   prior toolchain → undefined-symbol link errors.

4. release-npm.yml's subpackage publish loop is now idempotent:
   skips already-published versions instead of erroring. Avoids
   the split-state where a transient failure leaves subpackages
   at v but the main package never published.

5. build-image.yml's retention switched from
   `actions/delete-package-versions@v5`'s `min-versions-to-keep:
   350` (which breaks if cron is paused for days) to an explicit
   age-based prune via actions/github-script — deletes versions
   older than 14 days regardless of build cadence.

**UX:**

6. pull.rs adds `AGENT_VM_INSECURE_REGISTRY=1` env-var escape hatch
   so airgapped/intranet plain-HTTP registries
   (`registry.corp.example:5000`) can opt in. The hostname
   heuristic stays narrow (localhost/127.0.0.1/0.0.0.0/*.local/
   *.localhost) for safety.

7. msb_install.rs: MSB_PATH set-but-file-missing now falls back to
   the sibling-of-current_exe() if one exists (with a warn) and
   gives a much clearer error otherwise. The vanilla-msb rejection
   message now detects MSB_PATH presence and tells the user to
   unset it instead of generically suggesting "set MSB_PATH
   explicitly" (the LAST thing they need to hear when they just set
   it).

8. setup.rs warns when running from a source checkout (manifest_dir
   has images/Dockerfile) but the submodule isn't initialised —
   instead of silently pulling ghcr.io and confusing the dev who
   just edited images/Dockerfile.

9. npm-dist/agent-vm/bin/agent-vm.js' missing-optional error lists
   network-flake as the top cause (it is) plus --no-optional and
   inconsistent lockfile pinning. Previously blamed --no-optional
   as the most likely cause, which it usually isn't.

10. README files for both npm packages so npmjs.com renders
    something useful.

11. release-npm.yml's libkrunfw symlink step replaced the
    `( cd && ln && ln )` subshell with absolute-path `ln -sf`s.
    Subshell `set -e` propagation is bash-version-sensitive; the
    new form is unconditional.

84/84 → 85/85 tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 11:43:08 +03:00
Evgeny Boger
e094c74285 merge rewrite-microsandbox into fix-long-session-api-errors
# Conflicts:
#	crates/agent-vm/src/run.rs
2026-05-25 01:07:47 +03:00
Evgeny Boger
dbe92103b1 agent-vm: fix long-session ECONNRESET burst + MCP proxy auth
Long Claude Code sessions in agent-vm (the failure mode users see
as "every 10 seconds, Retrying in 17s · attempt 6/10" until the
session is dead) were hitting a false positive in the secret-
substitution layer. When a session's prompt body contained the
literal name of any non-Anthropic placeholder string (e.g.
investigation sessions reading other sessions' jsonl), the scan
flagged the request, dropped the conn mid-upload, and undici
reported it as ECONNRESET. Retries hit the same content, same
violation; the session became permanently wedged from that turn on.

This commit pairs with the vendor change `secrets/handler: scope
violation scan to header bytes only`, which restricts the scan to
credential-bearing positions (Authorization / X-*-Key headers, URL
query params on the request line) and leaves user-supplied body
content alone. See the bumped submodule for full rationale.

Local changes:

* **secrets.rs**: add ANTHROPIC_MCP_PROXY_HOST. Claude Code's MCP
  relay endpoint sends the same Anthropic access token but wasn't
  in the placeholder's allowed_hosts list, so every MCP request
  tripped the violation scan (also surfaced as ECONNRESET to the
  MCP client). Allowing this host fixes MCP for in-VM Claude.

* **run.rs**: thread that new host into the network builder, and
  add a focused post-mortem hint when `attach()` returns an error.
  The hint points at the per-sandbox log files
  (`~/.microsandbox/sandboxes/<name>/logs/{runtime.log,
  msb.stderr.log,msb-exit.log}`) and suggests `dmesg -T | tail`
  for host-OOM-kill cases. Surfaces what the vendor's `runtime:
  tee msb stderr` change makes available.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 01:01:29 +03:00
Evgeny Boger
ce745ece6d post-review cleanup: drop broken observability, tighten chrome MCP, finish placeholder rename
Folds in fourteen findings from the code review of the previous two
commits. Most are 'the commit advertised something it didn't actually
deliver' — straightforward to make honest.

**Observability commit was a no-op — revert it.**

- `main.rs` filter went back to the simple `warn` default. The previous
  `microsandbox_network::proxy=info,microsandbox_network::tls::proxy=info`
  targeted modules that (a) only emit `tracing::debug!` (so info
  filtered them out) and (b) run in the `msb sandbox` subprocess whose
  own tracing init reads no env at all. Net behaviour: zero proxy
  logs surfaced; the doc-comment claim was wishful thinking.

- `run.rs` post-mortem hint trimmed to one accurate line. Dropped the
  pointers to `msb.stderr.log` and `msb-exit.log` (vendor `spawn.rs`
  inherits stderr instead of teeing; `handle.rs::wait` writes no exit
  log). What remains: a single `(see <runtime.log> for the in-VM tail)`
  appended via `with_context`. Avoids the duplicated error output the
  old code produced — anyhow's main termination prints the chain once
  rather than the previous "eprintln before return then re-print on
  exit" combo.

- `runtime_log_path()` replaces `msb_sandbox_log_dir`; comment no
  longer claims to "mirror" upstream, and the final fallback matches
  `microsandbox_utils::resolve_home()` (`./.microsandbox`) rather than
  the previous incorrect `/var/lib/microsandbox`. Hint now also fires
  in the non-TTY branch's `with_context` and `anyhow::bail!` paths,
  not just TTY — CI users are the ones most likely to need the
  pointer.

**Chrome MCP cleanups.**

- `certutil` trust string `-t TC` → `-t C,,`. The previous form set
  the SSL column to `TC` (T = trusted issuer of *client* certs, C =
  trusted issuer of server certs). We only want server-cert trust;
  the T silently broadened scope. `C,,` is the textbook idiom.

- Wrapper's `--preserve-env` allow-list expanded to forward the env
  vars chrome-devtools-mcp itself documents
  (`CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS`, `CI`, `DEBUG`), agentd-
  set proxy config (`HTTP_PROXY` / `HTTPS_PROXY` / `NO_PROXY` plus
  lowercase variants), and basic locale (`TZ`, `LANG`, `LC_ALL`,
  `TMPDIR`). Without these, user MCP-config `env:` blocks and
  documented opt-outs were silently stripped by sudo's `env_reset`.

- Wrapper now `cd /home/chrome` before exec'ing. Inherited cwd was
  `/workspace` (the project bind-mount), which the chrome user (UID
  9999) cannot write — any MCP tool emitting a relative path
  (`./trace.json`, `./screenshot.png`, `--logFile=./debug.log`)
  EACCES'd silently. `/home/chrome` is chrome-owned and writable.

- Pre-warm `chrome-devtools-mcp@latest` into chrome's npm cache at
  image build time (`/home/chrome/.npm/_npx/...`). Previously every
  first MCP call paid a multi-second `npx -y` fetch and broke
  entirely when registry.npmjs.org was unreachable. The image gains
  ~21 MiB; cold launches drop the fetch.

- Bash prelude's certutil block is now gated on `AGENT_VM_NO_CHROME_MCP`
  not being set. Previously the env-var opt-out only suppressed the
  MCP entry in `claude.json`; the sudo→chrome→certutil round-trip
  ran on every launch regardless, exposing the opt-out user to the
  exact sudoers/NSS failure modes they were trying to avoid.

**Sticky opt-out fixed.** `write_default_claude_root_state` now
always owns the `chrome-devtools` key: `AGENT_VM_NO_CHROME_MCP=1`
explicitly `mcp.remove("chrome-devtools")`s a previously-written
entry instead of leaving it sticky in the on-disk merged JSON.

**OPENCODE placeholder rename completed.** `OPENCODE_OPENAI_ACCESS_PLACEHOLDER`
JWT signature is now `msb-opencode-placeholder-a-v2` (was
`MSB_OPENCODE_v1`), matching the rename rationale spelled out on
`OPENAI_ID_PLACEHOLDER`: non-token-shaped sentinels so Anthropic
doesn't flag transcripts that contain them.

**Stale literals swept.** `ARCHITECTURE.md` reads the new names at
all five reference sites. Six tests in `intercept_hook.rs` that
hardcoded `MSB_PLACEHOLDER_GH_TOKEN_v1[_xxxxxxxx]` as a standalone
fixture now use `secrets::GH_TOKEN_PLACEHOLDER` directly — so a
future placeholder shape change is exercised by the existing
regression assertions instead of slipping past them.

End-to-end verified inside a fresh sandbox:
- `claude mcp list` → ✓ Connected
- `navigate_page` https://example.com + `take_snapshot` returns the
  real "Example Domain" content
- NSS DB lists only the microsandbox CA with trust `C,,` (not `CT,,`)
- `chrome-devtools-mcp` package present in /home/chrome/.npm/_npx/
  out of the box (no per-launch fetch)
- `AGENT_VM_NO_CHROME_MCP=1`: claude.json mcpServers is `{}` (entry
  removed, not just absent on new files); NSS DB is empty (certutil
  prelude skipped)
- Wrapper preserves CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS, CI,
  DEBUG, HTTP_PROXY, TZ, NODE_EXTRA_CA_CERTS across the sudo hop
- 73 cargo unit tests pass (incl. the intercept_hook tests now using
  the constant)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 00:41:53 +03:00
Evgeny Boger
3df904c632 Revert "ci(npm-release): build libkrunfw from source instead of pulling upstream binary"
This reverts commit 631da16a41.
2026-05-25 00:38:06 +03:00
Evgeny Boger
631da16a41 ci(npm-release): build libkrunfw from source instead of pulling upstream binary
Eliminates the runtime dependency on superradcompany/microsandbox's
release tarball — we already vendor libkrunfw as a sub-submodule,
so building it ourselves is straightforward and removes:

- a foot-gun if upstream prunes / renames a release asset,
- the inability to patch libkrunfw if we ever need to,
- an asymmetry where we own the patched msb but borrow the .so.

Trade-off: a kernel build is ~10-15 min cold on the GH runner. Cache
keyed on the libkrunfw sub-submodule SHA makes that a one-time cost
per submodule bump — every subsequent release-npm run is a ~5 s
restore.

Steps:
1. Read `LIBKRUNFW_VERSION` / `LIBKRUNFW_ABI` from `vendor/
   microsandbox/crates/utils/lib/lib.rs` (single source of truth).
2. Restore `vendor/microsandbox/vendor/libkrunfw/libkrunfw.so.<ver>`
   from cache if present.
3. On miss: install kernel build deps (bc/bison/flex/libelf-dev/
   libssl-dev/kmod/cpio/python3-pyelftools/xz-utils) and run
   `make -j$(nproc)` in the libkrunfw tree. Cross-compile for arm64
   when matrix target ≠ host.
4. Copy the .so + recreate `libkrunfw.so.<ABI>` and `libkrunfw.so`
   symlinks into `npm-dist/agent-vm-$platform/lib/`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 00:25:30 +03:00
Evgeny Boger
42df2b4ff5 ci(npm-release): wire libkrunfw bundling from upstream microsandbox
Replaces the placeholder libkrunfw step with a concrete fetch of
the upstream microsandbox release tarball at:

  https://github.com/superradcompany/microsandbox/releases/download/v$MSB_VERSION/microsandbox-$os-$arch.tar.gz

(public release; verified 17 MiB, contains msb + libkrunfw.so.5.2.1).
The tarball's msb is discarded — we ship our own patched build — and
just libkrunfw* lands in `npm-dist/agent-vm-$platform/lib/`.

Versions come from the vendored source of truth (no duplication):
- `msb_version` ← `vendor/microsandbox/Cargo.toml` (workspace root,
  since the cli crate uses `version.workspace = true`).
- `libkrunfw_version` / `libkrunfw_abi` ← `vendor/microsandbox/
  crates/utils/lib/lib.rs` constants.

After extraction the workflow recreates the ABI symlinks that the
SDK's `setup::install` would normally make
(`libkrunfw.so.<ABI> -> libkrunfw.so.<VERSION>`,
`libkrunfw.so -> libkrunfw.so.<ABI>`), mirroring
`vendor/microsandbox/crates/microsandbox/lib/setup/download.rs::libkrunfw_symlinks`.

Smoke-tested locally: `curl -fsSL` follows GitHub's S3 redirect,
tarball extracts cleanly, glob finds libkrunfw.so.5.2.1 at the top
level (no nested prefix).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 00:18:42 +03:00