mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-18 18:04:45 +00:00
fix(codex): release textless completed app-server items
This commit is contained in:
@@ -1709,6 +1709,168 @@ describe("runCodexAppServerAttempt", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("releases the session when a real completed agent message omits text", async () => {
|
||||
let notify: (notification: CodexServerNotification) => Promise<void> = async () => undefined;
|
||||
const request = vi.fn(async (method: string) => {
|
||||
if (method === "thread/start") {
|
||||
return threadStartResult("thread-1");
|
||||
}
|
||||
if (method === "turn/start") {
|
||||
return turnStartResult("turn-1", "inProgress");
|
||||
}
|
||||
return {};
|
||||
});
|
||||
__testing.setCodexAppServerClientFactoryForTests(
|
||||
async () =>
|
||||
({
|
||||
request,
|
||||
addNotificationHandler: (handler: typeof notify) => {
|
||||
notify = handler;
|
||||
return () => undefined;
|
||||
},
|
||||
addRequestHandler: () => () => undefined,
|
||||
}) as never,
|
||||
);
|
||||
const params = createParams(
|
||||
path.join(tempDir, "session.jsonl"),
|
||||
path.join(tempDir, "workspace"),
|
||||
);
|
||||
params.timeoutMs = 200;
|
||||
|
||||
const run = runCodexAppServerAttempt(params, {
|
||||
turnAssistantCompletionIdleTimeoutMs: 5,
|
||||
});
|
||||
await vi.waitFor(
|
||||
() =>
|
||||
expect(request).toHaveBeenCalledWith("turn/start", expect.anything(), expect.anything()),
|
||||
{ interval: 1 },
|
||||
);
|
||||
await notify({
|
||||
method: "item/agentMessage/delta",
|
||||
params: {
|
||||
threadId: "thread-1",
|
||||
turnId: "turn-1",
|
||||
itemId: "msg-final-1",
|
||||
delta: "Done.",
|
||||
},
|
||||
});
|
||||
await notify({
|
||||
method: "item/completed",
|
||||
params: {
|
||||
threadId: "thread-1",
|
||||
turnId: "turn-1",
|
||||
item: {
|
||||
type: "agentMessage",
|
||||
id: "msg-final-1",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await expect(run).resolves.toMatchObject({
|
||||
aborted: false,
|
||||
timedOut: false,
|
||||
promptError: null,
|
||||
assistantTexts: ["Done."],
|
||||
});
|
||||
await vi.waitFor(
|
||||
() =>
|
||||
expect(request).toHaveBeenCalledWith(
|
||||
"turn/interrupt",
|
||||
{
|
||||
threadId: "thread-1",
|
||||
turnId: "turn-1",
|
||||
},
|
||||
{ timeoutMs: 5_000 },
|
||||
),
|
||||
{ interval: 1 },
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps the completed assistant release armed across bookkeeping notifications", async () => {
|
||||
let notify: (notification: CodexServerNotification) => Promise<void> = async () => undefined;
|
||||
const request = vi.fn(async (method: string) => {
|
||||
if (method === "thread/start") {
|
||||
return threadStartResult("thread-1");
|
||||
}
|
||||
if (method === "turn/start") {
|
||||
return turnStartResult("turn-1", "inProgress");
|
||||
}
|
||||
return {};
|
||||
});
|
||||
__testing.setCodexAppServerClientFactoryForTests(
|
||||
async () =>
|
||||
({
|
||||
request,
|
||||
addNotificationHandler: (handler: typeof notify) => {
|
||||
notify = handler;
|
||||
return () => undefined;
|
||||
},
|
||||
addRequestHandler: () => () => undefined,
|
||||
}) as never,
|
||||
);
|
||||
const params = createParams(
|
||||
path.join(tempDir, "session.jsonl"),
|
||||
path.join(tempDir, "workspace"),
|
||||
);
|
||||
params.timeoutMs = 200;
|
||||
|
||||
const run = runCodexAppServerAttempt(params, {
|
||||
turnAssistantCompletionIdleTimeoutMs: 5,
|
||||
});
|
||||
await vi.waitFor(
|
||||
() =>
|
||||
expect(request).toHaveBeenCalledWith("turn/start", expect.anything(), expect.anything()),
|
||||
{ interval: 1 },
|
||||
);
|
||||
await notify({
|
||||
method: "item/agentMessage/delta",
|
||||
params: {
|
||||
threadId: "thread-1",
|
||||
turnId: "turn-1",
|
||||
itemId: "msg-final-1",
|
||||
delta: "Done.",
|
||||
},
|
||||
});
|
||||
await notify({
|
||||
method: "item/completed",
|
||||
params: {
|
||||
threadId: "thread-1",
|
||||
turnId: "turn-1",
|
||||
item: {
|
||||
type: "agentMessage",
|
||||
id: "msg-final-1",
|
||||
},
|
||||
},
|
||||
});
|
||||
await notify({
|
||||
method: "turn/plan/updated",
|
||||
params: {
|
||||
threadId: "thread-1",
|
||||
turnId: "turn-1",
|
||||
plan: [],
|
||||
},
|
||||
});
|
||||
|
||||
await expect(run).resolves.toMatchObject({
|
||||
aborted: false,
|
||||
timedOut: false,
|
||||
promptError: null,
|
||||
assistantTexts: ["Done."],
|
||||
});
|
||||
await vi.waitFor(
|
||||
() =>
|
||||
expect(request).toHaveBeenCalledWith(
|
||||
"turn/interrupt",
|
||||
{
|
||||
threadId: "thread-1",
|
||||
turnId: "turn-1",
|
||||
},
|
||||
{ timeoutMs: 5_000 },
|
||||
),
|
||||
{ interval: 1 },
|
||||
);
|
||||
});
|
||||
|
||||
it("does not release the session after only a raw assistant response item", async () => {
|
||||
let notify: (notification: CodexServerNotification) => Promise<void> = async () => undefined;
|
||||
const request = vi.fn(async (method: string) => {
|
||||
|
||||
@@ -1258,7 +1258,10 @@ export async function runCodexAppServerAttempt(
|
||||
armTurnAssistantCompletionIdleWatch(describeNotificationActivity(notification));
|
||||
} else if (unblockedAssistantCompletionRelease) {
|
||||
armTurnAssistantCompletionIdleWatch(describeNotificationActivity(notification));
|
||||
} else if (isCurrentTurnNotification) {
|
||||
} else if (
|
||||
isCurrentTurnNotification &&
|
||||
shouldDisarmAssistantCompletionIdleWatch(notification)
|
||||
) {
|
||||
disarmTurnAssistantCompletionIdleWatch();
|
||||
}
|
||||
if (
|
||||
@@ -2635,7 +2638,20 @@ function isCompletedAssistantNotification(notification: CodexServerNotification)
|
||||
return false;
|
||||
}
|
||||
const item = isJsonObject(notification.params.item) ? notification.params.item : undefined;
|
||||
return Boolean(item && readString(item, "type") === "agentMessage" && readString(item, "text"));
|
||||
return Boolean(item && readString(item, "type") === "agentMessage");
|
||||
}
|
||||
|
||||
function shouldDisarmAssistantCompletionIdleWatch(notification: CodexServerNotification): boolean {
|
||||
if (!isJsonObject(notification.params)) {
|
||||
return false;
|
||||
}
|
||||
if (notification.method === "item/started") {
|
||||
return true;
|
||||
}
|
||||
if (notification.method === "item/agentMessage/delta") {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function readNotificationItemId(notification: CodexServerNotification): string | undefined {
|
||||
|
||||
Reference in New Issue
Block a user