From f6f23961dad98a930babd985b8700bab897f5e7f Mon Sep 17 00:00:00 2001 From: Dotta Date: Wed, 30 Sep 2026 22:21:22 -0500 Subject: [PATCH] fix(runner): scope Cursor identity policy to its sidecar profile Co-Authored-By: Paperclip --- .../src/cli/acpx-runtime-sidecar.test.ts | 16 +++++++++++++++- .../src/cli/acpx-runtime-sidecar.ts | 6 +++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/packages/paperclip-runner/src/cli/acpx-runtime-sidecar.test.ts b/packages/paperclip-runner/src/cli/acpx-runtime-sidecar.test.ts index 3f7be639145..e93ac92fcff 100644 --- a/packages/paperclip-runner/src/cli/acpx-runtime-sidecar.test.ts +++ b/packages/paperclip-runner/src/cli/acpx-runtime-sidecar.test.ts @@ -98,10 +98,24 @@ describe("qualified ACPX runtime sidecar", () => { expect(diagnostics).toEqual([]); }); + it.each(["codex", "claude", "grok", "pi", "copilot", null])("preserves existing non-Cursor sidecar identity policy: %s", agent => { + const source = readFileSync(new URL("./acpx-runtime-sidecar.ts", import.meta.url), "utf8"); + const start = source.indexOf("function stableProviderIdentity("); + const stable = new Function("createHash", "cursorToolIdentity", "openParams", `${stripTypeScriptTypes(source.slice(start, source.indexOf("\nfunction canonicalJson", start)))}; return stableProviderIdentity;`)(createHash, cursorToolIdentity, agent ? { agent } : null); + for (const kind of ["tool", "message"]) { + for (const raw of ["safe-tool", "tool/1", "native\u0080tool", "native\u0085tool", "native\u009ftool", "x".repeat(161)]) { + expect(stable(raw, kind)).toBe(raw); + } + for (const raw of ["native\u0000tool", "native\u007ftool", "x".repeat(241)]) { + const expected = `acpx-${kind}-${createHash("sha256").update("paperclip.acpx.provider-identity.v1\0").update(kind).update("\0").update(raw).digest("hex")}`; + expect(stable(raw, kind)).toBe(expected); + } + } + }); it.each(["tool-first", "permission-first"])("uses the same Cursor identity in actual sidecar tool and pending permission paths: %s", async order => { const source = readFileSync(new URL("./acpx-runtime-sidecar.ts", import.meta.url), "utf8"); const identityStart = source.indexOf("function stableProviderIdentity("); - const stableIdentity = new Function("createHash", "cursorToolIdentity", `${stripTypeScriptTypes(source.slice(identityStart, source.indexOf("\nfunction canonicalJson", identityStart)))}; return stableProviderIdentity;`)(createHash, cursorToolIdentity); + const stableIdentity = new Function("createHash", "cursorToolIdentity", "openParams", `${stripTypeScriptTypes(source.slice(identityStart, source.indexOf("\nfunction canonicalJson", identityStart)))}; return stableProviderIdentity;`)(createHash, cursorToolIdentity, { agent: "cursor" }); const boundStart = source.indexOf("function boundRuntimeEventForNormalization("); const bound = new Function("boundedOptionalText", "stableProviderIdentity", "safeAcpxLocations", "openParams", "safeOutput", `${stripTypeScriptTypes(source.slice(boundStart, source.indexOf("\nfunction sanitizeRuntimeEvent", boundStart)))}; return boundRuntimeEventForNormalization;`)( diff --git a/packages/paperclip-runner/src/cli/acpx-runtime-sidecar.ts b/packages/paperclip-runner/src/cli/acpx-runtime-sidecar.ts index f62f3c731c8..ff4dd8e3def 100644 --- a/packages/paperclip-runner/src/cli/acpx-runtime-sidecar.ts +++ b/packages/paperclip-runner/src/cli/acpx-runtime-sidecar.ts @@ -1438,9 +1438,9 @@ function stableRequestId( } function stableProviderIdentity(value: string, kind: string): string { - // Keep native Cursor permission/evidence IDs identical to tool activity. - // This helper implements the same existing transform for all tool events. - if (kind === "tool") return cursorToolIdentity(value); + // Cursor alone uses the richer permission/evidence identity policy. Other + // providers and message identities retain their existing sidecar mapping. + if (kind === "tool" && openParams?.agent === "cursor") return cursorToolIdentity(value); if (Buffer.byteLength(value) <= 240 && !/[\u0000-\u001f\u007f]/.test(value)) { return value; }