fix(codex): premark terminal app-server turns

This commit is contained in:
Peter Steinberger
2026-05-17 00:31:37 +01:00
parent c65801c5a9
commit 4cbf616d30
2 changed files with 63 additions and 11 deletions

View File

@@ -4849,6 +4849,46 @@ describe("runCodexAppServerAttempt", () => {
expect(result.timedOut).toBe(false);
});
it("does not fail when a buffered terminal notification is followed by client close", async () => {
let harness: ReturnType<typeof createAppServerHarness>;
let resolveBufferedTerminal!: () => void;
const bufferedTerminal = new Promise<void>((resolve) => {
resolveBufferedTerminal = resolve;
});
harness = createAppServerHarness(async (method) => {
if (method === "thread/start") {
return threadStartResult();
}
if (method === "turn/start") {
await harness.notify({
method: "item/started",
params: {
threadId: "thread-1",
turnId: "turn-1",
item: { id: "tool-1", type: "commandExecution" },
},
});
await harness.completeTurn({ threadId: "thread-1", turnId: "turn-1" });
resolveBufferedTerminal();
return turnStartResult("turn-1", "inProgress");
}
return {};
});
const run = runCodexAppServerAttempt(
createParams(path.join(tempDir, "session.jsonl"), path.join(tempDir, "workspace")),
{ turnTerminalIdleTimeoutMs: 60_000 },
);
await bufferedTerminal;
await new Promise<void>((resolve) => setImmediate(resolve));
harness.close();
const result = await run;
expect(result.promptError ?? undefined).toBeUndefined();
expect(result.aborted).toBe(false);
expect(result.timedOut).toBe(false);
});
it("does not time out when turn progress arrives before turn/start returns", async () => {
let harness: ReturnType<typeof createAppServerHarness>;
harness = createAppServerHarness(async (method) => {

View File

@@ -1464,6 +1464,19 @@ export async function runCodexAppServerAttempt(
});
};
const isTerminalTurnNotificationForTurn = (
notification: CodexServerNotification,
notificationTurnId: string,
): boolean => {
if (!isTurnNotification(notification.params, thread.threadId, notificationTurnId)) {
return false;
}
return (
notification.method === "turn/completed" ||
isCodexTurnAbortMarkerNotification(notification, { currentPromptText: promptBuild.prompt })
);
};
const handleNotification = async (notification: CodexServerNotification) => {
userInputBridge?.handleNotification(notification);
if (!projector || !turnId) {
@@ -1562,7 +1575,7 @@ export async function runCodexAppServerAttempt(
const isTurnAbortMarker =
isCurrentTurnNotification &&
isCodexTurnAbortMarkerNotification(notification, { currentPromptText: promptBuild.prompt });
const isTurnTerminal = isTurnCompletion || isTurnAbortMarker;
const isTurnTerminal = isTerminalTurnNotificationForTurn(notification, turnId);
if (isTurnTerminal) {
terminalTurnNotificationQueued = true;
}
@@ -1596,16 +1609,7 @@ export async function runCodexAppServerAttempt(
pendingNotifications.push(notification);
return Promise.resolve();
}
const isCurrentTurnNotification = isTurnNotification(
notification.params,
thread.threadId,
turnId,
);
if (
isCurrentTurnNotification &&
(notification.method === "turn/completed" ||
isCodexTurnAbortMarkerNotification(notification, { currentPromptText: promptBuild.prompt }))
) {
if (isTerminalTurnNotificationForTurn(notification, turnId)) {
terminalTurnNotificationQueued = true;
}
notificationQueue = notificationQueue.then(
@@ -2034,6 +2038,14 @@ export async function runCodexAppServerAttempt(
nativePostToolUseRelayEnabled:
nativeHookRelay?.allowedEvents.includes("post_tool_use") === true,
});
if (
isTerminalTurnStatus(turn.turn.status) ||
pendingNotifications.some((notification) =>
isTerminalTurnNotificationForTurn(notification, activeTurnId),
)
) {
terminalTurnNotificationQueued = true;
}
closeCleanup = (
client as {
addCloseHandler?: (handler: (client: CodexAppServerClient) => void) => () => void;