diff --git a/packages/paperclip-runner/src/live/runnerd-codex-transport.test.ts b/packages/paperclip-runner/src/live/runnerd-codex-transport.test.ts index 2f5d38c249a..3a5842abada 100644 --- a/packages/paperclip-runner/src/live/runnerd-codex-transport.test.ts +++ b/packages/paperclip-runner/src/live/runnerd-codex-transport.test.ts @@ -2120,6 +2120,33 @@ it("routes canonical session goals back through the Codex notification facade", ); }); +it("keeps canonical ACPX reasoning out of assistant deltas", () => { + expect(runnerdCanonicalNotificationMethod("item.delta", { + kind: "reasoning", channel: "summary", text: "Private reasoning", + })).toBe("item/reasoning/summaryTextDelta"); + expect(runnerdCanonicalNotificationMethod("item.delta", { + kind: "reasoning", channel: "detail", text: "Private reasoning", + })).toBe("item/reasoning/textDelta"); + expect(runnerdCanonicalNotificationMethod("item.delta", { + kind: "agentMessage", text: "Visible answer", + })).toBe("item/agentMessage/delta"); +}); + +it("preserves reasoning kinds and order inside coalesced canonical deltas", () => { + const events = [ + { kind: "reasoning", channel: "summary", text: "Private reasoning", itemId: "thought-1" }, + { kind: "agentMessage", text: "Visible answer", itemId: "answer-1" }, + { kind: "reasoning", channel: "detail", text: "Private detail", itemId: "thought-2" }, + ]; + expect(expandRunnerdCanonicalNotifications("item/agentMessage/delta", { + coalescedCount: events.length, events, + }, "item.delta")).toEqual([ + { method: "item/reasoning/summaryTextDelta", params: events[0] }, + { method: "item/agentMessage/delta", params: events[1] }, + { method: "item/reasoning/textDelta", params: events[2] }, + ]); +}); + it("continues consuming after the durable committed-event window rolls", () => { const rollingWindow = Array.from({ length: 64 }, (_, index) => ({ sourceSeq: index + 65, diff --git a/packages/paperclip-runner/src/live/runnerd-codex-transport.ts b/packages/paperclip-runner/src/live/runnerd-codex-transport.ts index d6a01fcd02f..50914b7e7d1 100644 --- a/packages/paperclip-runner/src/live/runnerd-codex-transport.ts +++ b/packages/paperclip-runner/src/live/runnerd-codex-transport.ts @@ -1322,10 +1322,19 @@ export function unseenRunnerdCommittedEvents< export function expandRunnerdCanonicalNotifications( method: string, input: unknown, + eventType?: string, ): Array<{ method: string; params: Record }> { const payload = record(input); if (!Array.isArray(payload.events)) return [{ method, params: payload }]; - return payload.events.map((event) => ({ method, params: record(event) })); + return payload.events.map((event) => { + const params = record(event); + return { + method: eventType + ? runnerdCanonicalNotificationMethod(eventType, params) ?? method + : method, + params, + }; + }); } export function runnerdCanonicalNotificationMethod( @@ -1338,6 +1347,14 @@ export function runnerdCanonicalNotificationMethod( if (eventType === "session.goal.snapshot" && payload.goal === null) { return undefined; } + // ACPX thoughts retain their reasoning kind through the notification facade. + // Treating them as assistant deltas exposes them in the task transcript and + // bypasses the existing reasoning redaction and progress handling. + if (eventType === "item.delta" && payload.kind === "reasoning") { + return payload.channel === "detail" + ? "item/reasoning/textDelta" + : "item/reasoning/summaryTextDelta"; + } return ( { "turn.started": "turn/started", @@ -5961,7 +5978,7 @@ class DurablePrpCodexTransport implements CodexAppServerTransport { : event.eventType === "provider.event" ? unwrapRunnerdProviderNotifications(eventPayload) : canonicalMethod - ? expandRunnerdCanonicalNotifications(canonicalMethod, eventPayload) + ? expandRunnerdCanonicalNotifications(canonicalMethod, eventPayload, event.eventType) : []; for (const payload of notifications) { const method = payload.method;