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:
parent
932ddb7b37
commit
f6a211479f
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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; \
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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). |
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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":
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
},
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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", () => {
|
||||
|
|
|
|||
|
|
@ -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, {
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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: () => ({
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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>',
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in New Issue