diff --git a/src/tools/background-task/create-background-wait.test.ts b/src/tools/background-task/create-background-wait.test.ts
new file mode 100644
index 000000000..ef7b717e9
--- /dev/null
+++ b/src/tools/background-task/create-background-wait.test.ts
@@ -0,0 +1,305 @@
+///
+
+import { describe, test, expect, beforeEach } from "bun:test"
+import type { BackgroundTask } from "../../features/background-agent"
+import type { BackgroundOutputClient, BackgroundOutputManager } from "./clients"
+import { createBackgroundWait } from "./create-background-wait"
+import { resetMessageCursor } from "../../shared/session-cursor"
+
+function createMockManager(tasks: Record>): BackgroundOutputManager {
+ return {
+ getTask: (id: string) => {
+ const partial = tasks[id]
+ if (!partial) return undefined
+ return {
+ id,
+ parentSessionID: "parent",
+ parentMessageID: "parent-msg",
+ description: partial.description ?? `Task ${id}`,
+ prompt: "test",
+ agent: partial.agent ?? "test-agent",
+ status: partial.status ?? "running",
+ sessionID: partial.sessionID ?? `session-${id}`,
+ ...partial,
+ } as BackgroundTask
+ },
+ }
+}
+
+function createMockClient(responseText = "Test result content"): BackgroundOutputClient {
+ return {
+ session: {
+ messages: async () => [{
+ id: "msg_1",
+ info: { role: "assistant", time: new Date().toISOString() },
+ parts: [{ type: "text", text: responseText }],
+ }],
+ },
+ }
+}
+
+const toolContext = {
+ sessionID: "test-session",
+ messageID: "test-message",
+ agent: "test-agent",
+ abort: new AbortController().signal,
+}
+
+describe("createBackgroundWait", () => {
+ beforeEach(() => {
+ resetMessageCursor()
+ })
+
+ describe("#given empty task_ids", () => {
+ describe("#when execute is called with empty array", () => {
+ test("#then returns JSON with error and empty members", async () => {
+ const manager = createMockManager({})
+ const client = createMockClient()
+ const tool = createBackgroundWait(manager, client)
+
+ const result = await tool.execute({ task_ids: [] }, toolContext)
+
+ const parsed = JSON.parse(result)
+ expect(parsed.error).toBe("task_ids array is required and must not be empty.")
+ expect(parsed.members).toEqual([])
+ expect(parsed.remaining_task_ids).toEqual([])
+ expect(parsed.completed_task).toBeNull()
+ expect(parsed.timeout).toBe(false)
+ expect(parsed.aborted).toBe(false)
+ })
+ })
+ })
+
+ describe("#given a completed council task", () => {
+ describe("#when execute is called with that task_id", () => {
+ test("#then returns JSON with council-specific fields", async () => {
+ const councilResponse = "Council verdict here"
+ const manager = createMockManager({
+ "task-1": {
+ status: "completed",
+ agent: "Council: Opus",
+ description: "Council analysis",
+ startedAt: new Date(Date.now() - 5000),
+ completedAt: new Date(),
+ },
+ })
+ const client = createMockClient(councilResponse)
+ const tool = createBackgroundWait(manager, client)
+
+ const result = await tool.execute({ task_ids: ["task-1"] }, toolContext)
+
+ const parsed = JSON.parse(result)
+ expect(parsed.completed_task).toBeDefined()
+ expect(parsed.completed_task.task_id).toBe("task-1")
+ expect(parsed.completed_task.status).toBe("completed")
+ expect(parsed.completed_task.has_response).toBe(true)
+ expect(parsed.completed_task.response_complete).toBe(true)
+ expect(parsed.completed_task.result).toBe("Council verdict here")
+ expect(parsed.completed_task.duration_s).toBeTypeOf("number")
+ expect(parsed.timeout).toBe(false)
+ expect(parsed.aborted).toBe(false)
+ })
+ })
+ })
+
+ describe("#given a completed non-council task", () => {
+ describe("#when execute is called with that task_id", () => {
+ test("#then returns JSON with result as string and no council fields", async () => {
+ const manager = createMockManager({
+ "task-2": {
+ status: "completed",
+ agent: "explore",
+ description: "Explore codebase",
+ startedAt: new Date(Date.now() - 3000),
+ completedAt: new Date(),
+ },
+ })
+ const client = createMockClient("Exploration complete: found 5 files")
+ const tool = createBackgroundWait(manager, client)
+
+ const result = await tool.execute({ task_ids: ["task-2"] }, toolContext)
+
+ const parsed = JSON.parse(result)
+ expect(parsed.completed_task).toBeDefined()
+ expect(parsed.completed_task.task_id).toBe("task-2")
+ expect(parsed.completed_task.status).toBe("completed")
+ expect(parsed.completed_task.result).toBeTypeOf("string")
+ expect(parsed.completed_task.result).toContain("Exploration complete")
+ expect(parsed.completed_task.has_response).toBeUndefined()
+ expect(parsed.completed_task.response_complete).toBeUndefined()
+ expect(parsed.timeout).toBe(false)
+ })
+ })
+ })
+
+ describe("#given an error task", () => {
+ describe("#when execute is called with that task_id", () => {
+ test("#then returns JSON with error field on completed_task", async () => {
+ const manager = createMockManager({
+ "task-3": {
+ status: "error",
+ agent: "explore",
+ description: "Failed task",
+ error: "Model failed",
+ startedAt: new Date(Date.now() - 2000),
+ },
+ })
+ const client = createMockClient()
+ const tool = createBackgroundWait(manager, client)
+
+ const result = await tool.execute({ task_ids: ["task-3"] }, toolContext)
+
+ const parsed = JSON.parse(result)
+ expect(parsed.completed_task).toBeDefined()
+ expect(parsed.completed_task.task_id).toBe("task-3")
+ expect(parsed.completed_task.status).toBe("error")
+ expect(parsed.completed_task.error).toBe("Model failed")
+ expect(parsed.completed_task.result).toBeUndefined()
+ expect(parsed.timeout).toBe(false)
+ })
+ })
+ })
+
+ describe("#given a task_id not found in manager", () => {
+ describe("#when execute is called with unknown task_id", () => {
+ test("#then returns JSON with not_found member in members array", async () => {
+ const manager = createMockManager({
+ "known-task": { status: "completed", agent: "explore" },
+ })
+ const client = createMockClient()
+ const tool = createBackgroundWait(manager, client)
+
+ const result = await tool.execute({ task_ids: ["known-task", "unknown-task"] }, toolContext)
+
+ const parsed = JSON.parse(result)
+ const unknownMember = parsed.members.find((m: Record) => m.task_id === "unknown-task")
+ expect(unknownMember).toBeDefined()
+ expect(unknownMember.status).toBe("not_found")
+ })
+ })
+ })
+
+ describe("#given a running task with session state and progress", () => {
+ describe("#when that task plus a completed task are waited on", () => {
+ test("#then completed task returns immediately and running task appears in members with session_state", async () => {
+ const now = new Date()
+ const manager = createMockManager({
+ "running-task": {
+ status: "running",
+ agent: "explore",
+ description: "Still running",
+ sessionState: "running",
+ progress: { lastUpdate: now, toolCalls: 5 },
+ },
+ "done-task": {
+ status: "completed",
+ agent: "explore",
+ description: "Already done",
+ startedAt: new Date(Date.now() - 1000),
+ completedAt: now,
+ },
+ })
+ const client = createMockClient("Done result")
+ const tool = createBackgroundWait(manager, client)
+
+ const result = await tool.execute({ task_ids: ["running-task", "done-task"] }, toolContext)
+
+ const parsed = JSON.parse(result)
+ expect(parsed.completed_task.task_id).toBe("done-task")
+
+ const runningMember = parsed.members.find((m: Record) => m.task_id === "running-task")
+ expect(runningMember).toBeDefined()
+ expect(runningMember.session_state).toBe("running")
+ expect(runningMember.last_activity_s).toBeTypeOf("number")
+ expect(runningMember.tool_calls).toBe(5)
+ })
+ })
+ })
+
+ describe("#given multiple tasks with one completed", () => {
+ describe("#when execute is called with all three task_ids", () => {
+ test("#then remaining_task_ids contains the non-terminal tasks", async () => {
+ const manager = createMockManager({
+ "t1": { status: "running", agent: "explore" },
+ "t2": { status: "completed", agent: "explore", startedAt: new Date() },
+ "t3": { status: "running", agent: "oracle" },
+ })
+ const client = createMockClient("Result from t2")
+ const tool = createBackgroundWait(manager, client)
+
+ const result = await tool.execute({ task_ids: ["t1", "t2", "t3"] }, toolContext)
+
+ const parsed = JSON.parse(result)
+ expect(parsed.completed_task.task_id).toBe("t2")
+ expect(parsed.remaining_task_ids).toContain("t1")
+ expect(parsed.remaining_task_ids).toContain("t3")
+ expect(parsed.remaining_task_ids).not.toContain("t2")
+ expect(parsed.remaining_task_ids).toHaveLength(2)
+ expect(parsed.progress.done).toBe(1)
+ expect(parsed.progress.total).toBe(3)
+ expect(parsed.progress.bar).toBe("#--")
+ })
+ })
+ })
+
+ describe("#given all results from every code path", () => {
+ describe("#when JSON.parse is applied to each result", () => {
+ test("#then every result is valid JSON", async () => {
+ const now = new Date()
+ const results: string[] = []
+
+ const emptyTool = createBackgroundWait(createMockManager({}), createMockClient())
+ results.push(await emptyTool.execute({ task_ids: [] }, toolContext))
+
+ const councilTool = createBackgroundWait(
+ createMockManager({
+ "c1": {
+ status: "completed",
+ agent: "Council: Opus",
+ startedAt: now,
+ completedAt: now,
+ },
+ }),
+ createMockClient("verdict"),
+ )
+ results.push(await councilTool.execute({ task_ids: ["c1"] }, toolContext))
+
+ resetMessageCursor()
+ const nonCouncilTool = createBackgroundWait(
+ createMockManager({
+ "nc1": {
+ status: "completed",
+ agent: "explore",
+ startedAt: now,
+ completedAt: now,
+ },
+ }),
+ createMockClient("result text"),
+ )
+ results.push(await nonCouncilTool.execute({ task_ids: ["nc1"] }, toolContext))
+
+ const errorTool = createBackgroundWait(
+ createMockManager({ "e1": { status: "error", error: "boom" } }),
+ createMockClient(),
+ )
+ results.push(await errorTool.execute({ task_ids: ["e1"] }, toolContext))
+
+ const abortController = new AbortController()
+ abortController.abort()
+ const abortedContext = { ...toolContext, abort: abortController.signal }
+ const notFoundTool = createBackgroundWait(createMockManager({}), createMockClient())
+ results.push(await notFoundTool.execute({ task_ids: ["missing"] }, abortedContext))
+
+ const cancelledTool = createBackgroundWait(
+ createMockManager({ "x1": { status: "cancelled", agent: "explore" } }),
+ createMockClient(),
+ )
+ results.push(await cancelledTool.execute({ task_ids: ["x1"] }, toolContext))
+
+ for (const result of results) {
+ expect(() => JSON.parse(result)).not.toThrow()
+ }
+ })
+ })
+ })
+})