diff --git a/src/tools/delegate-task/sync-continuation.test.ts b/src/tools/delegate-task/sync-continuation.test.ts index 0ddf5e7ed..7d0015503 100644 --- a/src/tools/delegate-task/sync-continuation.test.ts +++ b/src/tools/delegate-task/sync-continuation.test.ts @@ -244,6 +244,62 @@ describe("executeSyncContinuation - toast cleanup error paths", () => { expect(removeTaskCalls[0]).toBe("resume_sync_ses_test") }) + test("recovers from canonical aborted-operation message", async () => { + const mockClient = { + session: { + messages: async () => ({ + data: [ + { info: { id: "msg_001", role: "user", time: { created: 1000 } } }, + { + info: { id: "msg_002", role: "assistant", time: { created: 2000 }, finish: "end_turn" }, + parts: [{ type: "text", text: "Response" }], + }, + ], + }), + promptAsync: async () => ({}), + status: async () => ({ + data: { ses_test: { type: "idle" } }, + }), + }, + } + + const { executeSyncContinuation } = require("./sync-continuation") + + const deps = { + pollSyncSession: async () => "The operation was aborted.", + fetchSyncResult: async () => ({ ok: true as const, textContent: "Recovered result" }), + } + + const mockCtx = { + sessionID: "parent-session", + callID: "call-123", + metadata: () => {}, + } + + const mockExecutorCtx = { + client: mockClient, + } + + const args = { + task_id: "ses_test_12345678", + prompt: "test prompt", + description: "test task", + category: "test", + load_skills: [], + run_in_background: false, + } + + //#when + const result = await executeSyncContinuation(args, mockCtx, mockExecutorCtx, { + sessionID: "parent-session", + messageID: "parent-message", + }, deps) + + //#then + expect(result).toContain("Task continued and completed in") + expect(result).toContain("Recovered result") + }) + test("returns MessageAbortedError poll error when recovery fetch has no result", async () => { const mockClient = { session: { diff --git a/src/tools/delegate-task/sync-continuation.ts b/src/tools/delegate-task/sync-continuation.ts index 6e600616d..17c85f026 100644 --- a/src/tools/delegate-task/sync-continuation.ts +++ b/src/tools/delegate-task/sync-continuation.ts @@ -43,6 +43,10 @@ function shouldAttemptPollErrorRecovery(pollError: string): boolean { return true } + if (/^the operation was aborted\.?$/iu.test(trimmed)) { + return true + } + return false } diff --git a/src/tools/delegate-task/sync-task.test.ts b/src/tools/delegate-task/sync-task.test.ts index 0143885dd..e1d792cfe 100644 --- a/src/tools/delegate-task/sync-task.test.ts +++ b/src/tools/delegate-task/sync-task.test.ts @@ -224,6 +224,53 @@ describe("executeSyncTask - cleanup on error paths", () => { expect(deleteCalls[0]).toBe("ses_test_12345678") }) + test("recovers from canonical aborted-operation message", async () => { + const mockClient = { + session: { + create: async () => ({ data: { id: "ses_test_12345678" } }), + }, + } + + const { executeSyncTask } = require("./sync-task") + + const deps = { + createSyncSession: async () => ({ ok: true, sessionID: "ses_test_12345678" }), + sendSyncPrompt: async () => null, + pollSyncSession: async () => "The operation was aborted.", + fetchSyncResult: async () => ({ ok: true as const, textContent: "Recovered result" }), + } + + const mockCtx = { + sessionID: "parent-session", + callID: "call-123", + metadata: () => {}, + } + + const mockExecutorCtx = { + client: mockClient, + directory: "/tmp", + onSyncSessionCreated: null, + } + + const args = { + prompt: "test prompt", + description: "test task", + category: "test", + load_skills: [], + run_in_background: false, + command: null, + } + + //#when + const result = await executeSyncTask(args, mockCtx, mockExecutorCtx, { + sessionID: "parent-session", + }, "test-agent", undefined, undefined, undefined, undefined, deps) + + //#then + expect(result).toContain("Task completed in") + expect(result).toContain("Recovered result") + }) + test("does not recover from non-abort poll error containing abort-like words", async () => { const mockClient = { session: { @@ -607,7 +654,7 @@ describe("executeSyncTask - cleanup on error paths", () => { expect(result).toContain("Result from ses_second") expect(deleteCalls).toContain("ses_first") - const finalMetadata = metadataCalls.at(-1) + const finalMetadata = metadataCalls[metadataCalls.length - 1] expect(finalMetadata.metadata.sessionId).toBe("ses_second") expect(finalMetadata.metadata.taskId).toBe("ses_second") expect(finalMetadata.metadata.model).toEqual({ @@ -758,7 +805,7 @@ describe("executeSyncTask - cleanup on error paths", () => { }, "sisyphus-junior", initialModel, undefined, undefined, fallbackChain, deps) expect(result).toBe("Final retry failed") - const finalMetadata = metadataCalls.at(-1) + const finalMetadata = metadataCalls[metadataCalls.length - 1] expect(finalMetadata.metadata.sessionId).toBe("ses_second") expect(finalMetadata.metadata.taskId).toBe("ses_second") expect(finalMetadata.metadata.model).toEqual({ diff --git a/src/tools/delegate-task/sync-task.ts b/src/tools/delegate-task/sync-task.ts index 58b52a07b..556ca8225 100644 --- a/src/tools/delegate-task/sync-task.ts +++ b/src/tools/delegate-task/sync-task.ts @@ -34,6 +34,10 @@ function shouldAttemptPollErrorRecovery(pollError: string): boolean { return true } + if (/^the operation was aborted\.?$/iu.test(trimmed)) { + return true + } + return false }