diff --git a/packages/types/src/api.ts b/packages/types/src/api.ts index 6fb181b573..c2a134a35e 100644 --- a/packages/types/src/api.ts +++ b/packages/types/src/api.ts @@ -12,7 +12,8 @@ export interface RooCodeAPIEvents { message: [data: { taskId: string; action: "created" | "updated"; message: ClineMessage }] taskCreated: [taskId: string] taskStarted: [taskId: string] - taskModeSwitched: [taskId: string, mode: string] + taskAgentSwitched: [taskId: string, agent: string] + taskModeSwitched: [taskId: string, mode: string] // Backward compatibility alias taskPaused: [taskId: string] taskUnpaused: [taskId: string] taskAskResponded: [taskId: string] diff --git a/packages/types/src/ipc.ts b/packages/types/src/ipc.ts index 28accde9de..25d79c46f4 100644 --- a/packages/types/src/ipc.ts +++ b/packages/types/src/ipc.ts @@ -20,7 +20,7 @@ export enum RooCodeEventName { Message = "message", TaskCreated = "taskCreated", TaskStarted = "taskStarted", - TaskModeSwitched = "taskModeSwitched", + TaskAgentSwitched = "taskAgentSwitched", TaskPaused = "taskPaused", TaskUnpaused = "taskUnpaused", TaskAskResponded = "taskAskResponded", @@ -33,6 +33,9 @@ export enum RooCodeEventName { EvalFail = "evalFail", } +// Backward compatibility alias +export const TaskModeSwitched = RooCodeEventName.TaskAgentSwitched + export const rooCodeEventsSchema = z.object({ [RooCodeEventName.Message]: z.tuple([ z.object({ @@ -43,7 +46,7 @@ export const rooCodeEventsSchema = z.object({ ]), [RooCodeEventName.TaskCreated]: z.tuple([z.string()]), [RooCodeEventName.TaskStarted]: z.tuple([z.string()]), - [RooCodeEventName.TaskModeSwitched]: z.tuple([z.string(), z.string()]), + [RooCodeEventName.TaskAgentSwitched]: z.tuple([z.string(), z.string()]), [RooCodeEventName.TaskPaused]: z.tuple([z.string()]), [RooCodeEventName.TaskUnpaused]: z.tuple([z.string()]), [RooCodeEventName.TaskAskResponded]: z.tuple([z.string()]), @@ -121,8 +124,8 @@ export const taskEventSchema = z.discriminatedUnion("eventName", [ taskId: z.number().optional(), }), z.object({ - eventName: z.literal(RooCodeEventName.TaskModeSwitched), - payload: rooCodeEventsSchema.shape[RooCodeEventName.TaskModeSwitched], + eventName: z.literal(RooCodeEventName.TaskAgentSwitched), + payload: rooCodeEventsSchema.shape[RooCodeEventName.TaskAgentSwitched], taskId: z.number().optional(), }), z.object({ diff --git a/packages/types/src/mode.ts b/packages/types/src/mode.ts index 88dcbb9574..660d7cf62f 100644 --- a/packages/types/src/mode.ts +++ b/packages/types/src/mode.ts @@ -39,7 +39,7 @@ export const groupEntrySchema = z.union([toolGroupsSchema, z.tuple([toolGroupsSc export type GroupEntry = z.infer /** - * ModeConfig + * AgentConfig */ const groupEntryArraySchema = z.array(groupEntrySchema).refine( @@ -61,7 +61,7 @@ const groupEntryArraySchema = z.array(groupEntrySchema).refine( { message: "Duplicate groups are not allowed" }, ) -export const modeConfigSchema = z.object({ +export const agentConfigSchema = z.object({ slug: z.string().regex(/^[a-zA-Z0-9-]+$/, "Slug must contain only letters numbers and dashes"), name: z.string().min(1, "Name is required"), roleDefinition: z.string().min(1, "Role definition is required"), @@ -72,14 +72,18 @@ export const modeConfigSchema = z.object({ source: z.enum(["global", "project"]).optional(), }) -export type ModeConfig = z.infer +export type AgentConfig = z.infer + +// Keep ModeConfig as an alias for backward compatibility (will be removed in later phase) +export const modeConfigSchema = agentConfigSchema +export type ModeConfig = AgentConfig /** - * CustomModesSettings + * CustomAgentsSettings */ -export const customModesSettingsSchema = z.object({ - customModes: z.array(modeConfigSchema).refine( +export const customAgentsSettingsSchema = z.object({ + customModes: z.array(agentConfigSchema).refine( (modes) => { const slugs = new Set() @@ -98,7 +102,11 @@ export const customModesSettingsSchema = z.object({ ), }) -export type CustomModesSettings = z.infer +export type CustomAgentsSettings = z.infer + +// Keep CustomModesSettings as an alias for backward compatibility (will be removed in later phase) +export const customModesSettingsSchema = customAgentsSettingsSchema +export type CustomModesSettings = CustomAgentsSettings /** * PromptComponent @@ -114,12 +122,16 @@ export const promptComponentSchema = z.object({ export type PromptComponent = z.infer /** - * CustomModePrompts + * CustomAgentPrompts */ -export const customModePromptsSchema = z.record(z.string(), promptComponentSchema.optional()) +export const customAgentPromptsSchema = z.record(z.string(), promptComponentSchema.optional()) -export type CustomModePrompts = z.infer +export type CustomAgentPrompts = z.infer + +// Keep CustomModePrompts as an alias for backward compatibility (will be removed in later phase) +export const customModePromptsSchema = customAgentPromptsSchema +export type CustomModePrompts = CustomAgentPrompts /** * CustomSupportPrompts @@ -133,7 +145,7 @@ export type CustomSupportPrompts = z.infer * DEFAULT_MODES */ -export const DEFAULT_MODES: readonly ModeConfig[] = [ +export const DEFAULT_AGENTS: readonly AgentConfig[] = [ { slug: "architect", name: "🏗️ Architect", @@ -193,3 +205,6 @@ export const DEFAULT_MODES: readonly ModeConfig[] = [ "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.", }, ] as const + +// Keep DEFAULT_MODES as an alias for backward compatibility (will be removed in later phase) +export const DEFAULT_MODES = DEFAULT_AGENTS diff --git a/src/core/assistant-message/presentAssistantMessage.ts b/src/core/assistant-message/presentAssistantMessage.ts index ee3fa148b4..6f2ac4812f 100644 --- a/src/core/assistant-message/presentAssistantMessage.ts +++ b/src/core/assistant-message/presentAssistantMessage.ts @@ -4,7 +4,7 @@ import { serializeError } from "serialize-error" import type { ToolName, ClineAsk, ToolProgressStatus } from "@roo-code/types" import { TelemetryService } from "@roo-code/telemetry" -import { defaultModeSlug, getModeBySlug } from "../../shared/modes" +import { defaultAgentSlug, getAgentBySlug } from "../../shared/agents" import type { ToolParamName, ToolResponse } from "../../shared/tools" import { fetchInstructionsTool } from "../tools/fetchInstructionsTool" @@ -209,10 +209,10 @@ export async function presentAssistantMessage(cline: Task) { case "update_todo_list": return `[${block.name}]` case "new_task": { - const mode = block.params.mode ?? defaultModeSlug + const agent = block.params.mode ?? defaultAgentSlug const message = block.params.message ?? "(no message)" - const modeName = getModeBySlug(mode, customModes)?.name ?? mode - return `[${block.name} in ${modeName} mode: '${message}']` + // We'll get the custom agents when we actually need them + return `[${block.name} in ${agent} agent: '${message}']` } } } @@ -352,13 +352,13 @@ export async function presentAssistantMessage(cline: Task) { } // Validate tool use before execution. - const { mode, customModes } = (await cline.providerRef.deref()?.getState()) ?? {} + const { mode: agent, customModes: customAgents } = (await cline.providerRef.deref()?.getState()) ?? {} try { validateToolUse( block.name as ToolName, - mode ?? defaultModeSlug, - customModes ?? [], + agent ?? defaultAgentSlug, + customAgents ?? [], { apply_diff: cline.diffEnabled }, block.params, ) diff --git a/src/core/config/CustomAgentsManager.ts b/src/core/config/CustomAgentsManager.ts new file mode 100644 index 0000000000..f7e42a6eeb --- /dev/null +++ b/src/core/config/CustomAgentsManager.ts @@ -0,0 +1,1033 @@ +import * as vscode from "vscode" +import * as path from "path" +import * as fs from "fs/promises" +import * as os from "os" + +import * as yaml from "yaml" +import stripBom from "strip-bom" + +import { + type AgentConfig, + type ModeConfig, + type PromptComponent, + customAgentsSettingsSchema, + customModesSettingsSchema, + agentConfigSchema, + modeConfigSchema, +} from "@roo-code/types" + +import { fileExistsAtPath } from "../../utils/fs" +import { getWorkspacePath } from "../../utils/path" +import { getGlobalRooDirectory } from "../../services/roo-config" +import { logger } from "../../utils/logging" +import { GlobalFileNames } from "../../shared/globalFileNames" +import { ensureSettingsDirectoryExists } from "../../utils/globalContext" +import { t } from "../../i18n" + +const ROOMODES_FILENAME = ".roomodes" + +// Type definitions for import/export functionality +interface RuleFile { + relativePath: string + content: string +} + +interface ExportedAgentConfig extends AgentConfig { + rulesFiles?: RuleFile[] +} + +// Keep ExportedModeConfig as an alias for backward compatibility +type ExportedModeConfig = ExportedAgentConfig + +interface ImportData { + customModes: ExportedAgentConfig[] +} + +interface ExportResult { + success: boolean + yaml?: string + error?: string +} + +interface ImportResult { + success: boolean + error?: string +} + +export class CustomAgentsManager { + private static readonly cacheTTL = 10_000 + + private disposables: vscode.Disposable[] = [] + private isWriting = false + private writeQueue: Array<() => Promise> = [] + private cachedAgents: AgentConfig[] | null = null + private cachedAt: number = 0 + + constructor( + private readonly context: vscode.ExtensionContext, + private readonly onUpdate: () => Promise, + ) { + this.watchCustomModesFiles().catch((error) => { + console.error("[CustomModesManager] Failed to setup file watchers:", error) + }) + } + + private async queueWrite(operation: () => Promise): Promise { + this.writeQueue.push(operation) + + if (!this.isWriting) { + await this.processWriteQueue() + } + } + + private async processWriteQueue(): Promise { + if (this.isWriting || this.writeQueue.length === 0) { + return + } + + this.isWriting = true + + try { + while (this.writeQueue.length > 0) { + const operation = this.writeQueue.shift() + + if (operation) { + await operation() + } + } + } finally { + this.isWriting = false + } + } + + private async getWorkspaceRoomodes(): Promise { + const workspaceFolders = vscode.workspace.workspaceFolders + + if (!workspaceFolders || workspaceFolders.length === 0) { + return undefined + } + + const workspaceRoot = getWorkspacePath() + const roomodesPath = path.join(workspaceRoot, ROOMODES_FILENAME) + const exists = await fileExistsAtPath(roomodesPath) + return exists ? roomodesPath : undefined + } + + /** + * Regex pattern for problematic characters that need to be cleaned from YAML content + * Includes: + * - \u00A0: Non-breaking space + * - \u200B-\u200D: Zero-width spaces and joiners + * - \u2010-\u2015, \u2212: Various dash characters + * - \u2018-\u2019: Smart single quotes + * - \u201C-\u201D: Smart double quotes + */ + private static readonly PROBLEMATIC_CHARS_REGEX = + // eslint-disable-next-line no-misleading-character-class + /[\u00A0\u200B\u200C\u200D\u2010\u2011\u2012\u2013\u2014\u2015\u2212\u2018\u2019\u201C\u201D]/g + + /** + * Clean invisible and problematic characters from YAML content + */ + private cleanInvisibleCharacters(content: string): string { + // Single pass replacement for all problematic characters + return content.replace(CustomAgentsManager.PROBLEMATIC_CHARS_REGEX, (match) => { + switch (match) { + case "\u00A0": // Non-breaking space + return " " + case "\u200B": // Zero-width space + case "\u200C": // Zero-width non-joiner + case "\u200D": // Zero-width joiner + return "" + case "\u2018": // Left single quotation mark + case "\u2019": // Right single quotation mark + return "'" + case "\u201C": // Left double quotation mark + case "\u201D": // Right double quotation mark + return '"' + default: // Dash characters (U+2010 through U+2015, U+2212) + return "-" + } + }) + } + + /** + * Parse YAML content with enhanced error handling and preprocessing + */ + private parseYamlSafely(content: string, filePath: string): any { + // Clean the content + let cleanedContent = stripBom(content) + cleanedContent = this.cleanInvisibleCharacters(cleanedContent) + + try { + const parsed = yaml.parse(cleanedContent) + // Ensure we never return null or undefined + return parsed ?? {} + } catch (yamlError) { + // For .roomodes files, try JSON as fallback + if (filePath.endsWith(ROOMODES_FILENAME)) { + try { + // Try parsing the original content as JSON (not the cleaned content) + return JSON.parse(content) + } catch (jsonError) { + // JSON also failed, show the original YAML error + const errorMsg = yamlError instanceof Error ? yamlError.message : String(yamlError) + console.error(`[CustomModesManager] Failed to parse YAML from ${filePath}:`, errorMsg) + + const lineMatch = errorMsg.match(/at line (\d+)/) + const line = lineMatch ? lineMatch[1] : "unknown" + vscode.window.showErrorMessage(t("common:customModes.errors.yamlParseError", { line })) + + // Return empty object to prevent duplicate error handling + return {} + } + } + + // For non-.roomodes files, just log and return empty object + const errorMsg = yamlError instanceof Error ? yamlError.message : String(yamlError) + console.error(`[CustomModesManager] Failed to parse YAML from ${filePath}:`, errorMsg) + return {} + } + } + + private async loadAgentsFromFile(filePath: string): Promise { + try { + const content = await fs.readFile(filePath, "utf-8") + const settings = this.parseYamlSafely(content, filePath) + + // Ensure settings has customModes property + if (!settings || typeof settings !== "object" || !settings.customModes) { + return [] + } + + const result = customAgentsSettingsSchema.safeParse(settings) + + if (!result.success) { + console.error(`[CustomModesManager] Schema validation failed for ${filePath}:`, result.error) + + // Show user-friendly error for .roomodes files + if (filePath.endsWith(ROOMODES_FILENAME)) { + const issues = result.error.issues + .map((issue) => `• ${issue.path.join(".")}: ${issue.message}`) + .join("\n") + + vscode.window.showErrorMessage(t("common:customModes.errors.schemaValidationError", { issues })) + } + + return [] + } + + // Determine source based on file path + const isRoomodes = filePath.endsWith(ROOMODES_FILENAME) + const source = isRoomodes ? ("project" as const) : ("global" as const) + + // Add source to each mode + return result.data.customModes.map((mode) => ({ ...mode, source })) + } catch (error) { + // Only log if the error wasn't already handled in parseYamlSafely + if (!(error as any).alreadyHandled) { + const errorMsg = `Failed to load modes from ${filePath}: ${error instanceof Error ? error.message : String(error)}` + console.error(`[CustomModesManager] ${errorMsg}`) + } + return [] + } + } + + private async mergeCustomAgents(projectAgents: AgentConfig[], globalAgents: AgentConfig[]): Promise { + const slugs = new Set() + const merged: AgentConfig[] = [] + + // Add project agent (takes precedence) + for (const agent of projectAgents) { + if (!slugs.has(agent.slug)) { + slugs.add(agent.slug) + merged.push({ ...agent, source: "project" }) + } + } + + // Add non-duplicate global agents + for (const agent of globalAgents) { + if (!slugs.has(agent.slug)) { + slugs.add(agent.slug) + merged.push({ ...agent, source: "global" }) + } + } + + return merged + } + + // Keep mergeCustomModes as an alias for backward compatibility + private async mergeCustomModes(projectModes: ModeConfig[], globalModes: ModeConfig[]): Promise { + return this.mergeCustomAgents(projectModes, globalModes) + } + + public async getCustomModesFilePath(): Promise { + const settingsDir = await ensureSettingsDirectoryExists(this.context) + const filePath = path.join(settingsDir, GlobalFileNames.customModes) + const fileExists = await fileExistsAtPath(filePath) + + if (!fileExists) { + await this.queueWrite(() => fs.writeFile(filePath, yaml.stringify({ customModes: [] }, { lineWidth: 0 }))) + } + + return filePath + } + + private async watchCustomModesFiles(): Promise { + // Skip if test environment is detected + if (process.env.NODE_ENV === "test") { + return + } + + const settingsPath = await this.getCustomModesFilePath() + + // Watch settings file + const settingsWatcher = vscode.workspace.createFileSystemWatcher(settingsPath) + + const handleSettingsChange = async () => { + try { + // Ensure that the settings file exists (especially important for delete events) + await this.getCustomModesFilePath() + const content = await fs.readFile(settingsPath, "utf-8") + + const errorMessage = t("common:customModes.errors.invalidFormat") + + let config: any + + try { + config = this.parseYamlSafely(content, settingsPath) + } catch (error) { + console.error(error) + vscode.window.showErrorMessage(errorMessage) + return + } + + const result = customAgentsSettingsSchema.safeParse(config) + + if (!result.success) { + vscode.window.showErrorMessage(errorMessage) + return + } + + // Get modes from .roomodes if it exists (takes precedence) + const roomodesPath = await this.getWorkspaceRoomodes() + const roomodesModes = roomodesPath ? await this.loadAgentsFromFile(roomodesPath) : [] + + // Merge modes from both sources (.roomodes takes precedence) + const mergedModes = await this.mergeCustomModes(roomodesModes, result.data.customModes) + await this.context.globalState.update("customModes", mergedModes) + this.clearCache() + await this.onUpdate() + } catch (error) { + console.error(`[CustomModesManager] Error handling settings file change:`, error) + } + } + + this.disposables.push(settingsWatcher.onDidChange(handleSettingsChange)) + this.disposables.push(settingsWatcher.onDidCreate(handleSettingsChange)) + this.disposables.push(settingsWatcher.onDidDelete(handleSettingsChange)) + this.disposables.push(settingsWatcher) + + // Watch .roomodes file - watch the path even if it doesn't exist yet + const workspaceFolders = vscode.workspace.workspaceFolders + if (workspaceFolders && workspaceFolders.length > 0) { + const workspaceRoot = getWorkspacePath() + const roomodesPath = path.join(workspaceRoot, ROOMODES_FILENAME) + const roomodesWatcher = vscode.workspace.createFileSystemWatcher(roomodesPath) + + const handleRoomodesChange = async () => { + try { + const settingsModes = await this.loadAgentsFromFile(settingsPath) + const roomodesModes = await this.loadAgentsFromFile(roomodesPath) + // .roomodes takes precedence + const mergedModes = await this.mergeCustomModes(roomodesModes, settingsModes) + await this.context.globalState.update("customModes", mergedModes) + this.clearCache() + await this.onUpdate() + } catch (error) { + console.error(`[CustomModesManager] Error handling .roomodes file change:`, error) + } + } + + this.disposables.push(roomodesWatcher.onDidChange(handleRoomodesChange)) + this.disposables.push(roomodesWatcher.onDidCreate(handleRoomodesChange)) + this.disposables.push( + roomodesWatcher.onDidDelete(async () => { + // When .roomodes is deleted, refresh with only settings modes + try { + const settingsModes = await this.loadAgentsFromFile(settingsPath) + await this.context.globalState.update("customModes", settingsModes) + this.clearCache() + await this.onUpdate() + } catch (error) { + console.error(`[CustomModesManager] Error handling .roomodes file deletion:`, error) + } + }), + ) + this.disposables.push(roomodesWatcher) + } + } + + public async getCustomAgents(): Promise { + // Check if we have a valid cached result. + const now = Date.now() + + if (this.cachedAgents && now - this.cachedAt < CustomAgentsManager.cacheTTL) { + return this.cachedAgents + } + + // Get agents from settings file. + const settingsPath = await this.getCustomModesFilePath() + const settingsAgents = await this.loadAgentsFromFile(settingsPath) + + // Get agents from .roomodes if it exists. + const roomodesPath = await this.getWorkspaceRoomodes() + const roomodesAgents = roomodesPath ? await this.loadAgentsFromFile(roomodesPath) : [] + + // Create maps to store agents by source. + const projectAgents = new Map() + const globalAgents = new Map() + + // Add project agents (they take precedence). + for (const agent of roomodesAgents) { + projectAgents.set(agent.slug, { ...agent, source: "project" as const }) + } + + // Add global agents. + for (const agent of settingsAgents) { + if (!projectAgents.has(agent.slug)) { + globalAgents.set(agent.slug, { ...agent, source: "global" as const }) + } + } + + // Combine agents in the correct order: project agents first, then global agents. + const mergedAgents = [ + ...roomodesAgents.map((agent) => ({ ...agent, source: "project" as const })), + ...settingsAgents + .filter((agent) => !projectAgents.has(agent.slug)) + .map((agent) => ({ ...agent, source: "global" as const })), + ] + + await this.context.globalState.update("customModes", mergedAgents) + + this.cachedAgents = mergedAgents + this.cachedAt = now + + return mergedAgents + } + + // Keep getCustomModes as an alias for backward compatibility + public async getCustomModes(): Promise { + return this.getCustomAgents() + } + + public async updateCustomAgent(slug: string, config: AgentConfig): Promise { + try { + // Validate the agent configuration before saving + const validationResult = agentConfigSchema.safeParse(config) + if (!validationResult.success) { + const errors = validationResult.error.errors.map((e) => e.message).join(", ") + logger.error(`Invalid agent configuration for ${slug}`, { errors: validationResult.error.errors }) + throw new Error(`Invalid agent configuration: ${errors}`) + } + + const isProjectMode = config.source === "project" + let targetPath: string + + if (isProjectMode) { + const workspaceFolders = vscode.workspace.workspaceFolders + + if (!workspaceFolders || workspaceFolders.length === 0) { + logger.error("Failed to update project mode: No workspace folder found", { slug }) + throw new Error(t("common:customModes.errors.noWorkspaceForProject")) + } + + const workspaceRoot = getWorkspacePath() + targetPath = path.join(workspaceRoot, ROOMODES_FILENAME) + const exists = await fileExistsAtPath(targetPath) + + logger.info(`${exists ? "Updating" : "Creating"} project mode in ${ROOMODES_FILENAME}`, { + slug, + workspace: workspaceRoot, + }) + } else { + targetPath = await this.getCustomModesFilePath() + } + + await this.queueWrite(async () => { + // Ensure source is set correctly based on target file. + const modeWithSource = { + ...config, + source: isProjectMode ? ("project" as const) : ("global" as const), + } + + await this.updateAgentsInFile(targetPath, (modes) => { + const updatedModes = modes.filter((m: AgentConfig) => m.slug !== slug) + updatedModes.push(modeWithSource) + return updatedModes + }) + + this.clearCache() + await this.refreshMergedState() + }) + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error) + logger.error("Failed to update custom mode", { slug, error: errorMessage }) + vscode.window.showErrorMessage(t("common:customModes.errors.updateFailed", { error: errorMessage })) + } + } + + // Keep updateCustomMode as an alias for backward compatibility + public async updateCustomMode(slug: string, config: ModeConfig): Promise { + return this.updateCustomAgent(slug, config) + } + + private async updateAgentsInFile( + filePath: string, + operation: (agents: AgentConfig[]) => AgentConfig[], + ): Promise { + let content = "{}" + + try { + content = await fs.readFile(filePath, "utf-8") + } catch (error) { + // File might not exist yet. + content = yaml.stringify({ customModes: [] }, { lineWidth: 0 }) + } + + let settings + + try { + settings = this.parseYamlSafely(content, filePath) + } catch (error) { + // Error already logged in parseYamlSafely + settings = { customModes: [] } + } + + // Ensure settings is an object and has customModes property + if (!settings || typeof settings !== "object") { + settings = { customModes: [] } + } + if (!settings.customModes) { + settings.customModes = [] + } + + settings.customModes = operation(settings.customModes) + await fs.writeFile(filePath, yaml.stringify(settings, { lineWidth: 0 }), "utf-8") + } + + private async refreshMergedState(): Promise { + const settingsPath = await this.getCustomModesFilePath() + const roomodesPath = await this.getWorkspaceRoomodes() + + const settingsModes = await this.loadAgentsFromFile(settingsPath) + const roomodesModes = roomodesPath ? await this.loadAgentsFromFile(roomodesPath) : [] + const mergedModes = await this.mergeCustomAgents(roomodesModes, settingsModes) + + await this.context.globalState.update("customModes", mergedModes) + + this.clearCache() + + await this.onUpdate() + } + + public async deleteCustomMode(slug: string, fromMarketplace = false): Promise { + try { + const settingsPath = await this.getCustomModesFilePath() + const roomodesPath = await this.getWorkspaceRoomodes() + + const settingsModes = await this.loadAgentsFromFile(settingsPath) + const roomodesModes = roomodesPath ? await this.loadAgentsFromFile(roomodesPath) : [] + + // Find the mode in either file + const projectMode = roomodesModes.find((m: AgentConfig) => m.slug === slug) + const globalMode = settingsModes.find((m: AgentConfig) => m.slug === slug) + + if (!projectMode && !globalMode) { + throw new Error(t("common:customModes.errors.modeNotFound")) + } + + // Determine which mode to use for rules folder path calculation + const modeToDelete = projectMode || globalMode + + await this.queueWrite(async () => { + // Delete from project first if it exists there + if (projectMode && roomodesPath) { + await this.updateAgentsInFile(roomodesPath, (modes) => + modes.filter((m: AgentConfig) => m.slug !== slug), + ) + } + + // Delete from global settings if it exists there + if (globalMode) { + await this.updateAgentsInFile(settingsPath, (modes) => + modes.filter((m: AgentConfig) => m.slug !== slug), + ) + } + + // Delete associated rules folder + if (modeToDelete) { + await this.deleteRulesFolder(slug, modeToDelete, fromMarketplace) + } + + // Clear cache when modes are deleted + this.clearCache() + await this.refreshMergedState() + }) + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error) + vscode.window.showErrorMessage(t("common:customModes.errors.deleteFailed", { error: errorMessage })) + } + } + + /** + * Deletes the rules folder for a specific mode + * @param slug - The mode slug + * @param mode - The mode configuration to determine the scope + */ + private async deleteRulesFolder(slug: string, agent: AgentConfig, fromMarketplace = false): Promise { + try { + // Determine the scope based on source (project or global) + const scope = agent.source || "global" + + // Determine the rules folder path + let rulesFolderPath: string + if (scope === "project") { + const workspacePath = getWorkspacePath() + if (workspacePath) { + rulesFolderPath = path.join(workspacePath, ".roo", `rules-${slug}`) + } else { + return // No workspace, can't delete project rules + } + } else { + // Global scope - use OS home directory + const homeDir = os.homedir() + rulesFolderPath = path.join(homeDir, ".roo", `rules-${slug}`) + } + + // Check if the rules folder exists and delete it + const rulesFolderExists = await fileExistsAtPath(rulesFolderPath) + if (rulesFolderExists) { + try { + await fs.rm(rulesFolderPath, { recursive: true, force: true }) + logger.info(`Deleted rules folder for mode ${slug}: ${rulesFolderPath}`) + } catch (error) { + logger.error(`Failed to delete rules folder for mode ${slug}: ${error}`) + // Notify the user about the failure + const messageKey = fromMarketplace + ? "common:marketplace.mode.rulesCleanupFailed" + : "common:customModes.errors.rulesCleanupFailed" + vscode.window.showWarningMessage(t(messageKey, { rulesFolderPath })) + // Continue even if folder deletion fails + } + } + } catch (error) { + logger.error(`Error deleting rules folder for mode ${slug}`, { + error: error instanceof Error ? error.message : String(error), + }) + } + } + + public async resetCustomModes(): Promise { + try { + const filePath = await this.getCustomModesFilePath() + await fs.writeFile(filePath, yaml.stringify({ customModes: [] }, { lineWidth: 0 })) + await this.context.globalState.update("customModes", []) + this.clearCache() + await this.onUpdate() + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error) + vscode.window.showErrorMessage(t("common:customModes.errors.resetFailed", { error: errorMessage })) + } + } + + /** + * Checks if a mode has associated rules files in the .roo/rules-{slug}/ directory + * @param slug - The mode identifier to check + * @returns True if the mode has rules files with content, false otherwise + */ + public async checkRulesDirectoryHasContent(slug: string): Promise { + try { + // First, find the agent to determine its source + const allAgents = await this.getCustomAgents() + const agent = allAgents.find((a) => a.slug === slug) + + if (!agent) { + // If not in custom modes, check if it's in .roomodes (project-specific) + const workspacePath = getWorkspacePath() + if (!workspacePath) { + return false + } + + const roomodesPath = path.join(workspacePath, ROOMODES_FILENAME) + try { + const roomodesExists = await fileExistsAtPath(roomodesPath) + if (roomodesExists) { + const roomodesContent = await fs.readFile(roomodesPath, "utf-8") + const roomodesData = yaml.parse(roomodesContent) + const roomodesModes = roomodesData?.customModes || [] + + // Check if this specific mode exists in .roomodes + const modeInRoomodes = roomodesModes.find((m: any) => m.slug === slug) + if (!modeInRoomodes) { + return false // Mode not found anywhere + } + } else { + return false // No .roomodes file and not in custom modes + } + } catch (error) { + return false // Cannot read .roomodes and not in custom modes + } + } + + // Determine the correct rules directory based on agent source + let agentRulesDir: string + const isGlobalAgent = agent?.source === "global" + + if (isGlobalAgent) { + // For global agents, check in global .roo directory + const globalRooDir = getGlobalRooDirectory() + agentRulesDir = path.join(globalRooDir, `rules-${slug}`) + } else { + // For project agents, check in workspace .roo directory + const workspacePath = getWorkspacePath() + if (!workspacePath) { + return false + } + agentRulesDir = path.join(workspacePath, ".roo", `rules-${slug}`) + } + + try { + const stats = await fs.stat(agentRulesDir) + if (!stats.isDirectory()) { + return false + } + } catch (error) { + return false + } + + // Check if directory has any content files + try { + const entries = await fs.readdir(agentRulesDir, { withFileTypes: true }) + + for (const entry of entries) { + if (entry.isFile()) { + // Use path.join with agentRulesDir and entry.name for compatibility + const filePath = path.join(agentRulesDir, entry.name) + const content = await fs.readFile(filePath, "utf-8") + if (content.trim()) { + return true // Found at least one file with content + } + } + } + + return false // No files with content found + } catch (error) { + return false + } + } catch (error) { + logger.error("Failed to check rules directory for mode", { + slug, + error: error instanceof Error ? error.message : String(error), + }) + return false + } + } + + /** + * Exports a mode configuration with its associated rules files into a shareable YAML format + * @param slug - The mode identifier to export + * @param customPrompts - Optional custom prompts to merge into the export + * @returns Success status with YAML content or error message + */ + public async exportModeWithRules(slug: string, customPrompts?: PromptComponent): Promise { + try { + // Import modes from shared to check built-in modes + const { modes: builtInModes } = await import("../../shared/modes") + + // Get all current modes + const allModes = await this.getCustomModes() + let mode = allModes.find((m) => m.slug === slug) + + // If mode not found in custom modes, check if it's a built-in mode that has been customized + if (!mode) { + // Only check workspace-based modes if workspace is available + const workspacePath = getWorkspacePath() + if (workspacePath) { + const roomodesPath = path.join(workspacePath, ROOMODES_FILENAME) + try { + const roomodesExists = await fileExistsAtPath(roomodesPath) + if (roomodesExists) { + const roomodesContent = await fs.readFile(roomodesPath, "utf-8") + const roomodesData = yaml.parse(roomodesContent) + const roomodesModes = roomodesData?.customModes || [] + + // Find the mode in .roomodes + mode = roomodesModes.find((m: any) => m.slug === slug) + } + } catch (error) { + // Continue to check built-in modes + } + } + + // If still not found, check if it's a built-in mode + if (!mode) { + const builtInMode = builtInModes.find((m) => m.slug === slug) + if (builtInMode) { + // Use the built-in mode as the base + mode = { ...builtInMode } + } else { + return { success: false, error: "Mode not found" } + } + } + } + + // Determine the base directory based on mode source + const isGlobalMode = mode.source === "global" + let baseDir: string + if (isGlobalMode) { + // For global modes, use the global .roo directory + baseDir = getGlobalRooDirectory() + } else { + // For project modes, use the workspace directory + const workspacePath = getWorkspacePath() + if (!workspacePath) { + return { success: false, error: "No workspace found" } + } + baseDir = workspacePath + } + + // Check for .roo/rules-{slug}/ directory (or rules-{slug}/ for global) + const modeRulesDir = isGlobalMode + ? path.join(baseDir, `rules-${slug}`) + : path.join(baseDir, ".roo", `rules-${slug}`) + + let rulesFiles: RuleFile[] = [] + try { + const stats = await fs.stat(modeRulesDir) + if (stats.isDirectory()) { + // Extract content specific to this mode by looking for the mode-specific rules + const entries = await fs.readdir(modeRulesDir, { withFileTypes: true }) + + for (const entry of entries) { + if (entry.isFile()) { + // Use path.join with modeRulesDir and entry.name for compatibility + const filePath = path.join(modeRulesDir, entry.name) + const content = await fs.readFile(filePath, "utf-8") + if (content.trim()) { + // Calculate relative path based on mode source + const relativePath = isGlobalMode + ? path.relative(baseDir, filePath) + : path.relative(path.join(baseDir, ".roo"), filePath) + rulesFiles.push({ relativePath, content: content.trim() }) + } + } + } + } + } catch (error) { + // Directory doesn't exist, which is fine - mode might not have rules + } + + // Create an export mode with rules files preserved + const exportMode: ExportedModeConfig = { + ...mode, + // Remove source property for export + source: "project" as const, + } + + // Merge custom prompts if provided + if (customPrompts) { + if (customPrompts.roleDefinition) exportMode.roleDefinition = customPrompts.roleDefinition + if (customPrompts.description) exportMode.description = customPrompts.description + if (customPrompts.whenToUse) exportMode.whenToUse = customPrompts.whenToUse + if (customPrompts.customInstructions) exportMode.customInstructions = customPrompts.customInstructions + } + + // Add rules files if any exist + if (rulesFiles.length > 0) { + exportMode.rulesFiles = rulesFiles + } + + // Generate YAML + const exportData = { + customModes: [exportMode], + } + + const yamlContent = yaml.stringify(exportData) + + return { success: true, yaml: yamlContent } + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error) + logger.error("Failed to export mode with rules", { slug, error: errorMessage }) + return { success: false, error: errorMessage } + } + } + + /** + * Helper method to import rules files for a mode + * @param importMode - The mode being imported + * @param rulesFiles - The rules files to import + * @param source - The import source ("global" or "project") + */ + private async importRulesFiles( + importAgent: ExportedAgentConfig, + rulesFiles: RuleFile[], + source: "global" | "project", + ): Promise { + // Determine base directory and rules folder path based on source + let baseDir: string + let rulesFolderPath: string + + if (source === "global") { + baseDir = getGlobalRooDirectory() + rulesFolderPath = path.join(baseDir, `rules-${importAgent.slug}`) + } else { + const workspacePath = getWorkspacePath() + baseDir = path.join(workspacePath, ".roo") + rulesFolderPath = path.join(baseDir, `rules-${importAgent.slug}`) + } + + // Always remove the existing rules folder for this agent if it exists + // This ensures that if the imported agent has no rules, the folder is cleaned up + try { + await fs.rm(rulesFolderPath, { recursive: true, force: true }) + logger.info(`Removed existing ${source} rules folder for agent ${importAgent.slug}`) + } catch (error) { + // It's okay if the folder doesn't exist + logger.debug(`No existing ${source} rules folder to remove for agent ${importAgent.slug}`) + } + + // Only proceed with file creation if there are rules files to import + if (!rulesFiles || !Array.isArray(rulesFiles) || rulesFiles.length === 0) { + return + } + + // Import the new rules files with path validation + for (const ruleFile of rulesFiles) { + if (ruleFile.relativePath && ruleFile.content) { + // Validate the relative path to prevent path traversal attacks + const normalizedRelativePath = path.normalize(ruleFile.relativePath) + + // Ensure the path doesn't contain traversal sequences + if (normalizedRelativePath.includes("..") || path.isAbsolute(normalizedRelativePath)) { + logger.error(`Invalid file path detected: ${ruleFile.relativePath}`) + continue // Skip this file but continue with others + } + + const targetPath = path.join(baseDir, normalizedRelativePath) + const normalizedTargetPath = path.normalize(targetPath) + const expectedBasePath = path.normalize(baseDir) + + // Ensure the resolved path stays within the base directory + if (!normalizedTargetPath.startsWith(expectedBasePath)) { + logger.error(`Path traversal attempt detected: ${ruleFile.relativePath}`) + continue // Skip this file but continue with others + } + + // Ensure directory exists + const targetDir = path.dirname(targetPath) + await fs.mkdir(targetDir, { recursive: true }) + + // Write the file + await fs.writeFile(targetPath, ruleFile.content, "utf-8") + } + } + } + + /** + * Imports modes from YAML content, including their associated rules files + * @param yamlContent - The YAML content containing mode configurations + * @param source - Target level for import: "global" (all projects) or "project" (current workspace only) + * @returns Success status with optional error message + */ + public async importModeWithRules( + yamlContent: string, + source: "global" | "project" = "project", + ): Promise { + try { + // Parse the YAML content with proper type validation + let importData: ImportData + try { + const parsed = yaml.parse(yamlContent) + + // Validate the structure + if (!parsed?.customModes || !Array.isArray(parsed.customModes) || parsed.customModes.length === 0) { + return { success: false, error: "Invalid import format: Expected 'customModes' array in YAML" } + } + + importData = parsed as ImportData + } catch (parseError) { + return { + success: false, + error: `Invalid YAML format: ${parseError instanceof Error ? parseError.message : "Failed to parse YAML"}`, + } + } + + // Check workspace availability early if importing at project level + if (source === "project") { + const workspacePath = getWorkspacePath() + if (!workspacePath) { + return { success: false, error: "No workspace found" } + } + } + + // Process each mode in the import + for (const importMode of importData.customModes) { + const { rulesFiles, ...modeConfig } = importMode + + // Validate the agent configuration + const validationResult = agentConfigSchema.safeParse(modeConfig) + if (!validationResult.success) { + logger.error(`Invalid agent configuration for ${modeConfig.slug}`, { + errors: validationResult.error.errors, + }) + return { + success: false, + error: `Invalid mode configuration for ${modeConfig.slug}: ${validationResult.error.errors.map((e) => e.message).join(", ")}`, + } + } + + // Check for existing mode conflicts + const existingModes = await this.getCustomModes() + const existingMode = existingModes.find((m) => m.slug === importMode.slug) + if (existingMode) { + logger.info(`Overwriting existing mode: ${importMode.slug}`) + } + + // Import the mode configuration with the specified source + await this.updateCustomAgent(importMode.slug, { + ...modeConfig, + source: source, // Use the provided source parameter + }) + + // Import rules files (this also handles cleanup of existing rules folders) + await this.importRulesFiles(importMode, rulesFiles || [], source) + } + + // Refresh the modes after import + await this.refreshMergedState() + + return { success: true } + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error) + logger.error("Failed to import mode with rules", { error: errorMessage }) + return { success: false, error: errorMessage } + } + } + + private clearCache(): void { + this.cachedAgents = null + this.cachedAt = 0 + } + + dispose(): void { + for (const disposable of this.disposables) { + disposable.dispose() + } + + this.disposables = [] + } +} + +// Keep CustomModesManager as an alias for backward compatibility +export { CustomAgentsManager as CustomModesManager } diff --git a/src/core/config/CustomModesManager.ts b/src/core/config/CustomModesManager.ts index c388c1a537..166c08033e 100644 --- a/src/core/config/CustomModesManager.ts +++ b/src/core/config/CustomModesManager.ts @@ -1,997 +1,3 @@ -import * as vscode from "vscode" -import * as path from "path" -import * as fs from "fs/promises" -import * as os from "os" - -import * as yaml from "yaml" -import stripBom from "strip-bom" - -import { type ModeConfig, type PromptComponent, customModesSettingsSchema, modeConfigSchema } from "@roo-code/types" - -import { fileExistsAtPath } from "../../utils/fs" -import { getWorkspacePath } from "../../utils/path" -import { getGlobalRooDirectory } from "../../services/roo-config" -import { logger } from "../../utils/logging" -import { GlobalFileNames } from "../../shared/globalFileNames" -import { ensureSettingsDirectoryExists } from "../../utils/globalContext" -import { t } from "../../i18n" - -const ROOMODES_FILENAME = ".roomodes" - -// Type definitions for import/export functionality -interface RuleFile { - relativePath: string - content: string -} - -interface ExportedModeConfig extends ModeConfig { - rulesFiles?: RuleFile[] -} - -interface ImportData { - customModes: ExportedModeConfig[] -} - -interface ExportResult { - success: boolean - yaml?: string - error?: string -} - -interface ImportResult { - success: boolean - error?: string -} - -export class CustomModesManager { - private static readonly cacheTTL = 10_000 - - private disposables: vscode.Disposable[] = [] - private isWriting = false - private writeQueue: Array<() => Promise> = [] - private cachedModes: ModeConfig[] | null = null - private cachedAt: number = 0 - - constructor( - private readonly context: vscode.ExtensionContext, - private readonly onUpdate: () => Promise, - ) { - this.watchCustomModesFiles().catch((error) => { - console.error("[CustomModesManager] Failed to setup file watchers:", error) - }) - } - - private async queueWrite(operation: () => Promise): Promise { - this.writeQueue.push(operation) - - if (!this.isWriting) { - await this.processWriteQueue() - } - } - - private async processWriteQueue(): Promise { - if (this.isWriting || this.writeQueue.length === 0) { - return - } - - this.isWriting = true - - try { - while (this.writeQueue.length > 0) { - const operation = this.writeQueue.shift() - - if (operation) { - await operation() - } - } - } finally { - this.isWriting = false - } - } - - private async getWorkspaceRoomodes(): Promise { - const workspaceFolders = vscode.workspace.workspaceFolders - - if (!workspaceFolders || workspaceFolders.length === 0) { - return undefined - } - - const workspaceRoot = getWorkspacePath() - const roomodesPath = path.join(workspaceRoot, ROOMODES_FILENAME) - const exists = await fileExistsAtPath(roomodesPath) - return exists ? roomodesPath : undefined - } - - /** - * Regex pattern for problematic characters that need to be cleaned from YAML content - * Includes: - * - \u00A0: Non-breaking space - * - \u200B-\u200D: Zero-width spaces and joiners - * - \u2010-\u2015, \u2212: Various dash characters - * - \u2018-\u2019: Smart single quotes - * - \u201C-\u201D: Smart double quotes - */ - private static readonly PROBLEMATIC_CHARS_REGEX = - // eslint-disable-next-line no-misleading-character-class - /[\u00A0\u200B\u200C\u200D\u2010\u2011\u2012\u2013\u2014\u2015\u2212\u2018\u2019\u201C\u201D]/g - - /** - * Clean invisible and problematic characters from YAML content - */ - private cleanInvisibleCharacters(content: string): string { - // Single pass replacement for all problematic characters - return content.replace(CustomModesManager.PROBLEMATIC_CHARS_REGEX, (match) => { - switch (match) { - case "\u00A0": // Non-breaking space - return " " - case "\u200B": // Zero-width space - case "\u200C": // Zero-width non-joiner - case "\u200D": // Zero-width joiner - return "" - case "\u2018": // Left single quotation mark - case "\u2019": // Right single quotation mark - return "'" - case "\u201C": // Left double quotation mark - case "\u201D": // Right double quotation mark - return '"' - default: // Dash characters (U+2010 through U+2015, U+2212) - return "-" - } - }) - } - - /** - * Parse YAML content with enhanced error handling and preprocessing - */ - private parseYamlSafely(content: string, filePath: string): any { - // Clean the content - let cleanedContent = stripBom(content) - cleanedContent = this.cleanInvisibleCharacters(cleanedContent) - - try { - const parsed = yaml.parse(cleanedContent) - // Ensure we never return null or undefined - return parsed ?? {} - } catch (yamlError) { - // For .roomodes files, try JSON as fallback - if (filePath.endsWith(ROOMODES_FILENAME)) { - try { - // Try parsing the original content as JSON (not the cleaned content) - return JSON.parse(content) - } catch (jsonError) { - // JSON also failed, show the original YAML error - const errorMsg = yamlError instanceof Error ? yamlError.message : String(yamlError) - console.error(`[CustomModesManager] Failed to parse YAML from ${filePath}:`, errorMsg) - - const lineMatch = errorMsg.match(/at line (\d+)/) - const line = lineMatch ? lineMatch[1] : "unknown" - vscode.window.showErrorMessage(t("common:customModes.errors.yamlParseError", { line })) - - // Return empty object to prevent duplicate error handling - return {} - } - } - - // For non-.roomodes files, just log and return empty object - const errorMsg = yamlError instanceof Error ? yamlError.message : String(yamlError) - console.error(`[CustomModesManager] Failed to parse YAML from ${filePath}:`, errorMsg) - return {} - } - } - - private async loadModesFromFile(filePath: string): Promise { - try { - const content = await fs.readFile(filePath, "utf-8") - const settings = this.parseYamlSafely(content, filePath) - - // Ensure settings has customModes property - if (!settings || typeof settings !== "object" || !settings.customModes) { - return [] - } - - const result = customModesSettingsSchema.safeParse(settings) - - if (!result.success) { - console.error(`[CustomModesManager] Schema validation failed for ${filePath}:`, result.error) - - // Show user-friendly error for .roomodes files - if (filePath.endsWith(ROOMODES_FILENAME)) { - const issues = result.error.issues - .map((issue) => `• ${issue.path.join(".")}: ${issue.message}`) - .join("\n") - - vscode.window.showErrorMessage(t("common:customModes.errors.schemaValidationError", { issues })) - } - - return [] - } - - // Determine source based on file path - const isRoomodes = filePath.endsWith(ROOMODES_FILENAME) - const source = isRoomodes ? ("project" as const) : ("global" as const) - - // Add source to each mode - return result.data.customModes.map((mode) => ({ ...mode, source })) - } catch (error) { - // Only log if the error wasn't already handled in parseYamlSafely - if (!(error as any).alreadyHandled) { - const errorMsg = `Failed to load modes from ${filePath}: ${error instanceof Error ? error.message : String(error)}` - console.error(`[CustomModesManager] ${errorMsg}`) - } - return [] - } - } - - private async mergeCustomModes(projectModes: ModeConfig[], globalModes: ModeConfig[]): Promise { - const slugs = new Set() - const merged: ModeConfig[] = [] - - // Add project mode (takes precedence) - for (const mode of projectModes) { - if (!slugs.has(mode.slug)) { - slugs.add(mode.slug) - merged.push({ ...mode, source: "project" }) - } - } - - // Add non-duplicate global modes - for (const mode of globalModes) { - if (!slugs.has(mode.slug)) { - slugs.add(mode.slug) - merged.push({ ...mode, source: "global" }) - } - } - - return merged - } - - public async getCustomModesFilePath(): Promise { - const settingsDir = await ensureSettingsDirectoryExists(this.context) - const filePath = path.join(settingsDir, GlobalFileNames.customModes) - const fileExists = await fileExistsAtPath(filePath) - - if (!fileExists) { - await this.queueWrite(() => fs.writeFile(filePath, yaml.stringify({ customModes: [] }, { lineWidth: 0 }))) - } - - return filePath - } - - private async watchCustomModesFiles(): Promise { - // Skip if test environment is detected - if (process.env.NODE_ENV === "test") { - return - } - - const settingsPath = await this.getCustomModesFilePath() - - // Watch settings file - const settingsWatcher = vscode.workspace.createFileSystemWatcher(settingsPath) - - const handleSettingsChange = async () => { - try { - // Ensure that the settings file exists (especially important for delete events) - await this.getCustomModesFilePath() - const content = await fs.readFile(settingsPath, "utf-8") - - const errorMessage = t("common:customModes.errors.invalidFormat") - - let config: any - - try { - config = this.parseYamlSafely(content, settingsPath) - } catch (error) { - console.error(error) - vscode.window.showErrorMessage(errorMessage) - return - } - - const result = customModesSettingsSchema.safeParse(config) - - if (!result.success) { - vscode.window.showErrorMessage(errorMessage) - return - } - - // Get modes from .roomodes if it exists (takes precedence) - const roomodesPath = await this.getWorkspaceRoomodes() - const roomodesModes = roomodesPath ? await this.loadModesFromFile(roomodesPath) : [] - - // Merge modes from both sources (.roomodes takes precedence) - const mergedModes = await this.mergeCustomModes(roomodesModes, result.data.customModes) - await this.context.globalState.update("customModes", mergedModes) - this.clearCache() - await this.onUpdate() - } catch (error) { - console.error(`[CustomModesManager] Error handling settings file change:`, error) - } - } - - this.disposables.push(settingsWatcher.onDidChange(handleSettingsChange)) - this.disposables.push(settingsWatcher.onDidCreate(handleSettingsChange)) - this.disposables.push(settingsWatcher.onDidDelete(handleSettingsChange)) - this.disposables.push(settingsWatcher) - - // Watch .roomodes file - watch the path even if it doesn't exist yet - const workspaceFolders = vscode.workspace.workspaceFolders - if (workspaceFolders && workspaceFolders.length > 0) { - const workspaceRoot = getWorkspacePath() - const roomodesPath = path.join(workspaceRoot, ROOMODES_FILENAME) - const roomodesWatcher = vscode.workspace.createFileSystemWatcher(roomodesPath) - - const handleRoomodesChange = async () => { - try { - const settingsModes = await this.loadModesFromFile(settingsPath) - const roomodesModes = await this.loadModesFromFile(roomodesPath) - // .roomodes takes precedence - const mergedModes = await this.mergeCustomModes(roomodesModes, settingsModes) - await this.context.globalState.update("customModes", mergedModes) - this.clearCache() - await this.onUpdate() - } catch (error) { - console.error(`[CustomModesManager] Error handling .roomodes file change:`, error) - } - } - - this.disposables.push(roomodesWatcher.onDidChange(handleRoomodesChange)) - this.disposables.push(roomodesWatcher.onDidCreate(handleRoomodesChange)) - this.disposables.push( - roomodesWatcher.onDidDelete(async () => { - // When .roomodes is deleted, refresh with only settings modes - try { - const settingsModes = await this.loadModesFromFile(settingsPath) - await this.context.globalState.update("customModes", settingsModes) - this.clearCache() - await this.onUpdate() - } catch (error) { - console.error(`[CustomModesManager] Error handling .roomodes file deletion:`, error) - } - }), - ) - this.disposables.push(roomodesWatcher) - } - } - - public async getCustomModes(): Promise { - // Check if we have a valid cached result. - const now = Date.now() - - if (this.cachedModes && now - this.cachedAt < CustomModesManager.cacheTTL) { - return this.cachedModes - } - - // Get modes from settings file. - const settingsPath = await this.getCustomModesFilePath() - const settingsModes = await this.loadModesFromFile(settingsPath) - - // Get modes from .roomodes if it exists. - const roomodesPath = await this.getWorkspaceRoomodes() - const roomodesModes = roomodesPath ? await this.loadModesFromFile(roomodesPath) : [] - - // Create maps to store modes by source. - const projectModes = new Map() - const globalModes = new Map() - - // Add project modes (they take precedence). - for (const mode of roomodesModes) { - projectModes.set(mode.slug, { ...mode, source: "project" as const }) - } - - // Add global modes. - for (const mode of settingsModes) { - if (!projectModes.has(mode.slug)) { - globalModes.set(mode.slug, { ...mode, source: "global" as const }) - } - } - - // Combine modes in the correct order: project modes first, then global modes. - const mergedModes = [ - ...roomodesModes.map((mode) => ({ ...mode, source: "project" as const })), - ...settingsModes - .filter((mode) => !projectModes.has(mode.slug)) - .map((mode) => ({ ...mode, source: "global" as const })), - ] - - await this.context.globalState.update("customModes", mergedModes) - - this.cachedModes = mergedModes - this.cachedAt = now - - return mergedModes - } - - public async updateCustomMode(slug: string, config: ModeConfig): Promise { - try { - // Validate the mode configuration before saving - const validationResult = modeConfigSchema.safeParse(config) - if (!validationResult.success) { - const errors = validationResult.error.errors.map((e) => e.message).join(", ") - logger.error(`Invalid mode configuration for ${slug}`, { errors: validationResult.error.errors }) - throw new Error(`Invalid mode configuration: ${errors}`) - } - - const isProjectMode = config.source === "project" - let targetPath: string - - if (isProjectMode) { - const workspaceFolders = vscode.workspace.workspaceFolders - - if (!workspaceFolders || workspaceFolders.length === 0) { - logger.error("Failed to update project mode: No workspace folder found", { slug }) - throw new Error(t("common:customModes.errors.noWorkspaceForProject")) - } - - const workspaceRoot = getWorkspacePath() - targetPath = path.join(workspaceRoot, ROOMODES_FILENAME) - const exists = await fileExistsAtPath(targetPath) - - logger.info(`${exists ? "Updating" : "Creating"} project mode in ${ROOMODES_FILENAME}`, { - slug, - workspace: workspaceRoot, - }) - } else { - targetPath = await this.getCustomModesFilePath() - } - - await this.queueWrite(async () => { - // Ensure source is set correctly based on target file. - const modeWithSource = { - ...config, - source: isProjectMode ? ("project" as const) : ("global" as const), - } - - await this.updateModesInFile(targetPath, (modes) => { - const updatedModes = modes.filter((m) => m.slug !== slug) - updatedModes.push(modeWithSource) - return updatedModes - }) - - this.clearCache() - await this.refreshMergedState() - }) - } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error) - logger.error("Failed to update custom mode", { slug, error: errorMessage }) - vscode.window.showErrorMessage(t("common:customModes.errors.updateFailed", { error: errorMessage })) - } - } - - private async updateModesInFile(filePath: string, operation: (modes: ModeConfig[]) => ModeConfig[]): Promise { - let content = "{}" - - try { - content = await fs.readFile(filePath, "utf-8") - } catch (error) { - // File might not exist yet. - content = yaml.stringify({ customModes: [] }, { lineWidth: 0 }) - } - - let settings - - try { - settings = this.parseYamlSafely(content, filePath) - } catch (error) { - // Error already logged in parseYamlSafely - settings = { customModes: [] } - } - - // Ensure settings is an object and has customModes property - if (!settings || typeof settings !== "object") { - settings = { customModes: [] } - } - if (!settings.customModes) { - settings.customModes = [] - } - - settings.customModes = operation(settings.customModes) - await fs.writeFile(filePath, yaml.stringify(settings, { lineWidth: 0 }), "utf-8") - } - - private async refreshMergedState(): Promise { - const settingsPath = await this.getCustomModesFilePath() - const roomodesPath = await this.getWorkspaceRoomodes() - - const settingsModes = await this.loadModesFromFile(settingsPath) - const roomodesModes = roomodesPath ? await this.loadModesFromFile(roomodesPath) : [] - const mergedModes = await this.mergeCustomModes(roomodesModes, settingsModes) - - await this.context.globalState.update("customModes", mergedModes) - - this.clearCache() - - await this.onUpdate() - } - - public async deleteCustomMode(slug: string, fromMarketplace = false): Promise { - try { - const settingsPath = await this.getCustomModesFilePath() - const roomodesPath = await this.getWorkspaceRoomodes() - - const settingsModes = await this.loadModesFromFile(settingsPath) - const roomodesModes = roomodesPath ? await this.loadModesFromFile(roomodesPath) : [] - - // Find the mode in either file - const projectMode = roomodesModes.find((m) => m.slug === slug) - const globalMode = settingsModes.find((m) => m.slug === slug) - - if (!projectMode && !globalMode) { - throw new Error(t("common:customModes.errors.modeNotFound")) - } - - // Determine which mode to use for rules folder path calculation - const modeToDelete = projectMode || globalMode - - await this.queueWrite(async () => { - // Delete from project first if it exists there - if (projectMode && roomodesPath) { - await this.updateModesInFile(roomodesPath, (modes) => modes.filter((m) => m.slug !== slug)) - } - - // Delete from global settings if it exists there - if (globalMode) { - await this.updateModesInFile(settingsPath, (modes) => modes.filter((m) => m.slug !== slug)) - } - - // Delete associated rules folder - if (modeToDelete) { - await this.deleteRulesFolder(slug, modeToDelete, fromMarketplace) - } - - // Clear cache when modes are deleted - this.clearCache() - await this.refreshMergedState() - }) - } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error) - vscode.window.showErrorMessage(t("common:customModes.errors.deleteFailed", { error: errorMessage })) - } - } - - /** - * Deletes the rules folder for a specific mode - * @param slug - The mode slug - * @param mode - The mode configuration to determine the scope - */ - private async deleteRulesFolder(slug: string, mode: ModeConfig, fromMarketplace = false): Promise { - try { - // Determine the scope based on source (project or global) - const scope = mode.source || "global" - - // Determine the rules folder path - let rulesFolderPath: string - if (scope === "project") { - const workspacePath = getWorkspacePath() - if (workspacePath) { - rulesFolderPath = path.join(workspacePath, ".roo", `rules-${slug}`) - } else { - return // No workspace, can't delete project rules - } - } else { - // Global scope - use OS home directory - const homeDir = os.homedir() - rulesFolderPath = path.join(homeDir, ".roo", `rules-${slug}`) - } - - // Check if the rules folder exists and delete it - const rulesFolderExists = await fileExistsAtPath(rulesFolderPath) - if (rulesFolderExists) { - try { - await fs.rm(rulesFolderPath, { recursive: true, force: true }) - logger.info(`Deleted rules folder for mode ${slug}: ${rulesFolderPath}`) - } catch (error) { - logger.error(`Failed to delete rules folder for mode ${slug}: ${error}`) - // Notify the user about the failure - const messageKey = fromMarketplace - ? "common:marketplace.mode.rulesCleanupFailed" - : "common:customModes.errors.rulesCleanupFailed" - vscode.window.showWarningMessage(t(messageKey, { rulesFolderPath })) - // Continue even if folder deletion fails - } - } - } catch (error) { - logger.error(`Error deleting rules folder for mode ${slug}`, { - error: error instanceof Error ? error.message : String(error), - }) - } - } - - public async resetCustomModes(): Promise { - try { - const filePath = await this.getCustomModesFilePath() - await fs.writeFile(filePath, yaml.stringify({ customModes: [] }, { lineWidth: 0 })) - await this.context.globalState.update("customModes", []) - this.clearCache() - await this.onUpdate() - } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error) - vscode.window.showErrorMessage(t("common:customModes.errors.resetFailed", { error: errorMessage })) - } - } - - /** - * Checks if a mode has associated rules files in the .roo/rules-{slug}/ directory - * @param slug - The mode identifier to check - * @returns True if the mode has rules files with content, false otherwise - */ - public async checkRulesDirectoryHasContent(slug: string): Promise { - try { - // First, find the mode to determine its source - const allModes = await this.getCustomModes() - const mode = allModes.find((m) => m.slug === slug) - - if (!mode) { - // If not in custom modes, check if it's in .roomodes (project-specific) - const workspacePath = getWorkspacePath() - if (!workspacePath) { - return false - } - - const roomodesPath = path.join(workspacePath, ROOMODES_FILENAME) - try { - const roomodesExists = await fileExistsAtPath(roomodesPath) - if (roomodesExists) { - const roomodesContent = await fs.readFile(roomodesPath, "utf-8") - const roomodesData = yaml.parse(roomodesContent) - const roomodesModes = roomodesData?.customModes || [] - - // Check if this specific mode exists in .roomodes - const modeInRoomodes = roomodesModes.find((m: any) => m.slug === slug) - if (!modeInRoomodes) { - return false // Mode not found anywhere - } - } else { - return false // No .roomodes file and not in custom modes - } - } catch (error) { - return false // Cannot read .roomodes and not in custom modes - } - } - - // Determine the correct rules directory based on mode source - let modeRulesDir: string - const isGlobalMode = mode?.source === "global" - - if (isGlobalMode) { - // For global modes, check in global .roo directory - const globalRooDir = getGlobalRooDirectory() - modeRulesDir = path.join(globalRooDir, `rules-${slug}`) - } else { - // For project modes, check in workspace .roo directory - const workspacePath = getWorkspacePath() - if (!workspacePath) { - return false - } - modeRulesDir = path.join(workspacePath, ".roo", `rules-${slug}`) - } - - try { - const stats = await fs.stat(modeRulesDir) - if (!stats.isDirectory()) { - return false - } - } catch (error) { - return false - } - - // Check if directory has any content files - try { - const entries = await fs.readdir(modeRulesDir, { withFileTypes: true }) - - for (const entry of entries) { - if (entry.isFile()) { - // Use path.join with modeRulesDir and entry.name for compatibility - const filePath = path.join(modeRulesDir, entry.name) - const content = await fs.readFile(filePath, "utf-8") - if (content.trim()) { - return true // Found at least one file with content - } - } - } - - return false // No files with content found - } catch (error) { - return false - } - } catch (error) { - logger.error("Failed to check rules directory for mode", { - slug, - error: error instanceof Error ? error.message : String(error), - }) - return false - } - } - - /** - * Exports a mode configuration with its associated rules files into a shareable YAML format - * @param slug - The mode identifier to export - * @param customPrompts - Optional custom prompts to merge into the export - * @returns Success status with YAML content or error message - */ - public async exportModeWithRules(slug: string, customPrompts?: PromptComponent): Promise { - try { - // Import modes from shared to check built-in modes - const { modes: builtInModes } = await import("../../shared/modes") - - // Get all current modes - const allModes = await this.getCustomModes() - let mode = allModes.find((m) => m.slug === slug) - - // If mode not found in custom modes, check if it's a built-in mode that has been customized - if (!mode) { - // Only check workspace-based modes if workspace is available - const workspacePath = getWorkspacePath() - if (workspacePath) { - const roomodesPath = path.join(workspacePath, ROOMODES_FILENAME) - try { - const roomodesExists = await fileExistsAtPath(roomodesPath) - if (roomodesExists) { - const roomodesContent = await fs.readFile(roomodesPath, "utf-8") - const roomodesData = yaml.parse(roomodesContent) - const roomodesModes = roomodesData?.customModes || [] - - // Find the mode in .roomodes - mode = roomodesModes.find((m: any) => m.slug === slug) - } - } catch (error) { - // Continue to check built-in modes - } - } - - // If still not found, check if it's a built-in mode - if (!mode) { - const builtInMode = builtInModes.find((m) => m.slug === slug) - if (builtInMode) { - // Use the built-in mode as the base - mode = { ...builtInMode } - } else { - return { success: false, error: "Mode not found" } - } - } - } - - // Determine the base directory based on mode source - const isGlobalMode = mode.source === "global" - let baseDir: string - if (isGlobalMode) { - // For global modes, use the global .roo directory - baseDir = getGlobalRooDirectory() - } else { - // For project modes, use the workspace directory - const workspacePath = getWorkspacePath() - if (!workspacePath) { - return { success: false, error: "No workspace found" } - } - baseDir = workspacePath - } - - // Check for .roo/rules-{slug}/ directory (or rules-{slug}/ for global) - const modeRulesDir = isGlobalMode - ? path.join(baseDir, `rules-${slug}`) - : path.join(baseDir, ".roo", `rules-${slug}`) - - let rulesFiles: RuleFile[] = [] - try { - const stats = await fs.stat(modeRulesDir) - if (stats.isDirectory()) { - // Extract content specific to this mode by looking for the mode-specific rules - const entries = await fs.readdir(modeRulesDir, { withFileTypes: true }) - - for (const entry of entries) { - if (entry.isFile()) { - // Use path.join with modeRulesDir and entry.name for compatibility - const filePath = path.join(modeRulesDir, entry.name) - const content = await fs.readFile(filePath, "utf-8") - if (content.trim()) { - // Calculate relative path based on mode source - const relativePath = isGlobalMode - ? path.relative(baseDir, filePath) - : path.relative(path.join(baseDir, ".roo"), filePath) - rulesFiles.push({ relativePath, content: content.trim() }) - } - } - } - } - } catch (error) { - // Directory doesn't exist, which is fine - mode might not have rules - } - - // Create an export mode with rules files preserved - const exportMode: ExportedModeConfig = { - ...mode, - // Remove source property for export - source: "project" as const, - } - - // Merge custom prompts if provided - if (customPrompts) { - if (customPrompts.roleDefinition) exportMode.roleDefinition = customPrompts.roleDefinition - if (customPrompts.description) exportMode.description = customPrompts.description - if (customPrompts.whenToUse) exportMode.whenToUse = customPrompts.whenToUse - if (customPrompts.customInstructions) exportMode.customInstructions = customPrompts.customInstructions - } - - // Add rules files if any exist - if (rulesFiles.length > 0) { - exportMode.rulesFiles = rulesFiles - } - - // Generate YAML - const exportData = { - customModes: [exportMode], - } - - const yamlContent = yaml.stringify(exportData) - - return { success: true, yaml: yamlContent } - } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error) - logger.error("Failed to export mode with rules", { slug, error: errorMessage }) - return { success: false, error: errorMessage } - } - } - - /** - * Helper method to import rules files for a mode - * @param importMode - The mode being imported - * @param rulesFiles - The rules files to import - * @param source - The import source ("global" or "project") - */ - private async importRulesFiles( - importMode: ExportedModeConfig, - rulesFiles: RuleFile[], - source: "global" | "project", - ): Promise { - // Determine base directory and rules folder path based on source - let baseDir: string - let rulesFolderPath: string - - if (source === "global") { - baseDir = getGlobalRooDirectory() - rulesFolderPath = path.join(baseDir, `rules-${importMode.slug}`) - } else { - const workspacePath = getWorkspacePath() - baseDir = path.join(workspacePath, ".roo") - rulesFolderPath = path.join(baseDir, `rules-${importMode.slug}`) - } - - // Always remove the existing rules folder for this mode if it exists - // This ensures that if the imported mode has no rules, the folder is cleaned up - try { - await fs.rm(rulesFolderPath, { recursive: true, force: true }) - logger.info(`Removed existing ${source} rules folder for mode ${importMode.slug}`) - } catch (error) { - // It's okay if the folder doesn't exist - logger.debug(`No existing ${source} rules folder to remove for mode ${importMode.slug}`) - } - - // Only proceed with file creation if there are rules files to import - if (!rulesFiles || !Array.isArray(rulesFiles) || rulesFiles.length === 0) { - return - } - - // Import the new rules files with path validation - for (const ruleFile of rulesFiles) { - if (ruleFile.relativePath && ruleFile.content) { - // Validate the relative path to prevent path traversal attacks - const normalizedRelativePath = path.normalize(ruleFile.relativePath) - - // Ensure the path doesn't contain traversal sequences - if (normalizedRelativePath.includes("..") || path.isAbsolute(normalizedRelativePath)) { - logger.error(`Invalid file path detected: ${ruleFile.relativePath}`) - continue // Skip this file but continue with others - } - - const targetPath = path.join(baseDir, normalizedRelativePath) - const normalizedTargetPath = path.normalize(targetPath) - const expectedBasePath = path.normalize(baseDir) - - // Ensure the resolved path stays within the base directory - if (!normalizedTargetPath.startsWith(expectedBasePath)) { - logger.error(`Path traversal attempt detected: ${ruleFile.relativePath}`) - continue // Skip this file but continue with others - } - - // Ensure directory exists - const targetDir = path.dirname(targetPath) - await fs.mkdir(targetDir, { recursive: true }) - - // Write the file - await fs.writeFile(targetPath, ruleFile.content, "utf-8") - } - } - } - - /** - * Imports modes from YAML content, including their associated rules files - * @param yamlContent - The YAML content containing mode configurations - * @param source - Target level for import: "global" (all projects) or "project" (current workspace only) - * @returns Success status with optional error message - */ - public async importModeWithRules( - yamlContent: string, - source: "global" | "project" = "project", - ): Promise { - try { - // Parse the YAML content with proper type validation - let importData: ImportData - try { - const parsed = yaml.parse(yamlContent) - - // Validate the structure - if (!parsed?.customModes || !Array.isArray(parsed.customModes) || parsed.customModes.length === 0) { - return { success: false, error: "Invalid import format: Expected 'customModes' array in YAML" } - } - - importData = parsed as ImportData - } catch (parseError) { - return { - success: false, - error: `Invalid YAML format: ${parseError instanceof Error ? parseError.message : "Failed to parse YAML"}`, - } - } - - // Check workspace availability early if importing at project level - if (source === "project") { - const workspacePath = getWorkspacePath() - if (!workspacePath) { - return { success: false, error: "No workspace found" } - } - } - - // Process each mode in the import - for (const importMode of importData.customModes) { - const { rulesFiles, ...modeConfig } = importMode - - // Validate the mode configuration - const validationResult = modeConfigSchema.safeParse(modeConfig) - if (!validationResult.success) { - logger.error(`Invalid mode configuration for ${modeConfig.slug}`, { - errors: validationResult.error.errors, - }) - return { - success: false, - error: `Invalid mode configuration for ${modeConfig.slug}: ${validationResult.error.errors.map((e) => e.message).join(", ")}`, - } - } - - // Check for existing mode conflicts - const existingModes = await this.getCustomModes() - const existingMode = existingModes.find((m) => m.slug === importMode.slug) - if (existingMode) { - logger.info(`Overwriting existing mode: ${importMode.slug}`) - } - - // Import the mode configuration with the specified source - await this.updateCustomMode(importMode.slug, { - ...modeConfig, - source: source, // Use the provided source parameter - }) - - // Import rules files (this also handles cleanup of existing rules folders) - await this.importRulesFiles(importMode, rulesFiles || [], source) - } - - // Refresh the modes after import - await this.refreshMergedState() - - return { success: true } - } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error) - logger.error("Failed to import mode with rules", { error: errorMessage }) - return { success: false, error: errorMessage } - } - } - - private clearCache(): void { - this.cachedModes = null - this.cachedAt = 0 - } - - dispose(): void { - for (const disposable of this.disposables) { - disposable.dispose() - } - - this.disposables = [] - } -} +// Backward compatibility export +export { CustomAgentsManager as CustomModesManager } from "./CustomAgentsManager" +export * from "./CustomAgentsManager" diff --git a/src/core/config/ProviderSettingsManager.ts b/src/core/config/ProviderSettingsManager.ts index 350c8136f2..3749797b19 100644 --- a/src/core/config/ProviderSettingsManager.ts +++ b/src/core/config/ProviderSettingsManager.ts @@ -9,7 +9,7 @@ import { } from "@roo-code/types" import { TelemetryService } from "@roo-code/telemetry" -import { Mode, modes } from "../../shared/modes" +import { Agent, agents } from "../../shared/agents" const providerSettingsWithIdSchema = providerSettingsSchema.extend({ id: z.string().optional() }) const discriminatedProviderSettingsWithIdSchema = providerSettingsSchemaDiscriminated.and( @@ -21,7 +21,8 @@ type ProviderSettingsWithId = z.infer export const providerProfilesSchema = z.object({ currentApiConfigName: z.string(), apiConfigs: z.record(z.string(), providerSettingsWithIdSchema), - modeApiConfigs: z.record(z.string(), z.string()).optional(), + agentApiConfigs: z.record(z.string(), z.string()).optional(), + modeApiConfigs: z.record(z.string(), z.string()).optional(), // Keep for backward compatibility during migration migrations: z .object({ rateLimitSecondsMigrated: z.boolean().optional(), @@ -39,14 +40,14 @@ export class ProviderSettingsManager { private static readonly SCOPE_PREFIX = "roo_cline_config_" private readonly defaultConfigId = this.generateId() - private readonly defaultModeApiConfigs: Record = Object.fromEntries( - modes.map((mode) => [mode.slug, this.defaultConfigId]), + private readonly defaultAgentApiConfigs: Record = Object.fromEntries( + agents.map((agent) => [agent.slug, this.defaultConfigId]), ) private readonly defaultProviderProfiles: ProviderProfiles = { currentApiConfigName: "default", apiConfigs: { default: { id: this.defaultConfigId } }, - modeApiConfigs: this.defaultModeApiConfigs, + agentApiConfigs: this.defaultAgentApiConfigs, migrations: { rateLimitSecondsMigrated: true, // Mark as migrated on fresh installs diffSettingsMigrated: true, // Mark as migrated on fresh installs @@ -92,15 +93,20 @@ export class ProviderSettingsManager { let isDirty = false - // Migrate existing installs to have per-mode API config map - if (!providerProfiles.modeApiConfigs) { - // Use the currently selected config for all modes initially + // Migrate existing installs to have per-agent API config map + if (!providerProfiles.agentApiConfigs && !providerProfiles.modeApiConfigs) { + // Use the currently selected config for all agents initially const currentName = providerProfiles.currentApiConfigName const seedId = providerProfiles.apiConfigs[currentName]?.id ?? Object.values(providerProfiles.apiConfigs)[0]?.id ?? this.defaultConfigId - providerProfiles.modeApiConfigs = Object.fromEntries(modes.map((m) => [m.slug, seedId])) + providerProfiles.agentApiConfigs = Object.fromEntries(agents.map((a) => [a.slug, seedId])) + isDirty = true + } else if (providerProfiles.modeApiConfigs && !providerProfiles.agentApiConfigs) { + // Migrate from modeApiConfigs to agentApiConfigs + providerProfiles.agentApiConfigs = providerProfiles.modeApiConfigs + delete providerProfiles.modeApiConfigs isDirty = true } @@ -412,39 +418,48 @@ export class ProviderSettingsManager { } /** - * Set the API config for a specific mode. + * Set the API config for a specific agent. */ - public async setModeConfig(mode: Mode, configId: string) { + public async setAgentConfig(agent: Agent, configId: string) { try { return await this.lock(async () => { const providerProfiles = await this.load() - // Ensure the per-mode config map exists - if (!providerProfiles.modeApiConfigs) { - providerProfiles.modeApiConfigs = {} + // Ensure the per-agent config map exists + if (!providerProfiles.agentApiConfigs) { + providerProfiles.agentApiConfigs = {} } - // Assign the chosen config ID to this mode - providerProfiles.modeApiConfigs[mode] = configId + // Assign the chosen config ID to this agent + providerProfiles.agentApiConfigs[agent] = configId await this.store(providerProfiles) }) } catch (error) { - throw new Error(`Failed to set mode config: ${error}`) + throw new Error(`Failed to set agent config: ${error}`) } } /** - * Get the API config ID for a specific mode. + * Get the API config ID for a specific agent. */ - public async getModeConfigId(mode: Mode) { + public async getAgentConfigId(agent: Agent) { try { return await this.lock(async () => { - const { modeApiConfigs } = await this.load() - return modeApiConfigs?.[mode] + const { agentApiConfigs } = await this.load() + return agentApiConfigs?.[agent] }) } catch (error) { - throw new Error(`Failed to get mode config: ${error}`) + throw new Error(`Failed to get agent config: ${error}`) } } + // Backward compatibility aliases + public async setModeConfig(mode: Agent, configId: string) { + return this.setAgentConfig(mode, configId) + } + + public async getModeConfigId(mode: Agent) { + return this.getAgentConfigId(mode) + } + public async export() { try { return await this.lock(async () => { diff --git a/src/core/config/__tests__/ProviderSettingsManager.spec.ts b/src/core/config/__tests__/ProviderSettingsManager.spec.ts index e52c1974b6..56c239984a 100644 --- a/src/core/config/__tests__/ProviderSettingsManager.spec.ts +++ b/src/core/config/__tests__/ProviderSettingsManager.spec.ts @@ -61,7 +61,7 @@ describe("ProviderSettingsManager", () => { fuzzyMatchThreshold: 1.0, }, }, - modeApiConfigs: {}, + agentApiConfigs: {}, migrations: { rateLimitSecondsMigrated: true, diffSettingsMigrated: true, diff --git a/src/core/config/importExport.ts b/src/core/config/importExport.ts index c3d6f9c215..5eb856d892 100644 --- a/src/core/config/importExport.ts +++ b/src/core/config/importExport.ts @@ -11,13 +11,13 @@ import { TelemetryService } from "@roo-code/telemetry" import { ProviderSettingsManager, providerProfilesSchema } from "./ProviderSettingsManager" import { ContextProxy } from "./ContextProxy" -import { CustomModesManager } from "./CustomModesManager" +import { CustomAgentsManager, CustomModesManager } from "./CustomModesManager" import { t } from "../../i18n" export type ImportOptions = { providerSettingsManager: ProviderSettingsManager contextProxy: ContextProxy - customModesManager: CustomModesManager + customModesManager: CustomAgentsManager | CustomModesManager } type ExportOptions = { diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index fe8fd0f68f..c21c89c744 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -41,7 +41,7 @@ import { t } from "../../i18n" import { ClineApiReqCancelReason, ClineApiReqInfo } from "../../shared/ExtensionMessage" import { getApiMetrics } from "../../shared/getApiMetrics" import { ClineAskResponse } from "../../shared/WebviewMessage" -import { defaultModeSlug } from "../../shared/modes" +import { defaultAgentSlug } from "../../shared/modes" import { DiffStrategy } from "../../shared/tools" import { EXPERIMENT_IDS, experiments } from "../../shared/experiments" import { getModelMaxOutputTokens } from "../../shared/api" @@ -99,7 +99,7 @@ const MAX_EXPONENTIAL_BACKOFF_SECONDS = 600 // 10 minutes export type ClineEvents = { message: [{ action: "created" | "updated"; message: ClineMessage }] taskStarted: [] - taskModeSwitched: [taskId: string, mode: string] + taskAgentSwitched: [taskId: string, agent: string] taskPaused: [] taskUnpaused: [] taskAskResponded: [] @@ -145,7 +145,7 @@ export class Task extends EventEmitter { abandoned = false isInitialized = false isPaused: boolean = false - pausedModeSlug: string = defaultModeSlug + pausedAgentSlug: string = defaultAgentSlug private pauseInterval: NodeJS.Timeout | undefined // API @@ -1193,17 +1193,17 @@ export class Task extends EventEmitter { provider.log(`[subtasks] paused ${this.taskId}.${this.instanceId}`) await this.waitForResume() provider.log(`[subtasks] resumed ${this.taskId}.${this.instanceId}`) - const currentMode = (await provider.getState())?.mode ?? defaultModeSlug + const currentAgent = (await provider.getState())?.mode ?? defaultAgentSlug - if (currentMode !== this.pausedModeSlug) { - // The mode has changed, we need to switch back to the paused mode. - await provider.handleModeSwitch(this.pausedModeSlug) + if (currentAgent !== this.pausedAgentSlug) { + // The agent has changed, we need to switch back to the paused agent. + await provider.handleModeSwitch(this.pausedAgentSlug) - // Delay to allow mode change to take effect before next tool is executed. + // Delay to allow agent change to take effect before next tool is executed. await delay(500) provider.log( - `[subtasks] task ${this.taskId}.${this.instanceId} has switched back to '${this.pausedModeSlug}' from '${currentMode}'`, + `[subtasks] task ${this.taskId}.${this.instanceId} has switched back to '${this.pausedAgentSlug}' from '${currentAgent}'`, ) } } @@ -1626,8 +1626,8 @@ export class Task extends EventEmitter { const { browserViewportSize, mode, - customModes, - customModePrompts, + customModes: customAgents, + customModePrompts: customAgentPrompts, customInstructions, experiments, enableMcpServerCreation, @@ -1653,8 +1653,8 @@ export class Task extends EventEmitter { this.diffStrategy, browserViewportSize, mode, - customModePrompts, - customModes, + customAgentPrompts, + customAgents, customInstructions, this.diffEnabled, experiments, diff --git a/src/core/tools/newTaskTool.ts b/src/core/tools/newTaskTool.ts index 7cc7063b49..00fff80e2d 100644 --- a/src/core/tools/newTaskTool.ts +++ b/src/core/tools/newTaskTool.ts @@ -2,7 +2,7 @@ import delay from "delay" import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools" import { Task } from "../task/Task" -import { defaultModeSlug, getModeBySlug } from "../../shared/modes" +import { defaultAgentSlug, getAgentBySlug } from "../../shared/agents" import { formatResponse } from "../prompts/responses" import { t } from "../../i18n" @@ -14,21 +14,21 @@ export async function newTaskTool( pushToolResult: PushToolResult, removeClosingTag: RemoveClosingTag, ) { - const mode: string | undefined = block.params.mode + const agent: string | undefined = block.params.mode const message: string | undefined = block.params.message try { if (block.partial) { const partialMessage = JSON.stringify({ tool: "newTask", - mode: removeClosingTag("mode", mode), + mode: removeClosingTag("mode", agent), content: removeClosingTag("message", message), }) await cline.ask("tool", partialMessage, block.partial).catch(() => {}) return } else { - if (!mode) { + if (!agent) { cline.consecutiveMistakeCount++ cline.recordToolError("new_task") pushToolResult(await cline.sayAndCreateMissingParamError("new_task", "mode")) @@ -47,17 +47,17 @@ export async function newTaskTool( // Un-escape one level: \\@ -> \@ (removes one backslash for hierarchical subtasks) const unescapedMessage = message.replace(/\\\\@/g, "\\@") - // Verify the mode exists - const targetMode = getModeBySlug(mode, (await cline.providerRef.deref()?.getState())?.customModes) + // Verify the agent exists + const targetAgent = getAgentBySlug(agent, (await cline.providerRef.deref()?.getState())?.customModes) - if (!targetMode) { - pushToolResult(formatResponse.toolError(`Invalid mode: ${mode}`)) + if (!targetAgent) { + pushToolResult(formatResponse.toolError(`Invalid agent: ${agent}`)) return } const toolMessage = JSON.stringify({ tool: "newTask", - mode: targetMode.name, + mode: targetAgent.name, content: message, }) @@ -77,13 +77,13 @@ export async function newTaskTool( cline.checkpointSave(true) } - // Preserve the current mode so we can resume with it later. - cline.pausedModeSlug = (await provider.getState()).mode ?? defaultModeSlug + // Preserve the current agent so we can resume with it later. + cline.pausedAgentSlug = (await provider.getState()).mode ?? defaultAgentSlug - // Switch mode first, then create new task instance. - await provider.handleModeSwitch(mode) + // Switch agent first, then create new task instance. + await provider.handleModeSwitch(agent) - // Delay to allow mode change to take effect before next tool is executed. + // Delay to allow agent change to take effect before next tool is executed. await delay(500) const newCline = await provider.initClineWithTask(unescapedMessage, undefined, cline) @@ -93,7 +93,9 @@ export async function newTaskTool( } cline.emit("taskSpawned", newCline.taskId) - pushToolResult(`Successfully created new task in ${targetMode.name} mode with message: ${unescapedMessage}`) + pushToolResult( + `Successfully created new task in ${targetAgent.name} agent with message: ${unescapedMessage}`, + ) // Set the isPaused flag to true so the parent // task can wait for the sub-task to finish. diff --git a/src/core/tools/switchModeTool.ts b/src/core/tools/switchModeTool.ts index 8ce906b41f..b8573bf3bb 100644 --- a/src/core/tools/switchModeTool.ts +++ b/src/core/tools/switchModeTool.ts @@ -3,7 +3,7 @@ import delay from "delay" import { Task } from "../task/Task" import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools" import { formatResponse } from "../prompts/responses" -import { defaultModeSlug, getModeBySlug } from "../../shared/modes" +import { defaultAgentSlug, getAgentBySlug } from "../../shared/agents" export async function switchModeTool( cline: Task, @@ -13,21 +13,21 @@ export async function switchModeTool( pushToolResult: PushToolResult, removeClosingTag: RemoveClosingTag, ) { - const mode_slug: string | undefined = block.params.mode_slug + const agent_slug: string | undefined = block.params.mode_slug const reason: string | undefined = block.params.reason try { if (block.partial) { const partialMessage = JSON.stringify({ tool: "switchMode", - mode: removeClosingTag("mode_slug", mode_slug), + mode: removeClosingTag("mode_slug", agent_slug), reason: removeClosingTag("reason", reason), }) await cline.ask("tool", partialMessage, block.partial).catch(() => {}) return } else { - if (!mode_slug) { + if (!agent_slug) { cline.consecutiveMistakeCount++ cline.recordToolError("switch_mode") pushToolResult(await cline.sayAndCreateMissingParamError("switch_mode", "mode_slug")) @@ -36,41 +36,41 @@ export async function switchModeTool( cline.consecutiveMistakeCount = 0 - // Verify the mode exists - const targetMode = getModeBySlug(mode_slug, (await cline.providerRef.deref()?.getState())?.customModes) + // Verify the agent exists + const targetAgent = getAgentBySlug(agent_slug, (await cline.providerRef.deref()?.getState())?.customModes) - if (!targetMode) { + if (!targetAgent) { cline.recordToolError("switch_mode") - pushToolResult(formatResponse.toolError(`Invalid mode: ${mode_slug}`)) + pushToolResult(formatResponse.toolError(`Invalid agent: ${agent_slug}`)) return } - // Check if already in requested mode - const currentMode = (await cline.providerRef.deref()?.getState())?.mode ?? defaultModeSlug + // Check if already in requested agent + const currentAgent = (await cline.providerRef.deref()?.getState())?.mode ?? defaultAgentSlug - if (currentMode === mode_slug) { + if (currentAgent === agent_slug) { cline.recordToolError("switch_mode") - pushToolResult(`Already in ${targetMode.name} mode.`) + pushToolResult(`Already in ${targetAgent.name} agent.`) return } - const completeMessage = JSON.stringify({ tool: "switchMode", mode: mode_slug, reason }) + const completeMessage = JSON.stringify({ tool: "switchMode", mode: agent_slug, reason }) const didApprove = await askApproval("tool", completeMessage) if (!didApprove) { return } - // Switch the mode using shared handler - await cline.providerRef.deref()?.handleModeSwitch(mode_slug) + // Switch the agent using shared handler + await cline.providerRef.deref()?.handleModeSwitch(agent_slug) pushToolResult( - `Successfully switched from ${getModeBySlug(currentMode)?.name ?? currentMode} mode to ${ - targetMode.name - } mode${reason ? ` because: ${reason}` : ""}.`, + `Successfully switched from ${getAgentBySlug(currentAgent)?.name ?? currentAgent} agent to ${ + targetAgent.name + } agent${reason ? ` because: ${reason}` : ""}.`, ) - await delay(500) // Delay to allow mode change to take effect before next tool is executed + await delay(500) // Delay to allow agent change to take effect before next tool is executed return } diff --git a/src/core/tools/validateToolUse.ts b/src/core/tools/validateToolUse.ts index f0ce9e16e6..d133976052 100644 --- a/src/core/tools/validateToolUse.ts +++ b/src/core/tools/validateToolUse.ts @@ -1,15 +1,15 @@ import type { ToolName, ModeConfig } from "@roo-code/types" -import { Mode, isToolAllowedForMode } from "../../shared/modes" +import { Agent, isToolAllowedForAgent } from "../../shared/agents" export function validateToolUse( toolName: ToolName, - mode: Mode, - customModes?: ModeConfig[], + agent: Agent, + customAgents?: ModeConfig[], toolRequirements?: Record, toolParams?: Record, ): void { - if (!isToolAllowedForMode(toolName, mode, customModes ?? [], toolRequirements, toolParams)) { - throw new Error(`Tool "${toolName}" is not allowed in ${mode} mode.`) + if (!isToolAllowedForAgent(toolName, agent, customAgents ?? [], toolRequirements, toolParams)) { + throw new Error(`Tool "${toolName}" is not allowed in ${agent} mode.`) } } diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 6bcb85e337..a918d02546 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -806,7 +806,7 @@ export class ClineProvider if (cline) { TelemetryService.instance.captureModeSwitch(cline.taskId, newMode) - cline.emit("taskModeSwitched", cline.taskId, newMode) + cline.emit("taskAgentSwitched", cline.taskId, newMode) } await this.updateGlobalState("mode", newMode) diff --git a/src/core/webview/generateSystemPrompt.ts b/src/core/webview/generateSystemPrompt.ts index b8e87a1d4a..a4dc3f1f01 100644 --- a/src/core/webview/generateSystemPrompt.ts +++ b/src/core/webview/generateSystemPrompt.ts @@ -1,6 +1,6 @@ import * as vscode from "vscode" import { WebviewMessage } from "../../shared/WebviewMessage" -import { defaultModeSlug, getModeBySlug, getGroupName } from "../../shared/modes" +import { defaultAgentSlug, getAgentBySlug, getGroupName } from "../../shared/agents" import { buildApiHandler } from "../../api" import { experiments as experimentsModule, EXPERIMENT_IDS } from "../../shared/experiments" @@ -39,7 +39,7 @@ export const generateSystemPrompt = async (provider: ClineProvider, message: Web const cwd = provider.cwd - const mode = message.mode ?? defaultModeSlug + const agent = message.mode ?? defaultAgentSlug const customModes = await provider.customModesManager.getCustomModes() const rooIgnoreInstructions = provider.getCurrentCline()?.rooIgnoreController?.getInstructions() @@ -57,12 +57,12 @@ export const generateSystemPrompt = async (provider: ClineProvider, message: Web } // Check if the current mode includes the browser tool group - const modeConfig = getModeBySlug(mode, customModes) - const modeSupportsBrowser = modeConfig?.groups.some((group) => getGroupName(group) === "browser") ?? false + const agentConfig = getAgentBySlug(agent, customModes) + const agentSupportsBrowser = agentConfig?.groups.some((group) => getGroupName(group) === "browser") ?? false // Only enable browser tools if the model supports it, the mode includes browser tools, // and browser tools are enabled in settings - const canUseBrowserTool = modelSupportsComputerUse && modeSupportsBrowser && (browserToolEnabled ?? true) + const canUseBrowserTool = modelSupportsComputerUse && agentSupportsBrowser && (browserToolEnabled ?? true) const systemPrompt = await SYSTEM_PROMPT( provider.context, @@ -71,7 +71,7 @@ export const generateSystemPrompt = async (provider: ClineProvider, message: Web mcpEnabled ? provider.getMcpHub() : undefined, diffStrategy, browserViewportSize ?? "900x600", - mode, + agent, customModePrompts, customModes, customInstructions, diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index da73c56920..c740721a04 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -210,9 +210,9 @@ export const webviewMessageHandler = async ( switch (message.type) { case "webviewDidLaunch": - // Load custom modes first - const customModes = await provider.customModesManager.getCustomModes() - await updateGlobalState("customModes", customModes) + // Load custom agents first + const customAgents = await provider.customModesManager.getCustomModes() + await updateGlobalState("customModes", customAgents) provider.postStateToWebview() provider.workspaceTracker?.initializeFilePaths() // Don't await. @@ -772,10 +772,10 @@ export const webviewMessageHandler = async ( break } case "openCustomModesSettings": { - const customModesFilePath = await provider.customModesManager.getCustomModesFilePath() + const customAgentsFilePath = await provider.customModesManager.getCustomModesFilePath() - if (customModesFilePath) { - openFile(customModesFilePath) + if (customAgentsFilePath) { + openFile(customAgentsFilePath) } break @@ -1623,32 +1623,32 @@ export const webviewMessageHandler = async ( break case "updateCustomMode": if (message.modeConfig) { - // Check if this is a new mode or an update to an existing mode - const existingModes = await provider.customModesManager.getCustomModes() - const isNewMode = !existingModes.some((mode) => mode.slug === message.modeConfig?.slug) + // Check if this is a new agent or an update to an existing agent + const existingAgents = await provider.customModesManager.getCustomModes() + const isNewAgent = !existingAgents.some((agent) => agent.slug === message.modeConfig?.slug) await provider.customModesManager.updateCustomMode(message.modeConfig.slug, message.modeConfig) - // Update state after saving the mode - const customModes = await provider.customModesManager.getCustomModes() - await updateGlobalState("customModes", customModes) + // Update state after saving the agent + const customAgents = await provider.customModesManager.getCustomModes() + await updateGlobalState("customModes", customAgents) await updateGlobalState("mode", message.modeConfig.slug) await provider.postStateToWebview() - // Track telemetry for custom mode creation or update + // Track telemetry for custom agent creation or update if (TelemetryService.hasInstance()) { - if (isNewMode) { - // This is a new custom mode + if (isNewAgent) { + // This is a new custom agent TelemetryService.instance.captureCustomModeCreated( message.modeConfig.slug, message.modeConfig.name, ) } else { // Determine which setting was changed by comparing objects - const existingMode = existingModes.find((mode) => mode.slug === message.modeConfig?.slug) - const changedSettings = existingMode + const existingAgent = existingAgents.find((agent) => agent.slug === message.modeConfig?.slug) + const changedSettings = existingAgent ? Object.keys(message.modeConfig).filter( (key) => - JSON.stringify((existingMode as Record)[key]) !== + JSON.stringify((existingAgent as Record)[key]) !== JSON.stringify((message.modeConfig as Record)[key]), ) : [] @@ -1662,16 +1662,16 @@ export const webviewMessageHandler = async ( break case "deleteCustomMode": if (message.slug) { - // Get the mode details to determine source and rules folder path - const customModes = await provider.customModesManager.getCustomModes() - const modeToDelete = customModes.find((mode) => mode.slug === message.slug) + // Get the agent details to determine source and rules folder path + const customAgents = await provider.customModesManager.getCustomModes() + const agentToDelete = customAgents.find((agent) => agent.slug === message.slug) - if (!modeToDelete) { + if (!agentToDelete) { break } // Determine the scope based on source (project or global) - const scope = modeToDelete.source || "global" + const scope = agentToDelete.source || "global" // Determine the rules folder path let rulesFolderPath: string @@ -1701,16 +1701,16 @@ export const webviewMessageHandler = async ( break } - // Delete the mode + // Delete the agent await provider.customModesManager.deleteCustomMode(message.slug) // Delete the rules folder if it exists if (rulesFolderExists) { try { await fs.rm(rulesFolderPath, { recursive: true, force: true }) - provider.log(`Deleted rules folder for mode ${message.slug}: ${rulesFolderPath}`) + provider.log(`Deleted rules folder for agent ${message.slug}: ${rulesFolderPath}`) } catch (error) { - provider.log(`Failed to delete rules folder for mode ${message.slug}: ${error}`) + provider.log(`Failed to delete rules folder for agent ${message.slug}: ${error}`) // Notify the user about the failure vscode.window.showErrorMessage( t("common:errors.delete_rules_folder_failed", { @@ -1718,11 +1718,11 @@ export const webviewMessageHandler = async ( error: error instanceof Error ? error.message : String(error), }), ) - // Continue with mode deletion even if folder deletion fails + // Continue with agent deletion even if folder deletion fails } } - // Switch back to default mode after deletion + // Switch back to default agent after deletion await updateGlobalState("mode", defaultModeSlug) await provider.postStateToWebview() } @@ -1730,11 +1730,11 @@ export const webviewMessageHandler = async ( case "exportMode": if (message.slug) { try { - // Get custom mode prompts to check if built-in mode has been customized - const customModePrompts = getGlobalState("customModePrompts") || {} - const customPrompt = customModePrompts[message.slug] + // Get custom agent prompts to check if built-in agent has been customized + const customAgentPrompts = getGlobalState("customModePrompts") || {} + const customPrompt = customAgentPrompts[message.slug] - // Export the mode with any customizations merged directly + // Export the agent with any customizations merged directly const result = await provider.customModesManager.exportModeWithRules(message.slug, customPrompt) if (result.success && result.yaml) { @@ -1764,7 +1764,7 @@ export const webviewMessageHandler = async ( filters: { "YAML files": ["yaml", "yml"], }, - title: "Save mode export", + title: "Save agent export", }) if (saveUri && result.yaml) { @@ -1803,7 +1803,7 @@ export const webviewMessageHandler = async ( } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error) - provider.log(`Failed to export mode ${message.slug}: ${errorMessage}`) + provider.log(`Failed to export agent ${message.slug}: ${errorMessage}`) // Send error message to webview provider.postMessageToWebview({ @@ -1842,7 +1842,7 @@ export const webviewMessageHandler = async ( filters: { "YAML files": ["yaml", "yml"], }, - title: "Select mode export file to import", + title: "Select agent export file to import", }) if (fileUri && fileUri[0]) { @@ -1852,7 +1852,7 @@ export const webviewMessageHandler = async ( // Read the file content const yamlContent = await fs.readFile(fileUri[0].fsPath, "utf-8") - // Import the mode with the specified source level + // Import the agent with the specified source level const result = await provider.customModesManager.importModeWithRules( yamlContent, message.source || "project", // Default to project if not specified @@ -1860,8 +1860,8 @@ export const webviewMessageHandler = async ( if (result.success) { // Update state after importing - const customModes = await provider.customModesManager.getCustomModes() - await updateGlobalState("customModes", customModes) + const customAgents = await provider.customModesManager.getCustomModes() + await updateGlobalState("customModes", customAgents) await provider.postStateToWebview() // Send success message to webview @@ -1893,7 +1893,7 @@ export const webviewMessageHandler = async ( } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error) - provider.log(`Failed to import mode: ${errorMessage}`) + provider.log(`Failed to import agent: ${errorMessage}`) // Send error message to webview provider.postMessageToWebview({ diff --git a/src/extension/api.ts b/src/extension/api.ts index 7027cb963a..48e1a662f6 100644 --- a/src/extension/api.ts +++ b/src/extension/api.ts @@ -229,7 +229,9 @@ export class API extends EventEmitter implements RooCodeAPI { } }) - cline.on("taskModeSwitched", (taskId, mode) => this.emit(RooCodeEventName.TaskModeSwitched, taskId, mode)) + cline.on("taskAgentSwitched", (taskId, agent) => + this.emit(RooCodeEventName.TaskAgentSwitched, taskId, agent), + ) cline.on("taskAskResponded", () => this.emit(RooCodeEventName.TaskAskResponded, cline.taskId)) diff --git a/src/i18n/locales/ca/common.json b/src/i18n/locales/ca/common.json index cbe2c032b4..ab3da9a461 100644 --- a/src/i18n/locales/ca/common.json +++ b/src/i18n/locales/ca/common.json @@ -21,7 +21,7 @@ "confirmation": { "reset_state": "Estàs segur que vols restablir tots els estats i emmagatzematge secret a l'extensió? Això no es pot desfer.", "delete_config_profile": "Estàs segur que vols eliminar aquest perfil de configuració?", - "delete_custom_mode_with_rules": "Esteu segur que voleu suprimir aquest mode {scope}?\n\nAixò també suprimirà la carpeta de regles associada a:\n{rulesFolderPath}" + "delete_custom_mode_with_rules": "Esteu segur que voleu suprimir aquest agent {scope}?\n\nAixò també suprimirà la carpeta de regles associada a:\n{rulesFolderPath}" }, "errors": { "invalid_data_uri": "Format d'URI de dades no vàlid", @@ -47,7 +47,7 @@ "list_api_config": "Ha fallat l'obtenció de la llista de configuracions de l'API", "update_server_timeout": "Ha fallat l'actualització del temps d'espera del servidor", "hmr_not_running": "El servidor de desenvolupament local no està executant-se, l'HMR no funcionarà. Si us plau, executa 'npm run dev' abans de llançar l'extensió per habilitar l'HMR.", - "retrieve_current_mode": "Error en recuperar el mode actual de l'estat.", + "retrieve_current_mode": "Error en recuperar el agent actual de l'estat.", "failed_delete_repo": "Ha fallat l'eliminació del repositori o branca associada: {{error}}", "failed_remove_directory": "Ha fallat l'eliminació del directori de tasques: {{error}}", "custom_storage_path_unusable": "La ruta d'emmagatzematge personalitzada \"{{path}}\" no és utilitzable, s'utilitzarà la ruta predeterminada", @@ -86,7 +86,7 @@ "generate_complete_prompt": "Error de finalització de Gemini: {{error}}", "sources": "Fonts:" }, - "mode_import_failed": "Ha fallat la importació del mode: {{error}}" + "mode_import_failed": "Ha fallat la importació del agent: {{error}}" }, "warnings": { "no_terminal_content": "No s'ha seleccionat contingut de terminal", @@ -106,8 +106,8 @@ "image_saved": "Imatge desada a {{path}}", "organization_share_link_copied": "Enllaç de compartició d'organització copiat al porta-retalls!", "public_share_link_copied": "Enllaç de compartició pública copiat al porta-retalls!", - "mode_exported": "Mode '{{mode}}' exportat correctament", - "mode_imported": "Mode importat correctament" + "mode_exported": "Agent '{{agent}}' exportat correctament", + "mode_imported": "Agent importat correctament" }, "answers": { "yes": "Sí", @@ -146,14 +146,14 @@ "customModes": { "errors": { "yamlParseError": "YAML no vàlid al fitxer .roomodes a la línia {{line}}. Comprova:\n• Indentació correcta (utilitza espais, no tabuladors)\n• Cometes i claudàtors coincidents\n• Sintaxi YAML vàlida", - "schemaValidationError": "Format de modes personalitzats no vàlid a .roomodes:\n{{issues}}", - "invalidFormat": "Format de modes personalitzats no vàlid. Assegura't que la teva configuració segueix el format YAML correcte.", - "updateFailed": "Error en actualitzar el mode personalitzat: {{error}}", - "deleteFailed": "Error en eliminar el mode personalitzat: {{error}}", - "resetFailed": "Error en restablir els modes personalitzats: {{error}}", - "modeNotFound": "Error d'escriptura: Mode no trobat", - "noWorkspaceForProject": "No s'ha trobat cap carpeta d'espai de treball per al mode específic del projecte", - "rulesCleanupFailed": "El mode s'ha suprimit correctament, però no s'ha pogut suprimir la carpeta de regles a {{rulesFolderPath}}. És possible que l'hagis de suprimir manualment." + "schemaValidationError": "Format de agents personalitzats no vàlid a .roomodes:\n{{issues}}", + "invalidFormat": "Format de agents personalitzats no vàlid. Assegura't que la teva configuració segueix el format YAML correcte.", + "updateFailed": "Error en actualitzar el agent personalitzat: {{error}}", + "deleteFailed": "Error en eliminar el agent personalitzat: {{error}}", + "resetFailed": "Error en restablir els agents personalitzats: {{error}}", + "modeNotFound": "Error d'escriptura: Agent no trobat", + "noWorkspaceForProject": "No s'ha trobat cap carpeta d'espai de treball per al agent específic del projecte", + "rulesCleanupFailed": "El agent s'ha suprimit correctament, però no s'ha pogut suprimir la carpeta de regles a {{rulesFolderPath}}. És possible que l'hagis de suprimir manualment." }, "scope": { "project": "projecte", @@ -161,8 +161,8 @@ } }, "marketplace": { - "mode": { - "rulesCleanupFailed": "El mode s'ha eliminat correctament, però no s'ha pogut eliminar la carpeta de regles a {{rulesFolderPath}}. És possible que l'hagis d'eliminar manualment." + "agent": { + "rulesCleanupFailed": "El agent s'ha eliminat correctament, però no s'ha pogut eliminar la carpeta de regles a {{rulesFolderPath}}. És possible que l'hagis d'eliminar manualment." } }, "mdm": { @@ -174,9 +174,9 @@ }, "prompts": { "deleteMode": { - "title": "Suprimeix el mode personalitzat", - "description": "Esteu segur que voleu suprimir aquest mode {{scope}}? Això també suprimirà la carpeta de regles associada a: {{rulesFolderPath}}", - "descriptionNoRules": "Esteu segur que voleu suprimir aquest mode personalitzat?", + "title": "Suprimeix el agent personalitzat", + "description": "Esteu segur que voleu suprimir aquest agent {{scope}}? Això també suprimirà la carpeta de regles associada a: {{rulesFolderPath}}", + "descriptionNoRules": "Esteu segur que voleu suprimir aquest agent personalitzat?", "confirm": "Suprimeix" } }, diff --git a/src/i18n/locales/ca/marketplace.json b/src/i18n/locales/ca/marketplace.json index 6c64374447..c3a72025ad 100644 --- a/src/i18n/locales/ca/marketplace.json +++ b/src/i18n/locales/ca/marketplace.json @@ -1,11 +1,11 @@ { "type-group": { - "modes": "Modes", + "agents": "Agents", "mcps": "Servidors MCP", "match": "coincidència" }, "item-card": { - "type-mode": "Mode", + "type-agent": "Agent", "type-mcp": "Servidor MCP", "type-other": "Altre", "by-author": "per {{author}}", @@ -23,7 +23,7 @@ "type": { "label": "Tipus", "all": "Tots els tipus", - "mode": "Mode", + "agent": "Agent", "mcpServer": "Servidor MCP" }, "sort": { diff --git a/src/i18n/locales/de/common.json b/src/i18n/locales/de/common.json index 95d315e103..e7d2a58209 100644 --- a/src/i18n/locales/de/common.json +++ b/src/i18n/locales/de/common.json @@ -102,7 +102,7 @@ "image_saved": "Bild gespeichert unter {{path}}", "organization_share_link_copied": "Organisations-Freigabelink in die Zwischenablage kopiert!", "public_share_link_copied": "Öffentlicher Freigabelink in die Zwischenablage kopiert!", - "mode_exported": "Modus '{{mode}}' erfolgreich exportiert", + "mode_exported": "Modus '{{agent}}' erfolgreich exportiert", "mode_imported": "Modus erfolgreich importiert" }, "answers": { @@ -161,7 +161,7 @@ } }, "marketplace": { - "mode": { + "agent": { "rulesCleanupFailed": "Der Modus wurde erfolgreich entfernt, aber der Regelordner unter {{rulesFolderPath}} konnte nicht gelöscht werden. Möglicherweise musst du ihn manuell löschen." } }, diff --git a/src/i18n/locales/de/marketplace.json b/src/i18n/locales/de/marketplace.json index 2981441cf0..de2f98288e 100644 --- a/src/i18n/locales/de/marketplace.json +++ b/src/i18n/locales/de/marketplace.json @@ -1,11 +1,11 @@ { "type-group": { - "modes": "Modi", + "agents": "Modi", "mcps": "MCP-Server", "match": "Übereinstimmung" }, "item-card": { - "type-mode": "Modus", + "type-agent": "Modus", "type-mcp": "MCP-Server", "type-other": "Andere", "by-author": "von {{author}}", @@ -23,7 +23,7 @@ "type": { "label": "Typ", "all": "Alle Typen", - "mode": "Modus", + "agent": "Modus", "mcpServer": "MCP-Server" }, "sort": { diff --git a/src/i18n/locales/en/common.json b/src/i18n/locales/en/common.json index 4150e12f84..9cfa3c8400 100644 --- a/src/i18n/locales/en/common.json +++ b/src/i18n/locales/en/common.json @@ -17,7 +17,7 @@ "confirmation": { "reset_state": "Are you sure you want to reset all state and secret storage in the extension? This cannot be undone.", "delete_config_profile": "Are you sure you want to delete this configuration profile?", - "delete_custom_mode_with_rules": "Are you sure you want to delete this {scope} mode?\n\nThis will also delete the associated rules folder at:\n{rulesFolderPath}" + "delete_custom_agent_with_rules": "Are you sure you want to delete this {scope} agent?\n\nThis will also delete the associated rules folder at:\n{rulesFolderPath}" }, "errors": { "invalid_data_uri": "Invalid data URI format", @@ -43,7 +43,7 @@ "list_api_config": "Failed to get list api configuration", "update_server_timeout": "Failed to update server timeout", "hmr_not_running": "Local development server is not running, HMR will not work. Please run 'npm run dev' before launching the extension to enable HMR.", - "retrieve_current_mode": "Error: failed to retrieve current mode from state.", + "retrieve_current_agent": "Error: failed to retrieve current agent from state.", "failed_delete_repo": "Failed to delete associated shadow repository or branch: {{error}}", "failed_remove_directory": "Failed to remove task directory: {{error}}", "custom_storage_path_unusable": "Custom storage path \"{{path}}\" is unusable, will use default path", @@ -69,7 +69,7 @@ "share_auth_required": "Authentication required. Please sign in to share tasks.", "share_not_enabled": "Task sharing is not enabled for this organization.", "share_task_not_found": "Task not found or access denied.", - "mode_import_failed": "Failed to import mode: {{error}}", + "agent_import_failed": "Failed to import agent: {{error}}", "delete_rules_folder_failed": "Failed to delete rules folder: {{rulesFolderPath}}. Error: {{error}}", "claudeCode": { "processExited": "Claude Code process exited with code {{exitCode}}.", @@ -102,8 +102,8 @@ "public_share_link_copied": "Public share link copied to clipboard!", "image_copied_to_clipboard": "Image data URI copied to clipboard", "image_saved": "Image saved to {{path}}", - "mode_exported": "Mode '{{mode}}' exported successfully", - "mode_imported": "Mode imported successfully" + "agent_exported": "Agent '{{agent}}' exported successfully", + "agent_imported": "Agent imported successfully" }, "answers": { "yes": "Yes", @@ -135,14 +135,14 @@ "customModes": { "errors": { "yamlParseError": "Invalid YAML in .roomodes file at line {{line}}. Please check for:\n• Proper indentation (use spaces, not tabs)\n• Matching quotes and brackets\n• Valid YAML syntax", - "schemaValidationError": "Invalid custom modes format in .roomodes:\n{{issues}}", - "invalidFormat": "Invalid custom modes format. Please ensure your settings follow the correct YAML format.", - "updateFailed": "Failed to update custom mode: {{error}}", - "deleteFailed": "Failed to delete custom mode: {{error}}", - "resetFailed": "Failed to reset custom modes: {{error}}", - "modeNotFound": "Write error: Mode not found", - "noWorkspaceForProject": "No workspace folder found for project-specific mode", - "rulesCleanupFailed": "Mode deleted successfully, but failed to delete rules folder at {{rulesFolderPath}}. You may need to delete it manually." + "schemaValidationError": "Invalid custom agents format in .roomodes:\n{{issues}}", + "invalidFormat": "Invalid custom agents format. Please ensure your settings follow the correct YAML format.", + "updateFailed": "Failed to update custom agent: {{error}}", + "deleteFailed": "Failed to delete custom agent: {{error}}", + "resetFailed": "Failed to reset custom agents: {{error}}", + "agentNotFound": "Write error: Agent not found", + "noWorkspaceForProject": "No workspace folder found for project-specific agent", + "rulesCleanupFailed": "Agent deleted successfully, but failed to delete rules folder at {{rulesFolderPath}}. You may need to delete it manually." }, "scope": { "project": "project", @@ -150,8 +150,8 @@ } }, "marketplace": { - "mode": { - "rulesCleanupFailed": "Mode removed successfully, but failed to delete rules folder at {{rulesFolderPath}}. You may need to delete it manually." + "agent": { + "rulesCleanupFailed": "Agent removed successfully, but failed to delete rules folder at {{rulesFolderPath}}. You may need to delete it manually." } }, "mdm": { @@ -162,10 +162,10 @@ } }, "prompts": { - "deleteMode": { - "title": "Delete Custom Mode", - "description": "Are you sure you want to delete this {{scope}} mode? This will also delete the associated rules folder at: {{rulesFolderPath}}", - "descriptionNoRules": "Are you sure you want to delete this custom mode?", + "deleteAgent": { + "title": "Delete Custom Agent", + "description": "Are you sure you want to delete this {{scope}} agent? This will also delete the associated rules folder at: {{rulesFolderPath}}", + "descriptionNoRules": "Are you sure you want to delete this custom agent?", "confirm": "Delete" } }, diff --git a/src/i18n/locales/en/marketplace.json b/src/i18n/locales/en/marketplace.json index f141aaeb05..9a82c5cec3 100644 --- a/src/i18n/locales/en/marketplace.json +++ b/src/i18n/locales/en/marketplace.json @@ -1,11 +1,11 @@ { "type-group": { - "modes": "Modes", + "agents": "Agents", "mcps": "MCP Servers", "match": "match" }, "item-card": { - "type-mode": "Mode", + "type-agent": "Agent", "type-mcp": "MCP Server", "type-other": "Other", "by-author": "by {{author}}", @@ -23,7 +23,7 @@ "type": { "label": "Type", "all": "All Types", - "mode": "Mode", + "agent": "Agent", "mcpServer": "MCP Server" }, "sort": { diff --git a/src/i18n/locales/es/common.json b/src/i18n/locales/es/common.json index d47b95be7e..229255e141 100644 --- a/src/i18n/locales/es/common.json +++ b/src/i18n/locales/es/common.json @@ -102,7 +102,7 @@ "image_saved": "Imagen guardada en {{path}}", "organization_share_link_copied": "¡Enlace de compartición de organización copiado al portapapeles!", "public_share_link_copied": "¡Enlace de compartición pública copiado al portapapeles!", - "mode_exported": "Modo '{{mode}}' exportado correctamente", + "mode_exported": "Modo '{{agent}}' exportado correctamente", "mode_imported": "Modo importado correctamente" }, "answers": { @@ -161,7 +161,7 @@ } }, "marketplace": { - "mode": { + "agent": { "rulesCleanupFailed": "El modo se eliminó correctamente, pero no se pudo eliminar la carpeta de reglas en {{rulesFolderPath}}. Es posible que debas eliminarla manually." } }, diff --git a/src/i18n/locales/es/marketplace.json b/src/i18n/locales/es/marketplace.json index e12e1d1dc1..4a28c1e92d 100644 --- a/src/i18n/locales/es/marketplace.json +++ b/src/i18n/locales/es/marketplace.json @@ -1,11 +1,11 @@ { "type-group": { - "modes": "Modos", + "agents": "Modos", "mcps": "Servidores MCP", "match": "coincidencia" }, "item-card": { - "type-mode": "Modo", + "type-agent": "Modo", "type-mcp": "Servidor MCP", "type-other": "Otro", "by-author": "por {{author}}", @@ -23,7 +23,7 @@ "type": { "label": "Tipo", "all": "Todos los tipos", - "mode": "Modo", + "agent": "Modo", "mcpServer": "Servidor MCP" }, "sort": { diff --git a/src/i18n/locales/fr/common.json b/src/i18n/locales/fr/common.json index e239358bbf..e8ef6be401 100644 --- a/src/i18n/locales/fr/common.json +++ b/src/i18n/locales/fr/common.json @@ -17,7 +17,7 @@ "confirmation": { "reset_state": "Êtes-vous sûr de vouloir réinitialiser le global state et le stockage de secrets de l'extension ? Cette action est irréversible.", "delete_config_profile": "Êtes-vous sûr de vouloir supprimer ce profil de configuration ?", - "delete_custom_mode_with_rules": "Êtes-vous sûr de vouloir supprimer ce mode {scope} ?\n\nCela supprimera également le dossier de règles associé à l'adresse :\n{rulesFolderPath}" + "delete_custom_mode_with_rules": "Êtes-vous sûr de vouloir supprimer ce agent {scope} ?\n\nCela supprimera également le dossier de règles associé à l'adresse :\n{rulesFolderPath}" }, "errors": { "invalid_data_uri": "Format d'URI de données invalide", @@ -43,7 +43,7 @@ "list_api_config": "Erreur lors de l'obtention de la liste des configurations API", "update_server_timeout": "Erreur lors de la mise à jour du délai d'attente du serveur", "hmr_not_running": "Le serveur de développement local n'est pas en cours d'exécution, HMR ne fonctionnera pas. Veuillez exécuter 'npm run dev' avant de lancer l'extension pour activer l'HMR.", - "retrieve_current_mode": "Erreur lors de la récupération du mode actuel à partir du state.", + "retrieve_current_mode": "Erreur lors de la récupération du agent actuel à partir du state.", "failed_delete_repo": "Échec de la suppression du repo fantôme ou de la branche associée : {{error}}", "failed_remove_directory": "Échec de la suppression du répertoire de tâches : {{error}}", "custom_storage_path_unusable": "Le chemin de stockage personnalisé \"{{path}}\" est inutilisable, le chemin par défaut sera utilisé", @@ -69,7 +69,7 @@ "share_auth_required": "Authentification requise. Veuillez vous connecter pour partager des tâches.", "share_not_enabled": "Le partage de tâches n'est pas activé pour cette organisation.", "share_task_not_found": "Tâche non trouvée ou accès refusé.", - "mode_import_failed": "Échec de l'importation du mode : {{error}}", + "mode_import_failed": "Échec de l'importation du agent : {{error}}", "delete_rules_folder_failed": "Échec de la suppression du dossier de règles : {{rulesFolderPath}}. Erreur : {{error}}", "claudeCode": { "processExited": "Le processus Claude Code s'est terminé avec le code {{exitCode}}.", @@ -102,8 +102,8 @@ "image_saved": "Image enregistrée dans {{path}}", "organization_share_link_copied": "Lien de partage d'organisation copié dans le presse-papiers !", "public_share_link_copied": "Lien de partage public copié dans le presse-papiers !", - "mode_exported": "Mode '{{mode}}' exporté avec succès", - "mode_imported": "Mode importé avec succès" + "mode_exported": "Agent '{{agent}}' exporté avec succès", + "mode_imported": "Agent importé avec succès" }, "answers": { "yes": "Oui", @@ -146,14 +146,14 @@ "customModes": { "errors": { "yamlParseError": "YAML invalide dans le fichier .roomodes à la ligne {{line}}. Vérifie :\n• L'indentation correcte (utilise des espaces, pas de tabulations)\n• Les guillemets et crochets correspondants\n• La syntaxe YAML valide", - "schemaValidationError": "Format invalide des modes personnalisés dans .roomodes :\n{{issues}}", - "invalidFormat": "Format invalide des modes personnalisés. Assure-toi que tes paramètres suivent le format YAML correct.", - "updateFailed": "Échec de la mise à jour du mode personnalisé : {{error}}", - "deleteFailed": "Échec de la suppression du mode personnalisé : {{error}}", - "resetFailed": "Échec de la réinitialisation des modes personnalisés : {{error}}", - "modeNotFound": "Erreur d'écriture : Mode non trouvé", - "noWorkspaceForProject": "Aucun dossier d'espace de travail trouvé pour le mode spécifique au projet", - "rulesCleanupFailed": "Le mode a été supprimé avec succès, mais la suppression du dossier de règles à l'adresse {{rulesFolderPath}} a échoué. Vous devrez peut-être le supprimer manuellement." + "schemaValidationError": "Format invalide des agents personnalisés dans .roomodes :\n{{issues}}", + "invalidFormat": "Format invalide des agents personnalisés. Assure-toi que tes paramètres suivent le format YAML correct.", + "updateFailed": "Échec de la mise à jour du agent personnalisé : {{error}}", + "deleteFailed": "Échec de la suppression du agent personnalisé : {{error}}", + "resetFailed": "Échec de la réinitialisation des agents personnalisés : {{error}}", + "modeNotFound": "Erreur d'écriture : Agent non trouvé", + "noWorkspaceForProject": "Aucun dossier d'espace de travail trouvé pour le agent spécifique au projet", + "rulesCleanupFailed": "Le agent a été supprimé avec succès, mais la suppression du dossier de règles à l'adresse {{rulesFolderPath}} a échoué. Vous devrez peut-être le supprimer manuellement." }, "scope": { "project": "projet", @@ -161,8 +161,8 @@ } }, "marketplace": { - "mode": { - "rulesCleanupFailed": "Le mode a été supprimé avec succès, mais la suppression du dossier de règles à l'adresse {{rulesFolderPath}} a échoué. Vous devrez peut-être le supprimer manuellement." + "agent": { + "rulesCleanupFailed": "Le agent a été supprimé avec succès, mais la suppression du dossier de règles à l'adresse {{rulesFolderPath}} a échoué. Vous devrez peut-être le supprimer manuellement." } }, "mdm": { @@ -174,9 +174,9 @@ }, "prompts": { "deleteMode": { - "title": "Supprimer le mode personnalisé", - "description": "Êtes-vous sûr de vouloir supprimer ce mode {{scope}} ? Cela supprimera également le dossier de règles associé à l'adresse : {{rulesFolderPath}}", - "descriptionNoRules": "Êtes-vous sûr de vouloir supprimer ce mode personnalisé ?", + "title": "Supprimer le agent personnalisé", + "description": "Êtes-vous sûr de vouloir supprimer ce agent {{scope}} ? Cela supprimera également le dossier de règles associé à l'adresse : {{rulesFolderPath}}", + "descriptionNoRules": "Êtes-vous sûr de vouloir supprimer ce agent personnalisé ?", "confirm": "Supprimer" } }, diff --git a/src/i18n/locales/fr/marketplace.json b/src/i18n/locales/fr/marketplace.json index 7a42b0033e..b5d7c3a533 100644 --- a/src/i18n/locales/fr/marketplace.json +++ b/src/i18n/locales/fr/marketplace.json @@ -1,11 +1,11 @@ { "type-group": { - "modes": "Modes", + "agents": "Agents", "mcps": "Serveurs MCP", "match": "correspondance" }, "item-card": { - "type-mode": "Mode", + "type-agent": "Agent", "type-mcp": "Serveur MCP", "type-other": "Autre", "by-author": "par {{author}}", @@ -23,7 +23,7 @@ "type": { "label": "Type", "all": "Tous les types", - "mode": "Mode", + "agent": "Agent", "mcpServer": "Serveur MCP" }, "sort": { diff --git a/src/i18n/locales/hi/common.json b/src/i18n/locales/hi/common.json index d5ba036f63..7048efe8c6 100644 --- a/src/i18n/locales/hi/common.json +++ b/src/i18n/locales/hi/common.json @@ -102,7 +102,7 @@ "image_saved": "छवि {{path}} में सहेजी गई", "organization_share_link_copied": "संगठन साझाकरण लिंक क्लिपबोर्ड में कॉपी किया गया!", "public_share_link_copied": "सार्वजनिक साझाकरण लिंक क्लिपबोर्ड में कॉपी किया गया!", - "mode_exported": "मोड '{{mode}}' सफलतापूर्वक निर्यात किया गया", + "mode_exported": "मोड '{{agent}}' सफलतापूर्वक निर्यात किया गया", "mode_imported": "मोड सफलतापूर्वक आयात किया गया" }, "answers": { @@ -161,7 +161,7 @@ } }, "marketplace": { - "mode": { + "agent": { "rulesCleanupFailed": "मोड सफलतापूर्वक हटा दिया गया, लेकिन {{rulesFolderPath}} पर नियम फ़ोल्डर को हटाने में विफल रहा। आपको इसे मैन्युअल रूप से हटाना पड़ सकता है।" } }, diff --git a/src/i18n/locales/hi/marketplace.json b/src/i18n/locales/hi/marketplace.json index 94013c20e4..8357e6944d 100644 --- a/src/i18n/locales/hi/marketplace.json +++ b/src/i18n/locales/hi/marketplace.json @@ -1,11 +1,11 @@ { "type-group": { - "modes": "मोड्स", + "agents": "मोड्स", "mcps": "MCP सर्वर", "match": "मैच" }, "item-card": { - "type-mode": "मोड", + "type-agent": "मोड", "type-mcp": "MCP सर्वर", "type-other": "अन्य", "by-author": "{{author}} द्वारा", @@ -23,7 +23,7 @@ "type": { "label": "प्रकार", "all": "सभी प्रकार", - "mode": "मोड", + "agent": "मोड", "mcpServer": "MCP सर्वर" }, "sort": { diff --git a/src/i18n/locales/id/common.json b/src/i18n/locales/id/common.json index b261f69e24..515a92687e 100644 --- a/src/i18n/locales/id/common.json +++ b/src/i18n/locales/id/common.json @@ -17,7 +17,7 @@ "confirmation": { "reset_state": "Apakah kamu yakin ingin mereset semua state dan secret storage di ekstensi? Ini tidak dapat dibatalkan.", "delete_config_profile": "Apakah kamu yakin ingin menghapus profil konfigurasi ini?", - "delete_custom_mode_with_rules": "Anda yakin ingin menghapus mode {scope} ini?\n\nIni juga akan menghapus folder aturan terkait di:\n{rulesFolderPath}" + "delete_custom_mode_with_rules": "Anda yakin ingin menghapus agent {scope} ini?\n\nIni juga akan menghapus folder aturan terkait di:\n{rulesFolderPath}" }, "errors": { "invalid_data_uri": "Format data URI tidak valid", @@ -43,7 +43,7 @@ "list_api_config": "Gagal mendapatkan daftar konfigurasi api", "update_server_timeout": "Gagal memperbarui timeout server", "hmr_not_running": "Server pengembangan lokal tidak berjalan, HMR tidak akan bekerja. Silakan jalankan 'npm run dev' sebelum meluncurkan ekstensi untuk mengaktifkan HMR.", - "retrieve_current_mode": "Error: gagal mengambil mode saat ini dari state.", + "retrieve_current_mode": "Error: gagal mengambil agent saat ini dari state.", "failed_delete_repo": "Gagal menghapus shadow repository atau branch yang terkait: {{error}}", "failed_remove_directory": "Gagal menghapus direktori tugas: {{error}}", "custom_storage_path_unusable": "Path penyimpanan kustom \"{{path}}\" tidak dapat digunakan, akan menggunakan path default", @@ -69,7 +69,7 @@ "share_auth_required": "Autentikasi diperlukan. Silakan masuk untuk berbagi tugas.", "share_not_enabled": "Berbagi tugas tidak diaktifkan untuk organisasi ini.", "share_task_not_found": "Tugas tidak ditemukan atau akses ditolak.", - "mode_import_failed": "Gagal mengimpor mode: {{error}}", + "mode_import_failed": "Gagal mengimpor agent: {{error}}", "delete_rules_folder_failed": "Gagal menghapus folder aturan: {{rulesFolderPath}}. Error: {{error}}", "claudeCode": { "processExited": "Proses Claude Code keluar dengan kode {{exitCode}}.", @@ -102,8 +102,8 @@ "image_saved": "Gambar disimpan ke {{path}}", "organization_share_link_copied": "Tautan berbagi organisasi disalin ke clipboard!", "public_share_link_copied": "Tautan berbagi publik disalin ke clipboard!", - "mode_exported": "Mode '{{mode}}' berhasil diekspor", - "mode_imported": "Mode berhasil diimpor" + "mode_exported": "Agent '{{agent}}' berhasil diekspor", + "mode_imported": "Agent berhasil diimpor" }, "answers": { "yes": "Ya", @@ -146,14 +146,14 @@ "customModes": { "errors": { "yamlParseError": "YAML tidak valid dalam file .roomodes pada baris {{line}}. Silakan periksa:\n• Indentasi yang benar (gunakan spasi, bukan tab)\n• Tanda kutip dan kurung yang cocok\n• Sintaks YAML yang valid", - "schemaValidationError": "Format mode kustom tidak valid dalam .roomodes:\n{{issues}}", - "invalidFormat": "Format mode kustom tidak valid. Pastikan pengaturan kamu mengikuti format YAML yang benar.", - "updateFailed": "Gagal memperbarui mode kustom: {{error}}", - "deleteFailed": "Gagal menghapus mode kustom: {{error}}", - "resetFailed": "Gagal mereset mode kustom: {{error}}", - "modeNotFound": "Kesalahan tulis: Mode tidak ditemukan", - "noWorkspaceForProject": "Tidak ditemukan folder workspace untuk mode khusus proyek", - "rulesCleanupFailed": "Mode berhasil dihapus, tetapi gagal menghapus folder aturan di {{rulesFolderPath}}. Kamu mungkin perlu menghapusnya secara manual." + "schemaValidationError": "Format agent kustom tidak valid dalam .roomodes:\n{{issues}}", + "invalidFormat": "Format agent kustom tidak valid. Pastikan pengaturan kamu mengikuti format YAML yang benar.", + "updateFailed": "Gagal memperbarui agent kustom: {{error}}", + "deleteFailed": "Gagal menghapus agent kustom: {{error}}", + "resetFailed": "Gagal mereset agent kustom: {{error}}", + "modeNotFound": "Kesalahan tulis: Agent tidak ditemukan", + "noWorkspaceForProject": "Tidak ditemukan folder workspace untuk agent khusus proyek", + "rulesCleanupFailed": "Agent berhasil dihapus, tetapi gagal menghapus folder aturan di {{rulesFolderPath}}. Kamu mungkin perlu menghapusnya secara manual." }, "scope": { "project": "proyek", @@ -161,8 +161,8 @@ } }, "marketplace": { - "mode": { - "rulesCleanupFailed": "Mode berhasil dihapus, tetapi gagal menghapus folder aturan di {{rulesFolderPath}}. Kamu mungkin perlu menghapusnya secara manual." + "agent": { + "rulesCleanupFailed": "Agent berhasil dihapus, tetapi gagal menghapus folder aturan di {{rulesFolderPath}}. Kamu mungkin perlu menghapusnya secara manual." } }, "mdm": { @@ -174,9 +174,9 @@ }, "prompts": { "deleteMode": { - "title": "Hapus Mode Kustom", - "description": "Anda yakin ingin menghapus mode {{scope}} ini? Ini juga akan menghapus folder aturan terkait di: {{rulesFolderPath}}", - "descriptionNoRules": "Anda yakin ingin menghapus mode kustom ini?", + "title": "Hapus Agent Kustom", + "description": "Anda yakin ingin menghapus agent {{scope}} ini? Ini juga akan menghapus folder aturan terkait di: {{rulesFolderPath}}", + "descriptionNoRules": "Anda yakin ingin menghapus agent kustom ini?", "confirm": "Hapus" } }, diff --git a/src/i18n/locales/id/marketplace.json b/src/i18n/locales/id/marketplace.json index 77d93973a9..c26355f40e 100644 --- a/src/i18n/locales/id/marketplace.json +++ b/src/i18n/locales/id/marketplace.json @@ -1,11 +1,11 @@ { "type-group": { - "modes": "Mode", + "agents": "Agent", "mcps": "Server MCP", "match": "cocok" }, "item-card": { - "type-mode": "Mode", + "type-agent": "Agent", "type-mcp": "Server MCP", "type-other": "Lainnya", "by-author": "oleh {{author}}", @@ -23,7 +23,7 @@ "type": { "label": "Tipe", "all": "Semua Tipe", - "mode": "Mode", + "agent": "Agent", "mcpServer": "Server MCP" }, "sort": { diff --git a/src/i18n/locales/it/common.json b/src/i18n/locales/it/common.json index f5e15398ed..87bb18f47f 100644 --- a/src/i18n/locales/it/common.json +++ b/src/i18n/locales/it/common.json @@ -102,7 +102,7 @@ "image_saved": "Immagine salvata in {{path}}", "organization_share_link_copied": "Link di condivisione organizzazione copiato negli appunti!", "public_share_link_copied": "Link di condivisione pubblica copiato negli appunti!", - "mode_exported": "Modalità '{{mode}}' esportata con successo", + "mode_exported": "Modalità '{{agent}}' esportata con successo", "mode_imported": "Modalità importata con successo" }, "answers": { @@ -161,7 +161,7 @@ } }, "marketplace": { - "mode": { + "agent": { "rulesCleanupFailed": "La modalità è stata rimossa con successo, ma non è stato possibile eliminare la cartella delle regole in {{rulesFolderPath}}. Potrebbe essere necessario eliminarla manualmente." } }, diff --git a/src/i18n/locales/it/marketplace.json b/src/i18n/locales/it/marketplace.json index 3cdcd2b76c..33bf93ad56 100644 --- a/src/i18n/locales/it/marketplace.json +++ b/src/i18n/locales/it/marketplace.json @@ -1,11 +1,11 @@ { "type-group": { - "modes": "Modalità", + "agents": "Modalità", "mcps": "Server MCP", "match": "corrispondenza" }, "item-card": { - "type-mode": "Modalità", + "type-agent": "Modalità", "type-mcp": "Server MCP", "type-other": "Altro", "by-author": "di {{author}}", @@ -23,7 +23,7 @@ "type": { "label": "Tipo", "all": "Tutti i tipi", - "mode": "Modalità", + "agent": "Modalità", "mcpServer": "Server MCP" }, "sort": { diff --git a/src/i18n/locales/ja/common.json b/src/i18n/locales/ja/common.json index 9b43f64bcf..e150ac3726 100644 --- a/src/i18n/locales/ja/common.json +++ b/src/i18n/locales/ja/common.json @@ -102,7 +102,7 @@ "image_saved": "画像を{{path}}に保存しました", "organization_share_link_copied": "組織共有リンクがクリップボードにコピーされました!", "public_share_link_copied": "公開共有リンクがクリップボードにコピーされました!", - "mode_exported": "モード「{{mode}}」が正常にエクスポートされました", + "mode_exported": "モード「{{agent}}」が正常にエクスポートされました", "mode_imported": "モードが正常にインポートされました" }, "answers": { @@ -161,7 +161,7 @@ } }, "marketplace": { - "mode": { + "agent": { "rulesCleanupFailed": "モードは正常に削除されましたが、{{rulesFolderPath}} にあるルールフォルダの削除に失敗しました。手動で削除する必要がある場合があります。" } }, diff --git a/src/i18n/locales/ja/marketplace.json b/src/i18n/locales/ja/marketplace.json index 26cff2ade9..76e79dcbb7 100644 --- a/src/i18n/locales/ja/marketplace.json +++ b/src/i18n/locales/ja/marketplace.json @@ -1,11 +1,11 @@ { "type-group": { - "modes": "モード", + "agents": "モード", "mcps": "MCPサーバー", "match": "マッチ" }, "item-card": { - "type-mode": "モード", + "type-agent": "モード", "type-mcp": "MCPサーバー", "type-other": "その他", "by-author": "{{author}}による", @@ -23,7 +23,7 @@ "type": { "label": "タイプ", "all": "すべてのタイプ", - "mode": "モード", + "agent": "モード", "mcpServer": "MCPサーバー" }, "sort": { diff --git a/src/i18n/locales/ko/common.json b/src/i18n/locales/ko/common.json index 2bfbacad32..c3b24c2382 100644 --- a/src/i18n/locales/ko/common.json +++ b/src/i18n/locales/ko/common.json @@ -102,7 +102,7 @@ "image_saved": "이미지가 {{path}}에 저장되었습니다", "organization_share_link_copied": "조직 공유 링크가 클립보드에 복사되었습니다!", "public_share_link_copied": "공개 공유 링크가 클립보드에 복사되었습니다!", - "mode_exported": "'{{mode}}' 모드가 성공적으로 내보내졌습니다", + "mode_exported": "'{{agent}}' 모드가 성공적으로 내보내졌습니다", "mode_imported": "모드를 성공적으로 가져왔습니다" }, "answers": { @@ -161,7 +161,7 @@ } }, "marketplace": { - "mode": { + "agent": { "rulesCleanupFailed": "모드가 성공적으로 제거되었지만 {{rulesFolderPath}}의 규칙 폴더를 삭제하지 못했습니다. 수동으로 삭제해야 할 수도 있습니다." } }, diff --git a/src/i18n/locales/ko/marketplace.json b/src/i18n/locales/ko/marketplace.json index 52bd03edf7..48ccebca03 100644 --- a/src/i18n/locales/ko/marketplace.json +++ b/src/i18n/locales/ko/marketplace.json @@ -1,11 +1,11 @@ { "type-group": { - "modes": "모드", + "agents": "모드", "mcps": "MCP 서버", "match": "일치" }, "item-card": { - "type-mode": "모드", + "type-agent": "모드", "type-mcp": "MCP 서버", "type-other": "기타", "by-author": "{{author}} 작성", @@ -23,7 +23,7 @@ "type": { "label": "유형", "all": "모든 유형", - "mode": "모드", + "agent": "모드", "mcpServer": "MCP 서버" }, "sort": { diff --git a/src/i18n/locales/nl/common.json b/src/i18n/locales/nl/common.json index 645a371754..c417a88998 100644 --- a/src/i18n/locales/nl/common.json +++ b/src/i18n/locales/nl/common.json @@ -102,7 +102,7 @@ "image_saved": "Afbeelding opgeslagen naar {{path}}", "organization_share_link_copied": "Organisatie deel-link gekopieerd naar klembord!", "public_share_link_copied": "Openbare deel-link gekopieerd naar klembord!", - "mode_exported": "Modus '{{mode}}' succesvol geëxporteerd", + "mode_exported": "Modus '{{agent}}' succesvol geëxporteerd", "mode_imported": "Modus succesvol geïmporteerd" }, "answers": { @@ -161,7 +161,7 @@ } }, "marketplace": { - "mode": { + "agent": { "rulesCleanupFailed": "Modus succesvol verwijderd, maar het verwijderen van de regelsmap op {{rulesFolderPath}} is mislukt. Je moet deze mogelijk handmatig verwijderen." } }, diff --git a/src/i18n/locales/nl/marketplace.json b/src/i18n/locales/nl/marketplace.json index 5628b8f628..35845fd478 100644 --- a/src/i18n/locales/nl/marketplace.json +++ b/src/i18n/locales/nl/marketplace.json @@ -1,11 +1,11 @@ { "type-group": { - "modes": "Modi", + "agents": "Modi", "mcps": "MCP Servers", "match": "overeenkomst" }, "item-card": { - "type-mode": "Modus", + "type-agent": "Modus", "type-mcp": "MCP Server", "type-other": "Andere", "by-author": "door {{author}}", @@ -23,7 +23,7 @@ "type": { "label": "Type", "all": "Alle types", - "mode": "Modus", + "agent": "Modus", "mcpServer": "MCP Server" }, "sort": { diff --git a/src/i18n/locales/pl/common.json b/src/i18n/locales/pl/common.json index 45251e1ab4..de7d86f051 100644 --- a/src/i18n/locales/pl/common.json +++ b/src/i18n/locales/pl/common.json @@ -102,7 +102,7 @@ "image_saved": "Obraz zapisany w {{path}}", "organization_share_link_copied": "Link udostępniania organizacji skopiowany do schowka!", "public_share_link_copied": "Publiczny link udostępniania skopiowany do schowka!", - "mode_exported": "Tryb '{{mode}}' pomyślnie wyeksportowany", + "mode_exported": "Tryb '{{agent}}' pomyślnie wyeksportowany", "mode_imported": "Tryb pomyślnie zaimportowany" }, "answers": { @@ -161,7 +161,7 @@ } }, "marketplace": { - "mode": { + "agent": { "rulesCleanupFailed": "Tryb został pomyślnie usunięty, ale nie udało się usunąć folderu reguł w {{rulesFolderPath}}. Może być konieczne ręczne usunięcie." } }, diff --git a/src/i18n/locales/pl/marketplace.json b/src/i18n/locales/pl/marketplace.json index 029dbd95a3..ccb4677de2 100644 --- a/src/i18n/locales/pl/marketplace.json +++ b/src/i18n/locales/pl/marketplace.json @@ -1,11 +1,11 @@ { "type-group": { - "modes": "Tryby", + "agents": "Tryby", "mcps": "Serwery MCP", "match": "dopasowanie" }, "item-card": { - "type-mode": "Tryb", + "type-agent": "Tryb", "type-mcp": "Serwer MCP", "type-other": "Inne", "by-author": "przez {{author}}", @@ -23,7 +23,7 @@ "type": { "label": "Typ", "all": "Wszystkie typy", - "mode": "Tryb", + "agent": "Tryb", "mcpServer": "Serwer MCP" }, "sort": { diff --git a/src/i18n/locales/pt-BR/common.json b/src/i18n/locales/pt-BR/common.json index 29c951fb39..a9e5a30b5b 100644 --- a/src/i18n/locales/pt-BR/common.json +++ b/src/i18n/locales/pt-BR/common.json @@ -106,7 +106,7 @@ "image_saved": "Imagem salva em {{path}}", "organization_share_link_copied": "Link de compartilhamento da organização copiado para a área de transferência!", "public_share_link_copied": "Link de compartilhamento público copiado para a área de transferência!", - "mode_exported": "Modo '{{mode}}' exportado com sucesso", + "mode_exported": "Modo '{{agent}}' exportado com sucesso", "mode_imported": "Modo importado com sucesso" }, "answers": { @@ -161,7 +161,7 @@ } }, "marketplace": { - "mode": { + "agent": { "rulesCleanupFailed": "O modo foi removido com sucesso, mas falhou ao excluir a pasta de regras em {{rulesFolderPath}}. Você pode precisar excluí-la manualmente." } }, diff --git a/src/i18n/locales/pt-BR/marketplace.json b/src/i18n/locales/pt-BR/marketplace.json index b0af013888..2511e5e53b 100644 --- a/src/i18n/locales/pt-BR/marketplace.json +++ b/src/i18n/locales/pt-BR/marketplace.json @@ -1,11 +1,11 @@ { "type-group": { - "modes": "Modos", + "agents": "Modos", "mcps": "Servidores MCP", "match": "correspondência" }, "item-card": { - "type-mode": "Modo", + "type-agent": "Modo", "type-mcp": "Servidor MCP", "type-other": "Outro", "by-author": "por {{author}}", @@ -23,7 +23,7 @@ "type": { "label": "Tipo", "all": "Todos os tipos", - "mode": "Modo", + "agent": "Modo", "mcpServer": "Servidor MCP" }, "sort": { diff --git a/src/i18n/locales/ru/common.json b/src/i18n/locales/ru/common.json index 5f8fdf34c1..7c055e1d7c 100644 --- a/src/i18n/locales/ru/common.json +++ b/src/i18n/locales/ru/common.json @@ -102,7 +102,7 @@ "image_saved": "Изображение сохранено в {{path}}", "organization_share_link_copied": "Ссылка для совместного доступа организации скопирована в буфер обмена!", "public_share_link_copied": "Публичная ссылка для совместного доступа скопирована в буфер обмена!", - "mode_exported": "Режим '{{mode}}' успешно экспортирован", + "mode_exported": "Режим '{{agent}}' успешно экспортирован", "mode_imported": "Режим успешно импортирован" }, "answers": { @@ -161,7 +161,7 @@ } }, "marketplace": { - "mode": { + "agent": { "rulesCleanupFailed": "Режим успешно удален, но не удалось удалить папку правил в {{rulesFolderPath}}. Возможно, вам придется удалить ее вручную." } }, diff --git a/src/i18n/locales/ru/marketplace.json b/src/i18n/locales/ru/marketplace.json index a84b1ce3e9..d60e79d1f9 100644 --- a/src/i18n/locales/ru/marketplace.json +++ b/src/i18n/locales/ru/marketplace.json @@ -1,11 +1,11 @@ { "type-group": { - "modes": "Режимы", + "agents": "Режимы", "mcps": "MCP серверы", "match": "совпадение" }, "item-card": { - "type-mode": "Режим", + "type-agent": "Режим", "type-mcp": "MCP сервер", "type-other": "Другое", "by-author": "от {{author}}", @@ -23,7 +23,7 @@ "type": { "label": "Тип", "all": "Все типы", - "mode": "Режим", + "agent": "Режим", "mcpServer": "MCP сервер" }, "sort": { diff --git a/src/i18n/locales/tr/common.json b/src/i18n/locales/tr/common.json index c7feb38ef6..bf14d7c766 100644 --- a/src/i18n/locales/tr/common.json +++ b/src/i18n/locales/tr/common.json @@ -102,7 +102,7 @@ "image_saved": "Resim {{path}} konumuna kaydedildi", "organization_share_link_copied": "Kuruluş paylaşım bağlantısı panoya kopyalandı!", "public_share_link_copied": "Herkese açık paylaşım bağlantısı panoya kopyalandı!", - "mode_exported": "'{{mode}}' modu başarıyla dışa aktarıldı", + "mode_exported": "'{{agent}}' modu başarıyla dışa aktarıldı", "mode_imported": "Mod başarıyla içe aktarıldı" }, "answers": { @@ -161,7 +161,7 @@ } }, "marketplace": { - "mode": { + "agent": { "rulesCleanupFailed": "Mod başarıyla kaldırıldı, ancak {{rulesFolderPath}} konumundaki kurallar klasörü silinemedi. Manuel olarak silmeniz gerekebilir." } }, diff --git a/src/i18n/locales/tr/marketplace.json b/src/i18n/locales/tr/marketplace.json index b08381d71c..ff9d5010f5 100644 --- a/src/i18n/locales/tr/marketplace.json +++ b/src/i18n/locales/tr/marketplace.json @@ -1,11 +1,11 @@ { "type-group": { - "modes": "Modlar", + "agents": "Modlar", "mcps": "MCP Sunucuları", "match": "eşleşme" }, "item-card": { - "type-mode": "Mod", + "type-agent": "Mod", "type-mcp": "MCP Sunucusu", "type-other": "Diğer", "by-author": "{{author}} tarafından", @@ -23,7 +23,7 @@ "type": { "label": "Tür", "all": "Tüm Türler", - "mode": "Mod", + "agent": "Mod", "mcpServer": "MCP Sunucusu" }, "sort": { diff --git a/src/i18n/locales/vi/common.json b/src/i18n/locales/vi/common.json index 84b8b409dc..ffc0248be1 100644 --- a/src/i18n/locales/vi/common.json +++ b/src/i18n/locales/vi/common.json @@ -102,7 +102,7 @@ "image_saved": "Hình ảnh đã được lưu vào {{path}}", "organization_share_link_copied": "Liên kết chia sẻ tổ chức đã được sao chép vào clipboard!", "public_share_link_copied": "Liên kết chia sẻ công khai đã được sao chép vào clipboard!", - "mode_exported": "Chế độ '{{mode}}' đã được xuất thành công", + "mode_exported": "Chế độ '{{agent}}' đã được xuất thành công", "mode_imported": "Chế độ đã được nhập thành công" }, "answers": { @@ -161,7 +161,7 @@ } }, "marketplace": { - "mode": { + "agent": { "rulesCleanupFailed": "Đã xóa chế độ thành công, nhưng không thể xóa thư mục quy tắc tại {{rulesFolderPath}}. Bạn có thể cần xóa thủ công." } }, diff --git a/src/i18n/locales/vi/marketplace.json b/src/i18n/locales/vi/marketplace.json index be39054309..ccc626ad2e 100644 --- a/src/i18n/locales/vi/marketplace.json +++ b/src/i18n/locales/vi/marketplace.json @@ -1,11 +1,11 @@ { "type-group": { - "modes": "Chế độ", + "agents": "Chế độ", "mcps": "Máy chủ MCP", "match": "khớp" }, "item-card": { - "type-mode": "Chế độ", + "type-agent": "Chế độ", "type-mcp": "Máy chủ MCP", "type-other": "Khác", "by-author": "bởi {{author}}", @@ -23,7 +23,7 @@ "type": { "label": "Loại", "all": "Tất cả loại", - "mode": "Chế độ", + "agent": "Chế độ", "mcpServer": "Máy chủ MCP" }, "sort": { diff --git a/src/i18n/locales/zh-CN/common.json b/src/i18n/locales/zh-CN/common.json index 7798a8bbdb..ef0b525c7c 100644 --- a/src/i18n/locales/zh-CN/common.json +++ b/src/i18n/locales/zh-CN/common.json @@ -107,7 +107,7 @@ "image_saved": "图片已保存到 {{path}}", "organization_share_link_copied": "组织分享链接已复制到剪贴板!", "public_share_link_copied": "公开分享链接已复制到剪贴板!", - "mode_exported": "模式 '{{mode}}' 已成功导出", + "mode_exported": "模式 '{{agent}}' 已成功导出", "mode_imported": "模式已成功导入" }, "answers": { @@ -166,7 +166,7 @@ } }, "marketplace": { - "mode": { + "agent": { "rulesCleanupFailed": "模式已成功移除,但无法删除位于 {{rulesFolderPath}} 的规则文件夹。您可能需要手动删除。" } }, diff --git a/src/i18n/locales/zh-CN/marketplace.json b/src/i18n/locales/zh-CN/marketplace.json index 50a2ade635..f2dd312df2 100644 --- a/src/i18n/locales/zh-CN/marketplace.json +++ b/src/i18n/locales/zh-CN/marketplace.json @@ -1,11 +1,11 @@ { "type-group": { - "modes": "模式", + "agents": "模式", "mcps": "MCP 服务", "match": "匹配" }, "item-card": { - "type-mode": "模式", + "type-agent": "模式", "type-mcp": "MCP 服务", "type-other": "其他", "by-author": "作者:{{author}}", @@ -23,7 +23,7 @@ "type": { "label": "类型", "all": "所有类型", - "mode": "模式", + "agent": "模式", "mcpServer": "MCP 服务" }, "sort": { diff --git a/src/i18n/locales/zh-TW/common.json b/src/i18n/locales/zh-TW/common.json index c6105c2bf9..e5a82edde2 100644 --- a/src/i18n/locales/zh-TW/common.json +++ b/src/i18n/locales/zh-TW/common.json @@ -102,7 +102,7 @@ "image_saved": "圖片已儲存至 {{path}}", "organization_share_link_copied": "組織分享連結已複製到剪貼簿!", "public_share_link_copied": "公開分享連結已複製到剪貼簿!", - "mode_exported": "模式 '{{mode}}' 已成功匯出", + "mode_exported": "模式 '{{agent}}' 已成功匯出", "mode_imported": "模式已成功匯入" }, "answers": { @@ -161,7 +161,7 @@ } }, "marketplace": { - "mode": { + "agent": { "rulesCleanupFailed": "模式已成功移除,但無法刪除位於 {{rulesFolderPath}} 的規則資料夾。您可能需要手動刪除。" } }, diff --git a/src/i18n/locales/zh-TW/marketplace.json b/src/i18n/locales/zh-TW/marketplace.json index 3eaeb0e7e0..da833e16bd 100644 --- a/src/i18n/locales/zh-TW/marketplace.json +++ b/src/i18n/locales/zh-TW/marketplace.json @@ -1,11 +1,11 @@ { "type-group": { - "modes": "模式", + "agents": "模式", "mcps": "MCP 伺服器", "match": "符合" }, "item-card": { - "type-mode": "模式", + "type-agent": "模式", "type-mcp": "MCP 伺服器", "type-other": "其他", "by-author": "作者:{{author}}", @@ -23,7 +23,7 @@ "type": { "label": "類型", "all": "所有類型", - "mode": "模式", + "agent": "模式", "mcpServer": "MCP 伺服器" }, "sort": { diff --git a/src/shared/agents.ts b/src/shared/agents.ts new file mode 100644 index 0000000000..f62b1642d9 --- /dev/null +++ b/src/shared/agents.ts @@ -0,0 +1,450 @@ +import * as vscode from "vscode" + +import { + type GroupOptions, + type GroupEntry, + type AgentConfig, + type ModeConfig, + type CustomAgentPrompts, + type CustomModePrompts, + type ExperimentId, + type ToolGroup, + type PromptComponent, + DEFAULT_AGENTS, + DEFAULT_MODES, +} 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 Agent = string +export type Mode = Agent // Keep Mode as an alias for backward compatibility + +// 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() + + // 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 agents configuration as an ordered array +export const agents = DEFAULT_AGENTS +export const modes = agents // Keep modes as an alias for backward compatibility + +// Export the default agent slug +export const defaultAgentSlug = agents[0].slug +export const defaultModeSlug = defaultAgentSlug // Keep defaultModeSlug as an alias for backward compatibility + +// Helper functions +export function getAgentBySlug(slug: string, customAgents?: AgentConfig[]): AgentConfig | undefined { + // Check custom agents first + const customAgent = customAgents?.find((agent) => agent.slug === slug) + if (customAgent) { + return customAgent + } + // Then check built-in agents + return agents.find((agent) => agent.slug === slug) +} + +// Keep getModeBySlug as an alias for backward compatibility +export function getModeBySlug(slug: string, customModes?: ModeConfig[]): ModeConfig | undefined { + return getAgentBySlug(slug, customModes) +} + +export function getAgentConfig(slug: string, customAgents?: AgentConfig[]): AgentConfig { + const agent = getAgentBySlug(slug, customAgents) + if (!agent) { + throw new Error(`No agent found for slug: ${slug}`) + } + return agent +} + +// Keep getModeConfig as an alias for backward compatibility +export function getModeConfig(slug: string, customModes?: ModeConfig[]): ModeConfig { + return getAgentConfig(slug, customModes) +} + +// Get all available agents, with custom agents overriding built-in agents +export function getAllAgents(customAgents?: AgentConfig[]): AgentConfig[] { + if (!customAgents?.length) { + return [...agents] + } + + // Start with built-in agents + const allAgents = [...agents] + + // Process custom agents + customAgents.forEach((customAgent) => { + const index = allAgents.findIndex((agent) => agent.slug === customAgent.slug) + if (index !== -1) { + // Override existing agent + allAgents[index] = customAgent + } else { + // Add new agent + allAgents.push(customAgent) + } + }) + + return allAgents +} + +// Keep getAllModes as an alias for backward compatibility +export function getAllModes(customModes?: ModeConfig[]): ModeConfig[] { + return getAllAgents(customModes) +} + +// Check if an agent is custom or an override +export function isCustomAgent(slug: string, customAgents?: AgentConfig[]): boolean { + return !!customAgents?.some((agent) => agent.slug === slug) +} + +// Keep isCustomMode as an alias for backward compatibility +export function isCustomMode(slug: string, customModes?: ModeConfig[]): boolean { + return isCustomAgent(slug, customModes) +} + +/** + * Find an agent by its slug, don't fall back to built-in agents + */ +export function findAgentBySlug(slug: string, agents: readonly AgentConfig[] | undefined): AgentConfig | undefined { + return agents?.find((agent) => agent.slug === slug) +} + +// Keep findModeBySlug as an alias for backward compatibility +export function findModeBySlug(slug: string, modes: readonly ModeConfig[] | undefined): ModeConfig | undefined { + return findAgentBySlug(slug, modes) +} + +/** + * Get the agent selection based on the provided agent slug, prompt component, and custom agents. + * If a custom agent is found, it takes precedence over the built-in agents. + * If no custom agent is found, the built-in agent is used with partial merging from promptComponent. + * If neither is found, the default agent is used. + */ +export function getAgentSelection(agent: string, promptComponent?: PromptComponent, customAgents?: AgentConfig[]) { + const customAgent = findAgentBySlug(agent, customAgents) + const builtInAgent = findAgentBySlug(agent, agents) + + // If we have a custom agent, use it entirely + if (customAgent) { + return { + roleDefinition: customAgent.roleDefinition || "", + baseInstructions: customAgent.customInstructions || "", + description: customAgent.description || "", + } + } + + // Otherwise, use built-in agent as base and merge with promptComponent + const baseAgent = builtInAgent || agents[0] // fallback to default agent + + return { + roleDefinition: promptComponent?.roleDefinition || baseAgent.roleDefinition || "", + baseInstructions: promptComponent?.customInstructions || baseAgent.customInstructions || "", + description: baseAgent.description || "", + } +} + +// Keep getModeSelection as an alias for backward compatibility +export function getModeSelection(mode: string, promptComponent?: PromptComponent, customModes?: ModeConfig[]) { + return getAgentSelection(mode, promptComponent, customModes) +} + +// 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 isToolAllowedForAgent( + tool: string, + agentSlug: string, + customAgents: AgentConfig[], + toolRequirements?: Record, + toolParams?: Record, // All tool parameters + experiments?: Record, +): 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 agent = getAgentBySlug(agentSlug, customAgents) + if (!agent) { + return false + } + + // Check if tool is in any of the agent's groups and respects any group options + for (const group of agent.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(agent.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>/g) + if (filePathMatches) { + for (const match of filePathMatches) { + // More robust path extraction with validation + const pathMatch = match.match(/([^<]+)<\/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( + agent.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 +} + +// Keep isToolAllowedForMode as an alias for backward compatibility +export function isToolAllowedForMode( + tool: string, + modeSlug: string, + customModes: ModeConfig[], + toolRequirements?: Record, + toolParams?: Record, + experiments?: Record, +): boolean { + return isToolAllowedForAgent(tool, modeSlug, customModes, toolRequirements, toolParams, experiments) +} + +// Create the agent-specific default prompts +export const defaultPrompts: Readonly = Object.freeze( + Object.fromEntries( + agents.map((agent) => [ + agent.slug, + { + roleDefinition: agent.roleDefinition, + whenToUse: agent.whenToUse, + customInstructions: agent.customInstructions, + description: agent.description, + }, + ]), + ), +) + +// Helper function to get all agents with their prompt overrides from extension state +export async function getAllAgentsWithPrompts(context: vscode.ExtensionContext): Promise { + const customAgents = (await context.globalState.get("customModes")) || [] + const customAgentPrompts = (await context.globalState.get("customModePrompts")) || {} + + const allAgents = getAllAgents(customAgents) + return allAgents.map((agent) => ({ + ...agent, + roleDefinition: customAgentPrompts[agent.slug]?.roleDefinition ?? agent.roleDefinition, + whenToUse: customAgentPrompts[agent.slug]?.whenToUse ?? agent.whenToUse, + customInstructions: customAgentPrompts[agent.slug]?.customInstructions ?? agent.customInstructions, + // description is not overridable via customAgentPrompts, so we keep the original + })) +} + +// Keep getAllModesWithPrompts as an alias for backward compatibility +export async function getAllModesWithPrompts(context: vscode.ExtensionContext): Promise { + return getAllAgentsWithPrompts(context) +} + +// Helper function to get complete agent details with all overrides +export async function getFullAgentDetails( + agentSlug: string, + customAgents?: AgentConfig[], + customAgentPrompts?: CustomAgentPrompts, + options?: { + cwd?: string + globalCustomInstructions?: string + language?: string + }, +): Promise { + // First get the base agent config from custom agents or built-in agents + const baseAgent = getAgentBySlug(agentSlug, customAgents) || agents.find((a) => a.slug === agentSlug) || agents[0] + + // Check for any prompt component overrides + const promptComponent = customAgentPrompts?.[agentSlug] + + // Get the base custom instructions + const baseCustomInstructions = promptComponent?.customInstructions || baseAgent.customInstructions || "" + const baseWhenToUse = promptComponent?.whenToUse || baseAgent.whenToUse || "" + const baseDescription = promptComponent?.description || baseAgent.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, + agentSlug, + { language: options.language }, + ) + } + + // Return agent with any overrides applied + return { + ...baseAgent, + roleDefinition: promptComponent?.roleDefinition || baseAgent.roleDefinition, + whenToUse: baseWhenToUse, + description: baseDescription, + customInstructions: fullCustomInstructions, + } +} + +// Keep getFullModeDetails as an alias for backward compatibility +export async function getFullModeDetails( + modeSlug: string, + customModes?: ModeConfig[], + customModePrompts?: CustomModePrompts, + options?: { + cwd?: string + globalCustomInstructions?: string + language?: string + }, +): Promise { + return getFullAgentDetails(modeSlug, customModes, customModePrompts, options) +} + +// Helper function to safely get role definition +export function getRoleDefinition(agentSlug: string, customAgents?: AgentConfig[]): string { + const agent = getAgentBySlug(agentSlug, customAgents) + if (!agent) { + console.warn(`No agent found for slug: ${agentSlug}`) + return "" + } + return agent.roleDefinition +} + +// Helper function to safely get description +export function getDescription(agentSlug: string, customAgents?: AgentConfig[]): string { + const agent = getAgentBySlug(agentSlug, customAgents) + if (!agent) { + console.warn(`No agent found for slug: ${agentSlug}`) + return "" + } + return agent.description ?? "" +} + +// Helper function to safely get whenToUse +export function getWhenToUse(agentSlug: string, customAgents?: AgentConfig[]): string { + const agent = getAgentBySlug(agentSlug, customAgents) + if (!agent) { + console.warn(`No agent found for slug: ${agentSlug}`) + return "" + } + return agent.whenToUse ?? "" +} + +// Helper function to safely get custom instructions +export function getCustomInstructions(agentSlug: string, customAgents?: AgentConfig[]): string { + const agent = getAgentBySlug(agentSlug, customAgents) + if (!agent) { + console.warn(`No agent found for slug: ${agentSlug}`) + return "" + } + return agent.customInstructions ?? "" +} diff --git a/src/shared/modes.ts b/src/shared/modes.ts index f68d25c682..3fad558ffb 100644 --- a/src/shared/modes.ts +++ b/src/shared/modes.ts @@ -1,383 +1,2 @@ -import * as vscode from "vscode" - -import { - type GroupOptions, - type GroupEntry, - type ModeConfig, - type CustomModePrompts, - type ExperimentId, - type ToolGroup, - type PromptComponent, - DEFAULT_MODES, -} 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() - - // 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 = DEFAULT_MODES - -// 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, - toolParams?: Record, // All tool parameters - experiments?: Record, -): 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>/g) - if (filePathMatches) { - for (const match of filePathMatches) { - // More robust path extraction with validation - const pathMatch = match.match(/([^<]+)<\/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 = 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 { - const customModes = (await context.globalState.get("customModes")) || [] - const customModePrompts = (await context.globalState.get("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 { - // 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 ?? "" -} +// Backward compatibility export - re-export everything from agents.ts +export * from "./agents" diff --git a/webview-ui/src/components/agents/AgentsView.tsx b/webview-ui/src/components/agents/AgentsView.tsx new file mode 100644 index 0000000000..d102718c05 --- /dev/null +++ b/webview-ui/src/components/agents/AgentsView.tsx @@ -0,0 +1,1649 @@ +import React, { useState, useEffect, useMemo, useCallback, useRef } from "react" +import { + VSCodeCheckbox, + VSCodeRadioGroup, + VSCodeRadio, + VSCodeTextArea, + VSCodeLink, + VSCodeTextField, +} from "@vscode/webview-ui-toolkit/react" +import { Trans } from "react-i18next" +import { ChevronDown, X, Upload, Download } from "lucide-react" + +import { ModeConfig, GroupEntry, PromptComponent, ToolGroup, modeConfigSchema } from "@roo-code/types" + +import { + Mode, + getRoleDefinition, + getWhenToUse, + getDescription, + getCustomInstructions, + getAllModes, + findModeBySlug as findCustomModeBySlug, +} from "@roo/agents" +import { TOOL_GROUPS } from "@roo/tools" + +import { vscode } from "@src/utils/vscode" +import { buildDocLink } from "@src/utils/docLinks" +import { useAppTranslation } from "@src/i18n/TranslationContext" +import { useExtensionState } from "@src/context/ExtensionStateContext" +import { Tab, TabContent, TabHeader } from "@src/components/common/Tab" +import { + Button, + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, + Popover, + PopoverContent, + PopoverTrigger, + Command, + CommandInput, + CommandList, + CommandEmpty, + CommandItem, + CommandGroup, + Input, + StandardTooltip, +} from "@src/components/ui" +import { DeleteAgentDialog } from "@src/components/agents/DeleteAgentDialog" + +// Get all available groups that should show in prompts view +const availableGroups = (Object.keys(TOOL_GROUPS) as ToolGroup[]).filter((group) => !TOOL_GROUPS[group].alwaysAvailable) + +type ModeSource = "global" | "project" + +type AgentsViewProps = { + onDone: () => void +} + +// Helper to get group name regardless of format +function getGroupName(group: GroupEntry): ToolGroup { + return Array.isArray(group) ? group[0] : group +} + +const AgentsView = ({ onDone }: AgentsViewProps) => { + const { t } = useAppTranslation() + + const { + customModePrompts, + listApiConfigMeta, + currentApiConfigName, + mode, + customInstructions, + setCustomInstructions, + customModes, + } = useExtensionState() + + // Use a local state to track the visually active mode + // This prevents flickering when switching modes rapidly by: + // 1. Updating the UI immediately when a mode is clicked + // 2. Not syncing with the backend mode state (which would cause flickering) + // 3. Still sending the mode change to the backend for persistence + const [visualMode, setVisualMode] = useState(mode) + + // Memoize modes to preserve array order + const modes = useMemo(() => getAllModes(customModes), [customModes]) + + const [isDialogOpen, setIsDialogOpen] = useState(false) + const [selectedPromptContent, setSelectedPromptContent] = useState("") + const [selectedPromptTitle, setSelectedPromptTitle] = useState("") + const [isToolsEditMode, setIsToolsEditMode] = useState(false) + const [showConfigMenu, setShowConfigMenu] = useState(false) + const [isCreateModeDialogOpen, setIsCreateModeDialogOpen] = useState(false) + const [isSystemPromptDisclosureOpen, setIsSystemPromptDisclosureOpen] = useState(false) + const [isExporting, setIsExporting] = useState(false) + const [isImporting, setIsImporting] = useState(false) + const [showImportDialog, setShowImportDialog] = useState(false) + const [hasRulesToExport, setHasRulesToExport] = useState>({}) + const [showDeleteConfirm, setShowDeleteConfirm] = useState(false) + const [modeToDelete, setModeToDelete] = useState<{ + slug: string + name: string + source?: string + rulesFolderPath?: string + } | null>(null) + + // State for mode selection popover and search + const [open, setOpen] = useState(false) + const [searchValue, setSearchValue] = useState("") + const searchInputRef = useRef(null) + + // Local state for mode name input to allow visual emptying + const [localModeName, setLocalModeName] = useState("") + const [currentEditingModeSlug, setCurrentEditingModeSlug] = useState(null) + + // Direct update functions + const updateAgentPrompt = useCallback( + (mode: Mode, promptData: PromptComponent) => { + const existingPrompt = customModePrompts?.[mode] as PromptComponent + const updatedPrompt = { ...existingPrompt, ...promptData } + + // Only include properties that differ from defaults + if (updatedPrompt.roleDefinition === getRoleDefinition(mode)) { + delete updatedPrompt.roleDefinition + } + if (updatedPrompt.description === getDescription(mode)) { + delete updatedPrompt.description + } + if (updatedPrompt.whenToUse === getWhenToUse(mode)) { + delete updatedPrompt.whenToUse + } + + vscode.postMessage({ + type: "updatePrompt", + promptMode: mode, + customPrompt: updatedPrompt, + }) + }, + [customModePrompts], + ) + + const updateCustomMode = useCallback((slug: string, modeConfig: ModeConfig) => { + const source = modeConfig.source || "global" + + vscode.postMessage({ + type: "updateCustomMode", + slug, + modeConfig: { + ...modeConfig, + source, // Ensure source is set + }, + }) + }, []) + + // Helper function to find a mode by slug + const findModeBySlug = useCallback( + (searchSlug: string, modes: readonly ModeConfig[] | undefined): ModeConfig | undefined => { + return findCustomModeBySlug(searchSlug, modes) + }, + [], + ) + + const switchMode = useCallback((slug: string) => { + vscode.postMessage({ + type: "mode", + text: slug, + }) + }, []) + + // Handle mode switching with explicit state initialization + const handleModeSwitch = useCallback( + (modeConfig: ModeConfig) => { + if (modeConfig.slug === visualMode) return // Prevent unnecessary updates + + // Immediately update visual state for instant feedback + setVisualMode(modeConfig.slug) + + // Then send the mode change message to the backend + switchMode(modeConfig.slug) + + // Exit tools edit mode when switching modes + setIsToolsEditMode(false) + }, + [visualMode, switchMode], + ) + + // Handler for popover open state change + const onOpenChange = useCallback((open: boolean) => { + setOpen(open) + // Reset search when closing the popover + if (!open) { + setTimeout(() => setSearchValue(""), 100) + } + }, []) + + // Handler for clearing search input + const onClearSearch = useCallback(() => { + setSearchValue("") + searchInputRef.current?.focus() + }, []) + + // Helper function to get current mode's config + const getCurrentMode = useCallback((): ModeConfig | undefined => { + const findMode = (m: ModeConfig): boolean => m.slug === visualMode + return customModes?.find(findMode) || modes.find(findMode) + }, [visualMode, customModes, modes]) + + // Check if the current mode has rules to export + const checkRulesDirectory = useCallback((slug: string) => { + vscode.postMessage({ + type: "checkRulesDirectory", + slug: slug, + }) + }, []) + + // Check rules directory when mode changes + useEffect(() => { + const currentMode = getCurrentMode() + if (currentMode?.slug && hasRulesToExport[currentMode.slug] === undefined) { + checkRulesDirectory(currentMode.slug) + } + }, [getCurrentMode, checkRulesDirectory, hasRulesToExport]) + + // Reset local name state when mode changes + useEffect(() => { + if (currentEditingModeSlug && currentEditingModeSlug !== visualMode) { + setCurrentEditingModeSlug(null) + setLocalModeName("") + } + }, [visualMode, currentEditingModeSlug]) + + // Helper function to safely access mode properties + const getModeProperty = ( + mode: ModeConfig | undefined, + property: T, + ): ModeConfig[T] | undefined => { + return mode?.[property] + } + + // State for create mode dialog + const [newModeName, setNewModeName] = useState("") + const [newModeSlug, setNewModeSlug] = useState("") + const [newModeDescription, setNewModeDescription] = useState("") + const [newModeRoleDefinition, setNewModeRoleDefinition] = useState("") + const [newModeWhenToUse, setNewModeWhenToUse] = useState("") + const [newModeCustomInstructions, setNewModeCustomInstructions] = useState("") + const [newModeGroups, setNewModeGroups] = useState(availableGroups) + const [newModeSource, setNewModeSource] = useState("global") + + // Field-specific error states + const [nameError, setNameError] = useState("") + const [slugError, setSlugError] = useState("") + const [descriptionError, setDescriptionError] = useState("") + const [roleDefinitionError, setRoleDefinitionError] = useState("") + const [groupsError, setGroupsError] = useState("") + + // Helper to reset form state + const resetFormState = useCallback(() => { + // Reset form fields + setNewModeName("") + setNewModeSlug("") + setNewModeDescription("") + setNewModeGroups(availableGroups) + setNewModeRoleDefinition("") + setNewModeWhenToUse("") + setNewModeCustomInstructions("") + setNewModeSource("global") + // Reset error states + setNameError("") + setSlugError("") + setDescriptionError("") + setRoleDefinitionError("") + setGroupsError("") + }, []) + + // Reset form fields when dialog opens + useEffect(() => { + if (isCreateModeDialogOpen) { + resetFormState() + } + }, [isCreateModeDialogOpen, resetFormState]) + + // Helper function to generate a unique slug from a name + const generateSlug = useCallback((name: string, attempt = 0): string => { + const baseSlug = name + .toLowerCase() + .replace(/[^a-z0-9-]+/g, "-") + .replace(/^-+|-+$/g, "") + return attempt === 0 ? baseSlug : `${baseSlug}-${attempt}` + }, []) + + // Handler for name changes + const handleNameChange = useCallback( + (name: string) => { + setNewModeName(name) + setNewModeSlug(generateSlug(name)) + }, + [generateSlug], + ) + + const handleCreateMode = useCallback(() => { + // Clear previous errors + setNameError("") + setSlugError("") + setDescriptionError("") + setRoleDefinitionError("") + setGroupsError("") + + const source = newModeSource + const newMode: ModeConfig = { + slug: newModeSlug, + name: newModeName, + description: newModeDescription.trim() || undefined, + roleDefinition: newModeRoleDefinition.trim(), + whenToUse: newModeWhenToUse.trim() || undefined, + customInstructions: newModeCustomInstructions.trim() || undefined, + groups: newModeGroups, + source, + } + + // Validate the mode against the schema + const result = modeConfigSchema.safeParse(newMode) + + if (!result.success) { + // Map Zod errors to specific fields + result.error.errors.forEach((error) => { + const field = error.path[0] as string + const message = error.message + + switch (field) { + case "name": + setNameError(message) + break + case "slug": + setSlugError(message) + break + case "description": + setDescriptionError(message) + break + case "roleDefinition": + setRoleDefinitionError(message) + break + case "groups": + setGroupsError(message) + break + } + }) + return + } + + updateCustomMode(newModeSlug, newMode) + switchMode(newModeSlug) + setIsCreateModeDialogOpen(false) + resetFormState() + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ + newModeName, + newModeSlug, + newModeDescription, + newModeRoleDefinition, + newModeWhenToUse, // Add whenToUse dependency + newModeCustomInstructions, + newModeGroups, + newModeSource, + updateCustomMode, + ]) + + const isNameOrSlugTaken = useCallback( + (name: string, slug: string) => { + return modes.some((m) => m.slug === slug || m.name === name) + }, + [modes], + ) + + const openCreateModeDialog = useCallback(() => { + const baseNamePrefix = "New Custom Mode" + // Find unique name and slug + let attempt = 0 + let name = baseNamePrefix + let slug = generateSlug(name) + while (isNameOrSlugTaken(name, slug)) { + attempt++ + name = `${baseNamePrefix} ${attempt + 1}` + slug = generateSlug(name) + } + setNewModeName(name) + setNewModeSlug(slug) + setIsCreateModeDialogOpen(true) + }, [generateSlug, isNameOrSlugTaken]) + + // Handler for group checkbox changes + const handleGroupChange = useCallback( + (group: ToolGroup, isCustomMode: boolean, customMode: ModeConfig | undefined) => + (e: Event | React.FormEvent) => { + if (!isCustomMode) return // Prevent changes to built-in modes + const target = (e as CustomEvent)?.detail?.target || (e.target as HTMLInputElement) + const checked = target.checked + const oldGroups = customMode?.groups || [] + let newGroups: GroupEntry[] + if (checked) { + newGroups = [...oldGroups, group] + } else { + newGroups = oldGroups.filter((g) => getGroupName(g) !== group) + } + if (customMode) { + const source = customMode.source || "global" + + updateCustomMode(customMode.slug, { + ...customMode, + groups: newGroups, + source, + }) + } + }, + [updateCustomMode], + ) + + // Handle clicks outside the config menu + useEffect(() => { + const handleClickOutside = () => { + if (showConfigMenu) { + setShowConfigMenu(false) + } + } + + document.addEventListener("click", handleClickOutside) + return () => document.removeEventListener("click", handleClickOutside) + }, [showConfigMenu]) + + // Use a ref to store the current modeToDelete value + const modeToDeleteRef = useRef(modeToDelete) + + // Update the ref whenever modeToDelete changes + useEffect(() => { + modeToDeleteRef.current = modeToDelete + }, [modeToDelete]) + + useEffect(() => { + const handler = (event: MessageEvent) => { + const message = event.data + if (message.type === "systemPrompt") { + if (message.text) { + setSelectedPromptContent(message.text) + setSelectedPromptTitle(`System Prompt (${message.mode} mode)`) + setIsDialogOpen(true) + } + } else if (message.type === "exportModeResult") { + setIsExporting(false) + + if (!message.success) { + // Show error message + console.error("Failed to export mode:", message.error) + } + } else if (message.type === "importModeResult") { + setIsImporting(false) + setShowImportDialog(false) + + if (!message.success) { + // Only log error if it's not a cancellation + if (message.error !== "cancelled") { + console.error("Failed to import mode:", message.error) + } + } + } else if (message.type === "checkRulesDirectoryResult") { + setHasRulesToExport((prev) => ({ + ...prev, + [message.slug]: message.hasContent, + })) + } else if (message.type === "deleteCustomModeCheck") { + // Handle the check response + // Use the ref to get the current modeToDelete value + const currentModeToDelete = modeToDeleteRef.current + if (message.slug && currentModeToDelete && currentModeToDelete.slug === message.slug) { + setModeToDelete({ + ...currentModeToDelete, + rulesFolderPath: message.rulesFolderPath, + }) + setShowDeleteConfirm(true) + } + } + } + + window.addEventListener("message", handler) + return () => window.removeEventListener("message", handler) + }, []) // Empty dependency array - only register once + + const handleAgentReset = ( + modeSlug: string, + type: "roleDefinition" | "description" | "whenToUse" | "customInstructions", + ) => { + // Only reset for built-in modes + const existingPrompt = customModePrompts?.[modeSlug] as PromptComponent + const updatedPrompt = { ...existingPrompt } + delete updatedPrompt[type] // Remove the field entirely to ensure it reloads from defaults + + vscode.postMessage({ + type: "updatePrompt", + promptMode: modeSlug, + customPrompt: updatedPrompt, + }) + } + + return ( + + +

{t("prompts:title")}

+ +
+ + +
+
e.stopPropagation()} className="flex justify-between items-center mb-3"> +

{t("prompts:modes.title")}

+
+ + + +
+ + + + {showConfigMenu && ( +
e.stopPropagation()} + onMouseDown={(e) => e.stopPropagation()} + className="absolute top-full right-0 w-[200px] mt-1 bg-vscode-editor-background border border-vscode-input-border rounded shadow-md z-[1000]"> +
{ + e.preventDefault() // Prevent blur + vscode.postMessage({ + type: "openCustomModesSettings", + }) + setShowConfigMenu(false) + }} + onClick={(e) => e.preventDefault()}> + {t("prompts:modes.editGlobalModes")} +
+
{ + e.preventDefault() // Prevent blur + vscode.postMessage({ + type: "openFile", + text: "./.roomodes", + values: { + create: true, + content: JSON.stringify({ customModes: [] }, null, 2), + }, + }) + setShowConfigMenu(false) + }} + onClick={(e) => e.preventDefault()}> + {t("prompts:modes.editProjectModes")} +
+
+ )} +
+ + + +
+
+ +
+ + + + +
+ +
+ + + + + + +
+ + {searchValue.length > 0 && ( +
+ +
+ )} +
+ + + {searchValue && ( +
+ {t("prompts:modes.noMatchFound")} +
+ )} +
+ + {modes + .filter((modeConfig) => + searchValue + ? modeConfig.name + .toLowerCase() + .includes(searchValue.toLowerCase()) + : true, + ) + .map((modeConfig) => ( + { + handleModeSwitch(modeConfig) + setOpen(false) + }} + data-testid={`mode-option-${modeConfig.slug}`}> +
+ + {modeConfig.name} + + + {modeConfig.slug} + +
+
+ ))} +
+
+
+
+
+
+ {/* API Configuration - Moved Here */} +
+
{t("prompts:apiConfiguration.title")}
+
+ {t("prompts:apiConfiguration.select")} +
+
+ +
+
+
+ + {/* Name section */} +
+ {/* Only show name and delete for custom modes */} + {visualMode && findModeBySlug(visualMode, customModes) && ( +
+
+
{t("prompts:createModeDialog.name.label")}
+
+ { + const customMode = findModeBySlug(visualMode, customModes) + if (customMode) { + setCurrentEditingModeSlug(visualMode) + setLocalModeName(customMode.name) + } + }} + onChange={(e) => { + setLocalModeName(e.target.value) + }} + onBlur={() => { + const customMode = findModeBySlug(visualMode, customModes) + if (customMode && localModeName.trim()) { + // Only update if the name is not empty + updateCustomMode(visualMode, { + ...customMode, + name: localModeName, + source: customMode.source || "global", + }) + } + // Clear the editing state + setCurrentEditingModeSlug(null) + }} + className="w-full" + /> + + + +
+
+
+ )} + + {/* Role Definition section */} +
+
+
{t("prompts:roleDefinition.title")}
+ {!findModeBySlug(visualMode, customModes) && ( + + + + )} +
+
+ {t("prompts:roleDefinition.description")} +
+ { + const customMode = findModeBySlug(visualMode, customModes) + const prompt = customModePrompts?.[visualMode] as PromptComponent + return ( + customMode?.roleDefinition ?? + prompt?.roleDefinition ?? + getRoleDefinition(visualMode) + ) + })()} + onChange={(e) => { + const value = + (e as unknown as CustomEvent)?.detail?.target?.value || + ((e as any).target as HTMLTextAreaElement).value + const customMode = findModeBySlug(visualMode, customModes) + if (customMode) { + // For custom modes, update the JSON file + updateCustomMode(visualMode, { + ...customMode, + roleDefinition: value.trim() || "", + source: customMode.source || "global", + }) + } else { + // For built-in modes, update the prompts + updateAgentPrompt(visualMode, { + roleDefinition: value.trim() || undefined, + }) + } + }} + className="w-full" + rows={5} + data-testid={`${getCurrentMode()?.slug || "code"}-prompt-textarea`} + /> +
+ + {/* Description section */} +
+
+
{t("prompts:description.title")}
+ {!findModeBySlug(visualMode, customModes) && ( + + + + )} +
+
+ {t("prompts:description.description")} +
+ { + const customMode = findModeBySlug(visualMode, customModes) + const prompt = customModePrompts?.[visualMode] as PromptComponent + return customMode?.description ?? prompt?.description ?? getDescription(visualMode) + })()} + onChange={(e) => { + const value = + (e as unknown as CustomEvent)?.detail?.target?.value || + ((e as any).target as HTMLTextAreaElement).value + const customMode = findModeBySlug(visualMode, customModes) + if (customMode) { + // For custom modes, update the JSON file + updateCustomMode(visualMode, { + ...customMode, + description: value.trim() || undefined, + source: customMode.source || "global", + }) + } else { + // For built-in modes, update the prompts + updateAgentPrompt(visualMode, { + description: value.trim() || undefined, + }) + } + }} + className="w-full" + data-testid={`${getCurrentMode()?.slug || "code"}-description-textfield`} + /> +
+ + {/* When to Use section */} +
+
+
{t("prompts:whenToUse.title")}
+ {!findModeBySlug(visualMode, customModes) && ( + + + + )} +
+
+ {t("prompts:whenToUse.description")} +
+ { + const customMode = findModeBySlug(visualMode, customModes) + const prompt = customModePrompts?.[visualMode] as PromptComponent + return customMode?.whenToUse ?? prompt?.whenToUse ?? getWhenToUse(visualMode) + })()} + onChange={(e) => { + const value = + (e as unknown as CustomEvent)?.detail?.target?.value || + ((e as any).target as HTMLTextAreaElement).value + const customMode = findModeBySlug(visualMode, customModes) + if (customMode) { + // For custom modes, update the JSON file + updateCustomMode(visualMode, { + ...customMode, + whenToUse: value.trim() || undefined, + source: customMode.source || "global", + }) + } else { + // For built-in modes, update the prompts + updateAgentPrompt(visualMode, { + whenToUse: value.trim() || undefined, + }) + } + }} + className="w-full" + rows={4} + data-testid={`${getCurrentMode()?.slug || "code"}-when-to-use-textarea`} + /> +
+ + {/* Mode settings */} + <> + {/* Show tools for all modes */} +
+
+
{t("prompts:tools.title")}
+ {findModeBySlug(visualMode, customModes) && ( + + + + )} +
+ {!findModeBySlug(visualMode, customModes) && ( +
+ {t("prompts:tools.builtInModesText")} +
+ )} + {isToolsEditMode && findModeBySlug(visualMode, customModes) ? ( +
+ {availableGroups.map((group) => { + const currentMode = getCurrentMode() + const isCustomMode = findModeBySlug(visualMode, customModes) + const customMode = isCustomMode + const isGroupEnabled = isCustomMode + ? customMode?.groups?.some((g) => getGroupName(g) === group) + : currentMode?.groups?.some((g) => getGroupName(g) === group) + + return ( + + {t(`prompts:tools.toolNames.${group}`)} + {group === "edit" && ( +
+ {t("prompts:tools.allowedFiles")}{" "} + {(() => { + const currentMode = getCurrentMode() + const editGroup = currentMode?.groups?.find( + (g) => + Array.isArray(g) && + g[0] === "edit" && + g[1]?.fileRegex, + ) + if (!Array.isArray(editGroup)) return t("prompts:allFiles") + return ( + editGroup[1].description || + `/${editGroup[1].fileRegex}/` + ) + })()} +
+ )} +
+ ) + })} +
+ ) : ( +
+ {(() => { + const currentMode = getCurrentMode() + const enabledGroups = currentMode?.groups || [] + + // If there are no enabled groups, display translated "None" + if (enabledGroups.length === 0) { + return t("prompts:tools.noTools") + } + + return enabledGroups + .map((group) => { + const groupName = getGroupName(group) + const displayName = t(`prompts:tools.toolNames.${groupName}`) + if (Array.isArray(group) && group[1]?.fileRegex) { + const description = + group[1].description || `/${group[1].fileRegex}/` + return `${displayName} (${description})` + } + return displayName + }) + .join(", ") + })()} +
+ )} +
+ + + {/* Role definition for both built-in and custom modes */} +
+
+
{t("prompts:customInstructions.title")}
+ {!findModeBySlug(visualMode, customModes) && ( + + + + )} +
+
+ {t("prompts:customInstructions.description", { + modeName: getCurrentMode()?.name || "Code", + })} +
+ { + const customMode = findModeBySlug(visualMode, customModes) + const prompt = customModePrompts?.[visualMode] as PromptComponent + return ( + customMode?.customInstructions ?? + prompt?.customInstructions ?? + getCustomInstructions(mode, customModes) + ) + })()} + onChange={(e) => { + const value = + (e as unknown as CustomEvent)?.detail?.target?.value || + ((e as any).target as HTMLTextAreaElement).value + const customMode = findModeBySlug(visualMode, customModes) + if (customMode) { + // For custom modes, update the JSON file + updateCustomMode(visualMode, { + ...customMode, + customInstructions: value.trim() || undefined, + source: customMode.source || "global", + }) + } else { + // For built-in modes, update the prompts + const existingPrompt = customModePrompts?.[visualMode] as PromptComponent + updateAgentPrompt(visualMode, { + ...existingPrompt, + customInstructions: value.trim(), + }) + } + }} + rows={10} + className="w-full" + data-testid={`${getCurrentMode()?.slug || "code"}-custom-instructions-textarea`} + /> +
+ { + const currentMode = getCurrentMode() + if (!currentMode) return + + // Open or create an empty file + vscode.postMessage({ + type: "openFile", + text: `./.roo/rules-${currentMode.slug}/rules.md`, + values: { + create: true, + content: "", + }, + }) + }} + /> + ), + }} + /> +
+
+
+ +
+
+ + + + +
+ + {/* Export/Import Mode Buttons */} +
+ {/* Export button - visible when any mode is selected */} + {getCurrentMode() && ( + + )} + {/* Import button - always visible */} + +
+ + {/* Advanced Features Disclosure */} +
+ + + {isSystemPromptDisclosureOpen && ( +
+ {/* Override System Prompt Section */} +
+

+ Override System Prompt +

+
+ { + const currentMode = getCurrentMode() + if (!currentMode) return + + vscode.postMessage({ + type: "openFile", + text: `./.roo/system-prompt-${currentMode.slug}`, + values: { + create: true, + content: "", + }, + }) + }} + /> + ), + "1": ( + + ), + "2": , + }} + /> +
+
+
+ )} +
+
+ +
+

{t("prompts:globalCustomInstructions.title")}

+ +
+ + + +
+ { + const value = + (e as unknown as CustomEvent)?.detail?.target?.value || + ((e as any).target as HTMLTextAreaElement).value + setCustomInstructions(value || undefined) + vscode.postMessage({ + type: "customInstructions", + text: value.trim() || undefined, + }) + }} + rows={4} + className="w-full" + data-testid="global-custom-instructions-textarea" + /> +
+ + vscode.postMessage({ + type: "openFile", + text: "./.roo/rules/rules.md", + values: { + create: true, + content: "", + }, + }) + } + /> + ), + }} + /> +
+
+
+ + {isCreateModeDialogOpen && ( +
+
+
+ +

{t("prompts:createModeDialog.title")}

+
+
{t("prompts:createModeDialog.name.label")}
+ { + handleNameChange(e.target.value) + }} + className="w-full" + /> + {nameError && ( +
{nameError}
+ )} +
+
+
{t("prompts:createModeDialog.slug.label")}
+ { + setNewModeSlug(e.target.value) + }} + className="w-full" + /> +
+ {t("prompts:createModeDialog.slug.description")} +
+ {slugError && ( +
{slugError}
+ )} +
+
+
{t("prompts:createModeDialog.saveLocation.label")}
+
+ {t("prompts:createModeDialog.saveLocation.description")} +
+ ) => { + const target = ((e as CustomEvent)?.detail?.target || + (e.target as HTMLInputElement)) as HTMLInputElement + setNewModeSource(target.value as ModeSource) + }}> + + {t("prompts:createModeDialog.saveLocation.global.label")} +
+ {t("prompts:createModeDialog.saveLocation.global.description")} +
+
+ + {t("prompts:createModeDialog.saveLocation.project.label")} +
+ {t("prompts:createModeDialog.saveLocation.project.description")} +
+
+
+
+ +
+
+ {t("prompts:createModeDialog.roleDefinition.label")} +
+
+ {t("prompts:createModeDialog.roleDefinition.description")} +
+ { + setNewModeRoleDefinition((e.target as HTMLTextAreaElement).value) + }} + rows={4} + className="w-full" + /> + {roleDefinitionError && ( +
+ {roleDefinitionError} +
+ )} +
+ +
+
{t("prompts:createModeDialog.description.label")}
+
+ {t("prompts:createModeDialog.description.description")} +
+ { + setNewModeDescription((e.target as HTMLInputElement).value) + }} + className="w-full" + /> + {descriptionError && ( +
{descriptionError}
+ )} +
+ +
+
{t("prompts:createModeDialog.whenToUse.label")}
+
+ {t("prompts:createModeDialog.whenToUse.description")} +
+ { + setNewModeWhenToUse((e.target as HTMLTextAreaElement).value) + }} + rows={3} + className="w-full" + /> +
+
+
{t("prompts:createModeDialog.tools.label")}
+
+ {t("prompts:createModeDialog.tools.description")} +
+
+ {availableGroups.map((group) => ( + getGroupName(g) === group)} + onChange={(e: Event | React.FormEvent) => { + const target = + (e as CustomEvent)?.detail?.target || (e.target as HTMLInputElement) + const checked = target.checked + if (checked) { + setNewModeGroups([...newModeGroups, group]) + } else { + setNewModeGroups( + newModeGroups.filter((g) => getGroupName(g) !== group), + ) + } + }}> + {t(`prompts:tools.toolNames.${group}`)} + + ))} +
+ {groupsError && ( +
{groupsError}
+ )} +
+
+
+ {t("prompts:createModeDialog.customInstructions.label")} +
+
+ {t("prompts:createModeDialog.customInstructions.description")} +
+ { + setNewModeCustomInstructions((e.target as HTMLTextAreaElement).value) + }} + rows={4} + className="w-full" + /> +
+
+
+ + +
+
+
+ )} + + {isDialogOpen && ( +
+
+
+ +

+ {selectedPromptTitle || + t("prompts:systemPrompt.title", { + modeName: getCurrentMode()?.name || "Code", + })} +

+
+								{selectedPromptContent}
+							
+
+
+ +
+
+
+ )} + + {/* Import Mode Dialog */} + {showImportDialog && ( +
+
+

{t("prompts:modes.importMode")}

+

+ {t("prompts:importMode.selectLevel")} +

+
+ + +
+
+ + +
+
+
+ )} + + {/* Delete Mode Confirmation Dialog */} + { + if (modeToDelete) { + vscode.postMessage({ + type: "deleteCustomMode", + slug: modeToDelete.slug, + }) + setShowDeleteConfirm(false) + setModeToDelete(null) + } + }} + /> +
+ ) +} + +export default AgentsView diff --git a/webview-ui/src/components/agents/DeleteAgentDialog.tsx b/webview-ui/src/components/agents/DeleteAgentDialog.tsx new file mode 100644 index 0000000000..b2ace1079f --- /dev/null +++ b/webview-ui/src/components/agents/DeleteAgentDialog.tsx @@ -0,0 +1,61 @@ +import React from "react" +import { useAppTranslation } from "@src/i18n/TranslationContext" +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@src/components/ui" + +interface DeleteAgentDialogProps { + open: boolean + onOpenChange: (open: boolean) => void + modeToDelete: { + slug: string + name: string + source?: string + rulesFolderPath?: string + } | null + onConfirm: () => void +} + +export const DeleteAgentDialog: React.FC = ({ + open, + onOpenChange, + modeToDelete, + onConfirm, +}) => { + const { t } = useAppTranslation() + + return ( + + + + {t("prompts:deleteMode.title")} + + {modeToDelete && ( + <> + {t("prompts:deleteMode.message", { modeName: modeToDelete.name })} + {modeToDelete.rulesFolderPath && ( +
+ {t("prompts:deleteMode.rulesFolder", { + folderPath: modeToDelete.rulesFolderPath, + })} +
+ )} + + )} +
+
+ + {t("prompts:deleteMode.cancel")} + {t("prompts:deleteMode.confirm")} + +
+
+ ) +} diff --git a/webview-ui/src/components/modes/__tests__/ModesView.spec.tsx b/webview-ui/src/components/agents/__tests__/AgentsView.spec.tsx similarity index 95% rename from webview-ui/src/components/modes/__tests__/ModesView.spec.tsx rename to webview-ui/src/components/agents/__tests__/AgentsView.spec.tsx index e202114bbb..f406d288a0 100644 --- a/webview-ui/src/components/modes/__tests__/ModesView.spec.tsx +++ b/webview-ui/src/components/agents/__tests__/AgentsView.spec.tsx @@ -1,7 +1,7 @@ -// npx vitest src/components/modes/__tests__/ModesView.spec.tsx +// npx vitest src/components/agents/__tests__/AgentsView.spec.tsx import { render, screen, fireEvent, waitFor } from "@/utils/test-utils" -import ModesView from "../ModesView" +import AgentsView from "../AgentsView" import { ExtensionStateContext } from "@src/context/ExtensionStateContext" import { vscode } from "@src/utils/vscode" @@ -32,7 +32,7 @@ const renderPromptsView = (props = {}) => { const mockOnDone = vitest.fn() return render( - + , ) } @@ -128,7 +128,7 @@ describe("PromptsView", () => { const { unmount } = render( - + , ) @@ -154,7 +154,7 @@ describe("PromptsView", () => { render( - + , ) @@ -175,7 +175,7 @@ describe("PromptsView", () => { const { unmount } = render( - + , ) @@ -190,7 +190,7 @@ describe("PromptsView", () => { render( - + , ) diff --git a/webview-ui/src/components/chat/AgentSelector.tsx b/webview-ui/src/components/chat/AgentSelector.tsx new file mode 100644 index 0000000000..b3ab50bf6a --- /dev/null +++ b/webview-ui/src/components/chat/AgentSelector.tsx @@ -0,0 +1,304 @@ +import React from "react" +import { ChevronUp, Check, X } from "lucide-react" +import { cn } from "@/lib/utils" +import { useRooPortal } from "@/components/ui/hooks/useRooPortal" +import { Popover, PopoverContent, PopoverTrigger, StandardTooltip } from "@/components/ui" +import { IconButton } from "./IconButton" +import { vscode } from "@/utils/vscode" +import { useExtensionState } from "@/context/ExtensionStateContext" +import { useAppTranslation } from "@/i18n/TranslationContext" +import { Mode, getAllModes } from "@roo/agents" +import { ModeConfig, CustomModePrompts } from "@roo-code/types" +import { telemetryClient } from "@/utils/TelemetryClient" +import { TelemetryEventName } from "@roo-code/types" +import { Fzf } from "fzf" + +// Minimum number of modes required to show search functionality +const SEARCH_THRESHOLD = 6 + +interface AgentSelectorProps { + value: Mode + onChange: (value: Mode) => void + disabled?: boolean + title?: string + triggerClassName?: string + modeShortcutText: string + customModes?: ModeConfig[] + customModePrompts?: CustomModePrompts + disableSearch?: boolean +} + +export const AgentSelector = ({ + value, + onChange, + disabled = false, + title = "", + triggerClassName = "", + modeShortcutText, + customModes, + customModePrompts, + disableSearch = false, +}: AgentSelectorProps) => { + const [open, setOpen] = React.useState(false) + const [searchValue, setSearchValue] = React.useState("") + const searchInputRef = React.useRef(null) + const portalContainer = useRooPortal("roo-portal") + const { hasOpenedModeSelector, setHasOpenedModeSelector } = useExtensionState() + const { t } = useAppTranslation() + + const trackModeSelectorOpened = React.useCallback(() => { + // Track telemetry every time the mode selector is opened + telemetryClient.capture(TelemetryEventName.MODE_SELECTOR_OPENED) + + // Track first-time usage for UI purposes + if (!hasOpenedModeSelector) { + setHasOpenedModeSelector(true) + vscode.postMessage({ type: "hasOpenedModeSelector", bool: true }) + } + }, [hasOpenedModeSelector, setHasOpenedModeSelector]) + + // Get all modes including custom modes and merge custom prompt descriptions + const modes = React.useMemo(() => { + const allModes = getAllModes(customModes) + return allModes.map((mode) => ({ + ...mode, + description: customModePrompts?.[mode.slug]?.description ?? mode.description, + })) + }, [customModes, customModePrompts]) + + // Find the selected mode + const selectedMode = React.useMemo(() => modes.find((mode) => mode.slug === value), [modes, value]) + + // Memoize searchable items for fuzzy search with separate name and description search + const nameSearchItems = React.useMemo(() => { + return modes.map((mode) => ({ + original: mode, + searchStr: [mode.name, mode.slug].filter(Boolean).join(" "), + })) + }, [modes]) + + const descriptionSearchItems = React.useMemo(() => { + return modes.map((mode) => ({ + original: mode, + searchStr: mode.description || "", + })) + }, [modes]) + + // Create memoized Fzf instances for name and description searches + const nameFzfInstance = React.useMemo(() => { + return new Fzf(nameSearchItems, { + selector: (item) => item.searchStr, + }) + }, [nameSearchItems]) + + const descriptionFzfInstance = React.useMemo(() => { + return new Fzf(descriptionSearchItems, { + selector: (item) => item.searchStr, + }) + }, [descriptionSearchItems]) + + // Filter modes based on search value using fuzzy search with priority + const filteredModes = React.useMemo(() => { + if (!searchValue) return modes + + // First search in names/slugs + const nameMatches = nameFzfInstance.find(searchValue) + const nameMatchedModes = new Set(nameMatches.map((result) => result.item.original.slug)) + + // Then search in descriptions + const descriptionMatches = descriptionFzfInstance.find(searchValue) + + // Combine results: name matches first, then description matches + const combinedResults = [ + ...nameMatches.map((result) => result.item.original), + ...descriptionMatches + .filter((result) => !nameMatchedModes.has(result.item.original.slug)) + .map((result) => result.item.original), + ] + + return combinedResults + }, [modes, searchValue, nameFzfInstance, descriptionFzfInstance]) + + const onClearSearch = React.useCallback(() => { + setSearchValue("") + searchInputRef.current?.focus() + }, []) + + const handleSelect = React.useCallback( + (modeSlug: string) => { + onChange(modeSlug as Mode) + setOpen(false) + // Clear search after selection + setSearchValue("") + }, + [onChange], + ) + + const onOpenChange = React.useCallback( + (isOpen: boolean) => { + if (isOpen) trackModeSelectorOpened() + setOpen(isOpen) + // Clear search when closing + if (!isOpen) { + setSearchValue("") + } + }, + [trackModeSelectorOpened], + ) + + // Auto-focus search input when popover opens + React.useEffect(() => { + if (open && searchInputRef.current) { + searchInputRef.current.focus() + } + }, [open]) + + // Determine if search should be shown + const showSearch = !disableSearch && modes.length > SEARCH_THRESHOLD + + // Combine instruction text for tooltip + const instructionText = `${t("chat:modeSelector.description")} ${modeShortcutText}` + + const trigger = ( + + + {selectedMode?.name || ""} + + ) + + return ( + + {title ? {trigger} : trigger} + + +
+ {/* Show search bar only when there are more than SEARCH_THRESHOLD items, otherwise show info blurb */} + {showSearch ? ( +
+ setSearchValue(e.target.value)} + placeholder={t("chat:modeSelector.searchPlaceholder")} + className="w-full h-8 px-2 py-1 text-xs bg-vscode-input-background text-vscode-input-foreground border border-vscode-input-border rounded focus:outline-0" + data-testid="mode-search-input" + /> + {searchValue.length > 0 && ( +
+ +
+ )} +
+ ) : ( +
+

{instructionText}

+
+ )} + + {/* Mode List */} +
+ {filteredModes.length === 0 && searchValue ? ( +
+ {t("chat:modeSelector.noResults")} +
+ ) : ( +
+ {filteredModes.map((mode) => ( +
handleSelect(mode.slug)} + className={cn( + "px-3 py-1.5 text-sm cursor-pointer flex items-center", + "hover:bg-vscode-list-hoverBackground", + mode.slug === value + ? "bg-vscode-list-activeSelectionBackground text-vscode-list-activeSelectionForeground" + : "", + )} + data-testid="mode-selector-item"> +
+
{mode.name}
+ {mode.description && ( +
+ {mode.description} +
+ )} +
+ {mode.slug === value && } +
+ ))} +
+ )} +
+ + {/* Bottom bar with buttons on left and title on right */} +
+
+ { + window.postMessage( + { + type: "action", + action: "marketplaceButtonClicked", + values: { marketplaceTab: "mode" }, + }, + "*", + ) + setOpen(false) + }} + /> + { + vscode.postMessage({ + type: "switchTab", + tab: "modes", + }) + setOpen(false) + }} + /> +
+ + {/* Info icon and title on the right - only show info icon when search bar is visible */} +
+ {showSearch && ( + + + + )} +

+ {t("chat:modeSelector.title")} +

+
+
+
+
+
+ ) +} + +export default AgentSelector diff --git a/webview-ui/src/components/chat/ModeSelector.tsx b/webview-ui/src/components/chat/ModeSelector.tsx index 93dd2f1f4f..7601fcccb4 100644 --- a/webview-ui/src/components/chat/ModeSelector.tsx +++ b/webview-ui/src/components/chat/ModeSelector.tsx @@ -1,304 +1,3 @@ -import React from "react" -import { ChevronUp, Check, X } from "lucide-react" -import { cn } from "@/lib/utils" -import { useRooPortal } from "@/components/ui/hooks/useRooPortal" -import { Popover, PopoverContent, PopoverTrigger, StandardTooltip } from "@/components/ui" -import { IconButton } from "./IconButton" -import { vscode } from "@/utils/vscode" -import { useExtensionState } from "@/context/ExtensionStateContext" -import { useAppTranslation } from "@/i18n/TranslationContext" -import { Mode, getAllModes } from "@roo/modes" -import { ModeConfig, CustomModePrompts } from "@roo-code/types" -import { telemetryClient } from "@/utils/TelemetryClient" -import { TelemetryEventName } from "@roo-code/types" -import { Fzf } from "fzf" - -// Minimum number of modes required to show search functionality -const SEARCH_THRESHOLD = 6 - -interface ModeSelectorProps { - value: Mode - onChange: (value: Mode) => void - disabled?: boolean - title?: string - triggerClassName?: string - modeShortcutText: string - customModes?: ModeConfig[] - customModePrompts?: CustomModePrompts - disableSearch?: boolean -} - -export const ModeSelector = ({ - value, - onChange, - disabled = false, - title = "", - triggerClassName = "", - modeShortcutText, - customModes, - customModePrompts, - disableSearch = false, -}: ModeSelectorProps) => { - const [open, setOpen] = React.useState(false) - const [searchValue, setSearchValue] = React.useState("") - const searchInputRef = React.useRef(null) - const portalContainer = useRooPortal("roo-portal") - const { hasOpenedModeSelector, setHasOpenedModeSelector } = useExtensionState() - const { t } = useAppTranslation() - - const trackModeSelectorOpened = React.useCallback(() => { - // Track telemetry every time the mode selector is opened - telemetryClient.capture(TelemetryEventName.MODE_SELECTOR_OPENED) - - // Track first-time usage for UI purposes - if (!hasOpenedModeSelector) { - setHasOpenedModeSelector(true) - vscode.postMessage({ type: "hasOpenedModeSelector", bool: true }) - } - }, [hasOpenedModeSelector, setHasOpenedModeSelector]) - - // Get all modes including custom modes and merge custom prompt descriptions - const modes = React.useMemo(() => { - const allModes = getAllModes(customModes) - return allModes.map((mode) => ({ - ...mode, - description: customModePrompts?.[mode.slug]?.description ?? mode.description, - })) - }, [customModes, customModePrompts]) - - // Find the selected mode - const selectedMode = React.useMemo(() => modes.find((mode) => mode.slug === value), [modes, value]) - - // Memoize searchable items for fuzzy search with separate name and description search - const nameSearchItems = React.useMemo(() => { - return modes.map((mode) => ({ - original: mode, - searchStr: [mode.name, mode.slug].filter(Boolean).join(" "), - })) - }, [modes]) - - const descriptionSearchItems = React.useMemo(() => { - return modes.map((mode) => ({ - original: mode, - searchStr: mode.description || "", - })) - }, [modes]) - - // Create memoized Fzf instances for name and description searches - const nameFzfInstance = React.useMemo(() => { - return new Fzf(nameSearchItems, { - selector: (item) => item.searchStr, - }) - }, [nameSearchItems]) - - const descriptionFzfInstance = React.useMemo(() => { - return new Fzf(descriptionSearchItems, { - selector: (item) => item.searchStr, - }) - }, [descriptionSearchItems]) - - // Filter modes based on search value using fuzzy search with priority - const filteredModes = React.useMemo(() => { - if (!searchValue) return modes - - // First search in names/slugs - const nameMatches = nameFzfInstance.find(searchValue) - const nameMatchedModes = new Set(nameMatches.map((result) => result.item.original.slug)) - - // Then search in descriptions - const descriptionMatches = descriptionFzfInstance.find(searchValue) - - // Combine results: name matches first, then description matches - const combinedResults = [ - ...nameMatches.map((result) => result.item.original), - ...descriptionMatches - .filter((result) => !nameMatchedModes.has(result.item.original.slug)) - .map((result) => result.item.original), - ] - - return combinedResults - }, [modes, searchValue, nameFzfInstance, descriptionFzfInstance]) - - const onClearSearch = React.useCallback(() => { - setSearchValue("") - searchInputRef.current?.focus() - }, []) - - const handleSelect = React.useCallback( - (modeSlug: string) => { - onChange(modeSlug as Mode) - setOpen(false) - // Clear search after selection - setSearchValue("") - }, - [onChange], - ) - - const onOpenChange = React.useCallback( - (isOpen: boolean) => { - if (isOpen) trackModeSelectorOpened() - setOpen(isOpen) - // Clear search when closing - if (!isOpen) { - setSearchValue("") - } - }, - [trackModeSelectorOpened], - ) - - // Auto-focus search input when popover opens - React.useEffect(() => { - if (open && searchInputRef.current) { - searchInputRef.current.focus() - } - }, [open]) - - // Determine if search should be shown - const showSearch = !disableSearch && modes.length > SEARCH_THRESHOLD - - // Combine instruction text for tooltip - const instructionText = `${t("chat:modeSelector.description")} ${modeShortcutText}` - - const trigger = ( - - - {selectedMode?.name || ""} - - ) - - return ( - - {title ? {trigger} : trigger} - - -
- {/* Show search bar only when there are more than SEARCH_THRESHOLD items, otherwise show info blurb */} - {showSearch ? ( -
- setSearchValue(e.target.value)} - placeholder={t("chat:modeSelector.searchPlaceholder")} - className="w-full h-8 px-2 py-1 text-xs bg-vscode-input-background text-vscode-input-foreground border border-vscode-input-border rounded focus:outline-0" - data-testid="mode-search-input" - /> - {searchValue.length > 0 && ( -
- -
- )} -
- ) : ( -
-

{instructionText}

-
- )} - - {/* Mode List */} -
- {filteredModes.length === 0 && searchValue ? ( -
- {t("chat:modeSelector.noResults")} -
- ) : ( -
- {filteredModes.map((mode) => ( -
handleSelect(mode.slug)} - className={cn( - "px-3 py-1.5 text-sm cursor-pointer flex items-center", - "hover:bg-vscode-list-hoverBackground", - mode.slug === value - ? "bg-vscode-list-activeSelectionBackground text-vscode-list-activeSelectionForeground" - : "", - )} - data-testid="mode-selector-item"> -
-
{mode.name}
- {mode.description && ( -
- {mode.description} -
- )} -
- {mode.slug === value && } -
- ))} -
- )} -
- - {/* Bottom bar with buttons on left and title on right */} -
-
- { - window.postMessage( - { - type: "action", - action: "marketplaceButtonClicked", - values: { marketplaceTab: "mode" }, - }, - "*", - ) - setOpen(false) - }} - /> - { - vscode.postMessage({ - type: "switchTab", - tab: "modes", - }) - setOpen(false) - }} - /> -
- - {/* Info icon and title on the right - only show info icon when search bar is visible */} -
- {showSearch && ( - - - - )} -

- {t("chat:modeSelector.title")} -

-
-
-
-
-
- ) -} - -export default ModeSelector +// Backward compatibility export +export { AgentSelector as ModeSelector, default } from "./AgentSelector" +export * from "./AgentSelector" diff --git a/webview-ui/src/components/chat/__tests__/ModeSelector.spec.tsx b/webview-ui/src/components/chat/__tests__/ModeSelector.spec.tsx index a829168893..a600e8a115 100644 --- a/webview-ui/src/components/chat/__tests__/ModeSelector.spec.tsx +++ b/webview-ui/src/components/chat/__tests__/ModeSelector.spec.tsx @@ -1,8 +1,8 @@ import React from "react" import { render, screen, fireEvent } from "@/utils/test-utils" import { describe, test, expect, vi } from "vitest" -import ModeSelector from "../ModeSelector" -import { Mode } from "@roo/modes" +import { AgentSelector as ModeSelector } from "../AgentSelector" +import { Mode } from "@roo/agents" import { ModeConfig } from "@roo-code/types" // Mock the dependencies @@ -38,8 +38,8 @@ vi.mock("@/utils/TelemetryClient", () => ({ // Create a variable to control what getAllModes returns let mockModes: ModeConfig[] = [] -vi.mock("@roo/modes", async () => { - const actual = await vi.importActual("@roo/modes") +vi.mock("@roo/agents", async () => { + const actual = await vi.importActual("@roo/agents") return { ...actual, getAllModes: () => mockModes, diff --git a/webview-ui/src/components/modes/DeleteModeDialog.tsx b/webview-ui/src/components/modes/DeleteModeDialog.tsx index d801b3149b..1e9a3e1595 100644 --- a/webview-ui/src/components/modes/DeleteModeDialog.tsx +++ b/webview-ui/src/components/modes/DeleteModeDialog.tsx @@ -1,56 +1,3 @@ -import React from "react" -import { useAppTranslation } from "@src/i18n/TranslationContext" -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, -} from "@src/components/ui" - -interface DeleteModeDialogProps { - open: boolean - onOpenChange: (open: boolean) => void - modeToDelete: { - slug: string - name: string - source?: string - rulesFolderPath?: string - } | null - onConfirm: () => void -} - -export const DeleteModeDialog: React.FC = ({ open, onOpenChange, modeToDelete, onConfirm }) => { - const { t } = useAppTranslation() - - return ( - - - - {t("prompts:deleteMode.title")} - - {modeToDelete && ( - <> - {t("prompts:deleteMode.message", { modeName: modeToDelete.name })} - {modeToDelete.rulesFolderPath && ( -
- {t("prompts:deleteMode.rulesFolder", { - folderPath: modeToDelete.rulesFolderPath, - })} -
- )} - - )} -
-
- - {t("prompts:deleteMode.cancel")} - {t("prompts:deleteMode.confirm")} - -
-
- ) -} +// Backward compatibility export +export { DeleteAgentDialog as DeleteModeDialog } from "../agents/DeleteAgentDialog" +export * from "../agents/DeleteAgentDialog" diff --git a/webview-ui/src/components/modes/ModesView.tsx b/webview-ui/src/components/modes/ModesView.tsx index 170d03b0e4..b6eceb0d1f 100644 --- a/webview-ui/src/components/modes/ModesView.tsx +++ b/webview-ui/src/components/modes/ModesView.tsx @@ -1,1649 +1,3 @@ -import React, { useState, useEffect, useMemo, useCallback, useRef } from "react" -import { - VSCodeCheckbox, - VSCodeRadioGroup, - VSCodeRadio, - VSCodeTextArea, - VSCodeLink, - VSCodeTextField, -} from "@vscode/webview-ui-toolkit/react" -import { Trans } from "react-i18next" -import { ChevronDown, X, Upload, Download } from "lucide-react" - -import { ModeConfig, GroupEntry, PromptComponent, ToolGroup, modeConfigSchema } from "@roo-code/types" - -import { - Mode, - getRoleDefinition, - getWhenToUse, - getDescription, - getCustomInstructions, - getAllModes, - findModeBySlug as findCustomModeBySlug, -} from "@roo/modes" -import { TOOL_GROUPS } from "@roo/tools" - -import { vscode } from "@src/utils/vscode" -import { buildDocLink } from "@src/utils/docLinks" -import { useAppTranslation } from "@src/i18n/TranslationContext" -import { useExtensionState } from "@src/context/ExtensionStateContext" -import { Tab, TabContent, TabHeader } from "@src/components/common/Tab" -import { - Button, - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, - Popover, - PopoverContent, - PopoverTrigger, - Command, - CommandInput, - CommandList, - CommandEmpty, - CommandItem, - CommandGroup, - Input, - StandardTooltip, -} from "@src/components/ui" -import { DeleteModeDialog } from "@src/components/modes/DeleteModeDialog" - -// Get all available groups that should show in prompts view -const availableGroups = (Object.keys(TOOL_GROUPS) as ToolGroup[]).filter((group) => !TOOL_GROUPS[group].alwaysAvailable) - -type ModeSource = "global" | "project" - -type ModesViewProps = { - onDone: () => void -} - -// Helper to get group name regardless of format -function getGroupName(group: GroupEntry): ToolGroup { - return Array.isArray(group) ? group[0] : group -} - -const ModesView = ({ onDone }: ModesViewProps) => { - const { t } = useAppTranslation() - - const { - customModePrompts, - listApiConfigMeta, - currentApiConfigName, - mode, - customInstructions, - setCustomInstructions, - customModes, - } = useExtensionState() - - // Use a local state to track the visually active mode - // This prevents flickering when switching modes rapidly by: - // 1. Updating the UI immediately when a mode is clicked - // 2. Not syncing with the backend mode state (which would cause flickering) - // 3. Still sending the mode change to the backend for persistence - const [visualMode, setVisualMode] = useState(mode) - - // Memoize modes to preserve array order - const modes = useMemo(() => getAllModes(customModes), [customModes]) - - const [isDialogOpen, setIsDialogOpen] = useState(false) - const [selectedPromptContent, setSelectedPromptContent] = useState("") - const [selectedPromptTitle, setSelectedPromptTitle] = useState("") - const [isToolsEditMode, setIsToolsEditMode] = useState(false) - const [showConfigMenu, setShowConfigMenu] = useState(false) - const [isCreateModeDialogOpen, setIsCreateModeDialogOpen] = useState(false) - const [isSystemPromptDisclosureOpen, setIsSystemPromptDisclosureOpen] = useState(false) - const [isExporting, setIsExporting] = useState(false) - const [isImporting, setIsImporting] = useState(false) - const [showImportDialog, setShowImportDialog] = useState(false) - const [hasRulesToExport, setHasRulesToExport] = useState>({}) - const [showDeleteConfirm, setShowDeleteConfirm] = useState(false) - const [modeToDelete, setModeToDelete] = useState<{ - slug: string - name: string - source?: string - rulesFolderPath?: string - } | null>(null) - - // State for mode selection popover and search - const [open, setOpen] = useState(false) - const [searchValue, setSearchValue] = useState("") - const searchInputRef = useRef(null) - - // Local state for mode name input to allow visual emptying - const [localModeName, setLocalModeName] = useState("") - const [currentEditingModeSlug, setCurrentEditingModeSlug] = useState(null) - - // Direct update functions - const updateAgentPrompt = useCallback( - (mode: Mode, promptData: PromptComponent) => { - const existingPrompt = customModePrompts?.[mode] as PromptComponent - const updatedPrompt = { ...existingPrompt, ...promptData } - - // Only include properties that differ from defaults - if (updatedPrompt.roleDefinition === getRoleDefinition(mode)) { - delete updatedPrompt.roleDefinition - } - if (updatedPrompt.description === getDescription(mode)) { - delete updatedPrompt.description - } - if (updatedPrompt.whenToUse === getWhenToUse(mode)) { - delete updatedPrompt.whenToUse - } - - vscode.postMessage({ - type: "updatePrompt", - promptMode: mode, - customPrompt: updatedPrompt, - }) - }, - [customModePrompts], - ) - - const updateCustomMode = useCallback((slug: string, modeConfig: ModeConfig) => { - const source = modeConfig.source || "global" - - vscode.postMessage({ - type: "updateCustomMode", - slug, - modeConfig: { - ...modeConfig, - source, // Ensure source is set - }, - }) - }, []) - - // Helper function to find a mode by slug - const findModeBySlug = useCallback( - (searchSlug: string, modes: readonly ModeConfig[] | undefined): ModeConfig | undefined => { - return findCustomModeBySlug(searchSlug, modes) - }, - [], - ) - - const switchMode = useCallback((slug: string) => { - vscode.postMessage({ - type: "mode", - text: slug, - }) - }, []) - - // Handle mode switching with explicit state initialization - const handleModeSwitch = useCallback( - (modeConfig: ModeConfig) => { - if (modeConfig.slug === visualMode) return // Prevent unnecessary updates - - // Immediately update visual state for instant feedback - setVisualMode(modeConfig.slug) - - // Then send the mode change message to the backend - switchMode(modeConfig.slug) - - // Exit tools edit mode when switching modes - setIsToolsEditMode(false) - }, - [visualMode, switchMode], - ) - - // Handler for popover open state change - const onOpenChange = useCallback((open: boolean) => { - setOpen(open) - // Reset search when closing the popover - if (!open) { - setTimeout(() => setSearchValue(""), 100) - } - }, []) - - // Handler for clearing search input - const onClearSearch = useCallback(() => { - setSearchValue("") - searchInputRef.current?.focus() - }, []) - - // Helper function to get current mode's config - const getCurrentMode = useCallback((): ModeConfig | undefined => { - const findMode = (m: ModeConfig): boolean => m.slug === visualMode - return customModes?.find(findMode) || modes.find(findMode) - }, [visualMode, customModes, modes]) - - // Check if the current mode has rules to export - const checkRulesDirectory = useCallback((slug: string) => { - vscode.postMessage({ - type: "checkRulesDirectory", - slug: slug, - }) - }, []) - - // Check rules directory when mode changes - useEffect(() => { - const currentMode = getCurrentMode() - if (currentMode?.slug && hasRulesToExport[currentMode.slug] === undefined) { - checkRulesDirectory(currentMode.slug) - } - }, [getCurrentMode, checkRulesDirectory, hasRulesToExport]) - - // Reset local name state when mode changes - useEffect(() => { - if (currentEditingModeSlug && currentEditingModeSlug !== visualMode) { - setCurrentEditingModeSlug(null) - setLocalModeName("") - } - }, [visualMode, currentEditingModeSlug]) - - // Helper function to safely access mode properties - const getModeProperty = ( - mode: ModeConfig | undefined, - property: T, - ): ModeConfig[T] | undefined => { - return mode?.[property] - } - - // State for create mode dialog - const [newModeName, setNewModeName] = useState("") - const [newModeSlug, setNewModeSlug] = useState("") - const [newModeDescription, setNewModeDescription] = useState("") - const [newModeRoleDefinition, setNewModeRoleDefinition] = useState("") - const [newModeWhenToUse, setNewModeWhenToUse] = useState("") - const [newModeCustomInstructions, setNewModeCustomInstructions] = useState("") - const [newModeGroups, setNewModeGroups] = useState(availableGroups) - const [newModeSource, setNewModeSource] = useState("global") - - // Field-specific error states - const [nameError, setNameError] = useState("") - const [slugError, setSlugError] = useState("") - const [descriptionError, setDescriptionError] = useState("") - const [roleDefinitionError, setRoleDefinitionError] = useState("") - const [groupsError, setGroupsError] = useState("") - - // Helper to reset form state - const resetFormState = useCallback(() => { - // Reset form fields - setNewModeName("") - setNewModeSlug("") - setNewModeDescription("") - setNewModeGroups(availableGroups) - setNewModeRoleDefinition("") - setNewModeWhenToUse("") - setNewModeCustomInstructions("") - setNewModeSource("global") - // Reset error states - setNameError("") - setSlugError("") - setDescriptionError("") - setRoleDefinitionError("") - setGroupsError("") - }, []) - - // Reset form fields when dialog opens - useEffect(() => { - if (isCreateModeDialogOpen) { - resetFormState() - } - }, [isCreateModeDialogOpen, resetFormState]) - - // Helper function to generate a unique slug from a name - const generateSlug = useCallback((name: string, attempt = 0): string => { - const baseSlug = name - .toLowerCase() - .replace(/[^a-z0-9-]+/g, "-") - .replace(/^-+|-+$/g, "") - return attempt === 0 ? baseSlug : `${baseSlug}-${attempt}` - }, []) - - // Handler for name changes - const handleNameChange = useCallback( - (name: string) => { - setNewModeName(name) - setNewModeSlug(generateSlug(name)) - }, - [generateSlug], - ) - - const handleCreateMode = useCallback(() => { - // Clear previous errors - setNameError("") - setSlugError("") - setDescriptionError("") - setRoleDefinitionError("") - setGroupsError("") - - const source = newModeSource - const newMode: ModeConfig = { - slug: newModeSlug, - name: newModeName, - description: newModeDescription.trim() || undefined, - roleDefinition: newModeRoleDefinition.trim(), - whenToUse: newModeWhenToUse.trim() || undefined, - customInstructions: newModeCustomInstructions.trim() || undefined, - groups: newModeGroups, - source, - } - - // Validate the mode against the schema - const result = modeConfigSchema.safeParse(newMode) - - if (!result.success) { - // Map Zod errors to specific fields - result.error.errors.forEach((error) => { - const field = error.path[0] as string - const message = error.message - - switch (field) { - case "name": - setNameError(message) - break - case "slug": - setSlugError(message) - break - case "description": - setDescriptionError(message) - break - case "roleDefinition": - setRoleDefinitionError(message) - break - case "groups": - setGroupsError(message) - break - } - }) - return - } - - updateCustomMode(newModeSlug, newMode) - switchMode(newModeSlug) - setIsCreateModeDialogOpen(false) - resetFormState() - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [ - newModeName, - newModeSlug, - newModeDescription, - newModeRoleDefinition, - newModeWhenToUse, // Add whenToUse dependency - newModeCustomInstructions, - newModeGroups, - newModeSource, - updateCustomMode, - ]) - - const isNameOrSlugTaken = useCallback( - (name: string, slug: string) => { - return modes.some((m) => m.slug === slug || m.name === name) - }, - [modes], - ) - - const openCreateModeDialog = useCallback(() => { - const baseNamePrefix = "New Custom Mode" - // Find unique name and slug - let attempt = 0 - let name = baseNamePrefix - let slug = generateSlug(name) - while (isNameOrSlugTaken(name, slug)) { - attempt++ - name = `${baseNamePrefix} ${attempt + 1}` - slug = generateSlug(name) - } - setNewModeName(name) - setNewModeSlug(slug) - setIsCreateModeDialogOpen(true) - }, [generateSlug, isNameOrSlugTaken]) - - // Handler for group checkbox changes - const handleGroupChange = useCallback( - (group: ToolGroup, isCustomMode: boolean, customMode: ModeConfig | undefined) => - (e: Event | React.FormEvent) => { - if (!isCustomMode) return // Prevent changes to built-in modes - const target = (e as CustomEvent)?.detail?.target || (e.target as HTMLInputElement) - const checked = target.checked - const oldGroups = customMode?.groups || [] - let newGroups: GroupEntry[] - if (checked) { - newGroups = [...oldGroups, group] - } else { - newGroups = oldGroups.filter((g) => getGroupName(g) !== group) - } - if (customMode) { - const source = customMode.source || "global" - - updateCustomMode(customMode.slug, { - ...customMode, - groups: newGroups, - source, - }) - } - }, - [updateCustomMode], - ) - - // Handle clicks outside the config menu - useEffect(() => { - const handleClickOutside = () => { - if (showConfigMenu) { - setShowConfigMenu(false) - } - } - - document.addEventListener("click", handleClickOutside) - return () => document.removeEventListener("click", handleClickOutside) - }, [showConfigMenu]) - - // Use a ref to store the current modeToDelete value - const modeToDeleteRef = useRef(modeToDelete) - - // Update the ref whenever modeToDelete changes - useEffect(() => { - modeToDeleteRef.current = modeToDelete - }, [modeToDelete]) - - useEffect(() => { - const handler = (event: MessageEvent) => { - const message = event.data - if (message.type === "systemPrompt") { - if (message.text) { - setSelectedPromptContent(message.text) - setSelectedPromptTitle(`System Prompt (${message.mode} mode)`) - setIsDialogOpen(true) - } - } else if (message.type === "exportModeResult") { - setIsExporting(false) - - if (!message.success) { - // Show error message - console.error("Failed to export mode:", message.error) - } - } else if (message.type === "importModeResult") { - setIsImporting(false) - setShowImportDialog(false) - - if (!message.success) { - // Only log error if it's not a cancellation - if (message.error !== "cancelled") { - console.error("Failed to import mode:", message.error) - } - } - } else if (message.type === "checkRulesDirectoryResult") { - setHasRulesToExport((prev) => ({ - ...prev, - [message.slug]: message.hasContent, - })) - } else if (message.type === "deleteCustomModeCheck") { - // Handle the check response - // Use the ref to get the current modeToDelete value - const currentModeToDelete = modeToDeleteRef.current - if (message.slug && currentModeToDelete && currentModeToDelete.slug === message.slug) { - setModeToDelete({ - ...currentModeToDelete, - rulesFolderPath: message.rulesFolderPath, - }) - setShowDeleteConfirm(true) - } - } - } - - window.addEventListener("message", handler) - return () => window.removeEventListener("message", handler) - }, []) // Empty dependency array - only register once - - const handleAgentReset = ( - modeSlug: string, - type: "roleDefinition" | "description" | "whenToUse" | "customInstructions", - ) => { - // Only reset for built-in modes - const existingPrompt = customModePrompts?.[modeSlug] as PromptComponent - const updatedPrompt = { ...existingPrompt } - delete updatedPrompt[type] // Remove the field entirely to ensure it reloads from defaults - - vscode.postMessage({ - type: "updatePrompt", - promptMode: modeSlug, - customPrompt: updatedPrompt, - }) - } - - return ( - - -

{t("prompts:title")}

- -
- - -
-
e.stopPropagation()} className="flex justify-between items-center mb-3"> -

{t("prompts:modes.title")}

-
- - - -
- - - - {showConfigMenu && ( -
e.stopPropagation()} - onMouseDown={(e) => e.stopPropagation()} - className="absolute top-full right-0 w-[200px] mt-1 bg-vscode-editor-background border border-vscode-input-border rounded shadow-md z-[1000]"> -
{ - e.preventDefault() // Prevent blur - vscode.postMessage({ - type: "openCustomModesSettings", - }) - setShowConfigMenu(false) - }} - onClick={(e) => e.preventDefault()}> - {t("prompts:modes.editGlobalModes")} -
-
{ - e.preventDefault() // Prevent blur - vscode.postMessage({ - type: "openFile", - text: "./.roomodes", - values: { - create: true, - content: JSON.stringify({ customModes: [] }, null, 2), - }, - }) - setShowConfigMenu(false) - }} - onClick={(e) => e.preventDefault()}> - {t("prompts:modes.editProjectModes")} -
-
- )} -
- - - -
-
- -
- - - - -
- -
- - - - - - -
- - {searchValue.length > 0 && ( -
- -
- )} -
- - - {searchValue && ( -
- {t("prompts:modes.noMatchFound")} -
- )} -
- - {modes - .filter((modeConfig) => - searchValue - ? modeConfig.name - .toLowerCase() - .includes(searchValue.toLowerCase()) - : true, - ) - .map((modeConfig) => ( - { - handleModeSwitch(modeConfig) - setOpen(false) - }} - data-testid={`mode-option-${modeConfig.slug}`}> -
- - {modeConfig.name} - - - {modeConfig.slug} - -
-
- ))} -
-
-
-
-
-
- {/* API Configuration - Moved Here */} -
-
{t("prompts:apiConfiguration.title")}
-
- {t("prompts:apiConfiguration.select")} -
-
- -
-
-
- - {/* Name section */} -
- {/* Only show name and delete for custom modes */} - {visualMode && findModeBySlug(visualMode, customModes) && ( -
-
-
{t("prompts:createModeDialog.name.label")}
-
- { - const customMode = findModeBySlug(visualMode, customModes) - if (customMode) { - setCurrentEditingModeSlug(visualMode) - setLocalModeName(customMode.name) - } - }} - onChange={(e) => { - setLocalModeName(e.target.value) - }} - onBlur={() => { - const customMode = findModeBySlug(visualMode, customModes) - if (customMode && localModeName.trim()) { - // Only update if the name is not empty - updateCustomMode(visualMode, { - ...customMode, - name: localModeName, - source: customMode.source || "global", - }) - } - // Clear the editing state - setCurrentEditingModeSlug(null) - }} - className="w-full" - /> - - - -
-
-
- )} - - {/* Role Definition section */} -
-
-
{t("prompts:roleDefinition.title")}
- {!findModeBySlug(visualMode, customModes) && ( - - - - )} -
-
- {t("prompts:roleDefinition.description")} -
- { - const customMode = findModeBySlug(visualMode, customModes) - const prompt = customModePrompts?.[visualMode] as PromptComponent - return ( - customMode?.roleDefinition ?? - prompt?.roleDefinition ?? - getRoleDefinition(visualMode) - ) - })()} - onChange={(e) => { - const value = - (e as unknown as CustomEvent)?.detail?.target?.value || - ((e as any).target as HTMLTextAreaElement).value - const customMode = findModeBySlug(visualMode, customModes) - if (customMode) { - // For custom modes, update the JSON file - updateCustomMode(visualMode, { - ...customMode, - roleDefinition: value.trim() || "", - source: customMode.source || "global", - }) - } else { - // For built-in modes, update the prompts - updateAgentPrompt(visualMode, { - roleDefinition: value.trim() || undefined, - }) - } - }} - className="w-full" - rows={5} - data-testid={`${getCurrentMode()?.slug || "code"}-prompt-textarea`} - /> -
- - {/* Description section */} -
-
-
{t("prompts:description.title")}
- {!findModeBySlug(visualMode, customModes) && ( - - - - )} -
-
- {t("prompts:description.description")} -
- { - const customMode = findModeBySlug(visualMode, customModes) - const prompt = customModePrompts?.[visualMode] as PromptComponent - return customMode?.description ?? prompt?.description ?? getDescription(visualMode) - })()} - onChange={(e) => { - const value = - (e as unknown as CustomEvent)?.detail?.target?.value || - ((e as any).target as HTMLTextAreaElement).value - const customMode = findModeBySlug(visualMode, customModes) - if (customMode) { - // For custom modes, update the JSON file - updateCustomMode(visualMode, { - ...customMode, - description: value.trim() || undefined, - source: customMode.source || "global", - }) - } else { - // For built-in modes, update the prompts - updateAgentPrompt(visualMode, { - description: value.trim() || undefined, - }) - } - }} - className="w-full" - data-testid={`${getCurrentMode()?.slug || "code"}-description-textfield`} - /> -
- - {/* When to Use section */} -
-
-
{t("prompts:whenToUse.title")}
- {!findModeBySlug(visualMode, customModes) && ( - - - - )} -
-
- {t("prompts:whenToUse.description")} -
- { - const customMode = findModeBySlug(visualMode, customModes) - const prompt = customModePrompts?.[visualMode] as PromptComponent - return customMode?.whenToUse ?? prompt?.whenToUse ?? getWhenToUse(visualMode) - })()} - onChange={(e) => { - const value = - (e as unknown as CustomEvent)?.detail?.target?.value || - ((e as any).target as HTMLTextAreaElement).value - const customMode = findModeBySlug(visualMode, customModes) - if (customMode) { - // For custom modes, update the JSON file - updateCustomMode(visualMode, { - ...customMode, - whenToUse: value.trim() || undefined, - source: customMode.source || "global", - }) - } else { - // For built-in modes, update the prompts - updateAgentPrompt(visualMode, { - whenToUse: value.trim() || undefined, - }) - } - }} - className="w-full" - rows={4} - data-testid={`${getCurrentMode()?.slug || "code"}-when-to-use-textarea`} - /> -
- - {/* Mode settings */} - <> - {/* Show tools for all modes */} -
-
-
{t("prompts:tools.title")}
- {findModeBySlug(visualMode, customModes) && ( - - - - )} -
- {!findModeBySlug(visualMode, customModes) && ( -
- {t("prompts:tools.builtInModesText")} -
- )} - {isToolsEditMode && findModeBySlug(visualMode, customModes) ? ( -
- {availableGroups.map((group) => { - const currentMode = getCurrentMode() - const isCustomMode = findModeBySlug(visualMode, customModes) - const customMode = isCustomMode - const isGroupEnabled = isCustomMode - ? customMode?.groups?.some((g) => getGroupName(g) === group) - : currentMode?.groups?.some((g) => getGroupName(g) === group) - - return ( - - {t(`prompts:tools.toolNames.${group}`)} - {group === "edit" && ( -
- {t("prompts:tools.allowedFiles")}{" "} - {(() => { - const currentMode = getCurrentMode() - const editGroup = currentMode?.groups?.find( - (g) => - Array.isArray(g) && - g[0] === "edit" && - g[1]?.fileRegex, - ) - if (!Array.isArray(editGroup)) return t("prompts:allFiles") - return ( - editGroup[1].description || - `/${editGroup[1].fileRegex}/` - ) - })()} -
- )} -
- ) - })} -
- ) : ( -
- {(() => { - const currentMode = getCurrentMode() - const enabledGroups = currentMode?.groups || [] - - // If there are no enabled groups, display translated "None" - if (enabledGroups.length === 0) { - return t("prompts:tools.noTools") - } - - return enabledGroups - .map((group) => { - const groupName = getGroupName(group) - const displayName = t(`prompts:tools.toolNames.${groupName}`) - if (Array.isArray(group) && group[1]?.fileRegex) { - const description = - group[1].description || `/${group[1].fileRegex}/` - return `${displayName} (${description})` - } - return displayName - }) - .join(", ") - })()} -
- )} -
- - - {/* Role definition for both built-in and custom modes */} -
-
-
{t("prompts:customInstructions.title")}
- {!findModeBySlug(visualMode, customModes) && ( - - - - )} -
-
- {t("prompts:customInstructions.description", { - modeName: getCurrentMode()?.name || "Code", - })} -
- { - const customMode = findModeBySlug(visualMode, customModes) - const prompt = customModePrompts?.[visualMode] as PromptComponent - return ( - customMode?.customInstructions ?? - prompt?.customInstructions ?? - getCustomInstructions(mode, customModes) - ) - })()} - onChange={(e) => { - const value = - (e as unknown as CustomEvent)?.detail?.target?.value || - ((e as any).target as HTMLTextAreaElement).value - const customMode = findModeBySlug(visualMode, customModes) - if (customMode) { - // For custom modes, update the JSON file - updateCustomMode(visualMode, { - ...customMode, - customInstructions: value.trim() || undefined, - source: customMode.source || "global", - }) - } else { - // For built-in modes, update the prompts - const existingPrompt = customModePrompts?.[visualMode] as PromptComponent - updateAgentPrompt(visualMode, { - ...existingPrompt, - customInstructions: value.trim(), - }) - } - }} - rows={10} - className="w-full" - data-testid={`${getCurrentMode()?.slug || "code"}-custom-instructions-textarea`} - /> -
- { - const currentMode = getCurrentMode() - if (!currentMode) return - - // Open or create an empty file - vscode.postMessage({ - type: "openFile", - text: `./.roo/rules-${currentMode.slug}/rules.md`, - values: { - create: true, - content: "", - }, - }) - }} - /> - ), - }} - /> -
-
-
- -
-
- - - - -
- - {/* Export/Import Mode Buttons */} -
- {/* Export button - visible when any mode is selected */} - {getCurrentMode() && ( - - )} - {/* Import button - always visible */} - -
- - {/* Advanced Features Disclosure */} -
- - - {isSystemPromptDisclosureOpen && ( -
- {/* Override System Prompt Section */} -
-

- Override System Prompt -

-
- { - const currentMode = getCurrentMode() - if (!currentMode) return - - vscode.postMessage({ - type: "openFile", - text: `./.roo/system-prompt-${currentMode.slug}`, - values: { - create: true, - content: "", - }, - }) - }} - /> - ), - "1": ( - - ), - "2": , - }} - /> -
-
-
- )} -
-
- -
-

{t("prompts:globalCustomInstructions.title")}

- -
- - - -
- { - const value = - (e as unknown as CustomEvent)?.detail?.target?.value || - ((e as any).target as HTMLTextAreaElement).value - setCustomInstructions(value || undefined) - vscode.postMessage({ - type: "customInstructions", - text: value.trim() || undefined, - }) - }} - rows={4} - className="w-full" - data-testid="global-custom-instructions-textarea" - /> -
- - vscode.postMessage({ - type: "openFile", - text: "./.roo/rules/rules.md", - values: { - create: true, - content: "", - }, - }) - } - /> - ), - }} - /> -
-
-
- - {isCreateModeDialogOpen && ( -
-
-
- -

{t("prompts:createModeDialog.title")}

-
-
{t("prompts:createModeDialog.name.label")}
- { - handleNameChange(e.target.value) - }} - className="w-full" - /> - {nameError && ( -
{nameError}
- )} -
-
-
{t("prompts:createModeDialog.slug.label")}
- { - setNewModeSlug(e.target.value) - }} - className="w-full" - /> -
- {t("prompts:createModeDialog.slug.description")} -
- {slugError && ( -
{slugError}
- )} -
-
-
{t("prompts:createModeDialog.saveLocation.label")}
-
- {t("prompts:createModeDialog.saveLocation.description")} -
- ) => { - const target = ((e as CustomEvent)?.detail?.target || - (e.target as HTMLInputElement)) as HTMLInputElement - setNewModeSource(target.value as ModeSource) - }}> - - {t("prompts:createModeDialog.saveLocation.global.label")} -
- {t("prompts:createModeDialog.saveLocation.global.description")} -
-
- - {t("prompts:createModeDialog.saveLocation.project.label")} -
- {t("prompts:createModeDialog.saveLocation.project.description")} -
-
-
-
- -
-
- {t("prompts:createModeDialog.roleDefinition.label")} -
-
- {t("prompts:createModeDialog.roleDefinition.description")} -
- { - setNewModeRoleDefinition((e.target as HTMLTextAreaElement).value) - }} - rows={4} - className="w-full" - /> - {roleDefinitionError && ( -
- {roleDefinitionError} -
- )} -
- -
-
{t("prompts:createModeDialog.description.label")}
-
- {t("prompts:createModeDialog.description.description")} -
- { - setNewModeDescription((e.target as HTMLInputElement).value) - }} - className="w-full" - /> - {descriptionError && ( -
{descriptionError}
- )} -
- -
-
{t("prompts:createModeDialog.whenToUse.label")}
-
- {t("prompts:createModeDialog.whenToUse.description")} -
- { - setNewModeWhenToUse((e.target as HTMLTextAreaElement).value) - }} - rows={3} - className="w-full" - /> -
-
-
{t("prompts:createModeDialog.tools.label")}
-
- {t("prompts:createModeDialog.tools.description")} -
-
- {availableGroups.map((group) => ( - getGroupName(g) === group)} - onChange={(e: Event | React.FormEvent) => { - const target = - (e as CustomEvent)?.detail?.target || (e.target as HTMLInputElement) - const checked = target.checked - if (checked) { - setNewModeGroups([...newModeGroups, group]) - } else { - setNewModeGroups( - newModeGroups.filter((g) => getGroupName(g) !== group), - ) - } - }}> - {t(`prompts:tools.toolNames.${group}`)} - - ))} -
- {groupsError && ( -
{groupsError}
- )} -
-
-
- {t("prompts:createModeDialog.customInstructions.label")} -
-
- {t("prompts:createModeDialog.customInstructions.description")} -
- { - setNewModeCustomInstructions((e.target as HTMLTextAreaElement).value) - }} - rows={4} - className="w-full" - /> -
-
-
- - -
-
-
- )} - - {isDialogOpen && ( -
-
-
- -

- {selectedPromptTitle || - t("prompts:systemPrompt.title", { - modeName: getCurrentMode()?.name || "Code", - })} -

-
-								{selectedPromptContent}
-							
-
-
- -
-
-
- )} - - {/* Import Mode Dialog */} - {showImportDialog && ( -
-
-

{t("prompts:modes.importMode")}

-

- {t("prompts:importMode.selectLevel")} -

-
- - -
-
- - -
-
-
- )} - - {/* Delete Mode Confirmation Dialog */} - { - if (modeToDelete) { - vscode.postMessage({ - type: "deleteCustomMode", - slug: modeToDelete.slug, - }) - setShowDeleteConfirm(false) - setModeToDelete(null) - } - }} - /> -
- ) -} - -export default ModesView +// Backward compatibility export +export { default } from "../agents/AgentsView" +export * from "../agents/AgentsView" diff --git a/webview-ui/src/i18n/locales/ca/chat.json b/webview-ui/src/i18n/locales/ca/chat.json index 381576ad48..23c3a61fb6 100644 --- a/webview-ui/src/i18n/locales/ca/chat.json +++ b/webview-ui/src/i18n/locales/ca/chat.json @@ -88,19 +88,19 @@ "description": "Divideix les tasques en parts més petites i manejables." }, "stickyModels": { - "title": "Modes persistents", - "description": "Cada mode recorda el vostre darrer model utilitzat" + "title": "Agents persistents", + "description": "Cada agent recorda el vostre darrer model utilitzat" }, "tools": { "title": "Eines", "description": "Permet que la IA resolgui problemes navegant per la web, executant ordres i molt més." }, "customizableModes": { - "title": "Modes personalitzables", + "title": "Agents personalitzables", "description": "Personalitats especialitzades amb comportaments propis i models assignats" } }, - "selectMode": "Selecciona el mode d'interacció", + "selectMode": "Selecciona el agent d'interacció", "selectApiConfig": "Seleccioneu la configuració de l'API", "enhancePrompt": "Millora la sol·licitud amb context addicional", "addImages": "Afegeix imatges al missatge", @@ -108,24 +108,24 @@ "stopTts": "Atura la síntesi de veu", "typeMessage": "Escriu un missatge...", "typeTask": "Escriu la teva tasca aquí...", - "addContext": "@ per afegir context, / per canviar de mode", + "addContext": "@ per afegir context, / per canviar de agent", "dragFiles": "manté premut shift per arrossegar fitxers", "dragFilesImages": "manté premut shift per arrossegar fitxers/imatges", "enhancePromptDescription": "El botó 'Millora la sol·licitud' ajuda a millorar la teva sol·licitud proporcionant context addicional, aclariments o reformulacions. Prova d'escriure una sol·licitud aquí i fes clic al botó de nou per veure com funciona.", "modeSelector": { - "title": "Modes", - "marketplace": "Marketplace de Modes", - "settings": "Configuració de Modes", + "title": "Agents", + "marketplace": "Marketplace de Agents", + "settings": "Configuració de Agents", "description": "Personalitats especialitzades que adapten el comportament de Roo.", - "searchPlaceholder": "Cerca modes...", + "searchPlaceholder": "Cerca agents...", "noResults": "No s'han trobat resultats" }, "errorReadingFile": "Error en llegir el fitxer:", "noValidImages": "No s'ha processat cap imatge vàlida", "separator": "Separador", "edit": "Edita...", - "forNextMode": "per al següent mode", - "forPreviousMode": "per al mode anterior", + "forNextMode": "per al següent agent", + "forPreviousMode": "per al agent anterior", "error": "Error", "diffError": { "title": "Edició fallida" @@ -216,14 +216,14 @@ "wantsToUseTool": "Roo vol utilitzar una eina al servidor MCP {{serverName}}:", "wantsToAccessResource": "Roo vol accedir a un recurs al servidor MCP {{serverName}}:" }, - "modes": { - "wantsToSwitch": "Roo vol canviar a mode {{mode}}", - "wantsToSwitchWithReason": "Roo vol canviar a mode {{mode}} perquè: {{reason}}", - "didSwitch": "Roo ha canviat a mode {{mode}}", - "didSwitchWithReason": "Roo ha canviat a mode {{mode}} perquè: {{reason}}" + "agents": { + "wantsToSwitch": "Roo vol canviar a agent {{agent}}", + "wantsToSwitchWithReason": "Roo vol canviar a agent {{agent}} perquè: {{reason}}", + "didSwitch": "Roo ha canviat a agent {{agent}}", + "didSwitchWithReason": "Roo ha canviat a agent {{agent}} perquè: {{reason}}" }, "subtasks": { - "wantsToCreate": "Roo vol crear una nova subtasca en mode {{mode}}:", + "wantsToCreate": "Roo vol crear una nova subtasca en agent {{agent}}:", "wantsToFinish": "Roo vol finalitzar aquesta subtasca", "newTaskContent": "Instruccions de la subtasca", "completionContent": "Subtasca completada", diff --git a/webview-ui/src/i18n/locales/ca/history.json b/webview-ui/src/i18n/locales/ca/history.json index bd7dafe3da..1df99a0e1b 100644 --- a/webview-ui/src/i18n/locales/ca/history.json +++ b/webview-ui/src/i18n/locales/ca/history.json @@ -5,8 +5,8 @@ "cache": "Cau: +{{writes}} → {{reads}}", "apiCost": "Cost d'API: ${{cost}}", "history": "Historial", - "exitSelectionMode": "Sortir del mode de selecció", - "enterSelectionMode": "Entrar en mode de selecció", + "exitSelectionMode": "Sortir del agent de selecció", + "enterSelectionMode": "Entrar en agent de selecció", "done": "Fet", "searchPlaceholder": "Cerca a l'historial...", "newest": "Més recents", @@ -25,7 +25,7 @@ "cancel": "Cancel·lar", "delete": "Eliminar", "exitSelection": "Sortir de la selecció", - "selectionMode": "Mode de selecció", + "selectionMode": "Agent de selecció", "deselectAll": "Desseleccionar tot", "selectAll": "Seleccionar tot", "selectedItems": "{{selected}}/{{total}} elements seleccionats", diff --git a/webview-ui/src/i18n/locales/ca/marketplace.json b/webview-ui/src/i18n/locales/ca/marketplace.json index 1c4f1f805c..4c0df6a55b 100644 --- a/webview-ui/src/i18n/locales/ca/marketplace.json +++ b/webview-ui/src/i18n/locales/ca/marketplace.json @@ -11,12 +11,12 @@ "search": { "placeholder": "Cercar elements del marketplace...", "placeholderMcp": "Cercar MCPs...", - "placeholderMode": "Cercar modes..." + "placeholderMode": "Cercar agents..." }, "type": { "label": "Filtrar per tipus:", "all": "Tots els tipus", - "mode": "Mode", + "agent": "Agent", "mcpServer": "Servidor MCP" }, "sort": { @@ -36,7 +36,7 @@ "none": "Cap" }, "type-group": { - "modes": "Modes", + "agents": "Agents", "mcps": "Servidors MCP" }, "items": { @@ -79,7 +79,7 @@ }, "install": { "title": "Instal·lar {{name}}", - "titleMode": "Instal·lar mode {{name}}", + "titleMode": "Instal·lar agent {{name}}", "titleMcp": "Instal·lar MCP {{name}}", "scope": "Àmbit d'instal·lació", "project": "Projecte (espai de treball actual)", @@ -92,10 +92,10 @@ "successDescription": "Instal·lació completada amb èxit", "installed": "Instal·lat amb èxit!", "whatNextMcp": "Ara pots configurar i utilitzar aquest servidor MCP. Feu clic a la icona MCP de la barra lateral per canviar de pestanya.", - "whatNextMode": "Ara pots utilitzar aquest mode. Feu clic a la icona Modes de la barra lateral per canviar de pestanya.", + "whatNextMode": "Ara pots utilitzar aquest agent. Feu clic a la icona Agents de la barra lateral per canviar de pestanya.", "done": "Fet", "goToMcp": "Anar a la pestanya MCP", - "goToModes": "Anar a la configuració de Modes", + "goToModes": "Anar a la configuració de Agents", "moreInfoMcp": "Veure documentació MCP de {{name}}", "validationRequired": "Si us plau, proporciona un valor per a {{paramName}}", "prerequisites": "Prerequisits" @@ -130,10 +130,10 @@ } }, "removeConfirm": { - "mode": { - "title": "Eliminar el mode", - "message": "Estàs segur que vols eliminar el mode \"{{modeName}}\"?", - "rulesWarning": "Això també eliminarà qualsevol fitxer de regles associat per a aquest mode." + "agent": { + "title": "Eliminar el agent", + "message": "Estàs segur que vols eliminar el agent \"{{agentName}}\"?", + "rulesWarning": "Això també eliminarà qualsevol fitxer de regles associat per a aquest agent." }, "mcp": { "title": "Eliminar el servidor MCP", diff --git a/webview-ui/src/i18n/locales/ca/prompts.json b/webview-ui/src/i18n/locales/ca/prompts.json index 1f67068df0..62520f8224 100644 --- a/webview-ui/src/i18n/locales/ca/prompts.json +++ b/webview-ui/src/i18n/locales/ca/prompts.json @@ -1,24 +1,24 @@ { - "title": "Modes", + "title": "Agents", "done": "Fet", - "modes": { - "title": "Modes", - "createNewMode": "Crear nou mode", - "importMode": "Importar mode", - "noMatchFound": "No s'han trobat modes", - "editModesConfig": "Editar configuració de modes", - "editGlobalModes": "Editar modes globals", - "editProjectModes": "Editar modes de projecte (.roomodes)", - "createModeHelpText": "Els modes són persones especialitzades que adapten el comportament de Roo. <0>Aprèn sobre l'ús de modes o <1>Personalització de modes.", - "selectMode": "Cerqueu modes" + "agents": { + "title": "Agents", + "createNewMode": "Crear nou agent", + "importMode": "Importar agent", + "noMatchFound": "No s'han trobat agents", + "editModesConfig": "Editar configuració de agents", + "editGlobalModes": "Editar agents globals", + "editProjectModes": "Editar agents de projecte (.roomodes)", + "createModeHelpText": "Els agents són persones especialitzades que adapten el comportament de Roo. <0>Aprèn sobre l'ús de agents o <1>Personalització de agents.", + "selectMode": "Cerqueu agents" }, "apiConfiguration": { "title": "Configuració d'API", - "select": "Seleccioneu quina configuració d'API utilitzar per a aquest mode" + "select": "Seleccioneu quina configuració d'API utilitzar per a aquest agent" }, "tools": { "title": "Eines disponibles", - "builtInModesText": "Les eines per a modes integrats no es poden modificar", + "builtInModesText": "Les eines per a agents integrats no es poden modificar", "editTools": "Editar eines", "doneEditing": "Finalitzar edició", "allowedFiles": "Fitxers permesos:", @@ -34,32 +34,32 @@ "roleDefinition": { "title": "Definició de rol", "resetToDefault": "Restablir a valors predeterminats", - "description": "Definiu l'experiència i personalitat de Roo per a aquest mode. Aquesta descripció determina com Roo es presenta i aborda les tasques." + "description": "Definiu l'experiència i personalitat de Roo per a aquest agent. Aquesta descripció determina com Roo es presenta i aborda les tasques." }, "description": { "title": "Descripció curta (per a humans)", "resetToDefault": "Restablir a la descripció predeterminada", - "description": "Una breu descripció que es mostra al desplegable del selector de mode." + "description": "Una breu descripció que es mostra al desplegable del selector de agent." }, "whenToUse": { "title": "Quan utilitzar (opcional)", - "description": "Descriviu quan s'hauria d'utilitzar aquest mode. Això ajuda l'Orchestrator a escollir el mode correcte per a una tasca.", + "description": "Descriviu quan s'hauria d'utilitzar aquest agent. Això ajuda l'Orchestrator a escollir el agent correcte per a una tasca.", "resetToDefault": "Restablir la descripció 'Quan utilitzar' a valors predeterminats" }, "customInstructions": { - "title": "Instruccions personalitzades específiques del mode (opcional)", + "title": "Instruccions personalitzades específiques del agent (opcional)", "resetToDefault": "Restablir a valors predeterminats", - "description": "Afegiu directrius de comportament específiques per al mode {{modeName}}.", - "loadFromFile": "Les instruccions personalitzades específiques per al mode {{mode}} també es poden carregar des de la carpeta .roo/rules-{{slug}}/ al vostre espai de treball (.roorules-{{slug}} i .clinerules-{{slug}} estan obsolets i deixaran de funcionar aviat)." + "description": "Afegiu directrius de comportament específiques per al agent {{agentName}}.", + "loadFromFile": "Les instruccions personalitzades específiques per al agent {{agent}} també es poden carregar des de la carpeta .roo/rules-{{slug}}/ al vostre espai de treball (.roorules-{{slug}} i .clinerules-{{slug}} estan obsolets i deixaran de funcionar aviat)." }, "exportMode": { - "title": "Exportar mode", - "description": "Exporta aquest mode a un fitxer YAML amb totes les regles incloses per compartir fàcilment amb altres.", - "export": "Exportar mode", + "title": "Exportar agent", + "description": "Exporta aquest agent a un fitxer YAML amb totes les regles incloses per compartir fàcilment amb altres.", + "export": "Exportar agent", "exporting": "Exportant..." }, "importMode": { - "selectLevel": "Tria on importar aquest mode:", + "selectLevel": "Tria on importar aquest agent:", "import": "Importar", "importing": "Important...", "global": { @@ -68,21 +68,21 @@ }, "project": { "label": "Nivell de projecte", - "description": "Només disponible en aquest espai de treball. Si el mode exportat contenia fitxers de regles, es tornaran a crear a la carpeta .roo/rules-{slug}/." + "description": "Només disponible en aquest espai de treball. Si el agent exportat contenia fitxers de regles, es tornaran a crear a la carpeta .roo/rules-{slug}/." } }, "advanced": { "title": "Avançat" }, "globalCustomInstructions": { - "title": "Instruccions personalitzades per a tots els modes", - "description": "Aquestes instruccions s'apliquen a tots els modes. Proporcionen un conjunt bàsic de comportaments que es poden millorar amb instruccions específiques de cada mode a continuació. <0>Més informació", + "title": "Instruccions personalitzades per a tots els agents", + "description": "Aquestes instruccions s'apliquen a tots els agents. Proporcionen un conjunt bàsic de comportaments que es poden millorar amb instruccions específiques de cada agent a continuació. <0>Més informació", "loadFromFile": "Les instruccions també es poden carregar des de la carpeta .roo/rules/ al vostre espai de treball (.roorules i .clinerules estan obsolets i deixaran de funcionar aviat)." }, "systemPrompt": { "preview": "Previsualització del prompt del sistema", "copy": "Copiar prompt del sistema al portapapers", - "title": "Prompt del sistema (mode {{modeName}})" + "title": "Prompt del sistema (agent {{agentName}})" }, "supportPrompts": { "title": "Prompts de suport", @@ -149,11 +149,11 @@ "description": "<2>⚠️ Avís: Aquesta funcionalitat avançada eludeix les salvaguardes. <1>LLEGIU AIXÒ ABANS D'UTILITZAR!Sobreescriviu el prompt del sistema per defecte creant un fitxer a .roo/system-prompt-{{slug}}." }, "createModeDialog": { - "title": "Crear nou mode", + "title": "Crear nou agent", "close": "Tancar", "name": { "label": "Nom", - "placeholder": "Introduïu nom del mode" + "placeholder": "Introduïu nom del agent" }, "slug": { "label": "Slug", @@ -161,7 +161,7 @@ }, "saveLocation": { "label": "Ubicació per desar", - "description": "Trieu on desar aquest mode. Els modes específics del projecte tenen prioritat sobre els modes globals.", + "description": "Trieu on desar aquest agent. Els agents específics del projecte tenen prioritat sobre els agents globals.", "global": { "label": "Global", "description": "Disponible a tots els espais de treball" @@ -173,36 +173,36 @@ }, "roleDefinition": { "label": "Definició de rol", - "description": "Definiu l'experiència i personalitat de Roo per a aquest mode." + "description": "Definiu l'experiència i personalitat de Roo per a aquest agent." }, "whenToUse": { "label": "Quan utilitzar (opcional)", - "description": "Proporcioneu una descripció clara de quan aquest mode és més efectiu i per a quins tipus de tasques excel·leix." + "description": "Proporcioneu una descripció clara de quan aquest agent és més efectiu i per a quins tipus de tasques excel·leix." }, "tools": { "label": "Eines disponibles", - "description": "Seleccioneu quines eines pot utilitzar aquest mode." + "description": "Seleccioneu quines eines pot utilitzar aquest agent." }, "description": { "label": "Descripció curta (per a humans)", - "description": "Una breu descripció que es mostra al desplegable del selector de mode." + "description": "Una breu descripció que es mostra al desplegable del selector de agent." }, "customInstructions": { "label": "Instruccions personalitzades (opcional)", - "description": "Afegiu directrius de comportament específiques per a aquest mode." + "description": "Afegiu directrius de comportament específiques per a aquest agent." }, "buttons": { "cancel": "Cancel·lar", - "create": "Crear mode" + "create": "Crear agent" }, - "deleteMode": "Eliminar mode" + "deleteMode": "Eliminar agent" }, "allFiles": "tots els fitxers", "deleteMode": { - "title": "Suprimeix el mode", - "message": "Estàs segur que vols suprimir el mode \"{{modeName}}\"?", - "rulesFolder": "Aquest mode té una carpeta de regles a {{folderPath}} que també se suprimirà.", - "descriptionNoRules": "Esteu segur que voleu suprimir aquest mode personalitzat?", + "title": "Suprimeix el agent", + "message": "Estàs segur que vols suprimir el agent \"{{agentName}}\"?", + "rulesFolder": "Aquest agent té una carpeta de regles a {{folderPath}} que també se suprimirà.", + "descriptionNoRules": "Esteu segur que voleu suprimir aquest agent personalitzat?", "confirm": "Suprimeix", "cancel": "Cancel·la" } diff --git a/webview-ui/src/i18n/locales/ca/settings.json b/webview-ui/src/i18n/locales/ca/settings.json index aed9413f27..e8b0aed9d3 100644 --- a/webview-ui/src/i18n/locales/ca/settings.json +++ b/webview-ui/src/i18n/locales/ca/settings.json @@ -164,8 +164,8 @@ "description": "Habilitar l'aprovació automàtica d'eines MCP individuals a la vista de Servidors MCP (requereix tant aquesta configuració com la casella \"Permetre sempre\" de l'eina)" }, "modeSwitch": { - "label": "Mode", - "description": "Canviar automàticament entre diferents modes sense requerir aprovació" + "label": "Agent", + "description": "Canviar automàticament entre diferents agents sense requerir aprovació" }, "subtasks": { "label": "Subtasques", @@ -332,7 +332,7 @@ "draftModelId": "ID del model d'esborrany", "draftModelDesc": "El model d'esborrany ha de ser de la mateixa família de models perquè la descodificació especulativa funcioni correctament.", "selectDraftModel": "Seleccionar model d'esborrany", - "noModelsFound": "No s'han trobat models d'esborrany. Assegureu-vos que LM Studio s'està executant amb el mode servidor habilitat.", + "noModelsFound": "No s'han trobat models d'esborrany. Assegureu-vos que LM Studio s'està executant amb el agent servidor habilitat.", "description": "LM Studio permet executar models localment al vostre ordinador. Per a instruccions sobre com començar, consulteu la seva Guia d'inici ràpid. També necessitareu iniciar la funció de Servidor Local de LM Studio per utilitzar-la amb aquesta extensió. Nota: Roo Code utilitza prompts complexos i funciona millor amb models Claude. Els models menys capaços poden no funcionar com s'espera." }, "ollama": { @@ -641,8 +641,8 @@ "description": "Activar l'eina d'inserció de contingut experimental, permetent a Roo inserir contingut a números de línia específics sense necessitat de crear un diff." }, "POWER_STEERING": { - "name": "Utilitzar mode \"direcció assistida\" experimental", - "description": "Quan està activat, Roo recordarà al model els detalls de la seva definició de mode actual amb més freqüència. Això portarà a una adherència més forta a les definicions de rol i instruccions personalitzades, però utilitzarà més tokens per missatge." + "name": "Utilitzar agent \"direcció assistida\" experimental", + "description": "Quan està activat, Roo recordarà al model els detalls de la seva definició de agent actual amb més freqüència. Això portarà a una adherència més forta a les definicions de rol i instruccions personalitzades, però utilitzarà més tokens per missatge." }, "MULTI_SEARCH_AND_REPLACE": { "name": "Utilitzar eina diff de blocs múltiples experimental", @@ -654,7 +654,7 @@ }, "MARKETPLACE": { "name": "Habilitar Marketplace", - "description": "Quan està habilitat, pots instal·lar MCP i modes personalitzats del Marketplace." + "description": "Quan està habilitat, pots instal·lar MCP i agents personalitzats del Marketplace." }, "MULTI_FILE_APPLY_DIFF": { "name": "Habilita edicions de fitxers concurrents", diff --git a/webview-ui/src/i18n/locales/ca/welcome.json b/webview-ui/src/i18n/locales/ca/welcome.json index 8ef62ea205..e88aa32a74 100644 --- a/webview-ui/src/i18n/locales/ca/welcome.json +++ b/webview-ui/src/i18n/locales/ca/welcome.json @@ -1,6 +1,6 @@ { "greeting": "Benvingut a Roo Code!", - "introduction": "Amb una gamma de Modes integrats i ampliables, Roo Code et permet planificar, arquitectar, codificar, depurar i augmentar la teva productivitat com mai abans.", + "introduction": "Amb una gamma de Agents integrats i ampliables, Roo Code et permet planificar, arquitectar, codificar, depurar i augmentar la teva productivitat com mai abans.", "notice": "Per començar, aquesta extensió necessita un proveïdor d'API.", "start": "Som-hi!", "routers": { diff --git a/webview-ui/src/i18n/locales/de/chat.json b/webview-ui/src/i18n/locales/de/chat.json index 3d43ad90a1..714c359685 100644 --- a/webview-ui/src/i18n/locales/de/chat.json +++ b/webview-ui/src/i18n/locales/de/chat.json @@ -216,14 +216,14 @@ "wantsToUseTool": "Roo möchte ein Tool auf dem {{serverName}} MCP-Server verwenden:", "wantsToAccessResource": "Roo möchte auf eine Ressource auf dem {{serverName}} MCP-Server zugreifen:" }, - "modes": { - "wantsToSwitch": "Roo möchte zum {{mode}}-Modus wechseln", - "wantsToSwitchWithReason": "Roo möchte zum {{mode}}-Modus wechseln, weil: {{reason}}", - "didSwitch": "Roo hat zum {{mode}}-Modus gewechselt", - "didSwitchWithReason": "Roo hat zum {{mode}}-Modus gewechselt, weil: {{reason}}" + "agents": { + "wantsToSwitch": "Roo möchte zum {{agent}}-Modus wechseln", + "wantsToSwitchWithReason": "Roo möchte zum {{agent}}-Modus wechseln, weil: {{reason}}", + "didSwitch": "Roo hat zum {{agent}}-Modus gewechselt", + "didSwitchWithReason": "Roo hat zum {{agent}}-Modus gewechselt, weil: {{reason}}" }, "subtasks": { - "wantsToCreate": "Roo möchte eine neue Teilaufgabe im {{mode}}-Modus erstellen:", + "wantsToCreate": "Roo möchte eine neue Teilaufgabe im {{agent}}-Modus erstellen:", "wantsToFinish": "Roo möchte diese Teilaufgabe abschließen", "newTaskContent": "Teilaufgabenanweisungen", "completionContent": "Teilaufgabe abgeschlossen", diff --git a/webview-ui/src/i18n/locales/de/marketplace.json b/webview-ui/src/i18n/locales/de/marketplace.json index be83e6d6d3..859ce66819 100644 --- a/webview-ui/src/i18n/locales/de/marketplace.json +++ b/webview-ui/src/i18n/locales/de/marketplace.json @@ -16,7 +16,7 @@ "type": { "label": "Nach Typ filtern:", "all": "Alle Typen", - "mode": "Modus", + "agent": "Modus", "mcpServer": "MCP-Server" }, "sort": { @@ -36,7 +36,7 @@ "none": "Keine" }, "type-group": { - "modes": "Modi", + "agents": "Modi", "mcps": "MCP-Server" }, "items": { @@ -130,9 +130,9 @@ } }, "removeConfirm": { - "mode": { + "agent": { "title": "Modus entfernen", - "message": "Bist du sicher, dass du den Modus „{{modeName}}“ entfernen möchtest?", + "message": "Bist du sicher, dass du den Modus „{{agentName}}“ entfernen möchtest?", "rulesWarning": "Dadurch werden auch alle zugehörigen Regeldateien für diesen Modus entfernt." }, "mcp": { diff --git a/webview-ui/src/i18n/locales/de/prompts.json b/webview-ui/src/i18n/locales/de/prompts.json index 229178abb3..b17e25d027 100644 --- a/webview-ui/src/i18n/locales/de/prompts.json +++ b/webview-ui/src/i18n/locales/de/prompts.json @@ -1,7 +1,7 @@ { "title": "Modi", "done": "Fertig", - "modes": { + "agents": { "title": "Modi", "createNewMode": "Neuen Modus erstellen", "importMode": "Modus importieren", @@ -49,8 +49,8 @@ "customInstructions": { "title": "Modusspezifische benutzerdefinierte Anweisungen (optional)", "resetToDefault": "Auf Standardwerte zurücksetzen", - "description": "Fügen Sie verhaltensspezifische Richtlinien für den Modus {{modeName}} hinzu.", - "loadFromFile": "Benutzerdefinierte Anweisungen für den Modus {{mode}} können auch aus dem Ordner .roo/rules-{{slug}}/ in deinem Arbeitsbereich geladen werden (.roorules-{{slug}} und .clinerules-{{slug}} sind veraltet und werden bald nicht mehr funktionieren)." + "description": "Fügen Sie verhaltensspezifische Richtlinien für den Modus {{agentName}} hinzu.", + "loadFromFile": "Benutzerdefinierte Anweisungen für den Modus {{agent}} können auch aus dem Ordner .roo/rules-{{slug}}/ in deinem Arbeitsbereich geladen werden (.roorules-{{slug}} und .clinerules-{{slug}} sind veraltet und werden bald nicht mehr funktionieren)." }, "exportMode": { "title": "Modus exportieren", @@ -82,7 +82,7 @@ "systemPrompt": { "preview": "System-Prompt Vorschau", "copy": "System-Prompt in Zwischenablage kopieren", - "title": "System-Prompt (Modus {{modeName}})" + "title": "System-Prompt (Modus {{agentName}})" }, "supportPrompts": { "title": "Support-Prompts", @@ -200,7 +200,7 @@ "allFiles": "alle Dateien", "deleteMode": { "title": "Modus löschen", - "message": "Möchten Sie den Modus \"{{modeName}}\" wirklich löschen?", + "message": "Möchten Sie den Modus \"{{agentName}}\" wirklich löschen?", "rulesFolder": "Dieser Modus hat einen Regelordner unter {{folderPath}}, der ebenfalls gelöscht wird.", "descriptionNoRules": "Möchten Sie diesen benutzerdefinierten Modus wirklich löschen?", "confirm": "Löschen", diff --git a/webview-ui/src/i18n/locales/en/chat.json b/webview-ui/src/i18n/locales/en/chat.json index 91568f7e36..5021e229b5 100644 --- a/webview-ui/src/i18n/locales/en/chat.json +++ b/webview-ui/src/i18n/locales/en/chat.json @@ -100,26 +100,26 @@ }, "stickyModels": { "title": "Sticky Models", - "description": "Each mode remembers your last used model" + "description": "Each agent remembers your last used model" }, "tools": { "title": "Tools", "description": "Allow the AI to solve problems by browsing the web, running commands, and more" }, - "customizableModes": { - "title": "Customizable Modes", + "customizableAgents": { + "title": "Customizable Agents", "description": "Specialized personas with their own behaviors and assigned models" } }, - "selectMode": "Select mode for interaction", + "selectAgent": "Select agent for interaction", "selectApiConfig": "Select API configuration", "enhancePrompt": "Enhance prompt with additional context", - "modeSelector": { - "title": "Modes", - "marketplace": "Mode Marketplace", - "settings": "Mode Settings", + "agentSelector": { + "title": "Agents", + "marketplace": "Agent Marketplace", + "settings": "Agent Settings", "description": "Specialized personas that tailor Roo's behavior.", - "searchPlaceholder": "Search modes...", + "searchPlaceholder": "Search agents...", "noResults": "No results found" }, "enhancePromptDescription": "The 'Enhance Prompt' button helps improve your prompt by providing additional context, clarification, or rephrasing. Try typing a prompt in here and clicking the button again to see how it works.", @@ -128,15 +128,15 @@ "stopTts": "Stop text-to-speech", "typeMessage": "Type a message...", "typeTask": "Type your task here...", - "addContext": "@ to add context, / to switch modes", + "addContext": "@ to add context, / to switch agents", "dragFiles": "hold shift to drag in files", "dragFilesImages": "hold shift to drag in files/images", "errorReadingFile": "Error reading file:", "noValidImages": "No valid images were processed", "separator": "Separator", "edit": "Edit...", - "forNextMode": "for next mode", - "forPreviousMode": "for previous mode", + "forNextAgent": "for next agent", + "forPreviousAgent": "for previous agent", "apiRequest": { "title": "API Request", "failed": "API Request Failed", @@ -235,14 +235,14 @@ "wantsToUseTool": "Roo wants to use a tool on the {{serverName}} MCP server:", "wantsToAccessResource": "Roo wants to access a resource on the {{serverName}} MCP server:" }, - "modes": { - "wantsToSwitch": "Roo wants to switch to {{mode}} mode", - "wantsToSwitchWithReason": "Roo wants to switch to {{mode}} mode because: {{reason}}", - "didSwitch": "Roo switched to {{mode}} mode", - "didSwitchWithReason": "Roo switched to {{mode}} mode because: {{reason}}" + "agents": { + "wantsToSwitch": "Roo wants to switch to {{agent}} agent", + "wantsToSwitchWithReason": "Roo wants to switch to {{agent}} agent because: {{reason}}", + "didSwitch": "Roo switched to {{agent}} agent", + "didSwitchWithReason": "Roo switched to {{agent}} agent because: {{reason}}" }, "subtasks": { - "wantsToCreate": "Roo wants to create a new subtask in {{mode}} mode:", + "wantsToCreate": "Roo wants to create a new subtask in {{agent}} agent:", "wantsToFinish": "Roo wants to finish this subtask", "newTaskContent": "Subtask Instructions", "completionContent": "Subtask Completed", diff --git a/webview-ui/src/i18n/locales/en/marketplace.json b/webview-ui/src/i18n/locales/en/marketplace.json index 7c20060598..4838ca1793 100644 --- a/webview-ui/src/i18n/locales/en/marketplace.json +++ b/webview-ui/src/i18n/locales/en/marketplace.json @@ -11,12 +11,12 @@ "search": { "placeholder": "Search marketplace items...", "placeholderMcp": "Search MCPs...", - "placeholderMode": "Search Modes..." + "placeholderAgent": "Search Agents..." }, "type": { "label": "Filter by type:", "all": "All types", - "mode": "Mode", + "agent": "Agent", "mcpServer": "MCP Server" }, "sort": { @@ -36,7 +36,7 @@ "none": "None" }, "type-group": { - "modes": "Modes", + "agents": "Agents", "mcps": "MCP Servers" }, "items": { @@ -77,7 +77,7 @@ }, "install": { "title": "Install {{name}}", - "titleMode": "Install {{name}} Mode", + "titleAgent": "Install {{name}} Agent", "titleMcp": "Install {{name}} MCP", "scope": "Installation Scope", "project": "Project (current workspace)", @@ -91,10 +91,10 @@ "successDescription": "Installation completed successfully", "installed": "Successfully installed!", "whatNextMcp": "You can now configure and use this MCP server. Click the MCP icon in the sidebar to switch tabs.", - "whatNextMode": "You can now use this mode. Click the Modes icon in the sidebar to switch tabs.", + "whatNextAgent": "You can now use this agent. Click the Agents icon in the sidebar to switch tabs.", "done": "Done", "goToMcp": "Go to MCP Tab", - "goToModes": "Go to Modes Settings", + "goToAgents": "Go to Agents Settings", "moreInfoMcp": "View {{name}} MCP documentation", "validationRequired": "Please provide a value for {{paramName}}" }, @@ -128,10 +128,10 @@ } }, "removeConfirm": { - "mode": { - "title": "Remove Mode", - "message": "Are you sure you want to remove the mode \"{{modeName}}\"?", - "rulesWarning": "This will also remove any associated rules files for this mode." + "agent": { + "title": "Remove Agent", + "message": "Are you sure you want to remove the agent \"{{agentName}}\"?", + "rulesWarning": "This will also remove any associated rules files for this agent." }, "mcp": { "title": "Remove MCP Server", diff --git a/webview-ui/src/i18n/locales/en/prompts.json b/webview-ui/src/i18n/locales/en/prompts.json index 5d0e6ff8db..1b668240a7 100644 --- a/webview-ui/src/i18n/locales/en/prompts.json +++ b/webview-ui/src/i18n/locales/en/prompts.json @@ -1,24 +1,24 @@ { - "title": "Modes", + "title": "Agents", "done": "Done", - "modes": { - "title": "Modes", - "createNewMode": "Create new mode", - "importMode": "Import Mode", - "editModesConfig": "Edit modes configuration", - "editGlobalModes": "Edit Global Modes", - "editProjectModes": "Edit Project Modes (.roomodes)", - "createModeHelpText": "Modes are specialized personas that tailor Roo's behavior. <0>Learn about Using Modes or <1>Customizing Modes.", - "selectMode": "Search modes", - "noMatchFound": "No modes found" + "agents": { + "title": "Agents", + "createNewAgent": "Create new agent", + "importAgent": "Import Agent", + "editAgentsConfig": "Edit agents configuration", + "editGlobalAgents": "Edit Global Agents", + "editProjectAgents": "Edit Project Agents (.roomodes)", + "createAgentHelpText": "Agents are specialized personas that tailor Roo's behavior. <0>Learn about Using Agents or <1>Customizing Agents.", + "selectAgent": "Search agents", + "noMatchFound": "No agents found" }, "apiConfiguration": { "title": "API Configuration", - "select": "Select which API configuration to use for this mode" + "select": "Select which API configuration to use for this agent" }, "tools": { "title": "Available Tools", - "builtInModesText": "Tools for built-in modes cannot be modified", + "builtInAgentsText": "Tools for built-in agents cannot be modified", "editTools": "Edit tools", "doneEditing": "Done editing", "allowedFiles": "Allowed files:", @@ -34,54 +34,54 @@ "roleDefinition": { "title": "Role Definition", "resetToDefault": "Reset to default", - "description": "Define Roo's expertise and personality for this mode. This description shapes how Roo presents itself and approaches tasks." + "description": "Define Roo's expertise and personality for this agent. This description shapes how Roo presents itself and approaches tasks." }, "description": { "title": "Short description (for humans)", "resetToDefault": "Reset to default description", - "description": "A brief description shown in the mode selector dropdown." + "description": "A brief description shown in the agent selector dropdown." }, "whenToUse": { "title": "When to Use (optional)", - "description": "Guidance for Roo for when this mode should be used. This helps the Orchestrator choose the right mode for a task.", + "description": "Guidance for Roo for when this agent should be used. This helps the Orchestrator choose the right agent for a task.", "resetToDefault": "Reset to default 'When to Use' description" }, "customInstructions": { - "title": "Mode-specific Custom Instructions (optional)", + "title": "Agent-specific Custom Instructions (optional)", "resetToDefault": "Reset to default", - "description": "Add behavioral guidelines specific to {{modeName}} mode.", - "loadFromFile": "Custom instructions specific to {{mode}} mode can also be loaded from the .roo/rules-{{slug}}/ folder in your workspace (.roorules-{{slug}} and .clinerules-{{slug}} are deprecated and will stop working soon)." + "description": "Add behavioral guidelines specific to {{agentName}} agent.", + "loadFromFile": "Custom instructions specific to {{agent}} agent can also be loaded from the .roo/rules-{{slug}}/ folder in your workspace (.roorules-{{slug}} and .clinerules-{{slug}} are deprecated and will stop working soon)." }, - "exportMode": { - "title": "Export Mode", - "description": "Export this mode with rules from the .roo/rules-{{slug}}/ folder combined into a shareable YAML file. The original files remain unchanged.", + "exportAgent": { + "title": "Export Agent", + "description": "Export this agent with rules from the .roo/rules-{{slug}}/ folder combined into a shareable YAML file. The original files remain unchanged.", "exporting": "Exporting..." }, - "importMode": { - "selectLevel": "Choose where to import this mode:", + "importAgent": { + "selectLevel": "Choose where to import this agent:", "import": "Import", "importing": "Importing...", "global": { "label": "Global Level", - "description": "Available across all projects. If the exported mode contained rules files, they will be recreated in the global .roo/rules-{slug}/ folder." + "description": "Available across all projects. If the exported agent contained rules files, they will be recreated in the global .roo/rules-{slug}/ folder." }, "project": { "label": "Project Level", - "description": "Only available in this workspace. If the exported mode contained rules files, they will be recreated in .roo/rules-{slug}/ folder." + "description": "Only available in this workspace. If the exported agent contained rules files, they will be recreated in .roo/rules-{slug}/ folder." } }, "advanced": { "title": "Advanced: Override System Prompt" }, "globalCustomInstructions": { - "title": "Custom Instructions for All Modes", - "description": "These instructions apply to all modes. They provide a base set of behaviors that can be enhanced by mode-specific instructions below. <0>Learn more", + "title": "Custom Instructions for All Agents", + "description": "These instructions apply to all agents. They provide a base set of behaviors that can be enhanced by agent-specific instructions below. <0>Learn more", "loadFromFile": "Instructions can also be loaded from the .roo/rules/ folder in your workspace (.roorules and .clinerules are deprecated and will stop working soon)." }, "systemPrompt": { "preview": "Preview System Prompt", "copy": "Copy system prompt to clipboard", - "title": "System Prompt ({{modeName}} mode)" + "title": "System Prompt ({{agentName}} agent)" }, "supportPrompts": { "title": "Support Prompts", @@ -148,11 +148,11 @@ "description": "<2>⚠️ Warning: This advanced feature bypasses safeguards. <1>READ THIS BEFORE USING!Override the default system prompt by creating a file at .roo/system-prompt-{{slug}}." }, "createModeDialog": { - "title": "Create New Mode", + "title": "Create New Agent", "close": "Close", "name": { "label": "Name", - "placeholder": "Enter mode name" + "placeholder": "Enter agent name" }, "slug": { "label": "Slug", @@ -160,7 +160,7 @@ }, "saveLocation": { "label": "Save Location", - "description": "Choose where to save this mode. Project-specific modes take precedence over global modes.", + "description": "Choose where to save this agent. Project-specific agents take precedence over global agents.", "global": { "label": "Global", "description": "Available in all workspaces" @@ -172,36 +172,36 @@ }, "roleDefinition": { "label": "Role Definition", - "description": "Define Roo's expertise and personality for this mode." + "description": "Define Roo's expertise and personality for this agent." }, "description": { "label": "Short description (for humans)", - "description": "A brief description shown in the mode selector dropdown." + "description": "A brief description shown in the agent selector dropdown." }, "whenToUse": { "label": "When to Use (optional)", - "description": "Guidance for Roo for when this mode should be used. This helps the Orchestrator choose the right mode for a task." + "description": "Guidance for Roo for when this agent should be used. This helps the Orchestrator choose the right agent for a task." }, "tools": { "label": "Available Tools", - "description": "Select which tools this mode can use." + "description": "Select which tools this agent can use." }, "customInstructions": { "label": "Custom Instructions (optional)", - "description": "Add behavioral guidelines specific to this mode." + "description": "Add behavioral guidelines specific to this agent." }, "buttons": { "cancel": "Cancel", - "create": "Create Mode" + "create": "Create Agent" }, - "deleteMode": "Delete mode" + "deleteAgent": "Delete agent" }, "allFiles": "all files", - "deleteMode": { - "title": "Delete Mode", - "message": "Are you sure you want to delete the mode \"{{modeName}}\"?", - "rulesFolder": "This mode has a rules folder at {{folderPath}} that will also be deleted.", - "descriptionNoRules": "Are you sure you want to delete this custom mode?", + "deleteAgent": { + "title": "Delete Agent", + "message": "Are you sure you want to delete the agent \"{{agentName}}\"?", + "rulesFolder": "This agent has a rules folder at {{folderPath}} that will also be deleted.", + "descriptionNoRules": "Are you sure you want to delete this custom agent?", "confirm": "Delete", "cancel": "Cancel" } diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index cd83190326..98e8253882 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -161,9 +161,9 @@ "label": "MCP", "description": "Enable auto-approval of individual MCP tools in the MCP Servers view (requires both this setting and the tool's individual \"Always allow\" checkbox)" }, - "modeSwitch": { - "label": "Mode", - "description": "Automatically switch between different modes without requiring approval" + "agentSwitch": { + "label": "Agent", + "description": "Automatically switch between different agents without requiring approval" }, "subtasks": { "label": "Subtasks", @@ -641,7 +641,7 @@ }, "POWER_STEERING": { "name": "Use experimental \"power steering\" mode", - "description": "When enabled, Roo will remind the model about the details of its current mode definition more frequently. This will lead to stronger adherence to role definitions and custom instructions, but will use more tokens per message." + "description": "When enabled, Roo will remind the model about the details of its current agent definition more frequently. This will lead to stronger adherence to role definitions and custom instructions, but will use more tokens per message." }, "CONCURRENT_FILE_READS": { "name": "Enable concurrent file reads", @@ -653,7 +653,7 @@ }, "MARKETPLACE": { "name": "Enable Marketplace", - "description": "When enabled, you can install MCPs and custom modes from the Marketplace." + "description": "When enabled, you can install MCPs and custom agents from the Marketplace." }, "MULTI_FILE_APPLY_DIFF": { "name": "Enable concurrent file edits", diff --git a/webview-ui/src/i18n/locales/en/welcome.json b/webview-ui/src/i18n/locales/en/welcome.json index 2202f6fd61..c1fe4fd00c 100644 --- a/webview-ui/src/i18n/locales/en/welcome.json +++ b/webview-ui/src/i18n/locales/en/welcome.json @@ -1,6 +1,6 @@ { "greeting": "Welcome to Roo Code!", - "introduction": "With a range of built-in and extensible Modes, Roo Code lets you plan, architect, code, debug and boost your productivity like never before.", + "introduction": "With a range of built-in and extensible Agents, Roo Code lets you plan, architect, code, debug and boost your productivity like never before.", "notice": "To get started, this extension needs an API provider.", "start": "Let's go!", "routers": { diff --git a/webview-ui/src/i18n/locales/es/chat.json b/webview-ui/src/i18n/locales/es/chat.json index 7effb8f6ca..1afbab38fc 100644 --- a/webview-ui/src/i18n/locales/es/chat.json +++ b/webview-ui/src/i18n/locales/es/chat.json @@ -216,14 +216,14 @@ "wantsToUseTool": "Roo quiere usar una herramienta en el servidor MCP {{serverName}}:", "wantsToAccessResource": "Roo quiere acceder a un recurso en el servidor MCP {{serverName}}:" }, - "modes": { - "wantsToSwitch": "Roo quiere cambiar a modo {{mode}}", - "wantsToSwitchWithReason": "Roo quiere cambiar a modo {{mode}} porque: {{reason}}", - "didSwitch": "Roo cambió a modo {{mode}}", - "didSwitchWithReason": "Roo cambió a modo {{mode}} porque: {{reason}}" + "agents": { + "wantsToSwitch": "Roo quiere cambiar a modo {{agent}}", + "wantsToSwitchWithReason": "Roo quiere cambiar a modo {{agent}} porque: {{reason}}", + "didSwitch": "Roo cambió a modo {{agent}}", + "didSwitchWithReason": "Roo cambió a modo {{agent}} porque: {{reason}}" }, "subtasks": { - "wantsToCreate": "Roo quiere crear una nueva subtarea en modo {{mode}}:", + "wantsToCreate": "Roo quiere crear una nueva subtarea en modo {{agent}}:", "wantsToFinish": "Roo quiere finalizar esta subtarea", "newTaskContent": "Instrucciones de la subtarea", "completionContent": "Subtarea completada", diff --git a/webview-ui/src/i18n/locales/es/marketplace.json b/webview-ui/src/i18n/locales/es/marketplace.json index 39a45407ae..1d92be3e3f 100644 --- a/webview-ui/src/i18n/locales/es/marketplace.json +++ b/webview-ui/src/i18n/locales/es/marketplace.json @@ -16,7 +16,7 @@ "type": { "label": "Filtrar por tipo:", "all": "Todos los tipos", - "mode": "Modo", + "agent": "Modo", "mcpServer": "Servidor MCP" }, "sort": { @@ -36,7 +36,7 @@ "none": "Ninguno" }, "type-group": { - "modes": "Modos", + "agents": "Modos", "mcps": "Servidores MCP" }, "items": { @@ -130,9 +130,9 @@ } }, "removeConfirm": { - "mode": { + "agent": { "title": "Eliminar modo", - "message": "¿Estás seguro de que quieres eliminar el modo \"{{modeName}}\"?", + "message": "¿Estás seguro de que quieres eliminar el modo \"{{agentName}}\"?", "rulesWarning": "Esto también eliminará cualquier archivo de reglas asociado para este modo." }, "mcp": { diff --git a/webview-ui/src/i18n/locales/es/prompts.json b/webview-ui/src/i18n/locales/es/prompts.json index 50ab1cdb76..7b1c1c17a5 100644 --- a/webview-ui/src/i18n/locales/es/prompts.json +++ b/webview-ui/src/i18n/locales/es/prompts.json @@ -1,7 +1,7 @@ { "title": "Modos", "done": "Listo", - "modes": { + "agents": { "title": "Modos", "createNewMode": "Crear nuevo modo", "importMode": "Importar modo", @@ -49,8 +49,8 @@ "customInstructions": { "title": "Instrucciones personalizadas para el modo (opcional)", "resetToDefault": "Restablecer a valores predeterminados", - "description": "Agrega directrices de comportamiento específicas para el modo {{modeName}}.", - "loadFromFile": "Las instrucciones personalizadas para el modo {{mode}} también se pueden cargar desde la carpeta .roo/rules-{{slug}}/ en tu espacio de trabajo (.roorules-{{slug}} y .clinerules-{{slug}} están obsoletos y dejarán de funcionar pronto)." + "description": "Agrega directrices de comportamiento específicas para el modo {{agentName}}.", + "loadFromFile": "Las instrucciones personalizadas para el modo {{agent}} también se pueden cargar desde la carpeta .roo/rules-{{slug}}/ en tu espacio de trabajo (.roorules-{{slug}} y .clinerules-{{slug}} están obsoletos y dejarán de funcionar pronto)." }, "exportMode": { "title": "Exportar modo", @@ -82,7 +82,7 @@ "systemPrompt": { "preview": "Vista previa de la solicitud del sistema", "copy": "Copiar solicitud del sistema al portapapeles", - "title": "Solicitud del sistema (modo {{modeName}})" + "title": "Solicitud del sistema (modo {{agentName}})" }, "supportPrompts": { "title": "Solicitudes de soporte", @@ -200,7 +200,7 @@ "allFiles": "todos los archivos", "deleteMode": { "title": "Eliminar modo", - "message": "¿Estás seguro de que quieres eliminar el modo \"{{modeName}}\"?", + "message": "¿Estás seguro de que quieres eliminar el modo \"{{agentName}}\"?", "rulesFolder": "Este modo tiene una carpeta de reglas en {{folderPath}} que también se eliminará.", "descriptionNoRules": "¿Estás seguro de que quieres eliminar este modo personalizado?", "confirm": "Eliminar", diff --git a/webview-ui/src/i18n/locales/fr/chat.json b/webview-ui/src/i18n/locales/fr/chat.json index bc677bc770..b2ad420ca0 100644 --- a/webview-ui/src/i18n/locales/fr/chat.json +++ b/webview-ui/src/i18n/locales/fr/chat.json @@ -88,19 +88,19 @@ "description": "Divisez les tâches en parties plus petites et gérables." }, "stickyModels": { - "title": "Modes persistants", - "description": "Chaque mode se souvient de votre dernier modèle utilisé" + "title": "Agents persistants", + "description": "Chaque agent se souvient de votre dernier modèle utilisé" }, "tools": { "title": "Outils", "description": "Permettez à l'IA de résoudre des problèmes en naviguant sur le Web, en exécutant des commandes, et plus encore." }, "customizableModes": { - "title": "Modes personnalisables", + "title": "Agents personnalisables", "description": "Des personas spécialisés avec leurs propres comportements et modèles assignés" } }, - "selectMode": "Sélectionner le mode d'interaction", + "selectMode": "Sélectionner le agent d'interaction", "selectApiConfig": "Sélectionner la configuration de l'API", "enhancePrompt": "Améliorer la requête avec un contexte supplémentaire", "addImages": "Ajouter des images au message", @@ -108,24 +108,24 @@ "stopTts": "Arrêter la synthèse vocale", "typeMessage": "Écrivez un message...", "typeTask": "Écrivez votre tâche ici...", - "addContext": "@ pour ajouter du contexte, / pour changer de mode", + "addContext": "@ pour ajouter du contexte, / pour changer de agent", "dragFiles": "maintenir Maj pour glisser des fichiers", "dragFilesImages": "maintenir Maj pour glisser des fichiers/images", "enhancePromptDescription": "Le bouton 'Améliorer la requête' aide à améliorer votre demande en fournissant un contexte supplémentaire, des clarifications ou des reformulations. Essayez de taper une demande ici et cliquez à nouveau sur le bouton pour voir comment cela fonctionne.", "modeSelector": { - "title": "Modes", - "marketplace": "Marketplace de Modes", - "settings": "Paramètres des Modes", + "title": "Agents", + "marketplace": "Marketplace de Agents", + "settings": "Paramètres des Agents", "description": "Personas spécialisés qui adaptent le comportement de Roo.", - "searchPlaceholder": "Rechercher des modes...", + "searchPlaceholder": "Rechercher des agents...", "noResults": "Aucun résultat trouvé" }, "errorReadingFile": "Erreur lors de la lecture du fichier :", "noValidImages": "Aucune image valide n'a été traitée", "separator": "Séparateur", "edit": "Éditer...", - "forNextMode": "pour le prochain mode", - "forPreviousMode": "pour le mode précédent", + "forNextMode": "pour le prochain agent", + "forPreviousMode": "pour le agent précédent", "error": "Erreur", "diffError": { "title": "Modification échouée" @@ -216,14 +216,14 @@ "wantsToUseTool": "Roo veut utiliser un outil sur le serveur MCP {{serverName}} :", "wantsToAccessResource": "Roo veut accéder à une ressource sur le serveur MCP {{serverName}} :" }, - "modes": { - "wantsToSwitch": "Roo veut passer au mode {{mode}}", - "wantsToSwitchWithReason": "Roo veut passer au mode {{mode}} car : {{reason}}", - "didSwitch": "Roo est passé au mode {{mode}}", - "didSwitchWithReason": "Roo est passé au mode {{mode}} car : {{reason}}" + "agents": { + "wantsToSwitch": "Roo veut passer au agent {{agent}}", + "wantsToSwitchWithReason": "Roo veut passer au agent {{agent}} car : {{reason}}", + "didSwitch": "Roo est passé au agent {{agent}}", + "didSwitchWithReason": "Roo est passé au agent {{agent}} car : {{reason}}" }, "subtasks": { - "wantsToCreate": "Roo veut créer une nouvelle sous-tâche en mode {{mode}} :", + "wantsToCreate": "Roo veut créer une nouvelle sous-tâche en agent {{agent}} :", "wantsToFinish": "Roo veut terminer cette sous-tâche", "newTaskContent": "Instructions de la sous-tâche", "completionContent": "Sous-tâche terminée", diff --git a/webview-ui/src/i18n/locales/fr/history.json b/webview-ui/src/i18n/locales/fr/history.json index 80f08341a5..50551a80ca 100644 --- a/webview-ui/src/i18n/locales/fr/history.json +++ b/webview-ui/src/i18n/locales/fr/history.json @@ -5,8 +5,8 @@ "cache": "Cache: +{{writes}} → {{reads}}", "apiCost": "Coût API: ${{cost}}", "history": "Historique", - "exitSelectionMode": "Quitter le mode sélection", - "enterSelectionMode": "Entrer en mode sélection", + "exitSelectionMode": "Quitter le agent sélection", + "enterSelectionMode": "Entrer en agent sélection", "done": "Terminé", "searchPlaceholder": "Rechercher dans l'historique...", "newest": "Plus récentes", @@ -25,7 +25,7 @@ "cancel": "Annuler", "delete": "Supprimer", "exitSelection": "Quitter la sélection", - "selectionMode": "Mode sélection", + "selectionMode": "Agent sélection", "deselectAll": "Tout désélectionner", "selectAll": "Tout sélectionner", "selectedItems": "{{selected}}/{{total}} éléments sélectionnés", diff --git a/webview-ui/src/i18n/locales/fr/marketplace.json b/webview-ui/src/i18n/locales/fr/marketplace.json index 05a17150f7..55607225eb 100644 --- a/webview-ui/src/i18n/locales/fr/marketplace.json +++ b/webview-ui/src/i18n/locales/fr/marketplace.json @@ -11,12 +11,12 @@ "search": { "placeholder": "Rechercher des éléments du marketplace...", "placeholderMcp": "Rechercher des MCPs...", - "placeholderMode": "Rechercher des modes..." + "placeholderMode": "Rechercher des agents..." }, "type": { "label": "Filtrer par type :", "all": "Tous les types", - "mode": "Mode", + "agent": "Agent", "mcpServer": "Serveur MCP" }, "sort": { @@ -36,7 +36,7 @@ "none": "Aucun" }, "type-group": { - "modes": "Modes", + "agents": "Agents", "mcps": "Serveurs MCP" }, "items": { @@ -79,7 +79,7 @@ }, "install": { "title": "Installer {{name}}", - "titleMode": "Installer le mode {{name}}", + "titleMode": "Installer le agent {{name}}", "titleMcp": "Installer le MCP {{name}}", "scope": "Portée d'installation", "project": "Projet (espace de travail actuel)", @@ -92,10 +92,10 @@ "successDescription": "Installation terminée avec succès", "installed": "Installé avec succès !", "whatNextMcp": "Vous pouvez maintenant configurer et utiliser ce serveur MCP. Cliquez sur l'icône MCP dans la barre latérale pour changer d'onglet.", - "whatNextMode": "Vous pouvez maintenant utiliser ce mode. Cliquez sur l'icône Modes dans la barre latérale pour changer d'onglet.", + "whatNextMode": "Vous pouvez maintenant utiliser ce agent. Cliquez sur l'icône Agents dans la barre latérale pour changer d'onglet.", "done": "Terminé", "goToMcp": "Aller à l'onglet MCP", - "goToModes": "Aller aux paramètres des Modes", + "goToModes": "Aller aux paramètres des Agents", "moreInfoMcp": "Voir la documentation MCP de {{name}}", "validationRequired": "Veuillez fournir une valeur pour {{paramName}}", "prerequisites": "Prérequis" @@ -130,10 +130,10 @@ } }, "removeConfirm": { - "mode": { - "title": "Supprimer le mode", - "message": "Êtes-vous sûr de vouloir supprimer le mode « {{modeName}} » ?", - "rulesWarning": "Cela supprimera également tous les fichiers de règles associés à ce mode." + "agent": { + "title": "Supprimer le agent", + "message": "Êtes-vous sûr de vouloir supprimer le agent « {{agentName}} » ?", + "rulesWarning": "Cela supprimera également tous les fichiers de règles associés à ce agent." }, "mcp": { "title": "Supprimer le serveur MCP", diff --git a/webview-ui/src/i18n/locales/fr/prompts.json b/webview-ui/src/i18n/locales/fr/prompts.json index d48ef28fa4..9be12d03e5 100644 --- a/webview-ui/src/i18n/locales/fr/prompts.json +++ b/webview-ui/src/i18n/locales/fr/prompts.json @@ -1,24 +1,24 @@ { - "title": "Modes", + "title": "Agents", "done": "Terminé", - "modes": { - "title": "Modes", - "createNewMode": "Créer un nouveau mode", - "importMode": "Importer le mode", - "noMatchFound": "Aucun mode trouvé", - "editModesConfig": "Modifier la configuration des modes", - "editGlobalModes": "Modifier les modes globaux", - "editProjectModes": "Modifier les modes du projet (.roomodes)", - "createModeHelpText": "Les modes sont des personas spécialisés qui adaptent le comportement de Roo. <0>En savoir plus sur l'utilisation des modes ou <1>la personnalisation des modes.", - "selectMode": "Rechercher les modes" + "agents": { + "title": "Agents", + "createNewMode": "Créer un nouveau agent", + "importMode": "Importer le agent", + "noMatchFound": "Aucun agent trouvé", + "editModesConfig": "Modifier la configuration des agents", + "editGlobalModes": "Modifier les agents globaux", + "editProjectModes": "Modifier les agents du projet (.roomodes)", + "createModeHelpText": "Les agents sont des personas spécialisés qui adaptent le comportement de Roo. <0>En savoir plus sur l'utilisation des agents ou <1>la personnalisation des agents.", + "selectMode": "Rechercher les agents" }, "apiConfiguration": { "title": "Configuration API", - "select": "Sélectionnez la configuration API à utiliser pour ce mode" + "select": "Sélectionnez la configuration API à utiliser pour ce agent" }, "tools": { "title": "Outils disponibles", - "builtInModesText": "Les outils pour les modes intégrés ne peuvent pas être modifiés", + "builtInModesText": "Les outils pour les agents intégrés ne peuvent pas être modifiés", "editTools": "Modifier les outils", "doneEditing": "Terminer la modification", "allowedFiles": "Fichiers autorisés :", @@ -34,55 +34,55 @@ "roleDefinition": { "title": "Définition du rôle", "resetToDefault": "Réinitialiser aux valeurs par défaut", - "description": "Définissez l'expertise et la personnalité de Roo pour ce mode. Cette description façonne la manière dont Roo se présente et aborde les tâches." + "description": "Définissez l'expertise et la personnalité de Roo pour ce agent. Cette description façonne la manière dont Roo se présente et aborde les tâches." }, "description": { "title": "Description courte (pour humains)", "resetToDefault": "Réinitialiser à la description par défaut", - "description": "Une brève description affichée dans le menu déroulant du sélecteur de mode." + "description": "Une brève description affichée dans le menu déroulant du sélecteur de agent." }, "whenToUse": { "title": "Quand utiliser (optionnel)", - "description": "Décrivez quand ce mode doit être utilisé. Cela aide l'Orchestrateur à choisir le mode approprié pour une tâche.", + "description": "Décrivez quand ce agent doit être utilisé. Cela aide l'Orchestrateur à choisir le agent approprié pour une tâche.", "resetToDefault": "Réinitialiser la description 'Quand utiliser' aux valeurs par défaut" }, "customInstructions": { - "title": "Instructions personnalisées spécifiques au mode (optionnel)", + "title": "Instructions personnalisées spécifiques au agent (optionnel)", "resetToDefault": "Réinitialiser aux valeurs par défaut", - "description": "Ajoutez des directives comportementales spécifiques au mode {{modeName}}.", - "loadFromFile": "Les instructions personnalisées spécifiques au mode {{mode}} peuvent également être chargées depuis le dossier .roo/rules-{{slug}}/ dans votre espace de travail (.roorules-{{slug}} et .clinerules-{{slug}} sont obsolètes et cesseront de fonctionner bientôt)." + "description": "Ajoutez des directives comportementales spécifiques au agent {{agentName}}.", + "loadFromFile": "Les instructions personnalisées spécifiques au agent {{agent}} peuvent également être chargées depuis le dossier .roo/rules-{{slug}}/ dans votre espace de travail (.roorules-{{slug}} et .clinerules-{{slug}} sont obsolètes et cesseront de fonctionner bientôt)." }, "exportMode": { - "title": "Exporter le mode", - "description": "Exporte ce mode vers un fichier YAML avec toutes les règles incluses pour un partage facile avec d'autres.", - "export": "Exporter le mode", + "title": "Exporter le agent", + "description": "Exporte ce agent vers un fichier YAML avec toutes les règles incluses pour un partage facile avec d'autres.", + "export": "Exporter le agent", "exporting": "Exportation..." }, "importMode": { - "selectLevel": "Choisissez où importer ce mode :", + "selectLevel": "Choisissez où importer ce agent :", "import": "Importer", "importing": "Importation...", "global": { "label": "Niveau global", - "description": "Disponible dans tous les projets. Si le mode exporté contenait des fichiers de règles, ils seront recréés dans le dossier global .roo/rules-{slug}/." + "description": "Disponible dans tous les projets. Si le agent exporté contenait des fichiers de règles, ils seront recréés dans le dossier global .roo/rules-{slug}/." }, "project": { "label": "Niveau projet", - "description": "Disponible uniquement dans cet espace de travail. Si le mode exporté contenait des fichiers de règles, ils seront recréés dans le dossier .roo/rules-{slug}/." + "description": "Disponible uniquement dans cet espace de travail. Si le agent exporté contenait des fichiers de règles, ils seront recréés dans le dossier .roo/rules-{slug}/." } }, "advanced": { "title": "Avancé" }, "globalCustomInstructions": { - "title": "Instructions personnalisées pour tous les modes", - "description": "Ces instructions s'appliquent à tous les modes. Elles fournissent un ensemble de comportements de base qui peuvent être améliorés par des instructions spécifiques au mode ci-dessous. <0>En savoir plus", + "title": "Instructions personnalisées pour tous les agents", + "description": "Ces instructions s'appliquent à tous les agents. Elles fournissent un ensemble de comportements de base qui peuvent être améliorés par des instructions spécifiques au agent ci-dessous. <0>En savoir plus", "loadFromFile": "Les instructions peuvent également être chargées depuis le dossier .roo/rules/ dans votre espace de travail (.roorules et .clinerules sont obsolètes et cesseront de fonctionner bientôt)." }, "systemPrompt": { "preview": "Aperçu du prompt système", "copy": "Copier le prompt système dans le presse-papiers", - "title": "Prompt système (mode {{modeName}})" + "title": "Prompt système (agent {{agentName}})" }, "supportPrompts": { "title": "Prompts de support", @@ -149,11 +149,11 @@ "description": "<2>⚠️ Attention : Cette fonctionnalité avancée contourne les mesures de protection. <1>LISEZ CECI AVANT UTILISATION !Remplacez le prompt système par défaut en créant un fichier à l'emplacement .roo/system-prompt-{{slug}}." }, "createModeDialog": { - "title": "Créer un nouveau mode", + "title": "Créer un nouveau agent", "close": "Fermer", "name": { "label": "Nom", - "placeholder": "Entrez le nom du mode" + "placeholder": "Entrez le nom du agent" }, "slug": { "label": "Slug", @@ -161,7 +161,7 @@ }, "saveLocation": { "label": "Emplacement d'enregistrement", - "description": "Choisissez où enregistrer ce mode. Les modes spécifiques au projet ont priorité sur les modes globaux.", + "description": "Choisissez où enregistrer ce agent. Les agents spécifiques au projet ont priorité sur les agents globaux.", "global": { "label": "Global", "description": "Disponible dans tous les espaces de travail" @@ -173,36 +173,36 @@ }, "roleDefinition": { "label": "Définition du rôle", - "description": "Définissez l'expertise et la personnalité de Roo pour ce mode." + "description": "Définissez l'expertise et la personnalité de Roo pour ce agent." }, "whenToUse": { "label": "Quand utiliser (optionnel)", - "description": "Fournissez une description claire de quand ce mode est le plus efficace et pour quels types de tâches il excelle." + "description": "Fournissez une description claire de quand ce agent est le plus efficace et pour quels types de tâches il excelle." }, "tools": { "label": "Outils disponibles", - "description": "Sélectionnez quels outils ce mode peut utiliser." + "description": "Sélectionnez quels outils ce agent peut utiliser." }, "description": { "label": "Description courte (pour humains)", - "description": "Une brève description affichée dans le menu déroulant du sélecteur de mode." + "description": "Une brève description affichée dans le menu déroulant du sélecteur de agent." }, "customInstructions": { "label": "Instructions personnalisées (optionnel)", - "description": "Ajoutez des directives comportementales spécifiques à ce mode." + "description": "Ajoutez des directives comportementales spécifiques à ce agent." }, "buttons": { "cancel": "Annuler", - "create": "Créer le mode" + "create": "Créer le agent" }, - "deleteMode": "Supprimer le mode" + "deleteMode": "Supprimer le agent" }, "allFiles": "tous les fichiers", "deleteMode": { - "title": "Supprimer le mode", - "message": "Êtes-vous sûr de vouloir supprimer le mode \"{{modeName}}\" ?", - "rulesFolder": "Ce mode a un dossier de règles à {{folderPath}} qui sera également supprimé.", - "descriptionNoRules": "Êtes-vous sûr de vouloir supprimer ce mode personnalisé ?", + "title": "Supprimer le agent", + "message": "Êtes-vous sûr de vouloir supprimer le agent \"{{agentName}}\" ?", + "rulesFolder": "Ce agent a un dossier de règles à {{folderPath}} qui sera également supprimé.", + "descriptionNoRules": "Êtes-vous sûr de vouloir supprimer ce agent personnalisé ?", "confirm": "Supprimer", "cancel": "Annuler" } diff --git a/webview-ui/src/i18n/locales/fr/settings.json b/webview-ui/src/i18n/locales/fr/settings.json index ce3e2526de..46afd50c7f 100644 --- a/webview-ui/src/i18n/locales/fr/settings.json +++ b/webview-ui/src/i18n/locales/fr/settings.json @@ -165,8 +165,8 @@ "description": "Activer l'approbation automatique des outils MCP individuels dans la vue des serveurs MCP (nécessite à la fois ce paramètre et la case à cocher \"Toujours autoriser\" de l'outil)" }, "modeSwitch": { - "label": "Mode", - "description": "Basculer automatiquement entre différents modes sans nécessiter d'approbation" + "label": "Agent", + "description": "Basculer automatiquement entre différents agents sans nécessiter d'approbation" }, "subtasks": { "label": "Sous-tâches", @@ -332,7 +332,7 @@ "draftModelId": "ID du modèle brouillon", "draftModelDesc": "Le modèle brouillon doit être de la même famille de modèles pour que le décodage spéculatif fonctionne correctement.", "selectDraftModel": "Sélectionner le modèle brouillon", - "noModelsFound": "Aucun modèle brouillon trouvé. Veuillez vous assurer que LM Studio est en cours d'exécution avec le mode serveur activé.", + "noModelsFound": "Aucun modèle brouillon trouvé. Veuillez vous assurer que LM Studio est en cours d'exécution avec le agent serveur activé.", "description": "LM Studio vous permet d'exécuter des modèles localement sur votre ordinateur. Pour obtenir des instructions sur la mise en route, consultez leur guide de démarrage rapide. Vous devrez également démarrer la fonction serveur local de LM Studio pour l'utiliser avec cette extension. Remarque : Roo Code utilise des prompts complexes et fonctionne mieux avec les modèles Claude. Les modèles moins performants peuvent ne pas fonctionner comme prévu." }, "ollama": { @@ -641,8 +641,8 @@ "description": "Activer l'outil d'insertion de contenu expérimental, permettant à Roo d'insérer du contenu à des numéros de ligne spécifiques sans avoir besoin de créer un diff." }, "POWER_STEERING": { - "name": "Utiliser le mode \"direction assistée\" expérimental", - "description": "Lorsqu'il est activé, Roo rappellera plus fréquemment au modèle les détails de sa définition de mode actuelle. Cela conduira à une adhérence plus forte aux définitions de rôles et aux instructions personnalisées, mais utilisera plus de tokens par message." + "name": "Utiliser le agent \"direction assistée\" expérimental", + "description": "Lorsqu'il est activé, Roo rappellera plus fréquemment au modèle les détails de sa définition de agent actuelle. Cela conduira à une adhérence plus forte aux définitions de rôles et aux instructions personnalisées, mais utilisera plus de tokens par message." }, "MULTI_SEARCH_AND_REPLACE": { "name": "Utiliser l'outil diff multi-blocs expérimental", @@ -654,7 +654,7 @@ }, "MARKETPLACE": { "name": "Activer le Marketplace", - "description": "Lorsque cette option est activée, tu peux installer des MCP et des modes personnalisés depuis le Marketplace." + "description": "Lorsque cette option est activée, tu peux installer des MCP et des agents personnalisés depuis le Marketplace." }, "MULTI_FILE_APPLY_DIFF": { "name": "Activer les éditions de fichiers concurrentes", diff --git a/webview-ui/src/i18n/locales/fr/welcome.json b/webview-ui/src/i18n/locales/fr/welcome.json index 2e1ead38cb..aa40ecfa39 100644 --- a/webview-ui/src/i18n/locales/fr/welcome.json +++ b/webview-ui/src/i18n/locales/fr/welcome.json @@ -1,6 +1,6 @@ { "greeting": "Bienvenue sur Roo Code !", - "introduction": "Avec une gamme de Modes intégrés et extensibles, Roo Code te permet de planifier, architecturer, coder, déboguer et augmenter ta productivité comme jamais auparavant.", + "introduction": "Avec une gamme de Agents intégrés et extensibles, Roo Code te permet de planifier, architecturer, coder, déboguer et augmenter ta productivité comme jamais auparavant.", "notice": "Pour commencer, cette extension a besoin d'un fournisseur d'API.", "start": "C'est parti !", "routers": { diff --git a/webview-ui/src/i18n/locales/hi/chat.json b/webview-ui/src/i18n/locales/hi/chat.json index d050373c46..538141c754 100644 --- a/webview-ui/src/i18n/locales/hi/chat.json +++ b/webview-ui/src/i18n/locales/hi/chat.json @@ -216,14 +216,14 @@ "wantsToUseTool": "Roo {{serverName}} MCP सर्वर पर एक टूल का उपयोग करना चाहता है:", "wantsToAccessResource": "Roo {{serverName}} MCP सर्वर पर एक संसाधन का उपयोग करना चाहता है:" }, - "modes": { - "wantsToSwitch": "Roo {{mode}} मोड में स्विच करना चाहता है", - "wantsToSwitchWithReason": "Roo {{mode}} मोड में स्विच करना चाहता है क्योंकि: {{reason}}", - "didSwitch": "Roo {{mode}} मोड में स्विच कर गया", - "didSwitchWithReason": "Roo {{mode}} मोड में स्विच कर गया क्योंकि: {{reason}}" + "agents": { + "wantsToSwitch": "Roo {{agent}} मोड में स्विच करना चाहता है", + "wantsToSwitchWithReason": "Roo {{agent}} मोड में स्विच करना चाहता है क्योंकि: {{reason}}", + "didSwitch": "Roo {{agent}} मोड में स्विच कर गया", + "didSwitchWithReason": "Roo {{agent}} मोड में स्विच कर गया क्योंकि: {{reason}}" }, "subtasks": { - "wantsToCreate": "Roo {{mode}} मोड में एक नया उपकार्य बनाना चाहता है:", + "wantsToCreate": "Roo {{agent}} मोड में एक नया उपकार्य बनाना चाहता है:", "wantsToFinish": "Roo इस उपकार्य को समाप्त करना चाहता है", "newTaskContent": "उपकार्य निर्देश", "completionContent": "उपकार्य पूर्ण", diff --git a/webview-ui/src/i18n/locales/hi/marketplace.json b/webview-ui/src/i18n/locales/hi/marketplace.json index 07d5be7eb3..4e8f56994f 100644 --- a/webview-ui/src/i18n/locales/hi/marketplace.json +++ b/webview-ui/src/i18n/locales/hi/marketplace.json @@ -16,7 +16,7 @@ "type": { "label": "प्रकार के अनुसार फ़िल्टर करें:", "all": "सभी प्रकार", - "mode": "मोड", + "agent": "मोड", "mcpServer": "MCP सर्वर" }, "sort": { @@ -36,7 +36,7 @@ "none": "कोई नहीं" }, "type-group": { - "modes": "मोड", + "agents": "मोड", "mcps": "MCP सर्वर" }, "items": { @@ -128,9 +128,9 @@ } }, "removeConfirm": { - "mode": { + "agent": { "title": "मोड हटाएं", - "message": "क्या आप वाकई मोड \"{{modeName}}\" को हटाना चाहते हैं?", + "message": "क्या आप वाकई मोड \"{{agentName}}\" को हटाना चाहते हैं?", "rulesWarning": "यह इस मोड के लिए किसी भी संबंधित नियम फ़ाइल को भी हटा देगा।" }, "mcp": { diff --git a/webview-ui/src/i18n/locales/hi/prompts.json b/webview-ui/src/i18n/locales/hi/prompts.json index ff409f41f5..8dc2742a94 100644 --- a/webview-ui/src/i18n/locales/hi/prompts.json +++ b/webview-ui/src/i18n/locales/hi/prompts.json @@ -1,7 +1,7 @@ { "title": "मोड्स", "done": "हो गया", - "modes": { + "agents": { "title": "मोड्स", "createNewMode": "नया मोड बनाएँ", "importMode": "मोड आयात करें", @@ -49,8 +49,8 @@ "customInstructions": { "title": "मोड-विशिष्ट कस्टम निर्देश (वैकल्पिक)", "resetToDefault": "डिफ़ॉल्ट पर रीसेट करें", - "description": "{{modeName}} मोड के लिए विशिष्ट व्यवहार दिशानिर्देश जोड़ें।", - "loadFromFile": "{{mode}} मोड के लिए विशिष्ट कस्टम निर्देश आपके वर्कस्पेस में .roo/rules-{{slug}}/ फ़ोल्डर से भी लोड किए जा सकते हैं (.roorules-{{slug}} और .clinerules-{{slug}} पुराने हो गए हैं और जल्द ही काम करना बंद कर देंगे)।" + "description": "{{agentName}} मोड के लिए विशिष्ट व्यवहार दिशानिर्देश जोड़ें।", + "loadFromFile": "{{agent}} मोड के लिए विशिष्ट कस्टम निर्देश आपके वर्कस्पेस में .roo/rules-{{slug}}/ फ़ोल्डर से भी लोड किए जा सकते हैं (.roorules-{{slug}} और .clinerules-{{slug}} पुराने हो गए हैं और जल्द ही काम करना बंद कर देंगे)।" }, "exportMode": { "title": "मोड निर्यात करें", @@ -82,7 +82,7 @@ "systemPrompt": { "preview": "सिस्टम प्रॉम्प्ट का पूर्वावलोकन", "copy": "सिस्टम प्रॉम्प्ट को क्लिपबोर्ड पर कॉपी करें", - "title": "सिस्टम प्रॉम्प्ट ({{modeName}} मोड)" + "title": "सिस्टम प्रॉम्प्ट ({{agentName}} मोड)" }, "supportPrompts": { "title": "सहायता प्रॉम्प्ट्स", @@ -200,7 +200,7 @@ "allFiles": "सभी फाइलें", "deleteMode": { "title": "मोड हटाएं", - "message": "क्या आप वाकई मोड \"{{modeName}}\" को हटाना चाहते हैं?", + "message": "क्या आप वाकई मोड \"{{agentName}}\" को हटाना चाहते हैं?", "rulesFolder": "इस मोड में {{folderPath}} पर एक नियम फ़ोल्डर है जिसे भी हटा दिया जाएगा।", "descriptionNoRules": "क्या आप वाकई इस कस्टम मोड को हटाना चाहते हैं?", "confirm": "हटाएं", diff --git a/webview-ui/src/i18n/locales/id/chat.json b/webview-ui/src/i18n/locales/id/chat.json index ed6109e47b..4a609437b3 100644 --- a/webview-ui/src/i18n/locales/id/chat.json +++ b/webview-ui/src/i18n/locales/id/chat.json @@ -103,27 +103,27 @@ }, "stickyModels": { "title": "Model Sticky", - "description": "Setiap mode mengingat model terakhir yang kamu gunakan" + "description": "Setiap agent mengingat model terakhir yang kamu gunakan" }, "tools": { "title": "Tools", "description": "Izinkan AI menyelesaikan masalah dengan browsing web, menjalankan perintah, dan lainnya" }, "customizableModes": { - "title": "Mode yang Dapat Disesuaikan", + "title": "Agent yang Dapat Disesuaikan", "description": "Persona khusus dengan perilaku dan model yang ditugaskan sendiri" } }, - "selectMode": "Pilih mode untuk interaksi", + "selectMode": "Pilih agent untuk interaksi", "selectApiConfig": "Pilih konfigurasi API", "enhancePrompt": "Tingkatkan prompt dengan konteks tambahan", "enhancePromptDescription": "Tombol 'Tingkatkan Prompt' membantu memperbaiki prompt kamu dengan memberikan konteks tambahan, klarifikasi, atau penyusunan ulang. Coba ketik prompt di sini dan klik tombol lagi untuk melihat cara kerjanya.", "modeSelector": { - "title": "Mode", - "marketplace": "Marketplace Mode", - "settings": "Pengaturan Mode", + "title": "Agent", + "marketplace": "Marketplace Agent", + "settings": "Pengaturan Agent", "description": "Persona khusus yang menyesuaikan perilaku Roo.", - "searchPlaceholder": "Cari mode...", + "searchPlaceholder": "Cari agent...", "noResults": "Tidak ada hasil yang ditemukan" }, "addImages": "Tambahkan gambar ke pesan", @@ -131,15 +131,15 @@ "stopTts": "Hentikan text-to-speech", "typeMessage": "Ketik pesan...", "typeTask": "Bangun, cari, tanya sesuatu", - "addContext": "@ untuk menambah konteks, / untuk ganti mode", + "addContext": "@ untuk menambah konteks, / untuk ganti agent", "dragFiles": "tahan shift untuk drag file", "dragFilesImages": "tahan shift untuk drag file/gambar", "errorReadingFile": "Error membaca file:", "noValidImages": "Tidak ada gambar valid yang diproses", "separator": "Pemisah", "edit": "Edit...", - "forNextMode": "untuk mode selanjutnya", - "forPreviousMode": "untuk mode sebelumnya", + "forNextMode": "untuk agent selanjutnya", + "forPreviousMode": "untuk agent sebelumnya", "apiRequest": { "title": "Permintaan API", "failed": "Permintaan API Gagal", @@ -238,14 +238,14 @@ "wantsToUseTool": "Roo ingin menggunakan tool di server MCP {{serverName}}:", "wantsToAccessResource": "Roo ingin mengakses resource di server MCP {{serverName}}:" }, - "modes": { - "wantsToSwitch": "Roo ingin beralih ke mode {{mode}}", - "wantsToSwitchWithReason": "Roo ingin beralih ke mode {{mode}} karena: {{reason}}", - "didSwitch": "Roo beralih ke mode {{mode}}", - "didSwitchWithReason": "Roo beralih ke mode {{mode}} karena: {{reason}}" + "agents": { + "wantsToSwitch": "Roo ingin beralih ke agent {{agent}}", + "wantsToSwitchWithReason": "Roo ingin beralih ke agent {{agent}} karena: {{reason}}", + "didSwitch": "Roo beralih ke agent {{agent}}", + "didSwitchWithReason": "Roo beralih ke agent {{agent}} karena: {{reason}}" }, "subtasks": { - "wantsToCreate": "Roo ingin membuat subtugas baru dalam mode {{mode}}:", + "wantsToCreate": "Roo ingin membuat subtugas baru dalam agent {{agent}}:", "wantsToFinish": "Roo ingin menyelesaikan subtugas ini", "newTaskContent": "Instruksi Subtugas", "completionContent": "Subtugas Selesai", diff --git a/webview-ui/src/i18n/locales/id/history.json b/webview-ui/src/i18n/locales/id/history.json index 6459566e5a..67b44cffb0 100644 --- a/webview-ui/src/i18n/locales/id/history.json +++ b/webview-ui/src/i18n/locales/id/history.json @@ -5,8 +5,8 @@ "cache": "Cache: +{{writes}} → {{reads}}", "apiCost": "Biaya API: ${{cost}}", "history": "Riwayat", - "exitSelectionMode": "Keluar Mode Seleksi", - "enterSelectionMode": "Masuk Mode Seleksi", + "exitSelectionMode": "Keluar Agent Seleksi", + "enterSelectionMode": "Masuk Agent Seleksi", "done": "Selesai", "searchPlaceholder": "Pencarian fuzzy riwayat...", "newest": "Terbaru", @@ -25,7 +25,7 @@ "cancel": "Batal", "delete": "Hapus", "exitSelection": "Keluar Seleksi", - "selectionMode": "Mode Seleksi", + "selectionMode": "Agent Seleksi", "deselectAll": "Batalkan pilihan semua", "selectAll": "Pilih semua", "selectedItems": "Dipilih {{selected}}/{{total}} item", diff --git a/webview-ui/src/i18n/locales/id/marketplace.json b/webview-ui/src/i18n/locales/id/marketplace.json index fc663101d3..9a342b064d 100644 --- a/webview-ui/src/i18n/locales/id/marketplace.json +++ b/webview-ui/src/i18n/locales/id/marketplace.json @@ -11,12 +11,12 @@ "search": { "placeholder": "Cari item marketplace...", "placeholderMcp": "Cari MCP...", - "placeholderMode": "Cari Mode..." + "placeholderMode": "Cari Agent..." }, "type": { "label": "Filter berdasarkan tipe:", "all": "Semua tipe", - "mode": "Mode", + "agent": "Agent", "mcpServer": "Server MCP" }, "sort": { @@ -36,7 +36,7 @@ "none": "Tidak Ada" }, "type-group": { - "modes": "Mode", + "agents": "Agent", "mcps": "Server MCP" }, "items": { @@ -77,7 +77,7 @@ }, "install": { "title": "Instal {{name}}", - "titleMode": "Instal Mode {{name}}", + "titleMode": "Instal Agent {{name}}", "titleMcp": "Instal MCP {{name}}", "scope": "Cakupan Instalasi", "project": "Proyek (workspace saat ini)", @@ -91,10 +91,10 @@ "successDescription": "Instalasi berhasil diselesaikan", "installed": "Berhasil diinstal!", "whatNextMcp": "Anda sekarang dapat mengkonfigurasi dan menggunakan server MCP ini. Klik ikon MCP di sidebar untuk beralih tab.", - "whatNextMode": "Anda sekarang dapat menggunakan mode ini. Klik ikon Mode di sidebar untuk beralih tab.", + "whatNextMode": "Anda sekarang dapat menggunakan agent ini. Klik ikon Agent di sidebar untuk beralih tab.", "done": "Selesai", "goToMcp": "Ke Tab MCP", - "goToModes": "Ke Pengaturan Mode", + "goToModes": "Ke Pengaturan Agent", "moreInfoMcp": "Lihat dokumentasi MCP {{name}}", "validationRequired": "Silakan berikan nilai untuk {{paramName}}" }, @@ -128,10 +128,10 @@ } }, "removeConfirm": { - "mode": { - "title": "Hapus Mode", - "message": "Apakah Anda yakin ingin menghapus mode \"{{modeName}}\"?", - "rulesWarning": "Ini juga akan menghapus semua file aturan terkait untuk mode ini." + "agent": { + "title": "Hapus Agent", + "message": "Apakah Anda yakin ingin menghapus agent \"{{agentName}}\"?", + "rulesWarning": "Ini juga akan menghapus semua file aturan terkait untuk agent ini." }, "mcp": { "title": "Hapus Server MCP", diff --git a/webview-ui/src/i18n/locales/id/prompts.json b/webview-ui/src/i18n/locales/id/prompts.json index 52736ad69b..fb47adae6d 100644 --- a/webview-ui/src/i18n/locales/id/prompts.json +++ b/webview-ui/src/i18n/locales/id/prompts.json @@ -1,24 +1,24 @@ { - "title": "Mode", + "title": "Agent", "done": "Selesai", - "modes": { - "title": "Mode", - "createNewMode": "Buat mode baru", - "importMode": "Impor mode", - "noMatchFound": "Tidak ada mode yang ditemukan", - "editModesConfig": "Edit konfigurasi mode", - "editGlobalModes": "Edit Mode Global", - "editProjectModes": "Edit Mode Proyek (.roomodes)", - "createModeHelpText": "Mode adalah persona khusus yang menyesuaikan perilaku Roo Code. <0>Pelajari tentang Menggunakan Mode atau <1>Menyesuaikan Mode.", - "selectMode": "Cari mode" + "agents": { + "title": "Agent", + "createNewMode": "Buat agent baru", + "importMode": "Impor agent", + "noMatchFound": "Tidak ada agent yang ditemukan", + "editModesConfig": "Edit konfigurasi agent", + "editGlobalModes": "Edit Agent Global", + "editProjectModes": "Edit Agent Proyek (.roomodes)", + "createModeHelpText": "Agent adalah persona khusus yang menyesuaikan perilaku Roo Code. <0>Pelajari tentang Menggunakan Agent atau <1>Menyesuaikan Agent.", + "selectMode": "Cari agent" }, "apiConfiguration": { "title": "Konfigurasi API", - "select": "Pilih konfigurasi API mana yang akan digunakan untuk mode ini" + "select": "Pilih konfigurasi API mana yang akan digunakan untuk agent ini" }, "tools": { "title": "Tools yang Tersedia", - "builtInModesText": "Tools untuk mode bawaan tidak dapat dimodifikasi", + "builtInModesText": "Tools untuk agent bawaan tidak dapat dimodifikasi", "editTools": "Edit tools", "doneEditing": "Selesai mengedit", "allowedFiles": "File yang diizinkan:", @@ -34,32 +34,32 @@ "roleDefinition": { "title": "Definisi Peran", "resetToDefault": "Reset ke default", - "description": "Tentukan keahlian dan kepribadian Roo untuk mode ini. Deskripsi ini membentuk bagaimana Roo mempresentasikan dirinya dan mendekati tugas." + "description": "Tentukan keahlian dan kepribadian Roo untuk agent ini. Deskripsi ini membentuk bagaimana Roo mempresentasikan dirinya dan mendekati tugas." }, "description": { "title": "Deskripsi singkat (untuk manusia)", "resetToDefault": "Setel ulang ke deskripsi default", - "description": "Deskripsi singkat yang ditampilkan di dropdown pemilih mode." + "description": "Deskripsi singkat yang ditampilkan di dropdown pemilih agent." }, "whenToUse": { "title": "Kapan Menggunakan (opsional)", - "description": "Jelaskan kapan mode ini harus digunakan. Ini membantu Orchestrator memilih mode yang tepat untuk suatu tugas.", + "description": "Jelaskan kapan agent ini harus digunakan. Ini membantu Orchestrator memilih agent yang tepat untuk suatu tugas.", "resetToDefault": "Reset ke deskripsi default 'Kapan Menggunakan'" }, "customInstructions": { - "title": "Instruksi Kustom Khusus Mode (opsional)", + "title": "Instruksi Kustom Khusus Agent (opsional)", "resetToDefault": "Reset ke default", - "description": "Tambahkan panduan perilaku khusus untuk mode {{modeName}}.", - "loadFromFile": "Instruksi kustom khusus untuk mode {{mode}} juga dapat dimuat dari folder .roo/rules-{{slug}}/ di workspace Anda (.roomodes-{{slug}} dan .clinerules-{{slug}} sudah deprecated dan akan segera berhenti bekerja)." + "description": "Tambahkan panduan perilaku khusus untuk agent {{agentName}}.", + "loadFromFile": "Instruksi kustom khusus untuk agent {{agent}} juga dapat dimuat dari folder .roo/rules-{{slug}}/ di workspace Anda (.roomodes-{{slug}} dan .clinerules-{{slug}} sudah deprecated dan akan segera berhenti bekerja)." }, "exportMode": { - "title": "Ekspor Mode", - "description": "Ekspor mode ini ke file YAML dengan semua aturan yang disertakan untuk berbagi dengan mudah dengan orang lain.", - "export": "Ekspor Mode", + "title": "Ekspor Agent", + "description": "Ekspor agent ini ke file YAML dengan semua aturan yang disertakan untuk berbagi dengan mudah dengan orang lain.", + "export": "Ekspor Agent", "exporting": "Mengekspor..." }, "importMode": { - "selectLevel": "Pilih di mana akan mengimpor mode ini:", + "selectLevel": "Pilih di mana akan mengimpor agent ini:", "import": "Impor", "importing": "Mengimpor...", "global": { @@ -68,21 +68,21 @@ }, "project": { "label": "Tingkat Proyek", - "description": "Hanya tersedia di ruang kerja ini. Jika mode yang diekspor berisi file aturan, file tersebut akan dibuat ulang di folder .roo/rules-{slug}/." + "description": "Hanya tersedia di ruang kerja ini. Jika agent yang diekspor berisi file aturan, file tersebut akan dibuat ulang di folder .roo/rules-{slug}/." } }, "advanced": { "title": "Lanjutan" }, "globalCustomInstructions": { - "title": "Instruksi Kustom untuk Semua Mode", - "description": "Instruksi ini berlaku untuk semua mode. Mereka menyediakan set dasar perilaku yang dapat ditingkatkan oleh instruksi khusus mode di bawah. <0>Pelajari lebih lanjut", + "title": "Instruksi Kustom untuk Semua Agent", + "description": "Instruksi ini berlaku untuk semua agent. Mereka menyediakan set dasar perilaku yang dapat ditingkatkan oleh instruksi khusus agent di bawah. <0>Pelajari lebih lanjut", "loadFromFile": "Instruksi juga dapat dimuat dari folder .roo/rules/ di workspace Anda (.roorules dan .clinerules sudah deprecated dan akan segera berhenti bekerja)." }, "systemPrompt": { "preview": "Pratinjau System Prompt", "copy": "Salin system prompt ke clipboard", - "title": "System Prompt (mode {{modeName}})" + "title": "System Prompt (agent {{agentName}})" }, "supportPrompts": { "title": "Support Prompts", @@ -149,11 +149,11 @@ "description": "<2>⚠️ Peringatan: Fitur lanjutan ini melewati pengamanan. <1>BACA INI SEBELUM MENGGUNAKAN!Override system prompt default dengan membuat file di .roo/system-prompt-{{slug}}." }, "createModeDialog": { - "title": "Buat Mode Baru", + "title": "Buat Agent Baru", "close": "Tutup", "name": { "label": "Nama", - "placeholder": "Masukkan nama mode" + "placeholder": "Masukkan nama agent" }, "slug": { "label": "Slug", @@ -161,7 +161,7 @@ }, "saveLocation": { "label": "Lokasi Penyimpanan", - "description": "Pilih di mana menyimpan mode ini. Mode khusus proyek lebih diutamakan daripada mode global.", + "description": "Pilih di mana menyimpan agent ini. Agent khusus proyek lebih diutamakan daripada agent global.", "global": { "label": "Global", "description": "Tersedia di semua workspace" @@ -173,36 +173,36 @@ }, "roleDefinition": { "label": "Definisi Peran", - "description": "Tentukan keahlian dan kepribadian Roo untuk mode ini." + "description": "Tentukan keahlian dan kepribadian Roo untuk agent ini." }, "whenToUse": { "label": "Kapan Menggunakan (opsional)", - "description": "Berikan deskripsi yang jelas tentang kapan mode ini paling efektif dan jenis tugas apa yang unggul." + "description": "Berikan deskripsi yang jelas tentang kapan agent ini paling efektif dan jenis tugas apa yang unggul." }, "tools": { "label": "Tools yang Tersedia", - "description": "Pilih tools mana yang dapat digunakan mode ini." + "description": "Pilih tools mana yang dapat digunakan agent ini." }, "description": { "label": "Deskripsi singkat (untuk manusia)", - "description": "Deskripsi singkat yang ditampilkan di dropdown pemilih mode." + "description": "Deskripsi singkat yang ditampilkan di dropdown pemilih agent." }, "customInstructions": { "label": "Instruksi Kustom (opsional)", - "description": "Tambahkan panduan perilaku khusus untuk mode ini." + "description": "Tambahkan panduan perilaku khusus untuk agent ini." }, "buttons": { "cancel": "Batal", - "create": "Buat Mode" + "create": "Buat Agent" }, - "deleteMode": "Hapus mode" + "deleteMode": "Hapus agent" }, "allFiles": "semua file", "deleteMode": { - "title": "Hapus Mode", - "message": "Anda yakin ingin menghapus mode \"{{modeName}}\"?", - "rulesFolder": "Mode ini memiliki folder aturan di {{folderPath}} yang juga akan dihapus.", - "descriptionNoRules": "Apakah Anda yakin ingin menghapus mode kustom ini?", + "title": "Hapus Agent", + "message": "Anda yakin ingin menghapus agent \"{{agentName}}\"?", + "rulesFolder": "Agent ini memiliki folder aturan di {{folderPath}} yang juga akan dihapus.", + "descriptionNoRules": "Apakah Anda yakin ingin menghapus agent kustom ini?", "confirm": "Hapus", "cancel": "Batal" } diff --git a/webview-ui/src/i18n/locales/id/settings.json b/webview-ui/src/i18n/locales/id/settings.json index 82a10a05f4..05a0d88fe3 100644 --- a/webview-ui/src/i18n/locales/id/settings.json +++ b/webview-ui/src/i18n/locales/id/settings.json @@ -164,8 +164,8 @@ "description": "Aktifkan auto-approval tool MCP individual di tampilan Server MCP (memerlukan pengaturan ini dan checkbox \"Selalu izinkan\" tool tersebut)" }, "modeSwitch": { - "label": "Mode", - "description": "Secara otomatis beralih antara mode yang berbeda tanpa memerlukan persetujuan" + "label": "Agent", + "description": "Secara otomatis beralih antara agent yang berbeda tanpa memerlukan persetujuan" }, "subtasks": { "label": "Subtugas", @@ -336,7 +336,7 @@ "draftModelId": "Draft Model ID", "draftModelDesc": "Draft model harus dari keluarga model yang sama agar speculative decoding bekerja dengan benar.", "selectDraftModel": "Pilih Draft Model", - "noModelsFound": "Tidak ada draft model ditemukan. Pastikan LM Studio berjalan dengan Server Mode diaktifkan.", + "noModelsFound": "Tidak ada draft model ditemukan. Pastikan LM Studio berjalan dengan Server Agent diaktifkan.", "description": "LM Studio memungkinkan kamu menjalankan model secara lokal di komputer. Untuk instruksi cara memulai, lihat panduan quickstart mereka. Kamu juga perlu memulai fitur local server LM Studio untuk menggunakannya dengan ekstensi ini. Catatan: Roo Code menggunakan prompt kompleks dan bekerja terbaik dengan model Claude. Model yang kurang mampu mungkin tidak bekerja seperti yang diharapkan." }, "ollama": { @@ -667,8 +667,8 @@ "description": "Aktifkan tool insert content eksperimental, memungkinkan Roo menyisipkan konten pada nomor baris spesifik tanpa perlu membuat diff." }, "POWER_STEERING": { - "name": "Gunakan mode \"power steering\" eksperimental", - "description": "Ketika diaktifkan, Roo akan mengingatkan model tentang detail definisi mode saat ini lebih sering. Ini akan menghasilkan kepatuhan yang lebih kuat terhadap definisi peran dan instruksi kustom, tetapi akan menggunakan lebih banyak token per pesan." + "name": "Gunakan agent \"power steering\" eksperimental", + "description": "Ketika diaktifkan, Roo akan mengingatkan model tentang detail definisi agent saat ini lebih sering. Ini akan menghasilkan kepatuhan yang lebih kuat terhadap definisi peran dan instruksi kustom, tetapi akan menggunakan lebih banyak token per pesan." }, "AUTOCOMPLETE": { "name": "Gunakan fitur \"autocomplete\" eksperimental", @@ -684,7 +684,7 @@ }, "MARKETPLACE": { "name": "Aktifkan Marketplace", - "description": "Ketika diaktifkan, kamu dapat menginstal MCP dan mode kustom dari Marketplace." + "description": "Ketika diaktifkan, kamu dapat menginstal MCP dan agent kustom dari Marketplace." }, "MULTI_FILE_APPLY_DIFF": { "name": "Aktifkan edit file bersamaan", diff --git a/webview-ui/src/i18n/locales/id/welcome.json b/webview-ui/src/i18n/locales/id/welcome.json index b1d6d71c80..71ea9a0601 100644 --- a/webview-ui/src/i18n/locales/id/welcome.json +++ b/webview-ui/src/i18n/locales/id/welcome.json @@ -1,6 +1,6 @@ { "greeting": "Selamat datang di Roo Code!", - "introduction": "Dengan berbagai Mode bawaan dan dapat diperluas, Roo Code memungkinkan Anda merencanakan, merancang, coding, debug, dan meningkatkan produktivitas seperti yang belum pernah terjadi sebelumnya.", + "introduction": "Dengan berbagai Agent bawaan dan dapat diperluas, Roo Code memungkinkan Anda merencanakan, merancang, coding, debug, dan meningkatkan produktivitas seperti yang belum pernah terjadi sebelumnya.", "notice": "Untuk memulai, ekstensi ini memerlukan provider API.", "start": "Ayo mulai!", "routers": { diff --git a/webview-ui/src/i18n/locales/it/chat.json b/webview-ui/src/i18n/locales/it/chat.json index c35209a8cb..db9e4fb99c 100644 --- a/webview-ui/src/i18n/locales/it/chat.json +++ b/webview-ui/src/i18n/locales/it/chat.json @@ -216,14 +216,14 @@ "wantsToUseTool": "Roo vuole utilizzare uno strumento sul server MCP {{serverName}}:", "wantsToAccessResource": "Roo vuole accedere a una risorsa sul server MCP {{serverName}}:" }, - "modes": { - "wantsToSwitch": "Roo vuole passare alla modalità {{mode}}", - "wantsToSwitchWithReason": "Roo vuole passare alla modalità {{mode}} perché: {{reason}}", - "didSwitch": "Roo è passato alla modalità {{mode}}", - "didSwitchWithReason": "Roo è passato alla modalità {{mode}} perché: {{reason}}" + "agents": { + "wantsToSwitch": "Roo vuole passare alla modalità {{agent}}", + "wantsToSwitchWithReason": "Roo vuole passare alla modalità {{agent}} perché: {{reason}}", + "didSwitch": "Roo è passato alla modalità {{agent}}", + "didSwitchWithReason": "Roo è passato alla modalità {{agent}} perché: {{reason}}" }, "subtasks": { - "wantsToCreate": "Roo vuole creare una nuova sottoattività in modalità {{mode}}:", + "wantsToCreate": "Roo vuole creare una nuova sottoattività in modalità {{agent}}:", "wantsToFinish": "Roo vuole completare questa sottoattività", "newTaskContent": "Istruzioni sottoattività", "completionContent": "Sottoattività completata", diff --git a/webview-ui/src/i18n/locales/it/marketplace.json b/webview-ui/src/i18n/locales/it/marketplace.json index ab4aa459fa..decb9d5846 100644 --- a/webview-ui/src/i18n/locales/it/marketplace.json +++ b/webview-ui/src/i18n/locales/it/marketplace.json @@ -16,7 +16,7 @@ "type": { "label": "Filtra per tipo:", "all": "Tutti i tipi", - "mode": "Modalità", + "agent": "Modalità", "mcpServer": "Server MCP" }, "sort": { @@ -36,7 +36,7 @@ "none": "Nessuno" }, "type-group": { - "modes": "Modalità", + "agents": "Modalità", "mcps": "Server MCP" }, "items": { @@ -128,9 +128,9 @@ } }, "removeConfirm": { - "mode": { + "agent": { "title": "Rimuovi modalità", - "message": "Sei sicuro di voler rimuovere la modalità \"{{modeName}}\"?", + "message": "Sei sicuro di voler rimuovere la modalità \"{{agentName}}\"?", "rulesWarning": "Questo rimuoverà anche eventuali file di regole associati per questa modalità." }, "mcp": { diff --git a/webview-ui/src/i18n/locales/it/prompts.json b/webview-ui/src/i18n/locales/it/prompts.json index e6356f828b..cf3bc5563c 100644 --- a/webview-ui/src/i18n/locales/it/prompts.json +++ b/webview-ui/src/i18n/locales/it/prompts.json @@ -1,7 +1,7 @@ { "title": "Modi", "done": "Fatto", - "modes": { + "agents": { "title": "Modalità", "createNewMode": "Crea nuova modalità", "importMode": "Importa modalità", @@ -49,8 +49,8 @@ "customInstructions": { "title": "Istruzioni personalizzate specifiche per la modalità (opzionale)", "resetToDefault": "Ripristina predefiniti", - "description": "Aggiungi linee guida comportamentali specifiche per la modalità {{modeName}}.", - "loadFromFile": "Le istruzioni personalizzate specifiche per la modalità {{mode}} possono essere caricate anche dalla cartella .roo/rules-{{slug}}/ nel tuo spazio di lavoro (.roorules-{{slug}} e .clinerules-{{slug}} sono obsoleti e smetteranno di funzionare presto)." + "description": "Aggiungi linee guida comportamentali specifiche per la modalità {{agentName}}.", + "loadFromFile": "Le istruzioni personalizzate specifiche per la modalità {{agent}} possono essere caricate anche dalla cartella .roo/rules-{{slug}}/ nel tuo spazio di lavoro (.roorules-{{slug}} e .clinerules-{{slug}} sono obsoleti e smetteranno di funzionare presto)." }, "exportMode": { "title": "Esporta modalità", @@ -82,7 +82,7 @@ "systemPrompt": { "preview": "Anteprima prompt di sistema", "copy": "Copia prompt di sistema negli appunti", - "title": "Prompt di sistema (modalità {{modeName}})" + "title": "Prompt di sistema (modalità {{agentName}})" }, "supportPrompts": { "title": "Prompt di supporto", @@ -200,7 +200,7 @@ "allFiles": "tutti i file", "deleteMode": { "title": "Elimina modalità", - "message": "Sei sicuro di voler eliminare la modalità \"{{modeName}}\"?", + "message": "Sei sicuro di voler eliminare la modalità \"{{agentName}}\"?", "rulesFolder": "Questa modalità ha una cartella di regole in {{folderPath}} che verrà eliminata.", "descriptionNoRules": "Sei sicuro di voler eliminare questa modalità personalizzata?", "confirm": "Elimina", diff --git a/webview-ui/src/i18n/locales/ja/chat.json b/webview-ui/src/i18n/locales/ja/chat.json index 82fded799b..685cc5ee87 100644 --- a/webview-ui/src/i18n/locales/ja/chat.json +++ b/webview-ui/src/i18n/locales/ja/chat.json @@ -216,14 +216,14 @@ "wantsToUseTool": "RooはMCPサーバー{{serverName}}でツールを使用したい:", "wantsToAccessResource": "RooはMCPサーバー{{serverName}}のリソースにアクセスしたい:" }, - "modes": { - "wantsToSwitch": "Rooは{{mode}}モードに切り替えたい", - "wantsToSwitchWithReason": "Rooは次の理由で{{mode}}モードに切り替えたい: {{reason}}", - "didSwitch": "Rooは{{mode}}モードに切り替えました", - "didSwitchWithReason": "Rooは次の理由で{{mode}}モードに切り替えました: {{reason}}" + "agents": { + "wantsToSwitch": "Rooは{{agent}}モードに切り替えたい", + "wantsToSwitchWithReason": "Rooは次の理由で{{agent}}モードに切り替えたい: {{reason}}", + "didSwitch": "Rooは{{agent}}モードに切り替えました", + "didSwitchWithReason": "Rooは次の理由で{{agent}}モードに切り替えました: {{reason}}" }, "subtasks": { - "wantsToCreate": "Rooは{{mode}}モードで新しいサブタスクを作成したい:", + "wantsToCreate": "Rooは{{agent}}モードで新しいサブタスクを作成したい:", "wantsToFinish": "Rooはこのサブタスクを終了したい", "newTaskContent": "サブタスク指示", "completionContent": "サブタスク完了", diff --git a/webview-ui/src/i18n/locales/ja/marketplace.json b/webview-ui/src/i18n/locales/ja/marketplace.json index 72388d5224..c0ae3f537c 100644 --- a/webview-ui/src/i18n/locales/ja/marketplace.json +++ b/webview-ui/src/i18n/locales/ja/marketplace.json @@ -16,7 +16,7 @@ "type": { "label": "タイプでフィルター:", "all": "すべてのタイプ", - "mode": "モード", + "agent": "モード", "mcpServer": "MCPサーバー" }, "sort": { @@ -36,7 +36,7 @@ "none": "なし" }, "type-group": { - "modes": "モード", + "agents": "モード", "mcps": "MCPサーバー" }, "items": { @@ -128,9 +128,9 @@ } }, "removeConfirm": { - "mode": { + "agent": { "title": "モードを削除", - "message": "モード「{{modeName}}」を本当に削除しますか?", + "message": "モード「{{agentName}}」を本当に削除しますか?", "rulesWarning": "これにより、このモードに関連するルールファイルも削除されます。" }, "mcp": { diff --git a/webview-ui/src/i18n/locales/ja/prompts.json b/webview-ui/src/i18n/locales/ja/prompts.json index e9f108f615..7012681f12 100644 --- a/webview-ui/src/i18n/locales/ja/prompts.json +++ b/webview-ui/src/i18n/locales/ja/prompts.json @@ -1,7 +1,7 @@ { "title": "モード", "done": "完了", - "modes": { + "agents": { "title": "モード", "createNewMode": "新しいモードを作成", "importMode": "モードをインポート", @@ -49,8 +49,8 @@ "customInstructions": { "title": "モード固有のカスタム指示(オプション)", "resetToDefault": "デフォルトにリセット", - "description": "{{modeName}}モードに特化した行動ガイドラインを追加します。", - "loadFromFile": "{{mode}}モード固有のカスタム指示は、ワークスペースの.roo/rules-{{slug}}/フォルダからも読み込めます(.roorules-{{slug}}と.clinerules-{{slug}}は非推奨であり、まもなく機能しなくなります)。" + "description": "{{agentName}}モードに特化した行動ガイドラインを追加します。", + "loadFromFile": "{{agent}}モード固有のカスタム指示は、ワークスペースの.roo/rules-{{slug}}/フォルダからも読み込めます(.roorules-{{slug}}と.clinerules-{{slug}}は非推奨であり、まもなく機能しなくなります)。" }, "exportMode": { "title": "モードをエクスポート", @@ -82,7 +82,7 @@ "systemPrompt": { "preview": "システムプロンプトのプレビュー", "copy": "システムプロンプトをクリップボードにコピー", - "title": "システムプロンプト({{modeName}}モード)" + "title": "システムプロンプト({{agentName}}モード)" }, "supportPrompts": { "title": "サポートプロンプト", @@ -200,7 +200,7 @@ "allFiles": "すべてのファイル", "deleteMode": { "title": "モードを削除", - "message": "モード「{{modeName}}」を削除してもよろしいですか?", + "message": "モード「{{agentName}}」を削除してもよろしいですか?", "rulesFolder": "このモードには{{folderPath}}にルールフォルダがあり、それも削除されます。", "descriptionNoRules": "このカスタムモードを削除してもよろしいですか?", "confirm": "削除", diff --git a/webview-ui/src/i18n/locales/ko/chat.json b/webview-ui/src/i18n/locales/ko/chat.json index 0432ce5652..347f5a2fde 100644 --- a/webview-ui/src/i18n/locales/ko/chat.json +++ b/webview-ui/src/i18n/locales/ko/chat.json @@ -216,14 +216,14 @@ "wantsToUseTool": "Roo가 {{serverName}} MCP 서버에서 도구를 사용하고 싶어합니다:", "wantsToAccessResource": "Roo가 {{serverName}} MCP 서버에서 리소스에 접근하고 싶어합니다:" }, - "modes": { - "wantsToSwitch": "Roo가 {{mode}} 모드로 전환하고 싶어합니다", - "wantsToSwitchWithReason": "Roo가 다음 이유로 {{mode}} 모드로 전환하고 싶어합니다: {{reason}}", - "didSwitch": "Roo가 {{mode}} 모드로 전환했습니다", - "didSwitchWithReason": "Roo가 다음 이유로 {{mode}} 모드로 전환했습니다: {{reason}}" + "agents": { + "wantsToSwitch": "Roo가 {{agent}} 모드로 전환하고 싶어합니다", + "wantsToSwitchWithReason": "Roo가 다음 이유로 {{agent}} 모드로 전환하고 싶어합니다: {{reason}}", + "didSwitch": "Roo가 {{agent}} 모드로 전환했습니다", + "didSwitchWithReason": "Roo가 다음 이유로 {{agent}} 모드로 전환했습니다: {{reason}}" }, "subtasks": { - "wantsToCreate": "Roo가 {{mode}} 모드에서 새 하위 작업을 만들고 싶어합니다:", + "wantsToCreate": "Roo가 {{agent}} 모드에서 새 하위 작업을 만들고 싶어합니다:", "wantsToFinish": "Roo가 이 하위 작업을 완료하고 싶어합니다", "newTaskContent": "하위 작업 지침", "completionContent": "하위 작업 완료", diff --git a/webview-ui/src/i18n/locales/ko/marketplace.json b/webview-ui/src/i18n/locales/ko/marketplace.json index 54ec83863f..c4bc937b23 100644 --- a/webview-ui/src/i18n/locales/ko/marketplace.json +++ b/webview-ui/src/i18n/locales/ko/marketplace.json @@ -16,7 +16,7 @@ "type": { "label": "유형별 필터:", "all": "모든 유형", - "mode": "모드", + "agent": "모드", "mcpServer": "MCP 서버" }, "sort": { @@ -36,7 +36,7 @@ "none": "없음" }, "type-group": { - "modes": "모드", + "agents": "모드", "mcps": "MCP 서버" }, "items": { @@ -128,9 +128,9 @@ } }, "removeConfirm": { - "mode": { + "agent": { "title": "모드 제거", - "message": "정말로 '{{modeName}}' 모드를 제거하시겠습니까?", + "message": "정말로 '{{agentName}}' 모드를 제거하시겠습니까?", "rulesWarning": "이렇게 하면 이 모드와 관련된 모든 규칙 파일도 제거됩니다." }, "mcp": { diff --git a/webview-ui/src/i18n/locales/ko/prompts.json b/webview-ui/src/i18n/locales/ko/prompts.json index 688ddd18a9..0d9b19f182 100644 --- a/webview-ui/src/i18n/locales/ko/prompts.json +++ b/webview-ui/src/i18n/locales/ko/prompts.json @@ -1,7 +1,7 @@ { "title": "모드", "done": "완료", - "modes": { + "agents": { "title": "모드", "createNewMode": "새 모드 만들기", "importMode": "모드 가져오기", @@ -49,8 +49,8 @@ "customInstructions": { "title": "모드별 사용자 지정 지침 (선택 사항)", "resetToDefault": "기본값으로 재설정", - "description": "{{modeName}} 모드에 대한 특정 행동 지침을 추가하세요.", - "loadFromFile": "{{mode}} 모드에 대한 사용자 지정 지침은 작업 공간의 .roo/rules-{{slug}}/ 폴더에서도 로드할 수 있습니다(.roorules-{{slug}}와 .clinerules-{{slug}}는 더 이상 사용되지 않으며 곧 작동을 중단합니다)." + "description": "{{agentName}} 모드에 대한 특정 행동 지침을 추가하세요.", + "loadFromFile": "{{agent}} 모드에 대한 사용자 지정 지침은 작업 공간의 .roo/rules-{{slug}}/ 폴더에서도 로드할 수 있습니다(.roorules-{{slug}}와 .clinerules-{{slug}}는 더 이상 사용되지 않으며 곧 작동을 중단합니다)." }, "exportMode": { "title": "모드 내보내기", @@ -82,7 +82,7 @@ "systemPrompt": { "preview": "시스템 프롬프트 미리보기", "copy": "시스템 프롬프트를 클립보드에 복사", - "title": "시스템 프롬프트 ({{modeName}} 모드)" + "title": "시스템 프롬프트 ({{agentName}} 모드)" }, "supportPrompts": { "title": "지원 프롬프트", @@ -200,7 +200,7 @@ "allFiles": "모든 파일", "deleteMode": { "title": "모드 삭제", - "message": "\"{{modeName}}\" 모드를 삭제하시겠습니까?", + "message": "\"{{agentName}}\" 모드를 삭제하시겠습니까?", "rulesFolder": "이 모드에는 {{folderPath}}에 규칙 폴더가 있으며 함께 삭제됩니다.", "descriptionNoRules": "이 사용자 정의 모드를 삭제하시겠습니까?", "confirm": "삭제", diff --git a/webview-ui/src/i18n/locales/nl/chat.json b/webview-ui/src/i18n/locales/nl/chat.json index 6dae0113af..dd7310f1a2 100644 --- a/webview-ui/src/i18n/locales/nl/chat.json +++ b/webview-ui/src/i18n/locales/nl/chat.json @@ -211,14 +211,14 @@ "wantsToUseTool": "Roo wil een tool gebruiken op de {{serverName}} MCP-server:", "wantsToAccessResource": "Roo wil een bron benaderen op de {{serverName}} MCP-server:" }, - "modes": { - "wantsToSwitch": "Roo wil overschakelen naar {{mode}} modus", - "wantsToSwitchWithReason": "Roo wil overschakelen naar {{mode}} modus omdat: {{reason}}", - "didSwitch": "Roo is overgeschakeld naar {{mode}} modus", - "didSwitchWithReason": "Roo is overgeschakeld naar {{mode}} modus omdat: {{reason}}" + "agents": { + "wantsToSwitch": "Roo wil overschakelen naar {{agent}} modus", + "wantsToSwitchWithReason": "Roo wil overschakelen naar {{agent}} modus omdat: {{reason}}", + "didSwitch": "Roo is overgeschakeld naar {{agent}} modus", + "didSwitchWithReason": "Roo is overgeschakeld naar {{agent}} modus omdat: {{reason}}" }, "subtasks": { - "wantsToCreate": "Roo wil een nieuwe subtaak aanmaken in {{mode}} modus:", + "wantsToCreate": "Roo wil een nieuwe subtaak aanmaken in {{agent}} modus:", "wantsToFinish": "Roo wil deze subtaak voltooien", "newTaskContent": "Subtaak-instructies", "completionContent": "Subtaak voltooid", diff --git a/webview-ui/src/i18n/locales/nl/marketplace.json b/webview-ui/src/i18n/locales/nl/marketplace.json index b378375397..9a44a1a9cb 100644 --- a/webview-ui/src/i18n/locales/nl/marketplace.json +++ b/webview-ui/src/i18n/locales/nl/marketplace.json @@ -16,7 +16,7 @@ "type": { "label": "Filteren op type:", "all": "Alle types", - "mode": "Modus", + "agent": "Modus", "mcpServer": "MCP-server" }, "sort": { @@ -36,7 +36,7 @@ "none": "Geen" }, "type-group": { - "modes": "Modi", + "agents": "Modi", "mcps": "MCP-servers" }, "items": { @@ -128,9 +128,9 @@ } }, "removeConfirm": { - "mode": { + "agent": { "title": "Modus verwijderen", - "message": "Weet je zeker dat je de modus \"{{modeName}}\" wilt verwijderen?", + "message": "Weet je zeker dat je de modus \"{{agentName}}\" wilt verwijderen?", "rulesWarning": "Dit verwijdert ook alle bijbehorende regelbestanden voor deze modus." }, "mcp": { diff --git a/webview-ui/src/i18n/locales/nl/prompts.json b/webview-ui/src/i18n/locales/nl/prompts.json index 7c8ba28605..026e366c2c 100644 --- a/webview-ui/src/i18n/locales/nl/prompts.json +++ b/webview-ui/src/i18n/locales/nl/prompts.json @@ -1,7 +1,7 @@ { "title": "Modi", "done": "Gereed", - "modes": { + "agents": { "title": "Modi", "createNewMode": "Nieuwe modus aanmaken", "importMode": "Modus importeren", @@ -49,8 +49,8 @@ "customInstructions": { "title": "Modusspecifieke instructies (optioneel)", "resetToDefault": "Terugzetten naar standaard", - "description": "Voeg gedragsrichtlijnen toe die specifiek zijn voor de modus {{modeName}}.", - "loadFromFile": "Modusspecifieke instructies voor {{mode}} kunnen ook worden geladen uit de map .roo/rules-{{slug}}/ in je werkruimte (.roorules-{{slug}} en .clinerules-{{slug}} zijn verouderd en werken binnenkort niet meer)." + "description": "Voeg gedragsrichtlijnen toe die specifiek zijn voor de modus {{agentName}}.", + "loadFromFile": "Modusspecifieke instructies voor {{agent}} kunnen ook worden geladen uit de map .roo/rules-{{slug}}/ in je werkruimte (.roorules-{{slug}} en .clinerules-{{slug}} zijn verouderd en werken binnenkort niet meer)." }, "exportMode": { "title": "Modus exporteren", @@ -82,7 +82,7 @@ "systemPrompt": { "preview": "Systeemprompt bekijken", "copy": "Systeemprompt kopiëren naar klembord", - "title": "Systeemprompt ({{modeName}} modus)" + "title": "Systeemprompt ({{agentName}} modus)" }, "supportPrompts": { "title": "Ondersteuningsprompts", @@ -200,7 +200,7 @@ "allFiles": "alle bestanden", "deleteMode": { "title": "Modus verwijderen", - "message": "Weet je zeker dat je de modus \"{{modeName}}\" wilt verwijderen?", + "message": "Weet je zeker dat je de modus \"{{agentName}}\" wilt verwijderen?", "rulesFolder": "Deze modus heeft een regelmap op {{folderPath}} die ook wordt verwijderd.", "descriptionNoRules": "Weet je zeker dat je deze aangepaste modus wilt verwijderen?", "confirm": "Verwijderen", diff --git a/webview-ui/src/i18n/locales/nl/settings.json b/webview-ui/src/i18n/locales/nl/settings.json index 07f86818a2..92e47b8e3e 100644 --- a/webview-ui/src/i18n/locales/nl/settings.json +++ b/webview-ui/src/i18n/locales/nl/settings.json @@ -332,7 +332,7 @@ "draftModelId": "Draft Model-ID", "draftModelDesc": "Draft-model moet uit dezelfde modelfamilie komen voor correcte speculatieve decodering.", "selectDraftModel": "Selecteer draft-model", - "noModelsFound": "Geen draft-modellen gevonden. Zorg dat LM Studio draait met Server Mode ingeschakeld.", + "noModelsFound": "Geen draft-modellen gevonden. Zorg dat LM Studio draait met Server Agent ingeschakeld.", "description": "LM Studio laat je modellen lokaal op je computer draaien. Zie hun quickstart-gids voor instructies. Je moet ook de lokale server-functie van LM Studio starten om het met deze extensie te gebruiken. Let op: Roo Code gebruikt complexe prompts en werkt het beste met Claude-modellen. Minder krachtige modellen werken mogelijk niet zoals verwacht." }, "ollama": { diff --git a/webview-ui/src/i18n/locales/pl/chat.json b/webview-ui/src/i18n/locales/pl/chat.json index 984fa9a24c..f9bbca4604 100644 --- a/webview-ui/src/i18n/locales/pl/chat.json +++ b/webview-ui/src/i18n/locales/pl/chat.json @@ -216,14 +216,14 @@ "wantsToUseTool": "Roo chce użyć narzędzia na serwerze MCP {{serverName}}:", "wantsToAccessResource": "Roo chce uzyskać dostęp do zasobu na serwerze MCP {{serverName}}:" }, - "modes": { - "wantsToSwitch": "Roo chce przełączyć się na tryb {{mode}}", - "wantsToSwitchWithReason": "Roo chce przełączyć się na tryb {{mode}} ponieważ: {{reason}}", - "didSwitch": "Roo przełączył się na tryb {{mode}}", - "didSwitchWithReason": "Roo przełączył się na tryb {{mode}} ponieważ: {{reason}}" + "agents": { + "wantsToSwitch": "Roo chce przełączyć się na tryb {{agent}}", + "wantsToSwitchWithReason": "Roo chce przełączyć się na tryb {{agent}} ponieważ: {{reason}}", + "didSwitch": "Roo przełączył się na tryb {{agent}}", + "didSwitchWithReason": "Roo przełączył się na tryb {{agent}} ponieważ: {{reason}}" }, "subtasks": { - "wantsToCreate": "Roo chce utworzyć nowe podzadanie w trybie {{mode}}:", + "wantsToCreate": "Roo chce utworzyć nowe podzadanie w trybie {{agent}}:", "wantsToFinish": "Roo chce zakończyć to podzadanie", "newTaskContent": "Instrukcje podzadania", "completionContent": "Podzadanie zakończone", diff --git a/webview-ui/src/i18n/locales/pl/marketplace.json b/webview-ui/src/i18n/locales/pl/marketplace.json index 44bdd290d3..ad036e444b 100644 --- a/webview-ui/src/i18n/locales/pl/marketplace.json +++ b/webview-ui/src/i18n/locales/pl/marketplace.json @@ -16,7 +16,7 @@ "type": { "label": "Filtruj według typu:", "all": "Wszystkie typy", - "mode": "Tryb", + "agent": "Tryb", "mcpServer": "Serwer MCP" }, "sort": { @@ -36,7 +36,7 @@ "none": "Brak" }, "type-group": { - "modes": "Tryby", + "agents": "Tryby", "mcps": "Serwery MCP" }, "items": { @@ -128,9 +128,9 @@ } }, "removeConfirm": { - "mode": { + "agent": { "title": "Usuń tryb", - "message": "Czy na pewno chcesz usunąć tryb „{{modeName}}”?", + "message": "Czy na pewno chcesz usunąć tryb „{{agentName}}”?", "rulesWarning": "Spowoduje to również usunięcie wszelkich powiązanych plików reguł dla tego trybu." }, "mcp": { diff --git a/webview-ui/src/i18n/locales/pl/prompts.json b/webview-ui/src/i18n/locales/pl/prompts.json index c627f3a84d..c50c6b5b65 100644 --- a/webview-ui/src/i18n/locales/pl/prompts.json +++ b/webview-ui/src/i18n/locales/pl/prompts.json @@ -1,7 +1,7 @@ { "title": "Tryby", "done": "Gotowe", - "modes": { + "agents": { "title": "Tryby", "createNewMode": "Utwórz nowy tryb", "importMode": "Importuj tryb", @@ -49,8 +49,8 @@ "customInstructions": { "title": "Niestandardowe instrukcje dla trybu (opcjonalne)", "resetToDefault": "Przywróć domyślne", - "description": "Dodaj wytyczne dotyczące zachowania specyficzne dla trybu {{modeName}}.", - "loadFromFile": "Niestandardowe instrukcje dla trybu {{mode}} mogą być również ładowane z folderu .roo/rules-{{slug}}/ w Twoim obszarze roboczym (.roorules-{{slug}} i .clinerules-{{slug}} są przestarzałe i wkrótce przestaną działać)." + "description": "Dodaj wytyczne dotyczące zachowania specyficzne dla trybu {{agentName}}.", + "loadFromFile": "Niestandardowe instrukcje dla trybu {{agent}} mogą być również ładowane z folderu .roo/rules-{{slug}}/ w Twoim obszarze roboczym (.roorules-{{slug}} i .clinerules-{{slug}} są przestarzałe i wkrótce przestaną działać)." }, "exportMode": { "title": "Eksportuj tryb", @@ -82,7 +82,7 @@ "systemPrompt": { "preview": "Podgląd podpowiedzi systemowej", "copy": "Kopiuj podpowiedź systemową do schowka", - "title": "Podpowiedź systemowa (tryb {{modeName}})" + "title": "Podpowiedź systemowa (tryb {{agentName}})" }, "supportPrompts": { "title": "Podpowiedzi pomocnicze", @@ -200,7 +200,7 @@ "allFiles": "wszystkie pliki", "deleteMode": { "title": "Usuń tryb", - "message": "Czy na pewno chcesz usunąć tryb \"{{modeName}}\"?", + "message": "Czy na pewno chcesz usunąć tryb \"{{agentName}}\"?", "rulesFolder": "Ten tryb ma folder z regułami w {{folderPath}}, który również zostanie usunięty.", "descriptionNoRules": "Czy na pewno chcesz usunąć ten niestandardowy tryb?", "confirm": "Usuń", diff --git a/webview-ui/src/i18n/locales/pt-BR/chat.json b/webview-ui/src/i18n/locales/pt-BR/chat.json index cbb1918e74..3d57cc2797 100644 --- a/webview-ui/src/i18n/locales/pt-BR/chat.json +++ b/webview-ui/src/i18n/locales/pt-BR/chat.json @@ -216,14 +216,14 @@ "wantsToUseTool": "Roo quer usar uma ferramenta no servidor MCP {{serverName}}:", "wantsToAccessResource": "Roo quer acessar um recurso no servidor MCP {{serverName}}:" }, - "modes": { - "wantsToSwitch": "Roo quer mudar para o modo {{mode}}", - "wantsToSwitchWithReason": "Roo quer mudar para o modo {{mode}} porque: {{reason}}", - "didSwitch": "Roo mudou para o modo {{mode}}", - "didSwitchWithReason": "Roo mudou para o modo {{mode}} porque: {{reason}}" + "agents": { + "wantsToSwitch": "Roo quer mudar para o modo {{agent}}", + "wantsToSwitchWithReason": "Roo quer mudar para o modo {{agent}} porque: {{reason}}", + "didSwitch": "Roo mudou para o modo {{agent}}", + "didSwitchWithReason": "Roo mudou para o modo {{agent}} porque: {{reason}}" }, "subtasks": { - "wantsToCreate": "Roo quer criar uma nova subtarefa no modo {{mode}}:", + "wantsToCreate": "Roo quer criar uma nova subtarefa no modo {{agent}}:", "wantsToFinish": "Roo quer finalizar esta subtarefa", "newTaskContent": "Instruções da subtarefa", "completionContent": "Subtarefa concluída", diff --git a/webview-ui/src/i18n/locales/pt-BR/marketplace.json b/webview-ui/src/i18n/locales/pt-BR/marketplace.json index 5f472d4e80..608a83cf8b 100644 --- a/webview-ui/src/i18n/locales/pt-BR/marketplace.json +++ b/webview-ui/src/i18n/locales/pt-BR/marketplace.json @@ -16,7 +16,7 @@ "type": { "label": "Filtrar por tipo:", "all": "Todos os tipos", - "mode": "Modo", + "agent": "Modo", "mcpServer": "Servidor MCP" }, "sort": { @@ -36,7 +36,7 @@ "none": "Nenhum" }, "type-group": { - "modes": "Modos", + "agents": "Modos", "mcps": "Servidores MCP" }, "items": { @@ -128,9 +128,9 @@ } }, "removeConfirm": { - "mode": { + "agent": { "title": "Remover Modo", - "message": "Tem certeza de que deseja remover o modo \"{{modeName}}\"?", + "message": "Tem certeza de que deseja remover o modo \"{{agentName}}\"?", "rulesWarning": "Isso também removerá todos os arquivos de regras associados a este modo." }, "mcp": { diff --git a/webview-ui/src/i18n/locales/pt-BR/prompts.json b/webview-ui/src/i18n/locales/pt-BR/prompts.json index e5989d2894..1b275dc436 100644 --- a/webview-ui/src/i18n/locales/pt-BR/prompts.json +++ b/webview-ui/src/i18n/locales/pt-BR/prompts.json @@ -1,7 +1,7 @@ { "title": "Modos", "done": "Concluído", - "modes": { + "agents": { "title": "Modos", "createNewMode": "Criar novo modo", "importMode": "Importar modo", @@ -49,8 +49,8 @@ "customInstructions": { "title": "Instruções personalizadas específicas do modo (opcional)", "resetToDefault": "Restaurar para padrão", - "description": "Adicione diretrizes comportamentais específicas para o modo {{modeName}}.", - "loadFromFile": "Instruções personalizadas específicas para o modo {{mode}} também podem ser carregadas da pasta .roo/rules-{{slug}}/ no seu espaço de trabalho (.roorules-{{slug}} e .clinerules-{{slug}} estão obsoletos e deixarão de funcionar em breve)." + "description": "Adicione diretrizes comportamentais específicas para o modo {{agentName}}.", + "loadFromFile": "Instruções personalizadas específicas para o modo {{agent}} também podem ser carregadas da pasta .roo/rules-{{slug}}/ no seu espaço de trabalho (.roorules-{{slug}} e .clinerules-{{slug}} estão obsoletos e deixarão de funcionar em breve)." }, "exportMode": { "title": "Exportar modo", @@ -82,7 +82,7 @@ "systemPrompt": { "preview": "Visualizar prompt do sistema", "copy": "Copiar prompt do sistema para a área de transferência", - "title": "Prompt do sistema (modo {{modeName}})" + "title": "Prompt do sistema (modo {{agentName}})" }, "supportPrompts": { "title": "Prompts de suporte", @@ -200,7 +200,7 @@ "allFiles": "todos os arquivos", "deleteMode": { "title": "Excluir Modo", - "message": "Tem certeza de que deseja excluir o modo \"{{modeName}}\"?", + "message": "Tem certeza de que deseja excluir o modo \"{{agentName}}\"?", "rulesFolder": "Este modo tem uma pasta de regras em {{folderPath}} que também será excluída.", "descriptionNoRules": "Tem certeza de que deseja excluir este modo personalizado?", "confirm": "Excluir", diff --git a/webview-ui/src/i18n/locales/ru/chat.json b/webview-ui/src/i18n/locales/ru/chat.json index 378e749405..c7fbf56152 100644 --- a/webview-ui/src/i18n/locales/ru/chat.json +++ b/webview-ui/src/i18n/locales/ru/chat.json @@ -211,14 +211,14 @@ "wantsToUseTool": "Roo хочет использовать инструмент на сервере MCP {{serverName}}:", "wantsToAccessResource": "Roo хочет получить доступ к ресурсу на сервере MCP {{serverName}}:" }, - "modes": { - "wantsToSwitch": "Roo хочет переключиться в режим {{mode}}", - "wantsToSwitchWithReason": "Roo хочет переключиться в режим {{mode}}, потому что: {{reason}}", - "didSwitch": "Roo переключился в режим {{mode}}", - "didSwitchWithReason": "Roo переключился в режим {{mode}}, потому что: {{reason}}" + "agents": { + "wantsToSwitch": "Roo хочет переключиться в режим {{agent}}", + "wantsToSwitchWithReason": "Roo хочет переключиться в режим {{agent}}, потому что: {{reason}}", + "didSwitch": "Roo переключился в режим {{agent}}", + "didSwitchWithReason": "Roo переключился в режим {{agent}}, потому что: {{reason}}" }, "subtasks": { - "wantsToCreate": "Roo хочет создать новую подзадачу в режиме {{mode}}:", + "wantsToCreate": "Roo хочет создать новую подзадачу в режиме {{agent}}:", "wantsToFinish": "Roo хочет завершить эту подзадачу", "newTaskContent": "Инструкции по подзадаче", "completionContent": "Подзадача завершена", diff --git a/webview-ui/src/i18n/locales/ru/marketplace.json b/webview-ui/src/i18n/locales/ru/marketplace.json index f32d855406..2fe4e1c92d 100644 --- a/webview-ui/src/i18n/locales/ru/marketplace.json +++ b/webview-ui/src/i18n/locales/ru/marketplace.json @@ -16,7 +16,7 @@ "type": { "label": "Фильтр по типу:", "all": "Все типы", - "mode": "Режим", + "agent": "Режим", "mcpServer": "MCP сервер" }, "sort": { @@ -36,7 +36,7 @@ "none": "Нет" }, "type-group": { - "modes": "Режимы", + "agents": "Режимы", "mcps": "MCP серверы" }, "items": { @@ -128,9 +128,9 @@ } }, "removeConfirm": { - "mode": { + "agent": { "title": "Удалить режим", - "message": "Вы уверены, что хотите удалить режим «{{modeName}}»?", + "message": "Вы уверены, что хотите удалить режим «{{agentName}}»?", "rulesWarning": "Это также удалит все связанные файлы правил для этого режима." }, "mcp": { diff --git a/webview-ui/src/i18n/locales/ru/prompts.json b/webview-ui/src/i18n/locales/ru/prompts.json index c96d4b54a1..526048e827 100644 --- a/webview-ui/src/i18n/locales/ru/prompts.json +++ b/webview-ui/src/i18n/locales/ru/prompts.json @@ -1,7 +1,7 @@ { "title": "Режимы", "done": "Готово", - "modes": { + "agents": { "title": "Режимы", "createNewMode": "Создать новый режим", "importMode": "Импортировать режим", @@ -49,8 +49,8 @@ "customInstructions": { "title": "Пользовательские инструкции для режима (необязательно)", "resetToDefault": "Сбросить по умолчанию", - "description": "Добавьте рекомендации по поведению, специфичные для режима {{modeName}}.", - "loadFromFile": "Пользовательские инструкции для режима {{mode}} также можно загрузить из папки .roo/rules-{{slug}}/ в вашем рабочем пространстве (.roorules-{{slug}} и .clinerules-{{slug}} устарели и скоро перестанут работать)." + "description": "Добавьте рекомендации по поведению, специфичные для режима {{agentName}}.", + "loadFromFile": "Пользовательские инструкции для режима {{agent}} также можно загрузить из папки .roo/rules-{{slug}}/ в вашем рабочем пространстве (.roorules-{{slug}} и .clinerules-{{slug}} устарели и скоро перестанут работать)." }, "exportMode": { "title": "Экспортировать режим", @@ -79,7 +79,7 @@ "systemPrompt": { "preview": "Предпросмотр системного промпта", "copy": "Скопировать системный промпт в буфер обмена", - "title": "Системный промпт (режим {{modeName}})" + "title": "Системный промпт (режим {{agentName}})" }, "supportPrompts": { "title": "Вспомогательные промпты", @@ -200,7 +200,7 @@ }, "deleteMode": { "title": "Удалить режим", - "message": "Вы уверены, что хотите удалить режим \"{{modeName}}\"?", + "message": "Вы уверены, что хотите удалить режим \"{{agentName}}\"?", "rulesFolder": "У этого режима есть папка правил по адресу {{folderPath}}, которая также будет удалена.", "descriptionNoRules": "Вы уверены, что хотите удалить этот пользовательский режим?", "confirm": "Удалить", diff --git a/webview-ui/src/i18n/locales/tr/chat.json b/webview-ui/src/i18n/locales/tr/chat.json index e217184a9c..cf82911696 100644 --- a/webview-ui/src/i18n/locales/tr/chat.json +++ b/webview-ui/src/i18n/locales/tr/chat.json @@ -216,14 +216,14 @@ "wantsToUseTool": "Roo {{serverName}} MCP sunucusunda bir araç kullanmak istiyor:", "wantsToAccessResource": "Roo {{serverName}} MCP sunucusundaki bir kaynağa erişmek istiyor:" }, - "modes": { - "wantsToSwitch": "Roo {{mode}} moduna geçmek istiyor", - "wantsToSwitchWithReason": "Roo {{mode}} moduna geçmek istiyor çünkü: {{reason}}", - "didSwitch": "Roo {{mode}} moduna geçti", - "didSwitchWithReason": "Roo {{mode}} moduna geçti çünkü: {{reason}}" + "agents": { + "wantsToSwitch": "Roo {{agent}} moduna geçmek istiyor", + "wantsToSwitchWithReason": "Roo {{agent}} moduna geçmek istiyor çünkü: {{reason}}", + "didSwitch": "Roo {{agent}} moduna geçti", + "didSwitchWithReason": "Roo {{agent}} moduna geçti çünkü: {{reason}}" }, "subtasks": { - "wantsToCreate": "Roo {{mode}} modunda yeni bir alt görev oluşturmak istiyor:", + "wantsToCreate": "Roo {{agent}} modunda yeni bir alt görev oluşturmak istiyor:", "wantsToFinish": "Roo bu alt görevi bitirmek istiyor", "newTaskContent": "Alt Görev Talimatları", "completionContent": "Alt Görev Tamamlandı", diff --git a/webview-ui/src/i18n/locales/tr/marketplace.json b/webview-ui/src/i18n/locales/tr/marketplace.json index 279ae2c38a..b6d0d1a69f 100644 --- a/webview-ui/src/i18n/locales/tr/marketplace.json +++ b/webview-ui/src/i18n/locales/tr/marketplace.json @@ -16,7 +16,7 @@ "type": { "label": "Türe göre filtrele:", "all": "Tüm türler", - "mode": "Mod", + "agent": "Mod", "mcpServer": "MCP Sunucusu" }, "sort": { @@ -36,7 +36,7 @@ "none": "Hiçbiri" }, "type-group": { - "modes": "Modlar", + "agents": "Modlar", "mcps": "MCP Sunucuları" }, "items": { @@ -128,9 +128,9 @@ } }, "removeConfirm": { - "mode": { + "agent": { "title": "Modu Kaldır", - "message": "\"{{modeName}}\" modunu kaldırmak istediğinizden emin misiniz?", + "message": "\"{{agentName}}\" modunu kaldırmak istediğinizden emin misiniz?", "rulesWarning": "Bu, bu mod için ilişkili tüm kural dosyalarını da kaldıracaktır." }, "mcp": { diff --git a/webview-ui/src/i18n/locales/tr/prompts.json b/webview-ui/src/i18n/locales/tr/prompts.json index 9b7e2c569f..db97e43bcb 100644 --- a/webview-ui/src/i18n/locales/tr/prompts.json +++ b/webview-ui/src/i18n/locales/tr/prompts.json @@ -1,7 +1,7 @@ { "title": "Modlar", "done": "Tamamlandı", - "modes": { + "agents": { "title": "Modlar", "createNewMode": "Yeni mod oluştur", "importMode": "Modu içe aktar", @@ -49,8 +49,8 @@ "customInstructions": { "title": "Moda özgü özel talimatlar (isteğe bağlı)", "resetToDefault": "Varsayılana sıfırla", - "description": "{{modeName}} modu için özel davranış yönergeleri ekleyin.", - "loadFromFile": "{{mode}} moduna özgü özel talimatlar ayrıca çalışma alanınızdaki .roo/rules-{{slug}}/ klasöründen yüklenebilir (.roorules-{{slug}} ve .clinerules-{{slug}} kullanımdan kaldırılmıştır ve yakında çalışmayı durduracaklardır)." + "description": "{{agentName}} modu için özel davranış yönergeleri ekleyin.", + "loadFromFile": "{{agent}} moduna özgü özel talimatlar ayrıca çalışma alanınızdaki .roo/rules-{{slug}}/ klasöründen yüklenebilir (.roorules-{{slug}} ve .clinerules-{{slug}} kullanımdan kaldırılmıştır ve yakında çalışmayı durduracaklardır)." }, "exportMode": { "title": "Modu Dışa Aktar", @@ -79,7 +79,7 @@ "systemPrompt": { "preview": "Sistem promptunu önizle", "copy": "Sistem promptunu panoya kopyala", - "title": "Sistem promptu ({{modeName}} modu)" + "title": "Sistem promptu ({{agentName}} modu)" }, "supportPrompts": { "title": "Destek Promptları", @@ -200,7 +200,7 @@ }, "deleteMode": { "title": "Modu Sil", - "message": "\"{{modeName}}\" modunu silmek istediğinizden emin misiniz?", + "message": "\"{{agentName}}\" modunu silmek istediğinizden emin misiniz?", "rulesFolder": "Bu modun {{folderPath}} konumunda bir kurallar klasörü var ve bu da silinecek.", "descriptionNoRules": "Bu özel modu silmek istediğinizden emin misiniz?", "confirm": "Sil", diff --git a/webview-ui/src/i18n/locales/vi/chat.json b/webview-ui/src/i18n/locales/vi/chat.json index b6fe59d9b7..a06a95b2dd 100644 --- a/webview-ui/src/i18n/locales/vi/chat.json +++ b/webview-ui/src/i18n/locales/vi/chat.json @@ -216,14 +216,14 @@ "wantsToUseTool": "Roo muốn sử dụng một công cụ trên máy chủ MCP {{serverName}}:", "wantsToAccessResource": "Roo muốn truy cập một tài nguyên trên máy chủ MCP {{serverName}}:" }, - "modes": { - "wantsToSwitch": "Roo muốn chuyển sang chế độ {{mode}}", - "wantsToSwitchWithReason": "Roo muốn chuyển sang chế độ {{mode}} vì: {{reason}}", - "didSwitch": "Roo đã chuyển sang chế độ {{mode}}", - "didSwitchWithReason": "Roo đã chuyển sang chế độ {{mode}} vì: {{reason}}" + "agents": { + "wantsToSwitch": "Roo muốn chuyển sang chế độ {{agent}}", + "wantsToSwitchWithReason": "Roo muốn chuyển sang chế độ {{agent}} vì: {{reason}}", + "didSwitch": "Roo đã chuyển sang chế độ {{agent}}", + "didSwitchWithReason": "Roo đã chuyển sang chế độ {{agent}} vì: {{reason}}" }, "subtasks": { - "wantsToCreate": "Roo muốn tạo một nhiệm vụ phụ mới trong chế độ {{mode}}:", + "wantsToCreate": "Roo muốn tạo một nhiệm vụ phụ mới trong chế độ {{agent}}:", "wantsToFinish": "Roo muốn hoàn thành nhiệm vụ phụ này", "newTaskContent": "Hướng dẫn nhiệm vụ phụ", "completionContent": "Nhiệm vụ phụ đã hoàn thành", diff --git a/webview-ui/src/i18n/locales/vi/marketplace.json b/webview-ui/src/i18n/locales/vi/marketplace.json index fcb5beefc4..340e3fd21f 100644 --- a/webview-ui/src/i18n/locales/vi/marketplace.json +++ b/webview-ui/src/i18n/locales/vi/marketplace.json @@ -16,7 +16,7 @@ "type": { "label": "Lọc theo loại:", "all": "Tất cả loại", - "mode": "Chế độ", + "agent": "Chế độ", "mcpServer": "Máy chủ MCP" }, "sort": { @@ -36,7 +36,7 @@ "none": "Không có" }, "type-group": { - "modes": "Chế độ", + "agents": "Chế độ", "mcps": "Máy chủ MCP" }, "items": { @@ -128,9 +128,9 @@ } }, "removeConfirm": { - "mode": { + "agent": { "title": "Xóa chế độ", - "message": "Bạn có chắc chắn muốn xóa chế độ \"{{modeName}}\" không?", + "message": "Bạn có chắc chắn muốn xóa chế độ \"{{agentName}}\" không?", "rulesWarning": "Thao tác này cũng sẽ xóa mọi tệp quy tắc được liên kết cho chế độ này." }, "mcp": { diff --git a/webview-ui/src/i18n/locales/vi/prompts.json b/webview-ui/src/i18n/locales/vi/prompts.json index d3b7e75f3c..a1e57eb2c3 100644 --- a/webview-ui/src/i18n/locales/vi/prompts.json +++ b/webview-ui/src/i18n/locales/vi/prompts.json @@ -1,7 +1,7 @@ { "title": "Chế độ", "done": "Hoàn thành", - "modes": { + "agents": { "title": "Chế độ", "createNewMode": "Tạo chế độ mới", "importMode": "Nhập chế độ", @@ -49,8 +49,8 @@ "customInstructions": { "title": "Hướng dẫn tùy chỉnh dành riêng cho chế độ (tùy chọn)", "resetToDefault": "Đặt lại về mặc định", - "description": "Thêm hướng dẫn hành vi dành riêng cho chế độ {{modeName}}.", - "loadFromFile": "Hướng dẫn tùy chỉnh dành riêng cho chế độ {{mode}} cũng có thể được tải từ thư mục .roo/rules-{{slug}}/ trong không gian làm việc của bạn (.roorules-{{slug}} và .clinerules-{{slug}} đã lỗi thời và sẽ sớm ngừng hoạt động)." + "description": "Thêm hướng dẫn hành vi dành riêng cho chế độ {{agentName}}.", + "loadFromFile": "Hướng dẫn tùy chỉnh dành riêng cho chế độ {{agent}} cũng có thể được tải từ thư mục .roo/rules-{{slug}}/ trong không gian làm việc của bạn (.roorules-{{slug}} và .clinerules-{{slug}} đã lỗi thời và sẽ sớm ngừng hoạt động)." }, "exportMode": { "title": "Xuất chế độ", @@ -79,7 +79,7 @@ "systemPrompt": { "preview": "Xem trước lời nhắc hệ thống", "copy": "Sao chép lời nhắc hệ thống vào bộ nhớ tạm", - "title": "Lời nhắc hệ thống (chế độ {{modeName}})" + "title": "Lời nhắc hệ thống (chế độ {{agentName}})" }, "supportPrompts": { "title": "Lời nhắc hỗ trợ", @@ -200,7 +200,7 @@ }, "deleteMode": { "title": "Xóa chế độ", - "message": "Bạn có chắc chắn muốn xóa chế độ \"{{modeName}}\" không?", + "message": "Bạn có chắc chắn muốn xóa chế độ \"{{agentName}}\" không?", "rulesFolder": "Chế độ này có một thư mục quy tắc tại {{folderPath}} cũng sẽ bị xóa.", "descriptionNoRules": "Bạn có chắc chắn muốn xóa chế độ tùy chỉnh này không?", "confirm": "Xóa", diff --git a/webview-ui/src/i18n/locales/zh-CN/chat.json b/webview-ui/src/i18n/locales/zh-CN/chat.json index 4f87348a98..50d4f37be4 100644 --- a/webview-ui/src/i18n/locales/zh-CN/chat.json +++ b/webview-ui/src/i18n/locales/zh-CN/chat.json @@ -216,14 +216,14 @@ "wantsToUseTool": "Roo想在{{serverName}} MCP上使用工具:", "wantsToAccessResource": "Roo想访问{{serverName}} MCP服务上的资源:" }, - "modes": { - "wantsToSwitch": "即将切换至{{mode}}模式", - "wantsToSwitchWithReason": "即将切换至{{mode}}模式(原因:{{reason}})", - "didSwitch": "已切换至{{mode}}模式", - "didSwitchWithReason": "已切换至{{mode}}模式(原因:{{reason}})" + "agents": { + "wantsToSwitch": "即将切换至{{agent}}模式", + "wantsToSwitchWithReason": "即将切换至{{agent}}模式(原因:{{reason}})", + "didSwitch": "已切换至{{agent}}模式", + "didSwitchWithReason": "已切换至{{agent}}模式(原因:{{reason}})" }, "subtasks": { - "wantsToCreate": "Roo想在{{mode}}模式下创建新子任务:", + "wantsToCreate": "Roo想在{{agent}}模式下创建新子任务:", "wantsToFinish": "Roo想完成此子任务", "newTaskContent": "子任务说明", "completionContent": "子任务已完成", diff --git a/webview-ui/src/i18n/locales/zh-CN/marketplace.json b/webview-ui/src/i18n/locales/zh-CN/marketplace.json index 598da383d4..bf4d213672 100644 --- a/webview-ui/src/i18n/locales/zh-CN/marketplace.json +++ b/webview-ui/src/i18n/locales/zh-CN/marketplace.json @@ -16,7 +16,7 @@ "type": { "label": "按类型筛选:", "all": "所有类型", - "mode": "模式", + "agent": "模式", "mcpServer": "MCP 服务" }, "sort": { @@ -36,7 +36,7 @@ "none": "无" }, "type-group": { - "modes": "模式", + "agents": "模式", "mcps": "MCP 服务" }, "items": { @@ -128,9 +128,9 @@ } }, "removeConfirm": { - "mode": { + "agent": { "title": "删除模式", - "message": "您确定要删除\"{{modeName}}\"模式吗?", + "message": "您确定要删除\"{{agentName}}\"模式吗?", "rulesWarning": "这也将删除此模式的任何关联规则文件。" }, "mcp": { diff --git a/webview-ui/src/i18n/locales/zh-CN/prompts.json b/webview-ui/src/i18n/locales/zh-CN/prompts.json index 157ba5d7ea..85db6dbc88 100644 --- a/webview-ui/src/i18n/locales/zh-CN/prompts.json +++ b/webview-ui/src/i18n/locales/zh-CN/prompts.json @@ -1,7 +1,7 @@ { "title": "模式", "done": "完成", - "modes": { + "agents": { "title": "模式配置", "createNewMode": "新建模式", "importMode": "导入模式", @@ -49,7 +49,7 @@ "customInstructions": { "title": "模式专属规则(可选)", "resetToDefault": "重置为默认值", - "description": "{{modeName}}模式的专属规则", + "description": "{{agentName}}模式的专属规则", "loadFromFile": "支持从.roo/rules-{{slug}}/目录读取配置(.roorules-{{slug}}和.clinerules-{{slug}}已弃用并将很快停止工作)。" }, "exportMode": { @@ -79,7 +79,7 @@ "systemPrompt": { "preview": "预览系统提示词", "copy": "复制系统提示词", - "title": "系统提示词({{modeName}}模式)" + "title": "系统提示词({{agentName}}模式)" }, "supportPrompts": { "title": "功能提示词", @@ -200,7 +200,7 @@ }, "deleteMode": { "title": "删除模式", - "message": "您确定要删除\"{{modeName}}\"模式吗?", + "message": "您确定要删除\"{{agentName}}\"模式吗?", "rulesFolder": "此模式在 {{folderPath}} 有一个规则文件夹,该文件夹也将被删除。", "descriptionNoRules": "您确定要删除此自定义模式吗?", "confirm": "删除", diff --git a/webview-ui/src/i18n/locales/zh-TW/chat.json b/webview-ui/src/i18n/locales/zh-TW/chat.json index 0671a07f87..34317c7a38 100644 --- a/webview-ui/src/i18n/locales/zh-TW/chat.json +++ b/webview-ui/src/i18n/locales/zh-TW/chat.json @@ -216,14 +216,14 @@ "wantsToUseTool": "Roo 想要在 {{serverName}} MCP 伺服器上使用工具:", "wantsToAccessResource": "Roo 想要存取 {{serverName}} MCP 伺服器上的資源:" }, - "modes": { - "wantsToSwitch": "Roo 想要切換至 {{mode}} 模式", - "wantsToSwitchWithReason": "Roo 想要切換至 {{mode}} 模式,原因:{{reason}}", - "didSwitch": "Roo 已切換至 {{mode}} 模式", - "didSwitchWithReason": "Roo 已切換至 {{mode}} 模式,原因:{{reason}}" + "agents": { + "wantsToSwitch": "Roo 想要切換至 {{agent}} 模式", + "wantsToSwitchWithReason": "Roo 想要切換至 {{agent}} 模式,原因:{{reason}}", + "didSwitch": "Roo 已切換至 {{agent}} 模式", + "didSwitchWithReason": "Roo 已切換至 {{agent}} 模式,原因:{{reason}}" }, "subtasks": { - "wantsToCreate": "Roo 想要在 {{mode}} 模式下建立新的子工作:", + "wantsToCreate": "Roo 想要在 {{agent}} 模式下建立新的子工作:", "wantsToFinish": "Roo 想要完成此子工作", "newTaskContent": "子工作指示", "completionContent": "子工作已完成", diff --git a/webview-ui/src/i18n/locales/zh-TW/marketplace.json b/webview-ui/src/i18n/locales/zh-TW/marketplace.json index 1ac6ed53c2..694612b95d 100644 --- a/webview-ui/src/i18n/locales/zh-TW/marketplace.json +++ b/webview-ui/src/i18n/locales/zh-TW/marketplace.json @@ -16,7 +16,7 @@ "type": { "label": "依類型篩選:", "all": "所有類型", - "mode": "模式", + "agent": "模式", "mcpServer": "MCP 伺服器" }, "sort": { @@ -36,7 +36,7 @@ "none": "無" }, "type-group": { - "modes": "模式", + "agents": "模式", "mcps": "MCP 伺服器" }, "items": { @@ -128,9 +128,9 @@ } }, "removeConfirm": { - "mode": { + "agent": { "title": "移除模式", - "message": "您確定要移除「{{modeName}}」模式嗎?", + "message": "您確定要移除「{{agentName}}」模式嗎?", "rulesWarning": "這也會移除此模式的任何相關規則檔案。" }, "mcp": { diff --git a/webview-ui/src/i18n/locales/zh-TW/prompts.json b/webview-ui/src/i18n/locales/zh-TW/prompts.json index 3a2bae4af5..bf69515e60 100644 --- a/webview-ui/src/i18n/locales/zh-TW/prompts.json +++ b/webview-ui/src/i18n/locales/zh-TW/prompts.json @@ -1,7 +1,7 @@ { "title": "模式", "done": "完成", - "modes": { + "agents": { "title": "模式", "createNewMode": "建立新模式", "importMode": "匯入模式", @@ -49,8 +49,8 @@ "customInstructions": { "title": "模式專屬自訂指令(選用)", "resetToDefault": "重設為預設值", - "description": "為 {{modeName}} 模式新增專屬的行為指南。", - "loadFromFile": "{{mode}} 模式的自訂指令也可以從工作區的 .roo/rules-{{slug}}/ 資料夾載入(.roorules-{{slug}} 和 .clinerules-{{slug}} 已棄用並將很快停止運作)。" + "description": "為 {{agentName}} 模式新增專屬的行為指南。", + "loadFromFile": "{{agent}} 模式的自訂指令也可以從工作區的 .roo/rules-{{slug}}/ 資料夾載入(.roorules-{{slug}} 和 .clinerules-{{slug}} 已棄用並將很快停止運作)。" }, "exportMode": { "title": "匯出模式", @@ -79,7 +79,7 @@ "systemPrompt": { "preview": "預覽系統提示詞", "copy": "複製系統提示詞到剪貼簿", - "title": "系統提示詞({{modeName}} 模式)" + "title": "系統提示詞({{agentName}} 模式)" }, "supportPrompts": { "title": "輔助提示詞", @@ -200,7 +200,7 @@ }, "deleteMode": { "title": "刪除模式", - "message": "您確定要刪除「{{modeName}}」模式嗎?", + "message": "您確定要刪除「{{agentName}}」模式嗎?", "rulesFolder": "此模式在 {{folderPath}} 有一個規則資料夾,該資料夾也將被刪除。", "descriptionNoRules": "您確定要刪除此自訂模式嗎?", "confirm": "刪除",