fix: share current CLI runtimes across sandbox adapters (#12994)

## Thinking Path

- Paperclip Runner needs its runtime preinstalled for fast sandbox
startup.
- Native and local adapters should launch one current CLI installation
per provider.
- An older global copy can shadow that installation, and exact native
compatibility pins must match it.
- Update the qualified releases and binary digests, expose shared CLI
entrypoints from the provider pack, and prefer the image-owned bin
directory.
- Keep dependency installation in the image build; task startup only
discovers, links, and verifies artifacts.

## Linked Issues or Issue Description

**What happened?**
Remote native startup rejected a stale global Codex, while CLI-only
images lacked runnerd entirely.

**Expected behavior**
An image-baked runtime starts without uploading binaries or installing
packages. All adapters share the same current provider CLI.

**Steps to reproduce**
Start a native remote task with the old global Codex and the updated
runtime available only under `/opt/paperclip-runner/bin`.

**Paperclip version or commit**
Discovery behavior at `54a99d884`.

**Deployment mode**
Docker with a remote sandbox.

## What Changed

- Prefer `/opt/paperclip-runner/bin`, then the user's local bin
directory, then PATH. Existing metadata and version validation remains
in force.
- Qualify Codex 0.153.4, OpenCode 1.18.29, and Claude SDK 0.3.263 / CLI
2.1.263. Update binary digests, TypeScript/Rust checks, registry
defaults, and the displayed OpenCode version together.
- Share Codex and Claude's native executable with the ACP bridges
through exact dependency overrides. Preserve the separately qualified
ACP bridge implementations and their security patches.
- Expose shared provider-pack CLI launchers; fail the pack build if
Codex ACP resolves a separate Codex installation. Update the eval
image's other agent CLIs to current stable releases and remove duplicate
global provider installs.
- Document the single-current-CLI policy in source comments and
development guidance. Latest stable releases are resolved at
review/build preparation and pinned; task startup never auto-updates.

## Verification

- Native-session and adapter-registry suites: 158 tests passed.
- Provider suites: 88 tests passed, 7 Linux-only checks skipped on
macOS. One existing macOS temporary-path alias assertion passed when
rerun with canonical `TMPDIR=/private/tmp`.
- Package-contract and OpenCode materialization tests: 11 passed.
- Full typecheck, build, and token gates passed. Rust
native-provider/recovery tests: 19 passed.
- Broad local suite: 5,974 passed, 23 failed, 41 skipped. Failures are
in unchanged macOS workspace/path/port and connection suites; focused
runtime tests pass. All latest-head Linux PR checks passed, including
the full test shards, typecheck, build, runner verification, browser
suites, and canary dry run.
- The standalone fleet image built with one current provider CLI each
and passed native Codex/Claude binary-integrity checks. A disposable
Daytona sandbox reported ready in 798 ms; its baked runner completed an
API-key `gpt-5.6-luna` turn in 2,430 ms and returned the expected marker
with a usage receipt. No runtime artifacts were uploaded or installed.
- The normal shared `codex exec` entrypoint also completed an API-key
`gpt-5.6-luna` turn in 2,321 ms.
- Both image builds verify the complete generated lockfile against a
reviewed SHA-256 before package installation or lifecycle execution.
Root lockfile changes remain CI-owned. Merge and rollout remain on hold
for operator review.

## Risks

- Updating provider CLIs changes their behavior for all adapters;
version probes and live native smoke testing are required before image
promotion.
- The image-owned directory takes precedence. Its entries must launch
the same shared CLI as the global PATH, not a private older/newer copy.
- Application qualification pins and the deployed image must move
together. No startup fallback installation is added.
- No schema or authentication-policy changes.

## Model Used

OpenAI GPT-6 (Codex). The session does not expose a more specific model
ID or context-window size. Used reasoning, repository inspection, code
execution, and browser verification.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Dotta 2026-09-07 10:09:29 -05:00 committed by GitHub
parent 932ddb7b37
commit f6a211479f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
39 changed files with 272 additions and 103 deletions

View File

@ -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

View File

@ -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; \

View File

@ -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.

View File

@ -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). |

View File

@ -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"

View File

@ -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"
},

View File

@ -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",
)
};

View File

@ -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;

View File

@ -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()

View File

@ -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,

View File

@ -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",

View File

@ -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) {

View File

@ -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 });

View File

@ -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",

View File

@ -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 {

View File

@ -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,

View File

@ -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;
}

View File

@ -331,7 +331,7 @@ async function handle(message: RpcMessage): Promise<void> {
case "initialize":
result = {
user: { sessionId: "opencode" },
serverInfo: { name: "opencode", version: "1.18.17" },
serverInfo: { name: "opencode", version: "1.18.29" },
};
break;
case "thread/start":

View File

@ -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",
},
}),

View File

@ -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",

View File

@ -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",
});
});
});

View File

@ -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",

View File

@ -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<Uint8Array>({
@ -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();

View File

@ -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;

View File

@ -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);

View File

@ -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"

View File

@ -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

View File

@ -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", () => {

View File

@ -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, {

View File

@ -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"
},

View File

@ -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",

View File

@ -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"

View File

@ -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",

View File

@ -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: () => ({

View File

@ -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<unknown>;
};
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";

View File

@ -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,

View File

@ -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;

View File

@ -29,7 +29,7 @@ describe("Paperclip Runner Codex configuration", () => {
const html = renderRunner({ provider: "codex" });
expect(html).toContain('<option value="codex" selected="">Codex</option>');
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(
'<option value="opencode" selected="">OpenCode 1.18.17</option>',
'<option value="opencode" selected="">OpenCode 1.18.29</option>',
);
expect(html).toContain(
'<option value="allow" selected="">Full auto (allow)</option>',

View File

@ -236,7 +236,7 @@ export function CodexLocalConfigFields({
}}
>
<option value="codex">Codex</option>
<option value="opencode">OpenCode 1.18.17</option>
<option value="opencode">OpenCode 1.18.29</option>
<option value="claude_managed">Claude Managed</option>
<option value="aws_agentcore">AWS AgentCore</option>
<option value="acpx">ACPX</option>