diff --git a/packages/types/src/experiment.ts b/packages/types/src/experiment.ts index 5d5610daa1..37c6eecee7 100644 --- a/packages/types/src/experiment.ts +++ b/packages/types/src/experiment.ts @@ -11,6 +11,7 @@ export const experimentIds = [ "multiFileApplyDiff", "preventFocusDisruption", "imageGeneration", + "runSlashCommand", ] as const export const experimentIdsSchema = z.enum(experimentIds) @@ -26,6 +27,7 @@ export const experimentsSchema = z.object({ multiFileApplyDiff: z.boolean().optional(), preventFocusDisruption: z.boolean().optional(), imageGeneration: z.boolean().optional(), + runSlashCommand: z.boolean().optional(), }) export type Experiments = z.infer diff --git a/packages/types/src/tool.ts b/packages/types/src/tool.ts index c31f63df76..2c7495e5eb 100644 --- a/packages/types/src/tool.ts +++ b/packages/types/src/tool.ts @@ -34,6 +34,7 @@ export const toolNames = [ "fetch_instructions", "codebase_search", "update_todo_list", + "run_slash_command", "generate_image", ] as const diff --git a/src/core/assistant-message/presentAssistantMessage.ts b/src/core/assistant-message/presentAssistantMessage.ts index af1c57a5ee..689675999f 100644 --- a/src/core/assistant-message/presentAssistantMessage.ts +++ b/src/core/assistant-message/presentAssistantMessage.ts @@ -28,6 +28,7 @@ import { attemptCompletionTool } from "../tools/attemptCompletionTool" import { newTaskTool } from "../tools/newTaskTool" import { updateTodoListTool } from "../tools/updateTodoListTool" +import { runSlashCommandTool } from "../tools/runSlashCommandTool" import { generateImageTool } from "../tools/generateImageTool" import { formatResponse } from "../prompts/responses" @@ -222,6 +223,8 @@ export async function presentAssistantMessage(cline: Task) { const modeName = getModeBySlug(mode, customModes)?.name ?? mode return `[${block.name} in ${modeName} mode: '${message}']` } + case "run_slash_command": + return `[${block.name} for '${block.params.command}'${block.params.args ? ` with args: ${block.params.args}` : ""}]` case "generate_image": return `[${block.name} for '${block.params.path}']` } @@ -549,6 +552,9 @@ export async function presentAssistantMessage(cline: Task) { askFinishSubTaskApproval, ) break + case "run_slash_command": + await runSlashCommandTool(cline, block, askApproval, handleError, pushToolResult, removeClosingTag) + break case "generate_image": await generateImageTool(cline, block, askApproval, handleError, pushToolResult, removeClosingTag) break diff --git a/src/core/prompts/tools/index.ts b/src/core/prompts/tools/index.ts index 8b4e90733c..c212b18a3d 100644 --- a/src/core/prompts/tools/index.ts +++ b/src/core/prompts/tools/index.ts @@ -25,6 +25,7 @@ import { getSwitchModeDescription } from "./switch-mode" import { getNewTaskDescription } from "./new-task" import { getCodebaseSearchDescription } from "./codebase-search" import { getUpdateTodoListDescription } from "./update-todo-list" +import { getRunSlashCommandDescription } from "./run-slash-command" import { getGenerateImageDescription } from "./generate-image" import { CodeIndexManager } from "../../../services/code-index/manager" @@ -57,6 +58,7 @@ const toolDescriptionMap: Record string | undefined> apply_diff: (args) => args.diffStrategy ? args.diffStrategy.getToolDescription({ cwd: args.cwd, toolOptions: args.toolOptions }) : "", update_todo_list: (args) => getUpdateTodoListDescription(args), + run_slash_command: () => getRunSlashCommandDescription(), generate_image: (args) => getGenerateImageDescription(args), } @@ -136,6 +138,11 @@ export function getToolDescriptionsForMode( tools.delete("generate_image") } + // Conditionally exclude run_slash_command if experiment is not enabled + if (!experiments?.runSlashCommand) { + tools.delete("run_slash_command") + } + // Map tool descriptions for allowed tools const descriptions = Array.from(tools).map((toolName) => { const descriptionFn = toolDescriptionMap[toolName] @@ -171,5 +178,6 @@ export { getInsertContentDescription, getSearchAndReplaceDescription, getCodebaseSearchDescription, + getRunSlashCommandDescription, getGenerateImageDescription, } diff --git a/src/core/prompts/tools/run-slash-command.ts b/src/core/prompts/tools/run-slash-command.ts new file mode 100644 index 0000000000..27047dcbaa --- /dev/null +++ b/src/core/prompts/tools/run-slash-command.ts @@ -0,0 +1,32 @@ +/** + * Generates the run_slash_command tool description. + */ +export function getRunSlashCommandDescription(): string { + return `## run_slash_command +Description: Execute a slash command to get specific instructions or content. Slash commands are predefined templates that provide detailed guidance for common tasks. + +Parameters: +- command: (required) The name of the slash command to execute (e.g., "init", "test", "deploy") +- args: (optional) Additional arguments or context to pass to the command + +Usage: + +command_name +optional arguments + + +Examples: + +1. Running the init command to analyze a codebase: + +init + + +2. Running a command with additional context: + +test +focus on integration tests + + +The command content will be returned for you to execute or follow as instructions.` +} diff --git a/src/core/tools/__tests__/runSlashCommandTool.spec.ts b/src/core/tools/__tests__/runSlashCommandTool.spec.ts new file mode 100644 index 0000000000..07143e96cc --- /dev/null +++ b/src/core/tools/__tests__/runSlashCommandTool.spec.ts @@ -0,0 +1,380 @@ +import { describe, it, expect, vi, beforeEach } from "vitest" +import { runSlashCommandTool } from "../runSlashCommandTool" +import { Task } from "../../task/Task" +import { formatResponse } from "../../prompts/responses" +import { getCommand, getCommandNames } from "../../../services/command/commands" + +// Mock dependencies +vi.mock("../../../services/command/commands", () => ({ + getCommand: vi.fn(), + getCommandNames: vi.fn(), +})) + +describe("runSlashCommandTool", () => { + let mockTask: any + let mockAskApproval: any + let mockHandleError: any + let mockPushToolResult: any + let mockRemoveClosingTag: any + + beforeEach(() => { + vi.clearAllMocks() + + mockTask = { + consecutiveMistakeCount: 0, + recordToolError: vi.fn(), + sayAndCreateMissingParamError: vi.fn().mockResolvedValue("Missing parameter error"), + ask: vi.fn(), + cwd: "/test/project", + providerRef: { + deref: vi.fn().mockReturnValue({ + getState: vi.fn().mockResolvedValue({ + experiments: { + runSlashCommand: true, + }, + }), + }), + }, + } + + mockAskApproval = vi.fn().mockResolvedValue(true) + mockHandleError = vi.fn() + mockPushToolResult = vi.fn() + mockRemoveClosingTag = vi.fn((tag, text) => text || "") + }) + + it("should handle missing command parameter", async () => { + const block = { + type: "tool_use" as const, + name: "run_slash_command" as const, + params: {}, + partial: false, + } + + await runSlashCommandTool( + mockTask as Task, + block, + mockAskApproval, + mockHandleError, + mockPushToolResult, + mockRemoveClosingTag, + ) + + expect(mockTask.consecutiveMistakeCount).toBe(1) + expect(mockTask.recordToolError).toHaveBeenCalledWith("run_slash_command") + expect(mockTask.sayAndCreateMissingParamError).toHaveBeenCalledWith("run_slash_command", "command") + expect(mockPushToolResult).toHaveBeenCalledWith("Missing parameter error") + }) + + it("should handle command not found", async () => { + const block = { + type: "tool_use" as const, + name: "run_slash_command" as const, + params: { + command: "nonexistent", + }, + partial: false, + } + + vi.mocked(getCommand).mockResolvedValue(undefined) + vi.mocked(getCommandNames).mockResolvedValue(["init", "test", "deploy"]) + + await runSlashCommandTool( + mockTask as Task, + block, + mockAskApproval, + mockHandleError, + mockPushToolResult, + mockRemoveClosingTag, + ) + + expect(mockTask.recordToolError).toHaveBeenCalledWith("run_slash_command") + expect(mockPushToolResult).toHaveBeenCalledWith( + formatResponse.toolError("Command 'nonexistent' not found. Available commands: init, test, deploy"), + ) + }) + + it("should handle user rejection", async () => { + const block = { + type: "tool_use" as const, + name: "run_slash_command" as const, + params: { + command: "init", + }, + partial: false, + } + + const mockCommand = { + name: "init", + content: "Initialize project", + source: "built-in" as const, + filePath: "", + description: "Initialize the project", + } + + vi.mocked(getCommand).mockResolvedValue(mockCommand) + mockAskApproval.mockResolvedValue(false) + + await runSlashCommandTool( + mockTask as Task, + block, + mockAskApproval, + mockHandleError, + mockPushToolResult, + mockRemoveClosingTag, + ) + + expect(mockAskApproval).toHaveBeenCalled() + expect(mockPushToolResult).not.toHaveBeenCalled() + }) + + it("should successfully execute built-in command", async () => { + const block = { + type: "tool_use" as const, + name: "run_slash_command" as const, + params: { + command: "init", + }, + partial: false, + } + + const mockCommand = { + name: "init", + content: "Initialize project content here", + source: "built-in" as const, + filePath: "", + description: "Analyze codebase and create AGENTS.md", + } + + vi.mocked(getCommand).mockResolvedValue(mockCommand) + + await runSlashCommandTool( + mockTask as Task, + block, + mockAskApproval, + mockHandleError, + mockPushToolResult, + mockRemoveClosingTag, + ) + + expect(mockAskApproval).toHaveBeenCalledWith( + "tool", + JSON.stringify({ + tool: "runSlashCommand", + command: "init", + args: undefined, + source: "built-in", + description: "Analyze codebase and create AGENTS.md", + }), + ) + + expect(mockPushToolResult).toHaveBeenCalledWith( + `Command: /init +Description: Analyze codebase and create AGENTS.md +Source: built-in + +--- Command Content --- + +Initialize project content here`, + ) + }) + + it("should successfully execute command with arguments", async () => { + const block = { + type: "tool_use" as const, + name: "run_slash_command" as const, + params: { + command: "test", + args: "focus on unit tests", + }, + partial: false, + } + + const mockCommand = { + name: "test", + content: "Run tests with specific focus", + source: "project" as const, + filePath: ".roo/commands/test.md", + description: "Run project tests", + argumentHint: "test type or focus area", + } + + vi.mocked(getCommand).mockResolvedValue(mockCommand) + + await runSlashCommandTool( + mockTask as Task, + block, + mockAskApproval, + mockHandleError, + mockPushToolResult, + mockRemoveClosingTag, + ) + + expect(mockPushToolResult).toHaveBeenCalledWith( + `Command: /test +Description: Run project tests +Argument hint: test type or focus area +Provided arguments: focus on unit tests +Source: project + +--- Command Content --- + +Run tests with specific focus`, + ) + }) + + it("should handle global command", async () => { + const block = { + type: "tool_use" as const, + name: "run_slash_command" as const, + params: { + command: "deploy", + }, + partial: false, + } + + const mockCommand = { + name: "deploy", + content: "Deploy application to production", + source: "global" as const, + filePath: "~/.roo/commands/deploy.md", + } + + vi.mocked(getCommand).mockResolvedValue(mockCommand) + + await runSlashCommandTool( + mockTask as Task, + block, + mockAskApproval, + mockHandleError, + mockPushToolResult, + mockRemoveClosingTag, + ) + + expect(mockPushToolResult).toHaveBeenCalledWith( + `Command: /deploy +Source: global + +--- Command Content --- + +Deploy application to production`, + ) + }) + + it("should handle partial block", async () => { + const block = { + type: "tool_use" as const, + name: "run_slash_command" as const, + params: { + command: "init", + }, + partial: true, + } + + await runSlashCommandTool( + mockTask as Task, + block, + mockAskApproval, + mockHandleError, + mockPushToolResult, + mockRemoveClosingTag, + ) + + expect(mockTask.ask).toHaveBeenCalledWith( + "tool", + JSON.stringify({ + tool: "runSlashCommand", + command: "init", + args: "", + }), + true, + ) + + expect(mockPushToolResult).not.toHaveBeenCalled() + }) + + it("should handle errors during execution", async () => { + const block = { + type: "tool_use" as const, + name: "run_slash_command" as const, + params: { + command: "init", + }, + partial: false, + } + + const error = new Error("Test error") + vi.mocked(getCommand).mockRejectedValue(error) + + await runSlashCommandTool( + mockTask as Task, + block, + mockAskApproval, + mockHandleError, + mockPushToolResult, + mockRemoveClosingTag, + ) + + expect(mockHandleError).toHaveBeenCalledWith("running slash command", error) + }) + + it("should handle empty available commands list", async () => { + const block = { + type: "tool_use" as const, + name: "run_slash_command" as const, + params: { + command: "nonexistent", + }, + partial: false, + } + + vi.mocked(getCommand).mockResolvedValue(undefined) + vi.mocked(getCommandNames).mockResolvedValue([]) + + await runSlashCommandTool( + mockTask as Task, + block, + mockAskApproval, + mockHandleError, + mockPushToolResult, + mockRemoveClosingTag, + ) + + expect(mockPushToolResult).toHaveBeenCalledWith( + formatResponse.toolError("Command 'nonexistent' not found. Available commands: (none)"), + ) + }) + + it("should reset consecutive mistake count on valid command", async () => { + const block = { + type: "tool_use" as const, + name: "run_slash_command" as const, + params: { + command: "init", + }, + partial: false, + } + + mockTask.consecutiveMistakeCount = 5 + + const mockCommand = { + name: "init", + content: "Initialize project", + source: "built-in" as const, + filePath: "", + } + + vi.mocked(getCommand).mockResolvedValue(mockCommand) + + await runSlashCommandTool( + mockTask as Task, + block, + mockAskApproval, + mockHandleError, + mockPushToolResult, + mockRemoveClosingTag, + ) + + expect(mockTask.consecutiveMistakeCount).toBe(0) + }) +}) diff --git a/src/core/tools/runSlashCommandTool.ts b/src/core/tools/runSlashCommandTool.ts new file mode 100644 index 0000000000..06ceb5f19c --- /dev/null +++ b/src/core/tools/runSlashCommandTool.ts @@ -0,0 +1,108 @@ +import { Task } from "../task/Task" +import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools" +import { formatResponse } from "../prompts/responses" +import { getCommand, getCommandNames } from "../../services/command/commands" +import { EXPERIMENT_IDS, experiments } from "../../shared/experiments" + +export async function runSlashCommandTool( + task: Task, + block: ToolUse, + askApproval: AskApproval, + handleError: HandleError, + pushToolResult: PushToolResult, + removeClosingTag: RemoveClosingTag, +) { + // Check if run slash command experiment is enabled + const provider = task.providerRef.deref() + const state = await provider?.getState() + const isRunSlashCommandEnabled = experiments.isEnabled(state?.experiments ?? {}, EXPERIMENT_IDS.RUN_SLASH_COMMAND) + + if (!isRunSlashCommandEnabled) { + pushToolResult( + formatResponse.toolError( + "Run slash command is an experimental feature that must be enabled in settings. Please enable 'Run Slash Command' in the Experimental Settings section.", + ), + ) + return + } + + const commandName: string | undefined = block.params.command + const args: string | undefined = block.params.args + + try { + if (block.partial) { + const partialMessage = JSON.stringify({ + tool: "runSlashCommand", + command: removeClosingTag("command", commandName), + args: removeClosingTag("args", args), + }) + + await task.ask("tool", partialMessage, block.partial).catch(() => {}) + return + } else { + if (!commandName) { + task.consecutiveMistakeCount++ + task.recordToolError("run_slash_command") + pushToolResult(await task.sayAndCreateMissingParamError("run_slash_command", "command")) + return + } + + task.consecutiveMistakeCount = 0 + + // Get the command from the commands service + const command = await getCommand(task.cwd, commandName) + + if (!command) { + // Get available commands for error message + const availableCommands = await getCommandNames(task.cwd) + task.recordToolError("run_slash_command") + pushToolResult( + formatResponse.toolError( + `Command '${commandName}' not found. Available commands: ${availableCommands.join(", ") || "(none)"}`, + ), + ) + return + } + + const toolMessage = JSON.stringify({ + tool: "runSlashCommand", + command: commandName, + args: args, + source: command.source, + description: command.description, + }) + + const didApprove = await askApproval("tool", toolMessage) + + if (!didApprove) { + return + } + + // Build the result message + let result = `Command: /${commandName}` + + if (command.description) { + result += `\nDescription: ${command.description}` + } + + if (command.argumentHint) { + result += `\nArgument hint: ${command.argumentHint}` + } + + if (args) { + result += `\nProvided arguments: ${args}` + } + + result += `\nSource: ${command.source}` + result += `\n\n--- Command Content ---\n\n${command.content}` + + // Return the command content as the tool result + pushToolResult(result) + + return + } + } catch (error) { + await handleError("running slash command", error) + return + } +} diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index d0173d106d..062a1dd770 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -349,6 +349,7 @@ export interface ClineSayTool { | "insertContent" | "generateImage" | "imageGenerated" + | "runSlashCommand" path?: string diff?: string content?: string @@ -386,6 +387,11 @@ export interface ClineSayTool { }> question?: string imageData?: string // Base64 encoded image data for generated images + // Properties for runSlashCommand tool + command?: string + args?: string + source?: string + description?: string } // Must keep in sync with system prompt. diff --git a/src/shared/__tests__/experiments.spec.ts b/src/shared/__tests__/experiments.spec.ts index d805a19548..8a3c300441 100644 --- a/src/shared/__tests__/experiments.spec.ts +++ b/src/shared/__tests__/experiments.spec.ts @@ -30,6 +30,7 @@ describe("experiments", () => { multiFileApplyDiff: false, preventFocusDisruption: false, imageGeneration: false, + runSlashCommand: false, } expect(Experiments.isEnabled(experiments, EXPERIMENT_IDS.POWER_STEERING)).toBe(false) }) @@ -40,6 +41,7 @@ describe("experiments", () => { multiFileApplyDiff: false, preventFocusDisruption: false, imageGeneration: false, + runSlashCommand: false, } expect(Experiments.isEnabled(experiments, EXPERIMENT_IDS.POWER_STEERING)).toBe(true) }) @@ -50,6 +52,7 @@ describe("experiments", () => { multiFileApplyDiff: false, preventFocusDisruption: false, imageGeneration: false, + runSlashCommand: false, } expect(Experiments.isEnabled(experiments, EXPERIMENT_IDS.POWER_STEERING)).toBe(false) }) diff --git a/src/shared/experiments.ts b/src/shared/experiments.ts index b84d871503..90495c56b7 100644 --- a/src/shared/experiments.ts +++ b/src/shared/experiments.ts @@ -5,6 +5,7 @@ export const EXPERIMENT_IDS = { POWER_STEERING: "powerSteering", PREVENT_FOCUS_DISRUPTION: "preventFocusDisruption", IMAGE_GENERATION: "imageGeneration", + RUN_SLASH_COMMAND: "runSlashCommand", } as const satisfies Record type _AssertExperimentIds = AssertEqual>> @@ -20,6 +21,7 @@ export const experimentConfigsMap: Record = { POWER_STEERING: { enabled: false }, PREVENT_FOCUS_DISRUPTION: { enabled: false }, IMAGE_GENERATION: { enabled: false }, + RUN_SLASH_COMMAND: { enabled: false }, } export const experimentDefault = Object.fromEntries( diff --git a/src/shared/tools.ts b/src/shared/tools.ts index 8a8776764e..608b50752e 100644 --- a/src/shared/tools.ts +++ b/src/shared/tools.ts @@ -160,6 +160,11 @@ export interface NewTaskToolUse extends ToolUse { params: Partial, "mode" | "message" | "todos">> } +export interface RunSlashCommandToolUse extends ToolUse { + name: "run_slash_command" + params: Partial, "command" | "args">> +} + export interface SearchAndReplaceToolUse extends ToolUse { name: "search_and_replace" params: Required, "path" | "search" | "replace">> & @@ -197,6 +202,7 @@ export const TOOL_DISPLAY_NAMES: Record = { search_and_replace: "search and replace", codebase_search: "codebase search", update_todo_list: "update todo list", + run_slash_command: "run slash command", generate_image: "generate images", } as const @@ -237,6 +243,7 @@ export const ALWAYS_AVAILABLE_TOOLS: ToolName[] = [ "switch_mode", "new_task", "update_todo_list", + "run_slash_command", ] as const export type DiffResult = diff --git a/webview-ui/src/components/chat/ChatRow.tsx b/webview-ui/src/components/chat/ChatRow.tsx index 749c19fe59..850eca1cf5 100644 --- a/webview-ui/src/components/chat/ChatRow.tsx +++ b/webview-ui/src/components/chat/ChatRow.tsx @@ -791,6 +791,75 @@ export const ChatRowContent = ({ ) + case "runSlashCommand": { + const slashCommandInfo = tool + return ( + <> +
+ {toolIcon("play")} + + {message.type === "ask" + ? t("chat:slashCommand.wantsToRun") + : t("chat:slashCommand.didRun")} + +
+
+ +
+ + /{slashCommandInfo.command} + + {slashCommandInfo.source && ( + + {slashCommandInfo.source} + + )} +
+ +
+ {isExpanded && (slashCommandInfo.args || slashCommandInfo.description) && ( +
+ {slashCommandInfo.args && ( +
+ Arguments: + + {slashCommandInfo.args} + +
+ )} + {slashCommandInfo.description && ( +
+ {slashCommandInfo.description} +
+ )} +
+ )} +
+ + ) + } case "generateImage": return ( <> @@ -1159,6 +1228,80 @@ export const ChatRowContent = ({ return case "user_edit_todos": return {}} /> + case "tool" as any: + // Handle say tool messages + const sayTool = safeJsonParse(message.text) + if (!sayTool) return null + + switch (sayTool.tool) { + case "runSlashCommand": { + const slashCommandInfo = sayTool + return ( + <> +
+ + {t("chat:slashCommand.didRun")} +
+ + +
+ + /{slashCommandInfo.command} + + {slashCommandInfo.args && ( + + {slashCommandInfo.args} + + )} +
+ {slashCommandInfo.description && ( +
+ {slashCommandInfo.description} +
+ )} + {slashCommandInfo.source && ( +
+ + {slashCommandInfo.source} + +
+ )} +
+
+ + ) + } + default: + return null + } case "image": // Parse the JSON to get imageUri and imagePath const imageInfo = safeJsonParse<{ imageUri: string; imagePath: string }>(message.text || "{}") diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index 85840267fe..fb12a6eeed 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -1028,6 +1028,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction ({ + useTranslation: () => ({ + t: (key: string) => { + const translations: Record = { + "chat:slashCommand.wantsToRun": "Roo wants to run slash command:", + "chat:slashCommand.didRun": "Roo ran slash command:", + } + return translations[key] || key + }, + }), + Trans: ({ i18nKey, children }: { i18nKey: string; children?: React.ReactNode }) => { + return <>{children || i18nKey} + }, + initReactI18next: { + type: "3rdParty", + init: () => {}, + }, +})) + +// Mock VSCodeBadge +vi.mock("@vscode/webview-ui-toolkit/react", () => ({ + VSCodeBadge: ({ children, ...props }: { children: React.ReactNode }) => {children}, +})) + +const queryClient = new QueryClient() + +const renderChatRowWithProviders = (message: any, isExpanded = false) => { + return render( + + + + + , + ) +} + +const mockOnToggleExpand = vi.fn() +const mockOnSuggestionClick = vi.fn() +const mockOnBatchFileResponse = vi.fn() +const mockOnFollowUpUnmount = vi.fn() + +describe("ChatRow - runSlashCommand tool", () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + it("should display runSlashCommand ask message with command only", () => { + const message: any = { + type: "ask", + ask: "tool", + ts: Date.now(), + text: JSON.stringify({ + tool: "runSlashCommand", + command: "init", + }), + partial: false, + } + + const { getByText } = renderChatRowWithProviders(message) + + expect(getByText("Roo wants to run slash command:")).toBeInTheDocument() + expect(getByText("/init")).toBeInTheDocument() + }) + + it("should display runSlashCommand ask message with command and args", () => { + const message: any = { + type: "ask", + ask: "tool", + ts: Date.now(), + text: JSON.stringify({ + tool: "runSlashCommand", + command: "test", + args: "focus on unit tests", + description: "Run project tests", + source: "project", + }), + partial: false, + } + + const { getByText } = renderChatRowWithProviders(message, true) // Pass true to expand + + expect(getByText("Roo wants to run slash command:")).toBeInTheDocument() + expect(getByText("/test")).toBeInTheDocument() + expect(getByText("Arguments:")).toBeInTheDocument() + expect(getByText("focus on unit tests")).toBeInTheDocument() + expect(getByText("Run project tests")).toBeInTheDocument() + expect(getByText("project")).toBeInTheDocument() + }) + + it("should display runSlashCommand say message", () => { + const message: any = { + type: "say", + say: "tool", + ts: Date.now(), + text: JSON.stringify({ + tool: "runSlashCommand", + command: "deploy", + source: "global", + }), + partial: false, + } + + const { getByText } = renderChatRowWithProviders(message) + + expect(getByText("Roo ran slash command:")).toBeInTheDocument() + expect(getByText("/deploy")).toBeInTheDocument() + expect(getByText("global")).toBeInTheDocument() + }) +}) diff --git a/webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx b/webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx index af3c726e2d..c45b997622 100644 --- a/webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx +++ b/webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx @@ -231,6 +231,7 @@ describe("mergeExtensionState", () => { preventFocusDisruption: false, newTaskRequireTodos: false, imageGeneration: false, + runSlashCommand: false, } as Record, } @@ -250,6 +251,7 @@ describe("mergeExtensionState", () => { preventFocusDisruption: false, newTaskRequireTodos: false, imageGeneration: false, + runSlashCommand: false, }) }) }) diff --git a/webview-ui/src/i18n/locales/ca/chat.json b/webview-ui/src/i18n/locales/ca/chat.json index d56a34f7f2..1d20be0c40 100644 --- a/webview-ui/src/i18n/locales/ca/chat.json +++ b/webview-ui/src/i18n/locales/ca/chat.json @@ -391,5 +391,9 @@ "queuedMessages": { "title": "Missatges en cua:", "clickToEdit": "Feu clic per editar el missatge" + }, + "slashCommand": { + "wantsToRun": "Roo vol executar una comanda slash:", + "didRun": "Roo ha executat una comanda slash:" } } diff --git a/webview-ui/src/i18n/locales/ca/settings.json b/webview-ui/src/i18n/locales/ca/settings.json index 1648acf02d..52d1ce9e45 100644 --- a/webview-ui/src/i18n/locales/ca/settings.json +++ b/webview-ui/src/i18n/locales/ca/settings.json @@ -742,6 +742,10 @@ "modelSelectionDescription": "Selecciona el model per a la generació d'imatges", "warningMissingKey": "⚠️ La clau API d'OpenRouter és necessària per a la generació d'imatges. Si us plau, configura-la a dalt.", "successConfigured": "✓ La generació d'imatges està configurada i llesta per utilitzar" + }, + "RUN_SLASH_COMMAND": { + "name": "Habilitar comandes de barra diagonal iniciades pel model", + "description": "Quan està habilitat, Roo pot executar les vostres comandes de barra diagonal per executar fluxos de treball." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/de/chat.json b/webview-ui/src/i18n/locales/de/chat.json index 9f6c98ef2f..82f1c77fbf 100644 --- a/webview-ui/src/i18n/locales/de/chat.json +++ b/webview-ui/src/i18n/locales/de/chat.json @@ -391,5 +391,9 @@ "queuedMessages": { "title": "Warteschlange Nachrichten:", "clickToEdit": "Klicken zum Bearbeiten der Nachricht" + }, + "slashCommand": { + "wantsToRun": "Roo möchte einen Slash-Befehl ausführen:", + "didRun": "Roo hat einen Slash-Befehl ausgeführt:" } } diff --git a/webview-ui/src/i18n/locales/de/settings.json b/webview-ui/src/i18n/locales/de/settings.json index a4796bec48..870e587ac9 100644 --- a/webview-ui/src/i18n/locales/de/settings.json +++ b/webview-ui/src/i18n/locales/de/settings.json @@ -742,6 +742,10 @@ "modelSelectionDescription": "Wähle das Modell für die Bildgenerierung aus", "warningMissingKey": "⚠️ OpenRouter API-Schlüssel ist für Bildgenerierung erforderlich. Bitte konfiguriere ihn oben.", "successConfigured": "✓ Bildgenerierung ist konfiguriert und einsatzbereit" + }, + "RUN_SLASH_COMMAND": { + "name": "Modellinitierte Slash-Befehle aktivieren", + "description": "Wenn aktiviert, kann Roo deine Slash-Befehle ausführen, um Workflows zu starten." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/en/chat.json b/webview-ui/src/i18n/locales/en/chat.json index a7e4b51d02..72eacc5c58 100644 --- a/webview-ui/src/i18n/locales/en/chat.json +++ b/webview-ui/src/i18n/locales/en/chat.json @@ -381,6 +381,10 @@ "confirm": "Delete" } }, + "slashCommand": { + "wantsToRun": "Roo wants to run a slash command:", + "didRun": "Roo ran a slash command:" + }, "queuedMessages": { "title": "Queued Messages:", "clickToEdit": "Click to edit message" diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index 7df37a0270..8479be7793 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -741,6 +741,10 @@ "modelSelectionDescription": "Select the model to use for image generation", "warningMissingKey": "⚠️ OpenRouter API key is required for image generation. Please configure it above.", "successConfigured": "✓ Image generation is configured and ready to use" + }, + "RUN_SLASH_COMMAND": { + "name": "Enable model-initiated slash commands", + "description": "When enabled, Roo can run your slash commands to execute workflows." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/es/chat.json b/webview-ui/src/i18n/locales/es/chat.json index 3cb9631a3a..e63731b095 100644 --- a/webview-ui/src/i18n/locales/es/chat.json +++ b/webview-ui/src/i18n/locales/es/chat.json @@ -391,5 +391,9 @@ "queuedMessages": { "title": "Mensajes en cola:", "clickToEdit": "Haz clic para editar el mensaje" + }, + "slashCommand": { + "wantsToRun": "Roo quiere ejecutar un comando slash:", + "didRun": "Roo ejecutó un comando slash:" } } diff --git a/webview-ui/src/i18n/locales/es/settings.json b/webview-ui/src/i18n/locales/es/settings.json index cb4257978b..cb4c571cb4 100644 --- a/webview-ui/src/i18n/locales/es/settings.json +++ b/webview-ui/src/i18n/locales/es/settings.json @@ -742,6 +742,10 @@ "modelSelectionDescription": "Selecciona el modelo para la generación de imágenes", "warningMissingKey": "⚠️ La clave API de OpenRouter es requerida para la generación de imágenes. Por favor, configúrala arriba.", "successConfigured": "✓ La generación de imágenes está configurada y lista para usar" + }, + "RUN_SLASH_COMMAND": { + "name": "Habilitar comandos slash iniciados por el modelo", + "description": "Cuando está habilitado, Roo puede ejecutar tus comandos slash para ejecutar flujos de trabajo." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/fr/chat.json b/webview-ui/src/i18n/locales/fr/chat.json index 190c1114ad..2575489787 100644 --- a/webview-ui/src/i18n/locales/fr/chat.json +++ b/webview-ui/src/i18n/locales/fr/chat.json @@ -391,5 +391,9 @@ "queuedMessages": { "title": "Messages en file d'attente :", "clickToEdit": "Cliquez pour modifier le message" + }, + "slashCommand": { + "wantsToRun": "Roo veut exécuter une commande slash:", + "didRun": "Roo a exécuté une commande slash:" } } diff --git a/webview-ui/src/i18n/locales/fr/settings.json b/webview-ui/src/i18n/locales/fr/settings.json index 1516ea3085..3b9f7cab5b 100644 --- a/webview-ui/src/i18n/locales/fr/settings.json +++ b/webview-ui/src/i18n/locales/fr/settings.json @@ -742,6 +742,10 @@ "modelSelectionDescription": "Sélectionnez le modèle pour la génération d'images", "warningMissingKey": "⚠️ Une clé API OpenRouter est requise pour la génération d'images. Veuillez la configurer ci-dessus.", "successConfigured": "✓ La génération d'images est configurée et prête à utiliser" + }, + "RUN_SLASH_COMMAND": { + "name": "Activer les commandes slash initiées par le modèle", + "description": "Lorsque activé, Roo peut exécuter tes commandes slash pour lancer des workflows." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/hi/chat.json b/webview-ui/src/i18n/locales/hi/chat.json index 2de959c1ff..28fc26fcaf 100644 --- a/webview-ui/src/i18n/locales/hi/chat.json +++ b/webview-ui/src/i18n/locales/hi/chat.json @@ -391,5 +391,9 @@ "queuedMessages": { "title": "कतार में संदेश:", "clickToEdit": "संदेश संपादित करने के लिए क्लिक करें" + }, + "slashCommand": { + "wantsToRun": "Roo एक स्लैश कमांड चलाना चाहता है:", + "didRun": "Roo ने एक स्लैश कमांड चलाया:" } } diff --git a/webview-ui/src/i18n/locales/hi/settings.json b/webview-ui/src/i18n/locales/hi/settings.json index e27a565995..b2715c5df4 100644 --- a/webview-ui/src/i18n/locales/hi/settings.json +++ b/webview-ui/src/i18n/locales/hi/settings.json @@ -743,6 +743,10 @@ "modelSelectionDescription": "छवि निर्माण के लिए उपयोग करने वाला मॉडल चुनें", "warningMissingKey": "⚠️ छवि निर्माण के लिए OpenRouter API कुंजी आवश्यक है। कृपया इसे ऊपर कॉन्फ़िगर करें।", "successConfigured": "✓ छवि निर्माण कॉन्फ़िगर है और उपयोग के लिए तैयार है" + }, + "RUN_SLASH_COMMAND": { + "name": "मॉडल द्वारा शुरू किए गए स्लैश कमांड सक्षम करें", + "description": "जब सक्षम होता है, Roo वर्कफ़्लो चलाने के लिए आपके स्लैश कमांड चला सकता है।" } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/id/chat.json b/webview-ui/src/i18n/locales/id/chat.json index 1b2f1cf56f..0425a02b8f 100644 --- a/webview-ui/src/i18n/locales/id/chat.json +++ b/webview-ui/src/i18n/locales/id/chat.json @@ -397,5 +397,9 @@ "queuedMessages": { "title": "Pesan Antrian:", "clickToEdit": "Klik untuk mengedit pesan" + }, + "slashCommand": { + "wantsToRun": "Roo ingin menjalankan perintah slash:", + "didRun": "Roo telah menjalankan perintah slash:" } } diff --git a/webview-ui/src/i18n/locales/id/settings.json b/webview-ui/src/i18n/locales/id/settings.json index 1eb03e981a..35b558ce3d 100644 --- a/webview-ui/src/i18n/locales/id/settings.json +++ b/webview-ui/src/i18n/locales/id/settings.json @@ -772,6 +772,10 @@ "modelSelectionDescription": "Pilih model untuk pembuatan gambar", "warningMissingKey": "⚠️ Kunci API OpenRouter diperlukan untuk pembuatan gambar. Silakan konfigurasi di atas.", "successConfigured": "✓ Pembuatan gambar dikonfigurasi dan siap digunakan" + }, + "RUN_SLASH_COMMAND": { + "name": "Aktifkan perintah slash yang dimulai model", + "description": "Ketika diaktifkan, Roo dapat menjalankan perintah slash Anda untuk mengeksekusi alur kerja." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/it/chat.json b/webview-ui/src/i18n/locales/it/chat.json index e210124df0..4dd1270e34 100644 --- a/webview-ui/src/i18n/locales/it/chat.json +++ b/webview-ui/src/i18n/locales/it/chat.json @@ -391,5 +391,9 @@ "queuedMessages": { "title": "Messaggi in coda:", "clickToEdit": "Clicca per modificare il messaggio" + }, + "slashCommand": { + "wantsToRun": "Roo vuole eseguire un comando slash:", + "didRun": "Roo ha eseguito un comando slash:" } } diff --git a/webview-ui/src/i18n/locales/it/settings.json b/webview-ui/src/i18n/locales/it/settings.json index 0070e106d6..e430d9f6a8 100644 --- a/webview-ui/src/i18n/locales/it/settings.json +++ b/webview-ui/src/i18n/locales/it/settings.json @@ -743,6 +743,10 @@ "modelSelectionDescription": "Seleziona il modello per la generazione di immagini", "warningMissingKey": "⚠️ La chiave API OpenRouter è richiesta per la generazione di immagini. Configurala sopra.", "successConfigured": "✓ La generazione di immagini è configurata e pronta per l'uso" + }, + "RUN_SLASH_COMMAND": { + "name": "Abilita comandi slash avviati dal modello", + "description": "Quando abilitato, Roo può eseguire i tuoi comandi slash per eseguire flussi di lavoro." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/ja/chat.json b/webview-ui/src/i18n/locales/ja/chat.json index a5b393ebea..9a5d47fec8 100644 --- a/webview-ui/src/i18n/locales/ja/chat.json +++ b/webview-ui/src/i18n/locales/ja/chat.json @@ -391,5 +391,9 @@ "queuedMessages": { "title": "キューメッセージ:", "clickToEdit": "クリックしてメッセージを編集" + }, + "slashCommand": { + "wantsToRun": "Rooはスラッシュコマンドを実行したい:", + "didRun": "Rooはスラッシュコマンドを実行しました:" } } diff --git a/webview-ui/src/i18n/locales/ja/settings.json b/webview-ui/src/i18n/locales/ja/settings.json index a3bd27ea11..185abf598a 100644 --- a/webview-ui/src/i18n/locales/ja/settings.json +++ b/webview-ui/src/i18n/locales/ja/settings.json @@ -743,6 +743,10 @@ "modelSelectionDescription": "画像生成に使用するモデルを選択", "warningMissingKey": "⚠️ 画像生成にはOpenRouter APIキーが必要です。上記で設定してください。", "successConfigured": "✓ 画像生成が設定され、使用準備完了です" + }, + "RUN_SLASH_COMMAND": { + "name": "モデル開始スラッシュコマンドを有効にする", + "description": "有効にすると、Rooがワークフローを実行するためにあなたのスラッシュコマンドを実行できます。" } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/ko/chat.json b/webview-ui/src/i18n/locales/ko/chat.json index 927cdfe5cf..aaf29243b7 100644 --- a/webview-ui/src/i18n/locales/ko/chat.json +++ b/webview-ui/src/i18n/locales/ko/chat.json @@ -391,5 +391,9 @@ "queuedMessages": { "title": "대기열 메시지:", "clickToEdit": "클릭하여 메시지 편집" + }, + "slashCommand": { + "wantsToRun": "Roo가 슬래시 명령어를 실행하려고 합니다:", + "didRun": "Roo가 슬래시 명령어를 실행했습니다:" } } diff --git a/webview-ui/src/i18n/locales/ko/settings.json b/webview-ui/src/i18n/locales/ko/settings.json index 50fa8b98cb..794896e464 100644 --- a/webview-ui/src/i18n/locales/ko/settings.json +++ b/webview-ui/src/i18n/locales/ko/settings.json @@ -743,6 +743,10 @@ "modelSelectionDescription": "이미지 생성에 사용할 모델을 선택하세요", "warningMissingKey": "⚠️ 이미지 생성에는 OpenRouter API 키가 필요합니다. 위에서 설정해주세요.", "successConfigured": "✓ 이미지 생성이 구성되었으며 사용할 준비가 되었습니다" + }, + "RUN_SLASH_COMMAND": { + "name": "모델 시작 슬래시 명령 활성화", + "description": "활성화되면 Roo가 워크플로를 실행하기 위해 슬래시 명령을 실행할 수 있습니다." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/nl/chat.json b/webview-ui/src/i18n/locales/nl/chat.json index 48f9f345f9..c6d52fa92e 100644 --- a/webview-ui/src/i18n/locales/nl/chat.json +++ b/webview-ui/src/i18n/locales/nl/chat.json @@ -391,5 +391,9 @@ "queuedMessages": { "title": "Berichten in wachtrij:", "clickToEdit": "Klik om bericht te bewerken" + }, + "slashCommand": { + "wantsToRun": "Roo wil een slash commando uitvoeren:", + "didRun": "Roo heeft een slash commando uitgevoerd:" } } diff --git a/webview-ui/src/i18n/locales/nl/settings.json b/webview-ui/src/i18n/locales/nl/settings.json index 553bff848a..f77717b38e 100644 --- a/webview-ui/src/i18n/locales/nl/settings.json +++ b/webview-ui/src/i18n/locales/nl/settings.json @@ -743,6 +743,10 @@ "modelSelectionDescription": "Selecteer het model voor afbeeldingsgeneratie", "warningMissingKey": "⚠️ OpenRouter API-sleutel is vereist voor afbeeldingsgeneratie. Configureer deze hierboven.", "successConfigured": "✓ Afbeeldingsgeneratie is geconfigureerd en klaar voor gebruik" + }, + "RUN_SLASH_COMMAND": { + "name": "Model-geïnitieerde slash-commando's inschakelen", + "description": "Wanneer ingeschakeld, kan Roo je slash-commando's uitvoeren om workflows uit te voeren." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/pl/chat.json b/webview-ui/src/i18n/locales/pl/chat.json index f587406fa5..2028cb705b 100644 --- a/webview-ui/src/i18n/locales/pl/chat.json +++ b/webview-ui/src/i18n/locales/pl/chat.json @@ -391,5 +391,9 @@ "queuedMessages": { "title": "Wiadomości w kolejce:", "clickToEdit": "Kliknij, aby edytować wiadomość" + }, + "slashCommand": { + "wantsToRun": "Roo chce uruchomić komendę slash:", + "didRun": "Roo uruchomił komendę slash:" } } diff --git a/webview-ui/src/i18n/locales/pl/settings.json b/webview-ui/src/i18n/locales/pl/settings.json index 161c0c167e..a09c276fd1 100644 --- a/webview-ui/src/i18n/locales/pl/settings.json +++ b/webview-ui/src/i18n/locales/pl/settings.json @@ -743,6 +743,10 @@ "modelSelectionDescription": "Wybierz model do generowania obrazów", "warningMissingKey": "⚠️ Klucz API OpenRouter jest wymagany do generowania obrazów. Skonfiguruj go powyżej.", "successConfigured": "✓ Generowanie obrazów jest skonfigurowane i gotowe do użycia" + }, + "RUN_SLASH_COMMAND": { + "name": "Włącz polecenia slash inicjowane przez model", + "description": "Gdy włączone, Roo może uruchamiać twoje polecenia slash w celu wykonywania przepływów pracy." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/pt-BR/chat.json b/webview-ui/src/i18n/locales/pt-BR/chat.json index c4ccac7458..6ee23ca627 100644 --- a/webview-ui/src/i18n/locales/pt-BR/chat.json +++ b/webview-ui/src/i18n/locales/pt-BR/chat.json @@ -391,5 +391,9 @@ "queuedMessages": { "title": "Mensagens na fila:", "clickToEdit": "Clique para editar a mensagem" + }, + "slashCommand": { + "wantsToRun": "Roo quer executar um comando slash:", + "didRun": "Roo executou um comando slash:" } } diff --git a/webview-ui/src/i18n/locales/pt-BR/settings.json b/webview-ui/src/i18n/locales/pt-BR/settings.json index f4ed093fd9..e23dfe8e7b 100644 --- a/webview-ui/src/i18n/locales/pt-BR/settings.json +++ b/webview-ui/src/i18n/locales/pt-BR/settings.json @@ -743,6 +743,10 @@ "modelSelectionDescription": "Selecione o modelo para geração de imagens", "warningMissingKey": "⚠️ A chave de API do OpenRouter é necessária para geração de imagens. Configure-a acima.", "successConfigured": "✓ A geração de imagens está configurada e pronta para uso" + }, + "RUN_SLASH_COMMAND": { + "name": "Ativar comandos slash iniciados pelo modelo", + "description": "Quando ativado, Roo pode executar seus comandos slash para executar fluxos de trabalho." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/ru/chat.json b/webview-ui/src/i18n/locales/ru/chat.json index 9fefd4f380..6cafe6bac9 100644 --- a/webview-ui/src/i18n/locales/ru/chat.json +++ b/webview-ui/src/i18n/locales/ru/chat.json @@ -391,5 +391,9 @@ "queuedMessages": { "title": "Сообщения в очереди:", "clickToEdit": "Нажмите, чтобы редактировать сообщение" + }, + "slashCommand": { + "wantsToRun": "Roo хочет выполнить слеш-команду:", + "didRun": "Roo выполнил слеш-команду:" } } diff --git a/webview-ui/src/i18n/locales/ru/settings.json b/webview-ui/src/i18n/locales/ru/settings.json index 48cb988964..900996b569 100644 --- a/webview-ui/src/i18n/locales/ru/settings.json +++ b/webview-ui/src/i18n/locales/ru/settings.json @@ -743,6 +743,10 @@ "modelSelectionDescription": "Выберите модель для генерации изображений", "warningMissingKey": "⚠️ API-ключ OpenRouter необходим для генерации изображений. Настройте его выше.", "successConfigured": "✓ Генерация изображений настроена и готова к использованию" + }, + "RUN_SLASH_COMMAND": { + "name": "Включить слэш-команды, инициированные моделью", + "description": "Когда включено, Roo может выполнять ваши слэш-команды для выполнения рабочих процессов." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/tr/chat.json b/webview-ui/src/i18n/locales/tr/chat.json index 12757edab6..867acfbc9f 100644 --- a/webview-ui/src/i18n/locales/tr/chat.json +++ b/webview-ui/src/i18n/locales/tr/chat.json @@ -391,5 +391,9 @@ "queuedMessages": { "title": "Sıradaki Mesajlar:", "clickToEdit": "Mesajı düzenlemek için tıkla" + }, + "slashCommand": { + "wantsToRun": "Roo bir slash komutu çalıştırmak istiyor:", + "didRun": "Roo bir slash komutu çalıştırdı:" } } diff --git a/webview-ui/src/i18n/locales/tr/settings.json b/webview-ui/src/i18n/locales/tr/settings.json index 6fecf20208..e34f6c08b9 100644 --- a/webview-ui/src/i18n/locales/tr/settings.json +++ b/webview-ui/src/i18n/locales/tr/settings.json @@ -743,6 +743,10 @@ "modelSelectionDescription": "Görüntü üretimi için kullanılacak modeli seçin", "warningMissingKey": "⚠️ Görüntü üretimi için OpenRouter API anahtarı gereklidir. Lütfen yukarıda yapılandırın.", "successConfigured": "✓ Görüntü üretimi yapılandırılmış ve kullanıma hazır" + }, + "RUN_SLASH_COMMAND": { + "name": "Model tarafından başlatılan slash komutlarını etkinleştir", + "description": "Etkinleştirildiğinde, Roo iş akışlarını yürütmek için slash komutlarınızı çalıştırabilir." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/vi/chat.json b/webview-ui/src/i18n/locales/vi/chat.json index 2d0135d9ac..ef8e951aac 100644 --- a/webview-ui/src/i18n/locales/vi/chat.json +++ b/webview-ui/src/i18n/locales/vi/chat.json @@ -391,5 +391,9 @@ "queuedMessages": { "title": "Tin nhắn trong hàng đợi:", "clickToEdit": "Nhấp để chỉnh sửa tin nhắn" + }, + "slashCommand": { + "wantsToRun": "Roo muốn chạy lệnh slash:", + "didRun": "Roo đã chạy lệnh slash:" } } diff --git a/webview-ui/src/i18n/locales/vi/settings.json b/webview-ui/src/i18n/locales/vi/settings.json index 06bd8a1d61..8348156569 100644 --- a/webview-ui/src/i18n/locales/vi/settings.json +++ b/webview-ui/src/i18n/locales/vi/settings.json @@ -743,6 +743,10 @@ "modelSelectionDescription": "Chọn mô hình để sử dụng cho việc tạo hình ảnh", "warningMissingKey": "⚠️ Khóa API OpenRouter là bắt buộc để tạo hình ảnh. Vui lòng cấu hình ở trên.", "successConfigured": "✓ Tạo hình ảnh đã được cấu hình và sẵn sàng sử dụng" + }, + "RUN_SLASH_COMMAND": { + "name": "Bật lệnh slash do mô hình khởi tạo", + "description": "Khi được bật, Roo có thể chạy các lệnh slash của bạn để thực hiện các quy trình làm việc." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/zh-CN/chat.json b/webview-ui/src/i18n/locales/zh-CN/chat.json index a8491a63f6..1e430200a1 100644 --- a/webview-ui/src/i18n/locales/zh-CN/chat.json +++ b/webview-ui/src/i18n/locales/zh-CN/chat.json @@ -391,5 +391,9 @@ "queuedMessages": { "title": "队列消息:", "clickToEdit": "点击编辑消息" + }, + "slashCommand": { + "wantsToRun": "Roo 想要运行斜杠命令:", + "didRun": "Roo 运行了斜杠命令:" } } diff --git a/webview-ui/src/i18n/locales/zh-CN/settings.json b/webview-ui/src/i18n/locales/zh-CN/settings.json index 6db3eda5c6..69e13dbfdf 100644 --- a/webview-ui/src/i18n/locales/zh-CN/settings.json +++ b/webview-ui/src/i18n/locales/zh-CN/settings.json @@ -743,6 +743,10 @@ "modelSelectionDescription": "选择用于图像生成的模型", "warningMissingKey": "⚠️ 图像生成需要 OpenRouter API 密钥。请在上方配置。", "successConfigured": "✓ 图像生成已配置完成,可以使用" + }, + "RUN_SLASH_COMMAND": { + "name": "启用模型发起的斜杠命令", + "description": "启用后 Roo 可运行斜杠命令执行工作流程。" } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/zh-TW/chat.json b/webview-ui/src/i18n/locales/zh-TW/chat.json index c01142cdfa..f5183d65a9 100644 --- a/webview-ui/src/i18n/locales/zh-TW/chat.json +++ b/webview-ui/src/i18n/locales/zh-TW/chat.json @@ -391,5 +391,9 @@ "queuedMessages": { "title": "佇列中的訊息:", "clickToEdit": "點選以編輯訊息" + }, + "slashCommand": { + "wantsToRun": "Roo 想要執行斜線指令:", + "didRun": "Roo 執行了斜線指令:" } } diff --git a/webview-ui/src/i18n/locales/zh-TW/settings.json b/webview-ui/src/i18n/locales/zh-TW/settings.json index 9d622d1862..aa26fea4cf 100644 --- a/webview-ui/src/i18n/locales/zh-TW/settings.json +++ b/webview-ui/src/i18n/locales/zh-TW/settings.json @@ -743,6 +743,10 @@ "modelSelectionDescription": "選擇用於圖像生成的模型", "warningMissingKey": "⚠️ 圖像生成需要 OpenRouter API 金鑰。請在上方設定。", "successConfigured": "✓ 圖像生成已設定完成並準備使用" + }, + "RUN_SLASH_COMMAND": { + "name": "啟用模型啟動的斜線命令", + "description": "啟用時,Roo 可以執行您的斜線命令來執行工作流程。" } }, "promptCaching": {