fix(runner): preserve reasoning through native event translation

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-09-22 18:32:11 -07:00
1 parent ab17109a21
commit 768d4e7755
2 files changed
+46 -2

No files matched your search

@@ -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,
@@ -1322,10 +1322,19 @@ export function unseenRunnerdCommittedEvents<
export function expandRunnerdCanonicalNotifications(
method: string,
input: unknown,
eventType?: string,
): Array<{ method: string; params: Record<string, unknown> }> {
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;