This repository was archived by the owner on May 15, 2026. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 3.4k
Expand file tree
/
Copy pathNewTaskTool.ts
More file actions
152 lines (125 loc) · 4.69 KB
/
Copy pathNewTaskTool.ts
File metadata and controls
152 lines (125 loc) · 4.69 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
import * as vscode from "vscode"
import { TodoItem } from "@roo-code/types"
import { Task } from "../task/Task"
import { getModeBySlug } from "../../shared/modes"
import { formatResponse } from "../prompts/responses"
import { t } from "../../i18n"
import { parseMarkdownChecklist } from "./UpdateTodoListTool"
import { Package } from "../../shared/package"
import { BaseTool, ToolCallbacks } from "./BaseTool"
import type { ToolUse } from "../../shared/tools"
interface NewTaskParams {
mode: string
message: string
todos?: string
}
export class NewTaskTool extends BaseTool<"new_task"> {
readonly name = "new_task" as const
async execute(params: NewTaskParams, task: Task, callbacks: ToolCallbacks): Promise<void> {
const { mode, message, todos } = params
const { askApproval, handleError, pushToolResult } = callbacks
try {
// Validate required parameters.
if (!mode) {
task.consecutiveMistakeCount++
task.recordToolError("new_task")
task.didToolFailInCurrentTurn = true
pushToolResult(await task.sayAndCreateMissingParamError("new_task", "mode"))
return
}
if (!message) {
task.consecutiveMistakeCount++
task.recordToolError("new_task")
task.didToolFailInCurrentTurn = true
pushToolResult(await task.sayAndCreateMissingParamError("new_task", "message"))
return
}
// Get the VSCode setting for requiring todos.
const provider = task.providerRef.deref()
if (!provider) {
pushToolResult(formatResponse.toolError("Provider reference lost"))
return
}
const state = await provider.getState()
// Use Package.name (dynamic at build time) as the VSCode configuration namespace.
// Supports multiple extension variants (e.g., stable/nightly) without hardcoded strings.
const requireTodos = vscode.workspace
.getConfiguration(Package.name)
.get<boolean>("newTaskRequireTodos", false)
// Check if todos are required based on VSCode setting.
// Note: `undefined` means not provided, empty string is valid.
if (requireTodos && todos === undefined) {
task.consecutiveMistakeCount++
task.recordToolError("new_task")
task.didToolFailInCurrentTurn = true
pushToolResult(await task.sayAndCreateMissingParamError("new_task", "todos"))
return
}
// Parse todos if provided, otherwise use empty array
let todoItems: TodoItem[] = []
if (todos) {
try {
todoItems = parseMarkdownChecklist(todos)
} catch (error) {
task.consecutiveMistakeCount++
task.recordToolError("new_task")
task.didToolFailInCurrentTurn = true
pushToolResult(formatResponse.toolError("Invalid todos format: must be a markdown checklist"))
return
}
}
task.consecutiveMistakeCount = 0
// Un-escape one level of backslashes before '@' for hierarchical subtasks
// Un-escape one level: \\@ -> \@ (removes one backslash for hierarchical subtasks)
const unescapedMessage = message.replace(/\\\\@/g, "\\@")
// Verify the mode exists
const targetMode = getModeBySlug(mode, state?.customModes)
if (!targetMode) {
pushToolResult(formatResponse.toolError(`Invalid mode: ${mode}`))
return
}
const toolMessage = JSON.stringify({
tool: "newTask",
mode: targetMode.name,
content: message,
todos: todoItems,
})
const didApprove = await askApproval("tool", toolMessage)
if (!didApprove) {
return
}
// IMPORTANT: Push the tool_result BEFORE delegation, because delegateParentAndOpenChild
// disposes the parent task. If we push after, the tool_result is lost and
// flushPendingToolResultsToHistory will generate a placeholder "interrupted" tool_result,
// causing duplicate tool_results when the child completes (EXT-665).
//
// The child taskId isn't known yet, so we use a generic message. The actual completion
// result will be injected by reopenParentFromDelegation when the child completes.
pushToolResult(`Delegating to subtask...`)
// Delegate parent and open child as sole active task
await (provider as any).delegateParentAndOpenChild({
parentTaskId: task.taskId,
message: unescapedMessage,
initialTodos: todoItems,
mode,
})
return
} catch (error) {
await handleError("creating new task", error)
return
}
}
override async handlePartial(task: Task, block: ToolUse<"new_task">): Promise<void> {
const mode: string | undefined = block.params.mode
const message: string | undefined = block.params.message
const todos: string | undefined = block.params.todos
const partialMessage = JSON.stringify({
tool: "newTask",
mode: mode ?? "",
content: message ?? "",
todos: todos,
})
await task.ask("tool", partialMessage, block.partial).catch(() => {})
}
}
export const newTaskTool = new NewTaskTool()