mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-10 01:50:13 +01:00
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Sandbox provider plugins let those agents run in remote environments. > - The boxd provider exists, but its release entry excludes it from CI publishing. > - npm now has the `@paperclipai/plugin-boxd@0.0.0` bootstrap placeholder. > - This pull request enables CI publishing and documents npm installation. > - A pre-existing OpenCode fixture blocked CI because it qualified a group-writable host Node binary. > - This also gives that test its own Linux Node copy with strict permissions. > - Operators can install a real release after CI publishes it. ## Linked Issues or Issue Description Related implementation: https://github.com/paperclipai/paperclip/pull/17284. No matching public issue or duplicate release-enrollment PR was found. **What existing behavior does this improve?** Publishing and installing the existing boxd sandbox provider. **Subsystem affected** `packages/plugins` and the npm release manifest. **Current behavior** `publishFromCi` is false. npm has only the `0.0.0` placeholder. The README says the package is not published and requires a local-path install. **Proposed behavior** Include boxd in CI releases. Document stable installation, explicit published versions, and the placeholder limit. **Reason and benefit** Operators can install the provider through the existing npm plugin installer after a real release ships. **Breaking changes** None. This changes release enrollment and documentation only. **Additional context** This follows the existing plugin and sandbox roadmap items. It adds no new runtime capability. ## What Changed - Set `publishFromCi: true` for `@paperclipai/plugin-boxd` in the release manifest. - Replace the README's unpublished-package notice with npm installation guidance. - Explain that the `0.0.0` placeholder has no plugin functionality and that `latest` can point to it until the first stable release. - Keep the local development instructions available. - Fix the existing ingress-retry test to use an owned Linux Node copy and explicit executable permissions. This follows the existing OpenCode transport fixture. It does not change production launch checks. ## Verification - Passed: `node --test scripts/release-package-map.test.mjs scripts/check-release-package-bootstrap.test.mjs scripts/bootstrap-npm-package.test.mjs` (31 tests). - Passed: `node scripts/release-package-map.mjs check`. - Passed: `PAPERCLIP_RELEASE_BOOTSTRAP_BASE_SHA=origin/master node scripts/check-release-package-bootstrap.mjs scripts/release-package-manifest.json packages/plugins/sandbox-providers/boxd/README.md`. - Passed: `git diff --check`. - Confirmed that npm resolves `@paperclipai/plugin-boxd` to version `0.0.0`. - Full workspace typecheck, test suite, and build were not run locally. CI checks remain required before merge. - Initial CI failure: [native Runner job](https://github.com/paperclipai/paperclip/actions/runs/41889969053/job/126785636239), OpenCode ingress retry, `qualified OpenCode proxy command must not be group- or world-writable`. The failing fixture is byte-identical to the base commit `3a2249dd576`. It passed `process.execPath` to qualification, while other OpenCode tests already use a private Linux Node copy. - Local focused run: `pnpm exec vitest run server/src/services/native-runtime/native-ingress-bootstrap-retry.integration.test.ts` built the real Rust Runner and passed the Codex case. The OpenCode case stopped at `runner_remote_provider_artifact_incompatible: OpenCode proxy path belongs to the controller host` on macOS (`/Users/...`). This does not exercise the new Linux-only copy. - An unchanged-base OpenCode comparison was attempted with `-t "reacquires opencode ingress"`, but PostgreSQL setup failed with `ENOSPC` before the case ran. The local baseline comparison is therefore inconclusive. Task-owned Rust build outputs were cleaned to recover disk space. - At this stage Linux verification was pending. The successful final run is recorded below. - No new tests were needed. The existing release-tool tests cover manifest enrollment, dependency ordering, and bootstrap checks. - Current-head CI interruption: [workspaces-a shard 1/2](https://github.com/paperclipai/paperclip/actions/runs/41893808561/job/126799012853) was cancelled after the runner received a shutdown signal. The log shows no assertion failure. The job rerun request was rejected with HTTP 403 because the workflow was still running. The subsequent fixture correction starts a new full CI run and supersedes that pending rerun. - CI at `7fffa315`: [native Runner job](https://github.com/paperclipai/paperclip/actions/runs/41893808561/job/126799011711) passed executable permission checks, then failed because Node tried to resolve `/memfd:paperclip-verified-launch (deleted)`. The private executable was named `opencode-node`, which bypassed Runner's Node interpreter recognition. The fixture now names its copy `node`, matching the existing transport fixture and selecting the CommonJS descriptor loader. - Passed a local smoke test of the production CommonJS descriptor loader using an open, unlinked script file. This is macOS evidence only; the successful final Linux run below provides the complete ingress retry evidence. - Final verification on `bcacac542aa0a1b641b26c099c1e9fc4a1653ec4`: all 54 reported checks passed; three unrelated checks were skipped. [Native Runner integration job](https://github.com/paperclipai/paperclip/actions/runs/41895787712/job/126805844773) passed, including the OpenCode ingress retry regression. The fresh full run also passed the workspace shard that was interrupted on the previous attempt. Greptile reports 5/5 for this exact head with no unresolved review threads. Earlier failures and local verification limits remain recorded above. ## Risks - The maintainer confirmed that npm trusted publishing is configured for this package. The first real CI publish will verify OIDC authentication. - Merging enables publication in the next successful canary run. An installation that uses `latest` can still resolve to the placeholder until the first stable release includes this change. ## Model Used OpenAI Codex, exact model ID `gpt-6.1-sol`, with xhigh reasoning effort, repository tool use, and shell execution. The runtime does not expose the context window size. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing>
163 lines
7.6 KiB
Markdown
163 lines
7.6 KiB
Markdown
# `@paperclipai/plugin-boxd`
|
|
|
|
boxd sandbox provider for Paperclip. [boxd](https://boxd.sh) runs each sandbox
|
|
as an isolated KVM microVM. This package lives alongside E2B, Daytona and the
|
|
other sandbox providers, outside the root pnpm workspace. It uses the
|
|
[`@boxd-sh/sdk`](https://www.npmjs.com/package/@boxd-sh/sdk) package. Paperclip's
|
|
CI release pipeline publishes this plugin to npm.
|
|
|
|
## Install from npm
|
|
|
|
Once a stable release is available, install the package from the Plugins page
|
|
by name, or use the CLI:
|
|
|
|
```sh
|
|
paperclipai plugin install @paperclipai/plugin-boxd
|
|
```
|
|
|
|
Before the first stable release, the `latest` tag can still point to the
|
|
`0.0.0` bootstrap placeholder. That version reserves the package name and has
|
|
no plugin functionality. Check the published tags and choose a real release:
|
|
|
|
```sh
|
|
npm view @paperclipai/plugin-boxd dist-tags --json
|
|
paperclipai plugin install @paperclipai/plugin-boxd --version <published-version>
|
|
```
|
|
|
|
Replace `<published-version>` with the version shown for the desired tag, such
|
|
as `canary`. If no real release is available yet, use the local-path install
|
|
below. See the [publishing guide](../../../../doc/PUBLISHING.md) for the release
|
|
and bootstrap process.
|
|
|
|
## Build and install locally
|
|
|
|
Requires Node 24.11+, pnpm, and an installed Paperclip checkout.
|
|
|
|
```sh
|
|
cd packages/plugins/sandbox-providers/boxd
|
|
pnpm install --ignore-workspace --no-lockfile
|
|
pnpm typecheck
|
|
pnpm test
|
|
pnpm build
|
|
```
|
|
|
|
From the Paperclip checkout, with your instance running:
|
|
|
|
```sh
|
|
node cli/node_modules/tsx/dist/cli.mjs cli/src/index.ts plugin install /absolute/path/to/paperclip/packages/plugins/sandbox-providers/boxd
|
|
node cli/node_modules/tsx/dist/cli.mjs cli/src/index.ts plugin inspect paperclip.boxd-sandbox-provider
|
|
```
|
|
|
|
## Configure an environment
|
|
|
|
Configure boxd under **Instance Settings -> Environments**, not from the
|
|
plugin page. Select the `boxd` provider.
|
|
|
|
- `apiKey`: a boxd API key. Create one with
|
|
`boxd auth keys create paperclip --org <org>`. Each key belongs to one org,
|
|
and machines are created in that org. Paperclip saves a pasted key as a
|
|
company secret. `BOXD_API_KEY` on the Paperclip host is an optional fallback
|
|
when the environment has no key.
|
|
- `reuseLease`: default `false`. With `false`, the plugin deletes the machine
|
|
when the run ends. With `true`, the plugin suspends the machine when the run
|
|
ends, and the next run on the same issue resumes it. Disk, memory and running
|
|
processes stay. The provider lease ID is the machine ID, so it stays the same
|
|
across resumes.
|
|
- `snapshot`: optional snapshot name or ID. Each run restores this snapshot,
|
|
for example a machine with your tools and repository already in place.
|
|
- `vcpu`: optional machine size for fresh machines: 1 vCPU/4 GB, 2/8 GB,
|
|
4/16 GB or 8/32 GB. A blank value uses the org default. A snapshot keeps its
|
|
own size.
|
|
- `org`: optional org name or ID. A blank value uses the API key's org.
|
|
- `namePrefix`: machine name prefix, `paperclip` by default.
|
|
- `timeoutMs`: default command timeout, 3600000 ms by default.
|
|
|
|
The default boxd image is Ubuntu 24.04 with Node 24, `tar`, `git`, Docker,
|
|
Claude Code, Codex and OpenCode on the PATH. It meets
|
|
[the sandbox runtime requirements](../SANDBOX-REQUIREMENTS.md) without a
|
|
setup step. Use a snapshot to add other agent CLIs.
|
|
|
|
The probe creates a machine, prepares the workspace and deletes the machine.
|
|
It uses real boxd resources. The probe machine also has a 15-minute boxd delete
|
|
timer, and a probe whose machine could not be deleted reports a failure.
|
|
|
|
## Implemented behavior
|
|
|
|
- Every machine is created with `isolated: true`. An isolated machine has no
|
|
metadata endpoint, no in-VM `boxd` CLI and no boxd integrations, and other
|
|
machines in the org cannot reach it over the private network. The API key
|
|
stays in the plugin worker and never enters the machine.
|
|
- Machines carry the labels `paperclip-provider=boxd`,
|
|
`paperclip-environment=<id>` and `paperclip-run=<id>`. Use
|
|
`boxd machine list --label paperclip-provider=boxd` to find them.
|
|
- Commands run through `sh` in the workspace, with quoted arguments,
|
|
per-command environment, staged stdin, and separate stdout and stderr. A
|
|
command timeout ends the remote process group and returns `timedOut: true`.
|
|
- Stdin is staged with boxd's file API in a file only the machine user can
|
|
read, and removed after the command. The workspace directory is created the
|
|
same way. A machine whose boxd agent predates the file API, such as one
|
|
restored from an older snapshot, falls back to the older upload and `mkdir`
|
|
calls.
|
|
- A failed acquisition deletes its machine. If boxd cannot confirm the
|
|
deletion, the plugin throws `PluginEnvironmentCreationCleanupError` with the
|
|
machine ID, so the host records it and retries the teardown. The destroy hook
|
|
checks that the machine's labels match before it deletes it.
|
|
- A command on a stopped machine starts the machine and runs the command one
|
|
more time. A suspended or hibernated machine wakes on the command.
|
|
- Release deletes the machine and returns a receipt only after boxd no longer
|
|
lists it. With `reuseLease`, release suspends the machine. A release that
|
|
cancels active work stops the machine and keeps its disk.
|
|
- `onEnvironmentStopLease` stops the machine and keeps its disk.
|
|
`onEnvironmentInspectLease` reads the machine state and does not change it.
|
|
- When the host requests a lease expiry, the plugin sets a boxd delete timer
|
|
at or before that time and returns the expiry on the lease.
|
|
- The plugin keeps one SDK client for each API key, so commands do not
|
|
exchange the key for a new session token each time.
|
|
|
|
## Capability boundaries
|
|
|
|
Interactive login PTYs, template capture, native file sync, persistent
|
|
sessions, incremental output, duplex channels and runner ingress are not
|
|
advertised. Workspace transfer uses the host's base64-over-exec fallback.
|
|
boxd has one region (EU), amd64 machines only, and no GPUs.
|
|
|
|
## Opt-in live tests
|
|
|
|
Default tests use a mocked SDK and do not contact boxd. To run against boxd,
|
|
export `BOXD_API_KEY` (and optionally `BOXD_ORG`), then run:
|
|
|
|
```sh
|
|
BOXD_LIVE_TEST=1 pnpm test
|
|
```
|
|
|
|
The live tests run every hook against boxd. They check:
|
|
|
|
- config validation, and a probe with a sized machine that deletes the machine
|
|
afterwards (a bad API key returns a failed probe);
|
|
- the labels, the isolation and the org of an acquired machine;
|
|
- stdin, env, argument quoting, exit codes and stderr, and the removal of the
|
|
staged stdin file;
|
|
- an empty stdin, and a staged stdin file that only the machine user can read;
|
|
- a 5 MiB round trip through the base64-over-exec path;
|
|
- that a command timeout returns `timedOut` and ends the remote processes;
|
|
- suspend and resume with a file and a running process kept, stop and resume
|
|
with the file kept, and inspect in each state;
|
|
- a command on a stopped machine, which starts the machine and runs;
|
|
- resume and stop of a hibernated machine, stop of a suspended machine, and a
|
|
release with `cancelActiveWork` or `stop_and_retain`;
|
|
- a release without reuse, which deletes the machine;
|
|
- a `requestedExpiresAt` that boxd enforces by deleting the machine;
|
|
- a run restored from a snapshot, and a fresh machine at the configured size;
|
|
- that resume after destroy reports an expired lease, and that no test
|
|
machines are left.
|
|
|
|
Each test deletes its machines in `finally`. The tests print timings and no
|
|
credentials. A full run takes about two minutes.
|
|
|
|
## Optional managed-image inclusion
|
|
|
|
The bundled catalog key is `boxd`. Include the `boxd` directory in the Docker
|
|
`CLOUD_BUNDLED_PLUGINS` build argument, then include `boxd` in the managed
|
|
configuration's `plugins.autoInstall` list. The catalog entry does not
|
|
auto-install the plugin or change the default image.
|