Browse Source

Merge pull request #1 from cannuri/update-from-upstream

Update from upstream
cannuri 11 months ago
parent
commit
58532bc6db

+ 5 - 0
.changeset/empty-bees-suffer.md

@@ -0,0 +1,5 @@
+---
+"roo-cline": patch
+---
+
+Revert tool progress for now

+ 5 - 0
.changeset/sixty-ants-begin.md

@@ -0,0 +1,5 @@
+---
+"roo-cline": patch
+---
+
+v3.8.4

+ 12 - 0
CHANGELOG.md

@@ -1,5 +1,17 @@
 # Roo Code Changelog
 # Roo Code Changelog
 
 
+## [3.8.3] - 2025-03-09
+
+- Fix VS Code LM API model picker truncation issue
+
+## [3.8.2] - 2025-03-08
+
+- Create an auto-approval toggle for subtask creation and completion (thanks @shaybc!)
+- Show a progress indicator when using the multi-diff editing strategy (thanks @qdaxb!)
+- Add o3-mini support to the OpenAI-compatible provider (thanks @yt3trees!)
+- Fix encoding issue where unreadable characters were sometimes getting added to the beginning of files
+- Fix issue where settings dropdowns were getting truncated in some cases
+
 ## [3.8.1] - 2025-03-07
 ## [3.8.1] - 2025-03-07
 
 
 - Show the reserved output tokens in the context window visualization
 - Show the reserved output tokens in the context window visualization

+ 2 - 1
jest.config.js

@@ -30,9 +30,10 @@ module.exports = {
 		"^strip-ansi$": "<rootDir>/src/__mocks__/strip-ansi.js",
 		"^strip-ansi$": "<rootDir>/src/__mocks__/strip-ansi.js",
 		"^default-shell$": "<rootDir>/src/__mocks__/default-shell.js",
 		"^default-shell$": "<rootDir>/src/__mocks__/default-shell.js",
 		"^os-name$": "<rootDir>/src/__mocks__/os-name.js",
 		"^os-name$": "<rootDir>/src/__mocks__/os-name.js",
+		"^strip-bom$": "<rootDir>/src/__mocks__/strip-bom.js",
 	},
 	},
 	transformIgnorePatterns: [
 	transformIgnorePatterns: [
-		"node_modules/(?!(@modelcontextprotocol|delay|p-wait-for|globby|serialize-error|strip-ansi|default-shell|os-name)/)",
+		"node_modules/(?!(@modelcontextprotocol|delay|p-wait-for|globby|serialize-error|strip-ansi|default-shell|os-name|strip-bom)/)",
 	],
 	],
 	roots: ["<rootDir>/src", "<rootDir>/webview-ui/src"],
 	roots: ["<rootDir>/src", "<rootDir>/webview-ui/src"],
 	modulePathIgnorePatterns: [".vscode-test"],
 	modulePathIgnorePatterns: [".vscode-test"],

+ 19 - 7
package-lock.json

