From aa75ec9d56ee4e045cc06daa9fb0ffe8dc8a793a Mon Sep 17 00:00:00 2001 From: Nicky Leach Date: Thu, 29 Oct 2026 01:45:18 -0700 Subject: [PATCH] Replay Claude native options on ACP reconnect (#18135) ## Thinking Path > - Paperclip manages agents and their task execution. > - Runner uses ACPX to connect to Claude sessions. > - Claude receives native options when ACPX creates a session. > - ACPX omits those options when it loads or resumes the same session. > - A reconnect can lose allowed tools, instructions, and settings sources. > - This pull request replays the existing options through the existing Claude metadata contract. > - Resumed tasks keep the configuration admitted for their session. ## Linked Issues or Issue Description **What happened?** A Claude task can complete its first turn and then fail on a warm follow-up. The provider refuses an internal completion tool because `session/load` omits the `allowedTools` options sent with `session/new`. The same omission exists on `session/resume`. **Expected behavior** Creation, load, and resume must supply the same caller-selected native session options. The existing policy for native user settings must stay unchanged. **Steps to reproduce** Create a persistent Claude ACP session with an explicit allowed-tool list and appended instructions. Complete a turn, close its connection, and reconnect to the saved session. Inspect the ACP request. Before this change, the reconnect has no `_meta` field. **Paperclip version or commit** Reproduced on Runner source `e8b78cce5bb557b086bee9ca930eeaab1d09b244`. The offline protocol regression also fails on the current base `80a2b9284bf189e93ca4313085814548f48b8dc8`. Related: #18089 added prepared harness storage. #11798 concerns the separate legacy adapter and does not fix Runner reconnect metadata. ## Upstream status and backport scope ACPX [0.21.1](https://github.com/openclaw/acpx/releases/tag/v0.21.1) already replays session metadata on load and resume in its [released client implementation](https://github.com/openclaw/acpx/blob/v0.21.1/src/acp/client.ts). This PR backports that behavior to the currently qualified 0.13.1 integration. The released 0.21.1 runtime does not expose equivalents for all hooks used by Runner's existing patch: verified executable spawning, synchronous protocol admission guards, provider extension dispatch, and response-delivery receipts. Upgrading requires a compatibility pass that preserves these guarantees and removes local changes already covered upstream. This backport does not add a new Paperclip-specific dependency contract. ## What Changed - Replay existing Claude native options on `session/load` and `session/resume` through the same metadata builder used at creation. Other providers keep their existing reconnect requests. - Test both reconnect methods against the installed ACPX manager and a local ACP wire fixture. Cover allowed tools, model, turn limit, instructions, and both existing settings-source modes. - Update exact source attestations and generated profile declarations required by the shared ACPX patch. Keep historical fixtures unchanged. - Document the reconnect contract. No new home mode, plugin policy, or provider permission rule is added. ## Verification - Confirmed the new load and resume tests fail before the patch because metadata is absent. - Applied the canonical patch to pristine package bytes and confirmed it matches the tested runtime. - Targeted installed-package model-selection and reconnect suite plus changed Grok identity tests: 22 passed. - Rechecked the four new reconnect cases against the cleanly applied patch, with explicit expected values for all supplied options. - Three targeted current-profile and revision-boundary assertions pass locally. Updated the native Pi test digest; native compilation and execution remain in CI. - Maintained profile generators pass their consistency checks. - GitHub CI is green, including typecheck, native Runner checks, package contracts, packaged installation, build, and server/browser suites. One unrelated server database-cleanup timeout passed on the bounded failed-job rerun. No comprehensive local suite was run. Live post-deployment acceptance remains pending. ## Risks Claude reconnects now receive the same native settings sources and options as creation. This corrects configuration loss without changing caller policy. The shared dependency patch requires new source identities for other attested profiles; their reconnect behavior is unchanged. No database migration is required. ## Model Used OpenAI Codex, GPT-6 family, with code and tool use. Exact deployment ID and context-window size are not exposed in 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 - [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 --- packages/paperclip-runner/README.md | 4 +- packages/paperclip-runner/acpx-profiles.json | 16 ++-- .../paperclip-runner/cursor-contract.json | 6 +- .../runner-core/src/acpx_provider_backend.rs | 2 +- .../src/generated_acpx_profiles.rs | 8 +- .../tests/native_provider_backend.rs | 2 +- .../scripts/copilot-profile-identity.mjs | 4 +- .../scripts/grok-profile-identity.mjs | 4 +- .../drivers/acpx/copilot-installation.test.ts | 4 +- .../src/drivers/acpx/generated-profiles.ts | 16 ++-- .../src/drivers/acpx/pi-installation.test.ts | 30 +++---- .../acpx/profile-compatibility.test.ts | 3 +- .../src/drivers/acpx/profile-compatibility.ts | 6 +- .../src/providers/grok/launcher.cjs | 2 +- .../pi-acp/profile-v33-identity.json | 52 +++++++++++ ...-model-selection-package-contract.test.mjs | 31 ++++++- .../copilot-profile-v79-identity.json | 88 +++++++++++++++++++ .../fixtures/grok-profile-v11-identity.json | 35 ++++++++ patches/acpx@0.13.1.patch | 79 ++++++++++++----- scripts/pi-public-install-probe.mjs | 4 +- 20 files changed, 315 insertions(+), 81 deletions(-) create mode 100644 packages/paperclip-runner/test-fixtures/pi-acp/profile-v33-identity.json create mode 100644 packages/paperclip-runner/test/fixtures/copilot-profile-v79-identity.json create mode 100644 packages/paperclip-runner/test/fixtures/grok-profile-v11-identity.json diff --git a/packages/paperclip-runner/README.md b/packages/paperclip-runner/README.md index a1c76089dfd..4ba8686aec5 100644 --- a/packages/paperclip-runner/README.md +++ b/packages/paperclip-runner/README.md @@ -857,7 +857,9 @@ Prepare authentication, skills, and external MCP servers on the execution machin before launching. Runner does not install skills, copy or remove credentials, or rewrite user configuration in this mode. Providers can still refresh credentials and update their own metadata. Runner still supplies the requested -model, task instructions, and its internal task/completion tools. Local permission +model, task instructions, and its internal task/completion tools. Claude session +loads and resumes receive the same native session options as session creation, +including allowed tools and the selected settings sources. Local permission rules and hooks apply. Native Codex tools can read and modify the execution home (read-only for planning); credentials in that home are not isolated from tools. Native Codex separately denies Runner-owned protocol state; ACPX Claude retains diff --git a/packages/paperclip-runner/acpx-profiles.json b/packages/paperclip-runner/acpx-profiles.json index 21191338126..d9fb690f000 100644 --- a/packages/paperclip-runner/acpx-profiles.json +++ b/packages/paperclip-runner/acpx-profiles.json @@ -5,39 +5,39 @@ "protocolVersion": 1, "profiles": { "grok": { - "agentProfileVersion": 10, + "agentProfileVersion": 11, "agentServerPackage": "builtin:grok-acp", "agentServerVersion": "1", "agentRuntimePackage": "native:grok", "agentRuntimeVersion": "1.0.13", - "commandDigest": "sha256:2dccee3800dada11b65692e5cf591e3667a123cba2fbf5629a937f731043b423", + "commandDigest": "sha256:f43a6f5a583972cdcb422784969cd6a97654655b0f135f1cf178317909c404f8", "requiresProviderPolicy": false }, "pi": { - "agentProfileVersion": 32, + "agentProfileVersion": 33, "agentServerPackage": "pi-acp", "agentServerVersion": "0.0.33", "agentRuntimePackage": "@earendil-works/pi-coding-agent", "agentRuntimeVersion": "1.0.0", - "commandDigest": "sha256:ce13b75699331c813f8a42f2803c3e0e0fb1e6d24847452cb12b50d6ed2d0baf", + "commandDigest": "sha256:0260d5810e9247986800ad46c1f9b3511a5abd06cb8f142405d5caacbc3960fc", "requiresProviderPolicy": true }, "cursor": { - "agentProfileVersion": 15, + "agentProfileVersion": 16, "agentServerPackage": "cursor-agent", "agentServerVersion": "2026.09.26-dd393fe", "agentRuntimePackage": null, "agentRuntimeVersion": null, - "commandDigest": "sha256:ac8092119542c8fbe95dae18ba5ef4d3689803fec56b7d2735fa193eea42f59b", + "commandDigest": "sha256:759e7415fea8ed2b6b0e6f362b57652b8768835f9d1fdae4a94c1b5eb3669301", "requiresProviderPolicy": true }, "copilot": { - "agentProfileVersion": 78, + "agentProfileVersion": 79, "agentServerPackage": "@github/copilot", "agentServerVersion": "1.0.88", "agentRuntimePackage": null, "agentRuntimeVersion": null, - "commandDigest": "sha256:f7e7cff356db4b1b37458bd62fd76e9d81aa1cc46795e54b168755fb68bcee8c", + "commandDigest": "sha256:a0e9f339935f6ee616fee644c45cb09353e9aed1d1f22215739f9504153ceca1", "requiresProviderPolicy": true }, "claude": { diff --git a/packages/paperclip-runner/cursor-contract.json b/packages/paperclip-runner/cursor-contract.json index e1d6881b96c..be413ac559e 100644 --- a/packages/paperclip-runner/cursor-contract.json +++ b/packages/paperclip-runner/cursor-contract.json @@ -1,11 +1,11 @@ { "schema": "paperclip.cursor.production-contract.v1", - "profileVersion": 15, + "profileVersion": 16, "sourceSnapshot": "22c78242a4e0c2369fecf0c2dc4e7600fbad6706", "base": "dd868ed125cd709506dd9b29fca640a44d580501", "vendor": "2026.09.26-dd393fe", "nativePatch": "paperclip-cursor-usage-v4", - "acpxPatchSha256": "00d6af17216cb0cad902b48633eb19bf0ec6b05837a984cb24661fca593f09c1", + "acpxPatchSha256": "1e7dae8c83bf8620f53f2bea13c175eeff95e7e955783f6f529bb1dcbdcdb6d2", "publicSetup": "paperclipai runtime setup cursor", "assets": "public-server-and-provider-pack", "modes": [ @@ -19,5 +19,5 @@ "planAcceptance": "successful-run-open-task-explicit-continuation", "otherProviders": "mainline-gated", "modelSelection": "explicit-provider-verified-startup-and-reconnect", - "commandDigest": "sha256:ac8092119542c8fbe95dae18ba5ef4d3689803fec56b7d2735fa193eea42f59b" + "commandDigest": "sha256:759e7415fea8ed2b6b0e6f362b57652b8768835f9d1fdae4a94c1b5eb3669301" } diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/acpx_provider_backend.rs b/packages/paperclip-runner/runner/crates/runner-core/src/acpx_provider_backend.rs index 5439fbeb559..94c73c304d2 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/src/acpx_provider_backend.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/src/acpx_provider_backend.rs @@ -3246,7 +3246,7 @@ mod tests { #[test] fn pi_profile_matches_published_identity_and_rejects_prior_profiles() { let published: Value = serde_json::from_str(include_str!( - "../../../../test-fixtures/pi-acp/profile-v32-identity.json" + "../../../../test-fixtures/pi-acp/profile-v33-identity.json" )) .unwrap(); let mut value = descriptor("codex"); diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/generated_acpx_profiles.rs b/packages/paperclip-runner/runner/crates/runner-core/src/generated_acpx_profiles.rs index 1be4fa5e3d5..f61400ce917 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/src/generated_acpx_profiles.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/src/generated_acpx_profiles.rs @@ -20,7 +20,7 @@ pub(crate) fn acpx_release_profile(agent: &str) -> Option { agent_runtime_package: Some("native:grok"), agent_runtime_version: Some("1.0.13"), command_digest: - "sha256:2dccee3800dada11b65692e5cf591e3667a123cba2fbf5629a937f731043b423", + "sha256:f43a6f5a583972cdcb422784969cd6a97654655b0f135f1cf178317909c404f8", requires_provider_policy: false, }, "pi" => AcpxReleaseProfile { @@ -29,7 +29,7 @@ pub(crate) fn acpx_release_profile(agent: &str) -> Option { agent_runtime_package: Some("@earendil-works/pi-coding-agent"), agent_runtime_version: Some("1.0.0"), command_digest: - "sha256:ce13b75699331c813f8a42f2803c3e0e0fb1e6d24847452cb12b50d6ed2d0baf", + "sha256:0260d5810e9247986800ad46c1f9b3511a5abd06cb8f142405d5caacbc3960fc", requires_provider_policy: true, }, "cursor" => AcpxReleaseProfile { @@ -38,7 +38,7 @@ pub(crate) fn acpx_release_profile(agent: &str) -> Option { agent_runtime_package: None, agent_runtime_version: None, command_digest: - "sha256:ac8092119542c8fbe95dae18ba5ef4d3689803fec56b7d2735fa193eea42f59b", + "sha256:759e7415fea8ed2b6b0e6f362b57652b8768835f9d1fdae4a94c1b5eb3669301", requires_provider_policy: true, }, "copilot" => AcpxReleaseProfile { @@ -47,7 +47,7 @@ pub(crate) fn acpx_release_profile(agent: &str) -> Option { agent_runtime_package: None, agent_runtime_version: None, command_digest: - "sha256:f7e7cff356db4b1b37458bd62fd76e9d81aa1cc46795e54b168755fb68bcee8c", + "sha256:a0e9f339935f6ee616fee644c45cb09353e9aed1d1f22215739f9504153ceca1", requires_provider_policy: true, }, "claude" => AcpxReleaseProfile { diff --git a/packages/paperclip-runner/runner/crates/runner-core/tests/native_provider_backend.rs b/packages/paperclip-runner/runner/crates/runner-core/tests/native_provider_backend.rs index d1e144de057..b9b58e8e9be 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/tests/native_provider_backend.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/tests/native_provider_backend.rs @@ -17,7 +17,7 @@ use sha2::{Digest, Sha256}; const CODEX_ACPX_DIGEST: &str = "sha256:443491f4e814501a5dc47347e4510e84faeec9c9382e2d4e53bfe69386d5aabc"; const PI_ACPX_DIGEST: &str = - "sha256:ce13b75699331c813f8a42f2803c3e0e0fb1e6d24847452cb12b50d6ed2d0baf"; + "sha256:0260d5810e9247986800ad46c1f9b3511a5abd06cb8f142405d5caacbc3960fc"; fn temporary_directory(label: &str) -> PathBuf { let nonce = SystemTime::now() diff --git a/packages/paperclip-runner/scripts/copilot-profile-identity.mjs b/packages/paperclip-runner/scripts/copilot-profile-identity.mjs index 85f0ca11194..c92ee2ad192 100644 --- a/packages/paperclip-runner/scripts/copilot-profile-identity.mjs +++ b/packages/paperclip-runner/scripts/copilot-profile-identity.mjs @@ -7,9 +7,9 @@ import { fileURLToPath } from "node:url"; import { resolve, dirname } from "node:path"; const root = resolve(dirname(fileURLToPath(import.meta.url)), ".."); -const fixture = resolve(root, "test/fixtures/copilot-profile-v78-identity.json"); +const fixture = resolve(root, "test/fixtures/copilot-profile-v79-identity.json"); const historical = JSON.parse(readFileSync(resolve(root, "test/fixtures/copilot-profile-v12-identity.json"), "utf8")); -const declaration = { ...historical.declaration, agentProfileVersion: 78 }; +const declaration = { ...historical.declaration, agentProfileVersion: 79 }; delete declaration.permissionIdentitySourceSha256; const sources = { displayEventSourceSha256: "src/drivers/acpx/copilot-events.ts", diff --git a/packages/paperclip-runner/scripts/grok-profile-identity.mjs b/packages/paperclip-runner/scripts/grok-profile-identity.mjs index 7ee11031fe5..e0210feb2e2 100644 --- a/packages/paperclip-runner/scripts/grok-profile-identity.mjs +++ b/packages/paperclip-runner/scripts/grok-profile-identity.mjs @@ -7,9 +7,9 @@ import { fileURLToPath } from "node:url"; const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), ".."); const repositoryRoot = resolve(packageRoot, "../.."); -// Versions 1–9 remain decodable historical identities. Versioned fixtures are +// Versions 1–10 remain decodable historical identities. Versioned fixtures are // immutable; advance this version before changing a versioned declaration. -export const GROK_PROFILE_VERSION = 10; +export const GROK_PROFILE_VERSION = 11; // Contract surfaces: ACPX wire patch; sidecar parsing and error translation; // native effort/search/credential admission; host/sandbox/tool-bridge execution; // launcher integrity and platform pins; Rust descriptor writes, session state, diff --git a/packages/paperclip-runner/src/drivers/acpx/copilot-installation.test.ts b/packages/paperclip-runner/src/drivers/acpx/copilot-installation.test.ts index 58ddfd32d60..82812032921 100644 --- a/packages/paperclip-runner/src/drivers/acpx/copilot-installation.test.ts +++ b/packages/paperclip-runner/src/drivers/acpx/copilot-installation.test.ts @@ -87,9 +87,9 @@ vi.mock("./installation-integrity.js", () => ({ verifyNativeAcpxInstallation: vi describe("Copilot build-owned installation", () => { it("admits the current profile with environment launch preparation", () => { - expect(QUALIFIED_ACPX_PROFILES.copilot.agentProfileVersion).toBe(78); + expect(QUALIFIED_ACPX_PROFILES.copilot.agentProfileVersion).toBe(79); expect(QUALIFIED_ACPX_PROFILES.copilot.qualificationStatus).toBeUndefined(); - expect(QUALIFIED_ACPX_PROFILES.copilot.commandDigest).toBe("sha256:f7e7cff356db4b1b37458bd62fd76e9d81aa1cc46795e54b168755fb68bcee8c"); + expect(QUALIFIED_ACPX_PROFILES.copilot.commandDigest).toBe("sha256:a0e9f339935f6ee616fee644c45cb09353e9aed1d1f22215739f9504153ceca1"); }); it("retains the published profile 75 fixture while advancing the event contract", async () => { diff --git a/packages/paperclip-runner/src/drivers/acpx/generated-profiles.ts b/packages/paperclip-runner/src/drivers/acpx/generated-profiles.ts index 240ab6baa28..4512b8cfb92 100644 --- a/packages/paperclip-runner/src/drivers/acpx/generated-profiles.ts +++ b/packages/paperclip-runner/src/drivers/acpx/generated-profiles.ts @@ -9,12 +9,12 @@ export const QUALIFIED_ACPX_PROFILE_DATA = { "protocolVersion": 1, "acpxVersion": "0.13.1", "agent": "grok", - "agentProfileVersion": 10, + "agentProfileVersion": 11, "agentServerPackage": "builtin:grok-acp", "agentServerVersion": "1", "agentRuntimePackage": "native:grok", "agentRuntimeVersion": "1.0.13", - "commandDigest": "sha256:2dccee3800dada11b65692e5cf591e3667a123cba2fbf5629a937f731043b423", + "commandDigest": "sha256:f43a6f5a583972cdcb422784969cd6a97654655b0f135f1cf178317909c404f8", "permissionPolicy": "interactive" }, "pi": { @@ -22,12 +22,12 @@ export const QUALIFIED_ACPX_PROFILE_DATA = { "protocolVersion": 1, "acpxVersion": "0.13.1", "agent": "pi", - "agentProfileVersion": 32, + "agentProfileVersion": 33, "agentServerPackage": "pi-acp", "agentServerVersion": "0.0.33", "agentRuntimePackage": "@earendil-works/pi-coding-agent", "agentRuntimeVersion": "1.0.0", - "commandDigest": "sha256:ce13b75699331c813f8a42f2803c3e0e0fb1e6d24847452cb12b50d6ed2d0baf", + "commandDigest": "sha256:0260d5810e9247986800ad46c1f9b3511a5abd06cb8f142405d5caacbc3960fc", "permissionPolicy": "interactive" }, "cursor": { @@ -35,12 +35,12 @@ export const QUALIFIED_ACPX_PROFILE_DATA = { "protocolVersion": 1, "acpxVersion": "0.13.1", "agent": "cursor", - "agentProfileVersion": 15, + "agentProfileVersion": 16, "agentServerPackage": "cursor-agent", "agentServerVersion": "2026.09.26-dd393fe", "agentRuntimePackage": null, "agentRuntimeVersion": null, - "commandDigest": "sha256:ac8092119542c8fbe95dae18ba5ef4d3689803fec56b7d2735fa193eea42f59b", + "commandDigest": "sha256:759e7415fea8ed2b6b0e6f362b57652b8768835f9d1fdae4a94c1b5eb3669301", "permissionPolicy": "interactive" }, "copilot": { @@ -48,12 +48,12 @@ export const QUALIFIED_ACPX_PROFILE_DATA = { "protocolVersion": 1, "acpxVersion": "0.13.1", "agent": "copilot", - "agentProfileVersion": 78, + "agentProfileVersion": 79, "agentServerPackage": "@github/copilot", "agentServerVersion": "1.0.88", "agentRuntimePackage": null, "agentRuntimeVersion": null, - "commandDigest": "sha256:f7e7cff356db4b1b37458bd62fd76e9d81aa1cc46795e54b168755fb68bcee8c", + "commandDigest": "sha256:a0e9f339935f6ee616fee644c45cb09353e9aed1d1f22215739f9504153ceca1", "permissionPolicy": "interactive" }, "claude": { diff --git a/packages/paperclip-runner/src/drivers/acpx/pi-installation.test.ts b/packages/paperclip-runner/src/drivers/acpx/pi-installation.test.ts index ae80f0e1570..58921588738 100644 --- a/packages/paperclip-runner/src/drivers/acpx/pi-installation.test.ts +++ b/packages/paperclip-runner/src/drivers/acpx/pi-installation.test.ts @@ -210,21 +210,21 @@ describe("Pi installation factory", () => { it("rejects legacy profiles and tampered executable identities", () => { expect(() => assertPiInstallationProfile(candidate())).not.toThrow(); - expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 1 })).toThrow("version 32"); - expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 2 })).toThrow("version 32"); - expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 3 })).toThrow("version 32"); - expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 4 })).toThrow("version 32"); - expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 5 })).toThrow("version 32"); - expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 6 })).toThrow("version 32"); - expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 7 })).toThrow("version 32"); - expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 8 })).toThrow("version 32"); - expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 9 })).toThrow("version 32"); - expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 10 })).toThrow("version 32"); - expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 11 })).toThrow("version 32"); - expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 12 })).toThrow("version 32"); - expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 13 })).toThrow("version 32"); - expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 14 })).toThrow("version 32"); - for (const priorVersion of [15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]) expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: priorVersion })).toThrow("version 32"); + expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 1 })).toThrow("version 33"); + expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 2 })).toThrow("version 33"); + expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 3 })).toThrow("version 33"); + expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 4 })).toThrow("version 33"); + expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 5 })).toThrow("version 33"); + expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 6 })).toThrow("version 33"); + expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 7 })).toThrow("version 33"); + expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 8 })).toThrow("version 33"); + expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 9 })).toThrow("version 33"); + expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 10 })).toThrow("version 33"); + expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 11 })).toThrow("version 33"); + expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 12 })).toThrow("version 33"); + expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 13 })).toThrow("version 33"); + expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 14 })).toThrow("version 33"); + for (const priorVersion of [15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]) expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: priorVersion })).toThrow("version 33"); for (const changed of [{ agentServerVersion: "latest" }, { commandDigest: `sha256:${"0".repeat(64)}` }, { agentRuntimePackage: "ambient-pi" }]) { expect(() => assertPiInstallationProfile({ ...candidate(), ...changed })).toThrow("trusted declaration"); } diff --git a/packages/paperclip-runner/src/drivers/acpx/profile-compatibility.test.ts b/packages/paperclip-runner/src/drivers/acpx/profile-compatibility.test.ts index 7826bd202c2..57b35807402 100644 --- a/packages/paperclip-runner/src/drivers/acpx/profile-compatibility.test.ts +++ b/packages/paperclip-runner/src/drivers/acpx/profile-compatibility.test.ts @@ -10,7 +10,8 @@ describe("historical ACPX profile decoding", () => { expect(isSupportedAcpxProfileVersion("pi", 18)).toBe(true); expect(isSupportedAcpxProfileVersion("pi", 23)).toBe(true); expect(isSupportedAcpxProfileVersion("cursor", 15)).toBe(true); - expect(isSupportedAcpxProfileVersion("cursor", 16)).toBe(false); + expect(isSupportedAcpxProfileVersion("cursor", 16)).toBe(true); + expect(isSupportedAcpxProfileVersion("cursor", 17)).toBe(false); for (const agent of ["pi", "copilot"]) { expect(isSupportedAcpxProfileVersion(agent, 16)).toBe(true); const current = QUALIFIED_ACPX_PROFILE_DATA[agent as "pi" | "copilot"].agentProfileVersion; diff --git a/packages/paperclip-runner/src/drivers/acpx/profile-compatibility.ts b/packages/paperclip-runner/src/drivers/acpx/profile-compatibility.ts index c68a13527b1..15f892b66c5 100644 --- a/packages/paperclip-runner/src/drivers/acpx/profile-compatibility.ts +++ b/packages/paperclip-runner/src/drivers/acpx/profile-compatibility.ts @@ -2,13 +2,13 @@ import { QUALIFIED_ACPX_PROFILE_DATA } from "./generated-profiles.js"; /** Decodable historical profile revisions, not permission to launch them. * Exact current profile/package/digest admission is checked separately. */ -type HistoricalAcpxProfileVersion = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77; +type HistoricalAcpxProfileVersion = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78; type CurrentAcpxProfileVersion = typeof QUALIFIED_ACPX_PROFILE_DATA[keyof typeof QUALIFIED_ACPX_PROFILE_DATA]["agentProfileVersion"]; export type AcpxProfileVersion = HistoricalAcpxProfileVersion | CurrentAcpxProfileVersion; const historicalVersions: Readonly> = { - pi: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31], claude: [1, 2, 3, 4, 5], codex: [1, 2, 3, 4, 5], - grok: [1, 2, 3, 4, 5, 6, 7, 8, 9], copilot: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77], + pi: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32], claude: [1, 2, 3, 4, 5], codex: [1, 2, 3, 4, 5], + grok: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], copilot: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78], cursor: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], }; diff --git a/packages/paperclip-runner/src/providers/grok/launcher.cjs b/packages/paperclip-runner/src/providers/grok/launcher.cjs index 8bba68652b3..ec47bb32d29 100755 --- a/packages/paperclip-runner/src/providers/grok/launcher.cjs +++ b/packages/paperclip-runner/src/providers/grok/launcher.cjs @@ -1,4 +1,4 @@ -// Grok admission source identity: sha256:1fec0323461ed2e0b1d0172d205237ba23316a7e6a89c325b6eb3be907e373e3 +// Grok admission source identity: sha256:c5a4628adcdd9190c45558f42014b0055698b8f8c7eb301417238f38d913f503 // This launcher is admitted by digest and receives a verified native executable. // Never resolve an executable through PATH or accept command-line overrides. const { isAbsolute, join } = require("node:path"); diff --git a/packages/paperclip-runner/test-fixtures/pi-acp/profile-v33-identity.json b/packages/paperclip-runner/test-fixtures/pi-acp/profile-v33-identity.json new file mode 100644 index 00000000000..2eaf38dcde2 --- /dev/null +++ b/packages/paperclip-runner/test-fixtures/pi-acp/profile-v33-identity.json @@ -0,0 +1,52 @@ +{ + "commandDigest": "sha256:0260d5810e9247986800ad46c1f9b3511a5abd06cb8f142405d5caacbc3960fc", + "declaration": { + "schema": "paperclip.acpx_profile_declaration.v1", + "agent": "pi", + "agentProfileVersion": 33, + "acpxVersion": "0.13.1", + "agentServerVersion": "0.0.33", + "agentRuntimeVersion": "1.0.0", + "nodeVersion": "24.21.0", + "closure": { + "darwin-arm64": "282022db10150c6632b3444df421342e7d534bdf5d5fb1097a2e79d0625a2bcf", + "darwin-x64": "64e251e19009f755c0b04f73ce2138246faab71a961b0f13d75ebfcc34bef12e", + "linux-x64": "713b1fdff42fb56a1518bdc084f181d70bee8ebadc3e4b1d76321ed9108c8410" + }, + "wrapperSha256": "9d129b3d38772e93e97080aa6c4e574ac5df4e47ce5bc331a484a9fbf8188b36", + "helperSha256": "41e0490b617da0d60e0c8ec58ef311236f945129d99f816cff6897f78da7f91d", + "extensionSha256": "f5818573a355702388072b28926897db546a5cf346b5171f646582ce5f9102a4", + "agentFilesBinding": "registered-runtime-context-v1", + "nativeQuestions": "select-confirm-input-editor-v4-root-field-types", + "pathPolicy": "sdk-normalization-v1", + "mcpToolNames": "provider-safe-exact-source-v1", + "acpxPatchSha256": "1e7dae8c83bf8620f53f2bea13c175eeff95e7e955783f6f529bb1dcbdcdb6d2", + "nativeAssistantMessages": "pi1-rpc-deltas-v3-bound-message-and-tool-receipts", + "nativeNotices": "session-extension-v2-bounded-runtime-failure", + "messageProjectionSha256": "17d1e91b948dfa6a56e1905645add52b502041ad843c8d50a13b07956f22aadd", + "noticeProjectionSha256": "014485adc690998e395d334ea3ae67f72be6060a2fc78a1185df7b8d1da28fbf", + "sharedRuntimeContract": "paperclip.acpx-runtime-contract.v1", + "nativeInputDisposition": "queued-only-v1", + "cacheWarming": "disabled-owned-decision-v1", + "thinkingLevel": { + "supported": [ + "off", + "low", + "high", + "max" + ], + "newConfigurationDefault": "low", + "admission": "explicit-native-effective-before-prompt-v1", + "recovery": "mode-bound-v1" + }, + "modelSelection": "caller-selected-native-verified-v1", + "providerConfigurationSourceSha256": "75a9a13f13f224dc092bd8ebf49e8747162172943761e44476c8eb18d80c52d4", + "credentialEnvironmentSourceSha256": "3a93267146bfc3c489f43262386d6dcb05ec4e3a8b1765414cd43d80d0c64d4a", + "runtimeSandboxSourceSha256": "04a49a24e3828ce8fca908de15e31ae932b8b1b4f7ecec0308627f6a8da9ba20", + "recoveryIdentitySourceSha256": "f74d061cc07b0c7ca04df62ca991d940a733d9ef6594b3362451769fc10030b8", + "nativeCancellation": "acknowledged-cancelled-terminal-preserves-usage-v1", + "dependencySecurityPatchSha256": "5273a195b952f233336122842faaf650193c04f36b9359d20d4d63079b0ce814", + "openRouterAttributionSourceSha256": "1211191529d6073b733b311c3d69cada4973eede0f3e540c46e261f78e0b90f9", + "preparedHarnessHomeSourceSha256": "bb286f7ddc6e9597c56d46bf7ff6e3b556c5b680d7e147fd46518724432b083e" + } +} diff --git a/packages/paperclip-runner/test/acpx-model-selection-package-contract.test.mjs b/packages/paperclip-runner/test/acpx-model-selection-package-contract.test.mjs index 5e9b1771b69..ca099899807 100644 --- a/packages/paperclip-runner/test/acpx-model-selection-package-contract.test.mjs +++ b/packages/paperclip-runner/test/acpx-model-selection-package-contract.test.mjs @@ -37,8 +37,8 @@ async function fixture(t, agent, behavior = {}) { options:[{value:'advertised-model',name:'Advertised'},{value:'custom/model[variant=x]',name:'Variant'}]}]; readline.createInterface({input:process.stdin}).on('line',line=>{ const m=JSON.parse(line); - if(m.method==='initialize') send({id:m.id,result:{protocolVersion:1,agentCapabilities:{loadSession:true},authMethods:[]}}); - else if(m.method==='session/new'||m.method==='session/load') send({id:m.id,result:{sessionId:'session-1', + if(m.method==='initialize') send({id:m.id,result:{protocolVersion:1,agentCapabilities:${JSON.stringify({loadSession:true,...(behavior.resume ? {sessionCapabilities:{resume:{}}} : {})})},authMethods:[]}}); + else if(m.method==='session/new'||m.method==='session/load'||m.method==='session/resume') send({id:m.id,result:{sessionId:'session-1', ...(${JSON.stringify(response === "missing")} ? {} : {configOptions:config(${JSON.stringify(behavior.currentModel ?? "default")}), models:{currentModelId:${JSON.stringify(behavior.currentModel ?? "default")},availableModels:[{modelId:'advertised-model',name:'Advertised'},{modelId:'custom/model[variant=x]',name:'Variant'}]}})}}); else if(m.method==='session/set_config_option') { @@ -72,7 +72,7 @@ async function fixture(t, agent, behavior = {}) { async open() { const runtime = createAcpRuntime(options), owner = { runtime, handle: null }; runtimes.push(owner); - owner.handle = await runtime.ensureSession({ sessionKey: "model-selection", agent, mode: "persistent", cwd, sessionOptions: { model: selectedModel } }); + owner.handle = await runtime.ensureSession({ sessionKey: "model-selection", agent, mode: "persistent", cwd, sessionOptions: { model: selectedModel, ...behavior.sessionOptions } }); return owner; }, async prompt(owner) { @@ -143,3 +143,28 @@ test("Cursor command detection never expands a model into an advertised variant" assert.equal((await f.prompt(owner)).status, "completed"); assert.deepEqual(f.selected(), [selectedModel]); }); + +for (const includeUserSettings of [false, true]) for (const resume of [false, true]) test(`Claude ${resume ? "resume" : "load"} preserves native session options (user settings ${includeUserSettings})`, { timeout: 10_000 }, async t => { + const prior = process.env.ACPX_CLAUDE_INCLUDE_USER_SETTINGS; + if (includeUserSettings) process.env.ACPX_CLAUDE_INCLUDE_USER_SETTINGS = "1"; + else delete process.env.ACPX_CLAUDE_INCLUDE_USER_SETTINGS; + t.after(() => { if (prior === undefined) delete process.env.ACPX_CLAUDE_INCLUDE_USER_SETTINGS; else process.env.ACPX_CLAUDE_INCLUDE_USER_SETTINGS = prior; }); + const f = await fixture(t, "claude", { + resume, agentCommand: "/paperclip-verified/claude-agent-acp", + sessionOptions: { allowedTools: ["mcp__fixture__*"], maxTurns: 5, systemPrompt: { append: "Fixture instructions" } }, + }); + const first = await f.open(); + assert.equal((await f.prompt(first)).status, "completed"); + await f.close(first); + const second = await f.open(); + assert.equal((await f.prompt(second)).status, "completed"); + const created = f.requests.find(r => r.method === "session/new"); + const restored = f.requests.find(r => r.method === (resume ? "session/resume" : "session/load")); + assert.ok(restored, "the provider session reconnects"); + assert.equal(created.params._meta.claudeCode.options.model, selectedModel); + assert.equal(created.params._meta.claudeCode.options.maxTurns, 5); + assert.deepEqual(created.params._meta.systemPrompt, { append: "Fixture instructions" }); + assert.deepEqual(created.params._meta.claudeCode.options.allowedTools, ["mcp__fixture__*"]); + assert.deepEqual(created.params._meta.claudeCode.options.settingSources, includeUserSettings ? ["user", "project", "local"] : ["project", "local"]); + assert.deepEqual(restored.params._meta, created.params._meta, "reconnect must preserve the requested native launch settings"); +}); diff --git a/packages/paperclip-runner/test/fixtures/copilot-profile-v79-identity.json b/packages/paperclip-runner/test/fixtures/copilot-profile-v79-identity.json new file mode 100644 index 00000000000..27835e067e9 --- /dev/null +++ b/packages/paperclip-runner/test/fixtures/copilot-profile-v79-identity.json @@ -0,0 +1,88 @@ +{ + "commandDigest": "sha256:a0e9f339935f6ee616fee644c45cb09353e9aed1d1f22215739f9504153ceca1", + "declaration": { + "schema": "paperclip.acpx_profile_declaration.v1", + "agent": "copilot", + "agentProfileVersion": 79, + "acpxVersion": "0.13.1", + "agentServerVersion": "1.0.88", + "protocolPolicyRevision": "copilot-agent-manual-v1", + "acpxPatchSha256": "1e7dae8c83bf8620f53f2bea13c175eeff95e7e955783f6f529bb1dcbdcdb6d2", + "policySha256": "a4be69be43db84052560420a3ca546a665c9165573c24d543eb33d144ba138a3", + "distributionSourceSha256": "c0a65e247fc47b263bd369a3d5ab505c4b323d4e6493be8a9ecbeb0e9dbb9107", + "agentFilesBinding": "registered-runtime-context-v1", + "systemInstructionDelivery": "COPILOT_HOME/copilot-instructions.md:replace-under-lifetime-lease-before-launch:v1", + "sharedRuntimeContract": "paperclip.acpx-runtime-contract.v1", + "innerDistributionSourceSha256": "9bb08347eed01f1ab44e065c8b06ead9be3a25973d6e6329dc4c0a099096279a", + "upstreamAppSha256": "7b48282a19b5b0814a0c96ad5e3a125173d792cc55f9525b2effea962f6063c0", + "patchedAppSha256": "61f8eebe0c30cc03b311c6818b2acb158a6d16ca58c8bfac5ebd42db095c2679", + "messageIdentityContract": "copilot-native-message-id-v1", + "ownedDistributionDelivery": "COPILOT_CLI_DIST_DIR:lease-owned-guarded-inner-distribution:v1", + "permissionContextContract": "copilot-edit-permission-context-v1", + "permissionContextSourceSha256": "de2d38d702b3c574161af0c0c04790fb6f6347916eb3916ce7a131a8c229121b", + "permissionAdapterSourceSha256": "ee5929ed2ee647ad0369465dee9d51a00b2dd28e1ff735b43e969ddc9c164668", + "permissionLocationsSourceSha256": "f39a81da395b6e23aa2a1a45572a28846e5e81bfd3d764d9a456f74a79c752e9", + "permissionRedactionSourceSha256": "ad6770c69269087951790e2e87e22ac088e3d432769adf17ebc865ed512e6c4f", + "permissionClassifierSourceSha256": "1161eabd82e26133e3b3db3ab567f65d52808b03f176e93497e02c8330349fb4", + "semanticToolReceiptContract": "paperclip.semantic_tool_receipt.v2", + "semanticReceiptSourceSha256": "b2795726b2234a663fec1a6ca11f0551ae69882fe9e82aa39686e952d036e791", + "semanticBridgeSourceSha256": "8764b8c9032fdc7d1415b01e75a9fc57c550e7a5e11da4bed7d9100082872ae5", + "toolEvidenceSourceSha256": "6eef5039afc75bb851eaf9cd82a6f099e4bd398ca2deb59dd66f5b2a81a7a706", + "semanticNormalizedInputContract": "validated-forwarded-input-commit-v1", + "semanticSidecarSourceSha256": "7d6e948b7cff4df52a9afc916db46ace0f42b4b2d3134ba0c21ee0a928857e62", + "semanticDirectDriverSourceSha256": "d683429427dd8a1a5efe50952018ea486f808bdbf0a92fc5946ee267fe2c72ee", + "semanticValidationSourceSha256": "02fbb92681c78c5de5d2f1aae25aabc25a0e5df6b0ff4baccac652192a8da975", + "semanticNormalizationSourceSha256": "8b628b82e8eacc91cdcc6fdca2210ba4cf282f01e9c6f74e95ff271655b5047e", + "semanticCompletionContractSourceSha256": "f49398b682a5623812f5d3c33d6bb9a749008e47cc0ed969072041615f66df59", + "semanticValidatorsSourceSha256": "b20ec8ee6e7165117233ce4421a975bb9d3f276220ce31655c0b9a717cb2d9cb", + "semanticSchemaBundleSourceSha256": "81ef98ca7b34896a0f76390ac7af3dd8ef7609fe6f38d54bd6a69373d50fe40b", + "semanticSidecarProtocolSourceSha256": "26dd0cfbef8b2b401de6b7862a4af7f243fb703e23678a1a5ed8fa68d4784d42", + "semanticSidecarReceiptCommitContract": "correlated-tool-resolve-v1", + "displayEventSourceSha256": "d72f9a6035c247bc6a42c067c05753076c54322c4b58984e93922f3325dc6877", + "displayProjectionSourceSha256": "75952606b26dd93bbc582c1f0a4424487377272ea745fb3a27ceae031fd5dc7e", + "configuredEnvironmentSourceSha256": "5c9008d121979a3a8987e6b8a5752172eafdc4a2b5d725a73f3bbb8fb83be8ff", + "configuredEnvironmentRustSourceSha256": "85c87fae62b8a9377e4f0a4c00302c7519fae4397b1c48253f8f2b7ba7faf994", + "profileActivitySourceSha256": "4fdb6b0f9125fd8372ba6f27945fa5c8a3d3b2ec891df544092e3dc7a75d5282", + "profileExtensionsSourceSha256": "a80ddc95eb4505095bfdbbf85c758199949ab20be9c84b34c77f762eb7180571", + "profileInstallationSourceSha256": "3e61c4cdd4b78e38fcb16eaadae4f1fd65ce3c7127184ea7a80948c8e5d77f88", + "attachmentPolicySourceSha256": "4f7638b7cc64e18f6a4ba33fd46de2b3d49225e32aeab41f5c695b268fa4f8e2", + "singleReadEvidenceSourceSha256": "4069393405089bde613cecec04f5123c426594811da4c668bd96f0e7e283550f", + "nativeSessionRuntimeSourceSha256": "1fe6fa9e6b96d84d70432b85a57c473f833606d4a521563d5022b4d3c41576ce", + "nativeRuntimeContextSourceSha256": "71607f80dbb8dc5cc6c123d0b8f3995d6c5407277695a8c82cbedc90fb8f4842", + "nativeRuntimePromptsSourceSha256": "fff5755dba9b7dce8a5f10ff6ac6d5ed06b92ed079ce1e0a44ac72f2ce7869c1", + "nativeSessionContractSourceSha256": "22a8c96ce41ba4f03e976fc37f0cc7c5d05cee99fa789941da31ee1dab440b90", + "harnessSessionContractSourceSha256": "baba1a210b863518f01e0c875606ed37f9467c433222251438578bd999bc43b5", + "harnessBackendSourceSha256": "dc827e281ae6731545b166ee4d709a4f67f594a556dcbe63c70fe8f1aca26249", + "harnessSessionSourceSha256": "81361733e86b56dd5a66415aefc6f72e69be24acc8d3417370154ebb7fef6e94", + "transportContractSourceSha256": "03fca93d36a89b0cc0917335e293ad097fdf04356d21b307827bb0ea4c13f523", + "nativeBackendSourceSha256": "7495d9898d22dca1ebf6336361d332bafefc4498f6e49f1135025fad846c8586", + "nativeBackendFactorySourceSha256": "7e431c926b76d53c4c08e3a4105353dcd34bd940f6588a1a68f79631f6feeb44", + "nativeInstructionsSourceSha256": "82ac6e5f7275b4ef79fb166c7330efc504e80cab3d7e9081e4e40a8b570bc50b", + "providerLaunchSourceSha256": "c744235b3067ea75973523f78f1e13399337bc155f1775af694e34782d5c9bef", + "launchPreparationStoreSourceSha256": "ff6025568bbc0aa6ce545772d73dfca2d3da226740bd3a7b386b069e76cff296", + "runtimeRequestBridgeSourceSha256": "93a631b8439df1590b0cf442cf84a9b05f004f12bfee90291651ccfc0861effe", + "runtimeRequestFacadeSourceSha256": "d0ed28e0c5ddad34db8f437e704c8a755eb0b47f27e08e44185f66d1d2205295", + "runtimeRequestNotificationSourceSha256": "6196c6175f11c69295dc00fc773201f4fdeca26c500653870253c43b81e65211", + "guardedInstallationSourceSha256": "56a177d3e702914818dca4ca50e061f0d25f988bfc4c0582c36ce23c1696d82f", + "nativeExecutionSourceSha256": "d60b8ea63bad975c0144f0702b2749d7db9a7da50680a6147f20d4d11f2d5297", + "profileCompatibilitySourceSha256": "aa36385c5ed723b7f2bd5c96ff70db4587a040151f1ee70f5669b7f46f408713", + "installationSourceSha256": "a1c7c6f103b898216973e77ffbac0ef7572e250aa574d1a4065270e9dfa0dc2d", + "assetOwnershipSourceSha256": "1785e3f31e5dad5a13b53e25d8cf9482a9bd89a27b6c127830aed6b81acc605c", + "assetInstallerSourceSha256": "d3bd9514e5920d0cc3567aa50487fb534c57a6f66b8da0d3f22c5f6e499c841f", + "credentialBindingSourceSha256": "3a93267146bfc3c489f43262386d6dcb05ec4e3a8b1765414cd43d80d0c64d4a", + "admissionDiagnosticsSourceSha256": "df8f1aa4f2d1bc63b2635bdd09cb8165c8b7681ddcc2618435c7c08ca90475af", + "grokAdmissionSourceSha256": "f1e46cf2e1e36b7704bd797b678e3db8db2003f97bdb9d827cfa3d7662698777", + "sidecarErrorSourceSha256": "17948ad74350dc487ab20d24a0fb89daf0b426b98396454fff0e3856e922d82c", + "profileSourceSha256": "f5538b3dbefa7634a332b87d5ededcefb56aa7e5790cede1c7a0d948a548a456", + "metadataProbeSourceSha256": "be4c8416edd27061109bc1f33d0c26071b4facbb3f651beaae6b3c0275cce727", + "permissionToolIdentitySourceSha256": "770b47305e571a81f344883057d0e4a482a1cd59ac77d045836593a8e739deb1", + "semanticContinuationSourceSha256": "c9cc0fd516b3df92a6655f56ee9bef37615a889c371e2e137d8ac5c1176de811", + "preparedHarnessHomeSourceSha256": "bb286f7ddc6e9597c56d46bf7ff6e3b556c5b680d7e147fd46518724432b083e", + "runtimeHostSourceSha256": "13f9ac382a279a67adb8974c36bbed38fe43d9b93b87bd7a05e879ead7f03d63", + "runtimeAdapterSourceSha256": "7c47c05306ebe7d1ca6b5d046a96f5726a48adec4d27e7c16788f505f25b5db2", + "runtimeSandboxSourceSha256": "04a49a24e3828ce8fca908de15e31ae932b8b1b4f7ecec0308627f6a8da9ba20", + "cancellationSourceSha256": "a07d1a9e982dec0ae130c1ad1efa5bc75c6a39e013b730ba3ee45256f09ceeca", + "agentFilesSourceSha256": "fb32f6badc13dfa22f231479509b631b2f0eba0c99b1ab9f3cd5c0c601aa03ba", + "nativeDistributionSourceSha256": "aed327c389e957483a503cb1bb8b247ee7cd7ff4b528fb4567c39bcb4577690b" + } +} diff --git a/packages/paperclip-runner/test/fixtures/grok-profile-v11-identity.json b/packages/paperclip-runner/test/fixtures/grok-profile-v11-identity.json new file mode 100644 index 00000000000..78206d5b3f1 --- /dev/null +++ b/packages/paperclip-runner/test/fixtures/grok-profile-v11-identity.json @@ -0,0 +1,35 @@ +{ + "commandDigest": "sha256:f43a6f5a583972cdcb422784969cd6a97654655b0f135f1cf178317909c404f8", + "sourceDigest": "sha256:c5a4628adcdd9190c45558f42014b0055698b8f8c7eb301417238f38d913f503", + "declaration": { + "schema": "paperclip.grok-profile-identity.v1", + "agent": "grok", + "agentProfileVersion": 11, + "agentServerPackage": "builtin:grok-acp", + "agentServerVersion": "1", + "agentRuntimePackage": "native:grok", + "agentRuntimeVersion": "1.0.13", + "requiresProviderPolicy": false, + "acpxVersion": "0.13.1", + "sources": { + "patches/acpx@0.13.1.patch": "sha256:1e7dae8c83bf8620f53f2bea13c175eeff95e7e955783f6f529bb1dcbdcdb6d2", + "packages/paperclip-runner/src/cli/acpx-runtime-sidecar.ts": "sha256:7d6e948b7cff4df52a9afc916db46ace0f42b4b2d3134ba0c21ee0a928857e62", + "packages/paperclip-runner/src/cli/acpx-sidecar-input.ts": "sha256:17948ad74350dc487ab20d24a0fb89daf0b426b98396454fff0e3856e922d82c", + "packages/paperclip-runner/src/drivers/acpx/admission-diagnostics.ts": "sha256:df8f1aa4f2d1bc63b2635bdd09cb8165c8b7681ddcc2618435c7c08ca90475af", + "packages/paperclip-runner/src/drivers/acpx/grok-effort.ts": "sha256:f1e46cf2e1e36b7704bd797b678e3db8db2003f97bdb9d827cfa3d7662698777", + "packages/paperclip-runner/src/drivers/acpx/grok-web-search.ts": "sha256:754b5ca2345c045421f6007f34999508d996e1cea035f0f1952b3d5f00072b9c", + "packages/paperclip-runner/src/drivers/acpx/grok-credentials.ts": "sha256:1c4ff0be6da798b6fcc604d20a9198dcc8523a3806d563b5b5345b7260549f73", + "packages/paperclip-runner/src/drivers/acpx/codex-runtime-adapter.ts": "sha256:7c47c05306ebe7d1ca6b5d046a96f5726a48adec4d27e7c16788f505f25b5db2", + "packages/paperclip-runner/src/drivers/acpx/runtime-host.ts": "sha256:13f9ac382a279a67adb8974c36bbed38fe43d9b93b87bd7a05e879ead7f03d63", + "packages/paperclip-runner/src/drivers/preconfigured-home.ts": "sha256:bb286f7ddc6e9597c56d46bf7ff6e3b556c5b680d7e147fd46518724432b083e", + "packages/paperclip-runner/src/drivers/acpx/runtime-sandbox.ts": "sha256:04a49a24e3828ce8fca908de15e31ae932b8b1b4f7ecec0308627f6a8da9ba20", + "packages/paperclip-runner/src/drivers/acpx/sidecar-protocol.ts": "sha256:26dd0cfbef8b2b401de6b7862a4af7f243fb703e23678a1a5ed8fa68d4784d42", + "packages/paperclip-runner/src/drivers/acpx/native-distribution-integrity.ts": "sha256:aed327c389e957483a503cb1bb8b247ee7cd7ff4b528fb4567c39bcb4577690b", + "packages/paperclip-runner/src/providers/grok/launcher.cjs": "sha256:f0b698395a3704ed2ffaf84ea19bdb20c36c8a0a70b7c629c7b6ffe144e59e55", + "packages/paperclip-runner/src/providers/grok/platforms.json": "sha256:931eed3abbf2bf041e6c72e6e9486d976f4352613cf069b216ddf010c9a8e704", + "packages/paperclip-runner/runner/crates/runner-core/src/acpx_provider_backend.rs": "sha256:326deadb929e7c134da407bd3fa4d0853732e37d407c92d404979e7625c6c7cf", + "packages/paperclip-runner/runner/crates/runner-core/src/acpx_provider_session.rs": "sha256:e97a7c4d2cf9be9c6e0ada78076a5551b01401518597ab81cb6fbd5a1faf4a5e", + "packages/paperclip-runner/runner/crates/runner-core/src/acpx_sidecar_transport.rs": "sha256:f711428ec3bb9bcf723a5fd1382a3d266ed14e7ed5340b8a32eafe7357940144" + } + } +} diff --git a/patches/acpx@0.13.1.patch b/patches/acpx@0.13.1.patch index bfc33c0eb2d..1ed192d4f31 100644 --- a/patches/acpx@0.13.1.patch +++ b/patches/acpx@0.13.1.patch @@ -561,21 +561,52 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js })); result = claudeAcp ? await withTimeout(createPromise, resolveClaudeAcpSessionCreateTimeoutMs()) : await createPromise; } catch (error) { -@@ -4589,6 +4808,13 @@ - } catch (error) { +@@ -4454,6 +4673,7 @@ + } + async loadSessionWithOptions(sessionId, cwd = this.options.cwd, options = {}) { + const connection = this.getConnection(); ++ const { command, args } = resolveAgentCommandParts(this.options.agentCommand, this.options.agentArgv); + const sessionCwd = await resolveAgentSessionCwd(cwd, this.options.agentCommand); + const previousSuppression = this.applySessionUpdateSuppression(Boolean(options.suppressReplayUpdates)); + let response; +@@ -4461,7 +4681,9 @@ + response = await this.runConnectionRequest(() => connection.loadSession({ + sessionId, + cwd: sessionCwd, +- mcpServers: this.options.mcpServers ?? [] ++ mcpServers: this.options.mcpServers ?? [], ++ _meta: isClaudeAcpCommand(command, args) ++ ? buildClaudeCodeOptionsMeta(this.options.sessionOptions, true) : void 0 + })); + await this.waitForSessionUpdateDrain(options.replayIdleMs ?? REPLAY_IDLE_MS, options.replayDrainTimeoutMs ?? REPLAY_DRAIN_TIMEOUT_MS); + } finally { +@@ -4474,11 +4696,14 @@ + } + async resumeSession(sessionId, cwd = this.options.cwd) { + const connection = this.getConnection(); ++ const { command, args } = resolveAgentCommandParts(this.options.agentCommand, this.options.agentArgv); + const sessionCwd = await resolveAgentSessionCwd(cwd, this.options.agentCommand); + const response = await this.runConnectionRequest(() => connection.resumeSession({ + sessionId, + cwd: sessionCwd, +- mcpServers: this.options.mcpServers ?? [] ++ mcpServers: this.options.mcpServers ?? [], ++ _meta: isClaudeAcpCommand(command, args) ++ ? buildClaudeCodeOptionsMeta(this.options.sessionOptions, true) : void 0 + })); + this.loadedSessionId = sessionId; + const result = toReconnectedSessionResult(response); +@@ -4590,15 +4815,17 @@ throw maybeWrapSessionControlError("session/set_config_option", error, `for "${configId}"="${value}"`); } -+ } + } + async requestExtension(method, params) { + closedExtensionMethods([method]); + const payload = boundedExtensionRecord(params); + const connection = this.getConnection(); + const result = await this.runConnectionRequest(() => connection.extMethod(method, payload)); + return boundedExtensionRecord(result); - } - async setSessionModel(sessionId, modelId, controlOverride) { - const control = this.resolveModelControl(sessionId, controlOverride); -@@ -4593,12 +4819,7 @@ ++ } async setSessionModel(sessionId, modelId, controlOverride) { const control = this.resolveModelControl(sessionId, controlOverride); if (!control) throw new RequestedModelUnsupportedError(`Cannot set model "${modelId}": the ACP session did not advertise a model config option or legacy session/set_model support.`, "missing-capability"); @@ -589,7 +620,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js } async setSessionModelThroughConfig(sessionId, modelId, configId) { const connection = this.getConnection(); -@@ -4608,7 +4829,9 @@ +@@ -4608,7 +4835,9 @@ configId, value: modelId })); @@ -600,7 +631,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js return response; } catch (error) { return this.throwSessionModelError("session/set_config_option", modelId, error); -@@ -4864,7 +5087,7 @@ +@@ -4864,7 +5093,7 @@ } selectAuthMethod(methods) { for (const method of methods) { @@ -609,7 +640,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js if (envCredential) return { methodId: method.id, credential: envCredential, -@@ -4890,7 +5113,7 @@ +@@ -4890,7 +5119,7 @@ } readAgentSpecificEnvCredential(methodId) { if (!this.isGrokBuildAcpCommand() || methodId !== "xai.api_key") return; @@ -618,7 +649,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js return typeof value === "string" && value.trim().length > 0 ? value : void 0; } selectAgentManagedAuthMethod(methodId) { -@@ -4915,9 +5138,9 @@ +@@ -4915,9 +5144,9 @@ await connection.authenticate({ methodId: selected.methodId }); this.log(`authenticated with method ${selected.methodId} (${selected.source})`); } @@ -630,7 +661,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js if (hostResponse) return hostResponse; const { response, recorded } = await this.resolvePermissionRequestFromMode(params); if (!recorded) { -@@ -4926,14 +5149,81 @@ +@@ -4926,14 +5155,81 @@ } return response; } @@ -714,7 +745,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js })).then((response) => ({ kind: "response", response -@@ -4970,7 +5260,7 @@ +@@ -4970,7 +5266,7 @@ isElicitationSessionCancelling(sessionId) { return this.closing || this.cancellingSessionIds.has(sessionId); } @@ -723,7 +754,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js if (!this.options.onPermissionRequest) return; const signal = this.cancellationSignalForSession(params.sessionId); try { -@@ -4978,7 +5268,7 @@ +@@ -4978,7 +5274,7 @@ sessionId: params.sessionId, raw: params, inferredKind: inferToolKind(params) @@ -732,7 +763,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js return this.hostPermissionDecisionResponse(params, signal, decision); } catch (error) { return this.hostPermissionErrorResponse(params, signal, error); -@@ -5028,6 +5318,12 @@ +@@ -5028,6 +5324,12 @@ attachAgentLifecycleObservers(child) { child.once("exit", (exitCode, signal) => { this.recordAgentExit("process_exit", exitCode, signal); @@ -745,7 +776,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js }); child.once("close", (exitCode, signal) => { this.recordAgentExit("process_close", exitCode, signal); -@@ -5100,6 +5396,9 @@ +@@ -5100,6 +5402,9 @@ return await this.filesystem.readTextFile(params); } catch (error) { this.recordPermissionError(params.sessionId, error); @@ -755,7 +786,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js throw error; } } -@@ -5239,6 +5538,7 @@ +@@ -5239,6 +5544,7 @@ record.cumulative_token_usage = conversation.cumulative_token_usage; record.cumulative_cost = conversation.cumulative_cost; record.request_token_usage = conversation.request_token_usage; @@ -763,7 +794,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js } //#endregion //#region src/runtime/engine/session-options.ts -@@ -5701,7 +6001,8 @@ +@@ -5701,7 +6007,8 @@ updated_at: conversation.updated_at, cumulative_token_usage: deepClone(conversation.cumulative_token_usage ?? {}), cumulative_cost: cloneUsageCost(conversation.cumulative_cost), @@ -773,7 +804,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js }; } function cloneUsageCost(cost) { -@@ -5771,10 +6072,20 @@ +@@ -5771,10 +6078,20 @@ trimConversationForRuntime(conversation); return acpx; } @@ -796,7 +827,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js updateConversationTimestamp(conversation, timestamp); trimConversationForRuntime(conversation); return true; -@@ -6136,6 +6447,7 @@ +@@ -6136,6 +6453,7 @@ pendingAgentSessionId = loadState.pendingAgentSessionId; sessionModels = loadState.sessionModels; const preferenceReplay = await replayFreshSessionPreferences({ @@ -804,7 +835,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js client, record, createdFreshSession, -@@ -6193,14 +6505,16 @@ +@@ -6193,14 +6511,16 @@ if (shouldReconnect) process.stderr.write(`[acpx] saved session pid ${record.pid} is dead; respawning agent and attempting session reconnect\n`); } async function replayFreshSessionPreferences(params) { @@ -823,7 +854,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js client: params.client, sessionId: params.sessionId, desiredModeId: params.desiredModeId, -@@ -6219,7 +6533,7 @@ +@@ -6219,7 +6539,7 @@ verbose: params.verbose, suppressWarnings: params.suppressWarnings }); @@ -832,7 +863,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js client: params.client, record: params.record, sessionId: params.sessionId, -@@ -6435,6 +6749,27 @@ +@@ -6435,6 +6755,27 @@ //#region src/runtime/engine/prompt-turn.ts const SESSION_REPLY_IDLE_MS = 1e3; const SESSION_REPLY_DRAIN_TIMEOUT_MS = 5e3; @@ -860,7 +891,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js async function runPromptTurn(params) { try { const promptPromise = params.client.prompt(params.sessionId, params.prompt, params.onPromptRequestStarted, params.onElicitation); -@@ -6444,7 +6779,23 @@ +@@ -6444,7 +6785,23 @@ idleMs: SESSION_REPLY_IDLE_MS, timeoutMs: SESSION_REPLY_DRAIN_TIMEOUT_MS }).catch(() => {}); diff --git a/scripts/pi-public-install-probe.mjs b/scripts/pi-public-install-probe.mjs index ed3e018a6b2..525a38772f4 100644 --- a/scripts/pi-public-install-probe.mjs +++ b/scripts/pi-public-install-probe.mjs @@ -52,8 +52,8 @@ assert.equal(JSON.parse(await readFile(join(server, 'package.json'), 'utf8')).na const { resolvePaperclipRunnerBinary } = await import(pathToFileURL(join(server, 'dist/services/native-runtime/native-codex-runner.js'))); const { createCapabilityRunnerdCodexTransport } = await import(pathToFileURL(join(server, 'dist/vendor/paperclip-runner/live/runnerd-codex-transport.js'))); const { QUALIFIED_ACPX_PROFILES } = await import(pathToFileURL(join(server, 'dist/vendor/paperclip-runner/drivers/acpx/qualified-profiles.js'))); -assert.equal(QUALIFIED_ACPX_PROFILES.pi.agentProfileVersion, 32); -assert.equal(QUALIFIED_ACPX_PROFILES.pi.commandDigest, 'sha256:ce13b75699331c813f8a42f2803c3e0e0fb1e6d24847452cb12b50d6ed2d0baf'); +assert.equal(QUALIFIED_ACPX_PROFILES.pi.agentProfileVersion, 33); +assert.equal(QUALIFIED_ACPX_PROFILES.pi.commandDigest, 'sha256:0260d5810e9247986800ad46c1f9b3511a5abd06cb8f142405d5caacbc3960fc'); assert.equal(Object.hasOwn(QUALIFIED_ACPX_PROFILES.pi, 'qualificationModel'), false); assert.equal(Object.hasOwn(QUALIFIED_ACPX_PROFILES.pi, 'reportedModelId'), false); const daemon = resolvePaperclipRunnerBinary();