From e11c217d1569c61bd70e1f7b762b791cafd8b48e Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Tue, 24 Feb 2026 03:52:20 +0900 Subject: [PATCH] fix(tools/background-task): respect block=true even when full_session=true MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move blocking/polling logic before full_session branch so that block=true waits for task completion regardless of output format. 🤖 Generated with assistance of oh-my-opencode --- .../create-background-output.ts | 70 +++++++++---------- src/tools/background-task/tools.test.ts | 42 +++++++++++ 2 files changed, 76 insertions(+), 36 deletions(-) diff --git a/src/tools/background-task/create-background-output.ts b/src/tools/background-task/create-background-output.ts index 6bb4e8093..78593a884 100644 --- a/src/tools/background-task/create-background-output.ts +++ b/src/tools/background-task/create-background-output.ts @@ -77,13 +77,40 @@ export function createBackgroundOutput(manager: BackgroundOutputManager, client: storeToolMetadata(ctx.sessionID, callID, meta) } - const isActive = task.status === "pending" || task.status === "running" + const shouldBlock = args.block === true + const timeoutMs = Math.min(args.timeout ?? 60000, 600000) const fullSession = args.full_session ?? true + + let resolvedTask = task + + if (shouldBlock && (task.status === "pending" || task.status === "running")) { + const startTime = Date.now() + while (Date.now() - startTime < timeoutMs) { + await delay(1000) + + const currentTask = manager.getTask(args.task_id) + if (!currentTask) { + return `Task was deleted: ${args.task_id}` + } + + if (currentTask.status !== "pending" && currentTask.status !== "running") { + resolvedTask = currentTask + break + } + } + + const finalCheck = manager.getTask(args.task_id) + if (finalCheck) { + resolvedTask = finalCheck + } + } + + const isActive = resolvedTask.status === "pending" || resolvedTask.status === "running" const includeThinking = isActive || (args.include_thinking ?? false) const includeToolResults = isActive || (args.include_tool_results ?? false) if (fullSession) { - return await formatFullSession(task, client, { + return await formatFullSession(resolvedTask, client, { includeThinking, messageLimit: args.message_limit, sinceMessageId: args.since_message_id, @@ -92,44 +119,15 @@ export function createBackgroundOutput(manager: BackgroundOutputManager, client: }) } - const shouldBlock = args.block === true - const timeoutMs = Math.min(args.timeout ?? 60000, 600000) - - if (task.status === "completed") { - return await formatTaskResult(task, client) + if (resolvedTask.status === "completed") { + return await formatTaskResult(resolvedTask, client) } - if (task.status === "error" || task.status === "cancelled" || task.status === "interrupt") { - return formatTaskStatus(task) + if (resolvedTask.status === "error" || resolvedTask.status === "cancelled" || resolvedTask.status === "interrupt") { + return formatTaskStatus(resolvedTask) } - if (!shouldBlock) { - return formatTaskStatus(task) - } - - const startTime = Date.now() - while (Date.now() - startTime < timeoutMs) { - await delay(1000) - - const currentTask = manager.getTask(args.task_id) - if (!currentTask) { - return `Task was deleted: ${args.task_id}` - } - - if (currentTask.status === "completed") { - return await formatTaskResult(currentTask, client) - } - - if (currentTask.status === "error" || currentTask.status === "cancelled" || currentTask.status === "interrupt") { - return formatTaskStatus(currentTask) - } - } - - const finalTask = manager.getTask(args.task_id) - if (!finalTask) { - return `Task was deleted: ${args.task_id}` - } - return `Timeout exceeded (${timeoutMs}ms). Task still ${finalTask.status}.\n\n${formatTaskStatus(finalTask)}` + return formatTaskStatus(resolvedTask) } catch (error) { return `Error getting output: ${error instanceof Error ? error.message : String(error)}` } diff --git a/src/tools/background-task/tools.test.ts b/src/tools/background-task/tools.test.ts index c28cf5a6f..7ea7d0748 100644 --- a/src/tools/background-task/tools.test.ts +++ b/src/tools/background-task/tools.test.ts @@ -339,6 +339,48 @@ describe("background_output full_session", () => { }) }) + +describe("background_output blocking", () => { + test("block=true waits for task completion even with default full_session=true", async () => { + // #given a task that transitions running → completed after 2 polls + let pollCount = 0 + const task = createTask({ status: "running" }) + const manager: BackgroundOutputManager = { + getTask: (id: string) => { + if (id !== task.id) return undefined + pollCount++ + if (pollCount >= 3) { + task.status = "completed" + } + return task + }, + } + const client = createMockClient({ + "ses-1": [ + { + id: "m1", + info: { role: "assistant", time: "2026-01-01T00:00:00Z" }, + parts: [{ type: "text", text: "completed result" }], + }, + ], + }) + const tool = createBackgroundOutput(manager, client) + + // #when block=true, full_session not specified (defaults to true) + const output = await tool.execute({ + task_id: "task-1", + block: true, + timeout: 10000, + }, mockContext) + + // #then should have waited and returned full session output + expect(task.status).toBe("completed") + expect(pollCount).toBeGreaterThanOrEqual(3) + expect(output).toContain("# Full Session Output") + expect(output).toContain("completed result") + }) +}) + describe("background_cancel", () => { test("cancels a running task via manager", async () => { // #given