fix(codex): preserve raw reasoning source-reply guard

This commit is contained in:
Peter Steinberger
2026-05-27 07:23:20 +01:00
parent 4d6bcf9f17
commit 284098d2d8
2 changed files with 54 additions and 1 deletions

View File

@@ -5653,6 +5653,43 @@ describe("runCodexAppServerAttempt", () => {
expect(harness.request.mock.calls.some(([method]) => method === "turn/interrupt")).toBe(false);
});
it("keeps waiting after raw reasoning completes before a visible message call", async () => {
const harness = createStartedThreadHarness();
const params = createParams(
path.join(tempDir, "session.jsonl"),
path.join(tempDir, "workspace"),
);
params.timeoutMs = 60_000;
params.sourceReplyDeliveryMode = "message_tool_only";
let settled = false;
const run = runCodexAppServerAttempt(params, {
turnCompletionIdleTimeoutMs: 15,
turnTerminalIdleTimeoutMs: 500,
}).finally(() => {
settled = true;
});
await harness.waitForMethod("turn/start");
await harness.notify({
method: "rawResponseItem/completed",
params: {
threadId: "thread-1",
turnId: "turn-1",
item: { id: "raw-reasoning-1", type: "reasoning" },
},
});
await new Promise((resolve) => setTimeout(resolve, 25));
expect(settled).toBe(false);
await harness.completeTurn({ threadId: "thread-1", turnId: "turn-1" });
const result = await run;
expect(result.aborted).toBe(false);
expect(result.timedOut).toBe(false);
expect(result.promptError).toBeNull();
expect(harness.request.mock.calls.some(([method]) => method === "turn/interrupt")).toBe(false);
});
it("keeps the normal completion idle guard after non-source reasoning completes", async () => {
const harness = createStartedThreadHarness();
const params = createParams(

View File

@@ -2460,6 +2460,10 @@ export async function runCodexAppServerAttempt(
isReasoningItemCompletionNotification(notification) &&
activeTurnItemIds.size === 0 &&
params.sourceReplyDeliveryMode === "message_tool_only";
const shouldArmPostRawReasoningSourceReplyWatch =
rawResponseItemCompletedWithNoActiveItems &&
isRawReasoningCompletionNotification(notification) &&
params.sourceReplyDeliveryMode === "message_tool_only";
const shouldRearmCompletionIdleWatchAfterLastCurrentTurnItem =
isCurrentTurnNotification &&
notification.method === "item/completed" &&
@@ -2480,7 +2484,10 @@ export async function runCodexAppServerAttempt(
armTurnAssistantCompletionIdleWatch(describeNotificationActivity(notification));
} else if (postToolRawAssistantCompletionNeedsTerminalGuard) {
armTurnCompletionIdleWatch({ timeoutMs: postToolRawAssistantCompletionIdleTimeoutMs });
} else if (shouldArmPostReasoningSourceReplyWatch) {
} else if (
shouldArmPostReasoningSourceReplyWatch ||
shouldArmPostRawReasoningSourceReplyWatch
) {
armTurnCompletionIdleWatch({ timeoutMs: CODEX_POST_REASONING_SOURCE_REPLY_IDLE_TIMEOUT_MS });
} else if (unblockedAssistantCompletionRelease) {
armTurnAssistantCompletionIdleWatch(describeNotificationActivity(notification));
@@ -2511,6 +2518,7 @@ export async function runCodexAppServerAttempt(
!postToolRawAssistantCompletionNeedsTerminalGuard &&
!rawResponseItemCompletedWithNoActiveItems &&
!shouldArmPostReasoningSourceReplyWatch &&
!shouldArmPostRawReasoningSourceReplyWatch &&
!shouldRearmCompletionIdleWatchAfterLastCurrentTurnItem
) {
// The short completion-idle watchdog guards blind gaps after Codex
@@ -5274,6 +5282,14 @@ function isReasoningItemCompletionNotification(notification: CodexServerNotifica
return item ? readString(item, "type") === "reasoning" : false;
}
function isRawReasoningCompletionNotification(notification: CodexServerNotification): boolean {
if (!isJsonObject(notification.params) || notification.method !== "rawResponseItem/completed") {
return false;
}
const item = isJsonObject(notification.params.item) ? notification.params.item : undefined;
return item ? readString(item, "type") === "reasoning" : false;
}
function isAssistantCompletionReleaseNotification(
notification: CodexServerNotification,
turnCrossedToolHandoff: boolean,