@@ -1,12 +1,12 @@
 {
 {
 	"name": "roo-cline",
 	"name": "roo-cline",
-	"version": "3.8.1",
+	"version": "3.8.3",
 	"lockfileVersion": 3,
 	"lockfileVersion": 3,
 	"requires": true,
 	"requires": true,
 	"packages": {
 	"packages": {
 		"": {
 		"": {
 			"name": "roo-cline",
 			"name": "roo-cline",
-			"version": "3.8.1",
+			"version": "3.8.3",
 			"dependencies": {
 			"dependencies": {
 				"@anthropic-ai/bedrock-sdk": "^0.10.2",
 				"@anthropic-ai/bedrock-sdk": "^0.10.2",
 				"@anthropic-ai/sdk": "^0.37.0",
 				"@anthropic-ai/sdk": "^0.37.0",
@@ -51,6 +51,7 @@
 				"sound-play": "^1.1.0",
 				"sound-play": "^1.1.0",
 				"string-similarity": "^4.0.4",
 				"string-similarity": "^4.0.4",
 				"strip-ansi": "^7.1.0",
 				"strip-ansi": "^7.1.0",
+				"strip-bom": "^5.0.0",
 				"tmp": "^0.2.3",
 				"tmp": "^0.2.3",
 				"tree-sitter-wasms": "^0.1.11",
 				"tree-sitter-wasms": "^0.1.11",
 				"turndown": "^7.2.0",
 				"turndown": "^7.2.0",
@@ -10782,6 +10783,15 @@
 				"node": ">=8"
 				"node": ">=8"
 			}
 			}
 		},
 		},
+		"node_modules/jest-runtime/node_modules/strip-bom": {
+			"version": "4.0.0",
+			"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz",
+			"integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==",
+			"dev": true,
+			"engines": {
+				"node": ">=8"
+			}
+		},
 		"node_modules/jest-simple-dot-reporter": {
 		"node_modules/jest-simple-dot-reporter": {
 			"version": "1.0.5",
 			"version": "1.0.5",
 			"resolved": "https://registry.npmjs.org/jest-simple-dot-reporter/-/jest-simple-dot-reporter-1.0.5.tgz",
 			"resolved": "https://registry.npmjs.org/jest-simple-dot-reporter/-/jest-simple-dot-reporter-1.0.5.tgz",
@@ -14170,12 +14180,14 @@
 			}
 			}
 		},
 		},
 		"node_modules/strip-bom": {
 		"node_modules/strip-bom": {
-			"version": "4.0.0",
-			"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz",
-			"integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==",
-			"dev": true,
+			"version": "5.0.0",
+			"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-5.0.0.tgz",
+			"integrity": "sha512-p+byADHF7SzEcVnLvc/r3uognM1hUhObuHXxJcgLCfD194XAkaLbjq3Wzb0N5G2tgIjH0dgT708Z51QxMeu60A==",
 			"engines": {
 			"engines": {
-				"node": ">=8"
+				"node": ">=12"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/sindresorhus"
 			}
 			}
 		},
 		},
 		"node_modules/strip-final-newline": {
 		"node_modules/strip-final-newline": {

+ 3 - 2
package.json

@@ -3,7 +3,7 @@
 	"displayName": "Roo Code (prev. Roo Cline)",
 	"displayName": "Roo Code (prev. Roo Cline)",
 	"description": "A whole dev team of AI agents in your editor.",
 	"description": "A whole dev team of AI agents in your editor.",
 	"publisher": "RooVeterinaryInc",
 	"publisher": "RooVeterinaryInc",
-	"version": "3.8.1",
+	"version": "3.8.3",
 	"icon": "assets/icons/rocket.png",
 	"icon": "assets/icons/rocket.png",
 	"galleryBanner": {
 	"galleryBanner": {
 		"color": "#617A91",
 		"color": "#617A91",
@@ -265,8 +265,8 @@
 		"@anthropic-ai/sdk": "^0.37.0",
 		"@anthropic-ai/sdk": "^0.37.0",
 		"@anthropic-ai/vertex-sdk": "^0.7.0",
 		"@anthropic-ai/vertex-sdk": "^0.7.0",
 		"@aws-sdk/client-bedrock-runtime": "^3.706.0",
 		"@aws-sdk/client-bedrock-runtime": "^3.706.0",
-		"@google/generative-ai": "^0.18.0",
 		"@google-cloud/vertexai": "^1.9.3",
 		"@google-cloud/vertexai": "^1.9.3",
+		"@google/generative-ai": "^0.18.0",
 		"@mistralai/mistralai": "^1.3.6",
 		"@mistralai/mistralai": "^1.3.6",
 		"@modelcontextprotocol/sdk": "^1.0.1",
 		"@modelcontextprotocol/sdk": "^1.0.1",
 		"@types/clone-deep": "^4.0.4",
 		"@types/clone-deep": "^4.0.4",
@@ -304,6 +304,7 @@
 		"sound-play": "^1.1.0",
 		"sound-play": "^1.1.0",
 		"string-similarity": "^4.0.4",
 		"string-similarity": "^4.0.4",
 		"strip-ansi": "^7.1.0",
 		"strip-ansi": "^7.1.0",
+		"strip-bom": "^5.0.0",
 		"tmp": "^0.2.3",
 		"tmp": "^0.2.3",
 		"tree-sitter-wasms": "^0.1.11",
 		"tree-sitter-wasms": "^0.1.11",
 		"turndown": "^7.2.0",
 		"turndown": "^7.2.0",

+ 13 - 0
src/__mocks__/strip-bom.js

@@ -0,0 +1,13 @@
+// Mock implementation of strip-bom
+module.exports = function stripBom(string) {
+	if (typeof string !== "string") {
+		throw new TypeError("Expected a string")
+	}
+
+	// Removes UTF-8 BOM
+	if (string.charCodeAt(0) === 0xfeff) {
+		return string.slice(1)
+	}
+
+	return string
+}

+ 68 - 0
src/api/providers/openai.ts

@@ -66,6 +66,11 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
 		const deepseekReasoner = modelId.includes("deepseek-reasoner")
 		const deepseekReasoner = modelId.includes("deepseek-reasoner")
 		const ark = modelUrl.includes(".volces.com")
 		const ark = modelUrl.includes(".volces.com")
 
 
+		if (modelId.startsWith("o3-mini")) {
+			yield* this.handleO3FamilyMessage(modelId, systemPrompt, messages)
+			return
+		}
+
 		if (this.options.openAiStreamingEnabled ?? true) {
 		if (this.options.openAiStreamingEnabled ?? true) {
 			const systemMessage: OpenAI.Chat.ChatCompletionSystemMessageParam = {
 			const systemMessage: OpenAI.Chat.ChatCompletionSystemMessageParam = {
 				role: "system",
 				role: "system",
@@ -169,6 +174,69 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
 			throw error
 			throw error
 		}
 		}
 	}
 	}
+
+	private async *handleO3FamilyMessage(
+		modelId: string,
+		systemPrompt: string,
+		messages: Anthropic.Messages.MessageParam[],
+	): ApiStream {
+		if (this.options.openAiStreamingEnabled ?? true) {
+			const stream = await this.client.chat.completions.create({
+				model: "o3-mini",
+				messages: [
+					{
+						role: "developer",
+						content: `Formatting re-enabled\n${systemPrompt}`,
+					},
+					...convertToOpenAiMessages(messages),
+				],
+				stream: true,
+				stream_options: { include_usage: true },
+				reasoning_effort: this.getModel().info.reasoningEffort,
+			})
+
+			yield* this.handleStreamResponse(stream)
+		} else {
+			const requestOptions: OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming = {
+				model: modelId,
+				messages: [
+					{
+						role: "developer",
+						content: `Formatting re-enabled\n${systemPrompt}`,
+					},
+					...convertToOpenAiMessages(messages),
+				],
+			}
+
+			const response = await this.client.chat.completions.create(requestOptions)
+
+			yield {
+				type: "text",
+				text: response.choices[0]?.message.content || "",
+			}
+			yield this.processUsageMetrics(response.usage)
+		}
+	}
+
+	private async *handleStreamResponse(stream: AsyncIterable<OpenAI.Chat.Completions.ChatCompletionChunk>): ApiStream {
+		for await (const chunk of stream) {
+			const delta = chunk.choices[0]?.delta
+			if (delta?.content) {
+				yield {
+					type: "text",
+					text: delta.content,
+				}
+			}
+
+			if (chunk.usage) {
+				yield {
+					type: "usage",
+					inputTokens: chunk.usage.prompt_tokens || 0,
+					outputTokens: chunk.usage.completion_tokens || 0,
+				}
+			}
+		}
+	}
 }
 }
 
 
 export async function getOpenAiModels(baseUrl?: string, apiKey?: string) {
 export async function getOpenAiModels(baseUrl?: string, apiKey?: string) {

+ 21 - 12
src/core/Cline.ts

@@ -1417,6 +1417,18 @@ export class Cline {
 					return true
 					return true
 				}
 				}
 
 
+				const askFinishSubTaskApproval = async () => {
+					// ask the user to approve this task has completed, and he has reviewd it, and we can declare task is finished
+					// and return control to the parent task to continue running the rest of the sub-tasks
+					const toolMessage = JSON.stringify({
+						tool: "finishTask",
+						content:
+							"Subtask completed! You can review the results and suggest any corrections or next steps. If everything looks good, confirm to return the result to the parent task.",
+					})
+
+					return await askApproval("tool", toolMessage)
+				}
+
 				const handleError = async (action: string, error: Error) => {
 				const handleError = async (action: string, error: Error) => {
 					const errorString = `Error ${action}: ${JSON.stringify(serializeError(error))}`
 					const errorString = `Error ${action}: ${JSON.stringify(serializeError(error))}`
 					await this.say(
 					await this.say(
@@ -2945,13 +2957,6 @@ export class Cline {
 										// havent sent a command message yet so first send completion_result then command
 										// havent sent a command message yet so first send completion_result then command
 										await this.say("completion_result", result, undefined, false)
 										await this.say("completion_result", result, undefined, false)
 										telemetryService.captureTaskCompleted(this.taskId)
 										telemetryService.captureTaskCompleted(this.taskId)
-										if (this.isSubTask) {
-											// tell the provider to remove the current subtask and resume the previous task in the stack
-											await this.providerRef
-												.deref()
-												?.finishSubTask(`Task complete: ${lastMessage?.text}`)
-											break
-										}
 									}
 									}
 
 
 									// complete command message
 									// complete command message
@@ -2970,13 +2975,17 @@ export class Cline {
 								} else {
 								} else {
 									await this.say("completion_result", result, undefined, false)
 									await this.say("completion_result", result, undefined, false)
 									telemetryService.captureTaskCompleted(this.taskId)
 									telemetryService.captureTaskCompleted(this.taskId)
-									if (this.isSubTask) {
-										// tell the provider to remove the current subtask and resume the previous task in the stack
-										await this.providerRef
-											.deref()
-											?.finishSubTask(`Task complete: ${lastMessage?.text}`)
+								}
+
+								if (this.isSubTask) {
+									const didApprove = await askFinishSubTaskApproval()
+									if (!didApprove) {
 										break
 										break
 									}
 									}
+
+									// tell the provider to remove the current subtask and resume the previous task in the stack
+									await this.providerRef.deref()?.finishSubTask(`Task complete: ${lastMessage?.text}`)
+									break
 								}
 								}
 
 
 								// we already sent completion_result says, an empty string asks relinquishes control over button and field
 								// we already sent completion_result says, an empty string asks relinquishes control over button and field

+ 12 - 2
src/core/prompts/sections/modes.ts

@@ -11,12 +11,19 @@ export async function getModesSection(context: vscode.ExtensionContext): Promise
 	// Get all modes with their overrides from extension state
 	// Get all modes with their overrides from extension state
 	const allModes = await getAllModesWithPrompts(context)
 	const allModes = await getAllModesWithPrompts(context)
 
 
-	return `====
+	// Get enableCustomModeCreation setting from extension state
+	const shouldEnableCustomModeCreation = await context.globalState.get<boolean>("enableCustomModeCreation") ?? true
+
+	let modesContent = `====
 
 
 MODES
 MODES
 
 
 - These are the currently available modes:
 - These are the currently available modes:
-${allModes.map((mode: ModeConfig) => `  * "${mode.name}" mode (${mode.slug}) - ${mode.roleDefinition.split(".")[0]}`).join("\n")}
+${allModes.map((mode: ModeConfig) => `  * "${mode.name}" mode (${mode.slug}) - ${mode.roleDefinition.split(".")[0]}`).join("\n")}`
+
+	// Only include custom modes documentation if the feature is enabled
+	if (shouldEnableCustomModeCreation) {
+		modesContent += `
 
 
 - Custom modes can be configured in two ways:
 - Custom modes can be configured in two ways:
   1. Globally via '${customModesPath}' (created automatically on startup)
   1. Globally via '${customModesPath}' (created automatically on startup)
@@ -56,4 +63,7 @@ Both files should follow this structure:
     }
     }
   ]
   ]
 }`
 }`
+	}
+
+	return modesContent
 }
 }

