mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-08 21:03:51 +02:00
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 <noreply@paperclip.ing>
This commit is contained in:
1 parent
68b1a39459
commit
7d89e304f0
10 files changed
+39
-13
No files matched your search
@@ -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()')" \
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -38,7 +38,7 @@ pub(crate) fn acpx_release_profile(agent: &str) -> Option<AcpxReleaseProfile> {
|
||||
agent_runtime_package: None,
|
||||
agent_runtime_version: None,
|
||||
command_digest:
|
||||
"sha256:aaf20b110eb6e1de8bbea655157682ef0576416f7d80d6a02715805cbbfabde1",
|
||||
"sha256:d4d2e4f4542e8b4e1e86698ddbc617bf704221637bacb1f0b779a41bf5c649b9",
|
||||
requires_provider_policy: true,
|
||||
},
|
||||
"copilot" => AcpxReleaseProfile {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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<Record<string, readonly AcpxProfileVersion[]>> = {
|
||||
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],
|
||||
};
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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 })
|
||||
|
||||
Reference in new issue
Block a user