diff --git a/doc/DEVELOPING.md b/doc/DEVELOPING.md index cab7762fce..2ad1963b6f 100644 --- a/doc/DEVELOPING.md +++ b/doc/DEVELOPING.md @@ -807,6 +807,22 @@ agent workspace. The host `HOME` itself, a directory that contains it, a filesystem root, a `CODEX_HOME` overlap, or a canonical path outside the assigned workspace is rejected before provider startup. +### Preinstalled remote runner runtime + +For fast sandbox startup, bake `paperclip-runnerd` and the latest stable agent +CLIs into the sandbox image. Keep one version of each CLI shared by native and +local adapters; never retain an older global CLI beside a newer private copy. +Pin the resolved releases at image build time for reproducibility and refresh +the runner's qualification versions and binary digests together with those pins. +The ACP bridges remain separately qualified protocol dependencies. + +Native discovery checks `/opt/paperclip-runner/bin`, then `$HOME/.local/bin`, +then PATH. Any preferred-directory entry must launch the same shared CLI that +normal adapters use. Discovery picks the first executable; it does not compare +versions across directories. With these artifacts preinstalled, startup links +and verifies them without uploading a binary or installing packages. Deploy +the updated sandbox image with the matching runner qualification changes. + ### Native runner restart recovery Paperclip Runner keeps its heartbeat run, native session, logical runner, and diff --git a/docker/daytona-runner/Dockerfile b/docker/daytona-runner/Dockerfile index 87d819f49c..67e88e6fa0 100644 --- a/docker/daytona-runner/Dockerfile +++ b/docker/daytona-runner/Dockerfile @@ -18,27 +18,27 @@ WORKDIR /workspace COPY package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc tsconfig.base.json ./ COPY patches ./patches COPY scripts/link-plugin-dev-sdk.mjs ./scripts/link-plugin-dev-sdk.mjs -COPY packages/paperclip-eval-kernel/package.json ./packages/paperclip-eval-kernel/package.json -COPY packages/paperclip-runner/package.json ./packages/paperclip-runner/package.json -RUN pnpm install --frozen-lockfile --filter '@paperclipai/paperclip-runner...' -COPY packages/paperclip-eval-kernel/tsconfig.json ./packages/paperclip-eval-kernel/tsconfig.json -COPY packages/paperclip-eval-kernel/src ./packages/paperclip-eval-kernel/src -COPY packages/paperclip-runner/styles.css packages/paperclip-runner/tsconfig.json packages/paperclip-runner/tsconfig.surfaces.json ./packages/paperclip-runner/ -COPY packages/paperclip-runner/protocol ./packages/paperclip-runner/protocol -COPY packages/paperclip-runner/runner/crates/runner-core/src/generated_acpx_sidecar_contract.rs ./packages/paperclip-runner/runner/crates/runner-core/src/generated_acpx_sidecar_contract.rs -COPY \ - packages/paperclip-runner/scripts/acpx-sidecar-contract.mjs \ - packages/paperclip-runner/scripts/build-provider-pack.mjs \ - packages/paperclip-runner/scripts/build-verified-provider-entrypoints.mjs \ - packages/paperclip-runner/scripts/generate-acpx-sidecar-contract.mjs \ - packages/paperclip-runner/scripts/generate-protocol-schema-module.mjs \ - ./packages/paperclip-runner/scripts/ -COPY packages/paperclip-runner/src ./packages/paperclip-runner/src +# CI owns pnpm-lock.yaml and regenerates it for manifest-only PRs. Include the +# complete workspace manifest graph so all source-owned patches apply, then +# resolve before the frozen install. This happens only during image creation. +COPY packages ./packages +COPY server/package.json ./server/package.json +COPY ui/package.json ./ui/package.json +COPY cli/package.json ./cli/package.json +# The complete resolved lock (including transitive integrity hashes) is reviewed. +# Reject registry-time drift BEFORE installing packages or running lifecycle code. +# Refresh this digest together with source/provider dependency changes. +ARG PAPERCLIP_RUNNER_LOCK_SHA256=47a7c09302d47843054d0301f8f52f3da935b9c6ac771bace0409da752b6af7f +RUN pnpm install --resolution-only --ignore-scripts --no-frozen-lockfile \ + && printf '%s pnpm-lock.yaml\n' "${PAPERCLIP_RUNNER_LOCK_SHA256}" > /tmp/provider-lock.sha256 \ + && sha256sum -c /tmp/provider-lock.sha256 \ + && pnpm install --frozen-lockfile --filter '@paperclipai/paperclip-runner...' ARG PAPERCLIP_RUNNER_SOURCE_REVISION RUN test -n "${PAPERCLIP_RUNNER_SOURCE_REVISION}" RUN pnpm --filter @paperclipai/paperclip-runner build:typescript \ && PAPERCLIP_RUNNER_SOURCE_REVISION="${PAPERCLIP_RUNNER_SOURCE_REVISION}" \ - node packages/paperclip-runner/scripts/build-provider-pack.mjs /provider-pack + node packages/paperclip-runner/scripts/build-provider-pack.mjs /provider-pack \ + && chmod -R a+rX /provider-pack # Fleet sandbox base image. Keep this section aligned with # paperclipai/paperclip-cloud/fleet-sandbox-image/Dockerfile. The only Paperclip @@ -50,19 +50,25 @@ USER root ENV PAPERCLIP_RUNNER_PROVIDER_PACK_ROOT=/opt/paperclip-runner/provider-pack ENV PATH=${PAPERCLIP_RUNNER_PROVIDER_PACK_ROOT}/node_modules/.bin:/usr/local/share/nvm/current/bin:/usr/local/python/current/bin:/usr/local/py-utils/bin:${PATH} -RUN npm install -g \ - @anthropic-ai/claude-code@2.1.19 \ - @openai/codex@0.148.0 \ - @xai-official/grok@1.0.3 \ - @google/gemini-cli@0.56.0 \ - @moonshot-ai/kimi-code@0.38.0 \ - opencode-ai@1.18.17 \ +# Share the pack's latest stable Codex, Claude and OpenCode with every adapter. +# Never add a second CLI version to work around a runner compatibility pin. +RUN npm uninstall -g @anthropic-ai/claude-code @openai/codex opencode-ai \ + && npm install -g \ + @xai-official/grok@1.0.13 \ + @google/gemini-cli@0.58.0 \ + @moonshot-ai/kimi-code@0.41.0 \ && npm cache clean --force -RUN pip install --no-cache-dir --break-system-packages hermes-agent==0.15.2 +# Hermes requires Python >=3.11,<3.14; the image's default Python is newer. +# Use the distro interpreter in a dedicated environment without changing PATH +# or keeping an older Hermes CLI. Everything is installed before sandbox boot. +# Debian 13 in the sandbox base already includes Python 3.13 and python3-venv. +RUN /usr/bin/python3 -m venv /opt/hermes \ + && /opt/hermes/bin/pip install --no-cache-dir hermes-agent==0.19.0 \ + && ln -sf /opt/hermes/bin/hermes /usr/local/bin/hermes -ARG CURSOR_VERSION=2026.08.11-e8db854 -ARG CURSOR_SHA256_AMD64=bfff4bf6f4e9dd30c1d0ef0a70b6077b074015dd2948e4c50685d53afdcfce5a +ARG CURSOR_VERSION=2026.09.02-c22c1a3 +ARG CURSOR_SHA256_AMD64=b73b59854762535c0fc20d7ccc51c3b5a356a851491088d60a362be48750f53c RUN set -eu; \ arch="$(dpkg --print-architecture)"; \ [ "$arch" = "amd64" ] || { echo "FATAL: cursor pin only covers amd64, not $arch" >&2; exit 1; }; \ @@ -76,8 +82,8 @@ RUN set -eu; \ ln -sf /usr/local/bin/cursor-agent /usr/local/bin/agent; \ chmod -R a+rX /opt/cursor -ARG GH_VERSION=2.98.0 -ARG GH_SHA256_AMD64=3b8ac6b30336802fc1a858d7c084e11cdf24ac1a761ca90b68022d7d729208de +ARG GH_VERSION=2.100.0 +ARG GH_SHA256_AMD64=e4d4bb4498e8d007abe545b6568926793ace1b6447da598294a610018cb164be RUN set -eu; \ arch="$(dpkg --print-architecture)"; \ [ "$arch" = "amd64" ] || { echo "FATAL: gh pin only covers amd64, not $arch" >&2; exit 1; }; \ @@ -90,6 +96,10 @@ RUN set -eu; \ COPY --from=runnerd-build /workspace/packages/paperclip-runner/runner/target/release/paperclip-runnerd /usr/local/bin/paperclip-runnerd COPY --from=provider-pack-build /provider-pack /opt/paperclip-runner/provider-pack +RUN set -eu; for cli in codex claude opencode; do \ + printf '#!/bin/sh\nexec /opt/paperclip-runner/provider-pack/node_modules/.bin/%s "$@"\n' "$cli" > "/usr/local/bin/$cli"; \ + chmod 755 "/usr/local/bin/$cli"; \ + done # Keep revision-dependent metadata below the stable agent CLI installation # layers. A source-only image miss can then reuse those expensive layers from @@ -100,7 +110,6 @@ RUN test -n "${PAPERCLIP_RUNNER_CONTENT_ID}" \ && test -n "${PAPERCLIP_RUNNER_SOURCE_REVISION}" RUN set -eu; \ - chmod -R a+rX /opt/paperclip-runner/provider-pack; \ printf '%s\n' 'export PATH=/opt/paperclip-runner/provider-pack/node_modules/.bin:$PATH' \ > /etc/profile.d/01-paperclip-runner-provider-pack.sh; \ chmod 0644 /etc/profile.d/01-paperclip-runner-provider-pack.sh; \ diff --git a/docker/daytona-runner/README.md b/docker/daytona-runner/README.md index 766f5e8b3c..229529b962 100644 --- a/docker/daytona-runner/README.md +++ b/docker/daytona-runner/README.md @@ -2,7 +2,7 @@ This image is the Paperclip Cloud fleet sandbox image plus a source-built `paperclip-runnerd` and immutable provider pack. The pack contains Node 24.11, -OpenCode 1.18.17, the compiled OpenCode proxy, ACPX 0.13.1 sidecar, qualified ACP +OpenCode 1.18.29, the compiled OpenCode proxy, ACPX 0.13.1 sidecar, qualified ACP agents, and the production lockfile. Its manifest digests each executable bridge and binds the pack to the runner source revision, avoiding artifact upload and npm installation on every fresh lease. @@ -67,3 +67,13 @@ full Git SHA as `PAPERCLIP_RUNNER_SOURCE_REVISION`. Do not bake provider credentials, Paperclip bootstrap tickets, or Daytona preview tokens into this image. They remain per-run secret material. + +Provider CLI updates are manifest-only changes: repository CI owns the root +lockfile. The image build resolves the complete workspace manifest graph before +its frozen install, matching CI when a source commit precedes the lockfile bot. +The complete resolved lockfile must match `PAPERCLIP_RUNNER_LOCK_SHA256` before +package installation or lifecycle execution. Review and refresh that digest +with source dependency changes; registry-time resolution drift fails closed. +Keep one latest stable CLI installation per provider; refresh exact runtime +versions and qualification digests together, never install a private older copy +or download dependencies when a task starts. diff --git a/docs/deploy/environment-variables.md b/docs/deploy/environment-variables.md index 678552b25b..2f6900a244 100644 --- a/docs/deploy/environment-variables.md +++ b/docs/deploy/environment-variables.md @@ -23,7 +23,7 @@ All environment variables that Paperclip uses for server configuration. | `PAPERCLIP_RUNNER_CA_BUNDLE_PATH` | (unset) | Optional PEM CA bundle for direct runner WSS. Platform roots remain enabled. There is no insecure TLS bypass. | | `PAPERCLIP_RUNNER_REMOTE_BINARY_PATH` | (host build) | Host-local path to a `paperclip-runnerd` artifact built for the remote target OS and architecture. Required when Paperclip and the remote sandbox do not share a compatible platform; build metadata and the required transport mode are verified before launch. | | `PAPERCLIP_RUNNER_REMOTE_CODEX_PATH` | (unset) | Optional host-local path to a Codex executable built for the remote target OS and architecture. For remote Codex-backed runners, Paperclip stages and verifies this executable beside `paperclip-runnerd`. | -| `PAPERCLIP_RUNNER_REMOTE_CODEX_NPM_SPEC` | (unset) | Optional pinned npm package spec (for example, `@openai/codex@0.148.0`) installed inside each fresh remote lease when its Codex harness is not baked into the sandbox image. Mutually exclusive with `PAPERCLIP_RUNNER_REMOTE_CODEX_PATH`; Paperclip verifies the installed executable before starting `runnerd`. | +| `PAPERCLIP_RUNNER_REMOTE_CODEX_NPM_SPEC` | (unset) | Optional pinned npm package spec (for example, `@openai/codex@0.153.4`) installed inside each fresh remote lease when its Codex harness is not baked into the sandbox image. Mutually exclusive with `PAPERCLIP_RUNNER_REMOTE_CODEX_PATH`; Paperclip verifies the installed executable before starting `runnerd`. | | `PAPERCLIP_RUNNER_REMOTE_PROVIDER_PACK_PATH` | (unset) | Host-local path to the immutable provider pack built by `pnpm --filter @paperclipai/paperclip-runner build:provider-pack`. The pack includes its target-built Node 24.11 runtime, locked production dependencies, OpenCode proxy/executable, and ACPX sidecar. Remote OpenCode and ACPX fail closed without it. A preinstalled pack is accepted only when its complete digested manifest matches this build-owned pack; otherwise Paperclip stages this pack into the sandbox. | | `PAPERCLIP_HIDDEN_SETTINGS` | (unset) | Comma-separated settings surfaces to hide from the UI and floor at the API, for operators hosting Paperclip for others (managed cloud, internal shared server). See [Hiding settings surfaces](#hiding-settings-surfaces). | | `PAPERCLIP_SETTING_DEFAULTS` | (unset) | JSON object replacing the schema default of selected instance settings, for hosting operators. See [Operator setting defaults](#operator-setting-defaults). | diff --git a/package.json b/package.json index 3a27287be9..309517bd71 100644 --- a/package.json +++ b/package.json @@ -107,6 +107,8 @@ "@agentclientprotocol/codex-acp@1.6.2": "patches/@agentclientprotocol__codex-acp@1.6.2.patch" }, "overrides": { + "@agentclientprotocol/codex-acp@1.6.2>@openai/codex": "0.153.4", + "@agentclientprotocol/claude-agent-acp@0.70.0>@anthropic-ai/claude-agent-sdk": "0.3.263", "rollup": ">=4.59.0", "react": "^19.2.8", "react-dom": "^19.2.8" diff --git a/packages/paperclip-runner/package.json b/packages/paperclip-runner/package.json index 030fc51505..16941733a5 100644 --- a/packages/paperclip-runner/package.json +++ b/packages/paperclip-runner/package.json @@ -158,10 +158,11 @@ "dependencies": { "@agentclientprotocol/claude-agent-acp": "0.70.0", "@agentclientprotocol/codex-acp": "1.6.2", + "@openai/codex": "0.153.4", "acpx": "0.13.1", "ajv": "^8.20.0", "json-schema-to-ts": "^3.1.1", - "opencode-ai": "1.18.17", + "opencode-ai": "1.18.29", "react-markdown": "^10.1.0", "remark-gfm": "^4.0.1" }, diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/acpx_provider_backend.rs b/packages/paperclip-runner/runner/crates/runner-core/src/acpx_provider_backend.rs index 92e7323b49..ca259d30d2 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/src/acpx_provider_backend.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/src/acpx_provider_backend.rs @@ -146,7 +146,7 @@ impl AcpxProviderDescriptor { "@agentclientprotocol/claude-agent-acp", "0.70.0", Some("@anthropic-ai/claude-agent-sdk"), - Some("0.3.232"), + Some("0.3.263"), "sha256:9d73d1f0f121fb96cc8badb28c22d5bff02d8582eb2e40360a81c189e1b9422a", ), "codex" => ( @@ -154,7 +154,7 @@ impl AcpxProviderDescriptor { "@agentclientprotocol/codex-acp", "1.6.2", Some("@openai/codex"), - Some("0.148.0"), + Some("0.153.4"), "sha256:7a923b3829884d3cabcc9659d22cace3f86813e7bfffc90974b10140a45bc400", ), "pi" => return Err(DurableRunnerError::invalid( @@ -1601,7 +1601,7 @@ mod tests { "@agentclientprotocol/claude-agent-acp", "0.70.0", json!("@anthropic-ai/claude-agent-sdk"), - json!("0.3.232"), + json!("0.3.263"), "sha256:9d73d1f0f121fb96cc8badb28c22d5bff02d8582eb2e40360a81c189e1b9422a", ) } else { @@ -1610,7 +1610,7 @@ mod tests { "@agentclientprotocol/codex-acp", "1.6.2", json!("@openai/codex"), - json!("0.148.0"), + json!("0.153.4"), "sha256:7a923b3829884d3cabcc9659d22cace3f86813e7bfffc90974b10140a45bc400", ) }; diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/codex_provider.rs b/packages/paperclip-runner/runner/crates/runner-core/src/codex_provider.rs index b4ec2944f0..ee04c64c96 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/src/codex_provider.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/src/codex_provider.rs @@ -24,7 +24,7 @@ use crate::qualified_launch::verify_launch_artifact; use crate::question_response::validate_question_response; pub const CODEX_APP_SERVER_MAX_FRAME_BYTES: usize = 4 * 1024 * 1024; -const QUALIFIED_OPENCODE_VERSION: &str = "1.18.17"; +const QUALIFIED_OPENCODE_VERSION: &str = "1.18.29"; const DEFAULT_PROVIDER_TRACE_MAX_BYTES: usize = 64 * 1024 * 1024; const MAX_BUFFERED_MESSAGES: usize = 1_024; const MAX_BUFFERED_MESSAGE_BYTES: usize = 16 * 1024 * 1024; diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/provider_backend.rs b/packages/paperclip-runner/runner/crates/runner-core/src/provider_backend.rs index 9b2aab8c63..3d6be65aef 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/src/provider_backend.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/src/provider_backend.rs @@ -3267,7 +3267,7 @@ mod tests { CodexProviderConfig { provider: "opencode".to_owned(), driver: "opencode_server".to_owned(), - provider_version: "1.18.17".to_owned(), + provider_version: "1.18.29".to_owned(), command: PathBuf::from("node"), args: Vec::new(), cwd: std::env::current_dir() @@ -3495,7 +3495,7 @@ mod tests { CodexProviderConfig { provider: "opencode".to_owned(), driver: "opencode_server".to_owned(), - provider_version: "1.18.17".to_owned(), + provider_version: "1.18.29".to_owned(), command: PathBuf::from("node"), args: Vec::new(), cwd: std::env::current_dir() diff --git a/packages/paperclip-runner/runner/crates/runner-core/tests/native_provider_backend.rs b/packages/paperclip-runner/runner/crates/runner-core/tests/native_provider_backend.rs index ba36834ab7..3c2f37c674 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/tests/native_provider_backend.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/tests/native_provider_backend.rs @@ -144,7 +144,7 @@ fn prepare_payload(directory: &Path, agent: &str) -> Value { fn prepare_payload_with_mode(directory: &Path, agent: &str, mode: &str) -> Value { let operations = Vec::new(); let (runtime_package, runtime_version) = if agent == "codex" { - (json!("@openai/codex"), json!("0.148.0")) + (json!("@openai/codex"), json!("0.153.4")) } else { (Value::Null, Value::Null) }; @@ -240,7 +240,7 @@ fn opencode_prepare_payload(directory: &Path) -> Value { "kind": "opencode", "provider": "opencode", "driver": "opencode_server", - "providerVersion": "1.18.17", + "providerVersion": "1.18.29", "command": directory.join("qualified-opencode-proxy-command"), "args": [directory.join("qualified-opencode-proxy-script")], "cwd": directory, diff --git a/packages/paperclip-runner/scripts/build-provider-pack.mjs b/packages/paperclip-runner/scripts/build-provider-pack.mjs index b616674819..c9648a1ae5 100644 --- a/packages/paperclip-runner/scripts/build-provider-pack.mjs +++ b/packages/paperclip-runner/scripts/build-provider-pack.mjs @@ -9,12 +9,14 @@ import { readdirSync, readFileSync, readlinkSync, + realpathSync, renameSync, rmSync, writeFileSync, } from "node:fs"; import { tmpdir } from "node:os"; -import { dirname, join, resolve } from "node:path"; +import { dirname, join, relative, resolve } from "node:path"; +import { createRequire } from "node:module"; import { fileURLToPath } from "node:url"; const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), ".."); @@ -127,6 +129,15 @@ try { throw new Error(`pnpm deploy failed with exit code ${deployed.status}`); } + // Fail the image build if a bridge silently brings back an older/private + // provider CLI. A direct dependency alone does not deduplicate pnpm's graph. + const packRequire = createRequire(join(temporaryRoot, "package.json")); + const codexAcpRequire = createRequire(packRequire.resolve("@agentclientprotocol/codex-acp/package.json")); + if (realpathSync(codexAcpRequire.resolve("@openai/codex/package.json")) !== + realpathSync(packRequire.resolve("@openai/codex/package.json"))) { + throw new Error("Codex ACP must share the image's Codex installation"); + } + // Reuse the already-qualified build interpreter instead of introducing a // package-manager lifecycle hook or a second binary supply chain. The pack // manifest binds the copied bytes, platform, architecture, and minimum @@ -158,6 +169,23 @@ try { // every build and leaks a nonexistent host path after relocation. Replace // every provider-facing shim with a pack-relative launcher that always uses // the pinned Node executable owned by this pack. + // The image exposes these same installations to every adapter. Never add a + // separate global/runner-only CLI version; refresh these packages and their + // qualification digests together to the latest stable releases. + writePortableNodeShim("codex", "@openai/codex/bin/codex.js"); + const claudeAcpRequire = createRequire( + packRequire.resolve("@agentclientprotocol/claude-agent-acp/package.json"), + ); + // Use the ACP bridge's SDK dependency directly, avoiding a second peer- + // resolved SDK installation just to expose its CLI on the global PATH. + const sdkRequire = createRequire(claudeAcpRequire.resolve("@anthropic-ai/claude-agent-sdk")); + const claudeExecutable = sdkRequire.resolve( + `@anthropic-ai/claude-agent-sdk-${process.platform}-${process.arch}/claude`, + ); + writePortableExecutableShim( + "claude", + relative(realpathSync(join(temporaryRoot, "node_modules")), realpathSync(claudeExecutable)), + ); writePortableExecutableShim("node", "node/bin/node"); writePortableExecutableShim("opencode", "opencode-ai/bin/opencode.exe"); writePortableNodeShim("acpx", "acpx/dist/cli.js"); @@ -262,8 +290,8 @@ try { const payload = { pins: { nodeMinimum: minimumNodeVersion.join("."), - codex: "0.148.0", - opencode: "1.18.17", + codex: "0.153.4", + opencode: "1.18.29", acpx: "0.13.1", claudeAcp: "0.70.0", codexAcp: "1.6.2", diff --git a/packages/paperclip-runner/scripts/materialize-opencode-binary.mjs b/packages/paperclip-runner/scripts/materialize-opencode-binary.mjs index 37cb04a85a..f4be2ea8ab 100644 --- a/packages/paperclip-runner/scripts/materialize-opencode-binary.mjs +++ b/packages/paperclip-runner/scripts/materialize-opencode-binary.mjs @@ -13,7 +13,7 @@ import { import { dirname, join, resolve } from "node:path"; import { pathToFileURL } from "node:url"; -const OPENCODE_VERSION = "1.18.17"; +const OPENCODE_VERSION = "1.18.29"; const BASELINE_PACKAGE = "opencode-linux-x64-baseline"; function readPackage(path) { diff --git a/packages/paperclip-runner/scripts/materialize-opencode-binary.test.mjs b/packages/paperclip-runner/scripts/materialize-opencode-binary.test.mjs index b6f7bbfe7d..4bae1e206e 100644 --- a/packages/paperclip-runner/scripts/materialize-opencode-binary.test.mjs +++ b/packages/paperclip-runner/scripts/materialize-opencode-binary.test.mjs @@ -35,14 +35,14 @@ async function fixture(options = {}) { join(packageRoot, "package.json"), JSON.stringify({ name: "opencode-ai", - version: options.packageVersion ?? "1.18.17", + version: options.packageVersion ?? "1.18.29", }), ), writeFile( join(baselineRoot, "package.json"), JSON.stringify({ name: "opencode-linux-x64-baseline", - version: options.baselineVersion ?? "1.18.17", + version: options.baselineVersion ?? "1.18.29", }), ), writeFile(join(packageRoot, "bin", "opencode.exe"), "sentinel\n"), @@ -50,11 +50,11 @@ async function fixture(options = {}) { const source = join(baselineRoot, "bin", "opencode"); if (options.symlinkSource) { const realSource = join(root, "real-opencode"); - await writeFile(realSource, "#!/bin/sh\necho 1.18.17\n"); + await writeFile(realSource, "#!/bin/sh\necho 1.18.29\n"); await chmod(realSource, 0o755); await symlink(realSource, source); } else { - await writeFile(source, "#!/bin/sh\necho 1.18.17\n"); + await writeFile(source, "#!/bin/sh\necho 1.18.29\n"); await chmod(source, 0o755); } return packageRoot; @@ -67,7 +67,7 @@ test("materializes the pinned baseline executable with a verified version", asyn platform: "linux", architecture: "x64", }); - assert.equal(result.version, "1.18.17"); + assert.equal(result.version, "1.18.29"); assert.match(result.sourceDigest, /^[0-9a-f]{64}$/); }); @@ -80,7 +80,7 @@ test("refuses version, file-type, and platform drift", async () => { platform: "linux", architecture: "x64", }), - /Expected opencode-linux-x64-baseline@1\.18\.17/, + /Expected opencode-linux-x64-baseline@1\.18\.29/, ); const symlinkSource = await fixture({ symlinkSource: true }); diff --git a/packages/paperclip-runner/scripts/runner-protocol-eval-campaign.test.mjs b/packages/paperclip-runner/scripts/runner-protocol-eval-campaign.test.mjs index 26ca75ea07..ef1874faea 100644 --- a/packages/paperclip-runner/scripts/runner-protocol-eval-campaign.test.mjs +++ b/packages/paperclip-runner/scripts/runner-protocol-eval-campaign.test.mjs @@ -34,7 +34,7 @@ async function fixture() { provider: "opencode", driver: "opencode_server", model: "openrouter/example/model", - opencodeVersion: "1.18.17", + opencodeVersion: "1.18.29", }; const evalCase = { schema: "paperclip-runner/eval-case/v1", diff --git a/packages/paperclip-runner/src/backends/codex-native-backend.ts b/packages/paperclip-runner/src/backends/codex-native-backend.ts index bde981c261..fa06ca094e 100644 --- a/packages/paperclip-runner/src/backends/codex-native-backend.ts +++ b/packages/paperclip-runner/src/backends/codex-native-backend.ts @@ -66,7 +66,7 @@ function transportDriverIdentity(input: NativeExecutionInput): { return { kind: "opencode_server", displayName: "OpenCode server", - version: "1.18.17", + version: "1.18.29", }; case "claude_managed": return { diff --git a/packages/paperclip-runner/src/backends/native-backend-factory.test.ts b/packages/paperclip-runner/src/backends/native-backend-factory.test.ts index 545777babc..3f9ffb18b6 100644 --- a/packages/paperclip-runner/src/backends/native-backend-factory.test.ts +++ b/packages/paperclip-runner/src/backends/native-backend-factory.test.ts @@ -101,7 +101,7 @@ function acpxExecution( ? "@openai/codex" : "@anthropic-ai/claude-agent-sdk", agentRuntimeVersion: - agent === "pi" ? "0.84.2" : agent === "codex" ? "0.148.0" : "0.3.232", + agent === "pi" ? "0.84.2" : agent === "codex" ? "0.153.4" : "0.3.263", commandDigest: agent === "codex" ? "sha256:7a923b3829884d3cabcc9659d22cace3f86813e7bfffc90974b10140a45bc400" @@ -266,7 +266,7 @@ describe("native backend factory", () => { await expect(backend.descriptor()).resolves.toMatchObject({ kind: "runner", name: "opencode_server", - version: "1.18.17", + version: "1.18.29", capabilities: { steering: false, resume: true, @@ -418,7 +418,7 @@ describe("native backend factory", () => { await expect(backend.descriptor()).resolves.toMatchObject({ kind: "runner", name: "opencode_server", - version: "1.18.17", + version: "1.18.29", capabilities: { resume: true, interruption: true, diff --git a/packages/paperclip-runner/src/cli/eval-session.ts b/packages/paperclip-runner/src/cli/eval-session.ts index 7aadfd3fd2..c88c047920 100644 --- a/packages/paperclip-runner/src/cli/eval-session.ts +++ b/packages/paperclip-runner/src/cli/eval-session.ts @@ -159,9 +159,9 @@ export function evalSessionProviderVersion( request: EvalSessionRequest, ): string | null { if (request.provider === "opencode") { - const version = request.opencodeVersion ?? "1.18.17"; - if (version !== "1.18.17") { - throw new Error(`OpenCode evals require exact version 1.18.17; received ${version}`); + const version = request.opencodeVersion ?? "1.18.29"; + if (version !== "1.18.29") { + throw new Error(`OpenCode evals require exact version 1.18.29; received ${version}`); } return version; } diff --git a/packages/paperclip-runner/src/cli/opencode-app-server-proxy.ts b/packages/paperclip-runner/src/cli/opencode-app-server-proxy.ts index 45721a1191..1b1ba26ccd 100644 --- a/packages/paperclip-runner/src/cli/opencode-app-server-proxy.ts +++ b/packages/paperclip-runner/src/cli/opencode-app-server-proxy.ts @@ -331,7 +331,7 @@ async function handle(message: RpcMessage): Promise { case "initialize": result = { user: { sessionId: "opencode" }, - serverInfo: { name: "opencode", version: "1.18.17" }, + serverInfo: { name: "opencode", version: "1.18.29" }, }; break; case "thread/start": diff --git a/packages/paperclip-runner/src/drivers/acpx/installation-integrity.test.ts b/packages/paperclip-runner/src/drivers/acpx/installation-integrity.test.ts index cecf96be27..cb605c8949 100644 --- a/packages/paperclip-runner/src/drivers/acpx/installation-integrity.test.ts +++ b/packages/paperclip-runner/src/drivers/acpx/installation-integrity.test.ts @@ -429,7 +429,7 @@ describe("ACPX installation integrity", () => { }, { name: "@anthropic-ai/claude-agent-sdk", - version: "0.3.232", + version: "0.3.263", directory: join(dependencyRoot, "claude-agent-sdk"), }, { @@ -457,7 +457,7 @@ describe("ACPX installation integrity", () => { bin: "bin/server.js", dependencies: { "@agentclientprotocol/sdk": "1.3.0", - "@anthropic-ai/claude-agent-sdk": "0.3.232", + "@anthropic-ai/claude-agent-sdk": "0.3.263", zod: "^3.25.0 || ^4.0.0", }, }), @@ -528,7 +528,7 @@ describe("ACPX installation integrity", () => { bin: "bin/server.js", dependencies: { "@agentclientprotocol/sdk": "1.3.0", - "@anthropic-ai/claude-agent-sdk": "0.3.232", + "@anthropic-ai/claude-agent-sdk": "0.3.263", zod: "^3.25.0 || ^4.0.0", }, }), diff --git a/packages/paperclip-runner/src/drivers/acpx/installation-integrity.ts b/packages/paperclip-runner/src/drivers/acpx/installation-integrity.ts index becb0b496c..ef2f72a926 100644 --- a/packages/paperclip-runner/src/drivers/acpx/installation-integrity.ts +++ b/packages/paperclip-runner/src/drivers/acpx/installation-integrity.ts @@ -45,25 +45,25 @@ const VERIFIED_PROVIDER_RUNTIME_TARGET_ENV = const QUALIFIED_CLAUDE_LINUX_X64_RUNTIME = Object.freeze({ runtimePackageName: "@anthropic-ai/claude-agent-sdk", - runtimePackageVersion: "0.3.232", + runtimePackageVersion: "0.3.263", packageName: "@anthropic-ai/claude-agent-sdk-linux-x64", - packageVersion: "0.3.232", - dependencyDeclaration: "0.3.232", + packageVersion: "0.3.263", + dependencyDeclaration: "0.3.263", relativeExecutable: "claude", executableDigest: - "sha256:61d23f8749136907d586d5b11831ea8a5234d4c1dea40a5e55c33b52e204c6d1", + "sha256:26d020351e8112f4006790f3cfce43b4c9df0c1bb1d0e542364d64151b81d5ba", environmentVariable: "CLAUDE_CODE_EXECUTABLE", }); const QUALIFIED_CODEX_LINUX_X64_RUNTIME = Object.freeze({ runtimePackageName: "@openai/codex", - runtimePackageVersion: "0.148.0", + runtimePackageVersion: "0.153.4", packageName: "@openai/codex-linux-x64", - packageVersion: "0.148.0-linux-x64", - dependencyDeclaration: "npm:@openai/codex@0.148.0-linux-x64", + packageVersion: "0.153.4-linux-x64", + dependencyDeclaration: "npm:@openai/codex@0.153.4-linux-x64", relativeExecutable: "vendor/x86_64-unknown-linux-musl/bin/codex", executableDigest: - "sha256:ac2cfed85fb647d61e0150b8548102b330e4799d9d81ad5d354de701edf6b074", + "sha256:56ef98ab4032d317ab26e9b5e5a175650717351edb16ed9cde0cb6d1734d62da", environmentVariable: "CODEX_PATH", }); @@ -79,8 +79,8 @@ const QUALIFIED_CLAUDE_PROVIDER_DEPENDENCIES = Object.freeze([ }), Object.freeze({ packageName: "@anthropic-ai/claude-agent-sdk", - packageVersion: "0.3.232", - dependencyDeclaration: "0.3.232", + packageVersion: "0.3.263", + dependencyDeclaration: "0.3.263", }), Object.freeze({ packageName: "zod", diff --git a/packages/paperclip-runner/src/drivers/acpx/qualified-profiles.test.ts b/packages/paperclip-runner/src/drivers/acpx/qualified-profiles.test.ts index 3b8fa9135c..09a79ac021 100644 --- a/packages/paperclip-runner/src/drivers/acpx/qualified-profiles.test.ts +++ b/packages/paperclip-runner/src/drivers/acpx/qualified-profiles.test.ts @@ -27,14 +27,14 @@ describe("qualified ACPX profiles", () => { it("binds Codex ACP to the CLI runtime it launches", () => { expect(QUALIFIED_ACPX_PROFILES.codex).toMatchObject({ agentRuntimePackage: "@openai/codex", - agentRuntimeVersion: "0.148.0", + agentRuntimeVersion: "0.153.4", }); }); it("binds Claude ACP to the SDK and native CLI runtime it launches", () => { expect(QUALIFIED_ACPX_PROFILES.claude).toMatchObject({ agentRuntimePackage: "@anthropic-ai/claude-agent-sdk", - agentRuntimeVersion: "0.3.232", + agentRuntimeVersion: "0.3.263", }); }); }); diff --git a/packages/paperclip-runner/src/drivers/acpx/qualified-profiles.ts b/packages/paperclip-runner/src/drivers/acpx/qualified-profiles.ts index b953e19f5f..d851031398 100644 --- a/packages/paperclip-runner/src/drivers/acpx/qualified-profiles.ts +++ b/packages/paperclip-runner/src/drivers/acpx/qualified-profiles.ts @@ -64,7 +64,7 @@ export const QUALIFIED_ACPX_PROFILES: Readonly< agentServerPackage: "@agentclientprotocol/claude-agent-acp", agentServerVersion: "0.70.0", agentRuntimePackage: "@anthropic-ai/claude-agent-sdk", - agentRuntimeVersion: "0.3.232", + agentRuntimeVersion: "0.3.263", commandDigest: "sha256:9d73d1f0f121fb96cc8badb28c22d5bff02d8582eb2e40360a81c189e1b9422a", qualificationModel: "claude-sonnet-5", @@ -80,7 +80,7 @@ export const QUALIFIED_ACPX_PROFILES: Readonly< agentServerPackage: "@agentclientprotocol/codex-acp", agentServerVersion: "1.6.2", agentRuntimePackage: "@openai/codex", - agentRuntimeVersion: "0.148.0", + agentRuntimeVersion: "0.153.4", commandDigest: "sha256:7a923b3829884d3cabcc9659d22cace3f86813e7bfffc90974b10140a45bc400", qualificationModel: "gpt-5.6-sol", diff --git a/packages/paperclip-runner/src/drivers/opencode/opencode-server-driver.test.ts b/packages/paperclip-runner/src/drivers/opencode/opencode-server-driver.test.ts index 2c4aca9107..b3452ab6c8 100644 --- a/packages/paperclip-runner/src/drivers/opencode/opencode-server-driver.test.ts +++ b/packages/paperclip-runner/src/drivers/opencode/opencode-server-driver.test.ts @@ -308,7 +308,7 @@ describe("OpenCodeServerDriver", () => { headers: { "Content-Type": "application/json" }, }); if (url.pathname === "/global/health") - return json({ healthy: true, version: "1.18.17" }); + return json({ healthy: true, version: "1.18.29" }); if (url.pathname === "/event") { return new Response( new ReadableStream({ @@ -680,7 +680,7 @@ describe("OpenCodeServerDriver", () => { output: 2, costUsd: 0.001, provider: "openrouter", - driverVersion: "1.18.17", + driverVersion: "1.18.29", }); await session.interrupt?.({ turnId: turn.turnId }); const snapshot = await session.snapshot(); diff --git a/packages/paperclip-runner/src/drivers/opencode/opencode-server-driver.ts b/packages/paperclip-runner/src/drivers/opencode/opencode-server-driver.ts index a764d7b605..6e309cc09c 100644 --- a/packages/paperclip-runner/src/drivers/opencode/opencode-server-driver.ts +++ b/packages/paperclip-runner/src/drivers/opencode/opencode-server-driver.ts @@ -73,7 +73,7 @@ import { nativeMcpLaunchBinding } from "../native-mcp.js"; import { materializeNativeRuntimeSkills } from "../runtime-context-materializer.js"; export const OPENCODE_SERVER_DRIVER_KIND = "opencode_server" as const; -export const QUALIFIED_OPENCODE_VERSION = "1.18.17" as const; +export const QUALIFIED_OPENCODE_VERSION = "1.18.29" as const; export const QUALIFIED_OPENCODE_MODEL = "openrouter/deepseek/deepseek-v4-flash-0731" as const; diff --git a/packages/paperclip-runner/src/live/live-session.test.ts b/packages/paperclip-runner/src/live/live-session.test.ts index da740a6d1a..5018df90b5 100644 --- a/packages/paperclip-runner/src/live/live-session.test.ts +++ b/packages/paperclip-runner/src/live/live-session.test.ts @@ -656,7 +656,7 @@ describe("Capability live runnerd and Codex session", () => { expect(session.snapshot().config).toMatchObject({ provider: "opencode", driver: "opencode_server", - providerVersion: "1.18.17", + providerVersion: "1.18.29", requestedModel: "openrouter/deepseek/deepseek-v4-flash-0731", }); await service.shutdown(session.id); diff --git a/packages/paperclip-runner/src/live/live-session.ts b/packages/paperclip-runner/src/live/live-session.ts index 88ded8caf3..8cc9c15c2a 100644 --- a/packages/paperclip-runner/src/live/live-session.ts +++ b/packages/paperclip-runner/src/live/live-session.ts @@ -924,7 +924,7 @@ export class CapabilityLiveSessionService { ? "aws_agentcore_harness_api" : input.provider === "acpx" ? "acpx_runtime" : "codex_app_server", providerVersion: input.provider === "opencode" - ? "1.18.17" + ? "1.18.29" : input.provider === "claude_managed" ? input.managedProfile!.agentVersion : input.provider === "aws_agentcore" diff --git a/packages/paperclip-runner/src/live/runnerd-codex-transport.ts b/packages/paperclip-runner/src/live/runnerd-codex-transport.ts index 420d304d00..92ccd17a15 100644 --- a/packages/paperclip-runner/src/live/runnerd-codex-transport.ts +++ b/packages/paperclip-runner/src/live/runnerd-codex-transport.ts @@ -3160,7 +3160,7 @@ class DurablePrpCodexTransport implements CodexAppServerTransport { ? "opencode_server" : "codex_app_server", providerVersion: - provider === "opencode" ? "1.18.17" : "codex-app-server-v1", + provider === "opencode" ? "1.18.29" : "codex-app-server-v1", command: provider === "opencode" ? providerNodeCommand diff --git a/packages/paperclip-runner/test/acpx-codex-package-contract.test.mjs b/packages/paperclip-runner/test/acpx-codex-package-contract.test.mjs index abc5978a35..9ea9df7dfb 100644 --- a/packages/paperclip-runner/test/acpx-codex-package-contract.test.mjs +++ b/packages/paperclip-runner/test/acpx-codex-package-contract.test.mjs @@ -54,7 +54,10 @@ const nativeSessionExecutor = await readFile( ); test("the runner pins every qualified ACPX production dependency", () => { - assert.equal(runnerPackage.dependencies["@openai/codex"], undefined); + assert.equal(runnerPackage.dependencies["@openai/codex"], "0.153.4"); + assert.equal(runnerPackage.dependencies["@anthropic-ai/claude-agent-sdk"], undefined); + assert.equal(rootPackage.pnpm.overrides["@agentclientprotocol/codex-acp@1.6.2>@openai/codex"], runnerPackage.dependencies["@openai/codex"]); + assert.equal(rootPackage.pnpm.overrides["@agentclientprotocol/claude-agent-acp@0.70.0>@anthropic-ai/claude-agent-sdk"], "0.3.263"); assert.equal(runnerPackage.optionalDependencies, undefined); assert.equal(runnerPackage.dependencies.node, undefined); assert.equal(runnerPackage.dependencies.acpx, "0.13.1"); @@ -131,7 +134,7 @@ test("old and new pnpm configuration both apply the exact runtime patches", () = providerPackBuilder, /copyFileSync\(process\.execPath, stableNodeCommand\)/, ); - assert.match(codexPatch, /\+ "@openai\/codex": "0\.148\.0"/); + assert.match(codexPatch, /\+ "@openai\/codex": "0\.153\.4"/); }); test("the ACPX patch preserves launch-only state and verified spawning", () => { diff --git a/packages/paperclip-runner/test/fixtures/fake-opencode-server.mjs b/packages/paperclip-runner/test/fixtures/fake-opencode-server.mjs index ee93ebdd66..787ff49765 100644 --- a/packages/paperclip-runner/test/fixtures/fake-opencode-server.mjs +++ b/packages/paperclip-runner/test/fixtures/fake-opencode-server.mjs @@ -214,7 +214,7 @@ const server = createServer(async (request, response) => { if (request.headers.authorization !== expectedAuth) return json(response, 401, { error: "unauthorized" }); if (request.url === "/global/health") - return json(response, 200, { healthy: true, version: "1.18.17" }); + return json(response, 200, { healthy: true, version: "1.18.29" }); if (request.url === "/event") { eventConnections += 1; response.writeHead(200, { diff --git a/patches/@agentclientprotocol__claude-agent-acp@0.70.0.patch b/patches/@agentclientprotocol__claude-agent-acp@0.70.0.patch index dcdcd3bcdd..f1d54bb069 100644 --- a/patches/@agentclientprotocol__claude-agent-acp@0.70.0.patch +++ b/patches/@agentclientprotocol__claude-agent-acp@0.70.0.patch @@ -49,3 +49,13 @@ diff --git a/dist/acp-agent.js b/dist/acp-agent.js ...mcpServers, ...(fileChangeAuditSupport ? { [FILE_CHANGE_AUDIT_SERVER_NAME]: fileChangeAuditSupport.mcpServer } +diff --git a/package.json b/package.json +--- a/package.json ++++ b/package.json +@@ -65,5 +65,5 @@ + "dependencies": { + "@agentclientprotocol/sdk": "1.3.0", +- "@anthropic-ai/claude-agent-sdk": "0.3.232", ++ "@anthropic-ai/claude-agent-sdk": "0.3.263", + "zod": "^3.25.0 || ^4.0.0" + }, diff --git a/patches/@agentclientprotocol__codex-acp@1.6.2.patch b/patches/@agentclientprotocol__codex-acp@1.6.2.patch index efe631113b..445f6d423e 100644 --- a/patches/@agentclientprotocol__codex-acp@1.6.2.patch +++ b/patches/@agentclientprotocol__codex-acp@1.6.2.patch @@ -6,7 +6,7 @@ diff --git a/package.json b/package.json "dependencies": { "@agentclientprotocol/sdk": "^1.3.0", - "@openai/codex": "^0.148.0", -+ "@openai/codex": "0.148.0", ++ "@openai/codex": "0.153.4", "diff": "^9.0.0", "open": "^11.0.0", "vscode-jsonrpc": "^9.0.1", diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 9723d1c65f..929bd5f8b4 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -22,3 +22,12 @@ patchedDependencies: "@agentclientprotocol/claude-agent-acp@0.70.0": patches/@agentclientprotocol__claude-agent-acp@0.70.0.patch "@agentclientprotocol/claude-agent-acp@0.73.0": patches/@agentclientprotocol__claude-agent-acp@0.73.0.patch "@agentclientprotocol/codex-acp@1.6.2": patches/@agentclientprotocol__codex-acp@1.6.2.patch + +# Agent CLIs share the current runtime used by the native provider pack. +# pnpm patches change package files, but overrides control dependency resolution. +overrides: + "@agentclientprotocol/codex-acp@1.6.2>@openai/codex": "0.153.4" + "@agentclientprotocol/claude-agent-acp@0.70.0>@anthropic-ai/claude-agent-sdk": "0.3.263" + rollup: ">=4.59.0" + react: "^19.2.8" + react-dom: "^19.2.8" diff --git a/server/src/__tests__/adapter-registry.test.ts b/server/src/__tests__/adapter-registry.test.ts index c9e61f2e1f..e47f818f3d 100644 --- a/server/src/__tests__/adapter-registry.test.ts +++ b/server/src/__tests__/adapter-registry.test.ts @@ -312,8 +312,8 @@ describe("server adapter registry", () => { const expectedCodexInstall = `if ! command -v 'codex' >/dev/null 2>&1; then ${buildSandboxNpmInstallCommand("@openai/codex")}; fi`; const expectedGeminiInstall = `if ! command -v 'gemini' >/dev/null 2>&1; then ${buildSandboxNpmInstallCommand("@google/gemini-cli")}; fi`; const expectedOpenCodeInstall = `if ! command -v 'opencode' >/dev/null 2>&1; then ${buildSandboxNpmInstallCommand("opencode-ai")}; fi`; - const expectedRunnerCodexInstall = `if ! command -v 'codex' >/dev/null 2>&1; then ${buildSandboxNpmInstallCommand("@openai/codex@0.148.0")}; fi`; - const expectedRunnerOpenCodeInstall = `if ! command -v 'opencode' >/dev/null 2>&1; then ${buildSandboxNpmInstallCommand("opencode-ai@1.18.17")}; fi`; + const expectedRunnerCodexInstall = `if ! command -v 'codex' >/dev/null 2>&1; then ${buildSandboxNpmInstallCommand("@openai/codex@0.153.4")}; fi`; + const expectedRunnerOpenCodeInstall = `if ! command -v 'opencode' >/dev/null 2>&1; then ${buildSandboxNpmInstallCommand("opencode-ai@1.18.29")}; fi`; expect(findActiveServerAdapter("claude_local")?.getRuntimeCommandSpec?.({})).toEqual({ command: "claude", diff --git a/server/src/adapters/registry.ts b/server/src/adapters/registry.ts index f303cce14d..86791b6933 100644 --- a/server/src/adapters/registry.ts +++ b/server/src/adapters/registry.ts @@ -486,7 +486,7 @@ const paperclipRunnerAdapter: ServerAdapterModule = { "opencode", `opencode-ai@${QUALIFIED_OPENCODE_RUNNER_VERSION}`, ) - : buildNpmRuntimeCommandSpec(config, "codex", "@openai/codex@0.148.0"), + : buildNpmRuntimeCommandSpec(config, "codex", "@openai/codex@0.153.4"), agentConfigurationDoc: "# Paperclip Runner\n\nAdapter: paperclip_runner\n\nRuns Codex, OpenCode, Claude Managed, AWS AgentCore, or a qualified Claude/Codex ACP agent through the Rust Paperclip runner and authenticated PRP transport. Pi is not available through the qualified ACPX profile. Managed providers use company-scoped qualified profiles, explicit retention acknowledgement, and spend limits.\n", getConfigSchema: () => ({ diff --git a/server/src/services/native-runtime/native-session-executor.test.ts b/server/src/services/native-runtime/native-session-executor.test.ts index 686001f88f..df9910fc49 100644 --- a/server/src/services/native-runtime/native-session-executor.test.ts +++ b/server/src/services/native-runtime/native-session-executor.test.ts @@ -624,8 +624,8 @@ describe("remote provider pack manifest", () => { const payload = { pins: { nodeMinimum: "24.11.0", - codex: "0.148.0", - opencode: "1.18.17", + codex: "0.153.4", + opencode: "1.18.29", acpx: "0.13.1", claudeAcp: "0.70.0", codexAcp: "1.6.2", @@ -682,7 +682,7 @@ describe("remote provider pack manifest", () => { ); await writeManifest(); expect(readRemoteProviderPackManifest(root).payload.pins.opencode).toBe( - "1.18.17", + "1.18.29", ); for (const [artifactName, substituteName] of [ ["nodeCommand", "productionLock"], @@ -6011,6 +6011,84 @@ describe("runnerd provider runtime wiring", () => { ); }); + it("uses the image's shared Codex without uploading or installing artifacts", async () => { + const syncIn = vi.fn(async () => undefined); + const remoteExecute = vi.fn( + async (command: { command: string; args?: string[] }) => { + let stdout = ""; + const script = command.args?.[1] ?? ""; + if (command.args?.[0] === "--build-metadata") { + stdout = JSON.stringify({ + schema: "paperclip-runner/runnerd-build-metadata/v1", + binaryName: "paperclip-runnerd", + packageName: "@paperclipai/paperclip-runner", + binaryContractVersion: 2, + prpTransportModes: ["listen_ws"], + }); + } else if (command.args?.[0] === "--version") { + if ( + command.command.endsWith( + "/.paperclip-runtime/paperclip-runner/bin/codex", + ) + ) { + throw new Error("reached-preinstalled-codex-verification"); + } + stdout = "codex-cli 0.153.4"; + } else if (script.includes("command -v paperclip-runnerd")) { + stdout = "/usr/local/bin/paperclip-runnerd\n"; + } else if (script.includes("command -v codex")) { + stdout = script.includes("/opt/paperclip-runner/bin/codex") + ? "/opt/paperclip-runner/bin/codex\n" + : "/usr/local/bin/codex\n"; + } else if (!script.includes("ln -sfn")) { + throw new Error(`unexpected command: ${command.command}`); + } + return { + exitCode: 0, + signal: null, + timedOut: false, + stderr: "", + stdout, + }; + }, + ); + await createRunnerdBackend({ + db: leaseDb(execution), + execution, + runnerInstanceId: "runner-image-runtime", + runnerIngressAuthorized: true, + runnerExecutionTarget: { + kind: "remote", + transport: "sandbox", + remoteCwd: "/workspace", + environmentId: "environment", + leaseId: "lease", + providerKey: "daytona", + effectiveCapabilities: { runnerWebSocketIngress: true }, + runner: { execute: remoteExecute, syncIn }, + } as never, + }); + state.createTransport.mockClear(); + state.createBackend.mock.calls.at(-1)![1].codexTransportFactory!(); + const transport = state.createTransport.mock + .calls[0]![0] as RunnerTransportOptions & { + controlPlaneRegistration: (authority: unknown) => Promise; + }; + await expect(transport.controlPlaneRegistration({})).rejects.toThrow( + "reached-preinstalled-codex-verification", + ); + expect(syncIn).not.toHaveBeenCalled(); + expect(remoteExecute).toHaveBeenCalledWith( + expect.objectContaining({ + command: "/opt/paperclip-runner/bin/codex", + args: ["--version"], + }), + ); + expect( + remoteExecute.mock.calls.some(([call]) => call.command === "npm"), + ).toBe(false); + }); + it("binds a remote launch to the configured controller-owned runner artifact", async () => { const remoteCwd = "/home/daytona/paperclip-workspace"; const controllerArtifact = "/controller/artifacts/paperclip-runnerd"; diff --git a/server/src/services/native-runtime/native-session-executor.ts b/server/src/services/native-runtime/native-session-executor.ts index 80d850afbe..1639ded4eb 100644 --- a/server/src/services/native-runtime/native-session-executor.ts +++ b/server/src/services/native-runtime/native-session-executor.ts @@ -5036,8 +5036,8 @@ const RUNNERD_BINARY_CONTRACT_VERSION = 2; const REMOTE_PROVIDER_PACK_SCHEMA = "paperclip-runner/remote-provider-pack/v1"; const REMOTE_PROVIDER_PACK_PINS = { nodeMinimum: "24.11.0", - codex: "0.148.0", - opencode: "1.18.17", + codex: "0.153.4", + opencode: "1.18.29", acpx: "0.13.1", claudeAcp: "0.70.0", codexAcp: "1.6.2", @@ -6463,6 +6463,9 @@ async function createRunnerdBackendWithinSessionClaim( return parseRemoteExecutableCandidate(result.stdout); }; + // Image policy: keep one latest stable CLI installation shared by native and + // local adapters. Preferred bin entries must point to that same installation; + // never bake an older global CLI alongside a private runner-only version. const discoverPreinstalledExecutable = async ( name: "paperclip-runnerd" | "codex", ) => { @@ -6471,9 +6474,9 @@ async function createRunnerdBackendWithinSessionClaim( command: "sh", args: [ "-c", - `candidate="$HOME/.local/bin/${name}"; ` + - `if [ -x "$candidate" ]; then printf '%s\\n' "$candidate"; ` + - `else command -v ${name} 2>/dev/null || true; fi`, + `for candidate in /opt/paperclip-runner/bin/${name} "$HOME/.local/bin/${name}"; do ` + + `if [ -x "$candidate" ]; then printf '%s\\n' "$candidate"; exit 0; fi; done; ` + + `command -v ${name} 2>/dev/null || true`, ], cwd: remoteTarget.remoteCwd, bypassSession: true, diff --git a/server/src/services/native-runtime/provider-profile.ts b/server/src/services/native-runtime/provider-profile.ts index aa09ba455d..197f82f1f8 100644 --- a/server/src/services/native-runtime/provider-profile.ts +++ b/server/src/services/native-runtime/provider-profile.ts @@ -9,7 +9,7 @@ import { CLAUDE_MANAGED_QUALIFIED_MODEL, } from "../provider-profile-qualification.js"; -export const QUALIFIED_OPENCODE_RUNNER_VERSION = "1.18.17" as const; +export const QUALIFIED_OPENCODE_RUNNER_VERSION = "1.18.29" as const; export const DEFAULT_OPENCODE_RUNNER_MODEL = "openrouter/deepseek/deepseek-v4-flash-0731" as const; export const CLAUDE_MANAGED_BETA_VERSION = "managed-agents-2026-04-01" as const; diff --git a/ui/src/adapters/codex-local/config-fields.test.tsx b/ui/src/adapters/codex-local/config-fields.test.tsx index 72d32af7cc..62bc325b20 100644 --- a/ui/src/adapters/codex-local/config-fields.test.tsx +++ b/ui/src/adapters/codex-local/config-fields.test.tsx @@ -29,7 +29,7 @@ describe("Paperclip Runner Codex configuration", () => { const html = renderRunner({ provider: "codex" }); expect(html).toContain(''); - expect(html).toContain("OpenCode 1.18.17"); + expect(html).toContain("OpenCode 1.18.29"); expect(html).toContain("ACPX"); expect(html).toContain("Automatic (isolated)"); expect(html).not.toContain("Ask when requested"); @@ -46,7 +46,7 @@ describe("Paperclip Runner Codex configuration", () => { }); expect(html).toContain( - '', + '', ); expect(html).toContain( '', diff --git a/ui/src/adapters/codex-local/config-fields.tsx b/ui/src/adapters/codex-local/config-fields.tsx index 5ad85dc149..706e0df118 100644 --- a/ui/src/adapters/codex-local/config-fields.tsx +++ b/ui/src/adapters/codex-local/config-fields.tsx @@ -236,7 +236,7 @@ export function CodexLocalConfigFields({ }} > - +