Skip to content

Commit 0038813

Browse files
committed
test: isolate Codex terminal release decision
1 parent c4f0da0 commit 0038813

2 files changed

Lines changed: 79 additions & 120 deletions

File tree

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

Lines changed: 45 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -303,41 +303,6 @@ function mockCall(mock: unknown, label: string, index = 0): unknown[] {
303303
return call;
304304
}
305305

306-
async function waitForPromiseForTest<T>(
307-
promise: Promise<T>,
308-
timeoutMs: number,
309-
label: string,
310-
): Promise<T> {
311-
let timer: ReturnType<typeof setTimeout> | undefined;
312-
try {
313-
return await Promise.race([
314-
promise,
315-
new Promise<never>((_resolve, reject) => {
316-
timer = setTimeout(() => reject(new Error(`${label} timed out`)), timeoutMs);
317-
}),
318-
]);
319-
} finally {
320-
if (timer) {
321-
clearTimeout(timer);
322-
}
323-
}
324-
}
325-
326-
async function drainPromiseForTest(
327-
promise: Promise<unknown>,
328-
timeoutMs: number,
329-
label: string,
330-
): Promise<void> {
331-
await waitForPromiseForTest(
332-
promise.then(
333-
() => undefined,
334-
() => undefined,
335-
),
336-
timeoutMs,
337-
label,
338-
);
339-
}
340-
341306
function openSocket(url: string): Promise<WebSocket> {
342307
return new Promise((resolve, reject) => {
343308
const socket = new WebSocket(url);
@@ -3243,84 +3208,51 @@ describe("runCodexAppServerAttempt", () => {
32433208
}
32443209
});
32453210

3246-
it("releases the turn after terminal dynamic tool responses", async () => {
3247-
const harness = createStartedThreadHarness();
3248-
testing.setOpenClawCodingToolsFactoryForTests((options) => [
3249-
{
3250-
...createRuntimeDynamicTool("image_generate"),
3251-
execute: vi.fn(async () => {
3252-
await options?.onYield?.("Image generation started; wait for completion.");
3253-
return {
3254-
content: [{ type: "text" as const, text: "Background task started." }],
3255-
details: { async: true, status: "started", taskId: "task-1" },
3256-
terminate: true,
3257-
};
3258-
}),
3259-
},
3260-
]);
3261-
3262-
const params = createParams(
3263-
path.join(tempDir, "session.jsonl"),
3264-
path.join(tempDir, "workspace"),
3265-
);
3266-
const abortController = new AbortController();
3267-
params.abortSignal = abortController.signal;
3268-
params.disableTools = false;
3269-
params.runtimePlan = createCodexRuntimePlanFixture();
3270-
3271-
const run = runCodexAppServerAttempt(params);
3272-
let completed = false;
3273-
try {
3274-
await harness.waitForMethod("turn/start", 10_000);
3275-
3276-
const toolResult = (await harness.handleServerRequest({
3277-
id: "request-image-generate",
3278-
method: "item/tool/call",
3279-
params: {
3280-
threadId: "thread-1",
3281-
turnId: "turn-1",
3282-
callId: "call-image-1",
3283-
namespace: null,
3284-
tool: "image_generate",
3285-
arguments: { prompt: "lighthouse" },
3286-
},
3287-
})) as {
3288-
contentItems?: Array<{ text?: string; type?: string }>;
3289-
success?: boolean;
3290-
};
3291-
3292-
expect(toolResult).toEqual({
3293-
success: true,
3294-
contentItems: [{ type: "inputText", text: "Background task started." }],
3295-
});
3296-
expect(harness.requests.some((request) => request.method === "turn/interrupt")).toBe(false);
3297-
const result = await waitForPromiseForTest(run, 20_000, "Codex terminal dynamic tool run");
3298-
completed = true;
3299-
3300-
expect(result.timedOut).toBe(false);
3301-
expect(result.promptError).toBeNull();
3302-
expect(result.yieldDetected).toBe(true);
3303-
expect(result.messagesSnapshot.map((message) => message.role)).toEqual([
3304-
"user",
3305-
"assistant",
3306-
"toolResult",
3307-
]);
3308-
expect(
3309-
harness.requests.some(
3310-
(request) =>
3311-
request.method === "turn/interrupt" &&
3312-
(request.params as { turnId?: string } | undefined)?.turnId === "turn-1",
3313-
),
3314-
).toBe(true);
3315-
} finally {
3316-
if (!completed) {
3317-
harness.close();
3318-
abortController.abort(new Error("test cleanup"));
3319-
await drainPromiseForTest(run, 1_000, "Codex terminal dynamic tool cleanup").catch(
3320-
() => undefined,
3321-
);
3322-
}
3323-
}
3211+
it("allows turn release after successful terminal dynamic tool responses", () => {
3212+
expect(
3213+
testing.shouldReleaseTurnAfterTerminalDynamicTool({
3214+
completed: false,
3215+
aborted: false,
3216+
responseSuccess: true,
3217+
currentTurnHadNonTerminalDynamicToolResult: false,
3218+
activeAppServerTurnRequests: 0,
3219+
activeTurnItemIdsCount: 0,
3220+
pendingOpenClawDynamicToolCompletionIdsCount: 0,
3221+
}),
3222+
).toBe(true);
3223+
expect(
3224+
testing.shouldReleaseTurnAfterTerminalDynamicTool({
3225+
completed: false,
3226+
aborted: false,
3227+
responseSuccess: true,
3228+
currentTurnHadNonTerminalDynamicToolResult: true,
3229+
activeAppServerTurnRequests: 0,
3230+
activeTurnItemIdsCount: 0,
3231+
pendingOpenClawDynamicToolCompletionIdsCount: 0,
3232+
}),
3233+
).toBe(false);
3234+
expect(
3235+
testing.shouldReleaseTurnAfterTerminalDynamicTool({
3236+
completed: false,
3237+
aborted: false,
3238+
responseSuccess: true,
3239+
currentTurnHadNonTerminalDynamicToolResult: false,
3240+
activeAppServerTurnRequests: 1,
3241+
activeTurnItemIdsCount: 0,
3242+
pendingOpenClawDynamicToolCompletionIdsCount: 0,
3243+
}),
3244+
).toBe(false);
3245+
expect(
3246+
testing.shouldReleaseTurnAfterTerminalDynamicTool({
3247+
completed: false,
3248+
aborted: false,
3249+
responseSuccess: true,
3250+
currentTurnHadNonTerminalDynamicToolResult: false,
3251+
activeAppServerTurnRequests: 0,
3252+
activeTurnItemIdsCount: 0,
3253+
pendingOpenClawDynamicToolCompletionIdsCount: 1,
3254+
}),
3255+
).toBe(false);
33243256
});
33253257

33263258
it("keeps mixed dynamic tool batches running after one terminal result", async () => {

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

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2156,13 +2156,15 @@ export async function runCodexAppServerAttempt(
21562156
durationMs: number;
21572157
}) => {
21582158
if (
2159-
completed ||
2160-
runAbortController.signal.aborted ||
2161-
!params.response.success ||
2162-
currentTurnHadNonTerminalDynamicToolResult ||
2163-
activeAppServerTurnRequests > 0 ||
2164-
activeTurnItemIds.size > 0 ||
2165-
pendingOpenClawDynamicToolCompletionIds.size > 0
2159+
!shouldReleaseTurnAfterTerminalDynamicTool({
2160+
completed,
2161+
aborted: runAbortController.signal.aborted,
2162+
responseSuccess: params.response.success,
2163+
currentTurnHadNonTerminalDynamicToolResult,
2164+
activeAppServerTurnRequests,
2165+
activeTurnItemIdsCount: activeTurnItemIds.size,
2166+
pendingOpenClawDynamicToolCompletionIdsCount: pendingOpenClawDynamicToolCompletionIds.size,
2167+
})
21662168
) {
21672169
return;
21682170
}
@@ -3458,6 +3460,30 @@ type TerminalToolExecutionDiagnostic = Extract<
34583460
{ type: "tool.execution.blocked" | "tool.execution.completed" | "tool.execution.error" }
34593461
>;
34603462

3463+
type TerminalDynamicToolReleaseState = {
3464+
completed: boolean;
3465+
aborted: boolean;
3466+
responseSuccess: boolean;
3467+
currentTurnHadNonTerminalDynamicToolResult: boolean;
3468+
activeAppServerTurnRequests: number;
3469+
activeTurnItemIdsCount: number;
3470+
pendingOpenClawDynamicToolCompletionIdsCount: number;
3471+
};
3472+
3473+
function shouldReleaseTurnAfterTerminalDynamicTool(
3474+
state: TerminalDynamicToolReleaseState,
3475+
): boolean {
3476+
return (
3477+
!state.completed &&
3478+
!state.aborted &&
3479+
state.responseSuccess &&
3480+
!state.currentTurnHadNonTerminalDynamicToolResult &&
3481+
state.activeAppServerTurnRequests === 0 &&
3482+
state.activeTurnItemIdsCount === 0 &&
3483+
state.pendingOpenClawDynamicToolCompletionIdsCount === 0
3484+
);
3485+
}
3486+
34613487
function isDynamicToolTerminalDiagnosticEvent(
34623488
event: DiagnosticEventPayload,
34633489
): event is TerminalToolExecutionDiagnostic {
@@ -5588,6 +5614,7 @@ export const testing = {
55885614
shouldProjectMirroredHistoryForCodexStart,
55895615
shouldEnableCodexAppServerNativeToolSurface,
55905616
shouldForceMessageTool,
5617+
shouldReleaseTurnAfterTerminalDynamicTool,
55915618
buildCodexPluginThreadConfigEligibilityLogData,
55925619
withCodexStartupTimeout,
55935620
setOpenClawCodingToolsFactoryForTests(factory: OpenClawCodingToolsFactory): void {

0 commit comments

Comments
 (0)