+ 12 - 3
src/core/webview/ClineProvider.ts

@@ -984,6 +984,10 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 						await this.updateGlobalState("alwaysAllowModeSwitch", message.bool)
 						await this.updateGlobalState("alwaysAllowModeSwitch", message.bool)
 						await this.postStateToWebview()
 						await this.postStateToWebview()
 						break
 						break
+					case "alwaysAllowSubtasks":
+						await this.updateGlobalState("alwaysAllowSubtasks", message.bool)
+						await this.postStateToWebview()
+						break
 					case "askResponse":
 					case "askResponse":
 						this.getCurrentCline()?.handleWebviewAskResponse(
 						this.getCurrentCline()?.handleWebviewAskResponse(
 							message.askResponse!,
 							message.askResponse!,
@@ -993,9 +997,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 						break
 						break
 					case "clearTask":
 					case "clearTask":
 						// clear task resets the current session and allows for a new task to be started, if this session is a subtask - it allows the parent task to be resumed
 						// clear task resets the current session and allows for a new task to be started, if this session is a subtask - it allows the parent task to be resumed
-						await this.finishSubTask(
-							`new_task finished with an error!, it was stopped and canceled by the user.`,
-						)
+						await this.finishSubTask(`Task error: It was stopped and canceled by the user.`)
 						await this.postStateToWebview()
 						await this.postStateToWebview()
 						break
 						break
 					case "didShowAnnouncement":
 					case "didShowAnnouncement":
@@ -1476,6 +1478,10 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 						await this.updateGlobalState("enhancementApiConfigId", message.text)
 						await this.updateGlobalState("enhancementApiConfigId", message.text)
 						await this.postStateToWebview()
 						await this.postStateToWebview()
 						break
 						break
+					case "enableCustomModeCreation":
+						await this.updateGlobalState("enableCustomModeCreation", message.bool ?? true)
+						await this.postStateToWebview()
+						break
 					case "autoApprovalEnabled":
 					case "autoApprovalEnabled":
 						await this.updateGlobalState("autoApprovalEnabled", message.bool ?? false)
 						await this.updateGlobalState("autoApprovalEnabled", message.bool ?? false)
 						await this.postStateToWebview()
 						await this.postStateToWebview()
@@ -2177,6 +2183,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 			alwaysAllowBrowser,
 			alwaysAllowBrowser,
 			alwaysAllowMcp,
 			alwaysAllowMcp,
 			alwaysAllowModeSwitch,
 			alwaysAllowModeSwitch,
+			alwaysAllowSubtasks,
 			soundEnabled,
 			soundEnabled,
 			diffEnabled,
 			diffEnabled,
 			enableCheckpoints,
 			enableCheckpoints,
@@ -2224,6 +2231,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 			alwaysAllowBrowser: alwaysAllowBrowser ?? false,
 			alwaysAllowBrowser: alwaysAllowBrowser ?? false,
 			alwaysAllowMcp: alwaysAllowMcp ?? false,
 			alwaysAllowMcp: alwaysAllowMcp ?? false,
 			alwaysAllowModeSwitch: alwaysAllowModeSwitch ?? false,
 			alwaysAllowModeSwitch: alwaysAllowModeSwitch ?? false,
+			alwaysAllowSubtasks: alwaysAllowSubtasks ?? false,
 			uriScheme: vscode.env.uriScheme,
 			uriScheme: vscode.env.uriScheme,
 			currentTaskItem: this.getCurrentCline()?.taskId
 			currentTaskItem: this.getCurrentCline()?.taskId
 				? (taskHistory || []).find((item: HistoryItem) => item.id === this.getCurrentCline()?.taskId)
 				? (taskHistory || []).find((item: HistoryItem) => item.id === this.getCurrentCline()?.taskId)
@@ -2385,6 +2393,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 			alwaysAllowBrowser: stateValues.alwaysAllowBrowser ?? false,
 			alwaysAllowBrowser: stateValues.alwaysAllowBrowser ?? false,
 			alwaysAllowMcp: stateValues.alwaysAllowMcp ?? false,
 			alwaysAllowMcp: stateValues.alwaysAllowMcp ?? false,
 			alwaysAllowModeSwitch: stateValues.alwaysAllowModeSwitch ?? false,
 			alwaysAllowModeSwitch: stateValues.alwaysAllowModeSwitch ?? false,
+			alwaysAllowSubtasks: stateValues.alwaysAllowSubtasks ?? false,
 			taskHistory: stateValues.taskHistory,
 			taskHistory: stateValues.taskHistory,
 			allowedCommands: stateValues.allowedCommands,
 			allowedCommands: stateValues.allowedCommands,
 			soundEnabled: stateValues.soundEnabled ?? false,
 			soundEnabled: stateValues.soundEnabled ?? false,

+ 17 - 2
src/integrations/editor/DiffViewProvider.ts

@@ -7,6 +7,7 @@ import { formatResponse } from "../../core/prompts/responses"
 import { DecorationController } from "./DecorationController"
 import { DecorationController } from "./DecorationController"
 import * as diff from "diff"
 import * as diff from "diff"
 import { diagnosticsToProblemsString, getNewDiagnostics } from "../diagnostics"
 import { diagnosticsToProblemsString, getNewDiagnostics } from "../diagnostics"
+import stripBom from "strip-bom"
 
 
 export const DIFF_VIEW_URI_SCHEME = "cline-diff"
 export const DIFF_VIEW_URI_SCHEME = "cline-diff"
 
 
@@ -104,7 +105,7 @@ export class DiffViewProvider {
 		const edit = new vscode.WorkspaceEdit()
 		const edit = new vscode.WorkspaceEdit()
 		const rangeToReplace = new vscode.Range(0, 0, endLine + 1, 0)
 		const rangeToReplace = new vscode.Range(0, 0, endLine + 1, 0)
 		const contentToReplace = accumulatedLines.slice(0, endLine + 1).join("\n") + "\n"
 		const contentToReplace = accumulatedLines.slice(0, endLine + 1).join("\n") + "\n"
-		edit.replace(document.uri, rangeToReplace, contentToReplace)
+		edit.replace(document.uri, rangeToReplace, this.stripAllBOMs(contentToReplace))
 		await vscode.workspace.applyEdit(edit)
 		await vscode.workspace.applyEdit(edit)
 		// Update decorations
 		// Update decorations
 		this.activeLineController.setActiveLine(endLine)
 		this.activeLineController.setActiveLine(endLine)
@@ -128,7 +129,11 @@ export class DiffViewProvider {
 			}
 			}
 			// Apply the final content
 			// Apply the final content
 			const finalEdit = new vscode.WorkspaceEdit()
 			const finalEdit = new vscode.WorkspaceEdit()
-			finalEdit.replace(document.uri, new vscode.Range(0, 0, document.lineCount, 0), accumulatedContent)
+			finalEdit.replace(
+				document.uri,
+				new vscode.Range(0, 0, document.lineCount, 0),
+				this.stripAllBOMs(accumulatedContent),
+			)
 			await vscode.workspace.applyEdit(finalEdit)
 			await vscode.workspace.applyEdit(finalEdit)
 			// Clear all decorations at the end (after applying final edit)
 			// Clear all decorations at the end (after applying final edit)
 			this.fadedOverlayController.clear()
 			this.fadedOverlayController.clear()
@@ -336,6 +341,16 @@ export class DiffViewProvider {
 		}
 		}
 	}
 	}
 
 
