npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@agentproto/sandbox-computer

v0.7.1

Published

@agentproto/sandbox-computer — `SandboxProvider` for @agentproto/sandbox backed by a self-hosted microVM computer controller. Boots the agentproto daemon inside the computer, exposes its MCP endpoint, pauses (suspend) / resumes / detaches (rm) across the

Readme

@agentproto/sandbox-computer

SandboxProvider for @agentproto/sandbox backed by a self-hosted microVM "computer" fleet's controller — the same plumbing shape as @agentproto/sandbox-e2b (boot the agentproto daemon inside the box, expose its /mcp endpoint, pause/resume/reattach), against a controller API instead of a vendor SDK.

What it does

| SandboxProvider seam | Controller API | | --- | --- | | boot(spec) | POST /v1/computers → poll GET /v1/computers/:id until running → ensure the daemon is healthy in-box (install @agentproto/cli if needed, nohup agentproto serve … &) | | exec(opts) | POST /v1/computers/:id/exec (argv-based; the adapter wraps commands as /bin/bash -lc) | | readFile / writeFile | chunked base64 through the exec seam | | expose(port), mcpUrl | URL template (see config.gatewayUrl) | | pause() | POST /v1/computers/:id/suspend (full snapshot + stop) | | resume (inside connect) | POST /v1/computers/:id/resume — the controller may restore into a new computer id; the provider adopts it transparently and keeps the old id resolvable for probe() within the process | | stop() | DELETE /v1/computers/:id (rm + snapshot cleanup) | | probe(id) | GET /v1/computers/:id (404 ⇒ {alive:false}) | | connect(sandboxId) | GET → resume when suspended → ensure daemon → reattach |

Config

Via agent_start({ sandbox: { provider: "computer", config: … } }) or the provider's setup (stored creds), plus env fallbacks:

