forked from RooCodeInc/Roo-Code
-
Notifications
You must be signed in to change notification settings - Fork 12
Expand file tree
/
Copy pathmodes.ts
More file actions
333 lines (289 loc) · 11.8 KB
/
modes.ts
File metadata and controls
333 lines (289 loc) · 11.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
import * as vscode from "vscode"
import { TOOL_GROUPS, ToolGroup, ALWAYS_AVAILABLE_TOOLS } from "./tool-groups"
import { addCustomInstructions } from "../core/prompts/sections/custom-instructions"
// Mode types
export type Mode = string
// Group options type
export type GroupOptions = {
fileRegex?: string // Regular expression pattern
description?: string // Human-readable description of the pattern
}
// Group entry can be either a string or tuple with options
export type GroupEntry = ToolGroup | readonly [ToolGroup, GroupOptions]
// Mode configuration type
export type ModeConfig = {
slug: string
name: string
roleDefinition: string
customInstructions?: string
groups: readonly GroupEntry[] // Now supports both simple strings and tuples with options
source?: "global" | "project" // Where this mode was loaded from
}
// Mode-specific prompts only
export type PromptComponent = {
roleDefinition?: string
customInstructions?: string
}
export type CustomModePrompts = {
[key: string]: PromptComponent | undefined
}
// Helper to extract group name regardless of format
export function getGroupName(group: GroupEntry): ToolGroup {
return Array.isArray(group) ? group[0] : group
}
// Helper to get group options if they exist
function getGroupOptions(group: GroupEntry): GroupOptions | undefined {
return Array.isArray(group) ? group[1] : undefined
}
// Helper to check if a file path matches a regex pattern
export function doesFileMatchRegex(filePath: string, pattern: string): boolean {
try {
const regex = new RegExp(pattern)
return regex.test(filePath)
} catch (error) {
console.error(`Invalid regex pattern: ${pattern}`, error)
return false
}
}
// Helper to get all tools for a mode
export function getToolsForMode(groups: readonly GroupEntry[]): string[] {
const tools = new Set<string>()
// Add tools from each group
groups.forEach((group) => {
const groupName = getGroupName(group)
const groupConfig = TOOL_GROUPS[groupName]
groupConfig.tools.forEach((tool: string) => tools.add(tool))
})
// Always add required tools
ALWAYS_AVAILABLE_TOOLS.forEach((tool) => tools.add(tool))
return Array.from(tools)
}
// Main modes configuration as an ordered array
export const modes: readonly ModeConfig[] = [
{
slug: "creator",
name: "Creator",
roleDefinition:
"You are PearAI Agent (Powered by Roo Code / Cline), a creative and systematic software architect focused on turning high-level ideas into actionable plans. Your primary goal is to help users transform their ideas into structured action plans.",
groups: ["read", ["edit", { fileRegex: "\\.md$", description: "Markdown files only" }], "browser", "mcp"],
customInstructions:
"Your role is to help users transform their ideas into concrete action plans. When a user provides a prompt, analyze it carefully and create a detailed, step-by-step plan that outlines how to implement their idea. Focus on breaking down complex tasks into manageable steps, considering technical requirements, potential challenges, and best practices. The plan should be clear enough that it can be directly implemented by switching to Code mode afterward.",
},
{
slug: "code",
name: "Code",
roleDefinition:
"You are PearAI Agent (Powered by Roo Code / Cline), a highly skilled software engineer with extensive knowledge in many programming languages, frameworks, design patterns, and best practices.",
groups: ["read", "edit", "browser", "command", "mcp"],
},
{
slug: "architect",
name: "Architect",
roleDefinition:
"You are PearAI Agent (Powered by Roo Code / Cline), 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.",
groups: ["read", ["edit", { fileRegex: "\\.md$", description: "Markdown files only" }], "browser", "mcp"],
customInstructions:
"Depending on the user's request, you may need to do some information gathering (for example using read_file or search_files) to get more context about the task. You may also ask the user clarifying questions to get a better understanding of the task. Once you've gained more context about the user's request, you should create a detailed plan for how to accomplish the task. (You can write the plan to a markdown file if it seems appropriate.)\n\nThen you might 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. Finally once it seems like you've reached a good plan, use the switch_mode tool to request that the user switch to another mode to implement the solution.",
},
{
slug: "ask",
name: "Ask",
roleDefinition:
"You are PearAI Agent (Powered by Roo Code / Cline), a knowledgeable technical assistant focused on answering questions and providing information about software development, technology, and related topics.",
groups: ["read", "browser", "mcp"],
customInstructions:
"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.",
},
{
slug: "debug",
name: "Debug",
roleDefinition:
"You are PearAI Agent (Powered by Roo Code / Cline), an expert software debugger specializing in systematic problem diagnosis and resolution.",
groups: ["read", "edit", "browser", "command", "mcp"],
customInstructions:
"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.",
},
] as const
// Export the default mode slug
export const defaultModeSlug = modes[0].slug
// Helper functions
export function getModeBySlug(slug: string, customModes?: ModeConfig[]): ModeConfig | undefined {
// Check custom modes first
const customMode = customModes?.find((mode) => mode.slug === slug)
if (customMode) {
return customMode
}
// Then check built-in modes
return modes.find((mode) => mode.slug === slug)
}
export function getModeConfig(slug: string, customModes?: ModeConfig[]): ModeConfig {
const mode = getModeBySlug(slug, customModes)
if (!mode) {
throw new Error(`No mode found for slug: ${slug}`)
}
return mode
}
// Get all available modes, with custom modes overriding built-in modes
export function getAllModes(customModes?: ModeConfig[]): ModeConfig[] {
if (!customModes?.length) {
return [...modes]
}
// Start with built-in modes
const allModes = [...modes]
// Process custom modes
customModes.forEach((customMode) => {
const index = allModes.findIndex((mode) => mode.slug === customMode.slug)
if (index !== -1) {
// Override existing mode
allModes[index] = customMode
} else {
// Add new mode
allModes.push(customMode)
}
})
return allModes
}
// Check if a mode is custom or an override
export function isCustomMode(slug: string, customModes?: ModeConfig[]): boolean {
return !!customModes?.some((mode) => mode.slug === slug)
}
// Custom error class for file restrictions
export class FileRestrictionError extends Error {
constructor(mode: string, pattern: string, description: string | undefined, filePath: string) {
super(
`This mode (${mode}) can only edit files matching pattern: ${pattern}${description ? ` (${description})` : ""}. Got: ${filePath}`,
)
this.name = "FileRestrictionError"
}
}
export function isToolAllowedForMode(
tool: string,
modeSlug: string,
customModes: ModeConfig[],
toolRequirements?: Record<string, boolean>,
toolParams?: Record<string, any>, // All tool parameters
experiments?: Record<string, boolean>,
): boolean {
// Always allow these tools
if (ALWAYS_AVAILABLE_TOOLS.includes(tool as any)) {
return true
}
if (experiments && tool in experiments) {
if (!experiments[tool]) {
return false
}
}
// Check tool requirements if any exist
if (toolRequirements && tool in toolRequirements) {
if (!toolRequirements[tool]) {
return false
}
}
const mode = getModeBySlug(modeSlug, customModes)
if (!mode) {
return false
}
// Check if tool is in any of the mode's groups and respects any group options
for (const group of mode.groups) {
const groupName = getGroupName(group)
const options = getGroupOptions(group)
const groupConfig = TOOL_GROUPS[groupName]
// If the tool isn't in this group's tools, continue to next group
if (!groupConfig.tools.includes(tool)) {
continue
}
// If there are no options, allow the tool
if (!options) {
return true
}
// For the edit group, check file regex if specified
if (groupName === "edit" && options.fileRegex) {
const filePath = toolParams?.path
if (
filePath &&
(toolParams.diff || toolParams.content || toolParams.operations) &&
!doesFileMatchRegex(filePath, options.fileRegex)
) {
throw new FileRestrictionError(mode.name, options.fileRegex, options.description, filePath)
}
}
return true
}
return false
}
// Create the mode-specific default prompts
export const defaultPrompts: Readonly<CustomModePrompts> = Object.freeze(
Object.fromEntries(
modes.map((mode) => [
mode.slug,
{
roleDefinition: mode.roleDefinition,
customInstructions: mode.customInstructions,
},
]),
),
)
// Helper function to get all modes with their prompt overrides from extension state
export async function getAllModesWithPrompts(context: vscode.ExtensionContext): Promise<ModeConfig[]> {
const customModes = (await context.globalState.get<ModeConfig[]>("customModes")) || []
const customModePrompts = (await context.globalState.get<CustomModePrompts>("customModePrompts")) || {}
const allModes = getAllModes(customModes)
return allModes.map((mode) => ({
...mode,
roleDefinition: customModePrompts[mode.slug]?.roleDefinition ?? mode.roleDefinition,
customInstructions: customModePrompts[mode.slug]?.customInstructions ?? mode.customInstructions,
}))
}
// Helper function to get complete mode details with all overrides
export async function getFullModeDetails(
modeSlug: string,
customModes?: ModeConfig[],
customModePrompts?: CustomModePrompts,
options?: {
cwd?: string
globalCustomInstructions?: string
preferredLanguage?: string
},
): Promise<ModeConfig> {
// First get the base mode config from custom modes or built-in modes
const baseMode = getModeBySlug(modeSlug, customModes) || modes.find((m) => m.slug === modeSlug) || modes[0]
// Check for any prompt component overrides
const promptComponent = customModePrompts?.[modeSlug]
// Get the base custom instructions
const baseCustomInstructions = promptComponent?.customInstructions || baseMode.customInstructions || ""
// If we have cwd, load and combine all custom instructions
let fullCustomInstructions = baseCustomInstructions
if (options?.cwd) {
fullCustomInstructions = await addCustomInstructions(
baseCustomInstructions,
options.globalCustomInstructions || "",
options.cwd,
modeSlug,
{ preferredLanguage: options.preferredLanguage },
)
}
// Return mode with any overrides applied
return {
...baseMode,
roleDefinition: promptComponent?.roleDefinition || baseMode.roleDefinition,
customInstructions: fullCustomInstructions,
}
}
// Helper function to safely get role definition
export function getRoleDefinition(modeSlug: string, customModes?: ModeConfig[]): string {
const mode = getModeBySlug(modeSlug, customModes)
if (!mode) {
console.warn(`No mode found for slug: ${modeSlug}`)
return ""
}
return mode.roleDefinition
}
// Helper function to safely get custom instructions
export function getCustomInstructions(modeSlug: string, customModes?: ModeConfig[]): string {
const mode = getModeBySlug(modeSlug, customModes)
if (!mode) {
console.warn(`No mode found for slug: ${modeSlug}`)
return ""
}
return mode.customInstructions ?? ""
}