+	private stripAllBOMs(input: string): string {
+		let result = input
+		let previous
+		do {
+			previous = result
+			result = stripBom(result)
+		} while (result !== previous)
+		return result
+	}
+
 	// close editor if open?
 	// close editor if open?
 	async reset() {
 	async reset() {
 		this.editType = undefined
 		this.editType = undefined

+ 4 - 0
src/shared/ExtensionMessage.ts

@@ -109,6 +109,7 @@ export interface ExtensionState {
 	alwaysAllowMcp?: boolean
 	alwaysAllowMcp?: boolean
 	alwaysApproveResubmit?: boolean
 	alwaysApproveResubmit?: boolean
 	alwaysAllowModeSwitch?: boolean
 	alwaysAllowModeSwitch?: boolean
+	alwaysAllowSubtasks?: boolean
 	browserToolEnabled?: boolean
 	browserToolEnabled?: boolean
 	requestDelaySeconds: number
 	requestDelaySeconds: number
 	rateLimitSeconds: number // Minimum time between successive requests (0 = disabled)
 	rateLimitSeconds: number // Minimum time between successive requests (0 = disabled)
@@ -128,6 +129,7 @@ export interface ExtensionState {
 	terminalOutputLimit?: number
 	terminalOutputLimit?: number
 	mcpEnabled: boolean
 	mcpEnabled: boolean
 	enableMcpServerCreation: boolean
 	enableMcpServerCreation: boolean
+	enableCustomModeCreation?: boolean
 	mode: Mode
 	mode: Mode
 	modeApiConfigs?: Record<Mode, string>
 	modeApiConfigs?: Record<Mode, string>
 	enhancementApiConfigId?: string
 	enhancementApiConfigId?: string
@@ -168,6 +170,7 @@ export type ClineAsk =
 	| "mistake_limit_reached"
 	| "mistake_limit_reached"
 	| "browser_action_launch"
 	| "browser_action_launch"
 	| "use_mcp_server"
 	| "use_mcp_server"
+	| "finishTask"
 
 
 export type ClineSay =
 export type ClineSay =
 	| "task"
 	| "task"
@@ -207,6 +210,7 @@ export interface ClineSayTool {
 		| "searchFiles"
 		| "searchFiles"
 		| "switchMode"
 		| "switchMode"
 		| "newTask"
 		| "newTask"
+		| "finishTask"
 	path?: string
 	path?: string
 	diff?: string
 	diff?: string
 	content?: string
 	content?: string

+ 2 - 0
src/shared/WebviewMessage.ts

@@ -48,6 +48,7 @@ export interface WebviewMessage {
 		| "alwaysAllowBrowser"
 		| "alwaysAllowBrowser"
 		| "alwaysAllowMcp"
 		| "alwaysAllowMcp"
 		| "alwaysAllowModeSwitch"
 		| "alwaysAllowModeSwitch"
+		| "alwaysAllowSubtasks"
 		| "playSound"
 		| "playSound"
 		| "soundEnabled"
 		| "soundEnabled"
 		| "soundVolume"
 		| "soundVolume"
@@ -71,6 +72,7 @@ export interface WebviewMessage {
 		| "terminalOutputLimit"
 		| "terminalOutputLimit"
 		| "mcpEnabled"
 		| "mcpEnabled"
 		| "enableMcpServerCreation"
 		| "enableMcpServerCreation"
+		| "enableCustomModeCreation"
 		| "searchCommits"
 		| "searchCommits"
 		| "alwaysApproveResubmit"
 		| "alwaysApproveResubmit"
 		| "requestDelaySeconds"
 		| "requestDelaySeconds"

+ 2 - 0
src/shared/globalState.ts

@@ -40,6 +40,7 @@ export const GLOBAL_STATE_KEYS = [
 	"alwaysAllowBrowser",
 	"alwaysAllowBrowser",
 	"alwaysAllowMcp",
 	"alwaysAllowMcp",
 	"alwaysAllowModeSwitch",
 	"alwaysAllowModeSwitch",
+	"alwaysAllowSubtasks",
 	"taskHistory",
 	"taskHistory",
 	"openAiBaseUrl",
 	"openAiBaseUrl",
 	"openAiModelId",
 	"openAiModelId",
@@ -84,6 +85,7 @@ export const GLOBAL_STATE_KEYS = [
 	"enhancementApiConfigId",
 	"enhancementApiConfigId",
 	"experiments", // Map of experiment IDs to their enabled state
 	"experiments", // Map of experiment IDs to their enabled state
 	"autoApprovalEnabled",
 	"autoApprovalEnabled",
+	"enableCustomModeCreation", // Enable the ability for Roo to create custom modes
 	"customModes", // Array of custom modes
 	"customModes", // Array of custom modes
 	"unboundModelId",
 	"unboundModelId",
 	"requestyModelId",
 	"requestyModelId",

+ 18 - 3
webview-ui/src/components/chat/AutoApproveMenu.tsx

@@ -30,6 +30,8 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
 		setAlwaysAllowMcp,
 		setAlwaysAllowMcp,
 		alwaysAllowModeSwitch,
 		alwaysAllowModeSwitch,
 		setAlwaysAllowModeSwitch,
 		setAlwaysAllowModeSwitch,
+		alwaysAllowSubtasks,
+		setAlwaysAllowSubtasks,
 		alwaysApproveResubmit,
 		alwaysApproveResubmit,
 		setAlwaysApproveResubmit,
 		setAlwaysApproveResubmit,
 		autoApprovalEnabled,
 		autoApprovalEnabled,
@@ -75,11 +77,17 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
 		},
 		},
 		{
 		{
 			id: "switchModes",
 			id: "switchModes",
-			label: "Switch modes & create tasks",
+			label: "Switch modes",
 			shortName: "Modes",
 			shortName: "Modes",
 			enabled: alwaysAllowModeSwitch ?? false,
 			enabled: alwaysAllowModeSwitch ?? false,
-			description:
-				"Allows automatic switching between different AI modes and creating new tasks without requiring approval.",
+			description: "Allows automatic switching between different modes without requiring approval.",
+		},
+		{
+			id: "subtasks",
+			label: "Create & complete subtasks",
+			shortName: "Subtasks",
+			enabled: alwaysAllowSubtasks ?? false,
+			description: "Allow creation and completion of subtasks without requiring approval.",
 		},
 		},
 		{
 		{
 			id: "retryRequests",
 			id: "retryRequests",
@@ -136,6 +144,12 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
 		vscode.postMessage({ type: "alwaysAllowModeSwitch", bool: newValue })
 		vscode.postMessage({ type: "alwaysAllowModeSwitch", bool: newValue })
 	}, [alwaysAllowModeSwitch, setAlwaysAllowModeSwitch])
 	}, [alwaysAllowModeSwitch, setAlwaysAllowModeSwitch])
 
 
+	const handleSubtasksChange = useCallback(() => {
+		const newValue = !(alwaysAllowSubtasks ?? false)
+		setAlwaysAllowSubtasks(newValue)
+		vscode.postMessage({ type: "alwaysAllowSubtasks", bool: newValue })
+	}, [alwaysAllowSubtasks, setAlwaysAllowSubtasks])
+
 	const handleRetryChange = useCallback(() => {
 	const handleRetryChange = useCallback(() => {
 		const newValue = !(alwaysApproveResubmit ?? false)
 		const newValue = !(alwaysApproveResubmit ?? false)
 		setAlwaysApproveResubmit(newValue)
 		setAlwaysApproveResubmit(newValue)
@@ -150,6 +164,7 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
 		useBrowser: handleBrowserChange,
 		useBrowser: handleBrowserChange,
 		useMcp: handleMcpChange,
 		useMcp: handleMcpChange,
 		switchModes: handleModeSwitchChange,
 		switchModes: handleModeSwitchChange,
+		subtasks: handleSubtasksChange,
 		retryRequests: handleRetryChange,
 		retryRequests: handleRetryChange,
 	}
 	}
 
 

+ 13 - 1
webview-ui/src/components/chat/ChatRow.tsx

@@ -451,7 +451,7 @@ export const ChatRowContent = ({
 						<div style={headerStyle}>
 						<div style={headerStyle}>
 							{toolIcon("new-file")}
 							{toolIcon("new-file")}
 							<span style={{ fontWeight: "bold" }}>
 							<span style={{ fontWeight: "bold" }}>
-								Roo wants to create a new task in <code>{tool.mode}</code> mode:
+								Roo wants to create a new subtask in <code>{tool.mode}</code> mode:
 							</span>
 							</span>
 						</div>
 						</div>
 						<div style={{ paddingLeft: "26px", marginTop: "4px" }}>
 						<div style={{ paddingLeft: "26px", marginTop: "4px" }}>
@@ -459,6 +459,18 @@ export const ChatRowContent = ({
 						</div>
 						</div>
 					</>
 					</>
 				)
 				)
+			case "finishTask":
+				return (
+					<>
+						<div style={headerStyle}>
+							{toolIcon("checklist")}
+							<span style={{ fontWeight: "bold" }}>Roo wants to finish this subtask</span>
+						</div>
+						<div style={{ paddingLeft: "26px", marginTop: "4px" }}>
+							<code>{tool.content}</code>
+						</div>
+					</>
+				)
 			default:
 			default:
 				return null
 				return null
 		}
 		}

+ 10 - 2
webview-ui/src/components/chat/ChatView.tsx

@@ -61,6 +61,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
 		setMode,
 		setMode,
 		autoApprovalEnabled,
 		autoApprovalEnabled,
 		alwaysAllowModeSwitch,
 		alwaysAllowModeSwitch,
+		alwaysAllowSubtasks,
 		customModes,
 		customModes,
 		telemetrySetting,
 		telemetrySetting,
 	} = useExtensionState()
 	} = useExtensionState()
@@ -148,6 +149,10 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
 									setPrimaryButtonText("Save")
 									setPrimaryButtonText("Save")
 									setSecondaryButtonText("Reject")
 									setSecondaryButtonText("Reject")
 									break
 									break
+								case "finishTask":
+									setPrimaryButtonText("Complete Subtask and Return")
+									setSecondaryButtonText(undefined)
+									break
 								default:
 								default:
 									setPrimaryButtonText("Approve")
 									setPrimaryButtonText("Approve")
 									setSecondaryButtonText("Reject")
 									setSecondaryButtonText("Reject")
@@ -641,8 +646,10 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
 				(alwaysAllowMcp && message.ask === "use_mcp_server" && isMcpToolAlwaysAllowed(message)) ||
 				(alwaysAllowMcp && message.ask === "use_mcp_server" && isMcpToolAlwaysAllowed(message)) ||
 				(alwaysAllowModeSwitch &&
 				(alwaysAllowModeSwitch &&
 					message.ask === "tool" &&
 					message.ask === "tool" &&
-					(JSON.parse(message.text || "{}")?.tool === "switchMode" ||
-						JSON.parse(message.text || "{}")?.tool === "newTask"))
+					JSON.parse(message.text || "{}")?.tool === "switchMode") ||
+				(alwaysAllowSubtasks &&
+					message.ask === "tool" &&
+					["newTask", "finishTask"].includes(JSON.parse(message.text || "{}")?.tool))
 			)
 			)
 		},
 		},
 		[
 		[
@@ -657,6 +664,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
 			alwaysAllowMcp,
 			alwaysAllowMcp,
 			isMcpToolAlwaysAllowed,
 			isMcpToolAlwaysAllowed,
 			alwaysAllowModeSwitch,
 			alwaysAllowModeSwitch,
+			alwaysAllowSubtasks,
 		],
 		],
 	)
 	)
 
 

