diff --git a/src/core/prompts/tools/new-task.ts b/src/core/prompts/tools/new-task.ts
index 7301b7b422..d40b6b44b6 100644
--- a/src/core/prompts/tools/new-task.ts
+++ b/src/core/prompts/tools/new-task.ts
@@ -2,22 +2,34 @@ import { ToolArgs } from "./types"
export function getNewTaskDescription(_args: ToolArgs): string {
return `## new_task
-Description: This will let you create a new task instance in the chosen mode using your provided message.
+Description: This will let you create a new task instance in the chosen mode using your provided message and initial todo list.
Parameters:
- mode: (required) The slug of the mode to start the new task in (e.g., "code", "debug", "architect").
- message: (required) The initial user message or instructions for this new task.
+- todos: (required) A markdown checklist string defining the initial todo list for the task.
Usage:
your-mode-slug-here
Your initial instructions here
+
+[ ] First task to complete
+[ ] Second task to complete
+[ ] Third task to complete
+
Example:
code
-Implement a new feature for the application.
+Implement user authentication
+
+[ ] Set up auth middleware
+[ ] Create login endpoint
+[ ] Add session management
+[ ] Write tests
+
`
}
diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts
index edbde32ea7..71b86c22af 100644
--- a/src/core/task/Task.ts
+++ b/src/core/task/Task.ts
@@ -125,6 +125,7 @@ export type TaskOptions = {
rootTask?: Task
parentTask?: Task
taskNumber?: number
+ initialTodos?: TodoItem[]
onCreated?: (cline: Task) => void
}
@@ -270,6 +271,7 @@ export class Task extends EventEmitter {
rootTask,
parentTask,
taskNumber = -1,
+ initialTodos,
onCreated,
}: TaskOptions) {
super()
@@ -324,6 +326,11 @@ export class Task extends EventEmitter {
TelemetryService.instance.captureTaskCreated(this.taskId)
}
+ // Initialize todo list if provided
+ if (initialTodos) {
+ this.todoList = initialTodos
+ }
+
// Only set up diff strategy if diff is enabled
if (this.diffEnabled) {
// Default to old strategy, will be updated if experiment is enabled
diff --git a/src/core/tools/__tests__/newTaskTool.spec.ts b/src/core/tools/__tests__/newTaskTool.spec.ts
index 1dd79d6e98..97f0f37792 100644
--- a/src/core/tools/__tests__/newTaskTool.spec.ts
+++ b/src/core/tools/__tests__/newTaskTool.spec.ts
@@ -14,6 +14,13 @@ vi.mock("../../prompts/responses", () => ({
},
}))
+vi.mock("../updateTodoListTool", () => ({
+ parseMarkdownChecklist: vi.fn((md: string) => [
+ { id: "1", content: "Test todo 1", status: "pending" },
+ { id: "2", content: "Test todo 2", status: "pending" },
+ ]),
+}))
+
// Define a minimal type for the resolved value
type MockClineInstance = { taskId: string }
@@ -72,6 +79,7 @@ describe("newTaskTool", () => {
params: {
mode: "code",
message: "Review this: \\\\@file1.txt and also \\\\\\\\@file2.txt", // Input with \\@ and \\\\@
+ todos: "[ ] Test todo 1\n[ ] Test todo 2",
},
partial: false,
}
@@ -93,6 +101,12 @@ describe("newTaskTool", () => {
"Review this: \\@file1.txt and also \\\\\\@file2.txt", // Unit Test Expectation: \\@ -> \@, \\\\@ -> \\\\@
undefined,
mockCline,
+ {
+ initialTodos: [
+ { id: "1", content: "Test todo 1", status: "pending" },
+ { id: "2", content: "Test todo 2", status: "pending" },
+ ],
+ },
)
// Verify side effects
@@ -109,6 +123,7 @@ describe("newTaskTool", () => {
params: {
mode: "code",
message: "This is already unescaped: \\@file1.txt",
+ todos: "[ ] Test todo",
},
partial: false,
}
@@ -126,6 +141,12 @@ describe("newTaskTool", () => {
"This is already unescaped: \\@file1.txt", // Expected: \@ remains \@
undefined,
mockCline,
+ {
+ initialTodos: [
+ { id: "1", content: "Test todo 1", status: "pending" },
+ { id: "2", content: "Test todo 2", status: "pending" },
+ ],
+ },
)
})
@@ -136,6 +157,7 @@ describe("newTaskTool", () => {
params: {
mode: "code",
message: "A normal mention @file1.txt",
+ todos: "[ ] Test todo",
},
partial: false,
}
@@ -153,6 +175,12 @@ describe("newTaskTool", () => {
"A normal mention @file1.txt", // Expected: @ remains @
undefined,
mockCline,
+ {
+ initialTodos: [
+ { id: "1", content: "Test todo 1", status: "pending" },
+ { id: "2", content: "Test todo 2", status: "pending" },
+ ],
+ },
)
})
@@ -163,6 +191,7 @@ describe("newTaskTool", () => {
params: {
mode: "code",
message: "Mix: @file0.txt, \\@file1.txt, \\\\@file2.txt, \\\\\\\\@file3.txt",
+ todos: "[ ] Test todo",
},
partial: false,
}
@@ -180,8 +209,41 @@ describe("newTaskTool", () => {
"Mix: @file0.txt, \\@file1.txt, \\@file2.txt, \\\\\\@file3.txt", // Unit Test Expectation: @->@, \@->\@, \\@->\@, \\\\@->\\\\@
undefined,
mockCline,
+ {
+ initialTodos: [
+ { id: "1", content: "Test todo 1", status: "pending" },
+ { id: "2", content: "Test todo 2", status: "pending" },
+ ],
+ },
)
})
+ it("should handle missing todos parameter", async () => {
+ const block: ToolUse = {
+ type: "tool_use",
+ name: "new_task",
+ params: {
+ mode: "code",
+ message: "Test message",
+ // todos is missing
+ },
+ partial: false,
+ }
+
+ await newTaskTool(
+ mockCline as any,
+ block,
+ mockAskApproval,
+ mockHandleError,
+ mockPushToolResult,
+ mockRemoveClosingTag,
+ )
+
+ // Should call sayAndCreateMissingParamError for todos
+ expect(mockSayAndCreateMissingParamError).toHaveBeenCalledWith("new_task", "todos")
+ expect(mockCline.consecutiveMistakeCount).toBe(1)
+ expect(mockCline.recordToolError).toHaveBeenCalledWith("new_task")
+ })
+
// Add more tests for error handling (missing params, invalid mode, approval denied) if needed
})
diff --git a/src/core/tools/newTaskTool.ts b/src/core/tools/newTaskTool.ts
index cc56659d02..2540eb3584 100644
--- a/src/core/tools/newTaskTool.ts
+++ b/src/core/tools/newTaskTool.ts
@@ -5,6 +5,7 @@ import { Task } from "../task/Task"
import { defaultModeSlug, getModeBySlug } from "../../shared/modes"
import { formatResponse } from "../prompts/responses"
import { t } from "../../i18n"
+import { parseMarkdownChecklist } from "./updateTodoListTool"
export async function newTaskTool(
cline: Task,
@@ -16,6 +17,7 @@ export async function newTaskTool(
) {
const mode: string | undefined = block.params.mode
const message: string | undefined = block.params.message
+ const todos: string | undefined = block.params.todos
try {
if (block.partial) {
@@ -23,6 +25,7 @@ export async function newTaskTool(
tool: "newTask",
mode: removeClosingTag("mode", mode),
content: removeClosingTag("message", message),
+ todos: removeClosingTag("todos", todos),
})
await cline.ask("tool", partialMessage, block.partial).catch(() => {})
@@ -42,6 +45,13 @@ export async function newTaskTool(
return
}
+ if (!todos) {
+ cline.consecutiveMistakeCount++
+ cline.recordToolError("new_task")
+ pushToolResult(await cline.sayAndCreateMissingParamError("new_task", "todos"))
+ return
+ }
+
cline.consecutiveMistakeCount = 0
// Un-escape one level of backslashes before '@' for hierarchical subtasks
// Un-escape one level: \\@ -> \@ (removes one backslash for hierarchical subtasks)
@@ -55,10 +65,24 @@ export async function newTaskTool(
return
}
+ // Parse the todos markdown
+ let parsedTodos
+ try {
+ parsedTodos = parseMarkdownChecklist(todos)
+ } catch (error) {
+ pushToolResult(
+ formatResponse.toolError(
+ `Invalid todos markdown format: ${error instanceof Error ? error.message : String(error)}`,
+ ),
+ )
+ return
+ }
+
const toolMessage = JSON.stringify({
tool: "newTask",
mode: targetMode.name,
content: message,
+ todos: parsedTodos,
})
const didApprove = await askApproval("tool", toolMessage)
@@ -81,7 +105,9 @@ export async function newTaskTool(
cline.pausedModeSlug = (await provider.getState()).mode ?? defaultModeSlug
// Create new task instance first (this preserves parent's current mode in its history)
- const newCline = await provider.initClineWithTask(unescapedMessage, undefined, cline)
+ const newCline = await provider.initClineWithTask(unescapedMessage, undefined, cline, {
+ initialTodos: parsedTodos,
+ })
if (!newCline) {
pushToolResult(t("tools:newTask.errors.policy_restriction"))
return
diff --git a/src/core/tools/updateTodoListTool.ts b/src/core/tools/updateTodoListTool.ts
index cbb90338d3..de96c3cc76 100644
--- a/src/core/tools/updateTodoListTool.ts
+++ b/src/core/tools/updateTodoListTool.ts
@@ -100,7 +100,7 @@ function normalizeStatus(status: string | undefined): TodoStatus {
return "pending"
}
-function parseMarkdownChecklist(md: string): TodoItem[] {
+export function parseMarkdownChecklist(md: string): TodoItem[] {
if (typeof md !== "string") return []
const lines = md
.split(/\r?\n/)
diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts
index 1218999a9a..a40a159906 100644
--- a/src/core/webview/ClineProvider.ts
+++ b/src/core/webview/ClineProvider.ts
@@ -532,7 +532,12 @@ export class ClineProvider
options: Partial<
Pick<
TaskOptions,
- "enableDiff" | "enableCheckpoints" | "fuzzyMatchThreshold" | "consecutiveMistakeLimit" | "experiments"
+ | "enableDiff"
+ | "enableCheckpoints"
+ | "fuzzyMatchThreshold"
+ | "consecutiveMistakeLimit"
+ | "experiments"
+ | "initialTodos"
>
> = {},
) {
diff --git a/src/shared/tools.ts b/src/shared/tools.ts
index 67972243fe..047c2fe351 100644
--- a/src/shared/tools.ts
+++ b/src/shared/tools.ts
@@ -155,7 +155,7 @@ export interface SwitchModeToolUse extends ToolUse {
export interface NewTaskToolUse extends ToolUse {
name: "new_task"
- params: Partial, "mode" | "message">>
+ params: Partial, "mode" | "message" | "todos">>
}
export interface SearchAndReplaceToolUse extends ToolUse {