Skip to content

Commit be0e85a

Browse files
committed
fix(codex): accept abort markers without request responses
1 parent b273b81 commit be0e85a

2 files changed

Lines changed: 27 additions & 22 deletions

File tree

extensions/codex/src/app-server/run-attempt.test.ts

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2895,18 +2895,6 @@ describe("runCodexAppServerAttempt", () => {
28952895
});
28962896

28972897
await harness.waitForMethod("turn/start");
2898-
await harness.handleServerRequest({
2899-
id: "request-tool-1",
2900-
method: "item/tool/call",
2901-
params: {
2902-
threadId: "thread-1",
2903-
turnId: "turn-1",
2904-
callId: "call-1",
2905-
namespace: null,
2906-
tool: "message",
2907-
arguments: { action: "send", text: "already sent" },
2908-
},
2909-
});
29102898
await harness.notify({
29112899
method: "rawResponseItem/completed",
29122900
params: {
@@ -2919,7 +2907,7 @@ describe("runCodexAppServerAttempt", () => {
29192907
content: [
29202908
{
29212909
type: "input_text",
2922-
text: "<turn_aborted>\nThe user interrupted the previous turn on purpose.\n</turn_aborted>",
2910+
text: "<turn_aborted>\nThe user interrupted the previous turn on purpose. Any running unified exec processes may still be running in the background. If any tools/commands were aborted, they may have partially executed.\n</turn_aborted>",
29232911
},
29242912
],
29252913
},
@@ -2958,7 +2946,7 @@ describe("runCodexAppServerAttempt", () => {
29582946
content: [
29592947
{
29602948
type: "input_text",
2961-
text: "What does <turn_aborted> mean?",
2949+
text: "<turn_aborted>\nWhat does this marker mean?\n</turn_aborted>",
29622950
},
29632951
],
29642952
},

extensions/codex/src/app-server/run-attempt.ts

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -888,7 +888,6 @@ export async function runCodexAppServerAttempt(
888888
let turnCompletionLastActivityReason = "startup";
889889
let turnCompletionLastActivityDetails: Record<string, unknown> | undefined;
890890
let activeAppServerTurnRequests = 0;
891-
let sawTurnScopedRequestResponse = false;
892891

893892
const clearTurnCompletionIdleTimer = () => {
894893
if (turnCompletionIdleTimer) {
@@ -1149,9 +1148,7 @@ export async function runCodexAppServerAttempt(
11491148
// See openclaw/openclaw#67996.
11501149
const isTurnCompletion = notification.method === "turn/completed" && isCurrentTurnNotification;
11511150
const isTurnAbortMarker =
1152-
isCurrentTurnNotification &&
1153-
sawTurnScopedRequestResponse &&
1154-
isCodexTurnAbortMarkerNotification(notification);
1151+
isCurrentTurnNotification && isCodexTurnAbortMarkerNotification(notification);
11551152
const isTurnTerminal = isTurnCompletion || isTurnAbortMarker;
11561153
try {
11571154
await projector.handleNotification(notification);
@@ -1314,9 +1311,6 @@ export async function runCodexAppServerAttempt(
13141311
return response as JsonValue;
13151312
} finally {
13161313
activeAppServerTurnRequests = Math.max(0, activeAppServerTurnRequests - 1);
1317-
if (armCompletionWatchOnResponse) {
1318-
sawTurnScopedRequestResponse = true;
1319-
}
13201314
touchTurnCompletionActivity(`request:${request.method}:response`, {
13211315
arm: armCompletionWatchOnResponse,
13221316
});
@@ -2356,6 +2350,13 @@ function readNestedTurnId(record: JsonObject): string | undefined {
23562350
return isJsonObject(turn) ? readString(turn, "id") : undefined;
23572351
}
23582352

2353+
const CODEX_TURN_ABORT_MARKER_START = "<turn_aborted>";
2354+
const CODEX_TURN_ABORT_MARKER_END = "</turn_aborted>";
2355+
const CODEX_INTERRUPTED_USER_GUIDANCE =
2356+
"The user interrupted the previous turn on purpose. Any running unified exec processes may still be running in the background. If any tools/commands were aborted, they may have partially executed.";
2357+
const CODEX_INTERRUPTED_DEVELOPER_GUIDANCE =
2358+
"The previous turn was interrupted on purpose. Any running unified exec processes may still be running in the background. If any tools/commands were aborted, they may have partially executed.";
2359+
23592360
function isCodexTurnAbortMarkerNotification(notification: CodexServerNotification): boolean {
23602361
if (notification.method !== "rawResponseItem/completed" || !isJsonObject(notification.params)) {
23612362
return false;
@@ -2366,7 +2367,23 @@ function isCodexTurnAbortMarkerNotification(notification: CodexServerNotificatio
23662367
return false;
23672368
}
23682369
const text = extractRawResponseItemText(item).trim();
2369-
return text.startsWith("<turn_aborted>") && text.includes("</turn_aborted>");
2370+
const markerBody = readCodexTurnAbortMarkerBody(text);
2371+
return (
2372+
markerBody === CODEX_INTERRUPTED_USER_GUIDANCE ||
2373+
markerBody === CODEX_INTERRUPTED_DEVELOPER_GUIDANCE
2374+
);
2375+
}
2376+
2377+
function readCodexTurnAbortMarkerBody(text: string): string | undefined {
2378+
if (
2379+
!text.startsWith(CODEX_TURN_ABORT_MARKER_START) ||
2380+
!text.endsWith(CODEX_TURN_ABORT_MARKER_END)
2381+
) {
2382+
return undefined;
2383+
}
2384+
return text
2385+
.slice(CODEX_TURN_ABORT_MARKER_START.length, -CODEX_TURN_ABORT_MARKER_END.length)
2386+
.trim();
23702387
}
23712388

23722389
function extractRawResponseItemText(item: JsonObject): string {

0 commit comments

Comments
 (0)