diff --git a/src/core/prompts/system.ts b/src/core/prompts/system.ts index c8ebf91138..bfc12930a0 100644 --- a/src/core/prompts/system.ts +++ b/src/core/prompts/system.ts @@ -98,7 +98,6 @@ ${getToolDescriptionsForMode( experiments, partialReadsEnabled, settings, - settings?.disabledTools, )} ${getToolUseGuidelinesSection(codeIndexManager)} diff --git a/src/core/prompts/tools/__tests__/index.spec.ts b/src/core/prompts/tools/__tests__/index.spec.ts index 3b303d80ca..783d3d9f77 100644 --- a/src/core/prompts/tools/__tests__/index.spec.ts +++ b/src/core/prompts/tools/__tests__/index.spec.ts @@ -3,6 +3,8 @@ import { describe, it, expect } from "vitest" import { getToolDescriptionsForMode } from "../index" import { defaultModeSlug } from "../../../../shared/modes" +import { toolNames } from "@roo-code/types" +import { TOOL_GROUPS, ALWAYS_AVAILABLE_TOOLS } from "../../../../shared/tools" describe("getToolDescriptionsForMode", () => { const mockCwd = "/test/path" @@ -21,7 +23,6 @@ describe("getToolDescriptionsForMode", () => { undefined, // experiments undefined, // partialReadsEnabled undefined, // settings - undefined, // disabledTools ) expect(result).toBeTruthy() @@ -47,8 +48,7 @@ describe("getToolDescriptionsForMode", () => { undefined, undefined, undefined, - undefined, - disabledTools, + { disabledTools }, // settings object with disabledTools ) // Check that disabled tools are not included @@ -60,7 +60,7 @@ describe("getToolDescriptionsForMode", () => { expect(result).toContain("## search_files") }) - it("should not filter out always-available tools even if disabled", () => { + it("should filter out all tools including always-available tools when disabled", () => { const disabledTools = ["ask_followup_question", "attempt_completion"] const result = getToolDescriptionsForMode( defaultModeSlug, @@ -73,13 +73,12 @@ describe("getToolDescriptionsForMode", () => { undefined, undefined, undefined, - undefined, - disabledTools, + { disabledTools }, // settings object with disabledTools ) - // These tools should always be available - expect(result).toContain("## ask_followup_question") - expect(result).toContain("## attempt_completion") + // These tools should be filtered out since we now allow disabling all tools + expect(result).not.toContain("## ask_followup_question") + expect(result).not.toContain("## attempt_completion") }) it("should handle empty disabled tools array", () => { @@ -95,8 +94,7 @@ describe("getToolDescriptionsForMode", () => { undefined, undefined, undefined, - undefined, - disabledTools, + { disabledTools }, // settings object with empty disabledTools ) const resultWithoutDisabled = getToolDescriptionsForMode( defaultModeSlug, @@ -109,8 +107,7 @@ describe("getToolDescriptionsForMode", () => { undefined, undefined, undefined, - undefined, - undefined, + undefined, // no settings ) // Should return the same tools @@ -129,8 +126,7 @@ describe("getToolDescriptionsForMode", () => { undefined, undefined, undefined, - undefined, - undefined, + { disabledTools: undefined }, // settings with undefined disabledTools ) const resultWithoutDisabled = getToolDescriptionsForMode( defaultModeSlug, @@ -143,8 +139,7 @@ describe("getToolDescriptionsForMode", () => { undefined, undefined, undefined, - undefined, - undefined, + undefined, // no settings ) // Should return the same tools @@ -164,8 +159,7 @@ describe("getToolDescriptionsForMode", () => { undefined, undefined, undefined, - undefined, - disabledTools, + { disabledTools }, // settings object with disabledTools ) // Check that all disabled tools are filtered out @@ -175,8 +169,8 @@ describe("getToolDescriptionsForMode", () => { // Check that some other tools are still included expect(result).toContain("# Tools") - // Always available tools should still be there - expect(result).toContain("## ask_followup_question") + // Other tools that weren't disabled should still be there + expect(result).toContain("## list_code_definition_names") }) it("should handle invalid tool names in disabled list", () => { @@ -192,8 +186,7 @@ describe("getToolDescriptionsForMode", () => { undefined, undefined, undefined, - undefined, - disabledTools, + { disabledTools }, // settings object with disabledTools ) // Should still filter out valid disabled tools @@ -217,8 +210,7 @@ describe("getToolDescriptionsForMode", () => { undefined, undefined, undefined, - undefined, - disabledTools, + { disabledTools }, // settings object with disabledTools ) // execute_command should be filtered out @@ -228,4 +220,67 @@ describe("getToolDescriptionsForMode", () => { expect(result).toContain("## read_file") expect(result).toContain("## write_to_file") }) + + it("should have all tools from packages/types/src/tool.ts represented in TOOL_GROUPS or ALWAYS_AVAILABLE_TOOLS", () => { + // Get all tools from TOOL_GROUPS + const toolsInGroups = new Set() + Object.values(TOOL_GROUPS).forEach((group) => { + group.tools.forEach((tool) => toolsInGroups.add(tool)) + }) + + // Get all tools from ALWAYS_AVAILABLE_TOOLS + const alwaysAvailableSet = new Set(ALWAYS_AVAILABLE_TOOLS) + + // Combine both sets + const allRepresentedTools = new Set([...toolsInGroups, ...alwaysAvailableSet]) + + // Check that every tool from toolNames is represented + const missingTools: string[] = [] + toolNames.forEach((toolName) => { + if (!allRepresentedTools.has(toolName)) { + missingTools.push(toolName) + } + }) + + // Assert that there are no missing tools + expect(missingTools).toEqual([]) + }) + + it("should not have tools in TOOL_GROUPS that are not in packages/types/src/tool.ts", () => { + // Get all tools from TOOL_GROUPS + const toolsInGroups = new Set() + Object.values(TOOL_GROUPS).forEach((group) => { + group.tools.forEach((tool) => toolsInGroups.add(tool)) + }) + + // Convert toolNames to a Set for easier lookup + const validToolNames = new Set(toolNames) + + // Check that every tool in TOOL_GROUPS exists in toolNames + const invalidTools: string[] = [] + toolsInGroups.forEach((tool) => { + if (!validToolNames.has(tool as any)) { + invalidTools.push(tool) + } + }) + + // Assert that there are no invalid tools + expect(invalidTools).toEqual([]) + }) + + it("should not have tools in ALWAYS_AVAILABLE_TOOLS that are not in packages/types/src/tool.ts", () => { + // Convert toolNames to a Set for easier lookup + const validToolNames = new Set(toolNames) + + // Check that every tool in ALWAYS_AVAILABLE_TOOLS exists in toolNames + const invalidTools: string[] = [] + ALWAYS_AVAILABLE_TOOLS.forEach((tool) => { + if (!validToolNames.has(tool)) { + invalidTools.push(tool) + } + }) + + // Assert that there are no invalid tools + expect(invalidTools).toEqual([]) + }) }) diff --git a/src/core/prompts/tools/index.ts b/src/core/prompts/tools/index.ts index b1fc4018e0..60458aad87 100644 --- a/src/core/prompts/tools/index.ts +++ b/src/core/prompts/tools/index.ts @@ -61,7 +61,6 @@ export function getToolDescriptionsForMode( experiments?: Record, partialReadsEnabled?: boolean, settings?: Record, - disabledTools?: string[], ): string { const config = getModeConfig(mode, customModes) const args: ToolArgs = { @@ -110,30 +109,31 @@ export function getToolDescriptionsForMode( tools.delete("codebase_search") } - // Filter out disabled tools (except always-available tools) - if (disabledTools && disabledTools.length > 0) { - disabledTools.forEach((tool) => { - // Don't filter out always-available tools - if (!ALWAYS_AVAILABLE_TOOLS.includes(tool as any)) { - tools.delete(tool) + // Map tool descriptions for allowed tools + const toolsArray = Array.from(tools) + const descriptions = toolsArray + .map((toolName) => { + const descriptionFn = toolDescriptionMap[toolName] + if (!descriptionFn) { + return undefined + } + + return { + name: toolName, + description: descriptionFn({ + ...args, + toolOptions: undefined, // No tool options in group-based approach + }), } }) - } + .filter((item) => item && item.description) - // Map tool descriptions for allowed tools - const descriptions = Array.from(tools).map((toolName) => { - const descriptionFn = toolDescriptionMap[toolName] - if (!descriptionFn) { - return undefined - } + // Filter out disabled tools at the very end using the simplest possible implementation + const enabledDescriptions = descriptions + .filter((item) => !settings?.disabledTools?.includes(item!.name)) + .map((item) => item!.description) - return descriptionFn({ - ...args, - toolOptions: undefined, // No tool options in group-based approach - }) - }) - - return `# Tools\n\n${descriptions.filter(Boolean).join("\n\n")}` + return `# Tools\n\n${enabledDescriptions.join("\n\n")}` } // Export individual description functions for backward compatibility diff --git a/src/core/webview/generateSystemPrompt.ts b/src/core/webview/generateSystemPrompt.ts index 248b28bc45..a14275e1b6 100644 --- a/src/core/webview/generateSystemPrompt.ts +++ b/src/core/webview/generateSystemPrompt.ts @@ -24,7 +24,6 @@ export const generateSystemPrompt = async (provider: ClineProvider, message: Web language, maxReadFileLine, maxConcurrentFileReads, - disabledTools, } = await provider.getState() // Check experiment to determine which diff strategy to use @@ -83,7 +82,7 @@ export const generateSystemPrompt = async (provider: ClineProvider, message: Web maxReadFileLine !== -1, { maxConcurrentFileReads, - disabledTools, + disabledTools: (await provider.getState()).disabledTools, }, ) diff --git a/webview-ui/src/components/settings/ToolSettings.tsx b/webview-ui/src/components/settings/ToolSettings.tsx index cf5be5aaa1..68c4827793 100644 --- a/webview-ui/src/components/settings/ToolSettings.tsx +++ b/webview-ui/src/components/settings/ToolSettings.tsx @@ -3,6 +3,7 @@ import { Wrench } from "lucide-react" import { HTMLAttributes, useMemo } from "react" import { useAppTranslation } from "@/i18n/TranslationContext" +import { toolNames } from "@roo-code/types" import { Section } from "./Section" import { SectionHeader } from "./SectionHeader" @@ -13,65 +14,6 @@ type ToolSettingsProps = HTMLAttributes & { setCachedStateField: SetCachedStateField<"disabledTools"> } -// Import the constants from shared/tools.ts -// Tool display names mapping -const TOOL_DISPLAY_NAMES: Record = { - execute_command: "Run commands", - read_file: "Read files", - fetch_instructions: "Fetch instructions", - write_to_file: "Write files", - apply_diff: "Apply changes", - search_files: "Search files", - list_files: "List files", - list_code_definition_names: "List definitions", - browser_action: "Use a browser", - use_mcp_tool: "Use MCP tools", - access_mcp_resource: "Access MCP resources", - ask_followup_question: "Ask questions", - attempt_completion: "Complete tasks", - switch_mode: "Switch modes", - new_task: "Create new task", - insert_content: "Insert content", - search_and_replace: "Search and replace", - codebase_search: "Codebase search", - update_todo_list: "Update todo list", -} - -// Tools that are always available and cannot be disabled -const ALWAYS_AVAILABLE_TOOLS = [ - "ask_followup_question", - "attempt_completion", - "switch_mode", - "new_task", - "update_todo_list", -] - -// Tool groups configuration -const TOOL_GROUPS: Record = { - read: { - tools: [ - "read_file", - "fetch_instructions", - "search_files", - "list_files", - "list_code_definition_names", - "codebase_search", - ], - }, - edit: { - tools: ["apply_diff", "write_to_file", "insert_content", "search_and_replace"], - }, - browser: { - tools: ["browser_action"], - }, - command: { - tools: ["execute_command"], - }, - mcp: { - tools: ["use_mcp_tool", "access_mcp_resource"], - }, -} - export const ToolSettings = ({ disabledTools = [], setCachedStateField, ...props }: ToolSettingsProps) => { const { t } = useAppTranslation() @@ -90,34 +32,15 @@ export const ToolSettings = ({ disabledTools = [], setCachedStateField, ...props const isToolEnabled = (toolName: string) => !disabledTools.includes(toolName) - // Get all available tools dynamically from the global tools configuration + // Get all available tools dynamically from packages/types/src/tool.ts const allTools = useMemo(() => { - const tools = new Set() - - // Add all tools from tool groups - Object.values(TOOL_GROUPS).forEach((group) => { - group.tools.forEach((tool) => tools.add(tool)) - }) - - // Add always available tools - ALWAYS_AVAILABLE_TOOLS.forEach((tool) => tools.add(tool)) - - // Convert to array and sort alphabetically - return Array.from(tools).sort((a, b) => { - const nameA = TOOL_DISPLAY_NAMES[a as keyof typeof TOOL_DISPLAY_NAMES] || a - const nameB = TOOL_DISPLAY_NAMES[b as keyof typeof TOOL_DISPLAY_NAMES] || b + // Use the toolNames array from @roo-code/types and sort alphabetically by translated names + return [...toolNames].sort((a, b) => { + const nameA = t(`settings:tools.names.${a}`) || a + const nameB = t(`settings:tools.names.${b}`) || b return nameA.localeCompare(nameB) }) - }, []) - - // Separate tools into disableable and always-available - const disableableTools = useMemo(() => { - return allTools.filter((tool) => !ALWAYS_AVAILABLE_TOOLS.includes(tool as any)) - }, [allTools]) - - const alwaysAvailableTools = useMemo(() => { - return allTools.filter((tool) => ALWAYS_AVAILABLE_TOOLS.includes(tool as any)) - }, [allTools]) + }, [t]) return (
@@ -132,38 +55,16 @@ export const ToolSettings = ({ disabledTools = [], setCachedStateField, ...props
{t("settings:tools.description")}
- {/* Disableable tools */} - {disableableTools.map((tool) => ( + {/* All tools can be toggled */} + {allTools.map((tool) => ( handleToolToggle(tool, e.target.checked)}> - - {TOOL_DISPLAY_NAMES[tool as keyof typeof TOOL_DISPLAY_NAMES] || tool} - - - ))} - - {/* Separator */} - {alwaysAvailableTools.length > 0 && ( -
-
- {t("settings:tools.alwaysAvailable")} -
-
- )} - - {/* Always available tools (disabled checkboxes) */} - {alwaysAvailableTools.map((tool) => ( - - - {TOOL_DISPLAY_NAMES[tool as keyof typeof TOOL_DISPLAY_NAMES] || tool} - + {t(`settings:tools.names.${tool}`) || tool} ))}
- -
{t("settings:tools.note")}
) diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index cada31a2f7..8d893f30a2 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -723,6 +723,27 @@ "tools": { "description": "Disable specific tools to reduce token usage. Disabled tools won't be included in the system prompt.", "note": "Note: Some tools are always available and cannot be disabled (ask questions, complete tasks, switch modes, create new task, update todo list).", - "alwaysAvailable": "Always available tools" + "alwaysAvailable": "Always available tools", + "names": { + "execute_command": "Run commands", + "read_file": "Read files", + "fetch_instructions": "Fetch instructions", + "write_to_file": "Write files", + "apply_diff": "Apply changes", + "search_files": "Search files", + "list_files": "List files", + "list_code_definition_names": "List definitions", + "browser_action": "Use a browser", + "use_mcp_tool": "Use MCP tools", + "access_mcp_resource": "Access MCP resources", + "ask_followup_question": "Ask questions", + "attempt_completion": "Complete tasks", + "switch_mode": "Switch modes", + "new_task": "Create new task", + "insert_content": "Insert content", + "search_and_replace": "Search and replace", + "codebase_search": "Codebase search", + "update_todo_list": "Update todo list" + } } }