| config | env fallback | default | notes | | --- | --- | --- | --- | | controllerUrl | COMPUTER_CONTROLLER_URL | — | Controller base URL, e.g. http://host:7110. | | token (secret) | COMPUTER_CONTROLLER_TOKEN | — | Controller bearer service token. Resolved from a credential ref by the runtime (setup_sandbox_provider → stored creds → env), never logged or written anywhere else. | | tenantId | COMPUTER_TENANT_ID | agentproto | Sent as X-Tenant-Id on every request; computers are created under it. A stored setup_sandbox_provider value fills the env var when unset. | | egress | — | none | Allowlist forwarded as the create body's egress.allow: entries "host", "host:port" (port defaults to 443) or {host, port?}. The reference controller denies ALL egress, DNS included, when this is absent, so the on-boot npm i -g @agentproto/cli needs at least registry.npmjs.org. | | egressIp | — | "shared" | Egress IP selection forwarded as create egressIp (AIP-36 v1.3): "shared" (default, omitted from the create body so older controllers keep working), "auto", "dedicated", or a 1-8 character lowercase alphanumeric pool slot id. Also accepted from the spec-level network.egressIp; precedence: config.egressIp wins when set, otherwise network.egressIp applies, and "shared" is the default. A spec-level network.egressIp of "shared" counts as unset, so a conflict is only rejected before any controller call when both sides set different non-"shared" values. The booted handle reports the controller's egressIp: {id, publicIp} | null on boot and connect. With "auto", and a cooldown view in the boot options (SandboxBootOpts.egressHealth, which the runtime's egress health ledger fills for a spawn that names a model), the provider reads GET /v1/egress-pool and names an explicit healthy slot (least loaded first) instead of sending auto, moving to the next one if the controller refuses a slot as dedicated or full. A fleet treats a named slot as a pin on the host that owns it, so a fleet-capacity refusal of a named slot (the fleet's reason names the shortfall, e.g. memory, cpus or egress-slot-unavailable) means that host is full: the provider skips the remaining slots of that host (the fleet's pool entries carry hostId; without it the next slot is tried) and tries a slot on another host, and once none is left sends plain auto, which the fleet places host-first, before the ordinary fleet-capacity wait. Each skip is logged as a boot line. A no-active-host refusal is not skipped, since no other slot can relieve it. If every slot's address is cooling down for the model it throws SandboxEgressAllCoolingError (sandbox_egress_all_cooling) without creating a box. An unreadable pool falls back to the controller's own auto. provider.egressPool(spec) lists the slots and their public addresses. | | template | — | agentproto-workstation | Controller template id. The reference controller's stock registry currently only ships base/node/browser — point this at whatever template id your controller advertises (or ask the controller admin to register a workstation template). | | cpus, memoryMiB, rootDiskMiB | — | — | Resource limits forwarded to create. | | ttlSeconds | — | — | Computer TTL sent as create ttl. | | maxSessions | — | 1 | How many sessions THIS process may map onto one computer. | | sessionHolder | AGENTPROTO_SESSION_ID | none | Stable holder identity sent as the optional holder body on attach/detach and, in daemonRoute mode, as the X-Computer-Session-Holder header on every relayed request (HTTP and WebSocket). A controller that tracks holders then treats a re-attach by the same holder as idempotent (a restarted daemon re-attaches onto the slot its dead predecessor still held instead of hitting the 409 session limit) and scopes the detach to that holder's slot. The env fallback covers a provider running inside a spawned agent session; absent means the body-less legacy attach. An explicit gatewayUrl that goes through the controller can carry the header via extraHeaders instead. | | port | — | 18790 | Daemon port inside the computer. | | workspace | — | /home/user | agentproto serve --workspace. | | gatewayUrl | COMPUTER_GATEWAY_URL | http://<computer-name>:<port> | URL template with {port}/{id}/{hostPort} placeholders for reaching a computer's ports ({hostPort} is the host port the controller published for the daemon port; the boot fails if none). Default assumes the box network is addressable by the computer's generated name; set it when computers sit behind an edge proxy. The daemon is started with --allow-origin for this URL's origin, recorded in ~/.agentproto-daemon.origin; on reconnect a healthy daemon whose recorded origin differs (the host port changed) is restarted. A daemon without that file (autostarted by the template) is left as is. The runtime exposes it as an optional setup field (stored provider creds fill COMPUTER_GATEWAY_URL when unset), which a daemon-restart re-attach needs because it carries no per-spawn config. It is not required in daemonRoute mode; supplying both is rejected as a conflicting configuration. When the controller's GET omits ports, a reconnect recovers the host port from the origin marker. mcpUrl is the resolved URL plus /mcp. | | daemonRoute | COMPUTER_DAEMON_ROUTE (1) | off | Reach each computer's daemon through the controller at <controllerUrl>/v1/computers/{id}/daemon instead of a per-port gatewayUrl, which is not required in this mode and must be omitted (supplying both is rejected with a clear error). The daemon call then carries the controller credential in X-Controller-Authorization: Bearer … and the tenant in X-Tenant-Id, next to the daemon's own Authorization, via BootedSandbox.authHeaders. Needs a controller that serves that route. | | extraHeaders | COMPUTER_EXTRA_HEADERS (JSON object), COMPUTER_ACCESS_CLIENT_ID + COMPUTER_ACCESS_CLIENT_SECRET | none | Headers added to every controller call (and to daemon calls in daemonRoute mode), for a proxy in front of the controller that wants its own credentials. The id/secret pair maps to CF-Access-Client-Id / CF-Access-Client-Secret. Values are secrets: never logged, redacted from errors. Authorization, X-Tenant-Id and Content-Type are owned by the client and ignored here. Prefer the env vars (or the stored provider profile) over config, which can end up in session metadata. | | updateCliOnBoot, cliVersion, installPackages, setupCommands | — | install on | Same semantics as sandbox-e2b's boot flow. | | waitReadyTimeoutMs, daemonReadyTimeoutMs | — | 120s / 30s | Boot readiness windows. | | fleetCapacityWaitMs | none | 12 min | How long a create answered 503 fleet-capacity keeps being retried while the fleet frees or wakes a host (an OVH-shelved host takes about 10 min to come back). Each wait honours Retry-After, else fleetCapacityRetryMs, and is logged; the wait does not count against the boot deadline, and a cancelled spawn stops it. Every attempt of one boot sends the same Idempotency-Key, so a replayed create returns the same box. 0 fails on the first refusal. The auto provider sets it to 0 when overflow to e2b is on, unless set explicitly. | | fleetCapacityRetryMs | none | 30s | Wait between two fleet-capacity retries when the refusal carries no Retry-After. |

The provider validates the controller's egressIp field with the typed @agentproto/computer-api contract, so a malformed egressIp in a controller response fails the call instead of being ignored.

Template and box environment contract

The computer template image (agentproto-workstation by default) is NOT built from this repository. This repo only defines the e2b workstation under templates/workstation/; the microVM template is registered on the computer controller (a separate deployment/repository). Change the template there, then point config.template at the new id here.

The template must provide these basics for an agentproto session:

| item | requirement | | --- | --- | | gh | GitHub CLI must be on PATH. Without it a boxed agent cannot open or inspect PRs. | | writable scratch dirs | /tmp/opencode and the other adapter temp dirs must be writable by the session user (uid 1000), not root-owned. | | Node heap | The provider sets NODE_OPTIONS from config.memoryMiB: 60% of the box's RAM clamped to 768–8192 MB (8192 MB when memory is unknown). A caller NODE_OPTIONS that already sets --max-old-space-size wins; otherwise the heap flag is appended. Without this a monorepo pnpm install/build can die at Node's default heap. | | base tools | git, bash, curl and a TLS trust store are assumed by the boot flow. |

Recommended default egress