+ 42 - 1
webview-ui/src/components/prompts/PromptsView.tsx

@@ -71,6 +71,8 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
 		preferredLanguage,
 		preferredLanguage,
 		setPreferredLanguage,
 		setPreferredLanguage,
 		customModes,
 		customModes,
+		enableCustomModeCreation,
+		setEnableCustomModeCreation,
 	} = useExtensionState()
 	} = useExtensionState()
 
 
 	// Memoize modes to preserve array order
 	// Memoize modes to preserve array order
@@ -341,6 +343,17 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
 		return () => document.removeEventListener("click", handleClickOutside)
 		return () => document.removeEventListener("click", handleClickOutside)
 	}, [showConfigMenu])
 	}, [showConfigMenu])
 
 
+	// Add effect to sync enableCustomModeCreation with backend
+	useEffect(() => {
+		if (enableCustomModeCreation !== undefined) {
+			// Send the value to the extension's global state
+			vscode.postMessage({
+				type: "enableCustomModeCreation", // Using dedicated message type
+				bool: enableCustomModeCreation,
+			})
+		}
+	}, [enableCustomModeCreation])
+
 	useEffect(() => {
 	useEffect(() => {
 		const handler = (event: MessageEvent) => {
 		const handler = (event: MessageEvent) => {
 			const message = event.data
 			const message = event.data
@@ -541,7 +554,6 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
 						in your workspace.
 						in your workspace.
 					</div>
 					</div>
 				</div>
 				</div>
-
 				<div className="mt-5">
 				<div className="mt-5">
 					<div onClick={(e) => e.stopPropagation()} className="flex justify-between items-center mb-3">
 					<div onClick={(e) => e.stopPropagation()} className="flex justify-between items-center mb-3">
 						<h3 className="text-vscode-foreground m-0">Modes</h3>
 						<h3 className="text-vscode-foreground m-0">Modes</h3>
@@ -1010,6 +1022,35 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
 							</div>
 							</div>
 						)}
 						)}
 					</div>
 					</div>
+
+					{/*
+			NOTE: This setting is placed in PromptsView rather than SettingsView since it
+			directly affects the functionality related to modes and custom mode creation,
+			which are managed in this component. This is an intentional deviation from
+			the standard pattern described in cline_docs/settings.md.
+	*/}
+					<div className="mb-4 mt-4">
+						<VSCodeCheckbox
+							checked={enableCustomModeCreation ?? true}
+							onChange={(e: any) => {
+								// Just update the local state through React context
+								// The React context will update the global state
+								setEnableCustomModeCreation(e.target.checked)
+							}}>
+							<span style={{ fontWeight: "500" }}>Enable Custom Mode Creation Through Prompts</span>
+						</VSCodeCheckbox>
+						<p
+							style={{
+								fontSize: "12px",
+								marginTop: "5px",
+								color: "var(--vscode-descriptionForeground)",
+							}}>
+							When enabled, Roo allows you to create custom modes using prompts like ‘Make me a custom
+							mode that…’. Disabling this reduces your system prompt by about 700 tokens when this feature
+							isn’t needed. When disabled you can still manually create custom modes using the + button
+							above or by editing the related config JSON.
+						</p>
+					</div>
 				</div>
 				</div>
 
 
 				<div
 				<div

+ 18 - 14
webview-ui/src/components/settings/ApiOptions.tsx

@@ -1076,27 +1076,31 @@ const ApiOptions = ({
 							Language Model
 							Language Model
 						</label>
 						</label>
 						{vsCodeLmModels.length > 0 ? (
 						{vsCodeLmModels.length > 0 ? (
-							<Dropdown
-								id="vscode-lm-model"
+							<Select
 								value={
 								value={
 									apiConfiguration?.vsCodeLmModelSelector
 									apiConfiguration?.vsCodeLmModelSelector
 										? `${apiConfiguration.vsCodeLmModelSelector.vendor ?? ""}/${apiConfiguration.vsCodeLmModelSelector.family ?? ""}`
 										? `${apiConfiguration.vsCodeLmModelSelector.vendor ?? ""}/${apiConfiguration.vsCodeLmModelSelector.family ?? ""}`
 										: ""
 										: ""
 								}
 								}
-								onChange={handleInputChange("vsCodeLmModelSelector", (e) => {
-									const valueStr = (e as DropdownOption)?.value
+								onValueChange={handleInputChange("vsCodeLmModelSelector", (valueStr) => {
 									const [vendor, family] = valueStr.split("/")
 									const [vendor, family] = valueStr.split("/")
 									return { vendor, family }
 									return { vendor, family }
-								})}
-								options={[
-									{ value: "", label: "Select a model..." },
-									...vsCodeLmModels.map((model) => ({
-										value: `${model.vendor}/${model.family}`,
-										label: `${model.vendor} - ${model.family}`,
-									})),
-								]}
-								className="w-full"
-							/>
+								})}>
+								<SelectTrigger className="w-full">
+									<SelectValue placeholder="Select a model..." />
+								</SelectTrigger>
+								<SelectContent>
+									<SelectGroup>
+										{vsCodeLmModels.map((model) => (
+											<SelectItem
+												key={`${model.vendor}/${model.family}`}
+												value={`${model.vendor}/${model.family}`}>
+												{`${model.vendor} - ${model.family}`}
+											</SelectItem>
+										))}
+									</SelectGroup>
+								</SelectContent>
+							</Select>
 						) : (
 						) : (
 							<div className="text-sm text-vscode-descriptionForeground">
 							<div className="text-sm text-vscode-descriptionForeground">
 								The VS Code Language Model API allows you to run models provided by other VS Code
 								The VS Code Language Model API allows you to run models provided by other VS Code

+ 15 - 2
webview-ui/src/components/settings/AutoApproveSettings.tsx

@@ -18,6 +18,7 @@ type AutoApproveSettingsProps = HTMLAttributes<HTMLDivElement> & {
 	requestDelaySeconds: number
 	requestDelaySeconds: number
 	alwaysAllowMcp?: boolean
 	alwaysAllowMcp?: boolean
 	alwaysAllowModeSwitch?: boolean
 	alwaysAllowModeSwitch?: boolean
+	alwaysAllowSubtasks?: boolean
 	alwaysAllowExecute?: boolean
 	alwaysAllowExecute?: boolean
 	allowedCommands?: string[]
 	allowedCommands?: string[]
 	setCachedStateField: SetCachedStateField<keyof ExtensionStateContextType>
 	setCachedStateField: SetCachedStateField<keyof ExtensionStateContextType>
@@ -32,6 +33,7 @@ export const AutoApproveSettings = ({
 	requestDelaySeconds,
 	requestDelaySeconds,
 	alwaysAllowMcp,
 	alwaysAllowMcp,
 	alwaysAllowModeSwitch,
 	alwaysAllowModeSwitch,
+	alwaysAllowSubtasks,
 	alwaysAllowExecute,
 	alwaysAllowExecute,
 	allowedCommands,
 	allowedCommands,
 	setCachedStateField,
 	setCachedStateField,
@@ -173,10 +175,21 @@ export const AutoApproveSettings = ({
 					<VSCodeCheckbox
 					<VSCodeCheckbox
 						checked={alwaysAllowModeSwitch}
 						checked={alwaysAllowModeSwitch}
 						onChange={(e: any) => setCachedStateField("alwaysAllowModeSwitch", e.target.checked)}>
 						onChange={(e: any) => setCachedStateField("alwaysAllowModeSwitch", e.target.checked)}>
-						<span className="font-medium">Always approve mode switching & task creation</span>
+						<span className="font-medium">Always approve mode switching</span>
 					</VSCodeCheckbox>
 					</VSCodeCheckbox>
 					<p className="text-vscode-descriptionForeground text-sm mt-0">
 					<p className="text-vscode-descriptionForeground text-sm mt-0">
-						Automatically switch between different AI modes and create new tasks without requiring approval
+						Automatically switch between different modes without requiring approval
+					</p>
+				</div>
+
+				<div>
+					<VSCodeCheckbox
+						checked={alwaysAllowSubtasks}
+						onChange={(e: any) => setCachedStateField("alwaysAllowSubtasks", e.target.checked)}>
+						<span className="font-medium">Always approve creation & completion of subtasks</span>
+					</VSCodeCheckbox>
+					<p className="text-vscode-descriptionForeground text-sm mt-0">
+						Allow creation and completion of subtasks without requiring approval
 					</p>
 					</p>
 				</div>
 				</div>
 
 

+ 3 - 0
webview-ui/src/components/settings/SettingsView.tsx

@@ -63,6 +63,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone },
 		alwaysAllowExecute,
 		alwaysAllowExecute,
 		alwaysAllowMcp,
 		alwaysAllowMcp,
 		alwaysAllowModeSwitch,
 		alwaysAllowModeSwitch,
+		alwaysAllowSubtasks,
 		alwaysAllowWrite,
 		alwaysAllowWrite,
 		alwaysApproveResubmit,
 		alwaysApproveResubmit,
 		browserToolEnabled,
 		browserToolEnabled,
@@ -184,6 +185,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone },
 			vscode.postMessage({ type: "currentApiConfigName", text: currentApiConfigName })
 			vscode.postMessage({ type: "currentApiConfigName", text: currentApiConfigName })
 			vscode.postMessage({ type: "updateExperimental", values: experiments })
 			vscode.postMessage({ type: "updateExperimental", values: experiments })
 			vscode.postMessage({ type: "alwaysAllowModeSwitch", bool: alwaysAllowModeSwitch })
 			vscode.postMessage({ type: "alwaysAllowModeSwitch", bool: alwaysAllowModeSwitch })
+			vscode.postMessage({ type: "alwaysAllowSubtasks", bool: alwaysAllowSubtasks })
 			vscode.postMessage({ type: "upsertApiConfiguration", text: currentApiConfigName, apiConfiguration })
 			vscode.postMessage({ type: "upsertApiConfiguration", text: currentApiConfigName, apiConfiguration })
 			vscode.postMessage({ type: "telemetrySetting", text: telemetrySetting })
 			vscode.postMessage({ type: "telemetrySetting", text: telemetrySetting })
 			setChangeDetected(false)
 			setChangeDetected(false)
@@ -364,6 +366,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone },
 						requestDelaySeconds={requestDelaySeconds}
 						requestDelaySeconds={requestDelaySeconds}
 						alwaysAllowMcp={alwaysAllowMcp}
 						alwaysAllowMcp={alwaysAllowMcp}
 						alwaysAllowModeSwitch={alwaysAllowModeSwitch}
 						alwaysAllowModeSwitch={alwaysAllowModeSwitch}
+						alwaysAllowSubtasks={alwaysAllowSubtasks}
 						alwaysAllowExecute={alwaysAllowExecute}
 						alwaysAllowExecute={alwaysAllowExecute}
 						allowedCommands={allowedCommands}
 						allowedCommands={allowedCommands}
 						setCachedStateField={setCachedStateField}
 						setCachedStateField={setCachedStateField}

+ 7 - 0
webview-ui/src/context/ExtensionStateContext.tsx

@@ -31,6 +31,7 @@ export interface ExtensionStateContextType extends ExtensionState {
 	setAlwaysAllowBrowser: (value: boolean) => void
 	setAlwaysAllowBrowser: (value: boolean) => void
 	setAlwaysAllowMcp: (value: boolean) => void
 	setAlwaysAllowMcp: (value: boolean) => void
 	setAlwaysAllowModeSwitch: (value: boolean) => void
 	setAlwaysAllowModeSwitch: (value: boolean) => void
+	setAlwaysAllowSubtasks: (value: boolean) => void
 	setBrowserToolEnabled: (value: boolean) => void
 	setBrowserToolEnabled: (value: boolean) => void
 	setShowRooIgnoredFiles: (value: boolean) => void
 	setShowRooIgnoredFiles: (value: boolean) => void
 	setShowAnnouncement: (value: boolean) => void
 	setShowAnnouncement: (value: boolean) => void
@@ -52,6 +53,8 @@ export interface ExtensionStateContextType extends ExtensionState {
 	setMcpEnabled: (value: boolean) => void
 	setMcpEnabled: (value: boolean) => void
 	enableMcpServerCreation: boolean
 	enableMcpServerCreation: boolean
 	setEnableMcpServerCreation: (value: boolean) => void
 	setEnableMcpServerCreation: (value: boolean) => void
+	enableCustomModeCreation?: boolean
+	setEnableCustomModeCreation: (value: boolean) => void
 	alwaysApproveResubmit?: boolean
 	alwaysApproveResubmit?: boolean
 	setAlwaysApproveResubmit: (value: boolean) => void
 	setAlwaysApproveResubmit: (value: boolean) => void
 	requestDelaySeconds: number
 	requestDelaySeconds: number
@@ -117,6 +120,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
 		checkpointStorage: "task",
 		checkpointStorage: "task",
 		fuzzyMatchThreshold: 1.0,
 		fuzzyMatchThreshold: 1.0,
 		preferredLanguage: "English",
 		preferredLanguage: "English",
+		enableCustomModeCreation: true,
 		writeDelayMs: 1000,
 		writeDelayMs: 1000,
 		browserViewportSize: "900x600",
 		browserViewportSize: "900x600",
 		screenshotQuality: 75,
 		screenshotQuality: 75,
@@ -247,6 +251,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
 		setAlwaysAllowBrowser: (value) => setState((prevState) => ({ ...prevState, alwaysAllowBrowser: value })),
 		setAlwaysAllowBrowser: (value) => setState((prevState) => ({ ...prevState, alwaysAllowBrowser: value })),
 		setAlwaysAllowMcp: (value) => setState((prevState) => ({ ...prevState, alwaysAllowMcp: value })),
 		setAlwaysAllowMcp: (value) => setState((prevState) => ({ ...prevState, alwaysAllowMcp: value })),
 		setAlwaysAllowModeSwitch: (value) => setState((prevState) => ({ ...prevState, alwaysAllowModeSwitch: value })),
 		setAlwaysAllowModeSwitch: (value) => setState((prevState) => ({ ...prevState, alwaysAllowModeSwitch: value })),
+		setAlwaysAllowSubtasks: (value) => setState((prevState) => ({ ...prevState, alwaysAllowSubtasks: value })),
 		setShowAnnouncement: (value) => setState((prevState) => ({ ...prevState, shouldShowAnnouncement: value })),
 		setShowAnnouncement: (value) => setState((prevState) => ({ ...prevState, shouldShowAnnouncement: value })),
 		setAllowedCommands: (value) => setState((prevState) => ({ ...prevState, allowedCommands: value })),
 		setAllowedCommands: (value) => setState((prevState) => ({ ...prevState, allowedCommands: value })),
 		setSoundEnabled: (value) => setState((prevState) => ({ ...prevState, soundEnabled: value })),
 		setSoundEnabled: (value) => setState((prevState) => ({ ...prevState, soundEnabled: value })),
@@ -273,6 +278,8 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
 		setCustomSupportPrompts: (value) => setState((prevState) => ({ ...prevState, customSupportPrompts: value })),
 		setCustomSupportPrompts: (value) => setState((prevState) => ({ ...prevState, customSupportPrompts: value })),
 		setEnhancementApiConfigId: (value) =>
 		setEnhancementApiConfigId: (value) =>
 			setState((prevState) => ({ ...prevState, enhancementApiConfigId: value })),
 			setState((prevState) => ({ ...prevState, enhancementApiConfigId: value })),
+		setEnableCustomModeCreation: (value) =>
+			setState((prevState) => ({ ...prevState, enableCustomModeCreation: value })),
 		setAutoApprovalEnabled: (value) => setState((prevState) => ({ ...prevState, autoApprovalEnabled: value })),
 		setAutoApprovalEnabled: (value) => setState((prevState) => ({ ...prevState, autoApprovalEnabled: value })),
 		setCustomModes: (value) => setState((prevState) => ({ ...prevState, customModes: value })),
 		setCustomModes: (value) => setState((prevState) => ({ ...prevState, customModes: value })),
 		setMaxOpenTabsContext: (value) => setState((prevState) => ({ ...prevState, maxOpenTabsContext: value })),
 		setMaxOpenTabsContext: (value) => setState((prevState) => ({ ...prevState, maxOpenTabsContext: value })),