From be6f49a425bef0500a61901fd9e52ce421429665 Mon Sep 17 00:00:00 2001 From: Devin Foley Date: Tue, 22 Sep 2026 17:02:29 -0700 Subject: [PATCH] feat(runner): refresh shared coding harness runtimes (#13838) ## Thinking Path > - Paperclip runs agents through local adapters and the native runner. > - Both paths must use the same installed provider CLI. > - New models require current harness releases. > - The runner still pins Codex 0.153.4, Claude SDK 0.3.263, and OpenCode 1.18.29. > - Changing the image alone would fail the runner's exact version and executable checks. > - This pull request updates those dependencies, integrity checks, controller checks, and image pins together. > - Shared installations can then run the current models without a task-time download. ## Linked Issues or Issue Description Refs #13829, which updates model choices and reasoning controls. Searches found no open PR that updates these runtime pins. **Current behavior** The shared provider pack ships old CLIs. Claude Code 2.1.263 cannot run Opus 5.5, which requires 2.1.280. Remote controllers reject provider packs whose versions differ from their declared pins. **Proposed behavior** Use Codex 0.156.0, Claude Agent SDK 0.3.280 / Claude Code 2.1.280, and OpenCode 1.18.32 throughout the runner. Keep the reviewed ACP bridge patches and one shared CLI installation per provider. **Reason and benefit** Current harnesses support the new model IDs while preserving executable verification and remote provider-pack compatibility checks. ## What Changed - Update dependency overrides, the Codex ACP package patch, runtime profiles, and remote controller pins. - Verify the new Claude Linux x64 and macOS arm64/x64 executables and Codex Linux x64 executable against integrity-verified npm archives. - Refresh OpenCode version checks, fixtures, and the runner configuration label. - Refresh the eval image's Grok, Gemini, Kimi, Cursor, and GitHub CLI pins and archive hashes. Hermes remains current at 0.19.0. - Refresh the build-time lock digest from clean pnpm 9.15.4 resolution. Leave lockfile commits to repository automation. - Document model compatibility and the separation between CLI runtimes and patched ACP bridges. ## Verification - `pnpm -r typecheck` and `pnpm build` passed. - Rust workspace release tests passed. - Package/patch and OpenCode binary-materialization contract tests: 11 passed. - Real Codex 0.156.0 startup-ownership and paginated session-resume probes passed with isolated synthetic homes and no model turn. - Codex app-server `thread/start` preserved `gpt-6-sol` and `gpt-6-luna`; no `turn/start` was sent. An unauthenticated built-in catalog does not include those account-served entries. - Installed Claude integrity probes passed for `claude-opus-5-5` and `claude-fable-5-1`. - `pnpm --filter @paperclipai/paperclip-runner test:opencode:qualification` passed with the actual OpenCode 1.18.32 executable under Node 24 and Node 25. The loopback provider exercise covers health/version, session creation/read/delete, SSE, and a completed async prompt. - `pnpm check:token-gates` passed. - The targeted runner suite passed 130 tests. Three macOS failures in snapshot module lookup and OpenCode final-message selection also reproduce on the unchanged base; Linux CI will provide the platform check. - [Final Linux CI](https://github.com/paperclipai/paperclip/actions/runs/35798076399): all gates passed. Four jobs needed one retry after their CI workers received shutdown signals. The PR has 55 successful checks, two skipped checks, Greptile 5/5, and no unresolved review threads. - Changed runner configuration UI tests: 5 passed. - Full macOS `pnpm test:run` reached 13,094 passing server tests, 84 skipped, and 18 failures before the wrapper stopped. Failures involved skill-cache publication permissions, missing bundled connector skills in the worktree, and a conversation-reset timing case. The 10 cache permission failures reproduce on the unchanged base; both conversation-reset cases passed on a targeted retry. The wrapper did not reach its later workspace/serialized groups locally; Linux CI covers those groups. - The local Docker daemon did not respond, so no local Docker build was run. No billable model requests were made. ## Risks - Deploy the matching controller and provider pack together. Older controllers enforce their previous exact pins. - Current upstream CLIs can change behavior. Existing protocol tests and isolated real Codex probes cover the integration boundaries; authenticated model inference is not part of these checks. - ACP bridge package versions and executable digests stay unchanged because their executable bytes are unchanged. Only the underlying CLI/SDK dependencies move. - No schema migration. Revert the runtime and image pins together to roll back. ## Model Used OpenAI GPT-6 via Codex, with repository tools, code execution, and web research. The exact serving model ID and context window were not exposed by this session. ## 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 for the changed surfaces and real-executable probes; full macOS-suite limitations are listed above - [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 --- docker/daytona-runner/Dockerfile | 19 ++- docker/daytona-runner/README.md | 28 +++- docs/deploy/environment-variables.md | 2 +- package.json | 4 +- packages/paperclip-runner/package.json | 7 +- .../runner-core/src/acpx_provider_backend.rs | 8 +- .../crates/runner-core/src/codex_provider.rs | 2 +- .../runner-core/src/provider_backend.rs | 4 +- .../runner-core/tests/acpx_warm_attachment.rs | 2 +- .../tests/native_provider_backend.rs | 4 +- .../scripts/build-provider-pack.mjs | 4 +- .../scripts/materialize-opencode-binary.mjs | 2 +- .../materialize-opencode-binary.test.mjs | 12 +- .../scripts/qualify-opencode-runtime.test.mjs | 146 ++++++++++++++++++ .../runner-protocol-eval-campaign.test.mjs | 2 +- .../src/backends/codex-native-backend.ts | 2 +- .../backends/native-backend-factory.test.ts | 6 +- .../paperclip-runner/src/cli/eval-session.ts | 6 +- .../src/cli/opencode-app-server-proxy.ts | 2 +- .../acpx/installation-integrity.test.ts | 2 +- .../drivers/acpx/installation-integrity.ts | 24 +-- .../drivers/acpx/qualified-profiles.test.ts | 6 +- .../src/drivers/acpx/qualified-profiles.ts | 4 +- .../opencode/opencode-server-driver.test.ts | 4 +- .../opencode/opencode-server-driver.ts | 2 +- .../src/live/live-session.test.ts | 2 +- .../paperclip-runner/src/live/live-session.ts | 2 +- .../src/live/runnerd-codex-transport.ts | 2 +- .../test/acpx-codex-package-contract.test.mjs | 6 +- .../test/fixtures/fake-opencode-server.mjs | 2 +- ...agentclientprotocol__codex-acp@1.6.2.patch | 2 +- pnpm-workspace.yaml | 4 +- scripts/runner-api-eval-worker.ts | 2 +- .../tests/native-cleanup-paginated-codex.mjs | 8 +- .../tests/native-provider-startup-codex.mjs | 4 +- server/src/__tests__/adapter-registry.test.ts | 4 +- server/src/adapters/registry.ts | 2 +- .../native-session-executor.test.ts | 14 +- .../native-runtime/native-session-executor.ts | 4 +- .../native-runtime/provider-profile.ts | 2 +- .../codex-local/config-fields.test.tsx | 4 +- ui/src/adapters/codex-local/config-fields.tsx | 2 +- 42 files changed, 273 insertions(+), 97 deletions(-) create mode 100644 packages/paperclip-runner/scripts/qualify-opencode-runtime.test.mjs diff --git a/docker/daytona-runner/Dockerfile b/docker/daytona-runner/Dockerfile index cff873d317..3f471f5f65 100644 --- a/docker/daytona-runner/Dockerfile +++ b/docker/daytona-runner/Dockerfile @@ -28,7 +28,7 @@ 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=4b796c312833ebf2be4c38228babc0292c76774fb40d43bd18b54bd6b205753d +ARG PAPERCLIP_RUNNER_LOCK_SHA256=57b298aceebc48bb94ea0593347348256475da7b2fddb77025d9e57cc8759420 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 \ @@ -54,9 +54,9 @@ ENV PATH=${PAPERCLIP_RUNNER_PROVIDER_PACK_ROOT}/node_modules/.bin:/usr/local/sha # 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 \ + @xai-official/grok@1.0.41 \ + @google/gemini-cli@0.60.0 \ + @moonshot-ai/kimi-code@2.0.2 \ && npm cache clean --force # Hermes requires Python >=3.11,<3.14; the image's default Python is newer. @@ -67,8 +67,8 @@ 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.09.02-c22c1a3 -ARG CURSOR_SHA256_AMD64=b73b59854762535c0fc20d7ccc51c3b5a356a851491088d60a362be48750f53c +ARG CURSOR_VERSION=2026.09.18-9a7762b +ARG CURSOR_SHA256_AMD64=b1308f5a2fc05458b9d8966752986bb23a971bbcc67c842c1df94c4b8132bad9 RUN set -eu; \ arch="$(dpkg --print-architecture)"; \ [ "$arch" = "amd64" ] || { echo "FATAL: cursor pin only covers amd64, not $arch" >&2; exit 1; }; \ @@ -82,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.100.0 -ARG GH_SHA256_AMD64=e4d4bb4498e8d007abe545b6568926793ace1b6447da598294a610018cb164be +ARG GH_VERSION=2.101.0 +ARG GH_SHA256_AMD64=9bca2d1c16825f109907a23307628a2f0698fbf99662b73a5cf0b020293072b8 RUN set -eu; \ arch="$(dpkg --print-architecture)"; \ [ "$arch" = "amd64" ] || { echo "FATAL: gh pin only covers amd64, not $arch" >&2; exit 1; }; \ @@ -133,6 +133,9 @@ RUN /bin/sh -lc 'set -eu; \ for command_name in acpx claude-agent-acp codex-acp; do \ command -v "$command_name" >/dev/null || { echo "FATAL: $command_name not on PATH for daytona user" >&2; exit 1; }; \ done; \ + test "$(codex --version)" = "codex-cli 0.156.0"; \ + test "$(claude --version)" = "2.1.280 (Claude Code)"; \ + test "$(opencode --version)" = "1.18.32"; \ test "$(acpx --version)" = "0.13.1"; \ test "$(claude-agent-acp --version)" = "0.73.0"; \ test "$(codex-acp --version)" = "@agentclientprotocol/codex-acp 1.6.2"' diff --git a/docker/daytona-runner/README.md b/docker/daytona-runner/README.md index 229529b962..dd3686a9c1 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.29, the compiled OpenCode proxy, ACPX 0.13.1 sidecar, qualified ACP +OpenCode 1.18.32, 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. @@ -12,8 +12,34 @@ The fleet pins are intentionally copied from Update both definitions together until the fleet base is published as a stable image that this Dockerfile can extend directly. +## Harness versions + +The September 22, 2026 refresh pins Codex 0.156.0, Claude Agent SDK +0.3.280 (Claude Code 2.1.280), and OpenCode 1.18.32 in the shared provider +pack. Claude Code 2.1.280 is the minimum for +[Opus 5.5](https://code.claude.com/docs/en/model-config); it also supports +Fable 5.1. Codex uses the current +[GPT-6 Sol and Luna model IDs](https://learn.chatgpt.com/docs/models). +Grok CLI 1.0.41 supports the current +[Grok 4.7](https://docs.x.ai/developers/grok-4-7) model family. + +Keep the patched ACP bridge versions separate from their CLI runtime pins. +Their executable digests do not change when only the runtime dependency +changes. Refresh the runtime executable digests from integrity-verified npm +release archives for every supported platform, and keep the native runner, +provider manifest, and remote controller version checks aligned. + ## Build and verify +Run `pnpm --filter @paperclipai/paperclip-runner test:opencode:qualification` +after installing dependencies to exercise the actual pinned OpenCode executable. +It checks health/version, session creation and retrieval, SSE messages, an async +prompt, and session deletion against a loopback mock provider. It uses an +isolated home, starts no paid model request, and retires its process group. +Set `PAPERCLIP_TEST_OPENCODE_BINARY` to the materialized Linux executable when +qualifying an assembled provider pack. + + The fleet image is currently amd64-only because the pinned Cursor and GitHub CLI checksums cover amd64. diff --git a/docs/deploy/environment-variables.md b/docs/deploy/environment-variables.md index b8ccadbdb1..b2985bd7a9 100644 --- a/docs/deploy/environment-variables.md +++ b/docs/deploy/environment-variables.md @@ -24,7 +24,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.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_CODEX_NPM_SPEC` | (unset) | Optional pinned npm package spec (for example, `@openai/codex@0.156.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_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 41feeb179d..fb4396efec 100644 --- a/package.json +++ b/package.json @@ -116,8 +116,8 @@ "postgres@3.4.9": "patches/postgres@3.4.9.patch" }, "overrides": { - "@agentclientprotocol/codex-acp@1.6.2>@openai/codex": "0.153.4", - "@agentclientprotocol/claude-agent-acp@0.73.0>@anthropic-ai/claude-agent-sdk": "0.3.263", + "@agentclientprotocol/codex-acp@1.6.2>@openai/codex": "0.156.0", + "@agentclientprotocol/claude-agent-acp@0.73.0>@anthropic-ai/claude-agent-sdk": "0.3.280", "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 fbf51aa98b..ebc0614e39 100644 --- a/packages/paperclip-runner/package.json +++ b/packages/paperclip-runner/package.json @@ -159,16 +159,17 @@ "browser:dev": "pnpm run build:typescript && pnpm run build:runner-binaries && vite --config vite.config.ts", "browser:preview": "vite preview --config vite.config.ts", "verify": "pnpm run build && pnpm run typecheck && pnpm run check:replay-goldens && pnpm run test && pnpm run test:sdk && pnpm run test:scenarios && pnpm run check:browser-tokens && pnpm run test:browser && pnpm run test:browser:sdk && pnpm run test:browser:scenarios && pnpm run check:forbidden-imports && pnpm run check:tracked-imports && pnpm run check:numbered-milestones && pnpm run check:package-boundaries && pnpm run check:clean-consumers && pnpm run docs:validate && pnpm run check:conformance-parity && pnpm run check:replay-parity && pnpm run trace:conformance && pnpm run replay:fixture && pnpm run trace:local-runner -- --quiet", - "verify:rootless": "bash scripts/verify-rootless-linux.sh" + "verify:rootless": "bash scripts/verify-rootless-linux.sh", + "test:opencode:qualification": "PAPERCLIP_OPENCODE_QUALIFY=1 node --test scripts/qualify-opencode-runtime.test.mjs" }, "dependencies": { "@agentclientprotocol/claude-agent-acp": "0.73.0", "@agentclientprotocol/codex-acp": "1.6.2", - "@openai/codex": "0.153.4", + "@openai/codex": "0.156.0", "acpx": "0.13.1", "ajv": "^8.20.0", "json-schema-to-ts": "^3.1.1", - "opencode-ai": "1.18.29", + "opencode-ai": "1.18.32", "react-markdown": "^10.1.0", "remark-gfm": "^4.0.1", "smol-toml": "^1.4.2" 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 e4cf725f1f..41aa785e58 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 @@ -151,7 +151,7 @@ impl AcpxProviderDescriptor { "@agentclientprotocol/claude-agent-acp", "0.73.0", Some("@anthropic-ai/claude-agent-sdk"), - Some("0.3.263"), + Some("0.3.280"), "sha256:9d73d1f0f121fb96cc8badb28c22d5bff02d8582eb2e40360a81c189e1b9422a", ), "codex" => ( @@ -159,7 +159,7 @@ impl AcpxProviderDescriptor { "@agentclientprotocol/codex-acp", "1.6.2", Some("@openai/codex"), - Some("0.153.4"), + Some("0.156.0"), "sha256:c4538599d1ab767db5dff50934f13bb5ba313a59d9c4a83e993fac4617ea63d3", ), "pi" => return Err(DurableRunnerError::invalid( @@ -1852,7 +1852,7 @@ mod tests { "@agentclientprotocol/claude-agent-acp", "0.73.0", json!("@anthropic-ai/claude-agent-sdk"), - json!("0.3.263"), + json!("0.3.280"), "sha256:9d73d1f0f121fb96cc8badb28c22d5bff02d8582eb2e40360a81c189e1b9422a", ) } else { @@ -1861,7 +1861,7 @@ mod tests { "@agentclientprotocol/codex-acp", "1.6.2", json!("@openai/codex"), - json!("0.153.4"), + json!("0.156.0"), "sha256:c4538599d1ab767db5dff50934f13bb5ba313a59d9c4a83e993fac4617ea63d3", ) }; 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 892999199f..bfa72a01c7 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.29"; +const QUALIFIED_OPENCODE_VERSION: &str = "1.18.32"; 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 cf7f5468db..cc86427b96 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 @@ -4887,7 +4887,7 @@ mod tests { CodexProviderConfig { provider: "opencode".to_owned(), driver: "opencode_server".to_owned(), - provider_version: "1.18.29".to_owned(), + provider_version: "1.18.32".to_owned(), command: PathBuf::from("node"), args: Vec::new(), cwd: std::env::current_dir() @@ -5248,7 +5248,7 @@ mod tests { CodexProviderConfig { provider: "opencode".to_owned(), driver: "opencode_server".to_owned(), - provider_version: "1.18.29".to_owned(), + provider_version: "1.18.32".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/acpx_warm_attachment.rs b/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_warm_attachment.rs index d967378021..252953d7d6 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_warm_attachment.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_warm_attachment.rs @@ -83,7 +83,7 @@ fn checkpoints_the_sidecar_and_rebinds_consecutive_warm_runs_before_accepting_wo "providerVersion": "0.13.1", "agent": "codex", "model": "gpt-5.6-sol", "acpxVersion": "0.13.1", "agentServerPackage": "@agentclientprotocol/codex-acp", "agentServerVersion": "1.6.2", "agentRuntimePackage": "@openai/codex", - "agentRuntimeVersion": "0.153.4", "commandDigest": PROFILE_DIGEST, + "agentRuntimeVersion": "0.156.0", "commandDigest": PROFILE_DIGEST, "sidecarCommand": sidecar, "sidecarArgs": args, "runtimeDirectory": fixture.0, "normalizedSessionId": "session-1", "runId": "run-1", "cwd": fixture.0, "instructions": "Complete the supplied work.", 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 a781f430f8..c23930eeff 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 @@ -168,7 +168,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.153.4")) + (json!("@openai/codex"), json!("0.156.0")) } else { (Value::Null, Value::Null) }; @@ -265,7 +265,7 @@ fn opencode_prepare_payload(directory: &Path) -> Value { "kind": "opencode", "provider": "opencode", "driver": "opencode_server", - "providerVersion": "1.18.29", + "providerVersion": "1.18.32", "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 3b5e3fb18a..d9bac8ba49 100644 --- a/packages/paperclip-runner/scripts/build-provider-pack.mjs +++ b/packages/paperclip-runner/scripts/build-provider-pack.mjs @@ -290,8 +290,8 @@ try { const payload = { pins: { nodeMinimum: minimumNodeVersion.join("."), - codex: "0.153.4", - opencode: "1.18.29", + codex: "0.156.0", + opencode: "1.18.32", acpx: "0.13.1", claudeAcp: "0.73.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 f4be2ea8ab..ef984a0292 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.29"; +const OPENCODE_VERSION = "1.18.32"; 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 4bae1e206e..789fa62af7 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.29", + version: options.packageVersion ?? "1.18.32", }), ), writeFile( join(baselineRoot, "package.json"), JSON.stringify({ name: "opencode-linux-x64-baseline", - version: options.baselineVersion ?? "1.18.29", + version: options.baselineVersion ?? "1.18.32", }), ), 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.29\n"); + await writeFile(realSource, "#!/bin/sh\necho 1.18.32\n"); await chmod(realSource, 0o755); await symlink(realSource, source); } else { - await writeFile(source, "#!/bin/sh\necho 1.18.29\n"); + await writeFile(source, "#!/bin/sh\necho 1.18.32\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.29"); + assert.equal(result.version, "1.18.32"); 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\.29/, + /Expected opencode-linux-x64-baseline@1\.18\.32/, ); const symlinkSource = await fixture({ symlinkSource: true }); diff --git a/packages/paperclip-runner/scripts/qualify-opencode-runtime.test.mjs b/packages/paperclip-runner/scripts/qualify-opencode-runtime.test.mjs new file mode 100644 index 0000000000..9505c8952c --- /dev/null +++ b/packages/paperclip-runner/scripts/qualify-opencode-runtime.test.mjs @@ -0,0 +1,146 @@ +// Opt-in qualification of the installed CLI, not the fake protocol server. +// PAPERCLIP_OPENCODE_QUALIFY=1 node --test scripts/qualify-opencode-runtime.test.mjs +import assert from "node:assert/strict"; +import { spawn, execFileSync } from "node:child_process"; +import { once } from "node:events"; +import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; +import { createServer } from "node:http"; +import { tmpdir } from "node:os"; +import { dirname, join, resolve } from "node:path"; +import { setTimeout as delay } from "node:timers/promises"; +import test from "node:test"; + +const enabled = process.env.PAPERCLIP_OPENCODE_QUALIFY === "1"; +const packageRoot = resolve(import.meta.dirname, ".."); +const manifest = JSON.parse(await readFile(join(packageRoot, "package.json"), "utf8")); + +test("the pinned OpenCode executable serves health, sessions, SSE, and a local-provider prompt", { + skip: !enabled, + timeout: 60_000, +}, async (t) => { + const root = await mkdtemp(join(tmpdir(), "paperclip-opencode-qualification-")); + let provider; + let child; + let exited; + const streamAbort = new AbortController(); + const killGroup = (signal) => { + if (!child?.pid) return; + try { process.kill(-child.pid, signal); } catch (error) { if (error.code !== "ESRCH") throw error; } + }; + t.after(async () => { + streamAbort.abort(); + if (child?.pid) { + killGroup("SIGTERM"); + const kill = setTimeout(() => killGroup("SIGKILL"), 3000); + try { await exited; } finally { clearTimeout(kill); killGroup("SIGKILL"); } + } + if (provider) { provider.closeAllConnections(); await new Promise((done) => provider.close(done)); } + await rm(root, { recursive: true, force: true }); + }); + const command = resolve(process.env.PAPERCLIP_TEST_OPENCODE_BINARY ?? join(packageRoot, "node_modules/opencode-ai/bin/opencode.exe")); + assert.equal(execFileSync(command, ["--version"], { encoding: "utf8", timeout: 10_000 }).trim(), manifest.dependencies["opencode-ai"]); + const requests = []; + provider = createServer(async (request, response) => { + const chunks = []; + for await (const chunk of request) chunks.push(chunk); + const body = JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}"); + requests.push({ path: request.url, body }); + if (request.url !== "/v1/chat/completions") { + response.writeHead(404).end(); + return; + } + response.writeHead(200, { "Content-Type": "text/event-stream" }); + for (const [delta, finish_reason] of [ + [{ role: "assistant", content: "paperclip-opencode-qualified" }, null], + [{}, "stop"], + ]) { + response.write(`data: ${JSON.stringify({ id: "chatcmpl-qualification", object: "chat.completion.chunk", created: 1, model: "qualification", choices: [{ index: 0, delta, finish_reason }] })}\n\n`); + } + response.end("data: [DONE]\n\n"); + }); + provider.listen(0, "127.0.0.1"); + await once(provider, "listening"); + const providerUrl = `http://127.0.0.1:${provider.address().port}/v1`; + const config = join(root, "opencode.json"); + await writeFile(config, JSON.stringify({ + model: "openrouter/qualification", + small_model: "openrouter/qualification", + share: "disabled", autoupdate: false, plugin: [], + enabled_providers: ["openrouter"], + provider: { openrouter: { options: { baseURL: providerUrl, apiKey: "local-fixture-only" }, models: { qualification: { name: "qualification", limit: { context: 10000, output: 1000 } } } } }, + permission: { "*": "deny" }, + })); + const workspace = join(root, "workspace"); + await mkdir(workspace); + child = spawn(command, ["serve", "--hostname", "127.0.0.1", "--port", "0"], { + cwd: workspace, + env: { + PATH: `${dirname(process.execPath)}:${process.env.PATH ?? "/usr/bin:/bin"}`, + HOME: root, XDG_CONFIG_HOME: join(root, "config"), XDG_DATA_HOME: join(root, "data"), XDG_CACHE_HOME: join(root, "cache"), + OPENCODE_CONFIG: config, + OPENCODE_DISABLE_PROJECT_CONFIG: "true", OPENCODE_DISABLE_MODELS_FETCH: "true", OPENCODE_DISABLE_DEFAULT_PLUGINS: "true", + OPENCODE_SERVER_USERNAME: "paperclip", OPENCODE_SERVER_PASSWORD: "local-fixture-only", + }, + stdio: ["ignore", "pipe", "pipe"], + detached: true, + }); + let output = ""; + for (const stream of [child.stdout, child.stderr]) stream.on("data", (chunk) => { output = `${output}${chunk}`.slice(-16384); }); + exited = once(child, "exit"); + let baseUrl; + for (let attempt = 0; attempt < 150; attempt++) { + baseUrl = output.match(/http:\/\/127\.0\.0\.1:\d+/)?.[0]; + if (baseUrl) break; + assert.equal(child.exitCode, null, output); + await delay(100); + } + assert.ok(baseUrl, `OpenCode did not start: ${output}`); + const headers = { Authorization: `Basic ${Buffer.from("paperclip:local-fixture-only").toString("base64")}`, "Content-Type": "application/json" }; + const api = async (path, options = {}) => { + const response = await fetch(`${baseUrl}${path}`, { headers, signal: AbortSignal.timeout(15_000), ...options }); + assert.ok(response.ok, `${path}: ${response.status} ${await response.clone().text()}`); + return response; + }; + const health = await (await api("/global/health")).json(); + assert.deepEqual(health, { healthy: true, version: manifest.dependencies["opencode-ai"] }); + const session = await (await api("/session", { method: "POST", body: JSON.stringify({ title: "Runtime qualification" }) })).json(); + assert.equal((await (await api(`/session/${session.id}`)).json()).id, session.id); + const stream = await fetch(`${baseUrl}/event`, { headers, signal: streamAbort.signal }); + assert.equal(stream.status, 200); + assert.match(stream.headers.get("content-type"), /text\/event-stream/); + const reader = stream.body.getReader(); + const events = []; + const readEvents = (async () => { + let buffer = ""; + const decoder = new TextDecoder(); + try { + while (true) { + const { done, value } = await reader.read(); + if (done) break; + buffer += decoder.decode(value, { stream: true }); + let boundary; + while ((boundary = buffer.indexOf("\n\n")) !== -1) { + const frame = buffer.slice(0, boundary); buffer = buffer.slice(boundary + 2); + for (const line of frame.split("\n")) if (line.startsWith("data: ")) events.push(JSON.parse(line.slice(6))); + } + } + } catch (error) { if (!streamAbort.signal.aborted) throw error; } + })(); + const prompt = await api(`/session/${session.id}/prompt_async`, { + method: "POST", + body: JSON.stringify({ model: { providerID: "openrouter", modelID: "qualification" }, parts: [{ type: "text", text: "Reply with the qualification marker." }] }), + }); + assert.equal(prompt.status, 204); + let messages; + for (let attempt = 0; attempt < 150; attempt++) { + messages = await (await api(`/session/${session.id}/message`)).json(); + if (messages.some((message) => message.info.role === "assistant" && message.parts.some((part) => part.type === "text" && part.text === "paperclip-opencode-qualified"))) break; + await delay(100); + } + assert.ok(messages.some((message) => message.info.role === "assistant" && message.parts.some((part) => part.type === "text" && part.text === "paperclip-opencode-qualified")), JSON.stringify(messages)); + assert.ok(requests.some((request) => request.path === "/v1/chat/completions" && request.body.model === "qualification")); + assert.ok(events.some((event) => event.type === "message.part.updated" || event.type === "message.part.delta")); + streamAbort.abort(); + await readEvents; + assert.equal((await api(`/session/${session.id}`, { method: "DELETE" })).status, 200); +}); 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 ef1874faea..a8717ef6ba 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.29", + opencodeVersion: "1.18.32", }; 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 f1042a9854..93aaa7c92a 100644 --- a/packages/paperclip-runner/src/backends/codex-native-backend.ts +++ b/packages/paperclip-runner/src/backends/codex-native-backend.ts @@ -69,7 +69,7 @@ function transportDriverIdentity(input: NativeExecutionInput): { return { kind: "opencode_server", displayName: "OpenCode server", - version: "1.18.29", + version: "1.18.32", }; 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 7671cfc7f0..cd8a46a621 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.153.4" : "0.3.263", + agent === "pi" ? "0.84.2" : agent === "codex" ? "0.156.0" : "0.3.280", commandDigest: agent === "codex" ? "sha256:c4538599d1ab767db5dff50934f13bb5ba313a59d9c4a83e993fac4617ea63d3" @@ -266,7 +266,7 @@ describe("native backend factory", () => { await expect(backend.descriptor()).resolves.toMatchObject({ kind: "runner", name: "opencode_server", - version: "1.18.29", + version: "1.18.32", 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.29", + version: "1.18.32", 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 c88c047920..63b5d84d1a 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.29"; - if (version !== "1.18.29") { - throw new Error(`OpenCode evals require exact version 1.18.29; received ${version}`); + const version = request.opencodeVersion ?? "1.18.32"; + if (version !== "1.18.32") { + throw new Error(`OpenCode evals require exact version 1.18.32; 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 1b1ba26ccd..f02a71ef91 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.29" }, + serverInfo: { name: "opencode", version: "1.18.32" }, }; 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 132b87403a..7c7a9356a3 100644 --- a/packages/paperclip-runner/src/drivers/acpx/installation-integrity.test.ts +++ b/packages/paperclip-runner/src/drivers/acpx/installation-integrity.test.ts @@ -445,7 +445,7 @@ describe("ACPX installation integrity", () => { }, { name: "@anthropic-ai/claude-agent-sdk", - version: "0.3.263", + version: "0.3.280", directory: join(dependencyRoot, "claude-agent-sdk"), }, { diff --git a/packages/paperclip-runner/src/drivers/acpx/installation-integrity.ts b/packages/paperclip-runner/src/drivers/acpx/installation-integrity.ts index e65d90c947..ef101bdb79 100644 --- a/packages/paperclip-runner/src/drivers/acpx/installation-integrity.ts +++ b/packages/paperclip-runner/src/drivers/acpx/installation-integrity.ts @@ -45,13 +45,13 @@ const VERIFIED_PROVIDER_RUNTIME_TARGET_ENV = const QUALIFIED_CLAUDE_LINUX_X64_RUNTIME = Object.freeze({ runtimePackageName: "@anthropic-ai/claude-agent-sdk", - runtimePackageVersion: "0.3.263", + runtimePackageVersion: "0.3.280", packageName: "@anthropic-ai/claude-agent-sdk-linux-x64", - packageVersion: "0.3.263", - dependencyDeclaration: "0.3.263", + packageVersion: "0.3.280", + dependencyDeclaration: "0.3.280", relativeExecutable: "claude", executableDigest: - "sha256:26d020351e8112f4006790f3cfce43b4c9df0c1bb1d0e542364d64151b81d5ba", + "sha256:1e08503dbdf3c2cb0d706d32f3408277388d1c76ef108673e8fe42c1b322925b", environmentVariable: "CLAUDE_CODE_EXECUTABLE", }); @@ -59,24 +59,24 @@ const QUALIFIED_CLAUDE_DARWIN_RUNTIMES = { arm64: Object.freeze({ ...QUALIFIED_CLAUDE_LINUX_X64_RUNTIME, packageName: "@anthropic-ai/claude-agent-sdk-darwin-arm64", - executableDigest: "sha256:ef5d2909c8af49f31ab6d5487e90316777bc2fac170adfe8160716caa8aaf4f9", + executableDigest: "sha256:387a5c5dcdbb815085edf0baf79591f9d8894efe922bceaf3d75b1b08055229d", }), x64: Object.freeze({ ...QUALIFIED_CLAUDE_LINUX_X64_RUNTIME, packageName: "@anthropic-ai/claude-agent-sdk-darwin-x64", - executableDigest: "sha256:a94a8b229fa85c3a316c6b4a35e0aa22bec1aabbd3d1422826ce1d10ddc88751", + executableDigest: "sha256:c1d32d87630482250633208ab77855429b24010ae3086a7ff7539b57b93168d4", }), }; const QUALIFIED_CODEX_LINUX_X64_RUNTIME = Object.freeze({ runtimePackageName: "@openai/codex", - runtimePackageVersion: "0.153.4", + runtimePackageVersion: "0.156.0", packageName: "@openai/codex-linux-x64", - packageVersion: "0.153.4-linux-x64", - dependencyDeclaration: "npm:@openai/codex@0.153.4-linux-x64", + packageVersion: "0.156.0-linux-x64", + dependencyDeclaration: "npm:@openai/codex@0.156.0-linux-x64", relativeExecutable: "vendor/x86_64-unknown-linux-musl/bin/codex", executableDigest: - "sha256:56ef98ab4032d317ab26e9b5e5a175650717351edb16ed9cde0cb6d1734d62da", + "sha256:78a11f06e0a2dda42d13fba1d50dc62e8cbdb2d5f69789722f4d4d99b5cdbe30", environmentVariable: "CODEX_PATH", }); @@ -92,8 +92,8 @@ const QUALIFIED_CLAUDE_PROVIDER_DEPENDENCIES = Object.freeze([ }), Object.freeze({ packageName: "@anthropic-ai/claude-agent-sdk", - packageVersion: "0.3.263", - // The package's own package.json still declares 0.3.257 — 0.3.263 is + packageVersion: "0.3.280", + // The package's own package.json still declares 0.3.257 — 0.3.280 is // only what pnpm resolves, forced by the // "claude-agent-acp@0.73.0>@anthropic-ai/claude-agent-sdk" override in // the workspace root. This field binds the declared string, not the 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 6451a369f5..836e27c485 100644 --- a/packages/paperclip-runner/src/drivers/acpx/qualified-profiles.test.ts +++ b/packages/paperclip-runner/src/drivers/acpx/qualified-profiles.test.ts @@ -18,7 +18,7 @@ describe("qualified ACPX profiles", () => { } }); - it.each(["claude-opus-5", "custom-model-not-in-catalog"])("accepts the exact Claude model %s", (model) => { + it.each(["claude-opus-5-5", "claude-fable-5-1", "custom-model-not-in-catalog"])("accepts the exact Claude model %s", (model) => { expect(resolveQualifiedAcpxProfile("claude", model)).toMatchObject({ qualificationModel: model, reportedModelId: model, commandDigest: QUALIFIED_ACPX_PROFILES.claude.commandDigest, @@ -34,14 +34,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.153.4", + agentRuntimeVersion: "0.156.0", }); }); 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.263", + agentRuntimeVersion: "0.3.280", }); }); }); diff --git a/packages/paperclip-runner/src/drivers/acpx/qualified-profiles.ts b/packages/paperclip-runner/src/drivers/acpx/qualified-profiles.ts index ee54598b11..fd931b1808 100644 --- a/packages/paperclip-runner/src/drivers/acpx/qualified-profiles.ts +++ b/packages/paperclip-runner/src/drivers/acpx/qualified-profiles.ts @@ -56,7 +56,7 @@ export const QUALIFIED_ACPX_PROFILES: Readonly< agentServerPackage: "@agentclientprotocol/claude-agent-acp", agentServerVersion: "0.73.0", agentRuntimePackage: "@anthropic-ai/claude-agent-sdk", - agentRuntimeVersion: "0.3.263", + agentRuntimeVersion: "0.3.280", commandDigest: "sha256:9d73d1f0f121fb96cc8badb28c22d5bff02d8582eb2e40360a81c189e1b9422a", qualificationModel: "claude-sonnet-5", @@ -72,7 +72,7 @@ export const QUALIFIED_ACPX_PROFILES: Readonly< agentServerPackage: "@agentclientprotocol/codex-acp", agentServerVersion: "1.6.2", agentRuntimePackage: "@openai/codex", - agentRuntimeVersion: "0.153.4", + agentRuntimeVersion: "0.156.0", commandDigest: "sha256:c4538599d1ab767db5dff50934f13bb5ba313a59d9c4a83e993fac4617ea63d3", 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 b08741aba6..130b34f53b 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 @@ -309,7 +309,7 @@ describe("OpenCodeServerDriver", () => { headers: { "Content-Type": "application/json" }, }); if (url.pathname === "/global/health") - return json({ healthy: true, version: "1.18.29" }); + return json({ healthy: true, version: "1.18.32" }); if (url.pathname === "/event") { return new Response( new ReadableStream({ @@ -681,7 +681,7 @@ describe("OpenCodeServerDriver", () => { output: 2, costUsd: 0.001, provider: "openrouter", - driverVersion: "1.18.29", + driverVersion: "1.18.32", }); 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 f9a1f29cb3..91cc5809c8 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.29" as const; +export const QUALIFIED_OPENCODE_VERSION = "1.18.32" 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 643949a9a0..00bdc571f2 100644 --- a/packages/paperclip-runner/src/live/live-session.test.ts +++ b/packages/paperclip-runner/src/live/live-session.test.ts @@ -873,7 +873,7 @@ describe("Capability live runnerd and Codex session", () => { expect(session.snapshot().config).toMatchObject({ provider: "opencode", driver: "opencode_server", - providerVersion: "1.18.29", + providerVersion: "1.18.32", 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 821352e71f..73359bf267 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.29" + ? "1.18.32" : 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 4b62489003..1997257ef2 100644 --- a/packages/paperclip-runner/src/live/runnerd-codex-transport.ts +++ b/packages/paperclip-runner/src/live/runnerd-codex-transport.ts @@ -4565,7 +4565,7 @@ class DurablePrpCodexTransport implements CodexAppServerTransport { ? "opencode_server" : "codex_app_server", providerVersion: - provider === "opencode" ? "1.18.29" : "codex-app-server-v1", + provider === "opencode" ? "1.18.32" : "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 ab9e5ad6f7..14e6018a8b 100644 --- a/packages/paperclip-runner/test/acpx-codex-package-contract.test.mjs +++ b/packages/paperclip-runner/test/acpx-codex-package-contract.test.mjs @@ -57,10 +57,10 @@ const nativeSessionExecutor = await readFile( ); test("the runner pins every qualified ACPX production dependency", () => { - assert.equal(runnerPackage.dependencies["@openai/codex"], "0.153.4"); + assert.equal(runnerPackage.dependencies["@openai/codex"], "0.156.0"); 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.73.0>@anthropic-ai/claude-agent-sdk"], "0.3.263"); + assert.equal(rootPackage.pnpm.overrides["@agentclientprotocol/claude-agent-acp@0.73.0>@anthropic-ai/claude-agent-sdk"], "0.3.280"); assert.equal(runnerPackage.optionalDependencies, undefined); assert.equal(runnerPackage.dependencies.node, undefined); assert.equal(runnerPackage.dependencies.acpx, "0.13.1"); @@ -142,7 +142,7 @@ test("old and new pnpm configuration both apply the exact runtime patches", () = providerPackBuilder, /copyFileSync\(process\.execPath, stableNodeCommand\)/, ); - assert.match(codexPatch, /\+ "@openai\/codex": "0\.153\.4"/); + assert.match(codexPatch, /\+ "@openai\/codex": "0\.156\.0"/); }); 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 3a7db00308..c69a9c7d04 100644 --- a/packages/paperclip-runner/test/fixtures/fake-opencode-server.mjs +++ b/packages/paperclip-runner/test/fixtures/fake-opencode-server.mjs @@ -222,7 +222,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.29" }); + return json(response, 200, { healthy: true, version: "1.18.32" }); if (request.url === "/event") { eventConnections += 1; response.writeHead(200, { diff --git a/patches/@agentclientprotocol__codex-acp@1.6.2.patch b/patches/@agentclientprotocol__codex-acp@1.6.2.patch index 8f6c17911e..36becec2b8 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.153.4", ++ "@openai/codex": "0.156.0", "diff": "^9.0.0", "open": "^11.0.0", "vscode-jsonrpc": "^9.0.1", diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 1650614afc..19900634e4 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -32,8 +32,8 @@ patchedDependencies: # 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.73.0>@anthropic-ai/claude-agent-sdk": "0.3.263" + "@agentclientprotocol/codex-acp@1.6.2>@openai/codex": "0.156.0" + "@agentclientprotocol/claude-agent-acp@0.73.0>@anthropic-ai/claude-agent-sdk": "0.3.280" rollup: ">=4.59.0" react: "^19.2.8" react-dom: "^19.2.8" diff --git a/scripts/runner-api-eval-worker.ts b/scripts/runner-api-eval-worker.ts index 8423caaf71..4a76036e20 100644 --- a/scripts/runner-api-eval-worker.ts +++ b/scripts/runner-api-eval-worker.ts @@ -100,7 +100,7 @@ try { if (!request.reservationId || request.maxCostUsd !== 0.5 || !["gpt-5.6-luna", "claude-sonnet-5", ...OPENROUTER_MODELS].includes(request.model)) throw new Error("Paid attempt requires ledger reservation and qualified model"); if (isOpenRouter) { providerVersion = execFileSync(resolve("packages/paperclip-runner/node_modules/opencode-ai/bin/opencode.exe"), ["--version"], { encoding: "utf8" }).trim(); - if (providerVersion !== "1.18.29") throw new Error("OpenCode profile requires version 1.18.29"); + if (providerVersion !== "1.18.32") throw new Error("OpenCode profile requires version 1.18.32"); } const providerEnvironment = isOpenRouter ? openRouterEnvironment() : request.model === "claude-sonnet-5" ? (() => { const token = process.env.CLAUDE_CODE_OAUTH_TOKEN; diff --git a/scripts/tests/native-cleanup-paginated-codex.mjs b/scripts/tests/native-cleanup-paginated-codex.mjs index 401ea22dc9..fab7baa705 100644 --- a/scripts/tests/native-cleanup-paginated-codex.mjs +++ b/scripts/tests/native-cleanup-paginated-codex.mjs @@ -1,7 +1,7 @@ // Opt-in real Codex qualification; no model turn or live account data. // Run from the repository root: // node --import ./server/node_modules/tsx/dist/loader.mjs scripts/tests/native-cleanup-paginated-codex.mjs -// PAPERCLIP_TEST_CODEX_BINARY may select the exact installed Codex 0.153.4 binary. +// PAPERCLIP_TEST_CODEX_BINARY may select the exact installed Codex 0.156.0 binary. // Fresh synthetic fixture directories are retained for inspection; never reuse live homes. import { spawn, execFileSync } from "node:child_process"; import { @@ -26,10 +26,10 @@ if ( execFileSync(codexBinary, ["--version"], { encoding: "utf8", timeout: 10000, - }).trim() !== "codex-cli 0.153.4" + }).trim() !== "codex-cli 0.156.0" ) { throw new Error( - "This opt-in qualification requires Codex CLI 0.153.4. Requalify deliberately before changing the pin.", + "This opt-in qualification requires Codex CLI 0.156.0. Requalify deliberately before changing the pin.", ); } const home = await mkdtemp(join(tmpdir(), "paperclip-paginated-probe-")); @@ -132,7 +132,7 @@ const lines = timestamp, cwd: home, originator: "codex", - cli_version: "0.153.4", + cli_version: "0.156.0", source: "cli", model_provider: "openai", selected_capability_roots: [], diff --git a/scripts/tests/native-provider-startup-codex.mjs b/scripts/tests/native-provider-startup-codex.mjs index ec400e3695..2eca77066e 100644 --- a/scripts/tests/native-provider-startup-codex.mjs +++ b/scripts/tests/native-provider-startup-codex.mjs @@ -88,7 +88,7 @@ if (args.length === 0 || (args.length === 1 && args[0] === "--help")) { }).trim(); assert.equal( version, - "codex-cli 0.153.4", + "codex-cli 0.156.0", "qualified_codex_version_required", ); const ledger = join(fixture, "provider-process-starts.txt"); @@ -309,7 +309,7 @@ if (args.length === 0 || (args.length === 1 && args[0] === "--help")) { kind: "codex", provider: "codex", driver: "codex_app_server", - providerVersion: "0.153.4", + providerVersion: "0.156.0", command: shim, args: [ "--cd", diff --git a/server/src/__tests__/adapter-registry.test.ts b/server/src/__tests__/adapter-registry.test.ts index 1da4fcdc4d..3968812653 100644 --- a/server/src/__tests__/adapter-registry.test.ts +++ b/server/src/__tests__/adapter-registry.test.ts @@ -336,8 +336,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.153.4")}; fi`; - const expectedRunnerOpenCodeInstall = `if ! command -v 'opencode' >/dev/null 2>&1; then ${buildSandboxNpmInstallCommand("opencode-ai@1.18.29")}; fi`; + const expectedRunnerCodexInstall = `if ! command -v 'codex' >/dev/null 2>&1; then ${buildSandboxNpmInstallCommand("@openai/codex@0.156.0")}; fi`; + const expectedRunnerOpenCodeInstall = `if ! command -v 'opencode' >/dev/null 2>&1; then ${buildSandboxNpmInstallCommand("opencode-ai@1.18.32")}; fi`; expect(findActiveServerAdapter("claude_local")?.getRuntimeCommandSpec?.({})).toEqual({ command: "claude", diff --git a/server/src/adapters/registry.ts b/server/src/adapters/registry.ts index 96f97b5885..6f70a9a7e0 100644 --- a/server/src/adapters/registry.ts +++ b/server/src/adapters/registry.ts @@ -508,7 +508,7 @@ const paperclipRunnerAdapter: ServerAdapterModule = { "opencode", `opencode-ai@${QUALIFIED_OPENCODE_RUNNER_VERSION}`, ) - : buildNpmRuntimeCommandSpec(config, "codex", "@openai/codex@0.153.4"), + : buildNpmRuntimeCommandSpec(config, "codex", "@openai/codex@0.156.0"), agentConfigurationDoc: "# Paperclip Runner\n\nAdapter: paperclip_runner\n\nRuns Codex, OpenCode, Claude Managed, AWS AgentCore, or ACPX Claude 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 a6e79f988f..e11b057ac0 100644 --- a/server/src/services/native-runtime/native-session-executor.test.ts +++ b/server/src/services/native-runtime/native-session-executor.test.ts @@ -995,8 +995,8 @@ describe("remote provider pack manifest", () => { const payload = { pins: { nodeMinimum: "24.11.0", - codex: "0.153.4", - opencode: "1.18.29", + codex: "0.156.0", + opencode: "1.18.32", acpx: "0.13.1", claudeAcp: "0.73.0", codexAcp: "1.6.2", @@ -1053,7 +1053,7 @@ describe("remote provider pack manifest", () => { ); await writeManifest(); expect(readRemoteProviderPackManifest(root).payload.pins.opencode).toBe( - "1.18.29", + "1.18.32", ); for (const [artifactName, substituteName] of [ ["nodeCommand", "productionLock"], @@ -1983,7 +1983,7 @@ describe("remote preinstalled executable discovery", () => { const shim = '#!/bin/sh\ncat "$(dirname "$0")/version.txt"\nprintf "%s\\n" "$@"\n'; await writeFile(source, shim, { mode: 0o755 }); - await writeFile(join(installation, "version.txt"), "codex-cli 0.153.4\n"); + await writeFile(join(installation, "version.txt"), "codex-cli 0.156.0\n"); // Existing deployments may already have the old symlink. Never write // through it into the shared installation while upgrading the launcher. await symlink(source, target); @@ -1996,7 +1996,7 @@ describe("remote preinstalled executable discovery", () => { execFileSync(target, ["--version", "argument with 'quotes'"], { encoding: "utf8", }), - ).toBe("codex-cli 0.153.4\n--version\nargument with 'quotes'\n"); + ).toBe("codex-cli 0.156.0\n--version\nargument with 'quotes'\n"); expect(await readFile(source, "utf8")).toBe(shim); } expect(await readdir(join(root, "workspace", "bin"))).toEqual(["codex"]); @@ -10252,7 +10252,7 @@ describe("runnerd provider runtime wiring", () => { }), stderr: "", }; if (command.args?.[0] === "--version") return { - exitCode: 0, timedOut: false, stdout: "codex-cli 0.153.4", stderr: "", + exitCode: 0, timedOut: false, stdout: "codex-cli 0.156.0", stderr: "", }; if (command.args?.[2] === "paperclip-runner-launch") { throw new Error("fixture_stop_after_launch_staging"); @@ -10373,7 +10373,7 @@ describe("runnerd provider runtime wiring", () => { ) { throw new Error("reached-preinstalled-codex-verification"); } - stdout = "codex-cli 0.153.4"; + stdout = "codex-cli 0.156.0"; } else if (script === "uname -s; uname -m") { stdout = `${process.platform === "darwin" ? "Darwin" : "Linux"}\n${process.arch === "arm64" ? "arm64" : "x86_64"}\n`; } else if (script.includes("command -v 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 5f350aaa7b..17715dafa9 100644 --- a/server/src/services/native-runtime/native-session-executor.ts +++ b/server/src/services/native-runtime/native-session-executor.ts @@ -9127,8 +9127,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.153.4", - opencode: "1.18.29", + codex: "0.156.0", + opencode: "1.18.32", acpx: "0.13.1", claudeAcp: "0.73.0", codexAcp: "1.6.2", diff --git a/server/src/services/native-runtime/provider-profile.ts b/server/src/services/native-runtime/provider-profile.ts index 7cd125783d..3cd80f49f1 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.29" as const; +export const QUALIFIED_OPENCODE_RUNNER_VERSION = "1.18.32" 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 45f5aad7ce..47ca43073d 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.29"); + expect(html).toContain("OpenCode 1.18.32"); expect(html).toContain("ACPX"); expect(html).not.toContain("Permission mode"); expect(html).not.toContain("Ask when requested"); @@ -46,7 +46,7 @@ describe("Paperclip Runner Codex configuration", () => { }); expect(html).toContain( - '', + '', ); expect(html).toContain("Full auto (allow)"); expect(html).toContain('aria-label="Permission mode"'); diff --git a/ui/src/adapters/codex-local/config-fields.tsx b/ui/src/adapters/codex-local/config-fields.tsx index 5d569a1385..4c518314b8 100644 --- a/ui/src/adapters/codex-local/config-fields.tsx +++ b/ui/src/adapters/codex-local/config-fields.tsx @@ -229,7 +229,7 @@ export function CodexLocalConfigFields({ }} > - +