mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-06 21:05:21 +02:00
## Thinking Path > - Paperclip runs agents through local adapters and the native runner. > - Both paths must use the same installed provider CLI. > - New models require current harness releases. > - The runner still pins Codex 0.153.4, Claude SDK 0.3.263, and OpenCode 1.18.29. > - Changing the image alone would fail the runner's exact version and executable checks. > - This pull request updates those dependencies, integrity checks, controller checks, and image pins together. > - Shared installations can then run the current models without a task-time download. ## Linked Issues or Issue Description Refs #13829, which updates model choices and reasoning controls. Searches found no open PR that updates these runtime pins. **Current behavior** The shared provider pack ships old CLIs. Claude Code 2.1.263 cannot run Opus 5.5, which requires 2.1.280. Remote controllers reject provider packs whose versions differ from their declared pins. **Proposed behavior** Use Codex 0.156.0, Claude Agent SDK 0.3.280 / Claude Code 2.1.280, and OpenCode 1.18.32 throughout the runner. Keep the reviewed ACP bridge patches and one shared CLI installation per provider. **Reason and benefit** Current harnesses support the new model IDs while preserving executable verification and remote provider-pack compatibility checks. ## What Changed - Update dependency overrides, the Codex ACP package patch, runtime profiles, and remote controller pins. - Verify the new Claude Linux x64 and macOS arm64/x64 executables and Codex Linux x64 executable against integrity-verified npm archives. - Refresh OpenCode version checks, fixtures, and the runner configuration label. - Refresh the eval image's Grok, Gemini, Kimi, Cursor, and GitHub CLI pins and archive hashes. Hermes remains current at 0.19.0. - Refresh the build-time lock digest from clean pnpm 9.15.4 resolution. Leave lockfile commits to repository automation. - Document model compatibility and the separation between CLI runtimes and patched ACP bridges. ## Verification - `pnpm -r typecheck` and `pnpm build` passed. - Rust workspace release tests passed. - Package/patch and OpenCode binary-materialization contract tests: 11 passed. - Real Codex 0.156.0 startup-ownership and paginated session-resume probes passed with isolated synthetic homes and no model turn. - Codex app-server `thread/start` preserved `gpt-6-sol` and `gpt-6-luna`; no `turn/start` was sent. An unauthenticated built-in catalog does not include those account-served entries. - Installed Claude integrity probes passed for `claude-opus-5-5` and `claude-fable-5-1`. - `pnpm --filter @paperclipai/paperclip-runner test:opencode:qualification` passed with the actual OpenCode 1.18.32 executable under Node 24 and Node 25. The loopback provider exercise covers health/version, session creation/read/delete, SSE, and a completed async prompt. - `pnpm check:token-gates` passed. - The targeted runner suite passed 130 tests. Three macOS failures in snapshot module lookup and OpenCode final-message selection also reproduce on the unchanged base; Linux CI will provide the platform check. - [Final Linux CI](https://github.com/paperclipai/paperclip/actions/runs/35798076399): all gates passed. Four jobs needed one retry after their CI workers received shutdown signals. The PR has 55 successful checks, two skipped checks, Greptile 5/5, and no unresolved review threads. - Changed runner configuration UI tests: 5 passed. - Full macOS `pnpm test:run` reached 13,094 passing server tests, 84 skipped, and 18 failures before the wrapper stopped. Failures involved skill-cache publication permissions, missing bundled connector skills in the worktree, and a conversation-reset timing case. The 10 cache permission failures reproduce on the unchanged base; both conversation-reset cases passed on a targeted retry. The wrapper did not reach its later workspace/serialized groups locally; Linux CI covers those groups. - The local Docker daemon did not respond, so no local Docker build was run. No billable model requests were made. ## Risks - Deploy the matching controller and provider pack together. Older controllers enforce their previous exact pins. - Current upstream CLIs can change behavior. Existing protocol tests and isolated real Codex probes cover the integration boundaries; authenticated model inference is not part of these checks. - ACP bridge package versions and executable digests stay unchanged because their executable bytes are unchanged. Only the underlying CLI/SDK dependencies move. - No schema migration. Revert the runtime and image pins together to roll back. ## Model Used OpenAI GPT-6 via Codex, with repository tools, code execution, and web research. The exact serving model ID and context window were not exposed by this session. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass for the changed surfaces and real-executable probes; full macOS-suite limitations are listed above - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing>
110 lines
3.3 KiB
JavaScript
110 lines
3.3 KiB
JavaScript
import { createHash } from "node:crypto";
|
|
import { spawnSync } from "node:child_process";
|
|
import {
|
|
chmodSync,
|
|
copyFileSync,
|
|
existsSync,
|
|
linkSync,
|
|
lstatSync,
|
|
readFileSync,
|
|
realpathSync,
|
|
unlinkSync,
|
|
} from "node:fs";
|
|
import { dirname, join, resolve } from "node:path";
|
|
import { pathToFileURL } from "node:url";
|
|
|
|
const OPENCODE_VERSION = "1.18.32";
|
|
const BASELINE_PACKAGE = "opencode-linux-x64-baseline";
|
|
|
|
function readPackage(path) {
|
|
return JSON.parse(readFileSync(path, "utf8"));
|
|
}
|
|
|
|
function sha256(path) {
|
|
return createHash("sha256").update(readFileSync(path)).digest("hex");
|
|
}
|
|
|
|
function assertPackage(packageRoot, expectedName) {
|
|
const packageJson = readPackage(join(packageRoot, "package.json"));
|
|
if (
|
|
packageJson.name !== expectedName ||
|
|
packageJson.version !== OPENCODE_VERSION
|
|
) {
|
|
throw new Error(
|
|
`Expected ${expectedName}@${OPENCODE_VERSION}, received ${String(packageJson.name)}@${String(packageJson.version)}`,
|
|
);
|
|
}
|
|
}
|
|
|
|
export function materializePinnedOpenCodeBinary(options = {}) {
|
|
const platform = options.platform ?? process.platform;
|
|
const architecture = options.architecture ?? process.arch;
|
|
if (platform !== "linux" || architecture !== "x64") {
|
|
throw new Error(
|
|
`Pinned OpenCode materialization requires linux/x64, received ${platform}/${architecture}`,
|
|
);
|
|
}
|
|
|
|
const packageRoot = realpathSync(
|
|
options.packageRoot ??
|
|
resolve(import.meta.dirname, "../node_modules/opencode-ai"),
|
|
);
|
|
const dependencyRoot = dirname(packageRoot);
|
|
const baselineRoot = realpathSync(join(dependencyRoot, BASELINE_PACKAGE));
|
|
assertPackage(packageRoot, "opencode-ai");
|
|
assertPackage(baselineRoot, BASELINE_PACKAGE);
|
|
|
|
const source = join(baselineRoot, "bin", "opencode");
|
|
const target = join(packageRoot, "bin", "opencode.exe");
|
|
if (!lstatSync(source).isFile()) {
|
|
throw new Error("Pinned OpenCode source executable is not a regular file");
|
|
}
|
|
if (existsSync(target)) {
|
|
if (!lstatSync(target).isFile()) {
|
|
throw new Error("OpenCode target executable is not a regular file");
|
|
}
|
|
unlinkSync(target);
|
|
}
|
|
try {
|
|
linkSync(source, target);
|
|
} catch (error) {
|
|
const code = error?.code;
|
|
if (!new Set(["EACCES", "EMLINK", "EPERM", "EXDEV"]).has(code)) {
|
|
throw error;
|
|
}
|
|
copyFileSync(source, target);
|
|
}
|
|
chmodSync(target, 0o755);
|
|
|
|
const sourceDigest = sha256(source);
|
|
const targetDigest = sha256(target);
|
|
if (sourceDigest !== targetDigest) {
|
|
throw new Error("Materialized OpenCode executable digest mismatch");
|
|
}
|
|
const targetStat = lstatSync(target);
|
|
const mode = targetStat.mode & 0o777;
|
|
if (!targetStat.isFile() || (mode & 0o111) === 0 || mode & 0o022) {
|
|
throw new Error("Materialized OpenCode executable has unsafe permissions");
|
|
}
|
|
|
|
const version = spawnSync(target, ["--version"], {
|
|
encoding: "utf8",
|
|
timeout: 30_000,
|
|
windowsHide: true,
|
|
});
|
|
if (version.status !== 0 || version.stdout.trim() !== OPENCODE_VERSION) {
|
|
throw new Error(
|
|
`Materialized OpenCode executable did not report ${OPENCODE_VERSION}`,
|
|
);
|
|
}
|
|
return { sourceDigest, target, version: OPENCODE_VERSION };
|
|
}
|
|
|
|
const invokedPath = process.argv[1]
|
|
? pathToFileURL(realpathSync(process.argv[1])).href
|
|
: null;
|
|
if (invokedPath === import.meta.url) {
|
|
const result = materializePinnedOpenCodeBinary();
|
|
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
}
|