The controller denies all egress (DNS included) when config.egress is absent. For a normal coding session the allowlist should at least include:

egress: [
  "registry.npmjs.org",
  "github.com",
  "api.github.com",
  "objects.githubusercontent.com",
  "release-assets.githubusercontent.com",
]

objects.githubusercontent.com / release-assets.githubusercontent.com are needed by native package prebuilds (sharp, esbuild, etc.) that redirect there after the registry fetch. Add the model/provider endpoints your adapters use.

Git token scope

The workspace Git token is injected as AGENTPROTO_GIT_TOKEN (and mirrored as GH_TOKEN) by the runtime, not by this provider. It is minted outside this repository. If boxed gh calls to the check-runs API answer 403, the token needs checks:read (and pull_requests:read) in addition to the contents scope used for clone/push. That scope is configured where the workspace credential is created, not here.

Capabilities

Registered under the slug computer:

{
  "networkEgress": true,
  "mounts": false,
  "lifecyclePause": true,
  "readOnly": false,
  "exec": true,
  "sharedSessions": true
}

sharedSessions: true is descriptive metadata: this provider's box is advertised as shareable — one computer's daemon could hang off several agentproto sessions multiplexing over mcpUrl. No runtime behaviour reads this flag yet. config.maxSessions is forwarded at create (the controller sizes the computer's CPU and RAM for it) and counted by the controller: boot and connect attach a session, pause and stop detach it, and a connect past the cap is refused — except a re-connect by the same sessionHolder, which is idempotent and keeps its slot. The last session to stop removes the computer. A controller without the attach routes falls back to a per-process count.

Capacity and egress errors

The controller's machine error codes are mapped to typed errors with a stable code (the ComputerSandboxError exported by this package):

| controller response | provider error code | | --- | --- | | 400 {error:"egress-pool-disabled"} (no pool configured) | sandbox_egress_pool_unavailable | | 400 {error:"egress-pool-unknown"} (unknown pool/slot) | sandbox_egress_slot_unknown | | 409 {error:"egress-pool-exhausted"} (pool full) | sandbox_egress_pool_exhausted | | 409 {error:"egress-slot-dedicated"} (slot already dedicated) | sandbox_egress_slot_dedicated | | 503 {error:"host-capacity", resource, requested, available} | sandbox_capacity |

A 503 host-capacity response also carries the controller's Retry-After as retryAfterMs, alongside resource, requested and available on the error, so a caller can wait and retry (or fall back to another host) without parsing strings. Error messages only quote the status/code and capacity numbers; secrets never appear in them.

Secrets

  • The controller token travels only in the Authorization: Bearer … header.
  • A fresh --auth-token (UUID) gates the daemon inside each computer (the published port reaches it as a non-loopback peer, so the daemon requires it). It is written 0600 to ~/.agentproto-daemon.token in the box and returned on BootedSandbox.token/authHeaders for the host's authenticated reads. It appears nowhere else (no logs, no errors); a reconnect reads it back from that file. A daemon autostarted by the template has no token file and gets no auth headers.
  • Extra headers (for example an edge service token) are sent as given, and redacted from every error message like the service token.
  • This package logs nothing and redacts the service token from every error message it produces ([redacted]).

Pointing it at a controller

const booted = await computerSandboxProvider.boot(
  {
    provider: "computer",
    config: {
      controllerUrl: "https://host:7110",
      tenantId: "team-x",
      template: "node", // whatever the controller's registry advertises
      cpus: 4,
      memoryMiB: 8192,
      gatewayUrl: "https://edge.internal:{port}/computers/{id}",
      maxSessions: 4,
    },
  },
  { env: { /* secrets to deliver into the guest */ } },
)
// booted.mcpUrl — feed it to connectDaemonAgentSessionHost as usual.

Known gaps (adapter needs, controller lacks)

  • No attach expiry: an attach whose process died is not detected by the controller; an operator clears it with a forced delete.
  • resume renames the computer: a suspend→restore cycle rebuilds the box under a new generated id. The provider aliases old→new in-process; the session ledger may still record the pre-suspend id, so an attach after a cross-process restore requires the NEW id.
  • No generic port-publish API: only the browser template's CDP relay gets a host-side published port. mcpUrl therefore defaults to the computer's own name (http://<name>:<port>) or a gatewayUrl template — neither is brokered by the controller.
  • No extend-timeout endpoint: the TTL is fixed at create; extendTimeout is a documented no-op rather than a real re-arm.
  • Detached daemon start is unverified against real hardware: the API's exec endpoint is synchronous; the provider backgrounds agentproto serve with nohup … & inside one bash -lc. The preferred production path is a template that autostarts the daemon (the adapter probes health first and skips its own start when the daemon is already up).
  • Template registry: the reference controller registers only base/node/browser; the adapter's default template id is agentproto-workstation and must be registered controller-side, or config.template pointed at an existing one.