Просмотр исходного кода

feat: add optional todos parameter to new_task tool with experimental setting (#6329) (#6775)

* feat: add optional todos parameter to new_task tool with experimental setting (#6329)

- Add optional todos parameter to new_task tool for hierarchical task planning
- Implement experimental setting to optionally require todos parameter
- Add clean state-based UI rendering to avoid spurious messages
- Export and reuse parseMarkdownChecklist function
- Add comprehensive test coverage for both optional and required modes
- Maintain full backward compatibility (todos optional by default)

* fix: update new_task tool example to include todos parameter

- Updated the example in tool-use.ts to show the todos parameter
- This prevents AI confusion about whether todos is a valid parameter
- The example now demonstrates the complete tool usage pattern

* fix: make new_task tool definition dynamic based on experimental setting

- Tool description now changes based on newTaskRequireTodos setting
- When disabled: shows todos as (optional)
- When enabled: shows todos as (required) with no mention of configuration
- Added tests to verify dynamic behavior
- Ensures AI models get unambiguous instructions based on current settings

* fix: add translations for newTaskRequireTodos experimental setting

- Added translations for all 17 supported languages
- Ensures consistent UI experience across all locales

* test: update snapshots for new_task tool example with todos parameter

- Updated 13 snapshot files to reflect the new tool-use example
- All tests now pass with the updated example format

* Update src/core/tools/newTaskTool.ts

Co-authored-by: Copilot <[email protected]>

* fix: address PR review comments

- Replace any[] with TodoItem[] type in ExtensionStateContext.tsx for better type safety
- Remove redundant initialTodos parameter from startTask call in Task.ts (todos already set in constructor)
- Improve code clarity in newTaskTool.ts by checking provider reference early and reusing state

# Conflicts:
#	src/core/task/Task.ts
#	webview-ui/src/context/ExtensionStateContext.tsx

* fix: revert order of operations in startTask to fix integration test timeout

The change in order of operations (calling say() before postStateToWebview()) was causing the XML content file test to timeout. Reverting to the original order fixes the issue.

* fix: hide todos parameter from new_task tool prompt when experiment is disabled

- Modified getNewTaskDescription to completely omit todos parameter when experiment is off
- Updated tests to verify todos parameter is not shown at all when disabled
- Ensures tool prompt remains unchanged when experimental setting is disabled
- Maintains backward compatibility while providing cleaner prompt interface

* fix: update snapshots for new_task tool todos parameter

- Updated snapshots in add-custom-instructions.spec.ts
- Updated snapshots in system-prompt.spec.ts
- All tests now passing with the new todos parameter documentation

* feat: move newTaskRequireTodos from experimental to VSCode settings

- Added newTaskRequireTodos as a VSCode configuration property in src/package.json
- Added description in src/package.nls.json
- Updated newTaskTool.ts to read from VSCode configuration instead of experiments
- Removed NEW_TASK_REQUIRE_TODOS from experimental settings in src/shared/experiments.ts
- Removed newTaskRequireTodos from packages/types/src/experiment.ts
- Updated tests to use VSCode configuration mocking instead of experiments
- Removed references from experiments test file
- Maintains backward compatibility (defaults to false)

* fix: make new_task tool description dynamically reflect VSCode setting

- Updated new-task.ts to check args.settings instead of args.experiments
- Added newTaskRequireTodos to SystemPromptSettings interface
- Pass newTaskRequireTodos setting through Task.ts and generateSystemPrompt.ts
- Updated all related tests to use settings instead of experiments
- Fixed TypeScript errors in test files by adding newTaskRequireTodos property

This ensures the tool description correctly shows todos parameter as required/optional
based on the VSCode setting value, fixing the issue where Roo would try to use
new_task without the todos parameter when it was required.

---------

Co-authored-by: Copilot <[email protected]>
Co-authored-by: Merge Resolver <[email protected]>
Co-authored-by: Roo Code <[email protected]>
Hannes Rudolph 4 месяцев назад
Родитель
Сommit
4664955127
51 измененных файлов с 929 добавлено и 28 удалено
  1. 6 1
      packages/types/src/experiment.ts
  2. 6 0
      src/core/prompts/__tests__/__snapshots__/add-custom-instructions/architect-mode-prompt.snap
  3. 6 0
      src/core/prompts/__tests__/__snapshots__/add-custom-instructions/ask-mode-prompt.snap
  4. 6 0
      src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-disabled.snap
  5. 6 0
      src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-enabled.snap
  6. 6 0
      src/core/prompts/__tests__/__snapshots__/add-custom-instructions/partial-reads-enabled.snap
  7. 6 0
      src/core/prompts/__tests__/__snapshots__/system-prompt/consistent-system-prompt.snap
  8. 6 0
      src/core/prompts/__tests__/__snapshots__/system-prompt/with-computer-use-support.snap
  9. 6 0
      src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-false.snap
  10. 6 0
      src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-true.snap
  11. 6 0
      src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-undefined.snap
  12. 6 0
      src/core/prompts/__tests__/__snapshots__/system-prompt/with-different-viewport-size.snap
  13. 6 0
      src/core/prompts/__tests__/__snapshots__/system-prompt/with-mcp-hub-provided.snap
  14. 6 0
      src/core/prompts/__tests__/__snapshots__/system-prompt/with-undefined-mcp-hub.snap
  15. 3 0
      src/core/prompts/__tests__/system-prompt.spec.ts
  16. 64 8
      src/core/prompts/sections/__tests__/custom-instructions.spec.ts
  17. 6 0
      src/core/prompts/sections/tool-use.ts
  18. 117 0
      src/core/prompts/tools/__tests__/new-task.spec.ts
  19. 40 2
      src/core/prompts/tools/new-task.ts
  20. 1 0
      src/core/prompts/types.ts
  21. 14 0
      src/core/task/Task.ts
  22. 440 2
      src/core/tools/__tests__/newTaskTool.spec.ts
  23. 48 10
      src/core/tools/newTaskTool.ts
  24. 1 1
      src/core/tools/updateTodoListTool.ts
  25. 8 1
      src/core/webview/ClineProvider.ts
  26. 3 0
      src/core/webview/generateSystemPrompt.ts
  27. 5 0
      src/package.json
  28. 2 1
      src/package.nls.json
  29. 2 0
      src/shared/ExtensionMessage.ts
  30. 1 1
      src/shared/tools.ts
  31. 14 1
      webview-ui/src/components/chat/ChatView.tsx
  32. 2 0
      webview-ui/src/context/ExtensionStateContext.tsx
  33. 2 0
      webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx
  34. 4 0
      webview-ui/src/i18n/locales/ca/settings.json
  35. 4 0
      webview-ui/src/i18n/locales/de/settings.json
  36. 4 0
      webview-ui/src/i18n/locales/en/settings.json
  37. 4 0
      webview-ui/src/i18n/locales/es/settings.json
  38. 4 0
      webview-ui/src/i18n/locales/fr/settings.json
  39. 4 0
      webview-ui/src/i18n/locales/hi/settings.json
  40. 4 0
      webview-ui/src/i18n/locales/id/settings.json
  41. 4 0
      webview-ui/src/i18n/locales/it/settings.json
  42. 4 0
      webview-ui/src/i18n/locales/ja/settings.json
  43. 4 0
      webview-ui/src/i18n/locales/ko/settings.json
  44. 4 0
      webview-ui/src/i18n/locales/nl/settings.json
  45. 4 0
      webview-ui/src/i18n/locales/pl/settings.json
  46. 4 0
      webview-ui/src/i18n/locales/pt-BR/settings.json
  47. 4 0
      webview-ui/src/i18n/locales/ru/settings.json
  48. 4 0
      webview-ui/src/i18n/locales/tr/settings.json
  49. 4 0
      webview-ui/src/i18n/locales/vi/settings.json
  50. 4 0
      webview-ui/src/i18n/locales/zh-CN/settings.json
  51. 4 0
      webview-ui/src/i18n/locales/zh-TW/settings.json

+ 6 - 1
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)
 

+ 6 - 0
src/core/prompts/__tests__/__snapshots__/add-custom-instructions/architect-mode-prompt.snap

@@ -27,6 +27,12 @@ For example, to use the new_task tool:
 <new_task>
 <mode>code</mode>
 <message>Implement a new feature for the application.</message>
+<todos>
+[ ] Design the feature architecture
+[ ] Implement core functionality
+[ ] Add error handling
+[ ] Write tests
+</todos>
 </new_task>
 
 Always use the actual tool name as the XML tag name for proper parsing and execution.

+ 6 - 0
src/core/prompts/__tests__/__snapshots__/add-custom-instructions/ask-mode-prompt.snap

@@ -27,6 +27,12 @@ For example, to use the new_task tool:
 <new_task>
 <mode>code</mode>
 <message>Implement a new feature for the application.</message>
+<todos>
+[ ] Design the feature architecture
+[ ] Implement core functionality
+[ ] Add error handling
+[ ] Write tests
+</todos>
 </new_task>
 
 Always use the actual tool name as the XML tag name for proper parsing and execution.

+ 6 - 0
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:
 <new_task>
 <mode>code</mode>
 <message>Implement a new feature for the application.</message>
+<todos>
+[ ] Design the feature architecture
+[ ] Implement core functionality
+[ ] Add error handling
+[ ] Write tests
+</todos>
 </new_task>
 
 Always use the actual tool name as the XML tag name for proper parsing and execution.

+ 6 - 0
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:
 <new_task>
 <mode>code</mode>
 <message>Implement a new feature for the application.</message>
+<todos>
+[ ] Design the feature architecture
+[ ] Implement core functionality
+[ ] Add error handling
+[ ] Write tests
+</todos>
 </new_task>
 
 Always use the actual tool name as the XML tag name for proper parsing and execution.

+ 6 - 0
src/core/prompts/__tests__/__snapshots__/add-custom-instructions/partial-reads-enabled.snap

@@ -27,6 +27,12 @@ For example, to use the new_task tool:
 <new_task>
 <mode>code</mode>
 <message>Implement a new feature for the application.</message>
+<todos>
+[ ] Design the feature architecture
+[ ] Implement core functionality
+[ ] Add error handling
+[ ] Write tests
+</todos>
 </new_task>
 
 Always use the actual tool name as the XML tag name for proper parsing and execution.

+ 6 - 0
src/core/prompts/__tests__/__snapshots__/system-prompt/consistent-system-prompt.snap

@@ -27,6 +27,12 @@ For example, to use the new_task tool:
 <new_task>
 <mode>code</mode>
 <message>Implement a new feature for the application.</message>
+<todos>
+[ ] Design the feature architecture
+[ ] Implement core functionality
+[ ] Add error handling
+[ ] Write tests
+</todos>
 </new_task>
 
 Always use the actual tool name as the XML tag name for proper parsing and execution.

+ 6 - 0
src/core/prompts/__tests__/__snapshots__/system-prompt/with-computer-use-support.snap

@@ -27,6 +27,12 @@ For example, to use the new_task tool:
 <new_task>
 <mode>code</mode>
 <message>Implement a new feature for the application.</message>
+<todos>
+[ ] Design the feature architecture
+[ ] Implement core functionality
+[ ] Add error handling
+[ ] Write tests
+</todos>
 </new_task>
 
 Always use the actual tool name as the XML tag name for proper parsing and execution.

+ 6 - 0
src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-false.snap

@@ -27,6 +27,12 @@ For example, to use the new_task tool:
 <new_task>
 <mode>code</mode>
 <message>Implement a new feature for the application.</message>
+<todos>
+[ ] Design the feature architecture
+[ ] Implement core functionality
+[ ] Add error handling
+[ ] Write tests
+</todos>
 </new_task>
 
 Always use the actual tool name as the XML tag name for proper parsing and execution.

+ 6 - 0
src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-true.snap

@@ -27,6 +27,12 @@ For example, to use the new_task tool:
 <new_task>
 <mode>code</mode>
 <message>Implement a new feature for the application.</message>
+<todos>
+[ ] Design the feature architecture
+[ ] Implement core functionality
+[ ] Add error handling
+[ ] Write tests
+</todos>
 </new_task>
 
 Always use the actual tool name as the XML tag name for proper parsing and execution.

+ 6 - 0
src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-undefined.snap

@@ -27,6 +27,12 @@ For example, to use the new_task tool:
 <new_task>
 <mode>code</mode>
 <message>Implement a new feature for the application.</message>
+<todos>
+[ ] Design the feature architecture
+[ ] Implement core functionality
+[ ] Add error handling
+[ ] Write tests
+</todos>
 </new_task>
 
 Always use the actual tool name as the XML tag name for proper parsing and execution.

+ 6 - 0
src/core/prompts/__tests__/__snapshots__/system-prompt/with-different-viewport-size.snap

@@ -27,6 +27,12 @@ For example, to use the new_task tool:
 <new_task>
 <mode>code</mode>
 <message>Implement a new feature for the application.</message>
+<todos>
+[ ] Design the feature architecture
+[ ] Implement core functionality
+[ ] Add error handling
+[ ] Write tests
+</todos>
 </new_task>
 
 Always use the actual tool name as the XML tag name for proper parsing and execution.

+ 6 - 0
src/core/prompts/__tests__/__snapshots__/system-prompt/with-mcp-hub-provided.snap

@@ -27,6 +27,12 @@ For example, to use the new_task tool:
 <new_task>
 <mode>code</mode>
 <message>Implement a new feature for the application.</message>
+<todos>
+[ ] Design the feature architecture
+[ ] Implement core functionality
+[ ] Add error handling
+[ ] Write tests
+</todos>
 </new_task>
 
 Always use the actual tool name as the XML tag name for proper parsing and execution.

+ 6 - 0
src/core/prompts/__tests__/__snapshots__/system-prompt/with-undefined-mcp-hub.snap

@@ -27,6 +27,12 @@ For example, to use the new_task tool:
 <new_task>
 <mode>code</mode>
 <message>Implement a new feature for the application.</message>
+<todos>
+[ ] Design the feature architecture
+[ ] Implement core functionality
+[ ] Add error handling
+[ ] Write tests
+</todos>
 </new_task>
 
 Always use the actual tool name as the XML tag name for proper parsing and execution.

+ 3 - 0
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(

+ 64 - 8
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

+ 6 - 0
src/core/prompts/sections/tool-use.ts

@@ -20,6 +20,12 @@ For example, to use the new_task tool:
 <new_task>
 <mode>code</mode>
 <message>Implement a new feature for the application.</message>
+<todos>
+[ ] Design the feature architecture
+[ ] Implement core functionality
+[ ] Add error handling
+[ ] Write tests
+</todos>
 </new_task>
 
 Always use the actual tool name as the XML tag name for proper parsing and execution.`

+ 117 - 0
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("<todos>")
+		expect(description).not.toContain("</todos>")
+
+		// 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("<todos>")
+		expect(description).toContain("</todos>")
+		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("<todos>")
+		expect(description).not.toContain("</todos>")
+	})
+
+	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("<todos>")
+		expect(description).not.toContain("</todos>")
+	})
+
+	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 = /<todos>\s*\[\s*\]\s*Set up auth middleware/s
+		expect(descriptionOff).not.toMatch(todosPattern)
+		expect(descriptionOff).not.toContain("<todos>")
+
+		// When setting is on, should include todos in example
+		expect(descriptionOn).toMatch(todosPattern)
+		expect(descriptionOn).toContain("<todos>")
+	})
+})

+ 40 - 2
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:
 <mode>code</mode>
 <message>Implement a new feature for the application.</message>
 </new_task>
+`
+	}
+
+	// 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:
+<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 user authentication</message>
+<todos>
+[ ] Set up auth middleware
+[ ] Create login endpoint
+[ ] Add session management
+[ ] Write tests
+</todos>
+</new_task>
 `
 }

+ 1 - 0
src/core/prompts/types.ts

@@ -5,4 +5,5 @@ export interface SystemPromptSettings {
 	maxConcurrentFileReads: number
 	todoListEnabled: boolean
 	useAgentRules: boolean
+	newTaskRequireTodos: boolean
 }

+ 14 - 0
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<TaskEvents> implements TaskLike {
@@ -290,6 +291,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
 		parentTask,
 		taskNumber = -1,
 		onCreated,
+		initialTodos,
 	}: TaskOptions) {
 		super()
 
@@ -373,6 +375,11 @@ export class Task extends EventEmitter<TaskEvents> 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<TaskEvents> 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<TaskEvents> implements TaskLike {
 					maxConcurrentFileReads: maxConcurrentFileReads ?? 5,
 					todoListEnabled: apiConfiguration?.todoListEnabled ?? true,
 					useAgentRules: vscode.workspace.getConfiguration("roo-cline").get<boolean>("useAgentRules") ?? true,
+					newTaskRequireTodos: vscode.workspace
+						.getConfiguration("roo-cline")
+						.get<boolean>("newTaskRequireTodos", false),
 				},
 				undefined, // todoList
 				this.api.getModel().id,

+ 440 - 2
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<AskApproval>()
 const mockHandleError = vi.fn<HandleError>()
 const mockPushToolResult = vi.fn()
 const mockRemoveClosingTag = vi.fn((_name: string, value: string | undefined) => value ?? "")
-const mockCreateTask = vi.fn<() => Promise<MockClineInstance>>().mockResolvedValue({ taskId: "mock-subtask-id" })
+const mockCreateTask = vi
+	.fn<(text?: string, images?: string[], parentTask?: any, options?: any) => Promise<MockClineInstance>>()
+	.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
 })

+ 48 - 10
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<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) {
+				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.

+ 1 - 1
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/)

+ 8 - 1
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),

+ 3 - 0
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<boolean>("useAgentRules") ?? true,
+			newTaskRequireTodos: vscode.workspace
+				.getConfiguration("roo-cline")
+				.get<boolean>("newTaskRequireTodos", false),
 		},
 	)
 

+ 5 - 0
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%"
 				}
 			}
 		}

+ 2 - 1
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"
 }

+ 2 - 0
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

+ 1 - 1
src/shared/tools.ts

@@ -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 {

+ 14 - 1
webview-ui/src/components/chat/ChatView.tsx

@@ -87,6 +87,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
 	const {
 		clineMessages: messages,
 		currentTaskItem,
+		currentTaskTodos,
 		taskHistory,
 		apiConfiguration,
 		organizationAllowList,
@@ -144,8 +145,20 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
 	const task = useMemo(() => 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])
 

+ 2 - 0
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[]

+ 2 - 0
webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx

@@ -230,6 +230,7 @@ describe("mergeExtensionState", () => {
 				multiFileApplyDiff: true,
 				preventFocusDisruption: false,
 				assistantMessageParser: false,
+				newTaskRequireTodos: false,
 			} as Record<ExperimentId, boolean>,
 		}
 
@@ -248,6 +249,7 @@ describe("mergeExtensionState", () => {
 			multiFileApplyDiff: true,
 			preventFocusDisruption: false,
 			assistantMessageParser: false,
+			newTaskRequireTodos: false,
 		})
 	})
 })

+ 4 - 0
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": {

+ 4 - 0
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": {

+ 4 - 0
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": {

+ 4 - 0
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": {

+ 4 - 0
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": {

+ 4 - 0
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": {

+ 4 - 0
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": {

+ 4 - 0
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": {

+ 4 - 0
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": {

+ 4 - 0
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": {

+ 4 - 0
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": {

+ 4 - 0
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": {

+ 4 - 0
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": {

+ 4 - 0
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": {

+ 4 - 0
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": {

+ 4 - 0
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": {

+ 4 - 0
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": {

+ 4 - 0
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": {