























@@ -3147,86 +3147,105 @@ describe("runCodexAppServerAttempt", () => {
31473147path.join(tempDir, "session.jsonl"),
31483148path.join(tempDir, "workspace"),
31493149);
3150+const abortController = new AbortController();
3151+params.abortSignal = abortController.signal;
31503152params.disableTools = false;
31513153params.runtimePlan = createCodexRuntimePlanFixture();
3152315431533155const run = runCodexAppServerAttempt(params);
3154-await harness.waitForMethod("thread/start");
3155-3156-const toolResult = (await harness.handleServerRequest({
3157-id: "request-echo-tool",
3158-method: "item/tool/call",
3159-params: {
3160-threadId: "thread-1",
3161-turnId: "turn-1",
3162-callId: "call-echo-1",
3163-namespace: null,
3164-tool: "echo",
3165-arguments: {},
3166-},
3167-})) as {
3168-contentItems?: Array<{ text?: string; type?: string }>;
3169-success?: boolean;
3170-};
3171-3172-expect(toolResult.success).toBe(true);
3173-expect(toolResult.contentItems?.[0]).toEqual({
3174-type: "inputText",
3175-text: "echo done",
3176-});
3177-await flushDiagnosticEvents();
3178-unsubscribeDiagnostics();
3179-3180-const toolDiagnosticEvents = diagnosticEvents.filter(
3181-(
3182-event,
3183-): event is Extract<
3184-DiagnosticEventPayload,
3185-{ type: "tool.execution.started" | "tool.execution.completed" | "tool.execution.error" }
3186-> => event.type.startsWith("tool.execution."),
3187-);
3188-const toolDiagnosticEventSummaries = toolDiagnosticEvents.map((event) => ({
3189-type: event.type,
3190-toolName: event.toolName,
3191-toolCallId: event.toolCallId,
3192-}));
3193-expect(toolDiagnosticEventSummaries).toContainEqual({
3194-type: "tool.execution.started",
3195-toolName: "echo",
3196-toolCallId: "call-echo-1",
3197-});
3198-expect(toolDiagnosticEventSummaries.at(-1)).toEqual({
3199-type: "tool.execution.completed",
3200-toolName: "echo",
3201-toolCallId: "call-echo-1",
3202-});
3203-expect(
3204-toolDiagnosticEventSummaries.filter((event) => event.type === "tool.execution.started"),
3205-).toHaveLength(1);
3206-expect(activeDiagnosticToolKeys(diagnosticEvents)).toEqual(new Set());
3156+let completed = false;
3157+let diagnosticsSubscribed = true;
3158+try {
3159+await harness.waitForMethod("thread/start", 10_000);
320731603208-await harness.notify({
3209-method: "item/completed",
3210-params: {
3211-threadId: "thread-1",
3212-turnId: "turn-1",
3213-completedAtMs: Date.now(),
3214-item: {
3215-type: "dynamicToolCall",
3216-id: "call-echo-1",
3161+const toolResult = (await harness.handleServerRequest({
3162+id: "request-echo-tool",
3163+method: "item/tool/call",
3164+params: {
3165+threadId: "thread-1",
3166+turnId: "turn-1",
3167+callId: "call-echo-1",
32173168namespace: null,
32183169tool: "echo",
32193170arguments: {},
3220-status: "completed",
3221-contentItems: [{ type: "inputText", text: "echo done" }],
3222-success: true,
3223-durationMs: 1,
32243171},
3225-},
3226-});
3172+})) as {
3173+contentItems?: Array<{ text?: string; type?: string }>;
3174+success?: boolean;
3175+};
322731763228-await harness.completeTurn({ threadId: "thread-1", turnId: "turn-1" });
3229-await run;
3177+expect(toolResult.success).toBe(true);
3178+expect(toolResult.contentItems?.[0]).toEqual({
3179+type: "inputText",
3180+text: "echo done",
3181+});
3182+await flushDiagnosticEvents();
3183+unsubscribeDiagnostics();
3184+diagnosticsSubscribed = false;
3185+3186+const toolDiagnosticEvents = diagnosticEvents.filter(
3187+(
3188+event,
3189+): event is Extract<
3190+DiagnosticEventPayload,
3191+{
3192+type: "tool.execution.started" | "tool.execution.completed" | "tool.execution.error";
3193+}
3194+> => event.type.startsWith("tool.execution."),
3195+);
3196+const toolDiagnosticEventSummaries = toolDiagnosticEvents.map((event) => ({
3197+type: event.type,
3198+toolName: event.toolName,
3199+toolCallId: event.toolCallId,
3200+}));
3201+expect(toolDiagnosticEventSummaries).toContainEqual({
3202+type: "tool.execution.started",
3203+toolName: "echo",
3204+toolCallId: "call-echo-1",
3205+});
3206+expect(toolDiagnosticEventSummaries.at(-1)).toEqual({
3207+type: "tool.execution.completed",
3208+toolName: "echo",
3209+toolCallId: "call-echo-1",
3210+});
3211+expect(
3212+toolDiagnosticEventSummaries.filter((event) => event.type === "tool.execution.started"),
3213+).toHaveLength(1);
3214+expect(activeDiagnosticToolKeys(diagnosticEvents)).toEqual(new Set());
3215+3216+await harness.notify({
3217+method: "item/completed",
3218+params: {
3219+threadId: "thread-1",
3220+turnId: "turn-1",
3221+completedAtMs: Date.now(),
3222+item: {
3223+type: "dynamicToolCall",
3224+id: "call-echo-1",
3225+namespace: null,
3226+tool: "echo",
3227+arguments: {},
3228+status: "completed",
3229+contentItems: [{ type: "inputText", text: "echo done" }],
3230+success: true,
3231+durationMs: 1,
3232+},
3233+},
3234+});
3235+3236+await harness.completeTurn({ threadId: "thread-1", turnId: "turn-1" });
3237+completed = true;
3238+await run;
3239+} finally {
3240+if (diagnosticsSubscribed) {
3241+unsubscribeDiagnostics();
3242+}
3243+if (!completed) {
3244+harness.close();
3245+abortController.abort(new Error("test cleanup"));
3246+await run.catch(() => {});
3247+}
3248+}
32303249});
3231325032323251it("releases the turn after terminal dynamic tool responses", async () => {
@@ -3249,50 +3268,62 @@ describe("runCodexAppServerAttempt", () => {
32493268path.join(tempDir, "session.jsonl"),
32503269path.join(tempDir, "workspace"),
32513270);
3271+const abortController = new AbortController();
3272+params.abortSignal = abortController.signal;
32523273params.disableTools = false;
32533274params.runtimePlan = createCodexRuntimePlanFixture();
3254327532553276const run = runCodexAppServerAttempt(params);
3256-await harness.waitForMethod("turn/start");
3277+let completed = false;
3278+try {
3279+await harness.waitForMethod("turn/start", 10_000);
325732803258-const toolResult = (await harness.handleServerRequest({
3259-id: "request-image-generate",
3260-method: "item/tool/call",
3261-params: {
3262-threadId: "thread-1",
3263-turnId: "turn-1",
3264-callId: "call-image-1",
3265-namespace: null,
3266-tool: "image_generate",
3267-arguments: { prompt: "lighthouse" },
3268-},
3269-})) as {
3270-contentItems?: Array<{ text?: string; type?: string }>;
3271-success?: boolean;
3272-};
3281+ const toolResult = (await harness.handleServerRequest({
3282+ id: "request-image-generate",
3283+ method: "item/tool/call",
3284+ params: {
3285+ threadId: "thread-1",
3286+ turnId: "turn-1",
3287+ callId: "call-image-1",
3288+ namespace: null,
3289+ tool: "image_generate",
3290+ arguments: { prompt: "lighthouse" },
3291+ },
3292+ })) as {
3293+ contentItems?: Array<{ text?: string; type?: string }>;
3294+ success?: boolean;
3295+ };
327332963274-expect(toolResult).toEqual({
3275-success: true,
3276-contentItems: [{ type: "inputText", text: "Background task started." }],
3277-});
3278-expect(harness.requests.some((request) => request.method === "turn/interrupt")).toBe(false);
3279-const result = await run;
3297+expect(toolResult).toEqual({
3298+success: true,
3299+contentItems: [{ type: "inputText", text: "Background task started." }],
3300+});
3301+expect(harness.requests.some((request) => request.method === "turn/interrupt")).toBe(false);
3302+const result = await run;
3303+completed = true;
328033043281-expect(result.timedOut).toBe(false);
3282-expect(result.promptError).toBeNull();
3283-expect(result.yieldDetected).toBe(true);
3284-expect(result.messagesSnapshot.map((message) => message.role)).toEqual([
3285-"user",
3286-"assistant",
3287-"toolResult",
3288-]);
3289-expect(
3290-harness.requests.some(
3291-(request) =>
3292-request.method === "turn/interrupt" &&
3293-(request.params as { turnId?: string } | undefined)?.turnId === "turn-1",
3294-),
3295-).toBe(true);
3305+expect(result.timedOut).toBe(false);
3306+expect(result.promptError).toBeNull();
3307+expect(result.yieldDetected).toBe(true);
3308+expect(result.messagesSnapshot.map((message) => message.role)).toEqual([
3309+"user",
3310+"assistant",
3311+"toolResult",
3312+]);
3313+expect(
3314+harness.requests.some(
3315+(request) =>
3316+request.method === "turn/interrupt" &&
3317+(request.params as { turnId?: string } | undefined)?.turnId === "turn-1",
3318+),
3319+).toBe(true);
3320+} finally {
3321+if (!completed) {
3322+harness.close();
3323+abortController.abort(new Error("test cleanup"));
3324+await run.catch(() => {});
3325+}
3326+}
32963327});
3297332832983329it("keeps mixed dynamic tool batches running after one terminal result", async () => {
此內容由慣性聚合(RSS閱讀器)自動聚合整理,僅供閱讀參考。 原文來自 — 版權歸原作者所有。