diff --git a/packages/plugins/sandbox-providers/boxd/README.md b/packages/plugins/sandbox-providers/boxd/README.md index 13797a9f2d..af5001fa16 100644 --- a/packages/plugins/sandbox-providers/boxd/README.md +++ b/packages/plugins/sandbox-providers/boxd/README.md @@ -3,10 +3,31 @@ 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. The -package is not published yet; use a local-path install. Its release manifest -entry has `publishFromCi: false` until a maintainer bootstraps the first npm -publish. +[`@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 +``` + +Replace `` 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 diff --git a/scripts/release-package-manifest.json b/scripts/release-package-manifest.json index 16cf135629..0eb2824e11 100644 --- a/scripts/release-package-manifest.json +++ b/scripts/release-package-manifest.json @@ -127,7 +127,7 @@ { "dir": "packages/plugins/sandbox-providers/boxd", "name": "@paperclipai/plugin-boxd", - "publishFromCi": false + "publishFromCi": true }, { "dir": "packages/plugins/sandbox-providers/cloudflare", diff --git a/server/src/services/native-runtime/native-ingress-bootstrap-retry.integration.test.ts b/server/src/services/native-runtime/native-ingress-bootstrap-retry.integration.test.ts index e4240f16db..403963ec50 100644 --- a/server/src/services/native-runtime/native-ingress-bootstrap-retry.integration.test.ts +++ b/server/src/services/native-runtime/native-ingress-bootstrap-retry.integration.test.ts @@ -1,7 +1,7 @@ import { execFileSync, type ChildProcess } from "node:child_process"; import { createHash, randomUUID } from "node:crypto"; import { readFileSync } from "node:fs"; -import { copyFile, mkdir, readFile, stat, writeFile } from "node:fs/promises"; +import { chmod, copyFile, mkdir, readFile, stat, writeFile } from "node:fs/promises"; import https, { createServer as createHttpsServer } from "node:https"; import { createServer as createTcpServer } from "node:net"; import { join, resolve } from "node:path"; @@ -85,11 +85,18 @@ describeEmbeddedPostgres("native ingress bootstrap retry through the real Runner identity: { kind: provider } }, normalizedSessionId: native.normalizedSessionId })); const digest = (file: string) => `sha256:${createHash("sha256").update(readFileSync(file)).digest("hex")}`; const opencode = join(runtimeRoot, "fake-opencode-ingress"); + const providerNode = provider === "opencode" && process.platform === "linux" + ? join(runtimeRoot, "node") : process.execPath; const providerCalls = join(sandbox, "provider-calls.log"); if (provider === "opencode") { + if (process.platform === "linux") { + await copyFile(process.execPath, providerNode); + await chmod(providerNode, 0o500); + } const script = resolve(runnerWorkspace, "../test/fixtures/fake-opencode-server.mjs"); execFileSync("cc", ["-x", "c", "-o", opencode, "-"], { input: - `#include \n#include \n#include \nint main(int argc, char **argv) { FILE *log = fopen(${JSON.stringify(providerCalls)}, "a"); if (!log) return 126; fputs("process-start\\n", log); fclose(log); char **args = calloc(argc + 2, sizeof(char *)); args[0] = ${JSON.stringify(process.execPath)}; args[1] = ${JSON.stringify(script)}; for (int i = 1; i < argc; i++) args[i + 1] = argv[i]; execv(args[0], args); return 127; }` }); + `#include \n#include \n#include \nint main(int argc, char **argv) { FILE *log = fopen(${JSON.stringify(providerCalls)}, "a"); if (!log) return 126; fputs("process-start\\n", log); fclose(log); char **args = calloc(argc + 2, sizeof(char *)); args[0] = ${JSON.stringify(providerNode)}; args[1] = ${JSON.stringify(script)}; for (int i = 1; i < argc; i++) args[i + 1] = argv[i]; execv(args[0], args); return 127; }` }); + await chmod(opencode, 0o755); } const proxy = resolve(runnerWorkspace, "../dist/cli/opencode-app-server-proxy.cjs"); const identity = { runnerInstanceId: native.runnerInstanceId, environmentLeaseId: runId, runId, @@ -187,8 +194,8 @@ describeEmbeddedPostgres("native ingress bootstrap retry through the real Runner onAccountingEvent: async event => { await usageDelivery?.capture(event); }, environment: { CODEX_HOME: codexHome, PATH: process.env.PATH, OPENROUTER_API_KEY: "synthetic-fixture-key" }, sourceCodexHome: codexHome, ...(provider === "opencode" ? { opencodeCommand: opencode, opencodeCommandSha256: digest(opencode), - opencodeProxyPath: proxy, opencodeProxySha256: digest(proxy), providerNodeCommand: process.execPath, - providerNodeCommandSha256: digest(process.execPath), opencodePermissionMode: "allow" as const } : {}), + opencodeProxyPath: proxy, opencodeProxySha256: digest(proxy), providerNodeCommand: providerNode, + providerNodeCommandSha256: digest(providerNode), opencodePermissionMode: "allow" as const } : {}), prpIdentity: identity, lifecyclePolicy: { mode: "per_turn", idleTimeoutMs: null }, controlPlaneRegistration: async authority => { const controller = await currentNativeControllerIdentity();