


























@@ -1901,6 +1901,184 @@ describe("runCodexAppServerAttempt", () => {
19011901expect(request.mock.calls.some(([method]) => method === "turn/interrupt")).toBe(false);
19021902});
190319031904+it("does not release post-tool raw assistant progress after the assistant idle timeout", async () => {
1905+let notify: (notification: CodexServerNotification) => Promise<void> = async () => undefined;
1906+let handleRequest:
1907+| ((request: { id: string; method: string; params?: unknown }) => Promise<unknown>)
1908+| undefined;
1909+const request = vi.fn(async (method: string) => {
1910+if (method === "thread/start") {
1911+return threadStartResult("thread-1");
1912+}
1913+if (method === "turn/start") {
1914+return turnStartResult("turn-1", "inProgress");
1915+}
1916+return {};
1917+});
1918+setCodexAppServerClientFactoryForTest(
1919+async () =>
1920+({
1921+ request,
1922+addNotificationHandler: (handler: typeof notify) => {
1923+notify = handler;
1924+return () => undefined;
1925+},
1926+addRequestHandler: (
1927+handler: (request: {
1928+id: string;
1929+method: string;
1930+params?: unknown;
1931+}) => Promise<unknown>,
1932+) => {
1933+handleRequest = handler;
1934+return () => undefined;
1935+},
1936+}) as never,
1937+);
1938+const params = createParams(
1939+path.join(tempDir, "session.jsonl"),
1940+path.join(tempDir, "workspace"),
1941+);
1942+params.timeoutMs = 60_000;
1943+1944+const run = runCodexAppServerAttempt(params, {
1945+turnCompletionIdleTimeoutMs: 50,
1946+turnAssistantCompletionIdleTimeoutMs: 5,
1947+turnTerminalIdleTimeoutMs: 500,
1948+});
1949+await vi.waitFor(() => expect(handleRequest).toBeTypeOf("function"), { interval: 1 });
1950+1951+const toolResult = (await handleRequest?.({
1952+id: "request-tool-1",
1953+method: "item/tool/call",
1954+params: {
1955+threadId: "thread-1",
1956+turnId: "turn-1",
1957+callId: "call-1",
1958+namespace: null,
1959+tool: "message",
1960+arguments: { action: "send", text: "already sent" },
1961+},
1962+})) as { success?: boolean };
1963+expect(toolResult.success).toBe(false);
1964+await notify({
1965+method: "rawResponseItem/completed",
1966+params: {
1967+threadId: "thread-1",
1968+turnId: "turn-1",
1969+item: {
1970+type: "message",
1971+id: "raw-status-1",
1972+role: "assistant",
1973+content: [{ type: "output_text", text: "I'm writing the report now." }],
1974+},
1975+},
1976+});
1977+await new Promise((resolve) => setTimeout(resolve, 30));
1978+expect(request.mock.calls.some(([method]) => method === "turn/interrupt")).toBe(false);
1979+1980+await notify({
1981+method: "turn/completed",
1982+params: {
1983+threadId: "thread-1",
1984+turnId: "turn-1",
1985+turn: { id: "turn-1", status: "completed" },
1986+},
1987+});
1988+1989+const result = await run;
1990+expect(result.aborted).toBe(false);
1991+expect(result.timedOut).toBe(false);
1992+expect(result.promptError).toBeNull();
1993+expect(request.mock.calls.some(([method]) => method === "turn/interrupt")).toBe(false);
1994+});
1995+1996+it("does not release post-native-tool raw assistant progress after the assistant idle timeout", async () => {
1997+let notify: (notification: CodexServerNotification) => Promise<void> = async () => undefined;
1998+const request = vi.fn(async (method: string) => {
1999+if (method === "thread/start") {
2000+return threadStartResult("thread-1");
2001+}
2002+if (method === "turn/start") {
2003+return turnStartResult("turn-1", "inProgress");
2004+}
2005+return {};
2006+});
2007+setCodexAppServerClientFactoryForTest(
2008+async () =>
2009+({
2010+ request,
2011+addNotificationHandler: (handler: typeof notify) => {
2012+notify = handler;
2013+return () => undefined;
2014+},
2015+addRequestHandler: () => () => undefined,
2016+}) as never,
2017+);
2018+const params = createParams(
2019+path.join(tempDir, "session.jsonl"),
2020+path.join(tempDir, "workspace"),
2021+);
2022+params.timeoutMs = 60_000;
2023+2024+const run = runCodexAppServerAttempt(params, {
2025+turnCompletionIdleTimeoutMs: 100,
2026+turnAssistantCompletionIdleTimeoutMs: 5,
2027+turnTerminalIdleTimeoutMs: 500,
2028+});
2029+await vi.waitFor(
2030+() =>
2031+expect(request).toHaveBeenCalledWith("turn/start", expect.anything(), expect.anything()),
2032+{ interval: 1 },
2033+);
2034+await notify({
2035+method: "item/started",
2036+params: {
2037+threadId: "thread-1",
2038+turnId: "turn-1",
2039+item: { type: "commandExecution", id: "cmd-1", status: "inProgress" },
2040+},
2041+});
2042+await notify({
2043+method: "item/completed",
2044+params: {
2045+threadId: "thread-1",
2046+turnId: "turn-1",
2047+item: { type: "commandExecution", id: "cmd-1", status: "completed" },
2048+},
2049+});
2050+await notify({
2051+method: "rawResponseItem/completed",
2052+params: {
2053+threadId: "thread-1",
2054+turnId: "turn-1",
2055+item: {
2056+type: "message",
2057+id: "raw-status-1",
2058+role: "assistant",
2059+content: [{ type: "output_text", text: "I'm summarizing command output." }],
2060+},
2061+},
2062+});
2063+await new Promise((resolve) => setTimeout(resolve, 30));
2064+expect(request.mock.calls.some(([method]) => method === "turn/interrupt")).toBe(false);
2065+2066+await notify({
2067+method: "turn/completed",
2068+params: {
2069+threadId: "thread-1",
2070+turnId: "turn-1",
2071+turn: { id: "turn-1", status: "completed" },
2072+},
2073+});
2074+2075+const result = await run;
2076+expect(result.aborted).toBe(false);
2077+expect(result.timedOut).toBe(false);
2078+expect(result.promptError).toBeNull();
2079+expect(request.mock.calls.some(([method]) => method === "turn/interrupt")).toBe(false);
2080+});
2081+19042082it("logs raw assistant item context when the terminal watchdog fires", async () => {
19052083let notify: (notification: CodexServerNotification) => Promise<void> = async () => undefined;
19062084let handleRequest:
@@ -2435,7 +2613,83 @@ describe("runCodexAppServerAttempt", () => {
24352613});
24362614});
243726152438-it("does not release the session after only a raw assistant response item", async () => {
2616+it("does not release or return commentary raw assistant response items", async () => {
2617+let notify: (notification: CodexServerNotification) => Promise<void> = async () => undefined;
2618+const request = vi.fn(async (method: string) => {
2619+if (method === "thread/start") {
2620+return threadStartResult("thread-1");
2621+}
2622+if (method === "turn/start") {
2623+return turnStartResult("turn-1", "inProgress");
2624+}
2625+return {};
2626+});
2627+setCodexAppServerClientFactoryForTest(
2628+async () =>
2629+({
2630+ request,
2631+addNotificationHandler: (handler: typeof notify) => {
2632+notify = handler;
2633+return () => undefined;
2634+},
2635+addRequestHandler: () => () => undefined,
2636+}) as never,
2637+);
2638+const params = createParams(
2639+path.join(tempDir, "session.jsonl"),
2640+path.join(tempDir, "workspace"),
2641+);
2642+params.timeoutMs = 200;
2643+2644+const run = runCodexAppServerAttempt(params, {
2645+turnAssistantCompletionIdleTimeoutMs: 5,
2646+});
2647+await vi.waitFor(
2648+() =>
2649+expect(request).toHaveBeenCalledWith("turn/start", expect.anything(), expect.anything()),
2650+{ interval: 1 },
2651+);
2652+await notify({
2653+method: "rawResponseItem/completed",
2654+params: {
2655+threadId: "thread-1",
2656+turnId: "turn-1",
2657+item: {
2658+type: "message",
2659+id: "raw-commentary-1",
2660+role: "assistant",
2661+phase: "commentary",
2662+content: [{ type: "output_text", text: "I am checking the workspace." }],
2663+},
2664+},
2665+});
2666+await new Promise((resolve) => setTimeout(resolve, 20));
2667+2668+expect(request).not.toHaveBeenCalledWith("turn/interrupt", expect.anything());
2669+await notify({
2670+method: "turn/completed",
2671+params: {
2672+threadId: "thread-1",
2673+turnId: "turn-1",
2674+turn: { id: "turn-1", status: "completed" },
2675+},
2676+});
2677+2678+const result = await run;
2679+expect({
2680+aborted: result.aborted,
2681+timedOut: result.timedOut,
2682+promptError: result.promptError,
2683+assistantTexts: result.assistantTexts,
2684+}).toEqual({
2685+aborted: false,
2686+timedOut: false,
2687+promptError: null,
2688+assistantTexts: [],
2689+});
2690+});
2691+2692+it("releases the session after a raw assistant response item without turn completion", async () => {
24392693let notify: (notification: CodexServerNotification) => Promise<void> = async () => undefined;
24402694const request = vi.fn(async (method: string) => {
24412695if (method === "thread/start") {
@@ -2485,18 +2739,31 @@ describe("runCodexAppServerAttempt", () => {
24852739},
24862740},
24872741});
2488-await new Promise((resolve) => setTimeout(resolve, 20));
2489274224902743const result = await run;
24912744expect({
24922745aborted: result.aborted,
24932746timedOut: result.timedOut,
24942747promptError: result.promptError,
2748+assistantTexts: result.assistantTexts,
24952749}).toEqual({
2496-aborted: true,
2497-timedOut: true,
2498-promptError: "codex app-server turn idle timed out waiting for turn/completed",
2750+aborted: false,
2751+timedOut: false,
2752+promptError: null,
2753+assistantTexts: ["Done."],
24992754});
2755+await vi.waitFor(
2756+() =>
2757+expect(request).toHaveBeenCalledWith(
2758+"turn/interrupt",
2759+{
2760+threadId: "thread-1",
2761+turnId: "turn-1",
2762+},
2763+{ timeoutMs: 5_000 },
2764+),
2765+{ interval: 1 },
2766+);
25002767});
2501276825022769it("keeps waiting when a current-turn item is still active", async () => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。