modes.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. import * as vscode from "vscode"
  2. import { GroupOptions, GroupEntry, ModeConfig, PromptComponent, CustomModePrompts, ExperimentId } from "../schemas"
  3. import { TOOL_GROUPS, ToolGroup, ALWAYS_AVAILABLE_TOOLS } from "./tools"
  4. import { addCustomInstructions } from "../core/prompts/sections/custom-instructions"
  5. import { EXPERIMENT_IDS } from "./experiments"
  6. export type Mode = string
  7. export type { GroupOptions, GroupEntry, ModeConfig, PromptComponent, CustomModePrompts }
  8. // Helper to extract group name regardless of format
  9. export function getGroupName(group: GroupEntry): ToolGroup {
  10. if (typeof group === "string") {
  11. return group
  12. }
  13. return group[0]
  14. }
  15. // Helper to get group options if they exist
  16. function getGroupOptions(group: GroupEntry): GroupOptions | undefined {
  17. return Array.isArray(group) ? group[1] : undefined
  18. }
  19. // Helper to check if a file path matches a regex pattern
  20. export function doesFileMatchRegex(filePath: string, pattern: string): boolean {
  21. try {
  22. const regex = new RegExp(pattern)
  23. return regex.test(filePath)
  24. } catch (error) {
  25. console.error(`Invalid regex pattern: ${pattern}`, error)
  26. return false
  27. }
  28. }
  29. // Helper to get all tools for a mode
  30. export function getToolsForMode(groups: readonly GroupEntry[]): string[] {
  31. const tools = new Set<string>()
  32. // Add tools from each group
  33. groups.forEach((group) => {
  34. const groupName = getGroupName(group)
  35. const groupConfig = TOOL_GROUPS[groupName]
  36. groupConfig.tools.forEach((tool: string) => tools.add(tool))
  37. })
  38. // Always add required tools
  39. ALWAYS_AVAILABLE_TOOLS.forEach((tool) => tools.add(tool))
  40. return Array.from(tools)
  41. }
  42. // Main modes configuration as an ordered array
  43. export const modes: readonly ModeConfig[] = [
  44. {
  45. slug: "code",
  46. name: "💻 Code",
  47. roleDefinition:
  48. "You are Roo, a highly skilled software engineer with extensive knowledge in many programming languages, frameworks, design patterns, and best practices.",
  49. groups: ["read", "edit", "browser", "command", "mcp"],
  50. },
  51. {
  52. slug: "architect",
  53. name: "🏗️ Architect",
  54. roleDefinition:
  55. "You are Roo, an experienced technical leader who is inquisitive and an excellent planner. Your goal is to gather information and get context to create a detailed plan for accomplishing the user's task, which the user will review and approve before they switch into another mode to implement the solution.",
  56. groups: ["read", ["edit", { fileRegex: "\\.md$", description: "Markdown files only" }], "browser", "mcp"],
  57. customInstructions:
  58. "1. Do some information gathering (for example using read_file or search_files) to get more context about the task.\n\n2. You should also ask the user clarifying questions to get a better understanding of the task.\n\n3. Once you've gained more context about the user's request, you should create a detailed plan for how to accomplish the task. Include Mermaid diagrams if they help make your plan clearer.\n\n4. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and plan the best way to accomplish it.\n\n5. Once the user confirms the plan, ask them if they'd like you to write it to a markdown file.\n\n6. Use the switch_mode tool to request that the user switch to another mode to implement the solution.",
  59. },
  60. {
  61. slug: "ask",
  62. name: "❓ Ask",
  63. roleDefinition:
  64. "You are Roo, a knowledgeable technical assistant focused on answering questions and providing information about software development, technology, and related topics.",
  65. groups: ["read", "browser", "mcp"],
  66. customInstructions:
  67. "You can analyze code, explain concepts, and access external resources. Make sure to answer the user's questions and don't rush to switch to implementing code. Include Mermaid diagrams if they help make your response clearer.",
  68. },
  69. {
  70. slug: "debug",
  71. name: "🪲 Debug",
  72. roleDefinition:
  73. "You are Roo, an expert software debugger specializing in systematic problem diagnosis and resolution.",
  74. groups: ["read", "edit", "browser", "command", "mcp"],
  75. customInstructions:
  76. "Reflect on 5-7 different possible sources of the problem, distill those down to 1-2 most likely sources, and then add logs to validate your assumptions. Explicitly ask the user to confirm the diagnosis before fixing the problem.",
  77. },
  78. {
  79. slug: "orchestrator",
  80. name: "🪃 Orchestrator",
  81. roleDefinition:
  82. "You are Roo, a strategic workflow orchestrator who coordinates complex tasks by delegating them to appropriate specialized modes. You have a comprehensive understanding of each mode's capabilities and limitations, allowing you to effectively break down complex problems into discrete tasks that can be solved by different specialists.",
  83. groups: [],
  84. customInstructions:
  85. "Your role is to coordinate complex workflows by delegating tasks to specialized modes. As an orchestrator, you should:\n\n1. When given a complex task, break it down into logical subtasks that can be delegated to appropriate specialized modes.\n\n2. For each subtask, use the `new_task` tool to delegate. Choose the most appropriate mode for the subtask's specific goal and provide comprehensive instructions in the `message` parameter. These instructions must include:\n * All necessary context from the parent task or previous subtasks required to complete the work.\n * A clearly defined scope, specifying exactly what the subtask should accomplish.\n * An explicit statement that the subtask should *only* perform the work outlined in these instructions and not deviate.\n * An instruction for the subtask to signal completion by using the `attempt_completion` tool, providing a concise yet thorough summary of the outcome in the `result` parameter, keeping in mind that this summary will be the source of truth used to keep track of what was completed on this project.\n * A statement that these specific instructions supersede any conflicting general instructions the subtask's mode might have.\n\n3. Track and manage the progress of all subtasks. When a subtask is completed, analyze its results and determine the next steps.\n\n4. Help the user understand how the different subtasks fit together in the overall workflow. Provide clear reasoning about why you're delegating specific tasks to specific modes.\n\n5. When all subtasks are completed, synthesize the results and provide a comprehensive overview of what was accomplished.\n\n6. Ask clarifying questions when necessary to better understand how to break down complex tasks effectively.\n\n7. Suggest improvements to the workflow based on the results of completed subtasks.\n\nUse subtasks to maintain clarity. If a request significantly shifts focus or requires a different expertise (mode), consider creating a subtask rather than overloading the current one.",
  86. },
  87. ] as const
  88. // Export the default mode slug
  89. export const defaultModeSlug = modes[0].slug
  90. // Helper functions
  91. export function getModeBySlug(slug: string, customModes?: ModeConfig[]): ModeConfig | undefined {
  92. // Check custom modes first
  93. const customMode = customModes?.find((mode) => mode.slug === slug)
  94. if (customMode) {
  95. return customMode
  96. }
  97. // Then check built-in modes
  98. return modes.find((mode) => mode.slug === slug)
  99. }
  100. export function getModeConfig(slug: string, customModes?: ModeConfig[]): ModeConfig {
  101. const mode = getModeBySlug(slug, customModes)
  102. if (!mode) {
  103. throw new Error(`No mode found for slug: ${slug}`)
  104. }
  105. return mode
  106. }
  107. // Get all available modes, with custom modes overriding built-in modes
  108. export function getAllModes(customModes?: ModeConfig[]): ModeConfig[] {
  109. if (!customModes?.length) {
  110. return [...modes]
  111. }
  112. // Start with built-in modes
  113. const allModes = [...modes]
  114. // Process custom modes
  115. customModes.forEach((customMode) => {
  116. const index = allModes.findIndex((mode) => mode.slug === customMode.slug)
  117. if (index !== -1) {
  118. // Override existing mode
  119. allModes[index] = customMode
  120. } else {
  121. // Add new mode
  122. allModes.push(customMode)
  123. }
  124. })
  125. return allModes
  126. }
  127. // Check if a mode is custom or an override
  128. export function isCustomMode(slug: string, customModes?: ModeConfig[]): boolean {
  129. return !!customModes?.some((mode) => mode.slug === slug)
  130. }
  131. // Custom error class for file restrictions
  132. export class FileRestrictionError extends Error {
  133. constructor(mode: string, pattern: string, description: string | undefined, filePath: string) {
  134. super(
  135. `This mode (${mode}) can only edit files matching pattern: ${pattern}${description ? ` (${description})` : ""}. Got: ${filePath}`,
  136. )
  137. this.name = "FileRestrictionError"
  138. }
  139. }
  140. export function isToolAllowedForMode(
  141. tool: string,
  142. modeSlug: string,
  143. customModes: ModeConfig[],
  144. toolRequirements?: Record<string, boolean>,
  145. toolParams?: Record<string, any>, // All tool parameters
  146. experiments?: Record<string, boolean>,
  147. ): boolean {
  148. // Always allow these tools
  149. if (ALWAYS_AVAILABLE_TOOLS.includes(tool as any)) {
  150. return true
  151. }
  152. if (experiments && Object.values(EXPERIMENT_IDS).includes(tool as ExperimentId)) {
  153. if (!experiments[tool]) {
  154. return false
  155. }
  156. }
  157. // Check tool requirements if any exist
  158. if (toolRequirements && typeof toolRequirements === "object") {
  159. if (tool in toolRequirements && !toolRequirements[tool]) {
  160. return false
  161. }
  162. } else if (toolRequirements === false) {
  163. // If toolRequirements is a boolean false, all tools are disabled
  164. return false
  165. }
  166. const mode = getModeBySlug(modeSlug, customModes)
  167. if (!mode) {
  168. return false
  169. }
  170. // Check if tool is in any of the mode's groups and respects any group options
  171. for (const group of mode.groups) {
  172. const groupName = getGroupName(group)
  173. const options = getGroupOptions(group)
  174. const groupConfig = TOOL_GROUPS[groupName]
  175. // If the tool isn't in this group's tools, continue to next group
  176. if (!groupConfig.tools.includes(tool)) {
  177. continue
  178. }
  179. // If there are no options, allow the tool
  180. if (!options) {
  181. return true
  182. }
  183. // For the edit group, check file regex if specified
  184. if (groupName === "edit" && options.fileRegex) {
  185. const filePath = toolParams?.path
  186. if (
  187. filePath &&
  188. (toolParams.diff || toolParams.content || toolParams.operations) &&
  189. !doesFileMatchRegex(filePath, options.fileRegex)
  190. ) {
  191. throw new FileRestrictionError(mode.name, options.fileRegex, options.description, filePath)
  192. }
  193. }
  194. return true
  195. }
  196. return false
  197. }
  198. // Create the mode-specific default prompts
  199. export const defaultPrompts: Readonly<CustomModePrompts> = Object.freeze(
  200. Object.fromEntries(
  201. modes.map((mode) => [
  202. mode.slug,
  203. {
  204. roleDefinition: mode.roleDefinition,
  205. whenToUse: mode.whenToUse,
  206. customInstructions: mode.customInstructions,
  207. },
  208. ]),
  209. ),
  210. )
  211. // Helper function to get all modes with their prompt overrides from extension state
  212. export async function getAllModesWithPrompts(context: vscode.ExtensionContext): Promise<ModeConfig[]> {
  213. const customModes = (await context.globalState.get<ModeConfig[]>("customModes")) || []
  214. const customModePrompts = (await context.globalState.get<CustomModePrompts>("customModePrompts")) || {}
  215. const allModes = getAllModes(customModes)
  216. return allModes.map((mode) => ({
  217. ...mode,
  218. roleDefinition: customModePrompts[mode.slug]?.roleDefinition ?? mode.roleDefinition,
  219. whenToUse: customModePrompts[mode.slug]?.whenToUse ?? mode.whenToUse,
  220. customInstructions: customModePrompts[mode.slug]?.customInstructions ?? mode.customInstructions,
  221. }))
  222. }
  223. // Helper function to get complete mode details with all overrides
  224. export async function getFullModeDetails(
  225. modeSlug: string,
  226. customModes?: ModeConfig[],
  227. customModePrompts?: CustomModePrompts,
  228. options?: {
  229. cwd?: string
  230. globalCustomInstructions?: string
  231. language?: string
  232. },
  233. ): Promise<ModeConfig> {
  234. // First get the base mode config from custom modes or built-in modes
  235. const baseMode = getModeBySlug(modeSlug, customModes) || modes.find((m) => m.slug === modeSlug) || modes[0]
  236. // Check for any prompt component overrides
  237. const promptComponent = customModePrompts?.[modeSlug]
  238. // Get the base custom instructions
  239. const baseCustomInstructions = promptComponent?.customInstructions || baseMode.customInstructions || ""
  240. const baseWhenToUse = promptComponent?.whenToUse || baseMode.whenToUse || ""
  241. // If we have cwd, load and combine all custom instructions
  242. let fullCustomInstructions = baseCustomInstructions
  243. if (options?.cwd) {
  244. fullCustomInstructions = await addCustomInstructions(
  245. baseCustomInstructions,
  246. options.globalCustomInstructions || "",
  247. options.cwd,
  248. modeSlug,
  249. { language: options.language },
  250. )
  251. }
  252. // Return mode with any overrides applied
  253. return {
  254. ...baseMode,
  255. roleDefinition: promptComponent?.roleDefinition || baseMode.roleDefinition,
  256. whenToUse: baseWhenToUse,
  257. customInstructions: fullCustomInstructions,
  258. }
  259. }
  260. // Helper function to safely get role definition
  261. export function getRoleDefinition(modeSlug: string, customModes?: ModeConfig[]): string {
  262. const mode = getModeBySlug(modeSlug, customModes)
  263. if (!mode) {
  264. console.warn(`No mode found for slug: ${modeSlug}`)
  265. return ""
  266. }
  267. return mode.roleDefinition
  268. }
  269. // Helper function to safely get whenToUse
  270. export function getWhenToUse(modeSlug: string, customModes?: ModeConfig[]): string {
  271. const mode = getModeBySlug(modeSlug, customModes)
  272. if (!mode) {
  273. console.warn(`No mode found for slug: ${modeSlug}`)
  274. return ""
  275. }
  276. return mode.whenToUse ?? ""
  277. }
  278. // Helper function to safely get custom instructions
  279. export function getCustomInstructions(modeSlug: string, customModes?: ModeConfig[]): string {
  280. const mode = getModeBySlug(modeSlug, customModes)
  281. if (!mode) {
  282. console.warn(`No mode found for slug: ${modeSlug}`)
  283. return ""
  284. }
  285. return mode.customInstructions ?? ""
  286. }