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