Bind corrected ACPX profiles and deduplicate steering acknowledgements

Co-authored-by: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-10-02 15:37:44 -05:00
1 parent 36e7121046
commit 342287678f
22 files changed
+304 -94

No files matched your search

@@ -9,7 +9,10 @@ qualification. The existing draft stack must be reviewed in dependency order.
- Pi: `@earendil-works/pi-coding-agent@1.0.0`.
- Wrapper: `pi-acp@0.0.33`; ACPX: `0.13.1`; Node: `24.21.0`.
- Pi profile: 13. Model: `openrouter/deepseek/deepseek-v4-flash-0731`.
- Pi profile: 14. Model: `openrouter/deepseek/deepseek-v4-flash-0731`.
- Profile 14 binds the corrected outbound ACPX client patch. Profile 13 remains
historical evidence. Cursor 11 and Copilot 15 also bind that shared patch;
both remain pending and receive no new paid qualification in this work.
- Reasoning: native-confirmed `low`. No silent model or thinking fallback.
- Exact source, suite definition, installed package integrity, runner digest,
environment and cost evidence must accompany each attempt.
@@ -22,7 +25,7 @@ qualification. The existing draft stack must be reviewed in dependency order.
| Live lifecycle | Pending native question survives controller restart in the original process; one creation and resolution; stale answer after provider death rejected; Stop retires owned processes; three warm turns keep process identity | Installed local restart and three-turn warm continuity pass at `f5f57e380`. Pending-permission Stop and stale response rejection pass at `03dd6ef93`. Provider-death evidence remains required. |
| Product workflows | All 26 explicit Pi Product E2E cells (13 local, 13 Daytona); screenshots, public state, independent artifacts, terminal and cleanup evidence | Historical partial passes exist. Full final-source campaign remains required. |
| Runner protocol | Pi roster passes through the native packaged runner and authenticated mock control plane; lifecycle/denial/control cases remain distinct from Product tests | Installed get-task-context passes at `f5f57e380`; context-before-action times out. Five cells remain unexecuted. |
| Installed distribution | Public CLI/server tars on ARM Mac, Intel Mac and Linux; normal Pi setup; exact Linux companion and immutable Daytona image imported without binary override | Fresh ARM public installation and normal packaged no-key probe pass. The Linux fleet job is queued. Final-source Intel/Linux/Daytona receipts remain required. |
| Installed distribution | Public CLI/server tars on ARM Mac, Intel Mac and Linux; normal Pi setup; exact Linux companion and immutable Daytona image imported without binary override | Historical ARM and Intel public installations pass. Linux CI retains `session_handshake_timeout`; queued fleet jobs were cancelled. Final-source platform receipts, Linux companion and Daytona image remain required. |
| Governance and spend | Company isolation, human-only permission, duplicate/stale answers, Stop and budget hard stop; pricing estimates never become claimed bills | 58 focused governance/cost/session invariants pass at `f5f57e380`. Live pending-permission Stop passes at `03dd6ef93`. The qualification key cap is not proof of Paperclip budget enforcement. |
| Integration and rollout | Review each prerequisite; final-head typecheck, tests, build and CI pass; no unresolved review; exact artifacts; rollback recorded | Draft #14956 is stacked on #14924. Prerequisite PRs #14921–#14924 remain open. No merge or release. |
@@ -42,6 +45,24 @@ prices are estimates. Unpriced usage must stay unpriced in the ledger and UI.
## Evidence so far
- At `36e712104`, installed steering returns HTTP 200 and the native command
journal records accepted delivery. The public API retains the correct
correlation-bound acknowledgement at source sequence 65 and also a
rehydrated transport echo at 66. The exact-one acknowledgement gate rejects
that duplicate before permission denial. A regression using the actual
rehydration function reproduces both items; suppressing the transport echo
retains the one authoritative item. The interrupted attempt remains failed.
- The shared ACPX patch also requires portable hunk metadata and new byte-bound
identities. Pi advances to 14, Cursor to pending 11, and Copilot to pending
15. Historical fixtures remain immutable, and old installed identities fail
closed. The corrected patch passes all 16 packaging checks; identity and
steering regressions pass 244 tests. No other provider qualification expands.
- The credential-free Intel public installation at `36e712104` passes closed
admission in 44 seconds. Its Greptile review is 5/5, but CI is held by the
stale patch-bound profiles, portable patch metadata and Linux admission.
The paid attempt and task-owned Linux build were stopped when that identity
mismatch was found. Their evidence is retained; the owned server and database
processes are retired. Qualification must use newly installed profile 14.
- At `4237bc369`, the native turn-binding correction advances the steering
journey past `steering_stale_turn`, but the provider boundary still rejects
delivery. The real patched ACPX client lacks `requestExtension`: its types
@@ -209,7 +209,7 @@ impl AcpxProviderDescriptor {
"0.0.33",
Some("@earendil-works/pi-coding-agent"),
Some("1.0.0"),
"sha256:fe1e6da01b2a9e4c691ca27cf689d2d6de846a93be6b23fc1e103c9addd7b177",
"sha256:f35145437eeb355ed37bc5a4fa93d7ede561d9c45daf311979b46890b808ddd4",
),
"cursor" => (
self.model.as_str(),
@@ -217,7 +217,7 @@ impl AcpxProviderDescriptor {
"2026.09.26-dd393fe",
None,
None,
"sha256:1df2a15b93bc3a14fa47fa3315344ba023fe2412048047cdc6f32096a6336564",
"sha256:13207d7b6afcfe681d4fe9098655df056c2cf87e03f5141e961cd01c32c5bdd2",
),
"copilot" => (
self.model.as_str(),
@@ -225,7 +225,7 @@ impl AcpxProviderDescriptor {
"1.0.88",
None,
None,
"sha256:5e5955c57917a7e7c25703b3ed9e420cd72105eab033b611508dc37fdba3858b",
"sha256:8faf47520f156c62c79ea6ca7d1d90a85ddc9a6621f798bd14e87bc6c643bd04",
),
"grok" => (
"grok-4.7",
@@ -3014,7 +3014,7 @@ mod tests {
"cursor",
"cursor-agent",
"2026.09.26-dd393fe",
"sha256:1df2a15b93bc3a14fa47fa3315344ba023fe2412048047cdc6f32096a6336564",
"sha256:13207d7b6afcfe681d4fe9098655df056c2cf87e03f5141e961cd01c32c5bdd2",
None,
None,
"explicit-model",
@@ -3023,7 +3023,7 @@ mod tests {
"copilot",
"@github/copilot",
"1.0.88",
"sha256:5e5955c57917a7e7c25703b3ed9e420cd72105eab033b611508dc37fdba3858b",
"sha256:8faf47520f156c62c79ea6ca7d1d90a85ddc9a6621f798bd14e87bc6c643bd04",
None,
None,
"explicit-model",
@@ -3032,7 +3032,7 @@ mod tests {
"pi",
"pi-acp",
"0.0.33",
"sha256:fe1e6da01b2a9e4c691ca27cf689d2d6de846a93be6b23fc1e103c9addd7b177",
"sha256:f35145437eeb355ed37bc5a4fa93d7ede561d9c45daf311979b46890b808ddd4",
Some("@earendil-works/pi-coding-agent"),
Some("1.0.0"),
"openrouter/deepseek/deepseek-v4-flash-0731",
@@ -17,7 +17,7 @@ use sha2::{Digest, Sha256};
const CODEX_ACPX_DIGEST: &str =
"sha256:c4538599d1ab767db5dff50934f13bb5ba313a59d9c4a83e993fac4617ea63d3";
const PI_ACPX_DIGEST: &str =
"sha256:fe1e6da01b2a9e4c691ca27cf689d2d6de846a93be6b23fc1e103c9addd7b177";
"sha256:f35145437eeb355ed37bc5a4fa93d7ede561d9c45daf311979b46890b808ddd4";
fn temporary_directory(label: &str) -> PathBuf {
let nonce = SystemTime::now()
@@ -7,7 +7,7 @@ import { COPILOT_VERSION, materializePinnedCopilotBinary, resolveCopilotDistribu
const MAX_ARCHIVE_BYTES = 128 * 1024 * 1024;
const MAX_EXPANDED_BYTES = 384 * 1024 * 1024;
const PROFILE_DIGEST = "sha256:5e5955c57917a7e7c25703b3ed9e420cd72105eab033b611508dc37fdba3858b";
const PROFILE_DIGEST = "sha256:8faf47520f156c62c79ea6ca7d1d90a85ddc9a6621f798bd14e87bc6c643bd04";
/** Build-time only; outputRoot is the exact runner-owned platform asset directory. */
export async function buildPinnedCopilotDistribution({ outputRoot, platform = process.platform, architecture = process.arch }, { fetchImpl = fetch } = {}) {
@@ -39,7 +39,7 @@ export async function materializeCandidateProviderPack({ provider, outputRoot })
if (provider === "cursor") {
const result = await materializePinnedCursorDistribution({ destination: outputRoot });
return { version: result.version,
profileDigest: "sha256:1df2a15b93bc3a14fa47fa3315344ba023fe2412048047cdc6f32096a6336564",
profileDigest: "sha256:13207d7b6afcfe681d4fe9098655df056c2cf87e03f5141e961cd01c32c5bdd2",
closureDigest: `sha256:${result.closureSha256}` };
}
throw new Error(`The ${provider} candidate distribution builder is not included in this source revision`);
@@ -568,6 +568,9 @@ describe("native backend factory", () => {
it.each([
["cursor", "../../test/fixtures/cursor-acp/profile-v7-identity.json"],
["cursor", "../../test/fixtures/cursor-acp/profile-v10-identity.json"],
["copilot", "../../test/fixtures/copilot-profile-v14-identity.json"],
["pi", "../../test-fixtures/pi-acp/profile-v13-identity.json"],
["cursor", "../../test/fixtures/cursor-acp/profile-v9-identity.json"],
["copilot", "../../test/fixtures/copilot-profile-v7-identity.json"],
["copilot", "../../test/fixtures/copilot-profile-v9-identity.json"],
@@ -344,7 +344,7 @@ describe("NativeExecutionInputV1", () => {
profile: provider.profile,
});
expect(parseNativeExecutionInput(parsed)).toEqual(parsed);
for (const unsupportedVersion of [0, 15, 1.5, "14", null]) {
for (const unsupportedVersion of [0, 16, 1.5, "14", null]) {
expect(() => parseNativeExecutionInput({
...input,
session: { ...input.session, driverKind: "acpx_runtime" },
@@ -521,7 +521,7 @@ describe("native task context ownership", () => {
});
}
it.each(["off", "low", "high", "max"])("requires exact Pi13 thinking level %s in native input", piThinkingLevel => {
it.each(["off", "low", "high", "max"])("requires exact current Pi thinking level %s in native input", piThinkingLevel => {
const { qualificationModel, reportedModelId: _reported, permissionPolicy: _permission, modelPolicy: _policy, qualificationStatus: _status, ...profile } = QUALIFIED_ACPX_PROFILES.pi;
const value = { ...currentInput(), session: { ...currentInput().session, driverKind: "acpx_runtime" }, provider: { kind: "acpx", agent: "pi", model: qualificationModel, permissionMode: "approve-all", piThinkingLevel, profile } };
expect(parseNativeExecutionInput(value).provider).toEqual(value.provider);
@@ -91,7 +91,7 @@ export interface NativeAcpxProfileSnapshot {
protocolVersion: 1;
acpxVersion: "0.13.1";
agent: NativeAcpxAgent;
agentProfileVersion: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14;
agentProfileVersion: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15;
agentServerPackage: string;
agentServerVersion: string;
agentRuntimePackage: string | null;
@@ -578,9 +578,6 @@ export function parseNativeExecutionInput(value: unknown): NativeExecutionInput
if (provider.piThinkingLevel !== undefined && (provider.agent !== "pi" || (typeof provider.piThinkingLevel !== "string" || !["off", "low", "high", "max"].includes(provider.piThinkingLevel)))) {
throw new NativeExecutionInputError("input.provider.piThinkingLevel must be off, low, high, or max and is supported only for Pi");
}
if (provider.agent === "pi" && record(provider.profile, "input.provider.profile").agentProfileVersion === 13 && provider.piThinkingLevel === undefined) {
throw new NativeExecutionInputError("input.provider.piThinkingLevel is required for Pi profile 13");
}
if (provider.cursorMode !== undefined && (provider.agent !== "cursor"
|| (provider.cursorMode !== "agent" && provider.cursorMode !== "plan" && provider.cursorMode !== "ask"))) {
throw new NativeExecutionInputError("input.provider.cursorMode must be agent, plan, or ask and is supported only for Cursor");
@@ -616,10 +613,13 @@ export function parseNativeExecutionInput(value: unknown): NativeExecutionInput
|| profile.protocolVersion !== 1
|| profile.acpxVersion !== "0.13.1"
|| profile.agent !== provider.agent
|| (profile.agentProfileVersion !== 1 && profile.agentProfileVersion !== 2 && profile.agentProfileVersion !== 3 && profile.agentProfileVersion !== 4 && profile.agentProfileVersion !== 5 && profile.agentProfileVersion !== 6 && profile.agentProfileVersion !== 7 && profile.agentProfileVersion !== 8 && profile.agentProfileVersion !== 9 && profile.agentProfileVersion !== 10 && profile.agentProfileVersion !== 11 && profile.agentProfileVersion !== 12 && profile.agentProfileVersion !== 13 && profile.agentProfileVersion !== 14)
|| (profile.agentProfileVersion !== 1 && profile.agentProfileVersion !== 2 && profile.agentProfileVersion !== 3 && profile.agentProfileVersion !== 4 && profile.agentProfileVersion !== 5 && profile.agentProfileVersion !== 6 && profile.agentProfileVersion !== 7 && profile.agentProfileVersion !== 8 && profile.agentProfileVersion !== 9 && profile.agentProfileVersion !== 10 && profile.agentProfileVersion !== 11 && profile.agentProfileVersion !== 12 && profile.agentProfileVersion !== 13 && profile.agentProfileVersion !== 14 && profile.agentProfileVersion !== 15)
) {
throw new NativeExecutionInputError("input.provider.profile does not match the qualified ACPX v1 profile");
}
if (provider.agent === "pi" && profile.agentProfileVersion >= 13 && provider.piThinkingLevel === undefined) {
throw new NativeExecutionInputError("input.provider.piThinkingLevel is required for Pi profile 13 or later");
}
const runtimePackage = nullableText(profile.agentRuntimePackage, "input.provider.profile.agentRuntimePackage");
const runtimeVersion = nullableText(profile.agentRuntimeVersion, "input.provider.profile.agentRuntimeVersion");
if ((runtimePackage === null) !== (runtimeVersion === null)) {
@@ -80,8 +80,15 @@ describe("Copilot build-owned installation", () => {
expect(QUALIFIED_ACPX_PROFILES.copilot.qualificationStatus).toBe("pending");
});
it("admits the current v14 declaration and binds its source policy, receipts and patch hashes", () => {
const identity = JSON.parse(readFileSync(new URL("../../../test/fixtures/copilot-profile-v14-identity.json", import.meta.url), "utf8"));
it("preserves v14 history while binding the corrected outbound ACPX delivery", () => {
const prior = JSON.parse(readFileSync(new URL("../../../test/fixtures/copilot-profile-v14-identity.json", import.meta.url), "utf8"));
const current = JSON.parse(readFileSync(new URL("../../../test/fixtures/copilot-profile-v15-identity.json", import.meta.url), "utf8"));
expect(Object.keys(current.declaration).filter(key => current.declaration[key] !== prior.declaration[key]).sort()).toEqual(["acpxPatchSha256", "agentProfileVersion"]);
expect(QUALIFIED_ACPX_PROFILES.copilot.qualificationStatus).toBe("pending");
});
it("admits the current v15 declaration and binds its source policy, receipts and patch hashes", () => {
const identity = JSON.parse(readFileSync(new URL("../../../test/fixtures/copilot-profile-v15-identity.json", import.meta.url), "utf8"));
expect(identity.declaration.systemInstructionDelivery).toBe(COPILOT_SYSTEM_INSTRUCTION_DELIVERY);
expect(identity.declaration.sharedRuntimeContract).toBe("paperclip.acpx-runtime-contract.v1");
expect(identity.declaration.permissionContextContract).toBe(COPILOT_PERMISSION_CONTEXT_CONTRACT);
@@ -112,7 +119,7 @@ describe("Copilot build-owned installation", () => {
expect([...closure].filter(path => !bound.has(path))).toEqual(["negative-control:unbound-policy"]);
});
it.each([...PERMISSION_POLICY_SOURCES, ...SEMANTIC_RECEIPT_SOURCES])("changing %s changes the candidate identity", (_path, field) => {
const identity = JSON.parse(readFileSync(new URL("../../../test/fixtures/copilot-profile-v14-identity.json", import.meta.url), "utf8"));
const identity = JSON.parse(readFileSync(new URL("../../../test/fixtures/copilot-profile-v15-identity.json", import.meta.url), "utf8"));
identity.declaration[field] = "0".repeat(64);
const sorted = Object.fromEntries(Object.entries(identity.declaration).sort(([a], [b]) => a.localeCompare(b)));
expect(`sha256:${createHash("sha256").update(JSON.stringify(sorted)).digest("hex")}`).not.toBe(identity.commandDigest);
@@ -13,7 +13,7 @@ import { QUALIFIED_ACPX_PROFILES, type QualifiedAcpxProfile } from "./qualified-
const roots: string[] = [];
afterEach(async () => { vi.unstubAllEnvs(); await Promise.all(roots.splice(0).map(root => rm(root, { recursive: true, force: true }))); });
const candidate = (): QualifiedAcpxProfile => ({ ...QUALIFIED_ACPX_PROFILES.pi, agentProfileVersion: 13 });
const candidate = (): QualifiedAcpxProfile => ({ ...QUALIFIED_ACPX_PROFILES.pi, agentProfileVersion: 14 });
async function fixture() {
const root = await mkdtemp(join(tmpdir(), "pi-installation-test-")); roots.push(root);
@@ -26,11 +26,17 @@ async function fixture() {
}
describe("Pi installation factory", () => {
it("preserves profile 13 and changes only the version and corrected ACPX patch binding", async () => {
const prior = JSON.parse(await readFile(new URL("../../../test-fixtures/pi-acp/profile-v13-identity.json", import.meta.url), "utf8"));
const current = JSON.parse(await readFile(new URL("../../../test-fixtures/pi-acp/profile-v14-identity.json", import.meta.url), "utf8"));
expect(Object.keys(current.declaration).filter(key => JSON.stringify(current.declaration[key]) !== JSON.stringify(prior.declaration[key])).sort()).toEqual(["acpxPatchSha256", "agentProfileVersion"]);
});
it("binds the profile declaration to the reviewed patch and platform closure pins", async () => {
const hash = (bytes: string | Uint8Array) => createHash("sha256").update(bytes).digest("hex");
const canonical = (value: any): string => Array.isArray(value) ? `[${value.map(canonical).join(",")}]`
: value && typeof value === "object" ? `{${Object.keys(value).sort().map(key => `${JSON.stringify(key)}:${canonical(value[key])}`).join(",")}}` : JSON.stringify(value);
const identity = JSON.parse(await readFile(new URL("../../../test-fixtures/pi-acp/profile-v13-identity.json", import.meta.url), "utf8"));
const identity = JSON.parse(await readFile(new URL("../../../test-fixtures/pi-acp/profile-v14-identity.json", import.meta.url), "utf8"));
const declaration = identity.declaration;
expect(declaration.sharedRuntimeContract).toBe("paperclip.acpx-runtime-contract.v1");
expect(identity.commandDigest).toBe(`sha256:${hash(canonical(declaration))}`);
@@ -57,18 +63,19 @@ describe("Pi installation factory", () => {
it("rejects legacy profiles and caller-selected identities", () => {
expect(() => assertPiInstallationProfile(candidate())).not.toThrow();
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 1 })).toThrow("version 13");
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 2 })).toThrow("version 13");
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 3 })).toThrow("version 13");
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 4 })).toThrow("version 13");
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 5 })).toThrow("version 13");
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 6 })).toThrow("version 13");
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 7 })).toThrow("version 13");
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 8 })).toThrow("version 13");
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 9 })).toThrow("version 13");
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 10 })).toThrow("version 13");
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 11 })).toThrow("version 13");
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 12 })).toThrow("version 13");
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 1 })).toThrow("version 14");
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 2 })).toThrow("version 14");
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 3 })).toThrow("version 14");
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 4 })).toThrow("version 14");
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 5 })).toThrow("version 14");
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 6 })).toThrow("version 14");
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 7 })).toThrow("version 14");
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 8 })).toThrow("version 14");
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 9 })).toThrow("version 14");
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 10 })).toThrow("version 14");
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 11 })).toThrow("version 14");
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 12 })).toThrow("version 14");
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 13 })).toThrow("version 14");
for (const changed of [{ agentServerVersion: "latest" }, { commandDigest: `sha256:${"0".repeat(64)}` }, { reportedModelId: "different" }, { agentRuntimePackage: "ambient-pi" }]) {
expect(() => assertPiInstallationProfile({ ...candidate(), ...changed })).toThrow("trusted declaration");
}
@@ -25,7 +25,7 @@ function record(value: unknown): Record<string, unknown> {
/** Candidate identity is build-owned; model selection cannot substitute a CLI. */
export function assertPiInstallationProfile(profile: QualifiedAcpxProfile): void {
const trusted = QUALIFIED_ACPX_PROFILES.pi;
if (profile.agentProfileVersion !== 13) throw new Error("Pi rich ACP requires profile version 13; reopen the previous session");
if (profile.agentProfileVersion !== 14) throw new Error("Pi rich ACP requires profile version 14; reopen the previous session");
if (profile.agent !== "pi" || profile.driverKind !== trusted.driverKind || profile.protocolVersion !== trusted.protocolVersion || profile.acpxVersion !== trusted.acpxVersion || profile.agentServerPackage !== "pi-acp" || profile.agentServerVersion !== "0.0.33" || profile.agentRuntimePackage !== "@earendil-works/pi-coding-agent" || profile.agentRuntimeVersion !== "1.0.0" || profile.commandDigest !== trusted.commandDigest || profile.permissionPolicy !== "interactive" || profile.qualificationModel !== PI_MODEL || profile.reportedModelId !== PI_MODEL) throw new Error("Pi distribution profile differs from its trusted declaration");
}
@@ -63,8 +63,11 @@ describe("qualified ACPX profiles", () => {
});
});
it("binds Cursor v10 to native instructions, tool identity, closures and the exact ACPX guard patch", () => {
const identity = JSON.parse(readFileSync(new URL("../../../test/fixtures/cursor-acp/profile-v10-identity.json", import.meta.url), "utf8"));
it("binds Cursor v11 to native instructions, tool identity, closures and the exact ACPX guard patch", () => {
const prior = JSON.parse(readFileSync(new URL("../../../test/fixtures/cursor-acp/profile-v10-identity.json", import.meta.url), "utf8"));
const identity = JSON.parse(readFileSync(new URL("../../../test/fixtures/cursor-acp/profile-v11-identity.json", import.meta.url), "utf8"));
expect(Object.keys(identity.declaration).filter(key => JSON.stringify(identity.declaration[key]) !== JSON.stringify(prior.declaration[key])).sort()).toEqual(["acpxPatchSha256", "agentProfileVersion"]);
expect(QUALIFIED_ACPX_PROFILES.cursor.qualificationStatus).toBe("pending");
const distribution = JSON.parse(readFileSync(new URL("../../../cursor-distributions.json", import.meta.url), "utf8"));
expect(identity.declaration.distribution).toEqual(distribution);
expect(identity.declaration.sharedRuntimeContract).toBe("paperclip.acpx-runtime-contract.v1");
@@ -47,23 +47,23 @@ export const QUALIFIED_ACPX_PROFILES: Readonly<
protocolVersion: ACPX_DRIVER_PROTOCOL_VERSION,
acpxVersion: QUALIFIED_ACPX_VERSION,
agent: "pi",
agentProfileVersion: 13,
agentProfileVersion: 14,
agentServerPackage: "pi-acp",
agentServerVersion: "0.0.33",
agentRuntimePackage: "@earendil-works/pi-coding-agent",
agentRuntimeVersion: "1.0.0",
commandDigest:
"sha256:fe1e6da01b2a9e4c691ca27cf689d2d6de846a93be6b23fc1e103c9addd7b177",
"sha256:f35145437eeb355ed37bc5a4fa93d7ede561d9c45daf311979b46890b808ddd4",
qualificationModel: "openrouter/deepseek/deepseek-v4-flash-0731",
reportedModelId: "openrouter/deepseek/deepseek-v4-flash-0731",
permissionPolicy: "interactive",
},
cursor: {
driverKind: ACPX_DRIVER_KIND, protocolVersion: ACPX_DRIVER_PROTOCOL_VERSION,
acpxVersion: QUALIFIED_ACPX_VERSION, agent: "cursor", agentProfileVersion: 10,
acpxVersion: QUALIFIED_ACPX_VERSION, agent: "cursor", agentProfileVersion: 11,
agentServerPackage: "cursor-agent", agentServerVersion: "2026.09.26-dd393fe",
agentRuntimePackage: null, agentRuntimeVersion: null,
commandDigest: "sha256:1df2a15b93bc3a14fa47fa3315344ba023fe2412048047cdc6f32096a6336564",
commandDigest: "sha256:13207d7b6afcfe681d4fe9098655df056c2cf87e03f5141e961cd01c32c5bdd2",
// Authenticated discovery has not established a qualification model. Never
// turn this empty declaration into a default; callers must select an ID.
qualificationModel: "", reportedModelId: "", permissionPolicy: "interactive",
@@ -71,10 +71,10 @@ export const QUALIFIED_ACPX_PROFILES: Readonly<
},
copilot: {
driverKind: ACPX_DRIVER_KIND, protocolVersion: ACPX_DRIVER_PROTOCOL_VERSION,
acpxVersion: QUALIFIED_ACPX_VERSION, agent: "copilot", agentProfileVersion: 14,
acpxVersion: QUALIFIED_ACPX_VERSION, agent: "copilot", agentProfileVersion: 15,
agentServerPackage: "@github/copilot", agentServerVersion: "1.0.88",
agentRuntimePackage: null, agentRuntimeVersion: null,
commandDigest: "sha256:5e5955c57917a7e7c25703b3ed9e420cd72105eab033b611508dc37fdba3858b",
commandDigest: "sha256:8faf47520f156c62c79ea6ca7d1d90a85ddc9a6621f798bd14e87bc6c643bd04",
// Authenticated discovery has not established a qualification model. Never
// turn this empty declaration into a default; callers must select an ID.
qualificationModel: "", reportedModelId: "", permissionPolicy: "interactive",
@@ -27,6 +27,9 @@ afterEach(async () => {
describe("ACPX recovery identity", () => {
it.each([
["cursor", "../../../test/fixtures/cursor-acp/profile-v10-identity.json"],
["copilot", "../../../test/fixtures/copilot-profile-v14-identity.json"],
["pi", "../../../test-fixtures/pi-acp/profile-v13-identity.json"],
["cursor", "../../../test/fixtures/cursor-acp/profile-v7-identity.json"],
["cursor", "../../../test/fixtures/cursor-acp/profile-v8-identity.json"],
["cursor", "../../../test/fixtures/cursor-acp/profile-v9-identity.json"],
@@ -42,8 +42,30 @@ import {
type PrpEvent,
type PrpStructuredRunResult,
} from "./codex-app-server-driver.test-support.js";
import { rehydrateRunnerdItemNotification } from "../../live/runnerd-codex-transport.js";
describe("Codex app-server Codex driver", () => {
it("retains one correlation-bound steering acknowledgement after the rehydrated runnerd echo", async () => {
const transport = new FakeCodexTransport("thread-root");
const session = await makeDriver([transport]).openSession({
runId: "run-steering-echo", normalizedSessionId: "normalized-steering-echo", workingDirectory: WORKSPACE,
});
const { turnId } = await session.startTurn({ message: { role: "user", text: "Start." } });
await session.steer?.({ turnId, message: { role: "user", text: "Stay concise." }, correlationId: "queued-comment-1" });
transport.push("item/completed", rehydrateRunnerdItemNotification({
provider: "acpx", providerTurnId: turnId, itemId: `acpx-control-${"a".repeat(64)}`,
kind: "steering_acknowledgement", mode: "steer", status: "acknowledged",
text: "Steering acknowledged for the active turn.",
}, "thread-root", turnId));
await new Promise(resolve => setTimeout(resolve, 0));
await session.close({ reason: "fixture complete" });
const events: PrpEvent[] = [];
for await (const event of session.events()) events.push(event);
expect(events.filter(event => event.eventType === "item.completed" && event.payload.kind === "steering_acknowledgement"))
.toEqual([expect.objectContaining({ turnId, itemId: `${turnId}:steer:queued-comment-1`,
payload: expect.objectContaining({ kind: "steering_acknowledgement", status: "acknowledged" }) })]);
});
it("captures an exact skillless model/environment snapshot with credentials absent", async () => {
const transport = new FakeCodexTransport();
const session = await makeDriver([transport]).openSession({
@@ -331,12 +331,12 @@ async function mapNotificationBody(state: CodexSessionState, notification: Codex
if (
notification.method === "item/completed"
&& text(params.kind) === "steering_acknowledgement"
&& Object.keys(item).length === 0
) {
// runnerd persists its own command acknowledgement as a canonical PRP
// item. The request() call is already the authoritative acknowledgement
// and steer() emits the user-visible item with the active turn binding.
// Do not reinterpret this transport-level echo as an unbound Codex item.
// Rehydration adds an item object to this transport echo; it still must
// not become a second acknowledgement beside the correlation-bound item.
return;
}
if (notification.method === "paperclip/runResult") {
@@ -43,8 +43,8 @@ it("admits Pi live sessions without candidate opt-in while preserving the exact
const session = await service.create({ provider: "acpx", acpxAgent: "pi", piThinkingLevel: "low", requestedModel: "openrouter/deepseek/deepseek-v4-flash-0731" });
try {
expect(session.snapshot().config.acpxProfile).toMatchObject({
agent: "pi", agentProfileVersion: 13,
commandDigest: "sha256:fe1e6da01b2a9e4c691ca27cf689d2d6de846a93be6b23fc1e103c9addd7b177",
agent: "pi", agentProfileVersion: 14,
commandDigest: "sha256:f35145437eeb355ed37bc5a4fa93d7ede561d9c45daf311979b46890b808ddd4",
});
await expect(service.create({ provider: "acpx", acpxAgent: "pi", piThinkingLevel: "low", requestedModel: "another-model" }))
.rejects.toThrow("requires exact model");
@@ -0,0 +1,43 @@
{
"commandDigest": "sha256:f35145437eeb355ed37bc5a4fa93d7ede561d9c45daf311979b46890b808ddd4",
"declaration": {
"schema": "paperclip.acpx_profile_declaration.v1",
"agent": "pi",
"agentProfileVersion": 14,
"acpxVersion": "0.13.1",
"agentServerVersion": "0.0.33",
"agentRuntimeVersion": "1.0.0",
"nodeVersion": "24.21.0",
"closure": {
"darwin-arm64": "f6538a35e08f1c1816e738277a1843acfa1ce4522bbaa3340dbddd859dc7dd04",
"darwin-x64": "4728e5a4e7fc1ba602c3e219824fb84884b05a06cdd19dd3e521ee312e1b373a",
"linux-x64": "2957c0ec20ca1ace64d1a2b10c4a99f47f59e0c5c33a89161c1d5b48341c2b25"
},
"wrapperSha256": "8ef777e9e6a3cef37775bab2b800e4931330c23a0b151ce07fdc5b943f28435d",
"helperSha256": "2191b1e5f281d24508ec7eaff39011ee863a8a70bf54f4aa0490ad0172b323da",
"extensionSha256": "8f9b538dbe924a314099f9597ee3876f7ccd88211c4f4b2e52f46b6c7ffa76ba",
"agentFilesBinding": "registered-runtime-context-v1",
"nativeQuestions": "select-confirm-input-editor-v2",
"pathPolicy": "sdk-normalization-v1",
"mcpToolNames": "provider-safe-exact-source-v1",
"acpxPatchSha256": "c0139d0b3af085ec5272a7812c9f89aff0e8618d1d44d9c9e9a59525d1972436",
"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"
}
}
}
@@ -0,0 +1,43 @@
{
"commandDigest": "sha256:8faf47520f156c62c79ea6ca7d1d90a85ddc9a6621f798bd14e87bc6c643bd04",
"declaration": {
"schema": "paperclip.acpx_profile_declaration.v1",
"agent": "copilot",
"agentProfileVersion": 15,
"acpxVersion": "0.13.1",
"agentServerVersion": "1.0.88",
"protocolPolicyRevision": "copilot-agent-manual-v1",
"acpxPatchSha256": "c0139d0b3af085ec5272a7812c9f89aff0e8618d1d44d9c9e9a59525d1972436",
"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": "fe1bb611bea289d81b1c7c973fbb01b93de133a7caa8fe78b7ff4ee1f485b224",
"permissionAdapterSourceSha256": "49421a779c76e286ce65d75052026eab9630b1995af9ccf045a2d43d284633ce",
"permissionLocationsSourceSha256": "f39a81da395b6e23aa2a1a45572a28846e5e81bfd3d764d9a456f74a79c752e9",
"permissionRedactionSourceSha256": "687270d7bbe3dccce6c2fed8664e76cda724b43e545cb153b9f54b018de52927",
"permissionClassifierSourceSha256": "1161eabd82e26133e3b3db3ab567f65d52808b03f176e93497e02c8330349fb4",
"permissionIdentitySourceSha256": "770b47305e571a81f344883057d0e4a482a1cd59ac77d045836593a8e739deb1",
"semanticToolReceiptContract": "paperclip.semantic_tool_receipt.v2",
"semanticReceiptSourceSha256": "b2795726b2234a663fec1a6ca11f0551ae69882fe9e82aa39686e952d036e791",
"semanticBridgeSourceSha256": "8a43ebafb7a85dd1714ed6945670472666dcb63e8af32b4a74644163fa0e370d",
"toolEvidenceSourceSha256": "4b517eed447615b3cc820af348ccc360978175149caa0bbc838d1f78332abe75",
"semanticNormalizedInputContract": "validated-forwarded-input-commit-v1",
"semanticSidecarSourceSha256": "b83898b2453d0f425275d3bc58140675fc8f14505703103079094093f16be43f",
"semanticDirectDriverSourceSha256": "2db465e56ba41f06b406e039e566d9e3aea0a014fc6a47a6a54d5bc682dabcab",
"semanticValidationSourceSha256": "039fca52266fe21ba74ca64660d21228314d8ed737b19ba67941bfb25086e4d5",
"semanticNormalizationSourceSha256": "b1ce59251bf9c671f66e0e5173646190b9a2d1bfc5bdb7fb123824213986b92b",
"semanticCompletionContractSourceSha256": "00a684f9aea87f68eabf0974681c52461bc308631a1252a89c4ed087d7158b28",
"semanticValidatorsSourceSha256": "950b49b233d8dd62ff858398becfc55f0a181160cb8a434d9221b5c17816867c",
"semanticSchemaBundleSourceSha256": "2974c75590f816fbdbc7806f531698fdc8d62a99601a17625b81de0f074d6383",
"semanticSidecarProtocolSourceSha256": "72d9800603a652d6580084bb3b5dac4d4dd6d644324b0d15d293d7c198fd6b55",
"semanticSidecarReceiptCommitContract": "correlated-tool-resolve-v1"
}
}
@@ -0,0 +1,59 @@
{
"commandDigest": "sha256:13207d7b6afcfe681d4fe9098655df056c2cf87e03f5141e961cd01c32c5bdd2",
"declaration": {
"schema": "paperclip.acpx_profile_declaration.v1",
"agent": "cursor",
"agentProfileVersion": 11,
"acpxVersion": "0.13.1",
"agentServerVersion": "2026.09.26-dd393fe",
"patchVersion": "paperclip-cursor-usage-v4",
"distribution": {
"schema": "paperclip.cursor_distribution.v1",
"version": "2026.09.26-dd393fe",
"platforms": {
"darwin-arm64": {
"url": "https://downloads.cursor.com/lab/2026.09.26-dd393fe/darwin/arm64/agent-cli-package.tar.gz",
"archiveSha256": "538827d96a779bab854a865c8e42859e8e87db34b5f69770261b90fc8cfff191",
"closureSha256": "257424bd48e35412091c6adfc61e4648e836757ec1d240d890bba81a24918c30",
"executable": "node",
"entrypoint": "index.js",
"vendorClosureSha256": "77394184a89b0e7384971181da19c3c82d83a399b04e7944b3f94fe3d7e62b25"
},
"darwin-x64": {
"url": "https://downloads.cursor.com/lab/2026.09.26-dd393fe/darwin/x64/agent-cli-package.tar.gz",
"archiveSha256": "ed1771c44cbf0f8059c67cac0d939e6a60eb730bd066d7dc4cbb14de314cfb6e",
"closureSha256": "6f28c799c5afdc64fbdff8a2157f565f17ae7615efe014ac389d63bf70cf2be2",
"executable": "node",
"entrypoint": "index.js",
"vendorClosureSha256": "7c8775160af74e5fb8d25a30e8412c1a6476b0661529078a686e5118fd1c5e5f"
},
"linux-x64": {
"url": "https://downloads.cursor.com/lab/2026.09.26-dd393fe/linux/x64/agent-cli-package.tar.gz",
"archiveSha256": "8085fd120f5c71f4eae7fea26a043718e5644e3071e4fab3220a0e58c51f9593",
"closureSha256": "eadb8bb8ffb0450455b15b88c9b230307a9e149958a0c452d16dd157b4633d74",
"executable": "node",
"entrypoint": "index.js",
"vendorClosureSha256": "bf04ef8ea6a63191067a6b3e15139aa2c793d8419daab246aa3f0a012e1bbcd9"
}
},
"patchVersion": "paperclip-cursor-usage-v4"
},
"agentFilesBinding": "registered-runtime-context-v1",
"instructionBinding": "native-global-rules-v1",
"instructionAdmission": "synchronous-protocol-guard-v1",
"acpxPatchSha256": "c0139d0b3af085ec5272a7812c9f89aff0e8618d1d44d9c9e9a59525d1972436",
"sessionModeAdmission": "native-config-ack-recovery-bound-v1",
"sessionModes": [
"agent",
"plan",
"ask"
],
"defaultSessionMode": "agent",
"sharedRuntimeContract": "paperclip.acpx-runtime-contract.v1",
"nativePlanToolIdentity": "request-item-id-bound-lifecycle-v1",
"nativePermissionToolIdentity": "opaque-native-tool-id-sha256-v1",
"toolIdentitySourceSha256": "770b47305e571a81f344883057d0e4a482a1cd59ac77d045836593a8e739deb1",
"permissionAdapterSourceSha256": "49421a779c76e286ce65d75052026eab9630b1995af9ccf045a2d43d284633ce",
"toolEvidenceSourceSha256": "8310308ed9605415cb28b7c350d83defef68e90cd30bc2f0491d3d09825e8cd9"
}
}
+42 -43
View File
@@ -20,10 +20,9 @@ index 5e2113ac0..45aff03c3 100644
private setSessionModelThroughConfig;
private setSessionModelThroughLegacyMethod;
diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js
index d454fd7c5..6eba74345 100644
--- a/dist/live-checkpoint-BSIrfgVo.js
+++ b/dist/live-checkpoint-BSIrfgVo.js
@@ -1068,12 +1068,14 @@ function serializeSessionRecordForDisk(record) {
@@ -1068,12 +1068,14 @@
last_agent_disconnect_reason: canonical.lastAgentDisconnectReason,
protocol_version: canonical.protocolVersion,
agent_capabilities: canonical.agentCapabilities,
@@ -38,7 +37,7 @@ index d454fd7c5..6eba74345 100644
acpx: canonical.acpx,
imported_from: canonical.importedFrom ? {
record_id: canonical.importedFrom.recordId,
@@ -1239,7 +1241,8 @@ function parseRequestTokenUsage(raw) {
@@ -1239,7 +1241,8 @@
for (const [key, value] of Object.entries(record)) {
const parsed = parseTokenUsage(value);
if (parsed == null) return null;
@@ -48,7 +47,7 @@ index d454fd7c5..6eba74345 100644
}
return usage;
}
@@ -1325,6 +1328,93 @@ function isAgentMessage$1(raw) {
@@ -1325,6 +1328,93 @@
function isConversationMessage(raw) {
return raw === "Resume" || isUserMessage$1(raw) || isAgentMessage$1(raw);
}
@@ -142,7 +141,7 @@ index d454fd7c5..6eba74345 100644
function parseConversationRecord(record) {
if (!hasValidConversationCore(record)) return;
const title = parseConversationTitle(record.title);
@@ -1339,7 +1429,8 @@ function parseConversationRecord(record) {
@@ -1339,7 +1429,8 @@
updated_at: record.updated_at,
cumulative_token_usage: cumulativeTokenUsage ?? {},
cumulative_cost: cumulativeCost,
@@ -152,7 +151,7 @@ index d454fd7c5..6eba74345 100644
};
}
const INVALID_VALUE = Symbol("invalid");
@@ -1541,12 +1632,14 @@ function parseSessionRecord(raw) {
@@ -1541,12 +1632,14 @@
lastAgentDisconnectReason: optionals.lastAgentDisconnectReason,
protocolVersion: typeof record.protocol_version === "number" ? record.protocol_version : void 0,
agentCapabilities: asRecord$4(record.agent_capabilities),
@@ -167,7 +166,7 @@ index d454fd7c5..6eba74345 100644
acpx: parseAcpxState(record.acpx),
importedFrom: metadata.importedFrom
};
@@ -1661,8 +1754,9 @@ const ZED_TAG_KEYS = /* @__PURE__ */ new Set([
@@ -1661,8 +1754,9 @@
"RedactedThinking",
"ToolUse"
]);
@@ -178,7 +177,7 @@ index d454fd7c5..6eba74345 100644
"agent_capabilities",
"messages.Agent.content.ToolUse.input",
"acpx.desired_config_options",
@@ -3104,10 +3198,10 @@ function authEnvKey(methodId) {
@@ -3104,10 +3198,10 @@
authEnvKeyCache.set(methodId, key);
return key;
}
@@ -191,7 +190,7 @@ index d454fd7c5..6eba74345 100644
if (typeof value === "string" && value.trim().length > 0) return value;
}
function protectedEnvKey(key) {
@@ -3135,8 +3229,21 @@ function promotePrefixedAuthEnvironment(env) {
@@ -3135,8 +3229,21 @@
}
return protectedKeys;
}
@@ -215,7 +214,7 @@ index d454fd7c5..6eba74345 100644
const protectedAuthEnvKeys = promotePrefixedAuthEnvironment(env);
if (authCredentials) for (const [methodId, credential] of Object.entries(authCredentials)) {
addAuthCredentialEnvKeys(protectedAuthEnvKeys, methodId, credential);
@@ -3178,10 +3285,10 @@ function resolveConfiguredAuthCredential(methodId, authCredentials) {
@@ -3178,10 +3285,10 @@
const configCredentials = authCredentials ?? {};
return configCredentials[methodId] ?? configCredentials[toEnvToken(methodId)];
}
@@ -228,7 +227,7 @@ index d454fd7c5..6eba74345 100644
stdio: [
"pipe",
"pipe",
@@ -3961,10 +4068,13 @@ function resolveClientCapabilities(params) {
@@ -3961,10 +4068,13 @@
...params.elicitationModes.includes("url") ? { url: {} } : {}
} } : {}
};
@@ -244,7 +243,7 @@ index d454fd7c5..6eba74345 100644
};
}
function hasResponseField(response, field) {
@@ -4067,6 +4177,27 @@ function createNdJsonMessageStream(agentCommand, output, input) {
@@ -4067,6 +4177,27 @@
} })
};
}
@@ -272,7 +271,7 @@ index d454fd7c5..6eba74345 100644
var AcpClient = class {
options;
connection;
@@ -4105,7 +4236,9 @@ var AcpClient = class {
@@ -4105,7 +4236,9 @@
cwd: asAbsoluteCwd(options.cwd),
authPolicy: options.authPolicy ?? "skip",
permissionPolicy: snapshotPermissionPolicy(options.permissionPolicy),
@@ -283,7 +282,7 @@ index d454fd7c5..6eba74345 100644
};
this.eventHandlers = {
onAcpMessage: this.options.onAcpMessage,
@@ -4216,9 +4349,21 @@ var AcpClient = class {
@@ -4216,9 +4349,21 @@
this.lastAgentExit = void 0;
this.lastKnownPid = child.pid ?? void 0;
this.attachAgentLifecycleObservers(child);
@@ -305,7 +304,7 @@ index d454fd7c5..6eba74345 100644
if (!this.options.verbose) return;
process.stderr.write(chunk);
});
@@ -4226,6 +4371,7 @@ var AcpClient = class {
@@ -4226,6 +4371,7 @@
const output = Readable.toWeb(child.stdout);
const stream = this.createTappedStream(createNdJsonMessageStream(this.options.agentCommand, input, output));
const connection = this.createConnection(stream, launch);
@@ -313,7 +312,7 @@ index d454fd7c5..6eba74345 100644
connection.signal.addEventListener("abort", () => {
this.recordAgentExit("connection_close", child.exitCode ?? null, child.signalCode ?? null);
}, { once: true });
@@ -4253,7 +4399,12 @@ var AcpClient = class {
@@ -4253,7 +4399,12 @@
geminiAcp: isGeminiAcpCommand(spawnCommand, args),
copilotAcp: isCopilotAcpCommand(spawnCommand, args),
claudeAcp: isClaudeAcpCommand(spawnCommand, args),
@@ -327,7 +326,7 @@ index d454fd7c5..6eba74345 100644
};
}
logAgentLaunch(plan) {
@@ -4279,10 +4430,17 @@ var AcpClient = class {
@@ -4279,10 +4430,17 @@
}
async spawnAgentProcess(plan) {
const spawnCommand = buildAgentSpawnCommand(plan.spawnCommand, plan.args, process.platform, plan.spawnOptions.env);
@@ -347,7 +346,7 @@ index d454fd7c5..6eba74345 100644
try {
await waitForSpawn$1(spawnedChild);
} catch (error) {
@@ -4293,10 +4451,10 @@ var AcpClient = class {
@@ -4293,10 +4451,10 @@
createConnection(stream, launch) {
const app = client({ name: "acpx" }).onNotification(methods.client.session.update, async ({ params }) => {
await this.handleSessionUpdate(params);
@@ -361,7 +360,7 @@ index d454fd7c5..6eba74345 100644
}).onRequest(methods.client.fs.readTextFile, async ({ params }) => {
return await this.handleReadTextFile(params);
}).onRequest(methods.client.fs.writeTextFile, async ({ params }) => {
@@ -4312,6 +4470,11 @@ var AcpClient = class {
@@ -4312,6 +4470,11 @@
}).onRequest(methods.client.terminal.release, async ({ params }) => {
return await this.handleReleaseTerminal(params);
});
@@ -373,7 +372,7 @@ index d454fd7c5..6eba74345 100644
if (launch.devinAcp) app.onRequest("_cognition.ai/request_diagnostics", (params) => {
return params && typeof params === "object" && !Array.isArray(params) ? params : {};
}, async () => ({}));
@@ -4333,12 +4496,12 @@ var AcpClient = class {
@@ -4333,12 +4496,12 @@
async initializeProtocolConnection(connection, launch) {
const initializePromise = connection.initialize({
protocolVersion: PROTOCOL_VERSION,
@@ -388,7 +387,7 @@ index d454fd7c5..6eba74345 100644
clientInfo: resolveClientInfo(launch.devinAcp)
});
const initialized = launch.geminiAcp ? await withTimeout(initializePromise, resolveGeminiAcpStartupTimeoutMs()) : await initializePromise;
@@ -4358,6 +4521,49 @@ var AcpClient = class {
@@ -4358,6 +4521,49 @@
throw normalizedError;
}
createTappedStream(base) {
@@ -438,7 +437,7 @@ index d454fd7c5..6eba74345 100644
const onAcpMessage = () => this.eventHandlers.onAcpMessage;
const onAcpOutputMessage = () => this.eventHandlers.onAcpOutputMessage;
const elicitationRequestIds = /* @__PURE__ */ new Set();
@@ -4367,8 +4573,12 @@ var AcpClient = class {
@@ -4367,8 +4573,12 @@
const shouldSuppressInboundReplaySessionUpdate = (message) => {
return this.suppressReplaySessionUpdateMessages && isSessionUpdateNotification(message);
};
@@ -452,7 +451,7 @@ index d454fd7c5..6eba74345 100644
if (requestId !== void 0) {
elicitationRequestIds.add(requestId);
return;
@@ -4379,6 +4589,7 @@ var AcpClient = class {
@@ -4379,6 +4589,7 @@
}
};
return {
@@ -460,7 +459,7 @@ index d454fd7c5..6eba74345 100644
readable: new ReadableStream({ async start(controller) {
const reader = base.readable.getReader();
try {
@@ -4386,16 +4597,27 @@ var AcpClient = class {
@@ -4386,16 +4597,27 @@
const { value, done } = await reader.read();
if (done) break;
if (!value) continue;
@@ -490,7 +489,7 @@ index d454fd7c5..6eba74345 100644
const promptOwner = promptRequestOwner(message);
if (promptOwner) bindPromptOwner(promptOwner);
const id = responseId(message);
@@ -4403,10 +4625,20 @@ var AcpClient = class {
@@ -4403,10 +4625,20 @@
onAcpOutputMessage()?.("outbound", message);
onAcpMessage()?.("outbound", message);
}
@@ -511,7 +510,7 @@ index d454fd7c5..6eba74345 100644
writer.releaseLock();
}
} })
@@ -4422,7 +4654,10 @@ var AcpClient = class {
@@ -4422,7 +4654,10 @@
const createPromise = this.runConnectionRequest(() => connection.newSession({
cwd: sessionCwd,
mcpServers: this.options.mcpServers ?? [],
@@ -523,21 +522,21 @@ index d454fd7c5..6eba74345 100644
}));
result = claudeAcp ? await withTimeout(createPromise, resolveClaudeAcpSessionCreateTimeoutMs()) : await createPromise;
} catch (error) {
@@ -4596,6 +4831,13 @@ var AcpClient = class {
@@ -4589,6 +4824,13 @@
} catch (error) {
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);
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");
const resolvedModelId = resolveRequestedModelId({
@@ -4864,7 +5106,7 @@ var AcpClient = class {
@@ -4864,7 +5106,7 @@
}
selectAuthMethod(methods) {
for (const method of methods) {
@@ -546,7 +545,7 @@ index d454fd7c5..6eba74345 100644
if (envCredential) return {
methodId: method.id,
credential: envCredential,
@@ -4890,7 +5125,7 @@ var AcpClient = class {
@@ -4890,7 +5132,7 @@
}
readAgentSpecificEnvCredential(methodId) {
if (!this.isGrokBuildAcpCommand() || methodId !== "xai.api_key") return;
@@ -555,7 +554,7 @@ index d454fd7c5..6eba74345 100644
return typeof value === "string" && value.trim().length > 0 ? value : void 0;
}
selectAgentManagedAuthMethod(methodId) {
@@ -4915,9 +5150,9 @@ var AcpClient = class {
@@ -4915,9 +5157,9 @@
await connection.authenticate({ methodId: selected.methodId });
this.log(`authenticated with method ${selected.methodId} (${selected.source})`);
}
@@ -567,7 +566,7 @@ index d454fd7c5..6eba74345 100644
if (hostResponse) return hostResponse;
const { response, recorded } = await this.resolvePermissionRequestFromMode(params);
if (!recorded) {
@@ -4926,14 +5161,81 @@ var AcpClient = class {
@@ -4926,14 +5168,81 @@
}
return response;
}
@@ -651,7 +650,7 @@ index d454fd7c5..6eba74345 100644
})).then((response) => ({
kind: "response",
response
@@ -4970,7 +5272,7 @@ var AcpClient = class {
@@ -4970,7 +5279,7 @@
isElicitationSessionCancelling(sessionId) {
return this.closing || this.cancellingSessionIds.has(sessionId);
}
@@ -660,7 +659,7 @@ index d454fd7c5..6eba74345 100644
if (!this.options.onPermissionRequest) return;
const signal = this.cancellationSignalForSession(params.sessionId);
try {
@@ -4978,7 +5280,7 @@ var AcpClient = class {
@@ -4978,7 +5287,7 @@
sessionId: params.sessionId,
raw: params,
inferredKind: inferToolKind(params)
@@ -669,7 +668,7 @@ index d454fd7c5..6eba74345 100644
return this.hostPermissionDecisionResponse(params, signal, decision);
} catch (error) {
return this.hostPermissionErrorResponse(params, signal, error);
@@ -5028,6 +5330,12 @@ var AcpClient = class {
@@ -5028,6 +5337,12 @@
attachAgentLifecycleObservers(child) {
child.once("exit", (exitCode, signal) => {
this.recordAgentExit("process_exit", exitCode, signal);
@@ -682,7 +681,7 @@ index d454fd7c5..6eba74345 100644
});
child.once("close", (exitCode, signal) => {
this.recordAgentExit("process_close", exitCode, signal);
@@ -5239,6 +5547,7 @@ function applyConversation(record, conversation) {
@@ -5239,6 +5554,7 @@
record.cumulative_token_usage = conversation.cumulative_token_usage;
record.cumulative_cost = conversation.cumulative_cost;
record.request_token_usage = conversation.request_token_usage;
@@ -690,7 +689,7 @@ index d454fd7c5..6eba74345 100644
}
//#endregion
//#region src/runtime/engine/session-options.ts
@@ -5701,7 +6010,8 @@ function cloneSessionConversation(conversation) {
@@ -5701,7 +6017,8 @@
updated_at: conversation.updated_at,
cumulative_token_usage: deepClone(conversation.cumulative_token_usage ?? {}),
cumulative_cost: cloneUsageCost(conversation.cumulative_cost),
@@ -700,7 +699,7 @@ index d454fd7c5..6eba74345 100644
};
}
function cloneUsageCost(cost) {
@@ -5771,10 +6081,20 @@ function recordSessionUpdate(conversation, state, notification, timestamp = isoN
@@ -5771,10 +6088,20 @@
trimConversationForRuntime(conversation);
return acpx;
}
@@ -723,7 +722,7 @@ index d454fd7c5..6eba74345 100644
updateConversationTimestamp(conversation, timestamp);
trimConversationForRuntime(conversation);
return true;
@@ -6435,6 +6755,27 @@ async function withConnectedSession(options) {
@@ -6435,6 +6762,27 @@
//#region src/runtime/engine/prompt-turn.ts
const SESSION_REPLY_IDLE_MS = 1e3;
const SESSION_REPLY_DRAIN_TIMEOUT_MS = 5e3;
@@ -751,7 +750,7 @@ index d454fd7c5..6eba74345 100644
async function runPromptTurn(params) {
try {
const promptPromise = params.client.prompt(params.sessionId, params.prompt, params.onPromptRequestStarted, params.onElicitation);
@@ -6444,7 +6785,23 @@ async function runPromptTurn(params) {
@@ -6444,7 +6792,23 @@
idleMs: SESSION_REPLY_IDLE_MS,
timeoutMs: SESSION_REPLY_DRAIN_TIMEOUT_MS
}).catch(() => {});
+3 -3
View File
@@ -20,8 +20,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, 13);
assert.equal(QUALIFIED_ACPX_PROFILES.pi.commandDigest, 'sha256:fe1e6da01b2a9e4c691ca27cf689d2d6de846a93be6b23fc1e103c9addd7b177');
assert.equal(QUALIFIED_ACPX_PROFILES.pi.agentProfileVersion, 14);
assert.equal(QUALIFIED_ACPX_PROFILES.pi.commandDigest, 'sha256:f35145437eeb355ed37bc5a4fa93d7ede561d9c45daf311979b46890b808ddd4');
assert.equal(QUALIFIED_ACPX_PROFILES.pi.qualificationModel, 'openrouter/deepseek/deepseek-v4-flash-0731');
const daemon = resolvePaperclipRunnerBinary();
assert.equal(await realpath(daemon), join(server, 'dist/vendor/paperclip-runner/bin/paperclip-runnerd'));
@@ -63,7 +63,7 @@ try {
assert.equal(state.activeTurnId, null);
assert.equal(state.identity, null);
assert.equal(state.providerExitUnconfirmed, false);
console.log(JSON.stringify({ schema: 'paperclip.pi.public-npm-install.v1', target: `${process.platform}-${process.arch}`, normalPackagedDaemon: true, exactPiProfile: 13, runtime: '1.0.0', credentials: 'none', promptCalls: 0, settledMs, cleanRunnerExit: true }));
console.log(JSON.stringify({ schema: 'paperclip.pi.public-npm-install.v1', target: `${process.platform}-${process.arch}`, normalPackagedDaemon: true, exactPiProfile: 14, runtime: '1.0.0', credentials: 'none', promptCalls: 0, settledMs, cleanRunnerExit: true }));
} finally {
try { await bundle?.transport.close(); }
finally { clearTimeout(watchdog); await rm(root, { recursive: true, force: true }); }