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

Merge pull request #329 from samhvw8/feat/roo-cline-code-action

New Feature code action
Matt Rubens 11 месяцев назад
Родитель
Сommit
0a32e24c6d

+ 32 - 0
package.json

@@ -101,9 +101,41 @@
 				"command": "roo-cline.openInNewTab",
 				"command": "roo-cline.openInNewTab",
 				"title": "Open In New Tab",
 				"title": "Open In New Tab",
 				"category": "Roo Code"
 				"category": "Roo Code"
+			},
+			{
+				"command": "roo-cline.explainCode",
+				"title": "Roo Code: Explain Code",
+				"category": "Roo Code"
+			},
+			{
+				"command": "roo-cline.fixCode",
+				"title": "Roo Code: Fix Code",
+				"category": "Roo Code"
+			},
+			{
+				"command": "roo-cline.improveCode",
+				"title": "Roo Code: Improve Code",
+				"category": "Roo Code"
 			}
 			}
 		],
 		],
 		"menus": {
 		"menus": {
+			"editor/context": [
+				{
+					"command": "roo-cline.explainCode",
+					"when": "editorHasSelection",
+					"group": "Roo Code@1"
+				},
+				{
+					"command": "roo-cline.fixCode",
+					"when": "editorHasSelection",
+					"group": "Roo Code@2"
+				},
+				{
+					"command": "roo-cline.improveCode",
+					"when": "editorHasSelection",
+					"group": "Roo Code@3"
+				}
+			],
 			"view/title": [
 			"view/title": [
 				{
 				{
 					"command": "roo-cline.plusButtonClicked",
 					"command": "roo-cline.plusButtonClicked",

+ 2 - 2
src/core/Cline.ts

@@ -809,7 +809,7 @@ export class Cline {
 			})
 			})
 		}
 		}
 
 
-		const { browserViewportSize, mode, customPrompts, preferredLanguage } =
+		const { browserViewportSize, mode, customModePrompts, preferredLanguage } =
 			(await this.providerRef.deref()?.getState()) ?? {}
 			(await this.providerRef.deref()?.getState()) ?? {}
 		const { customModes } = (await this.providerRef.deref()?.getState()) ?? {}
 		const { customModes } = (await this.providerRef.deref()?.getState()) ?? {}
 		const systemPrompt = await (async () => {
 		const systemPrompt = await (async () => {
@@ -825,7 +825,7 @@ export class Cline {
 				this.diffStrategy,
 				this.diffStrategy,
 				browserViewportSize,
 				browserViewportSize,
 				mode,
 				mode,
-				customPrompts,
+				customModePrompts,
 				customModes,
 				customModes,
 				this.customInstructions,
 				this.customInstructions,
 				preferredLanguage,
 				preferredLanguage,

+ 179 - 0
src/core/CodeActionProvider.ts

@@ -0,0 +1,179 @@
+import * as vscode from "vscode"
+import * as path from "path"
+
+export const ACTION_NAMES = {
+	EXPLAIN: "Roo Code: Explain Code",
+	FIX: "Roo Code: Fix Code",
+	IMPROVE: "Roo Code: Improve Code",
+} as const
+
+const COMMAND_IDS = {
+	EXPLAIN: "roo-cline.explainCode",
+	FIX: "roo-cline.fixCode",
+	IMPROVE: "roo-cline.improveCode",
+} as const
+
+interface DiagnosticData {
+	message: string
+	severity: vscode.DiagnosticSeverity
+	code?: string | number | { value: string | number; target: vscode.Uri }
+	source?: string
+	range: vscode.Range
+}
+
+interface EffectiveRange {
+	range: vscode.Range
+	text: string
+}
+
+export class CodeActionProvider implements vscode.CodeActionProvider {
+	public static readonly providedCodeActionKinds = [
+		vscode.CodeActionKind.QuickFix,
+		vscode.CodeActionKind.RefactorRewrite,
+	]
+
+	// Cache file paths for performance
+	private readonly filePathCache = new WeakMap<vscode.TextDocument, string>()
+
+	private getEffectiveRange(
+		document: vscode.TextDocument,
+		range: vscode.Range | vscode.Selection,
+	): EffectiveRange | null {
+		try {
+			const selectedText = document.getText(range)
+			if (selectedText) {
+				return { range, text: selectedText }
+			}
+
+			const currentLine = document.lineAt(range.start.line)
+			if (!currentLine.text.trim()) {
+				return null
+			}
+
+			// Optimize range creation by checking bounds first
+			const startLine = Math.max(0, currentLine.lineNumber - 1)
+			const endLine = Math.min(document.lineCount - 1, currentLine.lineNumber + 1)
+
+			// Only create new positions if needed
+			const effectiveRange = new vscode.Range(
+				startLine === currentLine.lineNumber ? range.start : new vscode.Position(startLine, 0),
+				endLine === currentLine.lineNumber
+					? range.end
+					: new vscode.Position(endLine, document.lineAt(endLine).text.length),
+			)
+
+			return {
+				range: effectiveRange,
+				text: document.getText(effectiveRange),
+			}
+		} catch (error) {
+			console.error("Error getting effective range:", error)
+			return null
+		}
+	}
+
+	private getFilePath(document: vscode.TextDocument): string {
+		// Check cache first
+		let filePath = this.filePathCache.get(document)
+		if (filePath) {
+			return filePath
+		}
+
+		try {
+			const workspaceFolder = vscode.workspace.getWorkspaceFolder(document.uri)
+			if (!workspaceFolder) {
+				filePath = document.uri.fsPath
+			} else {
+				const relativePath = path.relative(workspaceFolder.uri.fsPath, document.uri.fsPath)
+				filePath = !relativePath || relativePath.startsWith("..") ? document.uri.fsPath : relativePath
+			}
+
+			// Cache the result
+			this.filePathCache.set(document, filePath)
+			return filePath
+		} catch (error) {
+			console.error("Error getting file path:", error)
+			return document.uri.fsPath
+		}
+	}
+
+	private createDiagnosticData(diagnostic: vscode.Diagnostic): DiagnosticData {
+		return {
+			message: diagnostic.message,
+			severity: diagnostic.severity,
+			code: diagnostic.code,
+			source: diagnostic.source,
+			range: diagnostic.range, // Reuse the range object
+		}
+	}
+
+	private createAction(title: string, kind: vscode.CodeActionKind, command: string, args: any[]): vscode.CodeAction {
+		const action = new vscode.CodeAction(title, kind)
+		action.command = { command, title, arguments: args }
+		return action
+	}
+
+	private hasIntersectingRange(range1: vscode.Range, range2: vscode.Range): boolean {
+		// Optimize range intersection check
+		return !(
+			range2.end.line < range1.start.line ||
+			range2.start.line > range1.end.line ||
+			(range2.end.line === range1.start.line && range2.end.character < range1.start.character) ||
+			(range2.start.line === range1.end.line && range2.start.character > range1.end.character)
+		)
+	}
+
+	public provideCodeActions(
+		document: vscode.TextDocument,
+		range: vscode.Range | vscode.Selection,
+		context: vscode.CodeActionContext,
+	): vscode.ProviderResult<(vscode.CodeAction | vscode.Command)[]> {
+		try {
+			const effectiveRange = this.getEffectiveRange(document, range)
+			if (!effectiveRange) {
+				return []
+			}
+
+			const filePath = this.getFilePath(document)
+			const actions: vscode.CodeAction[] = []
+
+			// Create actions using helper method
+			actions.push(
+				this.createAction(ACTION_NAMES.EXPLAIN, vscode.CodeActionKind.QuickFix, COMMAND_IDS.EXPLAIN, [
+					filePath,
+					effectiveRange.text,
+				]),
+			)
+
+			// Only process diagnostics if they exist
+			if (context.diagnostics.length > 0) {
+				const relevantDiagnostics = context.diagnostics.filter((d) =>
+					this.hasIntersectingRange(effectiveRange.range, d.range),
+				)
+
+				if (relevantDiagnostics.length > 0) {
+					const diagnosticMessages = relevantDiagnostics.map(this.createDiagnosticData)
+					actions.push(
+						this.createAction(ACTION_NAMES.FIX, vscode.CodeActionKind.QuickFix, COMMAND_IDS.FIX, [
+							filePath,
+							effectiveRange.text,
+							diagnosticMessages,
+						]),
+					)
+				}
+			}
+
+			actions.push(
+				this.createAction(ACTION_NAMES.IMPROVE, vscode.CodeActionKind.RefactorRewrite, COMMAND_IDS.IMPROVE, [
+					filePath,
+					effectiveRange.text,
+				]),
+			)
+
+			return actions
+		} catch (error) {
+			console.error("Error providing code actions:", error)
+			return []
+		}
+	}
+}

+ 147 - 0
src/core/__tests__/CodeActionProvider.test.ts

@@ -0,0 +1,147 @@
+import * as vscode from "vscode"
+import { CodeActionProvider } from "../CodeActionProvider"
+
+// Mock VSCode API
+jest.mock("vscode", () => ({
+	CodeAction: jest.fn().mockImplementation((title, kind) => ({
+		title,
+		kind,
+		command: undefined,
+	})),
+	CodeActionKind: {
+		QuickFix: { value: "quickfix" },
+		RefactorRewrite: { value: "refactor.rewrite" },
+	},
+	Range: jest.fn().mockImplementation((startLine, startChar, endLine, endChar) => ({
+		start: { line: startLine, character: startChar },
+		end: { line: endLine, character: endChar },
+	})),
+	Position: jest.fn().mockImplementation((line, character) => ({
+		line,
+		character,
+	})),
+	workspace: {
+		getWorkspaceFolder: jest.fn(),
+	},
+	DiagnosticSeverity: {
+		Error: 0,
+		Warning: 1,
+		Information: 2,
+		Hint: 3,
+	},
+}))
+
+describe("CodeActionProvider", () => {
+	let provider: CodeActionProvider
+	let mockDocument: any
+	let mockRange: any
+	let mockContext: any
+
+	beforeEach(() => {
+		provider = new CodeActionProvider()
+
+		// Mock document
+		mockDocument = {
+			getText: jest.fn(),
+			lineAt: jest.fn(),
+			lineCount: 10,
+			uri: { fsPath: "/test/file.ts" },
+		}
+
+		// Mock range
+		mockRange = new vscode.Range(0, 0, 0, 10)
+
+		// Mock context
+		mockContext = {
+			diagnostics: [],
+		}
+	})
+
+	describe("getEffectiveRange", () => {
+		it("should return selected text when available", () => {
+			mockDocument.getText.mockReturnValue("selected text")
+
+			const result = (provider as any).getEffectiveRange(mockDocument, mockRange)
+
+			expect(result).toEqual({
+				range: mockRange,
+				text: "selected text",
+			})
+		})
+
+		it("should return null for empty line", () => {
+			mockDocument.getText.mockReturnValue("")
+			mockDocument.lineAt.mockReturnValue({ text: "", lineNumber: 0 })
+
+			const result = (provider as any).getEffectiveRange(mockDocument, mockRange)
+
+			expect(result).toBeNull()
+		})
+	})
+
+	describe("getFilePath", () => {
+		it("should return relative path when in workspace", () => {
+			const mockWorkspaceFolder = {
+				uri: { fsPath: "/test" },
+			}
+			;(vscode.workspace.getWorkspaceFolder as jest.Mock).mockReturnValue(mockWorkspaceFolder)
+
+			const result = (provider as any).getFilePath(mockDocument)
+
+			expect(result).toBe("file.ts")
+		})
+
+		it("should return absolute path when not in workspace", () => {
+			;(vscode.workspace.getWorkspaceFolder as jest.Mock).mockReturnValue(null)
+
+			const result = (provider as any).getFilePath(mockDocument)
+
+			expect(result).toBe("/test/file.ts")
+		})
+	})
+
+	describe("provideCodeActions", () => {
+		beforeEach(() => {
+			mockDocument.getText.mockReturnValue("test code")
+			mockDocument.lineAt.mockReturnValue({ text: "test code", lineNumber: 0 })
+		})
+
+		it("should provide explain and improve actions by default", () => {
+			const actions = provider.provideCodeActions(mockDocument, mockRange, mockContext)
+
+			expect(actions).toHaveLength(2)
+			expect((actions as any)[0].title).toBe("Roo Code: Explain Code")
+			expect((actions as any)[1].title).toBe("Roo Code: Improve Code")
+		})
+
+		it("should provide fix action when diagnostics exist", () => {
+			mockContext.diagnostics = [
+				{
+					message: "test error",
+					severity: vscode.DiagnosticSeverity.Error,
+					range: mockRange,
+				},
+			]
+
+			const actions = provider.provideCodeActions(mockDocument, mockRange, mockContext)
+
+			expect(actions).toHaveLength(3)
+			expect((actions as any).some((a: any) => a.title === "Roo Code: Fix Code")).toBe(true)
+		})
+
+		it("should handle errors gracefully", () => {
+			const consoleErrorSpy = jest.spyOn(console, "error").mockImplementation(() => {})
+			mockDocument.getText.mockImplementation(() => {
+				throw new Error("Test error")
+			})
+			mockDocument.lineAt.mockReturnValue({ text: "test", lineNumber: 0 })
+
+			const actions = provider.provideCodeActions(mockDocument, mockRange, mockContext)
+
+			expect(actions).toEqual([])
+			expect(consoleErrorSpy).toHaveBeenCalledWith("Error getting effective range:", expect.any(Error))
+
+			consoleErrorSpy.mockRestore()
+		})
+	})
+})

+ 16 - 16
src/core/prompts/__tests__/system.test.ts

@@ -162,7 +162,7 @@ describe("SYSTEM_PROMPT", () => {
 			undefined, // diffStrategy
 			undefined, // diffStrategy
 			undefined, // browserViewportSize
 			undefined, // browserViewportSize
 			defaultModeSlug, // mode
 			defaultModeSlug, // mode
-			undefined, // customPrompts
+			undefined, // customModePrompts
 			undefined, // customModes
 			undefined, // customModes
 		)
 		)
 
 
@@ -178,7 +178,7 @@ describe("SYSTEM_PROMPT", () => {
 			undefined, // diffStrategy
 			undefined, // diffStrategy
 			"1280x800", // browserViewportSize
 			"1280x800", // browserViewportSize
 			defaultModeSlug, // mode
 			defaultModeSlug, // mode
-			undefined, // customPrompts
+			undefined, // customModePrompts
 			undefined, // customModes
 			undefined, // customModes
 		)
 		)
 
 
@@ -196,7 +196,7 @@ describe("SYSTEM_PROMPT", () => {
 			undefined, // diffStrategy
 			undefined, // diffStrategy
 			undefined, // browserViewportSize
 			undefined, // browserViewportSize
 			defaultModeSlug, // mode
 			defaultModeSlug, // mode
-			undefined, // customPrompts
+			undefined, // customModePrompts
 			undefined, // customModes
 			undefined, // customModes
 		)
 		)
 
 
@@ -212,7 +212,7 @@ describe("SYSTEM_PROMPT", () => {
 			undefined, // diffStrategy
 			undefined, // diffStrategy
 			undefined, // browserViewportSize
 			undefined, // browserViewportSize
 			defaultModeSlug, // mode
 			defaultModeSlug, // mode
-			undefined, // customPrompts
+			undefined, // customModePrompts
 			undefined, // customModes
 			undefined, // customModes
 		)
 		)
 
 
@@ -228,7 +228,7 @@ describe("SYSTEM_PROMPT", () => {
 			undefined, // diffStrategy
 			undefined, // diffStrategy
 			"900x600", // different viewport size
 			"900x600", // different viewport size
 			defaultModeSlug, // mode
 			defaultModeSlug, // mode
-			undefined, // customPrompts
+			undefined, // customModePrompts
 			undefined, // customModes
 			undefined, // customModes
 		)
 		)
 
 
@@ -244,7 +244,7 @@ describe("SYSTEM_PROMPT", () => {
 			new SearchReplaceDiffStrategy(), // Use actual diff strategy from the codebase
 			new SearchReplaceDiffStrategy(), // Use actual diff strategy from the codebase
 			undefined, // browserViewportSize
 			undefined, // browserViewportSize
 			defaultModeSlug, // mode
 			defaultModeSlug, // mode
-			undefined, // customPrompts
+			undefined, // customModePrompts
 			undefined, // customModes
 			undefined, // customModes
 			undefined, // globalCustomInstructions
 			undefined, // globalCustomInstructions
 			undefined, // preferredLanguage
 			undefined, // preferredLanguage
@@ -264,7 +264,7 @@ describe("SYSTEM_PROMPT", () => {
 			new SearchReplaceDiffStrategy(), // Use actual diff strategy from the codebase
 			new SearchReplaceDiffStrategy(), // Use actual diff strategy from the codebase
 			undefined, // browserViewportSize
 			undefined, // browserViewportSize
 			defaultModeSlug, // mode
 			defaultModeSlug, // mode
-			undefined, // customPrompts
+			undefined, // customModePrompts
 			undefined, // customModes
 			undefined, // customModes
 			undefined, // globalCustomInstructions
 			undefined, // globalCustomInstructions
 			undefined, // preferredLanguage
 			undefined, // preferredLanguage
@@ -284,7 +284,7 @@ describe("SYSTEM_PROMPT", () => {
 			new SearchReplaceDiffStrategy(), // Use actual diff strategy from the codebase
 			new SearchReplaceDiffStrategy(), // Use actual diff strategy from the codebase
 			undefined, // browserViewportSize
 			undefined, // browserViewportSize
 			defaultModeSlug, // mode
 			defaultModeSlug, // mode
-			undefined, // customPrompts
+			undefined, // customModePrompts
 			undefined, // customModes
 			undefined, // customModes
 			undefined, // globalCustomInstructions
 			undefined, // globalCustomInstructions
 			undefined, // preferredLanguage
 			undefined, // preferredLanguage
@@ -304,7 +304,7 @@ describe("SYSTEM_PROMPT", () => {
 			undefined, // diffStrategy
 			undefined, // diffStrategy
 			undefined, // browserViewportSize
 			undefined, // browserViewportSize
 			defaultModeSlug, // mode
 			defaultModeSlug, // mode
-			undefined, // customPrompts
+			undefined, // customModePrompts
 			undefined, // customModes
 			undefined, // customModes
 			undefined, // globalCustomInstructions
 			undefined, // globalCustomInstructions
 			"Spanish", // preferredLanguage
 			"Spanish", // preferredLanguage
@@ -334,7 +334,7 @@ describe("SYSTEM_PROMPT", () => {
 			undefined, // diffStrategy
 			undefined, // diffStrategy
 			undefined, // browserViewportSize
 			undefined, // browserViewportSize
 			"custom-mode", // mode
 			"custom-mode", // mode
-			undefined, // customPrompts
+			undefined, // customModePrompts
 			customModes, // customModes
 			customModes, // customModes
 			"Global instructions", // globalCustomInstructions
 			"Global instructions", // globalCustomInstructions
 		)
 		)
@@ -351,7 +351,7 @@ describe("SYSTEM_PROMPT", () => {
 	})
 	})
 
 
 	it("should use promptComponent roleDefinition when available", async () => {
 	it("should use promptComponent roleDefinition when available", async () => {
-		const customPrompts = {
+		const customModePrompts = {
 			[defaultModeSlug]: {
 			[defaultModeSlug]: {
 				roleDefinition: "Custom prompt role definition",
 				roleDefinition: "Custom prompt role definition",
 				customInstructions: "Custom prompt instructions",
 				customInstructions: "Custom prompt instructions",
@@ -366,7 +366,7 @@ describe("SYSTEM_PROMPT", () => {
 			undefined,
 			undefined,
 			undefined,
 			undefined,
 			defaultModeSlug,
 			defaultModeSlug,
-			customPrompts,
+			customModePrompts,
 			undefined,
 			undefined,
 		)
 		)
 
 
@@ -377,7 +377,7 @@ describe("SYSTEM_PROMPT", () => {
 	})
 	})
 
 
 	it("should fallback to modeConfig roleDefinition when promptComponent has no roleDefinition", async () => {
 	it("should fallback to modeConfig roleDefinition when promptComponent has no roleDefinition", async () => {
-		const customPrompts = {
+		const customModePrompts = {
 			[defaultModeSlug]: {
 			[defaultModeSlug]: {
 				customInstructions: "Custom prompt instructions",
 				customInstructions: "Custom prompt instructions",
 				// No roleDefinition provided
 				// No roleDefinition provided
@@ -392,7 +392,7 @@ describe("SYSTEM_PROMPT", () => {
 			undefined,
 			undefined,
 			undefined,
 			undefined,
 			defaultModeSlug,
 			defaultModeSlug,
-			customPrompts,
+			customModePrompts,
 			undefined,
 			undefined,
 		)
 		)
 
 
@@ -432,7 +432,7 @@ describe("addCustomInstructions", () => {
 			undefined, // diffStrategy
 			undefined, // diffStrategy
 			undefined, // browserViewportSize
 			undefined, // browserViewportSize
 			"architect", // mode
 			"architect", // mode
-			undefined, // customPrompts
+			undefined, // customModePrompts
 			undefined, // customModes
 			undefined, // customModes
 		)
 		)
 
 
@@ -448,7 +448,7 @@ describe("addCustomInstructions", () => {
 			undefined, // diffStrategy
 			undefined, // diffStrategy
 			undefined, // browserViewportSize
 			undefined, // browserViewportSize
 			"ask", // mode
 			"ask", // mode
-			undefined, // customPrompts
+			undefined, // customModePrompts
 			undefined, // customModes
 			undefined, // customModes
 		)
 		)
 
 

+ 3 - 3
src/core/prompts/system.ts

@@ -1,7 +1,7 @@
 import {
 import {
 	Mode,
 	Mode,
 	modes,
 	modes,
-	CustomPrompts,
+	CustomModePrompts,
 	PromptComponent,
 	PromptComponent,
 	getRoleDefinition,
 	getRoleDefinition,
 	defaultModeSlug,
 	defaultModeSlug,
@@ -97,7 +97,7 @@ export const SYSTEM_PROMPT = async (
 	diffStrategy?: DiffStrategy,
 	diffStrategy?: DiffStrategy,
 	browserViewportSize?: string,
 	browserViewportSize?: string,
 	mode: Mode = defaultModeSlug,
 	mode: Mode = defaultModeSlug,
-	customPrompts?: CustomPrompts,
+	customModePrompts?: CustomModePrompts,
 	customModes?: ModeConfig[],
 	customModes?: ModeConfig[],
 	globalCustomInstructions?: string,
 	globalCustomInstructions?: string,
 	preferredLanguage?: string,
 	preferredLanguage?: string,
@@ -115,7 +115,7 @@ export const SYSTEM_PROMPT = async (
 	}
 	}
 
 
 	// Check if it's a custom mode
 	// Check if it's a custom mode
-	const promptComponent = getPromptComponent(customPrompts?.[mode])
+	const promptComponent = getPromptComponent(customModePrompts?.[mode])
 	// Get full mode config from custom modes or fall back to built-in modes
 	// Get full mode config from custom modes or fall back to built-in modes
 	const currentMode = getModeBySlug(mode, customModes) || modes.find((m) => m.slug === mode) || modes[0]
 	const currentMode = getModeBySlug(mode, customModes) || modes.find((m) => m.slug === mode) || modes[0]
 
 

+ 104 - 47
src/core/webview/ClineProvider.ts

@@ -1,4 +1,5 @@
 import { Anthropic } from "@anthropic-ai/sdk"
 import { Anthropic } from "@anthropic-ai/sdk"
+import delay from "delay"
 import axios from "axios"
 import axios from "axios"
 import fs from "fs/promises"
 import fs from "fs/promises"
 import os from "os"
 import os from "os"
@@ -21,9 +22,8 @@ import { WebviewMessage } from "../../shared/WebviewMessage"
 import {
 import {
 	Mode,
 	Mode,
 	modes,
 	modes,
-	CustomPrompts,
+	CustomModePrompts,
 	PromptComponent,
 	PromptComponent,
-	enhance,
 	ModeConfig,
 	ModeConfig,
 	defaultModeSlug,
 	defaultModeSlug,
 	getModeBySlug,
 	getModeBySlug,
@@ -36,10 +36,13 @@ import { getNonce } from "./getNonce"
 import { getUri } from "./getUri"
 import { getUri } from "./getUri"
 import { playSound, setSoundEnabled, setSoundVolume } from "../../utils/sound"
 import { playSound, setSoundEnabled, setSoundVolume } from "../../utils/sound"
 import { checkExistKey } from "../../shared/checkExistApiConfig"
 import { checkExistKey } from "../../shared/checkExistApiConfig"
-import { enhancePrompt } from "../../utils/enhance-prompt"
+import { singleCompletionHandler } from "../../utils/single-completion-handler"
 import { getCommitInfo, searchCommits, getWorkingState } from "../../utils/git"
 import { getCommitInfo, searchCommits, getWorkingState } from "../../utils/git"
 import { ConfigManager } from "../config/ConfigManager"
 import { ConfigManager } from "../config/ConfigManager"
 import { CustomModesManager } from "../config/CustomModesManager"
 import { CustomModesManager } from "../config/CustomModesManager"
+import { CustomSupportPrompts, supportPrompt } from "../../shared/support-prompt"
+
+import { ACTION_NAMES } from "../CodeActionProvider"
 
 
 /*
 /*
 https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts
 https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts
@@ -108,7 +111,8 @@ type GlobalStateKey =
 	| "vsCodeLmModelSelector"
 	| "vsCodeLmModelSelector"
 	| "mode"
 	| "mode"
 	| "modeApiConfigs"
 	| "modeApiConfigs"
-	| "customPrompts"
+	| "customModePrompts"
+	| "customSupportPrompts"
 	| "enhancementApiConfigId"
 	| "enhancementApiConfigId"
 	| "experimentalDiffStrategy"
 	| "experimentalDiffStrategy"
 	| "autoApprovalEnabled"
 	| "autoApprovalEnabled"
@@ -181,6 +185,32 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 		return findLast(Array.from(this.activeInstances), (instance) => instance.view?.visible === true)
 		return findLast(Array.from(this.activeInstances), (instance) => instance.view?.visible === true)
 	}
 	}
 
 
+	public static async handleCodeAction(
+		promptType: keyof typeof ACTION_NAMES,
+		params: Record<string, string | any[]>,
+	): Promise<void> {
+		let visibleProvider = ClineProvider.getVisibleInstance()
+
+		// If no visible provider, try to show the sidebar view
+		if (!visibleProvider) {
+			await vscode.commands.executeCommand("roo-cline.SidebarProvider.focus")
+			// Wait briefly for the view to become visible
+			await delay(100)
+			visibleProvider = ClineProvider.getVisibleInstance()
+		}
+
+		// If still no visible provider, return
+		if (!visibleProvider) {
+			return
+		}
+
+		const { customSupportPrompts } = await visibleProvider.getState()
+
+		const prompt = supportPrompt.create(promptType, params, customSupportPrompts)
+
+		await visibleProvider.initClineWithTask(prompt)
+	}
+
 	resolveWebviewView(
 	resolveWebviewView(
 		webviewView: vscode.WebviewView | vscode.WebviewPanel,
 		webviewView: vscode.WebviewView | vscode.WebviewPanel,
 		//context: vscode.WebviewViewResolveContext<unknown>, used to recreate a deallocated webview, but we don't need this since we use retainContextWhenHidden
 		//context: vscode.WebviewViewResolveContext<unknown>, used to recreate a deallocated webview, but we don't need this since we use retainContextWhenHidden
@@ -267,7 +297,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 		await this.clearTask()
 		await this.clearTask()
 		const {
 		const {
 			apiConfiguration,
 			apiConfiguration,
-			customPrompts,
+			customModePrompts,
 			diffEnabled,
 			diffEnabled,
 			fuzzyMatchThreshold,
 			fuzzyMatchThreshold,
 			mode,
 			mode,
@@ -275,7 +305,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 			experimentalDiffStrategy,
 			experimentalDiffStrategy,
 		} = await this.getState()
 		} = await this.getState()
 
 
-		const modePrompt = customPrompts?.[mode]
+		const modePrompt = customModePrompts?.[mode] as PromptComponent
 		const effectiveInstructions = [globalInstructions, modePrompt?.customInstructions].filter(Boolean).join("\n\n")
 		const effectiveInstructions = [globalInstructions, modePrompt?.customInstructions].filter(Boolean).join("\n\n")
 
 
 		this.cline = new Cline(
 		this.cline = new Cline(
@@ -295,7 +325,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 		await this.clearTask()
 		await this.clearTask()
 		const {
 		const {
 			apiConfiguration,
 			apiConfiguration,
-			customPrompts,
+			customModePrompts,
 			diffEnabled,
 			diffEnabled,
 			fuzzyMatchThreshold,
 			fuzzyMatchThreshold,
 			mode,
 			mode,
@@ -303,7 +333,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 			experimentalDiffStrategy,
 			experimentalDiffStrategy,
 		} = await this.getState()
 		} = await this.getState()
 
 
-		const modePrompt = customPrompts?.[mode]
+		const modePrompt = customModePrompts?.[mode] as PromptComponent
 		const effectiveInstructions = [globalInstructions, modePrompt?.customInstructions].filter(Boolean).join("\n\n")
 		const effectiveInstructions = [globalInstructions, modePrompt?.customInstructions].filter(Boolean).join("\n\n")
 
 
 		this.cline = new Cline(
 		this.cline = new Cline(
@@ -782,47 +812,65 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 
 
 						await this.postStateToWebview()
 						await this.postStateToWebview()
 						break
 						break
-					case "updateEnhancedPrompt":
-						const existingPrompts = (await this.getGlobalState("customPrompts")) || {}
+					case "updateSupportPrompt":
+						try {
+							if (Object.keys(message?.values ?? {}).length === 0) {
+								return
+							}
+
+							const existingPrompts = (await this.getGlobalState("customSupportPrompts")) || {}
+
+							const updatedPrompts = {
+								...existingPrompts,
+								...message.values,
+							}
 
 
-						const updatedPrompts = {
-							...existingPrompts,
-							enhance: message.text,
+							await this.updateGlobalState("customSupportPrompts", updatedPrompts)
+							await this.postStateToWebview()
+						} catch (error) {
+							console.error("Error update support prompt:", error)
+							vscode.window.showErrorMessage("Failed to update support prompt")
 						}
 						}
+						break
+					case "resetSupportPrompt":
+						try {
+							if (!message?.text) {
+								return
+							}
+
+							const existingPrompts = ((await this.getGlobalState("customSupportPrompts")) ||
+								{}) as Record<string, any>
 
 
-						await this.updateGlobalState("customPrompts", updatedPrompts)
+							const updatedPrompts = {
+								...existingPrompts,
+							}
 
 
-						// Get current state and explicitly include customPrompts
-						const currentState = await this.getState()
+							updatedPrompts[message.text] = undefined
 
 
-						const stateWithPrompts = {
-							...currentState,
-							customPrompts: updatedPrompts,
+							await this.updateGlobalState("customSupportPrompts", updatedPrompts)
+							await this.postStateToWebview()
+						} catch (error) {
+							console.error("Error reset support prompt:", error)
+							vscode.window.showErrorMessage("Failed to reset support prompt")
 						}
 						}
-
-						// Post state with prompts
-						this.view?.webview.postMessage({
-							type: "state",
-							state: stateWithPrompts,
-						})
 						break
 						break
 					case "updatePrompt":
 					case "updatePrompt":
 						if (message.promptMode && message.customPrompt !== undefined) {
 						if (message.promptMode && message.customPrompt !== undefined) {
-							const existingPrompts = (await this.getGlobalState("customPrompts")) || {}
+							const existingPrompts = (await this.getGlobalState("customModePrompts")) || {}
 
 
 							const updatedPrompts = {
 							const updatedPrompts = {
 								...existingPrompts,
 								...existingPrompts,
 								[message.promptMode]: message.customPrompt,
 								[message.promptMode]: message.customPrompt,
 							}
 							}
 
 
-							await this.updateGlobalState("customPrompts", updatedPrompts)
+							await this.updateGlobalState("customModePrompts", updatedPrompts)
 
 
-							// Get current state and explicitly include customPrompts
+							// Get current state and explicitly include customModePrompts
 							const currentState = await this.getState()
 							const currentState = await this.getState()
 
 
 							const stateWithPrompts = {
 							const stateWithPrompts = {
 								...currentState,
 								...currentState,
-								customPrompts: updatedPrompts,
+								customModePrompts: updatedPrompts,
 							}
 							}
 
 
 							// Post state with prompts
 							// Post state with prompts
@@ -932,8 +980,12 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 					case "enhancePrompt":
 					case "enhancePrompt":
 						if (message.text) {
 						if (message.text) {
 							try {
 							try {
-								const { apiConfiguration, customPrompts, listApiConfigMeta, enhancementApiConfigId } =
-									await this.getState()
+								const {
+									apiConfiguration,
+									customSupportPrompts,
+									listApiConfigMeta,
+									enhancementApiConfigId,
+								} = await this.getState()
 
 
 								// Try to get enhancement config first, fall back to current config
 								// Try to get enhancement config first, fall back to current config
 								let configToUse: ApiConfiguration = apiConfiguration
 								let configToUse: ApiConfiguration = apiConfiguration
@@ -947,17 +999,17 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 									}
 									}
 								}
 								}
 
 
-								const getEnhancePrompt = (value: string | PromptComponent | undefined): string => {
-									if (typeof value === "string") {
-										return value
-									}
-									return enhance.prompt // Use the constant from modes.ts which we know is a string
-								}
-								const enhancedPrompt = await enhancePrompt(
+								const enhancedPrompt = await singleCompletionHandler(
 									configToUse,
 									configToUse,
-									message.text,
-									getEnhancePrompt(customPrompts?.enhance),
+									supportPrompt.create(
+										"ENHANCE",
+										{
+											userInput: message.text,
+										},
+										customSupportPrompts,
+									),
 								)
 								)
+
 								await this.postMessageToWebview({
 								await this.postMessageToWebview({
 									type: "enhancedPrompt",
 									type: "enhancedPrompt",
 									text: enhancedPrompt,
 									text: enhancedPrompt,
@@ -975,7 +1027,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 						try {
 						try {
 							const {
 							const {
 								apiConfiguration,
 								apiConfiguration,
-								customPrompts,
+								customModePrompts,
 								customInstructions,
 								customInstructions,
 								preferredLanguage,
 								preferredLanguage,
 								browserViewportSize,
 								browserViewportSize,
@@ -1005,7 +1057,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 								diffStrategy,
 								diffStrategy,
 								browserViewportSize ?? "900x600",
 								browserViewportSize ?? "900x600",
 								mode,
 								mode,
-								customPrompts,
+								customModePrompts,
 								customModes,
 								customModes,
 								customInstructions,
 								customInstructions,
 								preferredLanguage,
 								preferredLanguage,
@@ -1753,7 +1805,8 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 			currentApiConfigName,
 			currentApiConfigName,
 			listApiConfigMeta,
 			listApiConfigMeta,
 			mode,
 			mode,
-			customPrompts,
+			customModePrompts,
+			customSupportPrompts,
 			enhancementApiConfigId,
 			enhancementApiConfigId,
 			experimentalDiffStrategy,
 			experimentalDiffStrategy,
 			autoApprovalEnabled,
 			autoApprovalEnabled,
@@ -1792,7 +1845,8 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 			currentApiConfigName: currentApiConfigName ?? "default",
 			currentApiConfigName: currentApiConfigName ?? "default",
 			listApiConfigMeta: listApiConfigMeta ?? [],
 			listApiConfigMeta: listApiConfigMeta ?? [],
 			mode: mode ?? defaultModeSlug,
 			mode: mode ?? defaultModeSlug,
-			customPrompts: customPrompts ?? {},
+			customModePrompts: customModePrompts ?? {},
+			customSupportPrompts: customSupportPrompts ?? {},
 			enhancementApiConfigId,
 			enhancementApiConfigId,
 			experimentalDiffStrategy: experimentalDiffStrategy ?? false,
 			experimentalDiffStrategy: experimentalDiffStrategy ?? false,
 			autoApprovalEnabled: autoApprovalEnabled ?? false,
 			autoApprovalEnabled: autoApprovalEnabled ?? false,
@@ -1912,7 +1966,8 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 			vsCodeLmModelSelector,
 			vsCodeLmModelSelector,
 			mode,
 			mode,
 			modeApiConfigs,
 			modeApiConfigs,
-			customPrompts,
+			customModePrompts,
+			customSupportPrompts,
 			enhancementApiConfigId,
 			enhancementApiConfigId,
 			experimentalDiffStrategy,
 			experimentalDiffStrategy,
 			autoApprovalEnabled,
 			autoApprovalEnabled,
@@ -1977,7 +2032,8 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 			this.getGlobalState("vsCodeLmModelSelector") as Promise<vscode.LanguageModelChatSelector | undefined>,
 			this.getGlobalState("vsCodeLmModelSelector") as Promise<vscode.LanguageModelChatSelector | undefined>,
 			this.getGlobalState("mode") as Promise<Mode | undefined>,
 			this.getGlobalState("mode") as Promise<Mode | undefined>,
 			this.getGlobalState("modeApiConfigs") as Promise<Record<Mode, string> | undefined>,
 			this.getGlobalState("modeApiConfigs") as Promise<Record<Mode, string> | undefined>,
-			this.getGlobalState("customPrompts") as Promise<CustomPrompts | undefined>,
+			this.getGlobalState("customModePrompts") as Promise<CustomModePrompts | undefined>,
+			this.getGlobalState("customSupportPrompts") as Promise<CustomSupportPrompts | undefined>,
 			this.getGlobalState("enhancementApiConfigId") as Promise<string | undefined>,
 			this.getGlobalState("enhancementApiConfigId") as Promise<string | undefined>,
 			this.getGlobalState("experimentalDiffStrategy") as Promise<boolean | undefined>,
 			this.getGlobalState("experimentalDiffStrategy") as Promise<boolean | undefined>,
 			this.getGlobalState("autoApprovalEnabled") as Promise<boolean | undefined>,
 			this.getGlobalState("autoApprovalEnabled") as Promise<boolean | undefined>,
@@ -2088,7 +2144,8 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 			currentApiConfigName: currentApiConfigName ?? "default",
 			currentApiConfigName: currentApiConfigName ?? "default",
 			listApiConfigMeta: listApiConfigMeta ?? [],
 			listApiConfigMeta: listApiConfigMeta ?? [],
 			modeApiConfigs: modeApiConfigs ?? ({} as Record<Mode, string>),
 			modeApiConfigs: modeApiConfigs ?? ({} as Record<Mode, string>),
-			customPrompts: customPrompts ?? {},
+			customModePrompts: customModePrompts ?? {},
+			customSupportPrompts: customSupportPrompts ?? {},
 			enhancementApiConfigId,
 			enhancementApiConfigId,
 			experimentalDiffStrategy: experimentalDiffStrategy ?? false,
 			experimentalDiffStrategy: experimentalDiffStrategy ?? false,
 			autoApprovalEnabled: autoApprovalEnabled ?? false,
 			autoApprovalEnabled: autoApprovalEnabled ?? false,

+ 15 - 15
src/core/webview/__tests__/ClineProvider.test.ts

@@ -555,7 +555,7 @@ describe("ClineProvider", () => {
 			architect: "existing architect prompt",
 			architect: "existing architect prompt",
 		}
 		}
 		;(mockContext.globalState.get as jest.Mock).mockImplementation((key: string) => {
 		;(mockContext.globalState.get as jest.Mock).mockImplementation((key: string) => {
-			if (key === "customPrompts") {
+			if (key === "customModePrompts") {
 				return existingPrompts
 				return existingPrompts
 			}
 			}
 			return undefined
 			return undefined
@@ -569,7 +569,7 @@ describe("ClineProvider", () => {
 		})
 		})
 
 
 		// Verify state was updated correctly
 		// Verify state was updated correctly
-		expect(mockContext.globalState.update).toHaveBeenCalledWith("customPrompts", {
+		expect(mockContext.globalState.update).toHaveBeenCalledWith("customModePrompts", {
 			...existingPrompts,
 			...existingPrompts,
 			code: "new code prompt",
 			code: "new code prompt",
 		})
 		})
@@ -579,7 +579,7 @@ describe("ClineProvider", () => {
 			expect.objectContaining({
 			expect.objectContaining({
 				type: "state",
 				type: "state",
 				state: expect.objectContaining({
 				state: expect.objectContaining({
-					customPrompts: {
+					customModePrompts: {
 						...existingPrompts,
 						...existingPrompts,
 						code: "new code prompt",
 						code: "new code prompt",
 					},
 					},
@@ -588,17 +588,17 @@ describe("ClineProvider", () => {
 		)
 		)
 	})
 	})
 
 
-	test("customPrompts defaults to empty object", async () => {
-		// Mock globalState.get to return undefined for customPrompts
+	test("customModePrompts defaults to empty object", async () => {
+		// Mock globalState.get to return undefined for customModePrompts
 		;(mockContext.globalState.get as jest.Mock).mockImplementation((key: string) => {
 		;(mockContext.globalState.get as jest.Mock).mockImplementation((key: string) => {
-			if (key === "customPrompts") {
+			if (key === "customModePrompts") {
 				return undefined
 				return undefined
 			}
 			}
 			return null
 			return null
 		})
 		})
 
 
 		const state = await provider.getState()
 		const state = await provider.getState()
-		expect(state.customPrompts).toEqual({})
+		expect(state.customModePrompts).toEqual({})
 	})
 	})
 
 
 	test("uses mode-specific custom instructions in Cline initialization", async () => {
 	test("uses mode-specific custom instructions in Cline initialization", async () => {
@@ -611,7 +611,7 @@ describe("ClineProvider", () => {
 
 
 		jest.spyOn(provider, "getState").mockResolvedValue({
 		jest.spyOn(provider, "getState").mockResolvedValue({
 			apiConfiguration: mockApiConfig,
 			apiConfiguration: mockApiConfig,
-			customPrompts: {
+			customModePrompts: {
 				code: { customInstructions: modeCustomInstructions },
 				code: { customInstructions: modeCustomInstructions },
 			},
 			},
 			mode: "code",
 			mode: "code",
@@ -651,7 +651,7 @@ describe("ClineProvider", () => {
 			},
 			},
 		}
 		}
 		mockContext.globalState.get = jest.fn((key: string) => {
 		mockContext.globalState.get = jest.fn((key: string) => {
-			if (key === "customPrompts") {
+			if (key === "customModePrompts") {
 				return existingPrompts
 				return existingPrompts
 			}
 			}
 			return undefined
 			return undefined
@@ -668,7 +668,7 @@ describe("ClineProvider", () => {
 		})
 		})
 
 
 		// Verify state was updated correctly
 		// Verify state was updated correctly
-		expect(mockContext.globalState.update).toHaveBeenCalledWith("customPrompts", {
+		expect(mockContext.globalState.update).toHaveBeenCalledWith("customModePrompts", {
 			code: {
 			code: {
 				roleDefinition: "Code role",
 				roleDefinition: "Code role",
 				customInstructions: "New instructions",
 				customInstructions: "New instructions",
@@ -978,7 +978,7 @@ describe("ClineProvider", () => {
 					apiModelId: "test-model",
 					apiModelId: "test-model",
 					openRouterModelInfo: { supportsComputerUse: true },
 					openRouterModelInfo: { supportsComputerUse: true },
 				},
 				},
-				customPrompts: {},
+				customModePrompts: {},
 				mode: "code",
 				mode: "code",
 				mcpEnabled: false,
 				mcpEnabled: false,
 				browserViewportSize: "900x600",
 				browserViewportSize: "900x600",
@@ -1007,7 +1007,7 @@ describe("ClineProvider", () => {
 				}),
 				}),
 				"900x600", // browserViewportSize
 				"900x600", // browserViewportSize
 				"code", // mode
 				"code", // mode
-				{}, // customPrompts
+				{}, // customModePrompts
 				{}, // customModes
 				{}, // customModes
 				undefined, // effectiveInstructions
 				undefined, // effectiveInstructions
 				undefined, // preferredLanguage
 				undefined, // preferredLanguage
@@ -1027,7 +1027,7 @@ describe("ClineProvider", () => {
 					apiModelId: "test-model",
 					apiModelId: "test-model",
 					openRouterModelInfo: { supportsComputerUse: true },
 					openRouterModelInfo: { supportsComputerUse: true },
 				},
 				},
-				customPrompts: {},
+				customModePrompts: {},
 				mode: "code",
 				mode: "code",
 				mcpEnabled: false,
 				mcpEnabled: false,
 				browserViewportSize: "900x600",
 				browserViewportSize: "900x600",
@@ -1056,7 +1056,7 @@ describe("ClineProvider", () => {
 				}),
 				}),
 				"900x600", // browserViewportSize
 				"900x600", // browserViewportSize
 				"code", // mode
 				"code", // mode
-				{}, // customPrompts
+				{}, // customModePrompts
 				{}, // customModes
 				{}, // customModes
 				undefined, // effectiveInstructions
 				undefined, // effectiveInstructions
 				undefined, // preferredLanguage
 				undefined, // preferredLanguage
@@ -1071,7 +1071,7 @@ describe("ClineProvider", () => {
 					apiProvider: "openrouter",
 					apiProvider: "openrouter",
 					openRouterModelInfo: { supportsComputerUse: true },
 					openRouterModelInfo: { supportsComputerUse: true },
 				},
 				},
-				customPrompts: {
+				customModePrompts: {
 					architect: { customInstructions: "Architect mode instructions" },
 					architect: { customInstructions: "Architect mode instructions" },
 				},
 				},
 				mode: "architect",
 				mode: "architect",

+ 49 - 0
src/extension.ts

@@ -5,6 +5,7 @@ import * as vscode from "vscode"
 import { ClineProvider } from "./core/webview/ClineProvider"
 import { ClineProvider } from "./core/webview/ClineProvider"
 import { createClineAPI } from "./exports"
 import { createClineAPI } from "./exports"
 import "./utils/path" // necessary to have access to String.prototype.toPosix
 import "./utils/path" // necessary to have access to String.prototype.toPosix
+import { ACTION_NAMES, CodeActionProvider } from "./core/CodeActionProvider"
 import { DIFF_VIEW_URI_SCHEME } from "./integrations/editor/DiffViewProvider"
 import { DIFF_VIEW_URI_SCHEME } from "./integrations/editor/DiffViewProvider"
 
 
 /*
 /*
@@ -158,6 +159,54 @@ export function activate(context: vscode.ExtensionContext) {
 	}
 	}
 	context.subscriptions.push(vscode.window.registerUriHandler({ handleUri }))
 	context.subscriptions.push(vscode.window.registerUriHandler({ handleUri }))
 
 
+	// Register code actions provider
+	context.subscriptions.push(
+		vscode.languages.registerCodeActionsProvider({ pattern: "**/*" }, new CodeActionProvider(), {
+			providedCodeActionKinds: CodeActionProvider.providedCodeActionKinds,
+		}),
+	)
+
+	// Helper function to handle code actions
+	const registerCodeAction = (
+		context: vscode.ExtensionContext,
+		command: string,
+		promptType: keyof typeof ACTION_NAMES,
+		inputPrompt?: string,
+		inputPlaceholder?: string,
+	) => {
+		let userInput: string | undefined
+
+		context.subscriptions.push(
+			vscode.commands.registerCommand(
+				command,
+				async (filePath: string, selectedText: string, diagnostics?: any[]) => {
+					if (inputPrompt) {
+						userInput = await vscode.window.showInputBox({
+							prompt: inputPrompt,
+							placeHolder: inputPlaceholder,
+						})
+					}
+
+					const params = {
+						filePath,
+						selectedText,
+						...(diagnostics ? { diagnostics } : {}),
+						...(userInput ? { userInput } : {}),
+					}
+
+					await ClineProvider.handleCodeAction(promptType, params)
+				},
+			),
+		)
+	}
+
+	// Register code action commands
+	registerCodeAction(context, "roo-cline.explainCode", "EXPLAIN")
+
+	registerCodeAction(context, "roo-cline.fixCode", "FIX")
+
+	registerCodeAction(context, "roo-cline.improveCode", "IMPROVE")
+
 	return createClineAPI(outputChannel, sidebarProvider)
 	return createClineAPI(outputChannel, sidebarProvider)
 }
 }
 
 

+ 4 - 2
src/shared/ExtensionMessage.ts

@@ -4,7 +4,8 @@ import { ApiConfiguration, ApiProvider, ModelInfo } from "./api"
 import { HistoryItem } from "./HistoryItem"
 import { HistoryItem } from "./HistoryItem"
 import { McpServer } from "./mcp"
 import { McpServer } from "./mcp"
 import { GitCommit } from "../utils/git"
 import { GitCommit } from "../utils/git"
-import { Mode, CustomPrompts, ModeConfig } from "./modes"
+import { Mode, CustomModePrompts, ModeConfig } from "./modes"
+import { CustomSupportPrompts } from "./support-prompt"
 
 
 export interface LanguageModelChatSelector {
 export interface LanguageModelChatSelector {
 	vendor?: string
 	vendor?: string
@@ -82,7 +83,8 @@ export interface ExtensionState {
 	currentApiConfigName?: string
 	currentApiConfigName?: string
 	listApiConfigMeta?: ApiConfigMeta[]
 	listApiConfigMeta?: ApiConfigMeta[]
 	customInstructions?: string
 	customInstructions?: string
-	customPrompts?: CustomPrompts
+	customModePrompts?: CustomModePrompts
+	customSupportPrompts?: CustomSupportPrompts
 	alwaysAllowReadOnly?: boolean
 	alwaysAllowReadOnly?: boolean
 	alwaysAllowWrite?: boolean
 	alwaysAllowWrite?: boolean
 	alwaysAllowExecute?: boolean
 	alwaysAllowExecute?: boolean

+ 2 - 1
src/shared/WebviewMessage.ts

@@ -68,7 +68,8 @@ export interface WebviewMessage {
 		| "requestVsCodeLmModels"
 		| "requestVsCodeLmModels"
 		| "mode"
 		| "mode"
 		| "updatePrompt"
 		| "updatePrompt"
-		| "updateEnhancedPrompt"
+		| "updateSupportPrompt"
+		| "resetSupportPrompt"
 		| "getSystemPrompt"
 		| "getSystemPrompt"
 		| "systemPrompt"
 		| "systemPrompt"
 		| "enhancementApiConfigId"
 		| "enhancementApiConfigId"

+ 153 - 0
src/shared/__tests__/support-prompts.test.ts

@@ -0,0 +1,153 @@
+import { supportPrompt } from "../support-prompt"
+
+describe("Code Action Prompts", () => {
+	const testFilePath = "test/file.ts"
+	const testCode = "function test() { return true; }"
+
+	describe("EXPLAIN action", () => {
+		it("should format explain prompt correctly", () => {
+			const prompt = supportPrompt.create("EXPLAIN", {
+				filePath: testFilePath,
+				selectedText: testCode,
+			})
+
+			expect(prompt).toContain(`@/${testFilePath}`)
+			expect(prompt).toContain(testCode)
+			expect(prompt).toContain("purpose and functionality")
+			expect(prompt).toContain("Key components")
+			expect(prompt).toContain("Important patterns")
+		})
+	})
+
+	describe("FIX action", () => {
+		it("should format fix prompt without diagnostics", () => {
+			const prompt = supportPrompt.create("FIX", {
+				filePath: testFilePath,
+				selectedText: testCode,
+			})
+
+			expect(prompt).toContain(`@/${testFilePath}`)
+			expect(prompt).toContain(testCode)
+			expect(prompt).toContain("Address all detected problems")
+			expect(prompt).not.toContain("Current problems detected")
+		})
+
+		it("should format fix prompt with diagnostics", () => {
+			const diagnostics = [
+				{
+					source: "eslint",
+					message: "Missing semicolon",
+					code: "semi",
+				},
+				{
+					message: "Unused variable",
+					severity: 1,
+				},
+			]
+
+			const prompt = supportPrompt.create("FIX", {
+				filePath: testFilePath,
+				selectedText: testCode,
+				diagnostics,
+			})
+
+			expect(prompt).toContain("Current problems detected:")
+			expect(prompt).toContain("[eslint] Missing semicolon (semi)")
+			expect(prompt).toContain("[Error] Unused variable")
+			expect(prompt).toContain(testCode)
+		})
+	})
+
+	describe("IMPROVE action", () => {
+		it("should format improve prompt correctly", () => {
+			const prompt = supportPrompt.create("IMPROVE", {
+				filePath: testFilePath,
+				selectedText: testCode,
+			})
+
+			expect(prompt).toContain(`@/${testFilePath}`)
+			expect(prompt).toContain(testCode)
+			expect(prompt).toContain("Code readability")
+			expect(prompt).toContain("Performance optimization")
+			expect(prompt).toContain("Best practices")
+			expect(prompt).toContain("Error handling")
+		})
+	})
+
+	describe("ENHANCE action", () => {
+		it("should format enhance prompt correctly", () => {
+			const prompt = supportPrompt.create("ENHANCE", {
+				userInput: "test",
+			})
+
+			expect(prompt).toBe(
+				"Generate an enhanced version of this prompt (reply with only the enhanced prompt - no conversation, explanations, lead-in, bullet points, placeholders, or surrounding quotes):\n\ntest",
+			)
+			// Verify it ignores parameters since ENHANCE template doesn't use any
+			expect(prompt).not.toContain(testFilePath)
+			expect(prompt).not.toContain(testCode)
+		})
+	})
+
+	describe("get template", () => {
+		it("should return default template when no custom prompts provided", () => {
+			const template = supportPrompt.get(undefined, "EXPLAIN")
+			expect(template).toBe(supportPrompt.default.EXPLAIN)
+		})
+
+		it("should return custom template when provided", () => {
+			const customTemplate = "Custom template for explaining code"
+			const customSupportPrompts = {
+				EXPLAIN: customTemplate,
+			}
+			const template = supportPrompt.get(customSupportPrompts, "EXPLAIN")
+			expect(template).toBe(customTemplate)
+		})
+
+		it("should return default template when custom prompts does not include type", () => {
+			const customSupportPrompts = {
+				SOMETHING_ELSE: "Other template",
+			}
+			const template = supportPrompt.get(customSupportPrompts, "EXPLAIN")
+			expect(template).toBe(supportPrompt.default.EXPLAIN)
+		})
+	})
+
+	describe("create with custom prompts", () => {
+		it("should use custom template when provided", () => {
+			const customTemplate = "Custom template for ${filePath}"
+			const customSupportPrompts = {
+				EXPLAIN: customTemplate,
+			}
+
+			const prompt = supportPrompt.create(
+				"EXPLAIN",
+				{
+					filePath: testFilePath,
+					selectedText: testCode,
+				},
+				customSupportPrompts,
+			)
+
+			expect(prompt).toContain(`Custom template for ${testFilePath}`)
+			expect(prompt).not.toContain("purpose and functionality")
+		})
+
+		it("should use default template when custom prompts does not include type", () => {
+			const customSupportPrompts = {
+				EXPLAIN: "Other template",
+			}
+
+			const prompt = supportPrompt.create(
+				"EXPLAIN",
+				{
+					filePath: testFilePath,
+					selectedText: testCode,
+				},
+				customSupportPrompts,
+			)
+
+			expect(prompt).toContain("Other template")
+		})
+	})
+})

+ 11 - 28
src/shared/modes.ts

@@ -12,6 +12,16 @@ export type ModeConfig = {
 	groups: readonly ToolGroup[] // Now uses groups instead of tools array
 	groups: readonly ToolGroup[] // Now uses groups instead of tools array
 }
 }
 
 
+// Mode-specific prompts only
+export type PromptComponent = {
+	roleDefinition?: string
+	customInstructions?: string
+}
+
+export type CustomModePrompts = {
+	[key: string]: PromptComponent | undefined
+}
+
 // Helper to get all tools for a mode
 // Helper to get all tools for a mode
 export function getToolsForMode(groups: readonly ToolGroup[]): string[] {
 export function getToolsForMode(groups: readonly ToolGroup[]): string[] {
 	const tools = new Set<string>()
 	const tools = new Set<string>()
@@ -130,35 +140,8 @@ export function isToolAllowedForMode(
 	return mode.groups.some((group) => TOOL_GROUPS[group].includes(tool as string))
 	return mode.groups.some((group) => TOOL_GROUPS[group].includes(tool as string))
 }
 }
 
 
-export type PromptComponent = {
-	roleDefinition?: string
-	customInstructions?: string
-}
-
-// Mode-specific prompts only
-export type CustomPrompts = {
-	[key: string]: PromptComponent | undefined
-}
-
-// Separate enhance prompt type and definition
-export type EnhanceConfig = {
-	prompt: string
-}
-
-export const enhance: EnhanceConfig = {
-	prompt: "Generate an enhanced version of this prompt (reply with only the enhanced prompt - no conversation, explanations, lead-in, bullet points, placeholders, or surrounding quotes):",
-} as const
-
-// Completely separate enhance prompt handling
-export const enhancePrompt = {
-	default: enhance.prompt,
-	get: (customPrompts: Record<string, any> | undefined): string => {
-		return customPrompts?.enhance ?? enhance.prompt
-	},
-} as const
-
 // Create the mode-specific default prompts
 // Create the mode-specific default prompts
-export const defaultPrompts: Readonly<CustomPrompts> = Object.freeze(
+export const defaultPrompts: Readonly<CustomModePrompts> = Object.freeze(
 	Object.fromEntries(modes.map((mode) => [mode.slug, { roleDefinition: mode.roleDefinition }])),
 	Object.fromEntries(modes.map((mode) => [mode.slug, { roleDefinition: mode.roleDefinition }])),
 )
 )
 
 

+ 123 - 0
src/shared/support-prompt.ts

@@ -0,0 +1,123 @@
+// Support prompts
+type PromptParams = Record<string, string | any[]>
+
+const generateDiagnosticText = (diagnostics?: any[]) => {
+	if (!diagnostics?.length) return ""
+	return `\nCurrent problems detected:\n${diagnostics
+		.map((d) => `- [${d.source || "Error"}] ${d.message}${d.code ? ` (${d.code})` : ""}`)
+		.join("\n")}`
+}
+
+export const createPrompt = (template: string, params: PromptParams): string => {
+	let result = template
+	for (const [key, value] of Object.entries(params)) {
+		if (key === "diagnostics") {
+			result = result.replaceAll("${diagnosticText}", generateDiagnosticText(value as any[]))
+		} else {
+			result = result.replaceAll(`\${${key}}`, value as string)
+		}
+	}
+
+	// Replace any remaining user_input placeholders with empty string
+	result = result.replaceAll("${userInput}", "")
+
+	return result
+}
+
+interface SupportPromptConfig {
+	label: string
+	description: string
+	template: string
+}
+
+const supportPromptConfigs: Record<string, SupportPromptConfig> = {
+	ENHANCE: {
+		label: "Enhance Prompt",
+		description:
+			"Use prompt enhancement to get tailored suggestions or improvements for your inputs. This ensures Roo understands your intent and provides the best possible responses. Available via the ✨ icon in chat.",
+		template: `Generate an enhanced version of this prompt (reply with only the enhanced prompt - no conversation, explanations, lead-in, bullet points, placeholders, or surrounding quotes):
+
+\${userInput}`,
+	},
+	EXPLAIN: {
+		label: "Explain Code",
+		description:
+			"Get detailed explanations of code snippets, functions, or entire files. Useful for understanding complex code or learning new patterns. Available in the editor context menu (right-click on selected code).",
+		template: `Explain the following code from file path @/\${filePath}:
+\${userInput}
+
+\`\`\`
+\${selectedText}
+\`\`\`
+
+Please provide a clear and concise explanation of what this code does, including:
+1. The purpose and functionality
+2. Key components and their interactions
+3. Important patterns or techniques used`,
+	},
+	FIX: {
+		label: "Fix Issues",
+		description:
+			"Get help identifying and resolving bugs, errors, or code quality issues. Provides step-by-step guidance for fixing problems. Available in the editor context menu (right-click on selected code).",
+		template: `Fix any issues in the following code from file path @/\${filePath}
+\${diagnosticText}
+\${userInput}
+
+\`\`\`
+\${selectedText}
+\`\`\`
+
+Please:
+1. Address all detected problems listed above (if any)
+2. Identify any other potential bugs or issues
+3. Provide corrected code
+4. Explain what was fixed and why`,
+	},
+	IMPROVE: {
+		label: "Improve Code",
+		description:
+			"Receive suggestions for code optimization, better practices, and architectural improvements while maintaining functionality. Available in the editor context menu (right-click on selected code).",
+		template: `Improve the following code from file path @/\${filePath}:
+\${userInput}
+
+\`\`\`
+\${selectedText}
+\`\`\`
+
+Please suggest improvements for:
+1. Code readability and maintainability
+2. Performance optimization
+3. Best practices and patterns
+4. Error handling and edge cases
+
+Provide the improved code along with explanations for each enhancement.`,
+	},
+} as const
+
+type SupportPromptType = keyof typeof supportPromptConfigs
+
+export const supportPrompt = {
+	default: Object.fromEntries(Object.entries(supportPromptConfigs).map(([key, config]) => [key, config.template])),
+	get: (customSupportPrompts: Record<string, any> | undefined, type: SupportPromptType): string => {
+		return customSupportPrompts?.[type] ?? supportPromptConfigs[type].template
+	},
+	create: (type: SupportPromptType, params: PromptParams, customSupportPrompts?: Record<string, any>): string => {
+		const template = supportPrompt.get(customSupportPrompts, type)
+		return createPrompt(template, params)
+	},
+} as const
+
+export type { SupportPromptType }
+
+// Expose labels and descriptions for UI
+export const supportPromptLabels = Object.fromEntries(
+	Object.entries(supportPromptConfigs).map(([key, config]) => [key, config.label]),
+) as Record<SupportPromptType, string>
+
+export const supportPromptDescriptions = Object.fromEntries(
+	Object.entries(supportPromptConfigs).map(([key, config]) => [key, config.description]),
+) as Record<SupportPromptType, string>
+
+export type CustomSupportPrompts = {
+	[key: string]: string | undefined
+}

+ 12 - 14
src/test/extension.test.ts

@@ -8,8 +8,8 @@ const dotenv = require("dotenv")
 const testEnvPath = path.join(__dirname, ".test_env")
 const testEnvPath = path.join(__dirname, ".test_env")
 dotenv.config({ path: testEnvPath })
 dotenv.config({ path: testEnvPath })
 
 
-suite("Roo Cline Extension Test Suite", () => {
-	vscode.window.showInformationMessage("Starting Roo Cline extension tests.")
+suite("Roo Code Extension Test Suite", () => {
+	vscode.window.showInformationMessage("Starting Roo Code extension tests.")
 
 
 	test("Extension should be present", () => {
 	test("Extension should be present", () => {
 		const extension = vscode.extensions.getExtension("RooVeterinaryInc.roo-cline")
 		const extension = vscode.extensions.getExtension("RooVeterinaryInc.roo-cline")
@@ -123,6 +123,9 @@ suite("Roo Cline Extension Test Suite", () => {
 			"roo-cline.popoutButtonClicked",
 			"roo-cline.popoutButtonClicked",
 			"roo-cline.settingsButtonClicked",
 			"roo-cline.settingsButtonClicked",
 			"roo-cline.openInNewTab",
 			"roo-cline.openInNewTab",
+			"roo-cline.explainCode",
+			"roo-cline.fixCode",
+			"roo-cline.improveCode",
 		]
 		]
 
 
 		for (const cmd of expectedCommands) {
 		for (const cmd of expectedCommands) {
@@ -133,7 +136,7 @@ suite("Roo Cline Extension Test Suite", () => {
 	test("Views should be registered", () => {
 	test("Views should be registered", () => {
 		const view = vscode.window.createWebviewPanel(
 		const view = vscode.window.createWebviewPanel(
 			"roo-cline.SidebarProvider",
 			"roo-cline.SidebarProvider",
-			"Roo Cline",
+			"Roo Code",
 			vscode.ViewColumn.One,
 			vscode.ViewColumn.One,
 			{},
 			{},
 		)
 		)
@@ -181,17 +184,12 @@ suite("Roo Cline Extension Test Suite", () => {
 
 
 		// Create webview panel with development options
 		// Create webview panel with development options
 		const extensionUri = extension.extensionUri
 		const extensionUri = extension.extensionUri
-		const panel = vscode.window.createWebviewPanel(
-			"roo-cline.SidebarProvider",
-			"Roo Cline",
-			vscode.ViewColumn.One,
-			{
-				enableScripts: true,
-				enableCommandUris: true,
-				retainContextWhenHidden: true,
-				localResourceRoots: [extensionUri],
-			},
-		)
+		const panel = vscode.window.createWebviewPanel("roo-cline.SidebarProvider", "Roo Code", vscode.ViewColumn.One, {
+			enableScripts: true,
+			enableCommandUris: true,
+			retainContextWhenHidden: true,
+			localResourceRoots: [extensionUri],
+		})
 
 
 		try {
 		try {
 			// Initialize webview with development context
 			// Initialize webview with development context

+ 23 - 11
src/utils/__tests__/enhance-prompt.test.ts

@@ -1,7 +1,7 @@
-import { enhancePrompt } from "../enhance-prompt"
+import { singleCompletionHandler } from "../single-completion-handler"
 import { ApiConfiguration } from "../../shared/api"
 import { ApiConfiguration } from "../../shared/api"
 import { buildApiHandler, SingleCompletionHandler } from "../../api"
 import { buildApiHandler, SingleCompletionHandler } from "../../api"
-import { defaultPrompts } from "../../shared/modes"
+import { supportPrompt } from "../../shared/support-prompt"
 
 
 // Mock the API handler
 // Mock the API handler
 jest.mock("../../api", () => ({
 jest.mock("../../api", () => ({
@@ -34,17 +34,29 @@ describe("enhancePrompt", () => {
 	})
 	})
 
 
 	it("enhances prompt using default enhancement prompt when no custom prompt provided", async () => {
 	it("enhances prompt using default enhancement prompt when no custom prompt provided", async () => {
-		const result = await enhancePrompt(mockApiConfig, "Test prompt")
+		const result = await singleCompletionHandler(mockApiConfig, "Test prompt")
 
 
 		expect(result).toBe("Enhanced prompt")
 		expect(result).toBe("Enhanced prompt")
 		const handler = buildApiHandler(mockApiConfig)
 		const handler = buildApiHandler(mockApiConfig)
-		expect((handler as any).completePrompt).toHaveBeenCalledWith(`${defaultPrompts.enhance}\n\nTest prompt`)
+		expect((handler as any).completePrompt).toHaveBeenCalledWith(`Test prompt`)
 	})
 	})
 
 
 	it("enhances prompt using custom enhancement prompt when provided", async () => {
 	it("enhances prompt using custom enhancement prompt when provided", async () => {
 		const customEnhancePrompt = "You are a custom prompt enhancer"
 		const customEnhancePrompt = "You are a custom prompt enhancer"
-
-		const result = await enhancePrompt(mockApiConfig, "Test prompt", customEnhancePrompt)
+		const customEnhancePromptWithTemplate = customEnhancePrompt + "\n\n${userInput}"
+
+		const result = await singleCompletionHandler(
+			mockApiConfig,
+			supportPrompt.create(
+				"ENHANCE",
+				{
+					userInput: "Test prompt",
+				},
+				{
+					ENHANCE: customEnhancePromptWithTemplate,
+				},
+			),
+		)
 
 
 		expect(result).toBe("Enhanced prompt")
 		expect(result).toBe("Enhanced prompt")
 		const handler = buildApiHandler(mockApiConfig)
 		const handler = buildApiHandler(mockApiConfig)
@@ -52,11 +64,11 @@ describe("enhancePrompt", () => {
 	})
 	})
 
 
 	it("throws error for empty prompt input", async () => {
 	it("throws error for empty prompt input", async () => {
-		await expect(enhancePrompt(mockApiConfig, "")).rejects.toThrow("No prompt text provided")
+		await expect(singleCompletionHandler(mockApiConfig, "")).rejects.toThrow("No prompt text provided")
 	})
 	})
 
 
 	it("throws error for missing API configuration", async () => {
 	it("throws error for missing API configuration", async () => {
-		await expect(enhancePrompt({} as ApiConfiguration, "Test prompt")).rejects.toThrow(
+		await expect(singleCompletionHandler({} as ApiConfiguration, "Test prompt")).rejects.toThrow(
 			"No valid API configuration provided",
 			"No valid API configuration provided",
 		)
 		)
 	})
 	})
@@ -75,7 +87,7 @@ describe("enhancePrompt", () => {
 			}),
 			}),
 		})
 		})
 
 
-		await expect(enhancePrompt(mockApiConfig, "Test prompt")).rejects.toThrow(
+		await expect(singleCompletionHandler(mockApiConfig, "Test prompt")).rejects.toThrow(
 			"The selected API provider does not support prompt enhancement",
 			"The selected API provider does not support prompt enhancement",
 		)
 		)
 	})
 	})
@@ -101,7 +113,7 @@ describe("enhancePrompt", () => {
 			}),
 			}),
 		} as unknown as SingleCompletionHandler)
 		} as unknown as SingleCompletionHandler)
 
 
-		const result = await enhancePrompt(openRouterConfig, "Test prompt")
+		const result = await singleCompletionHandler(openRouterConfig, "Test prompt")
 
 
 		expect(buildApiHandler).toHaveBeenCalledWith(openRouterConfig)
 		expect(buildApiHandler).toHaveBeenCalledWith(openRouterConfig)
 		expect(result).toBe("Enhanced prompt")
 		expect(result).toBe("Enhanced prompt")
@@ -121,6 +133,6 @@ describe("enhancePrompt", () => {
 			}),
 			}),
 		} as unknown as SingleCompletionHandler)
 		} as unknown as SingleCompletionHandler)
 
 
-		await expect(enhancePrompt(mockApiConfig, "Test prompt")).rejects.toThrow("API Error")
+		await expect(singleCompletionHandler(mockApiConfig, "Test prompt")).rejects.toThrow("API Error")
 	})
 	})
 })
 })

+ 2 - 9
src/utils/enhance-prompt.ts → src/utils/single-completion-handler.ts

@@ -1,16 +1,11 @@
 import { ApiConfiguration } from "../shared/api"
 import { ApiConfiguration } from "../shared/api"
 import { buildApiHandler, SingleCompletionHandler } from "../api"
 import { buildApiHandler, SingleCompletionHandler } from "../api"
-import { defaultPrompts } from "../shared/modes"
 
 
 /**
 /**
  * Enhances a prompt using the configured API without creating a full Cline instance or task history.
  * Enhances a prompt using the configured API without creating a full Cline instance or task history.
  * This is a lightweight alternative that only uses the API's completion functionality.
  * This is a lightweight alternative that only uses the API's completion functionality.
  */
  */
-export async function enhancePrompt(
-	apiConfiguration: ApiConfiguration,
-	promptText: string,
-	enhancePrompt?: string,
-): Promise<string> {
+export async function singleCompletionHandler(apiConfiguration: ApiConfiguration, promptText: string): Promise<string> {
 	if (!promptText) {
 	if (!promptText) {
 		throw new Error("No prompt text provided")
 		throw new Error("No prompt text provided")
 	}
 	}
@@ -25,7 +20,5 @@ export async function enhancePrompt(
 		throw new Error("The selected API provider does not support prompt enhancement")
 		throw new Error("The selected API provider does not support prompt enhancement")
 	}
 	}
 
 
-	const enhancePromptText = enhancePrompt ?? defaultPrompts.enhance
-	const prompt = `${enhancePromptText}\n\n${promptText}`
-	return (handler as SingleCompletionHandler).completePrompt(prompt)
+	return (handler as SingleCompletionHandler).completePrompt(promptText)
 }
 }

+ 4 - 3
webview-ui/src/components/chat/__tests__/AutoApproveMenu.test.tsx

@@ -1,7 +1,7 @@
 import { render, fireEvent, screen } from "@testing-library/react"
 import { render, fireEvent, screen } from "@testing-library/react"
 import { useExtensionState } from "../../../context/ExtensionStateContext"
 import { useExtensionState } from "../../../context/ExtensionStateContext"
 import AutoApproveMenu from "../AutoApproveMenu"
 import AutoApproveMenu from "../AutoApproveMenu"
-import { codeMode, defaultPrompts } from "../../../../../src/shared/modes"
+import { defaultModeSlug, defaultPrompts } from "../../../../../src/shared/modes"
 
 
 // Mock the ExtensionStateContext hook
 // Mock the ExtensionStateContext hook
 jest.mock("../../../context/ExtensionStateContext")
 jest.mock("../../../context/ExtensionStateContext")
@@ -29,8 +29,9 @@ describe("AutoApproveMenu", () => {
 		requestDelaySeconds: 5,
 		requestDelaySeconds: 5,
 		currentApiConfigName: "default",
 		currentApiConfigName: "default",
 		listApiConfigMeta: [],
 		listApiConfigMeta: [],
-		mode: codeMode,
-		customPrompts: defaultPrompts,
+		mode: defaultModeSlug,
+		customModePrompts: defaultPrompts,
+		customSupportPrompts: {},
 		enhancementApiConfigId: "",
 		enhancementApiConfigId: "",
 		didHydrateState: true,
 		didHydrateState: true,
 		showWelcome: false,
 		showWelcome: false,

+ 194 - 130
webview-ui/src/components/prompts/PromptsView.tsx

@@ -8,14 +8,14 @@ import {
 	VSCodeCheckbox,
 	VSCodeCheckbox,
 } from "@vscode/webview-ui-toolkit/react"
 } from "@vscode/webview-ui-toolkit/react"
 import { useExtensionState } from "../../context/ExtensionStateContext"
 import { useExtensionState } from "../../context/ExtensionStateContext"
+import { Mode, PromptComponent, getRoleDefinition, getAllModes, ModeConfig } from "../../../../src/shared/modes"
 import {
 import {
-	Mode,
-	PromptComponent,
-	getRoleDefinition,
-	getAllModes,
-	ModeConfig,
-	enhancePrompt,
-} from "../../../../src/shared/modes"
+	supportPrompt,
+	SupportPromptType,
+	supportPromptLabels,
+	supportPromptDescriptions,
+} from "../../../../src/shared/support-prompt"
+
 import { TOOL_GROUPS, GROUP_DISPLAY_NAMES, ToolGroup } from "../../../../src/shared/tool-groups"
 import { TOOL_GROUPS, GROUP_DISPLAY_NAMES, ToolGroup } from "../../../../src/shared/tool-groups"
 import { vscode } from "../../utils/vscode"
 import { vscode } from "../../utils/vscode"
 
 
@@ -28,7 +28,8 @@ type PromptsViewProps = {
 
 
 const PromptsView = ({ onDone }: PromptsViewProps) => {
 const PromptsView = ({ onDone }: PromptsViewProps) => {
 	const {
 	const {
-		customPrompts,
+		customModePrompts,
+		customSupportPrompts,
 		listApiConfigMeta,
 		listApiConfigMeta,
 		enhancementApiConfigId,
 		enhancementApiConfigId,
 		setEnhancementApiConfigId,
 		setEnhancementApiConfigId,
@@ -50,11 +51,12 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
 	const [selectedPromptTitle, setSelectedPromptTitle] = useState("")
 	const [selectedPromptTitle, setSelectedPromptTitle] = useState("")
 	const [isToolsEditMode, setIsToolsEditMode] = useState(false)
 	const [isToolsEditMode, setIsToolsEditMode] = useState(false)
 	const [isCreateModeDialogOpen, setIsCreateModeDialogOpen] = useState(false)
 	const [isCreateModeDialogOpen, setIsCreateModeDialogOpen] = useState(false)
+	const [activeSupportTab, setActiveSupportTab] = useState<SupportPromptType>("ENHANCE")
 
 
 	// Direct update functions
 	// Direct update functions
 	const updateAgentPrompt = useCallback(
 	const updateAgentPrompt = useCallback(
 		(mode: Mode, promptData: PromptComponent) => {
 		(mode: Mode, promptData: PromptComponent) => {
-			const existingPrompt = customPrompts?.[mode]
+			const existingPrompt = customModePrompts?.[mode] as PromptComponent
 			const updatedPrompt = { ...existingPrompt, ...promptData }
 			const updatedPrompt = { ...existingPrompt, ...promptData }
 
 
 			// Only include properties that differ from defaults
 			// Only include properties that differ from defaults
@@ -68,7 +70,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
 				customPrompt: updatedPrompt,
 				customPrompt: updatedPrompt,
 			})
 			})
 		},
 		},
-		[customPrompts],
+		[customModePrompts],
 	)
 	)
 
 
 	const updateCustomMode = useCallback((slug: string, modeConfig: ModeConfig) => {
 	const updateCustomMode = useCallback((slug: string, modeConfig: ModeConfig) => {
@@ -254,36 +256,33 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
 		return () => window.removeEventListener("message", handler)
 		return () => window.removeEventListener("message", handler)
 	}, [])
 	}, [])
 
 
-	const updateEnhancePrompt = (value: string | undefined) => {
+	const updateSupportPrompt = (type: SupportPromptType, value: string | undefined) => {
 		vscode.postMessage({
 		vscode.postMessage({
-			type: "updateEnhancedPrompt",
-			text: value,
+			type: "updateSupportPrompt",
+			values: {
+				[type]: value,
+			},
 		})
 		})
 	}
 	}
 
 
-	const handleEnhancePromptChange = (e: Event | React.FormEvent<HTMLElement>): void => {
-		const value = (e as CustomEvent)?.detail?.target?.value || ((e as any).target as HTMLTextAreaElement).value
-		const trimmedValue = value.trim()
-		if (trimmedValue !== enhancePrompt.default) {
-			updateEnhancePrompt(trimmedValue || enhancePrompt.default)
-		}
-	}
-
 	const handleAgentReset = (modeSlug: string) => {
 	const handleAgentReset = (modeSlug: string) => {
 		// Only reset role definition for built-in modes
 		// Only reset role definition for built-in modes
-		const existingPrompt = customPrompts?.[modeSlug]
+		const existingPrompt = customModePrompts?.[modeSlug] as PromptComponent
 		updateAgentPrompt(modeSlug, {
 		updateAgentPrompt(modeSlug, {
 			...existingPrompt,
 			...existingPrompt,
 			roleDefinition: undefined,
 			roleDefinition: undefined,
 		})
 		})
 	}
 	}
 
 
-	const handleEnhanceReset = () => {
-		updateEnhancePrompt(undefined)
+	const handleSupportReset = (type: SupportPromptType) => {
+		vscode.postMessage({
+			type: "resetSupportPrompt",
+			text: type,
+		})
 	}
 	}
 
 
-	const getEnhancePromptValue = (): string => {
-		return enhancePrompt.get(customPrompts)
+	const getSupportPromptValue = (type: SupportPromptType): string => {
+		return supportPrompt.get(customSupportPrompts, type)
 	}
 	}
 
 
 	const handleTestEnhancement = () => {
 	const handleTestEnhancement = () => {
@@ -319,7 +318,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
 			</div>
 			</div>
 
 
 			<div style={{ flex: 1, overflow: "auto", padding: "0 20px" }}>
 			<div style={{ flex: 1, overflow: "auto", padding: "0 20px" }}>
-				<div style={{ marginBottom: "20px" }}>
+				<div style={{ paddingBottom: "20px", borderBottom: "1px solid var(--vscode-input-border)" }}>
 					<div style={{ marginBottom: "20px" }}>
 					<div style={{ marginBottom: "20px" }}>
 						<div style={{ fontWeight: "bold", marginBottom: "4px" }}>Preferred Language</div>
 						<div style={{ fontWeight: "bold", marginBottom: "4px" }}>Preferred Language</div>
 						<select
 						<select
@@ -392,7 +391,13 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
 						style={{ width: "100%" }}
 						style={{ width: "100%" }}
 						data-testid="global-custom-instructions-textarea"
 						data-testid="global-custom-instructions-textarea"
 					/>
 					/>
-					<div style={{ fontSize: "12px", color: "var(--vscode-descriptionForeground)", marginTop: "5px" }}>
+					<div
+						style={{
+							fontSize: "12px",
+							color: "var(--vscode-descriptionForeground)",
+							marginTop: "5px",
+							marginBottom: "40px",
+						}}>
 						Instructions can also be loaded from{" "}
 						Instructions can also be loaded from{" "}
 						<span
 						<span
 							style={{
 							style={{
@@ -416,7 +421,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
 					</div>
 					</div>
 				</div>
 				</div>
 
 
-				<div style={{ marginBottom: "20px" }}>
+				<div style={{ marginTop: "20px" }}>
 					<div
 					<div
 						style={{
 						style={{
 							display: "flex",
 							display: "flex",
@@ -563,7 +568,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
 						<VSCodeTextArea
 						<VSCodeTextArea
 							value={(() => {
 							value={(() => {
 								const customMode = findModeBySlug(mode, customModes)
 								const customMode = findModeBySlug(mode, customModes)
-								const prompt = customPrompts?.[mode]
+								const prompt = customModePrompts?.[mode] as PromptComponent
 								return customMode?.roleDefinition ?? prompt?.roleDefinition ?? getRoleDefinition(mode)
 								return customMode?.roleDefinition ?? prompt?.roleDefinition ?? getRoleDefinition(mode)
 							})()}
 							})()}
 							onChange={(e) => {
 							onChange={(e) => {
@@ -680,7 +685,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
 						<VSCodeTextArea
 						<VSCodeTextArea
 							value={(() => {
 							value={(() => {
 								const customMode = findModeBySlug(mode, customModes)
 								const customMode = findModeBySlug(mode, customModes)
-								const prompt = customPrompts?.[mode]
+								const prompt = customModePrompts?.[mode] as PromptComponent
 								return customMode?.customInstructions ?? prompt?.customInstructions ?? ""
 								return customMode?.customInstructions ?? prompt?.customInstructions ?? ""
 							})()}
 							})()}
 							onChange={(e) => {
 							onChange={(e) => {
@@ -696,7 +701,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
 									})
 									})
 								} else {
 								} else {
 									// For built-in modes, update the prompts
 									// For built-in modes, update the prompts
-									const existingPrompt = customPrompts?.[mode]
+									const existingPrompt = customModePrompts?.[mode] as PromptComponent
 									updateAgentPrompt(mode, {
 									updateAgentPrompt(mode, {
 										...existingPrompt,
 										...existingPrompt,
 										customInstructions: value.trim() || undefined,
 										customInstructions: value.trim() || undefined,
@@ -742,7 +747,14 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
 						</div>
 						</div>
 					</div>
 					</div>
 				</div>
 				</div>
-				<div style={{ marginBottom: "20px", display: "flex", justifyContent: "flex-start" }}>
+				<div
+					style={{
+						paddingBottom: "40px",
+						marginBottom: "20px",
+						borderBottom: "1px solid var(--vscode-input-border)",
+						display: "flex",
+						justifyContent: "flex-start",
+					}}>
 					<VSCodeButton
 					<VSCodeButton
 						appearance="primary"
 						appearance="primary"
 						onClick={() => {
 						onClick={() => {
@@ -759,116 +771,168 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
 					</VSCodeButton>
 					</VSCodeButton>
 				</div>
 				</div>
 
 
-				<h3 style={{ color: "var(--vscode-foreground)", margin: "40px 0 20px 0" }}>Prompt Enhancement</h3>
-
 				<div
 				<div
 					style={{
 					style={{
-						color: "var(--vscode-foreground)",
-						fontSize: "13px",
-						marginBottom: "20px",
-						marginTop: "5px",
+						marginTop: "20px",
+						paddingBottom: "60px",
+						borderBottom: "1px solid var(--vscode-input-border)",
 					}}>
 					}}>
-					Use prompt enhancement to get tailored suggestions or improvements for your inputs. This ensures Roo
-					understands your intent and provides the best possible responses.
-				</div>
-
-				<div style={{ display: "flex", flexDirection: "column", gap: "20px" }}>
-					<div>
-						<div style={{ marginBottom: "12px" }}>
-							<div style={{ marginBottom: "8px" }}>
-								<div style={{ fontWeight: "bold", marginBottom: "4px" }}>API Configuration</div>
-								<div style={{ fontSize: "13px", color: "var(--vscode-descriptionForeground)" }}>
-									You can select an API configuration to always use for enhancing prompts, or just use
-									whatever is currently selected
-								</div>
-							</div>
-							<VSCodeDropdown
-								value={enhancementApiConfigId || ""}
-								data-testid="api-config-dropdown"
-								onChange={(e: any) => {
-									const value = e.detail?.target?.value || e.target?.value
-									setEnhancementApiConfigId(value)
-									vscode.postMessage({
-										type: "enhancementApiConfigId",
-										text: value,
-									})
-								}}
-								style={{ width: "300px" }}>
-								<VSCodeOption value="">Use currently selected API configuration</VSCodeOption>
-								{(listApiConfigMeta || []).map((config) => (
-									<VSCodeOption key={config.id} value={config.id}>
-										{config.name}
-									</VSCodeOption>
-								))}
-							</VSCodeDropdown>
-						</div>
-
-						<div style={{ marginBottom: "8px" }}>
-							<div
-								style={{
-									display: "flex",
-									justifyContent: "space-between",
-									alignItems: "center",
-									marginBottom: "4px",
-								}}>
-								<div style={{ fontWeight: "bold" }}>Enhancement Prompt</div>
-								<div style={{ display: "flex", gap: "8px" }}>
-									<VSCodeButton
-										appearance="icon"
-										onClick={handleEnhanceReset}
-										title="Revert to default">
-										<span className="codicon codicon-discard"></span>
-									</VSCodeButton>
-								</div>
-							</div>
-							<div
+					<h3 style={{ color: "var(--vscode-foreground)", marginBottom: "12px" }}>Support Prompts</h3>
+					<div
+						style={{
+							display: "flex",
+							gap: "16px",
+							alignItems: "center",
+							marginBottom: "12px",
+							overflowX: "auto",
+							flexWrap: "nowrap",
+							paddingBottom: "4px",
+							paddingRight: "20px",
+						}}>
+						{Object.keys(supportPrompt.default).map((type) => (
+							<button
+								key={type}
+								data-testid={`${type}-tab`}
+								data-active={activeSupportTab === type ? "true" : "false"}
+								onClick={() => setActiveSupportTab(type as SupportPromptType)}
 								style={{
 								style={{
-									fontSize: "13px",
-									color: "var(--vscode-descriptionForeground)",
-									marginBottom: "8px",
+									padding: "4px 8px",
+									border: "none",
+									background: activeSupportTab === type ? "var(--vscode-button-background)" : "none",
+									color:
+										activeSupportTab === type
+											? "var(--vscode-button-foreground)"
+											: "var(--vscode-foreground)",
+									cursor: "pointer",
+									opacity: activeSupportTab === type ? 1 : 0.8,
+									borderRadius: "3px",
+									fontWeight: "bold",
 								}}>
 								}}>
-								This prompt will be used to refine your input when you hit the sparkle icon in chat.
-							</div>
+								{supportPromptLabels[type as SupportPromptType]}
+							</button>
+						))}
+					</div>
+
+					{/* Support prompt description */}
+					<div
+						style={{
+							fontSize: "13px",
+							color: "var(--vscode-descriptionForeground)",
+							margin: "8px 0 16px",
+						}}>
+						{supportPromptDescriptions[activeSupportTab]}
+					</div>
+
+					{/* Show active tab content */}
+					<div key={activeSupportTab}>
+						<div
+							style={{
+								display: "flex",
+								justifyContent: "space-between",
+								alignItems: "center",
+								marginBottom: "4px",
+							}}>
+							<div style={{ fontWeight: "bold" }}>Prompt</div>
+							<VSCodeButton
+								appearance="icon"
+								onClick={() => handleSupportReset(activeSupportTab)}
+								title={`Reset ${activeSupportTab} prompt to default`}>
+								<span className="codicon codicon-discard"></span>
+							</VSCodeButton>
 						</div>
 						</div>
+
 						<VSCodeTextArea
 						<VSCodeTextArea
-							value={getEnhancePromptValue()}
-							onChange={handleEnhancePromptChange}
-							rows={4}
+							value={getSupportPromptValue(activeSupportTab)}
+							onChange={(e) => {
+								const value =
+									(e as CustomEvent)?.detail?.target?.value ||
+									((e as any).target as HTMLTextAreaElement).value
+								const trimmedValue = value.trim()
+								updateSupportPrompt(activeSupportTab, trimmedValue || undefined)
+							}}
+							rows={6}
 							resize="vertical"
 							resize="vertical"
 							style={{ width: "100%" }}
 							style={{ width: "100%" }}
 						/>
 						/>
 
 
-						<div style={{ marginTop: "12px" }}>
-							<VSCodeTextArea
-								value={testPrompt}
-								onChange={(e) => setTestPrompt((e.target as HTMLTextAreaElement).value)}
-								placeholder="Enter a prompt to test the enhancement"
-								rows={3}
-								resize="vertical"
-								style={{ width: "100%" }}
-								data-testid="test-prompt-textarea"
-							/>
-							<div
-								style={{
-									marginTop: "8px",
-									display: "flex",
-									justifyContent: "flex-start",
-									alignItems: "center",
-									gap: 8,
-								}}>
-								<VSCodeButton
-									onClick={handleTestEnhancement}
-									disabled={isEnhancing}
-									appearance="primary">
-									Preview Prompt Enhancement
-								</VSCodeButton>
-							</div>
-						</div>
+						{activeSupportTab === "ENHANCE" && (
+							<>
+								<div>
+									<div
+										style={{
+											color: "var(--vscode-foreground)",
+											fontSize: "13px",
+											marginBottom: "20px",
+											marginTop: "5px",
+										}}></div>
+									<div style={{ marginBottom: "12px" }}>
+										<div style={{ marginBottom: "8px" }}>
+											<div style={{ fontWeight: "bold", marginBottom: "4px" }}>
+												API Configuration
+											</div>
+											<div
+												style={{
+													fontSize: "13px",
+													color: "var(--vscode-descriptionForeground)",
+												}}>
+												You can select an API configuration to always use for enhancing prompts,
+												or just use whatever is currently selected
+											</div>
+										</div>
+										<VSCodeDropdown
+											value={enhancementApiConfigId || ""}
+											data-testid="api-config-dropdown"
+											onChange={(e: any) => {
+												const value = e.detail?.target?.value || e.target?.value
+												setEnhancementApiConfigId(value)
+												vscode.postMessage({
+													type: "enhancementApiConfigId",
+													text: value,
+												})
+											}}
+											style={{ width: "300px" }}>
+											<VSCodeOption value="">
+												Use currently selected API configuration
+											</VSCodeOption>
+											{(listApiConfigMeta || []).map((config) => (
+												<VSCodeOption key={config.id} value={config.id}>
+													{config.name}
+												</VSCodeOption>
+											))}
+										</VSCodeDropdown>
+									</div>
+								</div>
+
+								<div style={{ marginTop: "12px" }}>
+									<VSCodeTextArea
+										value={testPrompt}
+										onChange={(e) => setTestPrompt((e.target as HTMLTextAreaElement).value)}
+										placeholder="Enter a prompt to test the enhancement"
+										rows={3}
+										resize="vertical"
+										style={{ width: "100%" }}
+										data-testid="test-prompt-textarea"
+									/>
+									<div
+										style={{
+											marginTop: "8px",
+											display: "flex",
+											justifyContent: "flex-start",
+											alignItems: "center",
+											gap: 8,
+										}}>
+										<VSCodeButton
+											onClick={handleTestEnhancement}
+											disabled={isEnhancing}
+											appearance="primary">
+											Preview Prompt Enhancement
+										</VSCodeButton>
+									</div>
+								</div>
+							</>
+						)}
 					</div>
 					</div>
 				</div>
 				</div>
-
-				{/* Bottom padding */}
-				<div style={{ height: "20px" }} />
 			</div>
 			</div>
 
 
 			{isCreateModeDialogOpen && (
 			{isCreateModeDialogOpen && (

+ 5 - 1
webview-ui/src/components/prompts/__tests__/PromptsView.test.tsx

@@ -12,7 +12,7 @@ jest.mock("../../../utils/vscode", () => ({
 }))
 }))
 
 
 const mockExtensionState = {
 const mockExtensionState = {
-	customPrompts: {},
+	customModePrompts: {},
 	listApiConfigMeta: [
 	listApiConfigMeta: [
 		{ id: "config1", name: "Config 1" },
 		{ id: "config1", name: "Config 1" },
 		{ id: "config2", name: "Config 2" },
 		{ id: "config2", name: "Config 2" },
@@ -166,6 +166,10 @@ describe("PromptsView", () => {
 	it("handles API configuration selection", () => {
 	it("handles API configuration selection", () => {
 		renderPromptsView()
 		renderPromptsView()
 
 
+		// Click the ENHANCE tab first to show the API config dropdown
+		const enhanceTab = screen.getByTestId("ENHANCE-tab")
+		fireEvent.click(enhanceTab)
+
 		const dropdown = screen.getByTestId("api-config-dropdown")
 		const dropdown = screen.getByTestId("api-config-dropdown")
 		fireEvent(
 		fireEvent(
 			dropdown,
 			dropdown,

+ 8 - 4
webview-ui/src/context/ExtensionStateContext.tsx

@@ -14,7 +14,8 @@ import { convertTextMateToHljs } from "../utils/textMateToHljs"
 import { findLastIndex } from "../../../src/shared/array"
 import { findLastIndex } from "../../../src/shared/array"
 import { McpServer } from "../../../src/shared/mcp"
 import { McpServer } from "../../../src/shared/mcp"
 import { checkExistKey } from "../../../src/shared/checkExistApiConfig"
 import { checkExistKey } from "../../../src/shared/checkExistApiConfig"
-import { Mode, CustomPrompts, defaultModeSlug, defaultPrompts, ModeConfig } from "../../../src/shared/modes"
+import { Mode, CustomModePrompts, defaultModeSlug, defaultPrompts, ModeConfig } from "../../../src/shared/modes"
+import { CustomSupportPrompts } from "../../../src/shared/support-prompt"
 
 
 export interface ExtensionStateContextType extends ExtensionState {
 export interface ExtensionStateContextType extends ExtensionState {
 	didHydrateState: boolean
 	didHydrateState: boolean
@@ -57,7 +58,8 @@ export interface ExtensionStateContextType extends ExtensionState {
 	onUpdateApiConfig: (apiConfig: ApiConfiguration) => void
 	onUpdateApiConfig: (apiConfig: ApiConfiguration) => void
 	mode: Mode
 	mode: Mode
 	setMode: (value: Mode) => void
 	setMode: (value: Mode) => void
-	setCustomPrompts: (value: CustomPrompts) => void
+	setCustomModePrompts: (value: CustomModePrompts) => void
+	setCustomSupportPrompts: (value: CustomSupportPrompts) => void
 	enhancementApiConfigId?: string
 	enhancementApiConfigId?: string
 	setEnhancementApiConfigId: (value: string) => void
 	setEnhancementApiConfigId: (value: string) => void
 	experimentalDiffStrategy: boolean
 	experimentalDiffStrategy: boolean
@@ -93,7 +95,8 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
 		currentApiConfigName: "default",
 		currentApiConfigName: "default",
 		listApiConfigMeta: [],
 		listApiConfigMeta: [],
 		mode: defaultModeSlug,
 		mode: defaultModeSlug,
-		customPrompts: defaultPrompts,
+		customModePrompts: defaultPrompts,
+		customSupportPrompts: {},
 		enhancementApiConfigId: "",
 		enhancementApiConfigId: "",
 		experimentalDiffStrategy: false,
 		experimentalDiffStrategy: false,
 		autoApprovalEnabled: false,
 		autoApprovalEnabled: false,
@@ -270,7 +273,8 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
 		setListApiConfigMeta,
 		setListApiConfigMeta,
 		onUpdateApiConfig,
 		onUpdateApiConfig,
 		setMode: (value: Mode) => setState((prevState) => ({ ...prevState, mode: value })),
 		setMode: (value: Mode) => setState((prevState) => ({ ...prevState, mode: value })),
-		setCustomPrompts: (value) => setState((prevState) => ({ ...prevState, customPrompts: value })),
+		setCustomModePrompts: (value) => setState((prevState) => ({ ...prevState, customModePrompts: value })),
+		setCustomSupportPrompts: (value) => setState((prevState) => ({ ...prevState, customSupportPrompts: value })),
 		setEnhancementApiConfigId: (value) =>
 		setEnhancementApiConfigId: (value) =>
 			setState((prevState) => ({ ...prevState, enhancementApiConfigId: value })),
 			setState((prevState) => ({ ...prevState, enhancementApiConfigId: value })),
 		setExperimentalDiffStrategy: (value) =>
 		setExperimentalDiffStrategy: (value) =>