diff --git a/packages/types/src/history.ts b/packages/types/src/history.ts index 302fbe3454..2b0a681f48 100644 --- a/packages/types/src/history.ts +++ b/packages/types/src/history.ts @@ -1,5 +1,27 @@ import { z } from "zod" +/** + * SubtaskQueueItem — a single queued subtask definition for sequential fan-out. + * Used by the orchestrator to define a pipeline of subtasks that execute one after another. + */ +export const subtaskQueueItemSchema = z.object({ + mode: z.string(), + message: z.string(), +}) + +export type SubtaskQueueItem = z.infer + +/** + * SubtaskResult — the result of a completed subtask in a queue. + */ +export const subtaskResultSchema = z.object({ + taskId: z.string(), + mode: z.string(), + summary: z.string(), +}) + +export type SubtaskResult = z.infer + /** * HistoryItem */ @@ -26,6 +48,10 @@ export const historyItemSchema = z.object({ awaitingChildId: z.string().optional(), // Child currently awaited (set when delegated) completedByChildId: z.string().optional(), // Child that completed and resumed this parent completionResultSummary: z.string().optional(), // Summary from completed child + // Sequential fan-out queue (Phase 2) + subtaskQueue: z.array(subtaskQueueItemSchema).optional(), // Remaining subtasks to execute + subtaskQueueIndex: z.number().optional(), // Current position in the original queue (0-based) + subtaskResults: z.array(subtaskResultSchema).optional(), // Results from completed queue subtasks }) export type HistoryItem = z.infer diff --git a/src/__tests__/sequential-fan-out.spec.ts b/src/__tests__/sequential-fan-out.spec.ts new file mode 100644 index 0000000000..44d7a4d41e --- /dev/null +++ b/src/__tests__/sequential-fan-out.spec.ts @@ -0,0 +1,242 @@ +/** + * Tests for Phase 2: Sequential fan-out / fan-in. + * + * Tests the subtask queue mechanism where an orchestrator can define + * multiple subtasks that execute one after another with automatic transitions. + */ + +import { describe, it, expect, vi } from "vitest" +import { RooCodeEventName } from "@roo-code/types" +import type { HistoryItem, SubtaskQueueItem } from "@roo-code/types" + +import { ClineProvider } from "../core/webview/ClineProvider" + +describe("Sequential fan-out queue types", () => { + it("SubtaskQueueItem has required mode and message fields", () => { + const item: SubtaskQueueItem = { mode: "code", message: "Implement feature X" } + expect(item.mode).toBe("code") + expect(item.message).toBe("Implement feature X") + }) + + it("HistoryItem can include subtask queue fields", () => { + const historyItem: Partial = { + id: "test-1", + subtaskQueue: [ + { mode: "code", message: "Step 1" }, + { mode: "debug", message: "Step 2" }, + ], + subtaskQueueIndex: 0, + subtaskResults: [{ taskId: "child-1", mode: "code", summary: "Done" }], + } + expect(historyItem.subtaskQueue).toHaveLength(2) + expect(historyItem.subtaskQueueIndex).toBe(0) + expect(historyItem.subtaskResults).toHaveLength(1) + }) + + it("HistoryItem subtask queue fields are optional", () => { + const historyItem: Partial = { + id: "test-2", + status: "active", + } + expect(historyItem.subtaskQueue).toBeUndefined() + expect(historyItem.subtaskQueueIndex).toBeUndefined() + expect(historyItem.subtaskResults).toBeUndefined() + }) +}) + +describe("advanceSubtaskQueue", () => { + const makeHistoryItem = (overrides: Partial = {}): HistoryItem => ({ + id: "parent-1", + number: 1, + ts: Date.now(), + task: "Test task", + tokensIn: 0, + tokensOut: 0, + totalCost: 0, + status: "delegated", + ...overrides, + }) + + it("returns handled=true when there are more subtasks in the queue", async () => { + const emitSpy = vi.fn() + const mockChild = { taskId: "child-2", start: vi.fn() } + const provider = { + getCurrentTask: vi.fn().mockReturnValue({ taskId: "child-1" }), + removeClineFromStack: vi.fn().mockResolvedValue(undefined), + getTaskWithId: vi.fn().mockResolvedValue({ + historyItem: makeHistoryItem({ id: "child-1", status: "active" }), + }), + updateTaskHistory: vi.fn().mockResolvedValue(undefined), + handleModeSwitch: vi.fn().mockResolvedValue(undefined), + createTask: vi.fn().mockResolvedValue(mockChild), + emit: emitSpy, + log: vi.fn(), + } + + const subtaskQueue: SubtaskQueueItem[] = [ + { mode: "code", message: "Step 1" }, + { mode: "debug", message: "Step 2" }, + ] + + const historyItem = makeHistoryItem({ + subtaskQueue, + subtaskQueueIndex: 0, + subtaskResults: [], + childIds: ["child-1"], + }) + + const result = await (ClineProvider.prototype as any).advanceSubtaskQueue.call(provider, { + parentTaskId: "parent-1", + childTaskId: "child-1", + completionResultSummary: "Step 1 done", + historyItem, + }) + + expect(result.handled).toBe(true) + + // Should have closed the current child + expect(provider.removeClineFromStack).toHaveBeenCalled() + + // Should have marked child as completed + expect(provider.updateTaskHistory).toHaveBeenCalledWith( + expect.objectContaining({ id: "child-1", status: "completed" }), + ) + + // Should have switched mode to next subtask's mode + expect(provider.handleModeSwitch).toHaveBeenCalledWith("debug") + + // Should have created the next child with the queued message + expect(provider.createTask).toHaveBeenCalledWith("Step 2", undefined, undefined, { + initialTodos: [], + initialStatus: "active", + startTask: false, + }) + + // Should have started the next child + expect(mockChild.start).toHaveBeenCalled() + + // Should have updated parent with advanced queue index + expect(provider.updateTaskHistory).toHaveBeenCalledWith( + expect.objectContaining({ + id: "parent-1", + subtaskQueueIndex: 1, + subtaskResults: [{ taskId: "child-1", mode: "unknown", summary: "Step 1 done" }], + awaitingChildId: "child-2", + delegatedToId: "child-2", + }), + ) + + // Should have emitted delegation events + expect(emitSpy).toHaveBeenCalledWith( + RooCodeEventName.TaskDelegationCompleted, + "parent-1", + "child-1", + "Step 1 done", + ) + expect(emitSpy).toHaveBeenCalledWith(RooCodeEventName.TaskDelegated, "parent-1", "child-2") + }) + + it("returns handled=false with aggregated summary when queue is exhausted", async () => { + const provider = { + getCurrentTask: vi.fn().mockReturnValue({ taskId: "child-2" }), + removeClineFromStack: vi.fn().mockResolvedValue(undefined), + getTaskWithId: vi.fn().mockResolvedValue({ + historyItem: makeHistoryItem({ id: "child-2", status: "active" }), + }), + updateTaskHistory: vi.fn().mockResolvedValue(undefined), + handleModeSwitch: vi.fn(), + createTask: vi.fn(), + emit: vi.fn(), + log: vi.fn(), + formatAggregatedQueueResults: (ClineProvider.prototype as any).formatAggregatedQueueResults, + } + + const subtaskQueue: SubtaskQueueItem[] = [{ mode: "code", message: "Step 1" }] + + const historyItem = makeHistoryItem({ + subtaskQueue, + subtaskQueueIndex: 0, + subtaskResults: [{ taskId: "child-1", mode: "code", summary: "Step 1 done" }], + childIds: ["child-1", "child-2"], + }) + + const result = await (ClineProvider.prototype as any).advanceSubtaskQueue.call(provider, { + parentTaskId: "parent-1", + childTaskId: "child-2", + completionResultSummary: "Step 2 done", + historyItem, + }) + + expect(result.handled).toBe(false) + expect(result.aggregatedSummary).toContain("Sequential Fan-Out Complete") + expect(result.aggregatedSummary).toContain("Step 1 done") + expect(result.aggregatedSummary).toContain("Step 2 done") + + // Should NOT have created a new child + expect(provider.createTask).not.toHaveBeenCalled() + + // Should have cleared queue from parent metadata + expect(provider.updateTaskHistory).toHaveBeenCalledWith( + expect.objectContaining({ + subtaskQueue: undefined, + subtaskQueueIndex: undefined, + }), + ) + }) + + it("returns handled=false immediately when queue is empty", async () => { + const provider = { + getCurrentTask: vi.fn(), + removeClineFromStack: vi.fn(), + getTaskWithId: vi.fn(), + updateTaskHistory: vi.fn(), + emit: vi.fn(), + log: vi.fn(), + formatAggregatedQueueResults: (ClineProvider.prototype as any).formatAggregatedQueueResults, + } + + const historyItem = makeHistoryItem({ + subtaskQueue: [], + subtaskQueueIndex: 0, + }) + + const result = await (ClineProvider.prototype as any).advanceSubtaskQueue.call(provider, { + parentTaskId: "parent-1", + childTaskId: "child-1", + completionResultSummary: "Done", + historyItem, + }) + + expect(result.handled).toBe(false) + expect(result.aggregatedSummary).toBe("Done") + }) +}) + +describe("formatAggregatedQueueResults", () => { + it("formats multiple results into a structured summary", () => { + const results = [ + { taskId: "child-1", mode: "code", summary: "Implemented feature X" }, + { taskId: "child-2", mode: "debug", summary: "Fixed bugs in feature X" }, + ] + + const formatted = (ClineProvider.prototype as any).formatAggregatedQueueResults(results, "Final result") + + expect(formatted).toContain("Sequential Fan-Out Complete (2 subtasks)") + expect(formatted).toContain("Subtask 1 (code)") + expect(formatted).toContain("Implemented feature X") + expect(formatted).toContain("Subtask 2 (debug)") + expect(formatted).toContain("Fixed bugs in feature X") + }) + + it("returns last summary when results array is empty", () => { + const formatted = (ClineProvider.prototype as any).formatAggregatedQueueResults([], "Just a summary") + expect(formatted).toBe("Just a summary") + }) + + it("handles single result", () => { + const results = [{ taskId: "child-1", mode: "code", summary: "Done" }] + const formatted = (ClineProvider.prototype as any).formatAggregatedQueueResults(results, "Done") + expect(formatted).toContain("Sequential Fan-Out Complete (1 subtask)") + expect(formatted).toContain("Subtask 1 (code)") + }) +}) diff --git a/src/core/assistant-message/NativeToolCallParser.ts b/src/core/assistant-message/NativeToolCallParser.ts index c391f9852c..30bf119e4f 100644 --- a/src/core/assistant-message/NativeToolCallParser.ts +++ b/src/core/assistant-message/NativeToolCallParser.ts @@ -633,6 +633,7 @@ export class NativeToolCallParser { mode: partialArgs.mode, message: partialArgs.message, todos: partialArgs.todos, + task_queue: partialArgs.task_queue, } } break @@ -982,6 +983,7 @@ export class NativeToolCallParser { mode: args.mode, message: args.message, todos: args.todos, + task_queue: args.task_queue, } as NativeArgsFor } break diff --git a/src/core/prompts/tools/native-tools/new_task.ts b/src/core/prompts/tools/native-tools/new_task.ts index f8e29e549d..051351a9a8 100644 --- a/src/core/prompts/tools/native-tools/new_task.ts +++ b/src/core/prompts/tools/native-tools/new_task.ts @@ -2,7 +2,9 @@ import type OpenAI from "openai" const NEW_TASK_DESCRIPTION = `Create a new task instance in the chosen mode using your provided message and initial todo list (if required). -CRITICAL: This tool MUST be called alone. Do NOT call this tool alongside other tools in the same message turn. If you need to gather information before delegating, use other tools in a separate turn first, then call new_task by itself in the next turn.` +CRITICAL: This tool MUST be called alone. Do NOT call this tool alongside other tools in the same message turn. If you need to gather information before delegating, use other tools in a separate turn first, then call new_task by itself in the next turn. + +SEQUENTIAL FAN-OUT: You can optionally provide a task_queue parameter to define additional subtasks that will execute automatically in sequence after the first subtask completes. Each queued subtask runs one after another without returning to the parent in between, saving time and API calls. Use this when you have planned multiple independent subtasks upfront. The first subtask is defined by the mode and message parameters; subsequent subtasks are defined in the task_queue array.` const MODE_PARAMETER_DESCRIPTION = `Slug of the mode to begin the new task in (e.g., code, debug, architect)` @@ -10,6 +12,8 @@ const MESSAGE_PARAMETER_DESCRIPTION = `Initial user instructions or context for const TODOS_PARAMETER_DESCRIPTION = `Optional initial todo list written as a markdown checklist; required when the workspace mandates todos` +const TASK_QUEUE_PARAMETER_DESCRIPTION = `Optional JSON array of additional subtasks to execute sequentially after the first subtask completes. Each element is an object with "mode" (string) and "message" (string). Example: [{"mode":"code","message":"Implement feature X"},{"mode":"debug","message":"Test feature X"}]. When provided, the system automatically transitions between subtasks without returning to the parent, collecting all results. The parent receives aggregated results when the entire queue completes.` + export default { type: "function", function: { @@ -31,6 +35,10 @@ export default { type: ["string", "null"], description: TODOS_PARAMETER_DESCRIPTION, }, + task_queue: { + type: ["string", "null"], + description: TASK_QUEUE_PARAMETER_DESCRIPTION, + }, }, required: ["mode", "message", "todos"], additionalProperties: false, diff --git a/src/core/tools/NewTaskTool.ts b/src/core/tools/NewTaskTool.ts index f36d8e1e37..636343a489 100644 --- a/src/core/tools/NewTaskTool.ts +++ b/src/core/tools/NewTaskTool.ts @@ -1,6 +1,7 @@ import * as vscode from "vscode" import { TodoItem } from "@roo-code/types" +import type { SubtaskQueueItem } from "@roo-code/types" import { Task } from "../task/Task" import { getModeBySlug } from "../../shared/modes" @@ -15,13 +16,14 @@ interface NewTaskParams { mode: string message: string todos?: string + task_queue?: string } export class NewTaskTool extends BaseTool<"new_task"> { readonly name = "new_task" as const async execute(params: NewTaskParams, task: Task, callbacks: ToolCallbacks): Promise { - const { mode, message, todos } = params + const { mode, message, todos, task_queue } = params const { askApproval, handleError, pushToolResult } = callbacks try { @@ -96,11 +98,47 @@ export class NewTaskTool extends BaseTool<"new_task"> { return } + // Parse task_queue if provided (sequential fan-out) + let queueItems: SubtaskQueueItem[] = [] + if (task_queue) { + try { + const parsed = JSON.parse(task_queue) + if (Array.isArray(parsed)) { + for (const item of parsed) { + if (typeof item.mode === "string" && typeof item.message === "string") { + // Validate each queued mode exists + const queuedMode = getModeBySlug(item.mode, state?.customModes) + if (!queuedMode) { + pushToolResult( + formatResponse.toolError( + `Invalid mode in task_queue: "${item.mode}". All queued subtasks must use valid modes.`, + ), + ) + return + } + queueItems.push({ mode: item.mode, message: item.message }) + } + } + } + } catch { + task.consecutiveMistakeCount++ + task.recordToolError("new_task") + task.didToolFailInCurrentTurn = true + pushToolResult( + formatResponse.toolError( + "Invalid task_queue format: must be a JSON array of objects with 'mode' and 'message' properties.", + ), + ) + return + } + } + const toolMessage = JSON.stringify({ tool: "newTask", mode: targetMode.name, content: message, todos: todoItems, + taskQueue: queueItems.length > 0 ? queueItems : undefined, }) const didApprove = await askApproval("tool", toolMessage) @@ -115,10 +153,15 @@ export class NewTaskTool extends BaseTool<"new_task"> { message: unescapedMessage, initialTodos: todoItems, mode, + subtaskQueue: queueItems.length > 0 ? queueItems : undefined, }) // Reflect delegation in tool result (no pause/unpause, no wait) - pushToolResult(`Delegated to child task ${child.taskId}`) + const queueMsg = + queueItems.length > 0 + ? ` (${queueItems.length} additional subtask${queueItems.length > 1 ? "s" : ""} queued)` + : "" + pushToolResult(`Delegated to child task ${child.taskId}${queueMsg}`) return } catch (error) { await handleError("creating new task", error) @@ -130,12 +173,14 @@ export class NewTaskTool extends BaseTool<"new_task"> { const mode: string | undefined = block.params.mode const message: string | undefined = block.params.message const todos: string | undefined = block.params.todos + const taskQueue: string | undefined = block.params.task_queue const partialMessage = JSON.stringify({ tool: "newTask", mode: mode ?? "", content: message ?? "", todos: todos, + taskQueue: taskQueue, }) await task.ask("tool", partialMessage, block.partial).catch(() => {}) diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index c1b086a6a1..67e6df1b61 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -85,7 +85,7 @@ import { CustomModesManager } from "../config/CustomModesManager" import { Task } from "../task/Task" import { webviewMessageHandler } from "./webviewMessageHandler" -import type { ClineMessage, TodoItem } from "@roo-code/types" +import type { ClineMessage, TodoItem, SubtaskQueueItem } from "@roo-code/types" import { readApiMessages, saveApiMessages, saveTaskMessages, TaskHistoryStore } from "../task-persistence" import { readTaskMessages } from "../task-persistence/taskMessages" import { getNonce } from "./getNonce" @@ -3017,8 +3017,9 @@ export class ClineProvider message: string initialTodos: TodoItem[] mode: string + subtaskQueue?: SubtaskQueueItem[] }): Promise { - const { parentTaskId, message, initialTodos, mode } = params + const { parentTaskId, message, initialTodos, mode, subtaskQueue } = params // Metadata-driven delegation is always enabled @@ -3121,6 +3122,9 @@ export class ClineProvider delegatedToId: child.taskId, awaitingChildId: child.taskId, childIds, + ...(subtaskQueue && subtaskQueue.length > 0 + ? { subtaskQueue, subtaskQueueIndex: 0, subtaskResults: [] } + : {}), } await this.updateTaskHistory(updatedHistory) } catch (err) { @@ -3152,12 +3156,28 @@ export class ClineProvider childTaskId: string completionResultSummary: string }): Promise { - const { parentTaskId, childTaskId, completionResultSummary } = params + const { parentTaskId, childTaskId } = params + let effectiveSummary = params.completionResultSummary const globalStoragePath = this.contextProxy.globalStorageUri.fsPath // 1) Load parent from history and current persisted messages const { historyItem } = await this.getTaskWithId(parentTaskId) + // PHASE 2: Sequential fan-out — check if parent has queued subtasks + if (historyItem.subtaskQueue && historyItem.subtaskQueue.length > 0) { + const queueAdvanceResult = await this.advanceSubtaskQueue({ + parentTaskId, + childTaskId, + completionResultSummary: effectiveSummary, + historyItem, + }) + if (queueAdvanceResult.handled) { + return // Queue advanced to next subtask; do NOT reopen parent + } + // Queue exhausted — use aggregated summary and continue with normal reopen + effectiveSummary = queueAdvanceResult.aggregatedSummary + } + let parentClineMessages: ClineMessage[] = [] try { parentClineMessages = await readTaskMessages({ @@ -3203,7 +3223,7 @@ export class ClineProvider const subtaskUiMessage: ClineMessage = { type: "say", say: "subtask_result", - text: completionResultSummary, + text: effectiveSummary, ts, } parentClineMessages.push(subtaskUiMessage) @@ -3316,7 +3336,7 @@ export class ClineProvider ...historyItem, status: "active", completedByChildId: childTaskId, - completionResultSummary, + completionResultSummary: effectiveSummary, awaitingChildId: undefined, childIds, } @@ -3324,7 +3344,7 @@ export class ClineProvider // 6) Emit TaskDelegationCompleted (provider-level) try { - this.emit(RooCodeEventName.TaskDelegationCompleted, parentTaskId, childTaskId, completionResultSummary) + this.emit(RooCodeEventName.TaskDelegationCompleted, parentTaskId, childTaskId, effectiveSummary) } catch { // non-fatal } @@ -3358,6 +3378,149 @@ export class ClineProvider } } + /** + * Advance the sequential fan-out subtask queue. + * Called when a child completes and the parent has a subtaskQueue. + * + * Returns { handled: true } if the next subtask was started (caller should return). + * Returns { handled: false, aggregatedSummary } if queue is exhausted (caller should continue with normal reopen). + */ + private async advanceSubtaskQueue(params: { + parentTaskId: string + childTaskId: string + completionResultSummary: string + historyItem: HistoryItem + }): Promise<{ handled: true } | { handled: false; aggregatedSummary: string }> { + const { parentTaskId, childTaskId, completionResultSummary, historyItem } = params + const { subtaskQueue, subtaskQueueIndex, subtaskResults } = historyItem + if (!subtaskQueue || subtaskQueue.length === 0) { + return { handled: false, aggregatedSummary: completionResultSummary } + } + + const currentIndex = subtaskQueueIndex ?? 0 + const nextIndex = currentIndex + 1 + + // Record this child's result + const completedMode = historyItem.mode ?? "unknown" + const updatedResults = [ + ...(subtaskResults ?? []), + { taskId: childTaskId, mode: completedMode, summary: completionResultSummary }, + ] + + // Close current child if still open + const current = this.getCurrentTask() + if (current?.taskId === childTaskId) { + await this.removeClineFromStack() + } + + // Mark child as completed + try { + const { historyItem: childHistory } = await this.getTaskWithId(childTaskId) + await this.updateTaskHistory({ ...childHistory, status: "completed" }) + } catch (err) { + this.log( + `[advanceSubtaskQueue] Failed to persist child completed status for ${childTaskId}: ${ + (err as Error)?.message ?? String(err) + }`, + ) + } + + // Emit completion event for the finished child + try { + this.emit(RooCodeEventName.TaskDelegationCompleted, parentTaskId, childTaskId, completionResultSummary) + } catch { + // non-fatal + } + + if (nextIndex <= subtaskQueue.length - 1) { + // More subtasks in queue — start the next one + const nextSubtask = subtaskQueue[nextIndex] + this.log( + `[advanceSubtaskQueue] Auto-advancing queue: subtask ${nextIndex + 1}/${subtaskQueue.length} (mode: ${nextSubtask.mode})`, + ) + + // Switch mode + try { + await this.handleModeSwitch(nextSubtask.mode as any) + } catch (e) { + this.log( + `[advanceSubtaskQueue] handleModeSwitch failed for queued mode '${nextSubtask.mode}': ${ + (e as Error)?.message ?? String(e) + }`, + ) + } + + // Create next child + const nextChild = await this.createTask(nextSubtask.message, undefined, undefined, { + initialTodos: [], + initialStatus: "active", + startTask: false, + }) + + // Update parent metadata + const childIds = Array.from(new Set([...(historyItem.childIds ?? []), childTaskId, nextChild.taskId])) + await this.updateTaskHistory({ + ...historyItem, + status: "delegated", + delegatedToId: nextChild.taskId, + awaitingChildId: nextChild.taskId, + childIds, + subtaskQueue, + subtaskQueueIndex: nextIndex, + subtaskResults: updatedResults, + }) + + // Start the child + nextChild.start() + + try { + this.emit(RooCodeEventName.TaskDelegated, parentTaskId, nextChild.taskId) + } catch { + // non-fatal + } + + return { handled: true } + } + + // Queue exhausted — aggregate results and let normal reopen proceed + const aggregatedSummary = this.formatAggregatedQueueResults(updatedResults, completionResultSummary) + + // Clear queue from parent metadata (will be fully updated by caller) + const childIds = Array.from(new Set([...(historyItem.childIds ?? []), childTaskId])) + await this.updateTaskHistory({ + ...historyItem, + subtaskQueue: undefined, + subtaskQueueIndex: undefined, + subtaskResults: updatedResults, + childIds, + }) + + return { handled: false, aggregatedSummary } + } + + /** + * Format aggregated results from all completed subtasks in a queue. + */ + private formatAggregatedQueueResults( + results: Array<{ taskId: string; mode: string; summary: string }>, + lastSummary: string, + ): string { + if (results.length === 0) { + return lastSummary + } + + const lines = [`## Sequential Fan-Out Complete (${results.length} subtask${results.length > 1 ? "s" : ""})`, ""] + + for (let i = 0; i < results.length; i++) { + const r = results[i] + lines.push(`### Subtask ${i + 1} (${r.mode}) — ${r.taskId}`) + lines.push(r.summary) + lines.push("") + } + + return lines.join("\n") + } + /** * Convert a file path to a webview-accessible URI * This method safely converts file paths to URIs that can be loaded in the webview diff --git a/src/shared/tools.ts b/src/shared/tools.ts index 4cac8335ea..b85639d0ae 100644 --- a/src/shared/tools.ts +++ b/src/shared/tools.ts @@ -56,6 +56,7 @@ export const toolParamNames = [ "start_line", "end_line", "todos", + "task_queue", "prompt", "image", // read_file parameters (native protocol) @@ -102,7 +103,7 @@ export type NativeToolArgs = { edit_file: { file_path: string; old_string: string; new_string: string; expected_replacements?: number } apply_patch: { patch: string } list_files: { path: string; recursive?: boolean } - new_task: { mode: string; message: string; todos?: string } + new_task: { mode: string; message: string; todos?: string; task_queue?: string } ask_followup_question: { question: string follow_up: Array<{ text: string; mode?: string }> @@ -240,7 +241,7 @@ export interface SwitchModeToolUse extends ToolUse<"switch_mode"> { export interface NewTaskToolUse extends ToolUse<"new_task"> { name: "new_task" - params: Partial, "mode" | "message" | "todos">> + params: Partial, "mode" | "message" | "todos" | "task_queue">> } export interface RunSlashCommandToolUse extends ToolUse<"run_slash_command"> {