mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-05 08:10:14 +00:00
feat: add required todos parameter to new_task tool for hierarchical task planning
- Export parseMarkdownChecklist function from updateTodoListTool.ts - Update NewTaskToolUse interface to include todos parameter - Add initialTodos to TaskOptions and Task constructor - Update ClineProvider.initClineWithTask to pass initialTodos - Implement todos parameter handling in newTaskTool.ts with markdown parsing - Update tool documentation to include todos parameter and examples - Add comprehensive tests for the new functionality Fixes #6329 BREAKING CHANGE: The new_task tool now requires a todos parameter containing a markdown checklist string
This commit is contained in:
parent
00a3738d30
commit
6ff42434d3
7 changed files with 118 additions and 6 deletions
|
|
@ -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:
|
||||
<new_task>
|
||||
<mode>your-mode-slug-here</mode>
|
||||
<message>Your initial instructions here</message>
|
||||
<todos>
|
||||
[ ] First task to complete
|
||||
[ ] Second task to complete
|
||||
[ ] Third task to complete
|
||||
</todos>
|
||||
</new_task>
|
||||
|
||||
Example:
|
||||
<new_task>
|
||||
<mode>code</mode>
|
||||
<message>Implement a new feature for the application.</message>
|
||||
<message>Implement user authentication</message>
|
||||
<todos>
|
||||
[ ] Set up auth middleware
|
||||
[ ] Create login endpoint
|
||||
[ ] Add session management
|
||||
[ ] Write tests
|
||||
</todos>
|
||||
</new_task>
|
||||
`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<ClineEvents> {
|
|||
rootTask,
|
||||
parentTask,
|
||||
taskNumber = -1,
|
||||
initialTodos,
|
||||
onCreated,
|
||||
}: TaskOptions) {
|
||||
super()
|
||||
|
|
@ -324,6 +326,11 @@ export class Task extends EventEmitter<ClineEvents> {
|
|||
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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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/)
|
||||
|
|
|
|||
|
|
@ -532,7 +532,12 @@ export class ClineProvider
|
|||
options: Partial<
|
||||
Pick<
|
||||
TaskOptions,
|
||||
"enableDiff" | "enableCheckpoints" | "fuzzyMatchThreshold" | "consecutiveMistakeLimit" | "experiments"
|
||||
| "enableDiff"
|
||||
| "enableCheckpoints"
|
||||
| "fuzzyMatchThreshold"
|
||||
| "consecutiveMistakeLimit"
|
||||
| "experiments"
|
||||
| "initialTodos"
|
||||
>
|
||||
> = {},
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -155,7 +155,7 @@ export interface SwitchModeToolUse extends ToolUse {
|
|||
|
||||
export interface NewTaskToolUse extends ToolUse {
|
||||
name: "new_task"
|
||||
params: Partial<Pick<Record<ToolParamName, string>, "mode" | "message">>
|
||||
params: Partial<Pick<Record<ToolParamName, string>, "mode" | "message" | "todos">>
|
||||
}
|
||||
|
||||
export interface SearchAndReplaceToolUse extends ToolUse {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue