From a02087a5c19100507c830abae659a930d6357fb3 Mon Sep 17 00:00:00 2001 From: Dotta Date: Wed, 2 Sep 2026 23:46:08 -0500 Subject: [PATCH] fix(runner): project ACPX session identity --- .../src/cli/acpx-runtime-sidecar.ts | 9 +++++---- .../src/drivers/acpx/recovery-identity.test.ts | 13 +++++++++++++ .../src/drivers/acpx/recovery-identity.ts | 18 ++++++++++++++++++ 3 files changed, 36 insertions(+), 4 deletions(-) diff --git a/packages/paperclip-runner/src/cli/acpx-runtime-sidecar.ts b/packages/paperclip-runner/src/cli/acpx-runtime-sidecar.ts index a9c4474e64..e5d8ddac31 100644 --- a/packages/paperclip-runner/src/cli/acpx-runtime-sidecar.ts +++ b/packages/paperclip-runner/src/cli/acpx-runtime-sidecar.ts @@ -23,6 +23,7 @@ import { type NormalizedAcpForm, } from "../drivers/acpx/acp-question-adapter.js"; import { openCodexAcpxRuntime } from "../drivers/acpx/codex-runtime-adapter.js"; +import { acpxProviderSessionIdentity } from "../drivers/acpx/recovery-identity.js"; import { resolveQualifiedAcpxProfile, type QualifiedAcpxAgent, @@ -282,7 +283,7 @@ async function dispatch( startedAt: new Date().toISOString(), }); return { - identity: opened.identity, + identity: acpxProviderSessionIdentity(openedHost.identity()), sidecarPid: process.pid, status: opened.status, }; @@ -397,7 +398,7 @@ async function dispatch( if (request.command === "session.read") { const activeHost = requireHost(); return { - identity: activeHost.identity(), + identity: acpxProviderSessionIdentity(activeHost.identity()), status: sanitizeRuntimeStatus( await readSidecarHostStatusWithin(activeHost), ), @@ -406,7 +407,7 @@ async function dispatch( if (request.command === "session.snapshot") { const activeHost = requireHost(); return { - identity: activeHost.identity(), + identity: acpxProviderSessionIdentity(activeHost.identity()), status: sanitizeRuntimeStatus( await readSidecarHostStatusWithin(activeHost), ), @@ -425,7 +426,7 @@ async function dispatch( // still serialized, and retainActiveHostCleanup keeps admission closed // until one sequential close proves ownership was released. const activeHost = requireHost({ allowCleanupRetry: true }); - const identity = activeHost.identity(); + const identity = acpxProviderSessionIdentity(activeHost.identity()); await closeSidecarHostForCommand( activeHost, boundedOptionalText(request.params.reason, "Paperclip suspension", 4_000), diff --git a/packages/paperclip-runner/src/drivers/acpx/recovery-identity.test.ts b/packages/paperclip-runner/src/drivers/acpx/recovery-identity.test.ts index e14b02a7cd..f295da359b 100644 --- a/packages/paperclip-runner/src/drivers/acpx/recovery-identity.test.ts +++ b/packages/paperclip-runner/src/drivers/acpx/recovery-identity.test.ts @@ -8,6 +8,7 @@ import { afterEach, describe, expect, it } from "vitest"; import { resolveQualifiedAcpxProfile } from "./qualified-profiles.js"; import { ACPX_IDENTITY_RECORD_SCHEMA, + acpxProviderSessionIdentity, createAcpxIdentityRecord, createAcpxRecoveryBinding, verifyExpectedAcpxIdentity, @@ -41,6 +42,18 @@ describe("ACPX recovery identity", () => { normalizedSessionId: "session-1", permissionMode: "approve-reads", }); + expect(acpxProviderSessionIdentity(record)).toEqual({ + kind: "acpx", + normalizedSessionId: "session-1", + acpxRecordId: fixture.expected.acpxRecordId, + backendSessionId: fixture.expected.backendSessionId, + agentSessionId: fixture.expected.agentSessionId, + profileDigest: fixture.binding.profileDigest, + workspaceDigest: fixture.binding.workspaceDigest, + requestedModel: fixture.binding.requestedModel, + effectiveModel: fixture.binding.effectiveModel, + permissionMode: "approve-reads", + }); expect(() => verifyExpectedAcpxIdentity(fixture.expected, fixture.binding, record), ).not.toThrow(); diff --git a/packages/paperclip-runner/src/drivers/acpx/recovery-identity.ts b/packages/paperclip-runner/src/drivers/acpx/recovery-identity.ts index f10836f2a7..d48faccec3 100644 --- a/packages/paperclip-runner/src/drivers/acpx/recovery-identity.ts +++ b/packages/paperclip-runner/src/drivers/acpx/recovery-identity.ts @@ -114,6 +114,24 @@ export function createAcpxIdentityRecord( }; } +/** Project the private persisted record into the PRP sidecar wire identity. */ +export function acpxProviderSessionIdentity( + record: AcpxIdentityRecord, +): AcpxExpectedSessionIdentity { + return { + kind: "acpx", + normalizedSessionId: record.normalizedSessionId, + acpxRecordId: record.acpxRecordId, + backendSessionId: record.backendSessionId, + agentSessionId: record.agentSessionId, + profileDigest: record.profileDigest, + workspaceDigest: record.workspaceDigest, + requestedModel: record.requestedModel, + effectiveModel: record.effectiveModel, + permissionMode: record.permissionMode, + }; +} + /** * Verify both the controller-provided identity and a persisted runtime record. * Only the complete v1 record is recoverable. Draft schema-less and