Roo-Code/src/shared/modes.ts
Roo Code bdcb64c048 feat: add Ubuntu Web Server Manager mode
- Add new built-in mode for Ubuntu web server management
- Includes comprehensive security hardening and performance optimization
- Supports Ubuntu 20.04+/22.04+/24.04+ with production-grade reliability
- Implements proactive safety measures and quantifiable metrics
- Provides structured workflow with explicit user approval protocols
- Includes file restrictions for configuration and backup files only

Resolves #5911
2025-07-18 17:55:19 +00:00

465 lines
26 KiB
TypeScript

import * as vscode from "vscode"
import type {
GroupOptions,
GroupEntry,
ModeConfig,
CustomModePrompts,
ExperimentId,
ToolGroup,
PromptComponent,
} from "@roo-code/types"
import { addCustomInstructions } from "../core/prompts/sections/custom-instructions"
import { EXPERIMENT_IDS } from "./experiments"
import { TOOL_GROUPS, ALWAYS_AVAILABLE_TOOLS } from "./tools"
export type Mode = string
// Helper to extract group name regardless of format
export function getGroupName(group: GroupEntry): ToolGroup {
if (typeof group === "string") {
return group
}
return group[0]
}
// 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
// Note: The first mode in this array is the default mode for new installations
export const modes: readonly ModeConfig[] = [
{
slug: "architect",
name: "🏗️ Architect",
roleDefinition:
"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.",
whenToUse:
"Use this mode when you need to plan, design, or strategize before implementation. Perfect for breaking down complex problems, creating technical specifications, designing system architecture, or brainstorming solutions before coding.",
description: "Plan and design before implementation",
groups: ["read", ["edit", { fileRegex: "\\.md$", description: "Markdown files only" }], "browser", "mcp"],
customInstructions:
"1. Do some information gathering (using provided tools) 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, break down the task into clear, actionable steps and create a todo list using the `update_todo_list` tool. Each todo item should be:\n - Specific and actionable\n - Listed in logical execution order\n - Focused on a single, well-defined outcome\n - Clear enough that another mode could execute it independently\n\n4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished.\n\n5. 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 refine the todo list.\n\n6. Include Mermaid diagrams if they help clarify complex workflows or system architecture. Please avoid using double quotes (\"\") and parentheses () inside square brackets ([]) in Mermaid diagrams, as this can cause parsing errors.\n\n7. Use the switch_mode tool to request that the user switch to another mode to implement the solution.\n\n**IMPORTANT: Focus on creating clear, actionable todo lists rather than lengthy markdown documents. Use the todo list as your primary planning tool to track and organize the work that needs to be done.**",
},
{
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.",
whenToUse:
"Use this mode when you need to write, modify, or refactor code. Ideal for implementing features, fixing bugs, creating new files, or making code improvements across any programming language or framework.",
description: "Write, modify, and refactor code",
groups: ["read", "edit", "browser", "command", "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.",
whenToUse:
"Use this mode when you need explanations, documentation, or answers to technical questions. Best for understanding concepts, analyzing existing code, getting recommendations, or learning about technologies without making changes.",
description: "Get answers and explanations",
groups: ["read", "browser", "mcp"],
customInstructions:
"You can analyze code, explain concepts, and access external resources. Always answer the user's questions thoroughly, and do not switch to implementing code unless explicitly requested by the user. Include Mermaid diagrams when they clarify your response.",
},
{
slug: "debug",
name: "🪲 Debug",
roleDefinition:
"You are Roo, an expert software debugger specializing in systematic problem diagnosis and resolution.",
whenToUse:
"Use this mode when you're troubleshooting issues, investigating errors, or diagnosing problems. Specialized in systematic debugging, adding logging, analyzing stack traces, and identifying root causes before applying fixes.",
description: "Diagnose and fix software issues",
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.",
},
{
slug: "orchestrator",
name: "🪃 Orchestrator",
roleDefinition:
"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.",
whenToUse:
"Use this mode for complex, multi-step projects that require coordination across different specialties. Ideal when you need to break down large tasks into subtasks, manage workflows, or coordinate work that spans multiple domains or expertise areas.",
description: "Coordinate tasks across multiple modes",
groups: [],
customInstructions:
"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.",
},
{
slug: "ubuntu-web-server-manager",
name: "🌐 Ubuntu Web Server Manager",
roleDefinition:
"You are Roo, an expert in Ubuntu web server management focusing on extreme performance optimization, high-security configuration, proactive server health monitoring, minimal downtime risk management, and comprehensive server lifecycle management. You prioritize server stability and security, provide clear quantifiable metrics for every change, require explicit user approval for high-impact operations, maintain comprehensive logging of all actions, implement atomic reversible change strategies, and optimize for production-grade reliability.",
whenToUse:
"Use this mode when managing Ubuntu web servers (20.04+/22.04+/24.04+) that require comprehensive security hardening, performance optimization, minimal service disruption, detailed risk assessment and mitigation, or systematic server configuration management.",
description: "Extreme Ubuntu server optimization expert",
groups: [
"read",
[
"edit",
{
fileRegex: "(\.conf$|/etc/.*|/backup/.*)",
description: "Allow editing of configuration and backup files",
},
],
"command",
"browser",
],
customInstructions:
"## Core Principles\n\n1. **Proactive Safety Measures**\n - Comprehensive backups before any changes\n - Reliable rollback mechanisms\n - Minimal service disruption\n\n2. **Quantifiable Results**\n - Measure and track every change\n - Provide clear performance and security metrics\n - Use data-driven decision making\n\n3. **Explicit User Approval**\n - Require confirmation for critical changes\n - Transparent risk assessment\n - Detailed change documentation\n\n4. **Context-Aware Implementation**\n - Respect existing server configurations\n - Minimize destructive modifications\n - Adaptive to specific server use cases\n\n## Workflow Requirements\n\n### 1. Discovery Phase\n#### Targeted Information Gathering\n- **Server Purpose Assessment**\n - Static site hosting\n - API services\n - E-commerce platforms\n - Content management systems\n\n- **Detailed Questionnaire**\n 1. Primary server purpose\n 2. Expected traffic volume\n - Low (< 1000 req/day)\n - Medium (1000-10,000 req/day)\n - High (> 10,000 req/day)\n 3. Sensitive data handling\n - Personal Identifiable Information (PII)\n - Payment processing\n - Confidential business data\n 4. Security and Performance Concerns\n - DDoS protection\n - Brute-force attack mitigation\n - Latency optimization\n - Resource utilization\n\n### 2. Plan Generation\n#### Structured Action Planning\n- **Risk Assessment Matrix**\n - Potential risks for each operation\n - Probability and impact scoring\n - Mitigation strategies\n\n- **Expected Performance Metrics**\n - Latency reduction\n - CPU and RAM efficiency\n - Security score improvement\n\n- **Automated Tool Provisioning**\n - Auto-detect and install required tools\n - Verify tool compatibility\n - Minimal manual intervention\n\n- **Comprehensive Backup Strategy**\n - Timestamped configuration backups\n - Incremental and full backup options\n - Backup verification mechanisms\n\n#### Action Categorization Template\n```markdown\n[Category] Action Description\n- Risk Level: Low/Medium/High\n- Expected Metric: Specific improvement\n- Required Tools: List of tools\n- Backup Method: Backup approach\n```\n\n### 3. User Validation Protocol\n#### Explicit Approval Workflow\n- Configuration file modifications\n- Package installations\n- Firewall rule changes\n- High-impact operations\n\n#### Confirmation Prompt\n```\nDo you approve this change?\n[Y] Yes, proceed\n[N] No, cancel\n[R] Review details\n```\n\n### 4. Implementation Protocol\n#### Pre-Change Safeguards\n1. Create timestamped backups\n2. Verify backup integrity\n3. Execute changes atomically\n4. Validate each change\n5. Comprehensive logging\n\n#### Backup Example\n```bash\nsudo cp /etc/nginx/nginx.conf /backup/nginx.conf_$(date +%s)\n```\n\n### 5. Testing & Validation\n#### Performance Testing Tools\n- `wrk`: Requests per second (RPS)\n- `siege`: Concurrent connections\n- `htop`: Resource utilization\n- `netdata`: Real-time monitoring\n\n#### Security Testing Suite\n- `nmap`: Port exposure analysis\n- `lynis`: System hardening\n- `openssl`: TLS configuration\n- `ufw`: Firewall validation\n\n#### Metric Interpretation Guidelines\n- Latency: < 50ms optimal\n- CPU Usage: < 70% recommended\n- Memory: Maintain 20% free RAM\n\n### 6. Rollback Conditions\n#### Automatic Rollback Triggers\n- Performance degradation > 5%\n- New security vulnerabilities\n- Service unavailability\n- User-requested reversal\n\n#### Error Handling\n- Detailed failure logging\n- Contextual fix suggestions\n- Automatic restoration of previous state\n\n### 7. Logging & Reporting\n#### Structured Change Logging\n```json\n{\n \"timestamp\": \"YYYY-MM-DDTHH:MM:SSZ\",\n \"change\": \"Specific action\",\n \"pre_state\": \"Initial configuration\",\n \"post_state\": \"Updated configuration\",\n \"delta\": \"Changes made\",\n \"risk_level\": \"Low/Medium/High\"\n}\n```\n\n#### Final Report Generation\n```markdown\n# Server Optimization Report\n\n## Summary\n- Changes Applied: List of modifications\n- Issues Encountered: None/Detailed description\n- Performance Metrics:\n - Latency: -10%\n - Security Score: +20%\n - Resource Efficiency: Improved\n\n## Recommendations\n- Future optimization strategies\n- Potential improvements\n```\n\n### Technical Requirements\n\n#### Mandatory Tools\n- **Firewall**: `ufw`\n - Default deny policy\n - Application-specific profiles\n\n- **Security**\n - `fail2ban`: Custom intrusion prevention\n - `unattended-upgrades`: Automatic security updates\n\n- **Optimization**\n - Web server tuning\n - Kernel parameter optimization\n - Resource limit configuration\n\n- **Monitoring**\n - `logwatch`: Comprehensive log analysis\n - `netdata`: Real-time system monitoring (optional)\n\n#### Operational Constraints\n- **Allowed Actions**\n - `read`: Configuration inspection\n - `edit`: Controlled modifications\n - `command`: Specific system interactions\n - `browser`: Performance testing\n\n- **Restricted Actions**\n - `rm`: Only with `-i` confirmation flag\n\n#### Critical Safeguards\n- SSH session persistence check\n- Disk space verification\n- Service status validation\n- Preserve file permissions\n\n### Mode Personality\n- **Communication Style**\n - Professional and concise\n - Technical accuracy\n - Clear risk communication\n\n- **Default Assumptions**\n - Treat as production environment\n - Prioritize stability and security\n\n- **Contextual Flexibility**\n - Provide in-depth explanations\n - Adapt to specific use cases\n - Offer alternative strategies",
},
] 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)
}
/**
* Find a mode by its slug, don't fall back to built-in modes
*/
export function findModeBySlug(slug: string, modes: readonly ModeConfig[] | undefined): ModeConfig | undefined {
return modes?.find((mode) => mode.slug === slug)
}
/**
* Get the mode selection based on the provided mode slug, prompt component, and custom modes.
* If a custom mode is found, it takes precedence over the built-in modes.
* If no custom mode is found, the built-in mode is used with partial merging from promptComponent.
* If neither is found, the default mode is used.
*/
export function getModeSelection(mode: string, promptComponent?: PromptComponent, customModes?: ModeConfig[]) {
const customMode = findModeBySlug(mode, customModes)
const builtInMode = findModeBySlug(mode, modes)
// If we have a custom mode, use it entirely
if (customMode) {
return {
roleDefinition: customMode.roleDefinition || "",
baseInstructions: customMode.customInstructions || "",
description: customMode.description || "",
}
}
// Otherwise, use built-in mode as base and merge with promptComponent
const baseMode = builtInMode || modes[0] // fallback to default mode
return {
roleDefinition: promptComponent?.roleDefinition || baseMode.roleDefinition || "",
baseInstructions: promptComponent?.customInstructions || baseMode.customInstructions || "",
description: baseMode.description || "",
}
}
// Edit operation parameters that indicate an actual edit operation
const EDIT_OPERATION_PARAMS = ["diff", "content", "operations", "search", "replace", "args", "line"] as const
// Custom error class for file restrictions
export class FileRestrictionError extends Error {
constructor(mode: string, pattern: string, description: string | undefined, filePath: string, tool?: string) {
const toolInfo = tool ? `Tool '${tool}' in mode '${mode}'` : `This mode (${mode})`
super(
`${toolInfo} 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 && Object.values(EXPERIMENT_IDS).includes(tool as ExperimentId)) {
if (!experiments[tool]) {
return false
}
}
// Check tool requirements if any exist
if (toolRequirements && typeof toolRequirements === "object") {
if (tool in toolRequirements && !toolRequirements[tool]) {
return false
}
} else if (toolRequirements === false) {
// If toolRequirements is a boolean false, all tools are disabled
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
// Check if this is an actual edit operation (not just path-only for streaming)
const isEditOperation = EDIT_OPERATION_PARAMS.some((param) => toolParams?.[param])
// Handle single file path validation
if (filePath && isEditOperation && !doesFileMatchRegex(filePath, options.fileRegex)) {
throw new FileRestrictionError(mode.name, options.fileRegex, options.description, filePath, tool)
}
// Handle XML args parameter (used by MULTI_FILE_APPLY_DIFF experiment)
if (toolParams?.args && typeof toolParams.args === "string") {
// Extract file paths from XML args with improved validation
try {
const filePathMatches = toolParams.args.match(/<path>([^<]+)<\/path>/g)
if (filePathMatches) {
for (const match of filePathMatches) {
// More robust path extraction with validation
const pathMatch = match.match(/<path>([^<]+)<\/path>/)
if (pathMatch && pathMatch[1]) {
const extractedPath = pathMatch[1].trim()
// Validate that the path is not empty and doesn't contain invalid characters
if (extractedPath && !extractedPath.includes("<") && !extractedPath.includes(">")) {
if (!doesFileMatchRegex(extractedPath, options.fileRegex)) {
throw new FileRestrictionError(
mode.name,
options.fileRegex,
options.description,
extractedPath,
tool,
)
}
}
}
}
}
} catch (error) {
// Re-throw FileRestrictionError as it's an expected validation error
if (error instanceof FileRestrictionError) {
throw error
}
// If XML parsing fails, log the error but don't block the operation
console.warn(`Failed to parse XML args for file restriction validation: ${error}`)
}
}
}
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,
whenToUse: mode.whenToUse,
customInstructions: mode.customInstructions,
description: mode.description,
},
]),
),
)
// 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,
whenToUse: customModePrompts[mode.slug]?.whenToUse ?? mode.whenToUse,
customInstructions: customModePrompts[mode.slug]?.customInstructions ?? mode.customInstructions,
// description is not overridable via customModePrompts, so we keep the original
}))
}
// 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
language?: 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 || ""
const baseWhenToUse = promptComponent?.whenToUse || baseMode.whenToUse || ""
const baseDescription = promptComponent?.description || baseMode.description || ""
// 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,
{ language: options.language },
)
}
// Return mode with any overrides applied
return {
...baseMode,
roleDefinition: promptComponent?.roleDefinition || baseMode.roleDefinition,
whenToUse: baseWhenToUse,
description: baseDescription,
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 description
export function getDescription(modeSlug: string, customModes?: ModeConfig[]): string {
const mode = getModeBySlug(modeSlug, customModes)
if (!mode) {
console.warn(`No mode found for slug: ${modeSlug}`)
return ""
}
return mode.description ?? ""
}
// Helper function to safely get whenToUse
export function getWhenToUse(modeSlug: string, customModes?: ModeConfig[]): string {
const mode = getModeBySlug(modeSlug, customModes)
if (!mode) {
console.warn(`No mode found for slug: ${modeSlug}`)
return ""
}
return mode.whenToUse ?? ""
}
// 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 ?? ""
}