diff --git a/packages/types/src/experiment.ts b/packages/types/src/experiment.ts
index 6574124629..3890e92ed7 100644
--- a/packages/types/src/experiment.ts
+++ b/packages/types/src/experiment.ts
@@ -6,7 +6,12 @@ import type { Keys, Equals, AssertEqual } from "./type-fu.js"
* ExperimentId
*/
-export const experimentIds = ["powerSteering", "multiFileApplyDiff", "preventFocusDisruption", "assistantMessageParser"] as const
+export const experimentIds = [
+ "powerSteering",
+ "multiFileApplyDiff",
+ "preventFocusDisruption",
+ "assistantMessageParser",
+] as const
export const experimentIdsSchema = z.enum(experimentIds)
diff --git a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/architect-mode-prompt.snap b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/architect-mode-prompt.snap
index cfec773b44..d67156ab98 100644
--- a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/architect-mode-prompt.snap
+++ b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/architect-mode-prompt.snap
@@ -27,6 +27,12 @@ For example, to use the new_task tool:
code
Implement a new feature for the application.
+
+[ ] Design the feature architecture
+[ ] Implement core functionality
+[ ] Add error handling
+[ ] Write tests
+
Always use the actual tool name as the XML tag name for proper parsing and execution.
diff --git a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/ask-mode-prompt.snap b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/ask-mode-prompt.snap
index 0289fc2200..5deea1465d 100644
--- a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/ask-mode-prompt.snap
+++ b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/ask-mode-prompt.snap
@@ -27,6 +27,12 @@ For example, to use the new_task tool:
code
Implement a new feature for the application.
+
+[ ] Design the feature architecture
+[ ] Implement core functionality
+[ ] Add error handling
+[ ] Write tests
+
Always use the actual tool name as the XML tag name for proper parsing and execution.
diff --git a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-disabled.snap b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-disabled.snap
index f16c11a97f..f6cd169723 100644
--- a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-disabled.snap
+++ b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-disabled.snap
@@ -27,6 +27,12 @@ For example, to use the new_task tool:
code
Implement a new feature for the application.
+
+[ ] Design the feature architecture
+[ ] Implement core functionality
+[ ] Add error handling
+[ ] Write tests
+
Always use the actual tool name as the XML tag name for proper parsing and execution.
diff --git a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-enabled.snap b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-enabled.snap
index 168dd87d59..5a88719371 100644
--- a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-enabled.snap
+++ b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-enabled.snap
@@ -27,6 +27,12 @@ For example, to use the new_task tool:
code
Implement a new feature for the application.
+
+[ ] Design the feature architecture
+[ ] Implement core functionality
+[ ] Add error handling
+[ ] Write tests
+
Always use the actual tool name as the XML tag name for proper parsing and execution.
diff --git a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/partial-reads-enabled.snap b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/partial-reads-enabled.snap
index bafff056b5..6d43d30e9a 100644
--- a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/partial-reads-enabled.snap
+++ b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/partial-reads-enabled.snap
@@ -27,6 +27,12 @@ For example, to use the new_task tool:
code
Implement a new feature for the application.
+
+[ ] Design the feature architecture
+[ ] Implement core functionality
+[ ] Add error handling
+[ ] Write tests
+
Always use the actual tool name as the XML tag name for proper parsing and execution.
diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/consistent-system-prompt.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/consistent-system-prompt.snap
index cfec773b44..d67156ab98 100644
--- a/src/core/prompts/__tests__/__snapshots__/system-prompt/consistent-system-prompt.snap
+++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/consistent-system-prompt.snap
@@ -27,6 +27,12 @@ For example, to use the new_task tool:
code
Implement a new feature for the application.
+
+[ ] Design the feature architecture
+[ ] Implement core functionality
+[ ] Add error handling
+[ ] Write tests
+
Always use the actual tool name as the XML tag name for proper parsing and execution.
diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-computer-use-support.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-computer-use-support.snap
index fb1fb5c77e..6aec1a0651 100644
--- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-computer-use-support.snap
+++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-computer-use-support.snap
@@ -27,6 +27,12 @@ For example, to use the new_task tool:
code
Implement a new feature for the application.
+
+[ ] Design the feature architecture
+[ ] Implement core functionality
+[ ] Add error handling
+[ ] Write tests
+
Always use the actual tool name as the XML tag name for proper parsing and execution.
diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-false.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-false.snap
index cfec773b44..d67156ab98 100644
--- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-false.snap
+++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-false.snap
@@ -27,6 +27,12 @@ For example, to use the new_task tool:
code
Implement a new feature for the application.
+
+[ ] Design the feature architecture
+[ ] Implement core functionality
+[ ] Add error handling
+[ ] Write tests
+
Always use the actual tool name as the XML tag name for proper parsing and execution.
diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-true.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-true.snap
index 7c0108ff75..a8165dc4f2 100644
--- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-true.snap
+++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-true.snap
@@ -27,6 +27,12 @@ For example, to use the new_task tool:
code
Implement a new feature for the application.
+
+[ ] Design the feature architecture
+[ ] Implement core functionality
+[ ] Add error handling
+[ ] Write tests
+
Always use the actual tool name as the XML tag name for proper parsing and execution.
diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-undefined.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-undefined.snap
index cfec773b44..d67156ab98 100644
--- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-undefined.snap
+++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-undefined.snap
@@ -27,6 +27,12 @@ For example, to use the new_task tool:
code
Implement a new feature for the application.
+
+[ ] Design the feature architecture
+[ ] Implement core functionality
+[ ] Add error handling
+[ ] Write tests
+
Always use the actual tool name as the XML tag name for proper parsing and execution.
diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-different-viewport-size.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-different-viewport-size.snap
index bf764fdf83..b0408f01b8 100644
--- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-different-viewport-size.snap
+++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-different-viewport-size.snap
@@ -27,6 +27,12 @@ For example, to use the new_task tool:
code
Implement a new feature for the application.
+
+[ ] Design the feature architecture
+[ ] Implement core functionality
+[ ] Add error handling
+[ ] Write tests
+
Always use the actual tool name as the XML tag name for proper parsing and execution.
diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-mcp-hub-provided.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-mcp-hub-provided.snap
index 168dd87d59..5a88719371 100644
--- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-mcp-hub-provided.snap
+++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-mcp-hub-provided.snap
@@ -27,6 +27,12 @@ For example, to use the new_task tool:
code
Implement a new feature for the application.
+
+[ ] Design the feature architecture
+[ ] Implement core functionality
+[ ] Add error handling
+[ ] Write tests
+
Always use the actual tool name as the XML tag name for proper parsing and execution.
diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-undefined-mcp-hub.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-undefined-mcp-hub.snap
index cfec773b44..d67156ab98 100644
--- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-undefined-mcp-hub.snap
+++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-undefined-mcp-hub.snap
@@ -27,6 +27,12 @@ For example, to use the new_task tool:
code
Implement a new feature for the application.
+
+[ ] Design the feature architecture
+[ ] Implement core functionality
+[ ] Add error handling
+[ ] Write tests
+
Always use the actual tool name as the XML tag name for proper parsing and execution.
diff --git a/src/core/prompts/__tests__/system-prompt.spec.ts b/src/core/prompts/__tests__/system-prompt.spec.ts
index 4d5579408c..f2d5469da4 100644
--- a/src/core/prompts/__tests__/system-prompt.spec.ts
+++ b/src/core/prompts/__tests__/system-prompt.spec.ts
@@ -580,6 +580,7 @@ describe("SYSTEM_PROMPT", () => {
maxConcurrentFileReads: 5,
todoListEnabled: false,
useAgentRules: true,
+ newTaskRequireTodos: false,
}
const prompt = await SYSTEM_PROMPT(
@@ -612,6 +613,7 @@ describe("SYSTEM_PROMPT", () => {
maxConcurrentFileReads: 5,
todoListEnabled: true,
useAgentRules: true,
+ newTaskRequireTodos: false,
}
const prompt = await SYSTEM_PROMPT(
@@ -643,6 +645,7 @@ describe("SYSTEM_PROMPT", () => {
maxConcurrentFileReads: 5,
todoListEnabled: true,
useAgentRules: true,
+ newTaskRequireTodos: false,
}
const prompt = await SYSTEM_PROMPT(
diff --git a/src/core/prompts/sections/__tests__/custom-instructions.spec.ts b/src/core/prompts/sections/__tests__/custom-instructions.spec.ts
index be9c25bcaa..49904465ab 100644
--- a/src/core/prompts/sections/__tests__/custom-instructions.spec.ts
+++ b/src/core/prompts/sections/__tests__/custom-instructions.spec.ts
@@ -535,7 +535,14 @@ describe("addCustomInstructions", () => {
"global instructions",
"/fake/path",
"test-mode",
- { settings: { maxConcurrentFileReads: 5, todoListEnabled: true, useAgentRules: true } },
+ {
+ settings: {
+ maxConcurrentFileReads: 5,
+ todoListEnabled: true,
+ useAgentRules: true,
+ newTaskRequireTodos: false,
+ },
+ },
)
expect(result).toContain("# Agent Rules Standard (AGENTS.md):")
@@ -560,7 +567,14 @@ describe("addCustomInstructions", () => {
"global instructions",
"/fake/path",
"test-mode",
- { settings: { maxConcurrentFileReads: 5, todoListEnabled: true, useAgentRules: false } },
+ {
+ settings: {
+ maxConcurrentFileReads: 5,
+ todoListEnabled: true,
+ useAgentRules: false,
+ newTaskRequireTodos: false,
+ },
+ },
)
expect(result).not.toContain("# Agent Rules Standard (AGENTS.md):")
@@ -614,7 +628,14 @@ describe("addCustomInstructions", () => {
"global instructions",
"/fake/path",
"test-mode",
- { settings: { maxConcurrentFileReads: 5, todoListEnabled: true, useAgentRules: true } },
+ {
+ settings: {
+ maxConcurrentFileReads: 5,
+ todoListEnabled: true,
+ useAgentRules: true,
+ newTaskRequireTodos: false,
+ },
+ },
)
expect(result).toContain("Global Instructions:\nglobal instructions")
@@ -653,7 +674,14 @@ describe("addCustomInstructions", () => {
"global instructions",
"/fake/path",
"test-mode",
- { settings: { maxConcurrentFileReads: 5, todoListEnabled: true, useAgentRules: true } },
+ {
+ settings: {
+ maxConcurrentFileReads: 5,
+ todoListEnabled: true,
+ useAgentRules: true,
+ newTaskRequireTodos: false,
+ },
+ },
)
// Should contain both AGENTS.md and .roorules content
@@ -714,7 +742,14 @@ describe("addCustomInstructions", () => {
"global instructions",
"/fake/path",
"test-mode",
- { settings: { maxConcurrentFileReads: 5, todoListEnabled: true, useAgentRules: true } },
+ {
+ settings: {
+ maxConcurrentFileReads: 5,
+ todoListEnabled: true,
+ useAgentRules: true,
+ newTaskRequireTodos: false,
+ },
+ },
)
expect(result).toContain("# Agent Rules Standard (AGENTS.md):")
@@ -759,7 +794,14 @@ describe("addCustomInstructions", () => {
"global instructions",
"/fake/path",
"test-mode",
- { settings: { maxConcurrentFileReads: 5, todoListEnabled: true, useAgentRules: true } },
+ {
+ settings: {
+ maxConcurrentFileReads: 5,
+ todoListEnabled: true,
+ useAgentRules: true,
+ newTaskRequireTodos: false,
+ },
+ },
)
expect(result).toContain("# Agent Rules Standard (AGENTS.md):")
@@ -806,7 +848,14 @@ describe("addCustomInstructions", () => {
"global instructions",
"/fake/path",
"test-mode",
- { settings: { maxConcurrentFileReads: 5, todoListEnabled: true, useAgentRules: true } },
+ {
+ settings: {
+ maxConcurrentFileReads: 5,
+ todoListEnabled: true,
+ useAgentRules: true,
+ newTaskRequireTodos: false,
+ },
+ },
)
expect(result).toContain("# Agent Rules Standard (AGENT.md):")
@@ -845,7 +894,14 @@ describe("addCustomInstructions", () => {
"global instructions",
"/fake/path",
"test-mode",
- { settings: { maxConcurrentFileReads: 5, todoListEnabled: true, useAgentRules: true } },
+ {
+ settings: {
+ maxConcurrentFileReads: 5,
+ todoListEnabled: true,
+ useAgentRules: true,
+ newTaskRequireTodos: false,
+ },
+ },
)
// Should contain AGENTS.md content (preferred) and not AGENT.md
diff --git a/src/core/prompts/sections/tool-use.ts b/src/core/prompts/sections/tool-use.ts
index ea27e5916a..434d2f8785 100644
--- a/src/core/prompts/sections/tool-use.ts
+++ b/src/core/prompts/sections/tool-use.ts
@@ -20,6 +20,12 @@ For example, to use the new_task tool:
code
Implement a new feature for the application.
+
+[ ] Design the feature architecture
+[ ] Implement core functionality
+[ ] Add error handling
+[ ] Write tests
+
Always use the actual tool name as the XML tag name for proper parsing and execution.`
diff --git a/src/core/prompts/tools/__tests__/new-task.spec.ts b/src/core/prompts/tools/__tests__/new-task.spec.ts
new file mode 100644
index 0000000000..b6379384d0
--- /dev/null
+++ b/src/core/prompts/tools/__tests__/new-task.spec.ts
@@ -0,0 +1,117 @@
+import { describe, it, expect } from "vitest"
+import { getNewTaskDescription } from "../new-task"
+import { ToolArgs } from "../types"
+
+describe("getNewTaskDescription", () => {
+ it("should not show todos parameter at all when setting is disabled", () => {
+ const args: ToolArgs = {
+ cwd: "/test",
+ supportsComputerUse: false,
+ settings: {
+ newTaskRequireTodos: false,
+ },
+ }
+
+ const description = getNewTaskDescription(args)
+
+ // Check that todos parameter is not mentioned at all
+ expect(description).not.toContain("todos:")
+ expect(description).not.toContain("todo list")
+ expect(description).not.toContain("")
+ expect(description).not.toContain("")
+
+ // Should have a simple example without todos
+ expect(description).toContain("Implement a new feature for the application")
+
+ // Should still have mode and message as required
+ expect(description).toContain("mode: (required)")
+ expect(description).toContain("message: (required)")
+ })
+
+ it("should show todos as required when setting is enabled", () => {
+ const args: ToolArgs = {
+ cwd: "/test",
+ supportsComputerUse: false,
+ settings: {
+ newTaskRequireTodos: true,
+ },
+ }
+
+ const description = getNewTaskDescription(args)
+
+ // Check that todos is marked as required
+ expect(description).toContain("todos: (required)")
+ expect(description).toContain("and initial todo list")
+
+ // Should not contain any mention of optional for todos
+ expect(description).not.toContain("todos: (optional)")
+ expect(description).not.toContain("optional initial todo list")
+
+ // Should include todos in the example
+ expect(description).toContain("")
+ expect(description).toContain("")
+ expect(description).toContain("Set up auth middleware")
+ })
+
+ it("should not show todos parameter when settings is undefined", () => {
+ const args: ToolArgs = {
+ cwd: "/test",
+ supportsComputerUse: false,
+ settings: undefined,
+ }
+
+ const description = getNewTaskDescription(args)
+
+ // Check that todos parameter is not shown by default
+ expect(description).not.toContain("todos:")
+ expect(description).not.toContain("todo list")
+ expect(description).not.toContain("")
+ expect(description).not.toContain("")
+ })
+
+ it("should not show todos parameter when newTaskRequireTodos is undefined", () => {
+ const args: ToolArgs = {
+ cwd: "/test",
+ supportsComputerUse: false,
+ settings: {},
+ }
+
+ const description = getNewTaskDescription(args)
+
+ // Check that todos parameter is not shown by default
+ expect(description).not.toContain("todos:")
+ expect(description).not.toContain("todo list")
+ expect(description).not.toContain("")
+ expect(description).not.toContain("")
+ })
+
+ it("should only include todos in example when setting is enabled", () => {
+ const argsWithSettingOff: ToolArgs = {
+ cwd: "/test",
+ supportsComputerUse: false,
+ settings: {
+ newTaskRequireTodos: false,
+ },
+ }
+
+ const argsWithSettingOn: ToolArgs = {
+ cwd: "/test",
+ supportsComputerUse: false,
+ settings: {
+ newTaskRequireTodos: true,
+ },
+ }
+
+ const descriptionOff = getNewTaskDescription(argsWithSettingOff)
+ const descriptionOn = getNewTaskDescription(argsWithSettingOn)
+
+ // When setting is off, should NOT include todos in example
+ const todosPattern = /\s*\[\s*\]\s*Set up auth middleware/s
+ expect(descriptionOff).not.toMatch(todosPattern)
+ expect(descriptionOff).not.toContain("")
+
+ // When setting is on, should include todos in example
+ expect(descriptionOn).toMatch(todosPattern)
+ expect(descriptionOn).toContain("")
+ })
+})
diff --git a/src/core/prompts/tools/new-task.ts b/src/core/prompts/tools/new-task.ts
index 7301b7b422..0b15bc0ad9 100644
--- a/src/core/prompts/tools/new-task.ts
+++ b/src/core/prompts/tools/new-task.ts
@@ -1,7 +1,11 @@
import { ToolArgs } from "./types"
-export function getNewTaskDescription(_args: ToolArgs): string {
- return `## new_task
+export function getNewTaskDescription(args: ToolArgs): string {
+ const todosRequired = args.settings?.newTaskRequireTodos === true
+
+ // When setting is disabled, don't show todos parameter at all
+ if (!todosRequired) {
+ return `## new_task
Description: This will let you create a new task instance in the chosen mode using your provided message.
Parameters:
@@ -19,5 +23,39 @@ Example:
code
Implement a new feature for the application.
+`
+ }
+
+ // When setting is enabled, show todos as required
+ return `## new_task
+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) The initial todo list in markdown checklist format for the new 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 user authentication
+
+[ ] Set up auth middleware
+[ ] Create login endpoint
+[ ] Add session management
+[ ] Write tests
+
+
`
}
diff --git a/src/core/prompts/types.ts b/src/core/prompts/types.ts
index 3977ea98c5..d90b1b821a 100644
--- a/src/core/prompts/types.ts
+++ b/src/core/prompts/types.ts
@@ -5,4 +5,5 @@ export interface SystemPromptSettings {
maxConcurrentFileReads: number
todoListEnabled: boolean
useAgentRules: boolean
+ newTaskRequireTodos: boolean
}
diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts
index 3c3afeaadf..2a5b7afe29 100644
--- a/src/core/task/Task.ts
+++ b/src/core/task/Task.ts
@@ -126,6 +126,7 @@ export type TaskOptions = {
parentTask?: Task
taskNumber?: number
onCreated?: (task: Task) => void
+ initialTodos?: TodoItem[]
}
export class Task extends EventEmitter implements TaskLike {
@@ -290,6 +291,7 @@ export class Task extends EventEmitter implements TaskLike {
parentTask,
taskNumber = -1,
onCreated,
+ initialTodos,
}: TaskOptions) {
super()
@@ -373,6 +375,11 @@ export class Task extends EventEmitter implements TaskLike {
this.toolRepetitionDetector = new ToolRepetitionDetector(this.consecutiveMistakeLimit)
+ // Initialize todo list if provided
+ if (initialTodos && initialTodos.length > 0) {
+ this.todoList = initialTodos
+ }
+
onCreated?.(this)
if (startTask) {
@@ -1078,6 +1085,10 @@ export class Task extends EventEmitter implements TaskLike {
// messages from previous session).
this.clineMessages = []
this.apiConversationHistory = []
+
+ // The todo list is already set in the constructor if initialTodos were provided
+ // No need to add any messages - the todoList property is already set
+
await this.providerRef.deref()?.postStateToWebview()
await this.say("text", task, images)
@@ -2228,6 +2239,9 @@ export class Task extends EventEmitter implements TaskLike {
maxConcurrentFileReads: maxConcurrentFileReads ?? 5,
todoListEnabled: apiConfiguration?.todoListEnabled ?? true,
useAgentRules: vscode.workspace.getConfiguration("roo-cline").get("useAgentRules") ?? true,
+ newTaskRequireTodos: vscode.workspace
+ .getConfiguration("roo-cline")
+ .get("newTaskRequireTodos", false),
},
undefined, // todoList
this.api.getModel().id,
diff --git a/src/core/tools/__tests__/newTaskTool.spec.ts b/src/core/tools/__tests__/newTaskTool.spec.ts
index 8680a60035..1c883c6fe5 100644
--- a/src/core/tools/__tests__/newTaskTool.spec.ts
+++ b/src/core/tools/__tests__/newTaskTool.spec.ts
@@ -2,6 +2,15 @@
import type { AskApproval, HandleError } from "../../../shared/tools"
+// Mock vscode module
+vi.mock("vscode", () => ({
+ workspace: {
+ getConfiguration: vi.fn(() => ({
+ get: vi.fn(),
+ })),
+ },
+}))
+
// Mock other modules first - these are hoisted to the top
vi.mock("../../../shared/modes", () => ({
getModeBySlug: vi.fn(),
@@ -14,6 +23,33 @@ vi.mock("../../prompts/responses", () => ({
},
}))
+vi.mock("../updateTodoListTool", () => ({
+ parseMarkdownChecklist: vi.fn((md: string) => {
+ // Simple mock implementation
+ const lines = md.split("\n").filter((line) => line.trim())
+ return lines.map((line, index) => {
+ let status = "pending"
+ let content = line
+
+ if (line.includes("[x]") || line.includes("[X]")) {
+ status = "completed"
+ content = line.replace(/^\[x\]\s*/i, "")
+ } else if (line.includes("[-]") || line.includes("[~]")) {
+ status = "in_progress"
+ content = line.replace(/^\[-\]\s*/, "").replace(/^\[~\]\s*/, "")
+ } else {
+ content = line.replace(/^\[\s*\]\s*/, "")
+ }
+
+ return {
+ id: `todo-${index}`,
+ content,
+ status,
+ }
+ })
+ }),
+}))
+
// Define a minimal type for the resolved value
type MockClineInstance = { taskId: string }
@@ -22,7 +58,9 @@ const mockAskApproval = vi.fn()
const mockHandleError = vi.fn()
const mockPushToolResult = vi.fn()
const mockRemoveClosingTag = vi.fn((_name: string, value: string | undefined) => value ?? "")
-const mockCreateTask = vi.fn<() => Promise>().mockResolvedValue({ taskId: "mock-subtask-id" })
+const mockCreateTask = vi
+ .fn<(text?: string, images?: string[], parentTask?: any, options?: any) => Promise>()
+ .mockResolvedValue({ taskId: "mock-subtask-id" })
const mockEmit = vi.fn()
const mockRecordToolError = vi.fn()
const mockSayAndCreateMissingParamError = vi.fn()
@@ -49,6 +87,7 @@ const mockCline = {
import { newTaskTool } from "../newTaskTool"
import type { ToolUse } from "../../../shared/tools"
import { getModeBySlug } from "../../../shared/modes"
+import * as vscode from "vscode"
describe("newTaskTool", () => {
beforeEach(() => {
@@ -63,6 +102,11 @@ describe("newTaskTool", () => {
}) // Default valid mode
mockCline.consecutiveMistakeCount = 0
mockCline.isPaused = false
+ // Default: VSCode setting is disabled
+ const mockGet = vi.fn().mockReturnValue(false)
+ vi.mocked(vscode.workspace.getConfiguration).mockReturnValue({
+ get: mockGet,
+ } as any)
})
it("should correctly un-escape \\\\@ to \\@ in the message passed to the new task", async () => {
@@ -72,6 +116,7 @@ describe("newTaskTool", () => {
params: {
mode: "code",
message: "Review this: \\\\@file1.txt and also \\\\\\\\@file2.txt", // Input with \\@ and \\\\@
+ todos: "[ ] First task\n[ ] Second task",
},
partial: false,
}
@@ -93,6 +138,12 @@ describe("newTaskTool", () => {
"Review this: \\@file1.txt and also \\\\\\@file2.txt", // Unit Test Expectation: \\@ -> \@, \\\\@ -> \\\\@
undefined,
mockCline,
+ expect.objectContaining({
+ initialTodos: expect.arrayContaining([
+ expect.objectContaining({ content: "First task" }),
+ expect.objectContaining({ content: "Second task" }),
+ ]),
+ }),
)
// Verify side effects
@@ -109,6 +160,7 @@ describe("newTaskTool", () => {
params: {
mode: "code",
message: "This is already unescaped: \\@file1.txt",
+ todos: "[ ] Test todo",
},
partial: false,
}
@@ -126,6 +178,9 @@ describe("newTaskTool", () => {
"This is already unescaped: \\@file1.txt", // Expected: \@ remains \@
undefined,
mockCline,
+ expect.objectContaining({
+ initialTodos: expect.any(Array),
+ }),
)
})
@@ -136,6 +191,7 @@ describe("newTaskTool", () => {
params: {
mode: "code",
message: "A normal mention @file1.txt",
+ todos: "[ ] Test todo",
},
partial: false,
}
@@ -153,6 +209,9 @@ describe("newTaskTool", () => {
"A normal mention @file1.txt", // Expected: @ remains @
undefined,
mockCline,
+ expect.objectContaining({
+ initialTodos: expect.any(Array),
+ }),
)
})
@@ -163,6 +222,7 @@ describe("newTaskTool", () => {
params: {
mode: "code",
message: "Mix: @file0.txt, \\@file1.txt, \\\\@file2.txt, \\\\\\\\@file3.txt",
+ todos: "[ ] Test todo",
},
partial: false,
}
@@ -180,8 +240,386 @@ describe("newTaskTool", () => {
"Mix: @file0.txt, \\@file1.txt, \\@file2.txt, \\\\\\@file3.txt", // Unit Test Expectation: @->@, \@->\@, \\@->\@, \\\\@->\\\\@
undefined,
mockCline,
+ expect.objectContaining({
+ initialTodos: expect.any(Array),
+ }),
)
})
- // Add more tests for error handling (missing params, invalid mode, approval denied) if needed
+ it("should handle missing todos parameter gracefully (backward compatibility)", async () => {
+ const block: ToolUse = {
+ type: "tool_use",
+ name: "new_task",
+ params: {
+ mode: "code",
+ message: "Test message",
+ // todos missing - should work for backward compatibility
+ },
+ partial: false,
+ }
+
+ await newTaskTool(
+ mockCline as any,
+ block,
+ mockAskApproval,
+ mockHandleError,
+ mockPushToolResult,
+ mockRemoveClosingTag,
+ )
+
+ // Should NOT error when todos is missing
+ expect(mockSayAndCreateMissingParamError).not.toHaveBeenCalledWith("new_task", "todos")
+ expect(mockCline.consecutiveMistakeCount).toBe(0)
+ expect(mockCline.recordToolError).not.toHaveBeenCalledWith("new_task")
+
+ // Should create task with empty todos array
+ expect(mockCreateTask).toHaveBeenCalledWith(
+ "Test message",
+ undefined,
+ mockCline,
+ expect.objectContaining({
+ initialTodos: [],
+ }),
+ )
+
+ // Should complete successfully
+ expect(mockPushToolResult).toHaveBeenCalledWith(expect.stringContaining("Successfully created new task"))
+ })
+
+ it("should work with todos parameter when provided", async () => {
+ const block: ToolUse = {
+ type: "tool_use",
+ name: "new_task",
+ params: {
+ mode: "code",
+ message: "Test message with todos",
+ todos: "[ ] First task\n[ ] Second task",
+ },
+ partial: false,
+ }
+
+ await newTaskTool(
+ mockCline as any,
+ block,
+ mockAskApproval,
+ mockHandleError,
+ mockPushToolResult,
+ mockRemoveClosingTag,
+ )
+
+ // Should parse and include todos when provided
+ expect(mockCreateTask).toHaveBeenCalledWith(
+ "Test message with todos",
+ undefined,
+ mockCline,
+ expect.objectContaining({
+ initialTodos: expect.arrayContaining([
+ expect.objectContaining({ content: "First task" }),
+ expect.objectContaining({ content: "Second task" }),
+ ]),
+ }),
+ )
+
+ expect(mockPushToolResult).toHaveBeenCalledWith(expect.stringContaining("Successfully created new task"))
+ })
+
+ it("should error when mode parameter is missing", async () => {
+ const block: ToolUse = {
+ type: "tool_use",
+ name: "new_task",
+ params: {
+ // mode missing
+ message: "Test message",
+ todos: "[ ] Test todo",
+ },
+ partial: false,
+ }
+
+ await newTaskTool(
+ mockCline as any,
+ block,
+ mockAskApproval,
+ mockHandleError,
+ mockPushToolResult,
+ mockRemoveClosingTag,
+ )
+
+ expect(mockSayAndCreateMissingParamError).toHaveBeenCalledWith("new_task", "mode")
+ expect(mockCline.consecutiveMistakeCount).toBe(1)
+ expect(mockCline.recordToolError).toHaveBeenCalledWith("new_task")
+ })
+
+ it("should error when message parameter is missing", async () => {
+ const block: ToolUse = {
+ type: "tool_use",
+ name: "new_task",
+ params: {
+ mode: "code",
+ // message missing
+ todos: "[ ] Test todo",
+ },
+ partial: false,
+ }
+
+ await newTaskTool(
+ mockCline as any,
+ block,
+ mockAskApproval,
+ mockHandleError,
+ mockPushToolResult,
+ mockRemoveClosingTag,
+ )
+
+ expect(mockSayAndCreateMissingParamError).toHaveBeenCalledWith("new_task", "message")
+ expect(mockCline.consecutiveMistakeCount).toBe(1)
+ expect(mockCline.recordToolError).toHaveBeenCalledWith("new_task")
+ })
+
+ it("should parse todos with different statuses correctly", async () => {
+ const block: ToolUse = {
+ type: "tool_use",
+ name: "new_task",
+ params: {
+ mode: "code",
+ message: "Test message",
+ todos: "[ ] Pending task\n[x] Completed task\n[-] In progress task",
+ },
+ partial: false,
+ }
+
+ await newTaskTool(
+ mockCline as any,
+ block,
+ mockAskApproval,
+ mockHandleError,
+ mockPushToolResult,
+ mockRemoveClosingTag,
+ )
+
+ expect(mockCreateTask).toHaveBeenCalledWith(
+ "Test message",
+ undefined,
+ mockCline,
+ expect.objectContaining({
+ initialTodos: expect.arrayContaining([
+ expect.objectContaining({ content: "Pending task", status: "pending" }),
+ expect.objectContaining({ content: "Completed task", status: "completed" }),
+ expect.objectContaining({ content: "In progress task", status: "in_progress" }),
+ ]),
+ }),
+ )
+ })
+
+ describe("VSCode setting: newTaskRequireTodos", () => {
+ it("should NOT require todos when VSCode setting is disabled (default)", async () => {
+ // Ensure VSCode setting is disabled
+ const mockGet = vi.fn().mockReturnValue(false)
+ vi.mocked(vscode.workspace.getConfiguration).mockReturnValue({
+ get: mockGet,
+ } as any)
+
+ const block: ToolUse = {
+ type: "tool_use",
+ name: "new_task",
+ params: {
+ mode: "code",
+ message: "Test message",
+ // todos missing - should work when setting is disabled
+ },
+ partial: false,
+ }
+
+ await newTaskTool(
+ mockCline as any,
+ block,
+ mockAskApproval,
+ mockHandleError,
+ mockPushToolResult,
+ mockRemoveClosingTag,
+ )
+
+ // Should NOT error when todos is missing and setting is disabled
+ expect(mockSayAndCreateMissingParamError).not.toHaveBeenCalledWith("new_task", "todos")
+ expect(mockCline.consecutiveMistakeCount).toBe(0)
+ expect(mockCline.recordToolError).not.toHaveBeenCalledWith("new_task")
+
+ // Should create task with empty todos array
+ expect(mockCreateTask).toHaveBeenCalledWith(
+ "Test message",
+ undefined,
+ mockCline,
+ expect.objectContaining({
+ initialTodos: [],
+ }),
+ )
+
+ // Should complete successfully
+ expect(mockPushToolResult).toHaveBeenCalledWith(expect.stringContaining("Successfully created new task"))
+ })
+
+ it("should REQUIRE todos when VSCode setting is enabled", async () => {
+ // Enable VSCode setting
+ const mockGet = vi.fn().mockReturnValue(true)
+ vi.mocked(vscode.workspace.getConfiguration).mockReturnValue({
+ get: mockGet,
+ } as any)
+
+ const block: ToolUse = {
+ type: "tool_use",
+ name: "new_task",
+ params: {
+ mode: "code",
+ message: "Test message",
+ // todos missing - should error when setting is enabled
+ },
+ partial: false,
+ }
+
+ await newTaskTool(
+ mockCline as any,
+ block,
+ mockAskApproval,
+ mockHandleError,
+ mockPushToolResult,
+ mockRemoveClosingTag,
+ )
+
+ // Should error when todos is missing and setting is enabled
+ expect(mockSayAndCreateMissingParamError).toHaveBeenCalledWith("new_task", "todos")
+ expect(mockCline.consecutiveMistakeCount).toBe(1)
+ expect(mockCline.recordToolError).toHaveBeenCalledWith("new_task")
+
+ // Should NOT create task
+ expect(mockCreateTask).not.toHaveBeenCalled()
+ expect(mockPushToolResult).not.toHaveBeenCalledWith(
+ expect.stringContaining("Successfully created new task"),
+ )
+ })
+
+ it("should work with todos when VSCode setting is enabled", async () => {
+ // Enable VSCode setting
+ const mockGet = vi.fn().mockReturnValue(true)
+ vi.mocked(vscode.workspace.getConfiguration).mockReturnValue({
+ get: mockGet,
+ } as any)
+
+ const block: ToolUse = {
+ type: "tool_use",
+ name: "new_task",
+ params: {
+ mode: "code",
+ message: "Test message",
+ todos: "[ ] First task\n[ ] Second task",
+ },
+ partial: false,
+ }
+
+ await newTaskTool(
+ mockCline as any,
+ block,
+ mockAskApproval,
+ mockHandleError,
+ mockPushToolResult,
+ mockRemoveClosingTag,
+ )
+
+ // Should NOT error when todos is provided and setting is enabled
+ expect(mockSayAndCreateMissingParamError).not.toHaveBeenCalledWith("new_task", "todos")
+ expect(mockCline.consecutiveMistakeCount).toBe(0)
+
+ // Should create task with parsed todos
+ expect(mockCreateTask).toHaveBeenCalledWith(
+ "Test message",
+ undefined,
+ mockCline,
+ expect.objectContaining({
+ initialTodos: expect.arrayContaining([
+ expect.objectContaining({ content: "First task" }),
+ expect.objectContaining({ content: "Second task" }),
+ ]),
+ }),
+ )
+
+ // Should complete successfully
+ expect(mockPushToolResult).toHaveBeenCalledWith(expect.stringContaining("Successfully created new task"))
+ })
+
+ it("should work with empty todos string when VSCode setting is enabled", async () => {
+ // Enable VSCode setting
+ const mockGet = vi.fn().mockReturnValue(true)
+ vi.mocked(vscode.workspace.getConfiguration).mockReturnValue({
+ get: mockGet,
+ } as any)
+
+ const block: ToolUse = {
+ type: "tool_use",
+ name: "new_task",
+ params: {
+ mode: "code",
+ message: "Test message",
+ todos: "", // Empty string should be accepted
+ },
+ partial: false,
+ }
+
+ await newTaskTool(
+ mockCline as any,
+ block,
+ mockAskApproval,
+ mockHandleError,
+ mockPushToolResult,
+ mockRemoveClosingTag,
+ )
+
+ // Should NOT error when todos is empty string and setting is enabled
+ expect(mockSayAndCreateMissingParamError).not.toHaveBeenCalledWith("new_task", "todos")
+ expect(mockCline.consecutiveMistakeCount).toBe(0)
+
+ // Should create task with empty todos array
+ expect(mockCreateTask).toHaveBeenCalledWith(
+ "Test message",
+ undefined,
+ mockCline,
+ expect.objectContaining({
+ initialTodos: [],
+ }),
+ )
+
+ // Should complete successfully
+ expect(mockPushToolResult).toHaveBeenCalledWith(expect.stringContaining("Successfully created new task"))
+ })
+
+ it("should check VSCode setting with correct configuration key", async () => {
+ const mockGet = vi.fn().mockReturnValue(false)
+ const mockGetConfiguration = vi.fn().mockReturnValue({
+ get: mockGet,
+ } as any)
+ vi.mocked(vscode.workspace.getConfiguration).mockImplementation(mockGetConfiguration)
+
+ const block: ToolUse = {
+ type: "tool_use",
+ name: "new_task",
+ params: {
+ mode: "code",
+ message: "Test message",
+ },
+ partial: false,
+ }
+
+ await newTaskTool(
+ mockCline as any,
+ block,
+ mockAskApproval,
+ mockHandleError,
+ mockPushToolResult,
+ mockRemoveClosingTag,
+ )
+
+ // Verify that VSCode configuration was accessed correctly
+ expect(mockGetConfiguration).toHaveBeenCalledWith("roo-cline")
+ expect(mockGet).toHaveBeenCalledWith("newTaskRequireTodos", false)
+ })
+ })
+
+ // Add more tests for error handling (invalid mode, approval denied) if needed
})
diff --git a/src/core/tools/newTaskTool.ts b/src/core/tools/newTaskTool.ts
index dbe08cbae8..2a3ab293b6 100644
--- a/src/core/tools/newTaskTool.ts
+++ b/src/core/tools/newTaskTool.ts
@@ -1,12 +1,14 @@
import delay from "delay"
+import * as vscode from "vscode"
-import { RooCodeEventName } from "@roo-code/types"
+import { RooCodeEventName, TodoItem } from "@roo-code/types"
import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools"
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,
@@ -18,6 +20,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) {
@@ -25,11 +28,13 @@ 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(() => {})
return
} else {
+ // Validate required parameters
if (!mode) {
cline.consecutiveMistakeCount++
cline.recordToolError("new_task")
@@ -44,13 +49,46 @@ export async function newTaskTool(
return
}
+ // Get the VSCode setting for requiring todos
+ const provider = cline.providerRef.deref()
+ if (!provider) {
+ pushToolResult(formatResponse.toolError("Provider reference lost"))
+ return
+ }
+ const state = await provider.getState()
+ const requireTodos = vscode.workspace
+ .getConfiguration("roo-cline")
+ .get("newTaskRequireTodos", false)
+
+ // Check if todos are required based on VSCode setting
+ // Note: undefined means not provided, empty string is valid
+ if (requireTodos && todos === undefined) {
+ cline.consecutiveMistakeCount++
+ cline.recordToolError("new_task")
+ pushToolResult(await cline.sayAndCreateMissingParamError("new_task", "todos"))
+ return
+ }
+
+ // Parse todos if provided, otherwise use empty array
+ let todoItems: TodoItem[] = []
+ if (todos) {
+ try {
+ todoItems = parseMarkdownChecklist(todos)
+ } catch (error) {
+ cline.consecutiveMistakeCount++
+ cline.recordToolError("new_task")
+ pushToolResult(formatResponse.toolError("Invalid todos format: must be a markdown checklist"))
+ return
+ }
+ }
+
cline.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, (await cline.providerRef.deref()?.getState())?.customModes)
+ const targetMode = getModeBySlug(mode, state?.customModes)
if (!targetMode) {
pushToolResult(formatResponse.toolError(`Invalid mode: ${mode}`))
@@ -61,6 +99,7 @@ export async function newTaskTool(
tool: "newTask",
mode: targetMode.name,
content: message,
+ todos: todoItems,
})
const didApprove = await askApproval("tool", toolMessage)
@@ -69,11 +108,7 @@ export async function newTaskTool(
return
}
- const provider = cline.providerRef.deref()
-
- if (!provider) {
- return
- }
+ // Provider is guaranteed to be defined here due to earlier check
if (cline.enableCheckpoints) {
cline.checkpointSave(true)
@@ -83,8 +118,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.createTask(unescapedMessage, undefined, cline)
-
+ const newCline = await provider.createTask(unescapedMessage, undefined, cline, {
+ initialTodos: todoItems,
+ })
if (!newCline) {
pushToolResult(t("tools:newTask.errors.policy_restriction"))
return
@@ -98,7 +134,9 @@ export async function newTaskTool(
cline.emit(RooCodeEventName.TaskSpawned, newCline.taskId)
- pushToolResult(`Successfully created new task in ${targetMode.name} mode with message: ${unescapedMessage}`)
+ pushToolResult(
+ `Successfully created new task in ${targetMode.name} mode with message: ${unescapedMessage} and ${todoItems.length} todo items`,
+ )
// Set the isPaused flag to true so the parent
// task can wait for the sub-task to finish.
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 77dfa42a3b..b6c00ee379 100644
--- a/src/core/webview/ClineProvider.ts
+++ b/src/core/webview/ClineProvider.ts
@@ -757,7 +757,12 @@ export class ClineProvider
options: Partial<
Pick<
TaskOptions,
- "enableDiff" | "enableCheckpoints" | "fuzzyMatchThreshold" | "consecutiveMistakeLimit" | "experiments"
+ | "enableDiff"
+ | "enableCheckpoints"
+ | "fuzzyMatchThreshold"
+ | "consecutiveMistakeLimit"
+ | "experiments"
+ | "initialTodos"
>
> = {},
) {
@@ -791,6 +796,7 @@ export class ClineProvider
taskNumber: this.clineStack.length + 1,
onCreated: this.taskCreationCallback,
enableTaskBridge: isRemoteControlEnabled(cloudUserInfo, remoteControlEnabled),
+ initialTodos: options.initialTodos,
...options,
})
@@ -1791,6 +1797,7 @@ export class ClineProvider
? (taskHistory || []).find((item: HistoryItem) => item.id === this.getCurrentTask()?.taskId)
: undefined,
clineMessages: this.getCurrentTask()?.clineMessages || [],
+ currentTaskTodos: this.getCurrentTask()?.todoList || [],
taskHistory: (taskHistory || [])
.filter((item: HistoryItem) => item.ts && item.task)
.sort((a: HistoryItem, b: HistoryItem) => b.ts - a.ts),
diff --git a/src/core/webview/generateSystemPrompt.ts b/src/core/webview/generateSystemPrompt.ts
index 9b96dbedbb..a639d8a960 100644
--- a/src/core/webview/generateSystemPrompt.ts
+++ b/src/core/webview/generateSystemPrompt.ts
@@ -85,6 +85,9 @@ export const generateSystemPrompt = async (provider: ClineProvider, message: Web
maxConcurrentFileReads: maxConcurrentFileReads ?? 5,
todoListEnabled: apiConfiguration?.todoListEnabled ?? true,
useAgentRules: vscode.workspace.getConfiguration("roo-cline").get("useAgentRules") ?? true,
+ newTaskRequireTodos: vscode.workspace
+ .getConfiguration("roo-cline")
+ .get("newTaskRequireTodos", false),
},
)
diff --git a/src/package.json b/src/package.json
index c655e19ce6..b79d1dc8f1 100644
--- a/src/package.json
+++ b/src/package.json
@@ -398,6 +398,11 @@
"minimum": 0,
"maximum": 3600,
"description": "%settings.apiRequestTimeout.description%"
+ },
+ "roo-cline.newTaskRequireTodos": {
+ "type": "boolean",
+ "default": false,
+ "description": "%settings.newTaskRequireTodos.description%"
}
}
}
diff --git a/src/package.nls.json b/src/package.nls.json
index f1c132be65..a18887218a 100644
--- a/src/package.nls.json
+++ b/src/package.nls.json
@@ -39,5 +39,6 @@
"settings.enableCodeActions.description": "Enable Roo Code quick fixes",
"settings.autoImportSettingsPath.description": "Path to a RooCode configuration file to automatically import on extension startup. Supports absolute paths and paths relative to the home directory (e.g. '~/Documents/roo-code-settings.json'). Leave empty to disable auto-import.",
"settings.useAgentRules.description": "Enable loading of AGENTS.md files for agent-specific rules (see https://agent-rules.org/)",
- "settings.apiRequestTimeout.description": "Maximum time in seconds to wait for API responses (0 = no timeout, 1-3600s, default: 600s). Higher values are recommended for local providers like LM Studio and Ollama that may need more processing time."
+ "settings.apiRequestTimeout.description": "Maximum time in seconds to wait for API responses (0 = no timeout, 1-3600s, default: 600s). Higher values are recommended for local providers like LM Studio and Ollama that may need more processing time.",
+ "settings.newTaskRequireTodos.description": "Require todos parameter when creating new tasks with the new_task tool"
}
diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts
index ebdc137432..dccc1f2af0 100644
--- a/src/shared/ExtensionMessage.ts
+++ b/src/shared/ExtensionMessage.ts
@@ -8,6 +8,7 @@ import type {
Experiments,
ClineMessage,
MarketplaceItem,
+ TodoItem,
} from "@roo-code/types"
import type { CloudUserInfo, OrganizationAllowList, ShareVisibility } from "@roo-code/cloud"
@@ -274,6 +275,7 @@ export type ExtensionState = Pick<
version: string
clineMessages: ClineMessage[]
currentTaskItem?: HistoryItem
+ currentTaskTodos?: TodoItem[] // Initial todos for the current task
apiConfiguration?: ProviderSettings
uriScheme?: string
shouldShowAnnouncement: boolean
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 {
diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx
index 3b77bbdb7d..d2a053b07f 100644
--- a/webview-ui/src/components/chat/ChatView.tsx
+++ b/webview-ui/src/components/chat/ChatView.tsx
@@ -87,6 +87,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction messages.at(0), [messages])
const latestTodos = useMemo(() => {
+ // First check if we have initial todos from the state (for new subtasks)
+ if (currentTaskTodos && currentTaskTodos.length > 0) {
+ // Check if there are any todo updates in messages
+ const messageBasedTodos = getLatestTodo(messages)
+ // If there are message-based todos, they take precedence (user has updated them)
+ if (messageBasedTodos && messageBasedTodos.length > 0) {
+ return messageBasedTodos
+ }
+ // Otherwise use the initial todos from state
+ return currentTaskTodos
+ }
+ // Fall back to extracting from messages
return getLatestTodo(messages)
- }, [messages])
+ }, [messages, currentTaskTodos])
const modifiedMessages = useMemo(() => combineApiRequests(combineCommandSequences(messages.slice(1))), [messages])
diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx
index b0045977c3..e632b27a31 100644
--- a/webview-ui/src/context/ExtensionStateContext.tsx
+++ b/webview-ui/src/context/ExtensionStateContext.tsx
@@ -6,6 +6,7 @@ import {
type CustomModePrompts,
type ModeConfig,
type ExperimentId,
+ type TodoItem,
} from "@roo-code/types"
import { type OrganizationAllowList, ORGANIZATION_ALLOW_ALL } from "@roo/cloud"
@@ -31,6 +32,7 @@ export interface ExtensionStateContextType extends ExtensionState {
mcpServers: McpServer[]
hasSystemPromptOverride?: boolean
currentCheckpoint?: string
+ currentTaskTodos?: TodoItem[] // Initial todos for the current task
filePaths: string[]
openedTabs: Array<{ label: string; isActive: boolean; path?: string }>
commands: Command[]
diff --git a/webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx b/webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx
index a688cac885..b19f152457 100644
--- a/webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx
+++ b/webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx
@@ -230,6 +230,7 @@ describe("mergeExtensionState", () => {
multiFileApplyDiff: true,
preventFocusDisruption: false,
assistantMessageParser: false,
+ newTaskRequireTodos: false,
} as Record,
}
@@ -248,6 +249,7 @@ describe("mergeExtensionState", () => {
multiFileApplyDiff: true,
preventFocusDisruption: false,
assistantMessageParser: false,
+ newTaskRequireTodos: false,
})
})
})
diff --git a/webview-ui/src/i18n/locales/ca/settings.json b/webview-ui/src/i18n/locales/ca/settings.json
index d9c7ce7ee2..54d66c17c7 100644
--- a/webview-ui/src/i18n/locales/ca/settings.json
+++ b/webview-ui/src/i18n/locales/ca/settings.json
@@ -719,6 +719,10 @@
"ASSISTANT_MESSAGE_PARSER": {
"name": "Utilitza el nou analitzador de missatges",
"description": "Activa l'analitzador de missatges en streaming experimental que millora el rendiment en respostes llargues processant els missatges de manera més eficient."
+ },
+ "NEW_TASK_REQUIRE_TODOS": {
+ "name": "Requerir la llista 'todos' per a noves tasques",
+ "description": "Quan estigui activat, l'eina new_task requerirà que es proporcioni un paràmetre 'todos'. Això garanteix que totes les noves tasques comencin amb una llista clara d'objectius. Quan estigui desactivat (per defecte), el paràmetre 'todos' continua sent opcional per a la compatibilitat amb versions anteriors."
}
},
"promptCaching": {
diff --git a/webview-ui/src/i18n/locales/de/settings.json b/webview-ui/src/i18n/locales/de/settings.json
index 7f09401e57..402e57c8dc 100644
--- a/webview-ui/src/i18n/locales/de/settings.json
+++ b/webview-ui/src/i18n/locales/de/settings.json
@@ -719,6 +719,10 @@
"ASSISTANT_MESSAGE_PARSER": {
"name": "Neuen Nachrichtenparser verwenden",
"description": "Aktiviere den experimentellen Streaming-Nachrichtenparser, der lange Antworten durch effizientere Verarbeitung spürbar schneller macht."
+ },
+ "NEW_TASK_REQUIRE_TODOS": {
+ "name": "'todos'-Liste für neue Aufgaben anfordern",
+ "description": "Wenn aktiviert, erfordert das new_task-Tool die Angabe eines todos-Parameters. Dies stellt sicher, dass alle neuen Aufgaben mit einer klaren Zielliste beginnen. Wenn deaktiviert (Standard), bleibt der todos-Parameter aus Gründen der Abwärtskompatibilität optional."
}
},
"promptCaching": {
diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json
index d18a3bbd5e..63a9445924 100644
--- a/webview-ui/src/i18n/locales/en/settings.json
+++ b/webview-ui/src/i18n/locales/en/settings.json
@@ -718,6 +718,10 @@
"ASSISTANT_MESSAGE_PARSER": {
"name": "Use new message parser",
"description": "Enable the experimental streaming message parser that provides significant performance improvements for long assistant responses by processing messages more efficiently."
+ },
+ "NEW_TASK_REQUIRE_TODOS": {
+ "name": "Require 'todos' list for new tasks",
+ "description": "When enabled, the new_task tool will require a todos parameter to be provided. This ensures all new tasks start with a clear list of objectives. When disabled (default), the todos parameter remains optional for backward compatibility."
}
},
"promptCaching": {
diff --git a/webview-ui/src/i18n/locales/es/settings.json b/webview-ui/src/i18n/locales/es/settings.json
index ec9795d8b0..a67e2520d0 100644
--- a/webview-ui/src/i18n/locales/es/settings.json
+++ b/webview-ui/src/i18n/locales/es/settings.json
@@ -719,6 +719,10 @@
"ASSISTANT_MESSAGE_PARSER": {
"name": "Usar el nuevo analizador de mensajes",
"description": "Activa el analizador de mensajes en streaming experimental que mejora el rendimiento en respuestas largas procesando los mensajes de forma más eficiente."
+ },
+ "NEW_TASK_REQUIRE_TODOS": {
+ "name": "Requerir lista de 'todos' para nuevas tareas",
+ "description": "Cuando está habilitado, la herramienta new_task requerirá que se proporcione un parámetro todos. Esto asegura que todas las nuevas tareas comiencen con una lista clara de objetivos. Cuando está deshabilitado (predeterminado), el parámetro todos permanece opcional por compatibilidad con versiones anteriores."
}
},
"promptCaching": {
diff --git a/webview-ui/src/i18n/locales/fr/settings.json b/webview-ui/src/i18n/locales/fr/settings.json
index 68230b1a60..36e4629c43 100644
--- a/webview-ui/src/i18n/locales/fr/settings.json
+++ b/webview-ui/src/i18n/locales/fr/settings.json
@@ -719,6 +719,10 @@
"ASSISTANT_MESSAGE_PARSER": {
"name": "Utiliser le nouveau parseur de messages",
"description": "Active le parseur de messages en streaming expérimental qui accélère nettement les longues réponses en traitant les messages plus efficacement."
+ },
+ "NEW_TASK_REQUIRE_TODOS": {
+ "name": "Exiger la liste 'todos' pour les nouvelles tâches",
+ "description": "Lorsqu'il est activé, l'outil new_task exigera qu'un paramètre todos soit fourni. Cela garantit que toutes les nouvelles tâches commencent avec une liste claire d'objectifs. Lorsqu'il est désactivé (par défaut), le paramètre todos reste facultatif pour la compatibilité descendante."
}
},
"promptCaching": {
diff --git a/webview-ui/src/i18n/locales/hi/settings.json b/webview-ui/src/i18n/locales/hi/settings.json
index f98f7e6510..a419e7a688 100644
--- a/webview-ui/src/i18n/locales/hi/settings.json
+++ b/webview-ui/src/i18n/locales/hi/settings.json
@@ -720,6 +720,10 @@
"ASSISTANT_MESSAGE_PARSER": {
"name": "नए मैसेज पार्सर का उपयोग करें",
"description": "प्रायोगिक स्ट्रीमिंग मैसेज पार्सर सक्षम करें, जो लंबे उत्तरों के लिए संदेशों को अधिक कुशलता से प्रोसेस करके प्रदर्शन को बेहतर बनाता है।"
+ },
+ "NEW_TASK_REQUIRE_TODOS": {
+ "name": "नए कार्यों के लिए 'todos' सूची की आवश्यकता है",
+ "description": "जब सक्षम किया जाता है, तो new_task टूल को todos पैरामीटर प्रदान करने की आवश्यकता होगी। यह सुनिश्चित करता है कि सभी नए कार्य स्पष्ट उद्देश्यों की सूची के साथ शुरू हों। जब अक्षम किया जाता है (डिफ़ॉल्ट), तो todos पैरामीटर पिछड़े संगतता के लिए वैकल्पिक रहता है।"
}
},
"promptCaching": {
diff --git a/webview-ui/src/i18n/locales/id/settings.json b/webview-ui/src/i18n/locales/id/settings.json
index 748bf198eb..fe16c7724a 100644
--- a/webview-ui/src/i18n/locales/id/settings.json
+++ b/webview-ui/src/i18n/locales/id/settings.json
@@ -749,6 +749,10 @@
"ASSISTANT_MESSAGE_PARSER": {
"name": "Gunakan parser pesan baru",
"description": "Aktifkan parser pesan streaming eksperimental yang meningkatkan kinerja untuk respons panjang dengan memproses pesan lebih efisien."
+ },
+ "NEW_TASK_REQUIRE_TODOS": {
+ "name": "Membutuhkan daftar 'todos' untuk tugas baru",
+ "description": "Ketika diaktifkan, alat new_task akan membutuhkan parameter todos untuk disediakan. Ini memastikan semua tugas baru dimulai dengan daftar tujuan yang jelas. Ketika dinonaktifkan (default), parameter todos tetap opsional untuk kompatibilitas mundur."
}
},
"promptCaching": {
diff --git a/webview-ui/src/i18n/locales/it/settings.json b/webview-ui/src/i18n/locales/it/settings.json
index b97d96a61b..89b900de98 100644
--- a/webview-ui/src/i18n/locales/it/settings.json
+++ b/webview-ui/src/i18n/locales/it/settings.json
@@ -720,6 +720,10 @@
"ASSISTANT_MESSAGE_PARSER": {
"name": "Usa il nuovo parser dei messaggi",
"description": "Abilita il parser di messaggi in streaming sperimentale che migliora nettamente le risposte lunghe elaborando i messaggi in modo più efficiente."
+ },
+ "NEW_TASK_REQUIRE_TODOS": {
+ "name": "Richiedi elenco 'todos' per nuove attività",
+ "description": "Quando abilitato, lo strumento new_task richiederà la fornitura di un parametro todos. Ciò garantisce che tutte le nuove attività inizino con un elenco chiaro di obiettivi. Quando disabilitato (impostazione predefinita), il parametro todos rimane facoltativo per la compatibilità con le versioni precedenti."
}
},
"promptCaching": {
diff --git a/webview-ui/src/i18n/locales/ja/settings.json b/webview-ui/src/i18n/locales/ja/settings.json
index 8061418d38..d597f259d2 100644
--- a/webview-ui/src/i18n/locales/ja/settings.json
+++ b/webview-ui/src/i18n/locales/ja/settings.json
@@ -720,6 +720,10 @@
"ASSISTANT_MESSAGE_PARSER": {
"name": "新しいメッセージパーサーを使う",
"description": "実験的なストリーミングメッセージパーサーを有効にします。長い回答をより効率的に処理し、遅延を減らします。"
+ },
+ "NEW_TASK_REQUIRE_TODOS": {
+ "name": "新しいタスクには'todos'リストを必須にする",
+ "description": "有効にすると、new_taskツールはtodosパラメータの提供が必須になります。これにより、すべての新しいタスクが明確な目的のリストで開始されることが保証されます。無効(デフォルト)の場合、下位互換性のためにtodosパラメータはオプションのままです。"
}
},
"promptCaching": {
diff --git a/webview-ui/src/i18n/locales/ko/settings.json b/webview-ui/src/i18n/locales/ko/settings.json
index 08f3a8c4e7..381622ad74 100644
--- a/webview-ui/src/i18n/locales/ko/settings.json
+++ b/webview-ui/src/i18n/locales/ko/settings.json
@@ -720,6 +720,10 @@
"ASSISTANT_MESSAGE_PARSER": {
"name": "새 메시지 파서 사용",
"description": "실험적 스트리밍 메시지 파서를 활성화합니다. 긴 응답을 더 효율적으로 처리해 지연을 줄입니다."
+ },
+ "NEW_TASK_REQUIRE_TODOS": {
+ "name": "새 작업에 'todos' 목록 필요",
+ "description": "활성화하면 new_task 도구에 todos 매개변수를 제공해야 합니다. 이렇게 하면 모든 새 작업이 명확한 목표 목록으로 시작됩니다. 비활성화하면(기본값) 이전 버전과의 호환성을 위해 todos 매개변수는 선택 사항으로 유지됩니다."
}
},
"promptCaching": {
diff --git a/webview-ui/src/i18n/locales/nl/settings.json b/webview-ui/src/i18n/locales/nl/settings.json
index cc4eb2c90f..27811c0bca 100644
--- a/webview-ui/src/i18n/locales/nl/settings.json
+++ b/webview-ui/src/i18n/locales/nl/settings.json
@@ -720,6 +720,10 @@
"ASSISTANT_MESSAGE_PARSER": {
"name": "Nieuwe berichtparser gebruiken",
"description": "Schakel de experimentele streaming-berichtparser in die lange antwoorden sneller maakt door berichten efficiënter te verwerken."
+ },
+ "NEW_TASK_REQUIRE_TODOS": {
+ "name": "'todos'-lijst vereisen voor nieuwe taken",
+ "description": "Wanneer ingeschakeld, vereist de new_task-tool dat een todos-parameter wordt opgegeven. Dit zorgt ervoor dat alle nieuwe taken beginnen met een duidelijke lijst met doelstellingen. Wanneer uitgeschakeld (standaard), blijft de todos-parameter optioneel voor achterwaartse compatibiliteit."
}
},
"promptCaching": {
diff --git a/webview-ui/src/i18n/locales/pl/settings.json b/webview-ui/src/i18n/locales/pl/settings.json
index 107be09fdd..b101a98b78 100644
--- a/webview-ui/src/i18n/locales/pl/settings.json
+++ b/webview-ui/src/i18n/locales/pl/settings.json
@@ -720,6 +720,10 @@
"ASSISTANT_MESSAGE_PARSER": {
"name": "Użyj nowego parsera wiadomości",
"description": "Włącz eksperymentalny parser wiadomości w strumieniu, który przyspiesza długie odpowiedzi dzięki bardziej wydajnemu przetwarzaniu wiadomości."
+ },
+ "NEW_TASK_REQUIRE_TODOS": {
+ "name": "Wymagaj listy 'todos' dla nowych zadań",
+ "description": "Gdy włączone, narzędzie new_task będzie wymagało podania parametru todos. Zapewnia to, że wszystkie nowe zadania rozpoczynają się od jasnej listy celów. Gdy wyłączone (domyślnie), parametr todos pozostaje opcjonalny dla zachowania kompatybilności wstecznej."
}
},
"promptCaching": {
diff --git a/webview-ui/src/i18n/locales/pt-BR/settings.json b/webview-ui/src/i18n/locales/pt-BR/settings.json
index 54343a2fc5..20064675c8 100644
--- a/webview-ui/src/i18n/locales/pt-BR/settings.json
+++ b/webview-ui/src/i18n/locales/pt-BR/settings.json
@@ -720,6 +720,10 @@
"ASSISTANT_MESSAGE_PARSER": {
"name": "Usar o novo parser de mensagens",
"description": "Ativa o parser de mensagens em streaming experimental que acelera respostas longas ao processar as mensagens de forma mais eficiente."
+ },
+ "NEW_TASK_REQUIRE_TODOS": {
+ "name": "Exigir lista de 'todos' para novas tarefas",
+ "description": "Quando ativado, a ferramenta new_task exigirá que um parâmetro todos seja fornecido. Isso garante que todas as novas tarefas comecem com uma lista clara de objetivos. Quando desativado (padrão), o parâmetro todos permanece opcional para compatibilidade com versões anteriores."
}
},
"promptCaching": {
diff --git a/webview-ui/src/i18n/locales/ru/settings.json b/webview-ui/src/i18n/locales/ru/settings.json
index ac5fafdc17..4143a331fc 100644
--- a/webview-ui/src/i18n/locales/ru/settings.json
+++ b/webview-ui/src/i18n/locales/ru/settings.json
@@ -720,6 +720,10 @@
"ASSISTANT_MESSAGE_PARSER": {
"name": "Использовать новый парсер сообщений",
"description": "Включите экспериментальный потоковый парсер сообщений, который ускоряет длинные ответы благодаря более эффективной обработке сообщений."
+ },
+ "NEW_TASK_REQUIRE_TODOS": {
+ "name": "Требовать список 'todos' для новых задач",
+ "description": "Если включено, инструмент new_task будет требовать предоставления параметра todos. Это гарантирует, что все новые задачи начинаются с четкого списка целей. Когда отключено (по умолчанию), параметр todos остается необязательным для обратной совместимости."
}
},
"promptCaching": {
diff --git a/webview-ui/src/i18n/locales/tr/settings.json b/webview-ui/src/i18n/locales/tr/settings.json
index 0c4138e783..5de8363038 100644
--- a/webview-ui/src/i18n/locales/tr/settings.json
+++ b/webview-ui/src/i18n/locales/tr/settings.json
@@ -720,6 +720,10 @@
"ASSISTANT_MESSAGE_PARSER": {
"name": "Yeni mesaj ayrıştırıcıyı kullan",
"description": "Uzun yanıtları daha verimli işleyerek hızlandıran deneysel akış mesaj ayrıştırıcısını etkinleştir."
+ },
+ "NEW_TASK_REQUIRE_TODOS": {
+ "name": "Yeni görevler için 'todos' listesi gerektir",
+ "description": "Etkinleştirildiğinde, new_task aracı bir todos parametresi sağlanmasını gerektirir. Bu, tüm yeni görevlerin net bir hedef listesiyle başlamasını sağlar. Devre dışı bırakıldığında (varsayılan), todos parametresi geriye dönük uyumluluk için isteğe bağlı kalır."
}
},
"promptCaching": {
diff --git a/webview-ui/src/i18n/locales/vi/settings.json b/webview-ui/src/i18n/locales/vi/settings.json
index 5b2a5a6ef8..6fce8db6f4 100644
--- a/webview-ui/src/i18n/locales/vi/settings.json
+++ b/webview-ui/src/i18n/locales/vi/settings.json
@@ -720,6 +720,10 @@
"ASSISTANT_MESSAGE_PARSER": {
"name": "Dùng bộ phân tích tin nhắn mới",
"description": "Bật bộ phân tích tin nhắn streaming thử nghiệm. Tính năng này tăng tốc phản hồi dài bằng cách xử lý tin nhắn hiệu quả hơn."
+ },
+ "NEW_TASK_REQUIRE_TODOS": {
+ "name": "Yêu cầu danh sách 'todos' cho các nhiệm vụ mới",
+ "description": "Khi được bật, công cụ new_task sẽ yêu cầu cung cấp tham số todos. Điều này đảm bảo tất cả các nhiệm vụ mới bắt đầu với một danh sách mục tiêu rõ ràng. Khi bị tắt (mặc định), tham số todos vẫn là tùy chọn để tương thích ngược."
}
},
"promptCaching": {
diff --git a/webview-ui/src/i18n/locales/zh-CN/settings.json b/webview-ui/src/i18n/locales/zh-CN/settings.json
index 9e56ba74ee..948693768c 100644
--- a/webview-ui/src/i18n/locales/zh-CN/settings.json
+++ b/webview-ui/src/i18n/locales/zh-CN/settings.json
@@ -720,6 +720,10 @@
"ASSISTANT_MESSAGE_PARSER": {
"name": "使用新的消息解析器",
"description": "启用实验性的流式消息解析器。通过更高效地处理消息,可显著提升长回复的性能。"
+ },
+ "NEW_TASK_REQUIRE_TODOS": {
+ "name": "要求新任务提供 'todos' 列表",
+ "description": "启用后,new_task 工具将需要提供 todos 参数。这可以确保所有新任务都以明确的目标列表开始。禁用时(默认),todos 参数保持可选,以实现向后兼容。"
}
},
"promptCaching": {
diff --git a/webview-ui/src/i18n/locales/zh-TW/settings.json b/webview-ui/src/i18n/locales/zh-TW/settings.json
index 6bfab1435c..8dd76aee3f 100644
--- a/webview-ui/src/i18n/locales/zh-TW/settings.json
+++ b/webview-ui/src/i18n/locales/zh-TW/settings.json
@@ -720,6 +720,10 @@
"ASSISTANT_MESSAGE_PARSER": {
"name": "使用全新訊息解析器",
"description": "啟用實驗性的串流訊息解析器。透過更有效率地處理訊息,能顯著提升長回覆的效能。"
+ },
+ "NEW_TASK_REQUIRE_TODOS": {
+ "name": "要求新工作提供 'todos' 列表",
+ "description": "啟用後,new_task 工具將需要提供 todos 參數。這可以確保所有新工作都以明確的目標列表開始。停用時(預設),todos 參數保持可選,以實現向後相容。"
}
},
"promptCaching": {