mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
227 lines
7 KiB
TypeScript
227 lines
7 KiB
TypeScript
import { TOOL_GROUPS, ToolGroup, ALWAYS_AVAILABLE_TOOLS } from "./tool-groups"
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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)
|
|
TOOL_GROUPS[groupName].forEach((tool) => 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: "code",
|
|
name: "Code",
|
|
roleDefinition:
|
|
"You are Roo, 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 Roo, a software architecture expert specializing in analyzing codebases, identifying patterns, and providing high-level technical guidance. You excel at understanding complex systems, evaluating architectural decisions, and suggesting improvements. You can edit markdown documentation files to help document architectural decisions and patterns.",
|
|
groups: ["read", ["edit", { fileRegex: "\\.md$", description: "Markdown files only" }], "browser", "mcp"],
|
|
},
|
|
{
|
|
slug: "ask",
|
|
name: "Ask",
|
|
roleDefinition:
|
|
"You are Roo, a knowledgeable technical assistant focused on answering questions and providing information about software development, technology, and related topics. You can analyze code, explain concepts, and access external resources. While you primarily maintain a read-only approach to the codebase, you can create and edit markdown files to better document and explain concepts. Make sure to answer the user's questions and don't rush to switch to implementing code.",
|
|
groups: ["read", ["edit", { fileRegex: "\\.md$", description: "Markdown files only" }], "browser", "mcp"],
|
|
},
|
|
] 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
|
|
): boolean {
|
|
// Always allow these tools
|
|
if (ALWAYS_AVAILABLE_TOOLS.includes(tool as any)) {
|
|
return true
|
|
}
|
|
|
|
// 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)
|
|
|
|
// If the tool isn't in this group, continue to next group
|
|
if (!TOOL_GROUPS[groupName].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) &&
|
|
!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 }])),
|
|
)
|
|
|
|
// 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
|
|
}
|