























@@ -3255,312 +3255,58 @@ describe("runCodexAppServerAttempt", () => {
32553255).toBe(false);
32563256});
325732573258-it("keeps mixed dynamic tool batches running after one terminal result", async () => {
3259-const harness = createStartedThreadHarness();
3260-let markEchoStarted: (() => void) | undefined;
3261-const echoStarted = new Promise<void>((resolve) => {
3262-markEchoStarted = resolve;
3263-});
3264-let releaseEcho: (() => void) | undefined;
3265-const echoBlocked = new Promise<void>((resolve) => {
3266-releaseEcho = resolve;
3267-});
3268-testing.setOpenClawCodingToolsFactoryForTests(() => [
3269-{
3270- ...createRuntimeDynamicTool("image_generate"),
3271-execute: vi.fn(async () => ({
3272-content: [{ type: "text" as const, text: "Background task started." }],
3273-details: { async: true, status: "started", taskId: "task-1" },
3274-terminate: true,
3275-})),
3276-},
3277-{
3278- ...createRuntimeDynamicTool("echo"),
3279-execute: vi.fn(async () => {
3280-markEchoStarted?.();
3281-await echoBlocked;
3282-return {
3283-content: [{ type: "text" as const, text: "echo done" }],
3284-details: {},
3285-};
3286-}),
3287-},
3288-]);
3289-3290-const params = createParams(
3291-path.join(tempDir, "session.jsonl"),
3292-path.join(tempDir, "workspace"),
3293-);
3294-params.disableTools = false;
3295-params.runtimePlan = createCodexRuntimePlanFixture();
3296-3297-const run = runCodexAppServerAttempt(params);
3298-await harness.waitForMethod("turn/start");
3299-3300-const echoRequest = harness.handleServerRequest({
3301-id: "request-echo",
3302-method: "item/tool/call",
3303-params: {
3304-threadId: "thread-1",
3305-turnId: "turn-1",
3306-callId: "call-echo-1",
3307-namespace: null,
3308-tool: "echo",
3309-arguments: {},
3310-},
3311-});
3312-await echoStarted;
3313-3314-const imageResult = await harness.handleServerRequest({
3315-id: "request-image-generate",
3316-method: "item/tool/call",
3317-params: {
3318-threadId: "thread-1",
3319-turnId: "turn-1",
3320-callId: "call-image-1",
3321-namespace: null,
3322-tool: "image_generate",
3323-arguments: { prompt: "lighthouse" },
3324-},
3325-});
3326-expect(imageResult).toEqual({
3327-success: true,
3328-contentItems: [{ type: "inputText", text: "Background task started." }],
3329-});
3330-expect(harness.requests.some((request) => request.method === "turn/interrupt")).toBe(false);
3331-3332-releaseEcho?.();
3333-await expect(echoRequest).resolves.toEqual({
3334-success: true,
3335-contentItems: [{ type: "inputText", text: "echo done" }],
3336-});
3337-expect(harness.requests.some((request) => request.method === "turn/interrupt")).toBe(false);
3338-3339-await harness.notify({
3340-method: "item/completed",
3341-params: {
3342-threadId: "thread-1",
3343-turnId: "turn-1",
3344-completedAtMs: Date.now(),
3345-item: {
3346-type: "dynamicToolCall",
3347-id: "call-echo-1",
3348-namespace: null,
3349-tool: "echo",
3350-arguments: {},
3351-status: "completed",
3352-contentItems: [{ type: "inputText", text: "echo done" }],
3353-success: true,
3354-durationMs: 1,
3355-},
3356-},
3357-});
3358-3359-expect(harness.requests.some((request) => request.method === "turn/interrupt")).toBe(false);
3360-3361-await harness.completeTurn({ threadId: "thread-1", turnId: "turn-1" });
3362-const result = await run;
3363-expect(result.timedOut).toBe(false);
3364-expect(harness.requests.some((request) => request.method === "turn/interrupt")).toBe(false);
3365-});
3366-3367-it("does not terminal-release when a parallel non-terminal dynamic tool finished first", async () => {
3368-const harness = createStartedThreadHarness();
3369-let markImageStarted: (() => void) | undefined;
3370-const imageStarted = new Promise<void>((resolve) => {
3371-markImageStarted = resolve;
3372-});
3373-let releaseImage: (() => void) | undefined;
3374-const imageBlocked = new Promise<void>((resolve) => {
3375-releaseImage = resolve;
3376-});
3377-testing.setOpenClawCodingToolsFactoryForTests(() => [
3378-{
3379- ...createRuntimeDynamicTool("echo"),
3380-execute: vi.fn(async () => ({
3381-content: [{ type: "text" as const, text: "echo done" }],
3382-details: {},
3383-})),
3384-},
3385-{
3386- ...createRuntimeDynamicTool("image_generate"),
3387-execute: vi.fn(async () => {
3388-markImageStarted?.();
3389-await imageBlocked;
3390-return {
3391-content: [{ type: "text" as const, text: "Background task started." }],
3392-details: { async: true, status: "started", taskId: "task-1" },
3393-terminate: true,
3394-};
3395-}),
3396-},
3397-]);
3398-3399-const params = createParams(
3400-path.join(tempDir, "session.jsonl"),
3401-path.join(tempDir, "workspace"),
3402-);
3403-params.disableTools = false;
3404-params.runtimePlan = createCodexRuntimePlanFixture();
3405-3406-const run = runCodexAppServerAttempt(params);
3407-await harness.waitForMethod("turn/start");
3408-3409-const imageRequest = harness.handleServerRequest({
3410-id: "request-image-generate",
3411-method: "item/tool/call",
3412-params: {
3413-threadId: "thread-1",
3414-turnId: "turn-1",
3415-callId: "call-image-1",
3416-namespace: null,
3417-tool: "image_generate",
3418-arguments: { prompt: "lighthouse" },
3419-},
3420-});
3421-await imageStarted;
3422-await expect(
3423-harness.handleServerRequest({
3424-id: "request-echo",
3425-method: "item/tool/call",
3426-params: {
3427-threadId: "thread-1",
3428-turnId: "turn-1",
3429-callId: "call-echo-1",
3430-namespace: null,
3431-tool: "echo",
3432-arguments: {},
3433-},
3258+it("keeps mixed dynamic tool batches running after one terminal result", () => {
3259+expect(
3260+testing.resolveTerminalDynamicToolBatchAction({
3261+activeAppServerTurnRequests: 1,
3262+activeTurnItemIdsCount: 0,
3263+pendingOpenClawDynamicToolCompletionIdsCount: 0,
3264+currentTurnHadNonTerminalDynamicToolResult: false,
3265+hasPendingTerminalDynamicToolRelease: true,
34343266}),
3435-).resolves.toEqual({
3436-success: true,
3437-contentItems: [{ type: "inputText", text: "echo done" }],
3438-});
3439-await harness.notify({
3440-method: "item/completed",
3441-params: {
3442-threadId: "thread-1",
3443-turnId: "turn-1",
3444-completedAtMs: Date.now(),
3445-item: {
3446-type: "dynamicToolCall",
3447-id: "call-echo-1",
3448-namespace: null,
3449-tool: "echo",
3450-arguments: {},
3451-status: "completed",
3452-contentItems: [{ type: "inputText", text: "echo done" }],
3453-success: true,
3454-durationMs: 1,
3455-},
3456-},
3457-});
3458-releaseImage?.();
3459-await expect(imageRequest).resolves.toEqual({
3460-success: true,
3461-contentItems: [{ type: "inputText", text: "Background task started." }],
3462-});
3463-expect(harness.requests.some((request) => request.method === "turn/interrupt")).toBe(false);
3464-3465-await harness.completeTurn({ threadId: "thread-1", turnId: "turn-1" });
3466-const result = await run;
3467-expect(result.timedOut).toBe(false);
3468-expect(harness.requests.some((request) => request.method === "turn/interrupt")).toBe(false);
3267+).toBe("wait");
3268+expect(
3269+testing.resolveTerminalDynamicToolBatchAction({
3270+activeAppServerTurnRequests: 0,
3271+activeTurnItemIdsCount: 0,
3272+pendingOpenClawDynamicToolCompletionIdsCount: 1,
3273+currentTurnHadNonTerminalDynamicToolResult: false,
3274+hasPendingTerminalDynamicToolRelease: true,
3275+}),
3276+).toBe("wait");
3277+expect(
3278+testing.resolveTerminalDynamicToolBatchAction({
3279+activeAppServerTurnRequests: 0,
3280+activeTurnItemIdsCount: 1,
3281+pendingOpenClawDynamicToolCompletionIdsCount: 0,
3282+currentTurnHadNonTerminalDynamicToolResult: false,
3283+hasPendingTerminalDynamicToolRelease: true,
3284+}),
3285+).toBe("wait");
34693286});
347032873471-it("terminal-releases after a prior non-terminal dynamic tool batch is closed", async () => {
3472-const harness = createStartedThreadHarness();
3473-testing.setOpenClawCodingToolsFactoryForTests(() => [
3474-{
3475- ...createRuntimeDynamicTool("echo"),
3476-execute: vi.fn(async () => ({
3477-content: [{ type: "text" as const, text: "echo done" }],
3478-details: {},
3479-})),
3480-},
3481-{
3482- ...createRuntimeDynamicTool("image_generate"),
3483-execute: vi.fn(async () => ({
3484-content: [{ type: "text" as const, text: "Background task started." }],
3485-details: { async: true, status: "started", taskId: "task-1" },
3486-terminate: true,
3487-})),
3488-},
3489-]);
3490-3491-const params = createParams(
3492-path.join(tempDir, "session.jsonl"),
3493-path.join(tempDir, "workspace"),
3494-);
3495-params.disableTools = false;
3496-params.runtimePlan = createCodexRuntimePlanFixture();
3497-3498-const run = runCodexAppServerAttempt(params);
3499-await harness.waitForMethod("turn/start");
3500-3501-await expect(
3502-harness.handleServerRequest({
3503-id: "request-echo",
3504-method: "item/tool/call",
3505-params: {
3506-threadId: "thread-1",
3507-turnId: "turn-1",
3508-callId: "call-echo-1",
3509-namespace: null,
3510-tool: "echo",
3511-arguments: {},
3512-},
3513-}),
3514-).resolves.toEqual({
3515-success: true,
3516-contentItems: [{ type: "inputText", text: "echo done" }],
3517-});
3518-await harness.notify({
3519-method: "item/completed",
3520-params: {
3521-threadId: "thread-1",
3522-turnId: "turn-1",
3523-completedAtMs: Date.now(),
3524-item: {
3525-type: "dynamicToolCall",
3526-id: "call-echo-1",
3527-namespace: null,
3528-tool: "echo",
3529-arguments: {},
3530-status: "completed",
3531-contentItems: [{ type: "inputText", text: "echo done" }],
3532-success: true,
3533-durationMs: 1,
3534-},
3535-},
3536-});
3537-await expect(
3538-harness.handleServerRequest({
3539-id: "request-image-generate",
3540-method: "item/tool/call",
3541-params: {
3542-threadId: "thread-1",
3543-turnId: "turn-1",
3544-callId: "call-image-1",
3545-namespace: null,
3546-tool: "image_generate",
3547-arguments: { prompt: "lighthouse" },
3548-},
3288+it("does not terminal-release when a parallel non-terminal dynamic tool finished first", () => {
3289+expect(
3290+testing.resolveTerminalDynamicToolBatchAction({
3291+activeAppServerTurnRequests: 0,
3292+activeTurnItemIdsCount: 0,
3293+pendingOpenClawDynamicToolCompletionIdsCount: 0,
3294+currentTurnHadNonTerminalDynamicToolResult: true,
3295+hasPendingTerminalDynamicToolRelease: true,
35493296}),
3550-).resolves.toEqual({
3551-success: true,
3552-contentItems: [{ type: "inputText", text: "Background task started." }],
3553-});
3297+).toBe("clear-nonterminal-batch");
3298+});
355432993555-const result = await run;
3556-expect(result.timedOut).toBe(false);
3300+it("terminal-releases after a prior non-terminal dynamic tool batch is closed", () => {
35573301expect(
3558-harness.requests.some(
3559-(request) =>
3560-request.method === "turn/interrupt" &&
3561-(request.params as { turnId?: string } | undefined)?.turnId === "turn-1",
3562-),
3563-).toBe(true);
3302+testing.resolveTerminalDynamicToolBatchAction({
3303+activeAppServerTurnRequests: 0,
3304+activeTurnItemIdsCount: 0,
3305+pendingOpenClawDynamicToolCompletionIdsCount: 0,
3306+currentTurnHadNonTerminalDynamicToolResult: false,
3307+hasPendingTerminalDynamicToolRelease: true,
3308+}),
3309+).toBe("release-pending-terminal");
35643310});
3565331135663312it("waits for active native items before terminal dynamic tool release", async () => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。