From 7d89e304f09f33fed05cbfeb6eceeaaf32a4a1d2 Mon Sep 17 00:00:00 2001 From: Dotta Date: Wed, 7 Oct 2026 06:57:28 -0500 Subject: [PATCH] fix(acpx): separate restored history from active turn events Keep history reconstruction without publishing restored text and tools as live output. Version the changed Cursor contract as profile 16 and retain replay compatibility. Co-Authored-By: Paperclip --- docker/daytona-runner/Dockerfile | 2 +- packages/paperclip-runner/acpx-profiles.json | 4 ++-- packages/paperclip-runner/cursor-contract.json | 6 +++--- packages/paperclip-runner/docs/hermes.md | 12 ++++++++++++ .../runner-core/src/generated_acpx_profiles.rs | 2 +- .../src/drivers/acpx/generated-profiles.ts | 4 ++-- .../src/drivers/acpx/profile-compatibility.test.ts | 3 ++- .../src/drivers/acpx/profile-compatibility.ts | 4 ++-- patches/acpx@0.13.1.patch | 13 +++++++++++++ tests/runner-e2e/daytona-image-content.ts | 2 +- 10 files changed, 39 insertions(+), 13 deletions(-) diff --git a/docker/daytona-runner/Dockerfile b/docker/daytona-runner/Dockerfile index 0a42e2bccb..725892c40b 100644 --- a/docker/daytona-runner/Dockerfile +++ b/docker/daytona-runner/Dockerfile @@ -33,7 +33,7 @@ COPY cli/package.json ./cli/package.json # The complete resolved lock (including transitive integrity hashes) is reviewed. # Reject registry-time drift BEFORE installing packages or running lifecycle code. # Refresh this digest together with source/provider dependency changes. -ARG PAPERCLIP_RUNNER_LOCK_SHA256=c63e2e731c27744e3de3a7dc8b3e115465ffe8c42e87c2a14e7d8927163f1b13 +ARG PAPERCLIP_RUNNER_LOCK_SHA256=f5ee14ee77b1dc7771fe455d619c880fc64b1e62e40a15704addc9f7430e5c50 # pnpm 9 subtracts PNPM_WORKERS from available CPUs; it is not a worker count. # Subtract all available CPUs to select its minimum (one tarball worker). RUN export PNPM_WORKERS="$(node -p 'require("node:os").availableParallelism()')" \ diff --git a/packages/paperclip-runner/acpx-profiles.json b/packages/paperclip-runner/acpx-profiles.json index 3b5ebfd1dc..7b4d7ed987 100644 --- a/packages/paperclip-runner/acpx-profiles.json +++ b/packages/paperclip-runner/acpx-profiles.json @@ -23,12 +23,12 @@ "requiresProviderPolicy": true }, "cursor": { - "agentProfileVersion": 15, + "agentProfileVersion": 16, "agentServerPackage": "cursor-agent", "agentServerVersion": "2026.09.26-dd393fe", "agentRuntimePackage": null, "agentRuntimeVersion": null, - "commandDigest": "sha256:aaf20b110eb6e1de8bbea655157682ef0576416f7d80d6a02715805cbbfabde1", + "commandDigest": "sha256:d4d2e4f4542e8b4e1e86698ddbc617bf704221637bacb1f0b779a41bf5c649b9", "requiresProviderPolicy": true }, "copilot": { diff --git a/packages/paperclip-runner/cursor-contract.json b/packages/paperclip-runner/cursor-contract.json index 22a3d5264e..b740d08d59 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": "de7c4a5b35347156ae4a22142afd7a3731200f262ac6e1da3c10c666f50369e6", + "acpxPatchSha256": "94c72d31bf3b9ed656d57a341a353fb79f4028b443def7858b6e1fa33d0b8238", "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:aaf20b110eb6e1de8bbea655157682ef0576416f7d80d6a02715805cbbfabde1" + "commandDigest": "sha256:d4d2e4f4542e8b4e1e86698ddbc617bf704221637bacb1f0b779a41bf5c649b9" } diff --git a/packages/paperclip-runner/docs/hermes.md b/packages/paperclip-runner/docs/hermes.md index 019ef62810..31771fe7bf 100644 --- a/packages/paperclip-runner/docs/hermes.md +++ b/packages/paperclip-runner/docs/hermes.md @@ -60,6 +60,11 @@ excluded from recovery copies. Only `hermes/memories` and `hermes/skills` enter managed agent-file storage. Session databases stay private to a normalized conversation. Assigned Paperclip skills remain separate protected inputs. +Between turns, authenticated run attachment refreshes the registered agent-file +working copy and assigned tool bindings. The session still pins prompt, bundle, +skill, connection, model, and permission identities. Unknown policy changes and +changes within the same run are rejected. + ## Interaction contract - Reasoning and assistant text use distinct message identities. Native tool @@ -75,6 +80,7 @@ conversation. Assigned Paperclip skills remain separate protected inputs. - Steering requires an explicit native acknowledgement. Follow-ups remain in the durable Paperclip queue. Stop cancels native work and pending input before bounded provider-process cleanup. +- Restored history is recorded without becoming new assistant text or live tools. - Restoration requires nonempty native history and follows the native compaction chain. Missing history and failed persistence are errors. - Usage is a per-prompt delta. Native price calculations are labeled estimates; @@ -97,6 +103,12 @@ idempotency key. Listing/inspection use existing authorized read APIs. Scheduled firings create ordinary Paperclip work with routine provenance and retain existing ownership, budget, pause, concurrency and duplicate-fire rules. +Provisioning requires uv 0.12.17 and, on macOS, Command Line Tools for +`install_name_tool` plus `codesign`. The Python closure normalizes interpreter +installation paths so local consumers and image builds reproduce the same pin. +Credential cleanup runs after verified exit even when learned-state collection +fails; save failures remain visible through normal runtime errors. + ## Qualification Hermes stays visibly **pending**. Candidate execution is an operator diagnostic 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 0724f16709..a578794fd1 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 @@ -38,7 +38,7 @@ pub(crate) fn acpx_release_profile(agent: &str) -> Option { agent_runtime_package: None, agent_runtime_version: None, command_digest: - "sha256:aaf20b110eb6e1de8bbea655157682ef0576416f7d80d6a02715805cbbfabde1", + "sha256:d4d2e4f4542e8b4e1e86698ddbc617bf704221637bacb1f0b779a41bf5c649b9", requires_provider_policy: true, }, "copilot" => AcpxReleaseProfile { diff --git a/packages/paperclip-runner/src/drivers/acpx/generated-profiles.ts b/packages/paperclip-runner/src/drivers/acpx/generated-profiles.ts index 61db22a799..146443086c 100644 --- a/packages/paperclip-runner/src/drivers/acpx/generated-profiles.ts +++ b/packages/paperclip-runner/src/drivers/acpx/generated-profiles.ts @@ -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:aaf20b110eb6e1de8bbea655157682ef0576416f7d80d6a02715805cbbfabde1", + "commandDigest": "sha256:d4d2e4f4542e8b4e1e86698ddbc617bf704221637bacb1f0b779a41bf5c649b9", "permissionPolicy": "interactive" }, "copilot": { 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 936237d15c..1ee2cc24ff 100644 --- a/packages/paperclip-runner/src/drivers/acpx/profile-compatibility.test.ts +++ b/packages/paperclip-runner/src/drivers/acpx/profile-compatibility.test.ts @@ -5,6 +5,7 @@ describe("historical ACPX profile decoding", () => { it("retains each provider's existing revision boundary", () => { expect(isSupportedAcpxProfileVersion("cursor", 14)).toBe(true); expect(isSupportedAcpxProfileVersion("cursor", 15)).toBe(true); + expect(isSupportedAcpxProfileVersion("cursor", 16)).toBe(true); expect(isSupportedAcpxProfileVersion("hermes", 1)).toBe(true); expect(isSupportedAcpxProfileVersion("hermes", 2)).toBe(false); for (const agent of ["pi", "claude", "codex", "grok", "copilot"]) { @@ -12,7 +13,7 @@ describe("historical ACPX profile decoding", () => { expect(isSupportedAcpxProfileVersion(agent, 6)).toBe(false); } }); - it.each([0, 16, 1.5, "11", null, undefined, NaN])("rejects invalid revisions: %s", version => { + it.each([0, 17, 1.5, "11", null, undefined, NaN])("rejects invalid revisions: %s", version => { expect(isSupportedAcpxProfileVersion("cursor", version)).toBe(false); }); it.each(["unknown", "toString", "__proto__"])("rejects unregistered providers: %s", agent => { diff --git a/packages/paperclip-runner/src/drivers/acpx/profile-compatibility.ts b/packages/paperclip-runner/src/drivers/acpx/profile-compatibility.ts index fd796db6a0..600b822f30 100644 --- a/packages/paperclip-runner/src/drivers/acpx/profile-compatibility.ts +++ b/packages/paperclip-runner/src/drivers/acpx/profile-compatibility.ts @@ -1,10 +1,10 @@ /** Decodable historical profile revisions, not permission to launch them. * Exact current profile/package/digest admission is checked separately. */ -export type AcpxProfileVersion = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15; +export type AcpxProfileVersion = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16; const historicalVersions: Readonly> = { pi: [1, 2, 3, 4, 5], claude: [1, 2, 3, 4, 5], codex: [1, 2, 3, 4, 5], grok: [1, 2, 3, 4, 5], copilot: [1, 2, 3, 4, 5], - cursor: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], + cursor: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], hermes: [1], }; diff --git a/patches/acpx@0.13.1.patch b/patches/acpx@0.13.1.patch index 1c12d243a1..c627243c32 100644 --- a/patches/acpx@0.13.1.patch +++ b/patches/acpx@0.13.1.patch @@ -1097,6 +1097,19 @@ diff --git a/dist/runtime.js b/dist/runtime.js const active = owner.activeTurn; if (active) { const { task, turn } = active; +@@ -833,9 +880,7 @@ + if (turn.connectionUpdates) { + turn.connectionUpdates.push(notification); +- this.emitRuntimeTurnEvent(task, { +- jsonrpc: "2.0", +- method: "session/update", +- params: notification +- }); ++ // ACP load/resume replays saved history before accepting a prompt. ++ // Retain it in the session projection without publishing it as new ++ // turn output, including tool calls and interruption metadata. + return; + } @@ -863,6 +910,9 @@ projection.checkpoint.request(); } diff --git a/tests/runner-e2e/daytona-image-content.ts b/tests/runner-e2e/daytona-image-content.ts index c7864614da..ee0ed9854e 100644 --- a/tests/runner-e2e/daytona-image-content.ts +++ b/tests/runner-e2e/daytona-image-content.ts @@ -303,7 +303,7 @@ if (invokedPath && import.meta.url === pathToFileURL(invokedPath).href) { const arguments_ = process.argv.slice(2); const candidateFlag = arguments_[0]; if (arguments_.length > 1 || (candidateFlag !== undefined && !candidateFlag.startsWith("--candidate-providers="))) { - throw new Error("Expected only --candidate-providers=cursor,copilot,pi"); + throw new Error("Expected only --candidate-providers=cursor,copilot,pi,hermes"); } const candidateProviders = candidateFlag?.slice("--candidate-providers=".length).split(",").filter(Boolean) ?? []; computeDaytonaImageContentId({ candidateProviders })