Browse Source

fix: handle unknown/invalid native tool calls to prevent extension freeze (#9834)

Daniel 3 tuần trước cách đây
mục cha
commit
9f111e174b

+ 264 - 0
src/core/assistant-message/__tests__/presentAssistantMessage-unknown-tool.spec.ts

@@ -0,0 +1,264 @@
+// npx vitest src/core/assistant-message/__tests__/presentAssistantMessage-unknown-tool.spec.ts
+
+import { describe, it, expect, beforeEach, vi } from "vitest"
+import { presentAssistantMessage } from "../presentAssistantMessage"
+
+// Mock dependencies
+vi.mock("../../task/Task")
+vi.mock("../../tools/validateToolUse", () => ({
+	validateToolUse: vi.fn(),
+}))
+vi.mock("@roo-code/telemetry", () => ({
+	TelemetryService: {
+		instance: {
+			captureToolUsage: vi.fn(),
+			captureConsecutiveMistakeError: vi.fn(),
+		},
+	},
+}))
+
+describe("presentAssistantMessage - Unknown Tool Handling", () => {
+	let mockTask: any
+
+	beforeEach(() => {
+		// Create a mock Task with minimal properties needed for testing
+		mockTask = {
+			taskId: "test-task-id",
+			instanceId: "test-instance",
+			abort: false,
+			presentAssistantMessageLocked: false,
+			presentAssistantMessageHasPendingUpdates: false,
+			currentStreamingContentIndex: 0,
+			assistantMessageContent: [],
+			userMessageContent: [],
+			didCompleteReadingStream: false,
+			didRejectTool: false,
+			didAlreadyUseTool: false,
+			diffEnabled: false,
+			consecutiveMistakeCount: 0,
+			clineMessages: [],
+			api: {
+				getModel: () => ({ id: "test-model", info: {} }),
+			},
+			browserSession: {
+				closeBrowser: vi.fn().mockResolvedValue(undefined),
+			},
+			recordToolUsage: vi.fn(),
+			recordToolError: vi.fn(),
+			toolRepetitionDetector: {
+				check: vi.fn().mockReturnValue({ allowExecution: true }),
+			},
+			providerRef: {
+				deref: () => ({
+					getState: vi.fn().mockResolvedValue({
+						mode: "code",
+						customModes: [],
+					}),
+				}),
+			},
+			say: vi.fn().mockResolvedValue(undefined),
+			ask: vi.fn().mockResolvedValue({ response: "yesButtonClicked" }),
+		}
+	})
+
+	it("should return error for unknown tool in native protocol", async () => {
+		// Set up a tool_use block with an unknown tool name and an ID (native protocol)
+		const toolCallId = "tool_call_unknown_123"
+		mockTask.assistantMessageContent = [
+			{
+				type: "tool_use",
+				id: toolCallId, // ID indicates native protocol
+				name: "nonexistent_tool",
+				params: { some: "param" },
+				partial: false,
+			},
+		]
+
+		// Execute presentAssistantMessage
+		await presentAssistantMessage(mockTask)
+
+		// Verify that a tool_result with error was pushed
+		const toolResult = mockTask.userMessageContent.find(
+			(item: any) => item.type === "tool_result" && item.tool_use_id === toolCallId,
+		)
+
+		expect(toolResult).toBeDefined()
+		expect(toolResult.tool_use_id).toBe(toolCallId)
+		// The error is wrapped in JSON by formatResponse.toolError
+		expect(toolResult.content).toContain("nonexistent_tool")
+		expect(toolResult.content).toContain("does not exist")
+		expect(toolResult.content).toContain("error")
+
+		// Verify consecutiveMistakeCount was incremented
+		expect(mockTask.consecutiveMistakeCount).toBe(1)
+
+		// Verify recordToolError was called
+		expect(mockTask.recordToolError).toHaveBeenCalledWith(
+			"nonexistent_tool",
+			expect.stringContaining("Unknown tool"),
+		)
+
+		// Verify error message was shown to user (uses i18n key)
+		expect(mockTask.say).toHaveBeenCalledWith("error", "unknownToolError")
+	})
+
+	it("should return error for unknown tool in XML protocol", async () => {
+		// Set up a tool_use block with an unknown tool name WITHOUT an ID (XML protocol)
+		mockTask.assistantMessageContent = [
+			{
+				type: "tool_use",
+				// No ID = XML protocol
+				name: "fake_tool_that_does_not_exist",
+				params: { param1: "value1" },
+				partial: false,
+			},
+		]
+
+		// Execute presentAssistantMessage
+		await presentAssistantMessage(mockTask)
+
+		// For XML protocol, error is pushed as text blocks
+		const textBlocks = mockTask.userMessageContent.filter((item: any) => item.type === "text")
+
+		// There should be text blocks with error message
+		expect(textBlocks.length).toBeGreaterThan(0)
+		const hasErrorMessage = textBlocks.some(
+			(block: any) =>
+				block.text?.includes("fake_tool_that_does_not_exist") && block.text?.includes("does not exist"),
+		)
+		expect(hasErrorMessage).toBe(true)
+
+		// Verify consecutiveMistakeCount was incremented
+		expect(mockTask.consecutiveMistakeCount).toBe(1)
+
+		// Verify recordToolError was called
+		expect(mockTask.recordToolError).toHaveBeenCalled()
+
+		// Verify error message was shown to user (uses i18n key)
+		expect(mockTask.say).toHaveBeenCalledWith("error", "unknownToolError")
+	})
+
+	it("should handle unknown tool without freezing (native protocol)", async () => {
+		// This test ensures the extension doesn't freeze when an unknown tool is called
+		const toolCallId = "tool_call_freeze_test"
+		mockTask.assistantMessageContent = [
+			{
+				type: "tool_use",
+				id: toolCallId, // Native protocol
+				name: "this_tool_definitely_does_not_exist",
+				params: {},
+				partial: false,
+			},
+		]
+
+		// The test will timeout if the extension freezes
+		const timeoutPromise = new Promise<boolean>((_, reject) => {
+			setTimeout(() => reject(new Error("Test timed out - extension likely froze")), 5000)
+		})
+
+		const resultPromise = presentAssistantMessage(mockTask).then(() => true)
+
+		// Race between the function completing and the timeout
+		const completed = await Promise.race([resultPromise, timeoutPromise])
+		expect(completed).toBe(true)
+
+		// Verify a tool_result was pushed (critical for API not to freeze)
+		const toolResult = mockTask.userMessageContent.find(
+			(item: any) => item.type === "tool_result" && item.tool_use_id === toolCallId,
+		)
+		expect(toolResult).toBeDefined()
+	})
+
+	it("should increment consecutiveMistakeCount for unknown tools", async () => {
+		// Test with multiple unknown tools to ensure mistake count increments
+		const toolCallId = "tool_call_mistake_test"
+		mockTask.assistantMessageContent = [
+			{
+				type: "tool_use",
+				id: toolCallId,
+				name: "unknown_tool_1",
+				params: {},
+				partial: false,
+			},
+		]
+
+		expect(mockTask.consecutiveMistakeCount).toBe(0)
+
+		await presentAssistantMessage(mockTask)
+
+		expect(mockTask.consecutiveMistakeCount).toBe(1)
+	})
+
+	it("should set userMessageContentReady after handling unknown tool", async () => {
+		const toolCallId = "tool_call_ready_test"
+		mockTask.assistantMessageContent = [
+			{
+				type: "tool_use",
+				id: toolCallId,
+				name: "unknown_tool",
+				params: {},
+				partial: false,
+			},
+		]
+
+		mockTask.didCompleteReadingStream = true
+		mockTask.userMessageContentReady = false
+
+		await presentAssistantMessage(mockTask)
+
+		// userMessageContentReady should be set after processing
+		expect(mockTask.userMessageContentReady).toBe(true)
+	})
+
+	it("should still work with didAlreadyUseTool flag for unknown tool", async () => {
+		const toolCallId = "tool_call_already_used_test"
+		mockTask.assistantMessageContent = [
+			{
+				type: "tool_use",
+				id: toolCallId,
+				name: "unknown_tool",
+				params: {},
+				partial: false,
+			},
+		]
+
+		mockTask.didAlreadyUseTool = true
+
+		await presentAssistantMessage(mockTask)
+
+		// When didAlreadyUseTool is true, should send error tool_result
+		const toolResult = mockTask.userMessageContent.find(
+			(item: any) => item.type === "tool_result" && item.tool_use_id === toolCallId,
+		)
+
+		expect(toolResult).toBeDefined()
+		expect(toolResult.is_error).toBe(true)
+		expect(toolResult.content).toContain("was not executed because a tool has already been used")
+	})
+
+	it("should still work with didRejectTool flag for unknown tool", async () => {
+		const toolCallId = "tool_call_rejected_test"
+		mockTask.assistantMessageContent = [
+			{
+				type: "tool_use",
+				id: toolCallId,
+				name: "unknown_tool",
+				params: {},
+				partial: false,
+			},
+		]
+
+		mockTask.didRejectTool = true
+
+		await presentAssistantMessage(mockTask)
+
+		// When didRejectTool is true, should send error tool_result
+		const toolResult = mockTask.userMessageContent.find(
+			(item: any) => item.type === "tool_result" && item.tool_use_id === toolCallId,
+		)
+
+		expect(toolResult).toBeDefined()
+		expect(toolResult.is_error).toBe(true)
+		expect(toolResult.content).toContain("due to user rejecting a previous tool")
+	})
+})

+ 77 - 25
src/core/assistant-message/presentAssistantMessage.ts

@@ -8,6 +8,7 @@ import { TelemetryService } from "@roo-code/telemetry"
 import { defaultModeSlug, getModeBySlug } from "../../shared/modes"
 import { defaultModeSlug, getModeBySlug } from "../../shared/modes"
 import type { ToolParamName, ToolResponse, ToolUse, McpToolUse } from "../../shared/tools"
 import type { ToolParamName, ToolResponse, ToolUse, McpToolUse } from "../../shared/tools"
 import { Package } from "../../shared/package"
 import { Package } from "../../shared/package"
+import { t } from "../../i18n"
 
 
 import { fetchInstructionsTool } from "../tools/FetchInstructionsTool"
 import { fetchInstructionsTool } from "../tools/FetchInstructionsTool"
 import { listFilesTool } from "../tools/ListFilesTool"
 import { listFilesTool } from "../tools/ListFilesTool"
@@ -333,7 +334,11 @@ export async function presentAssistantMessage(cline: Task) {
 			await cline.say("text", content, undefined, block.partial)
 			await cline.say("text", content, undefined, block.partial)
 			break
 			break
 		}
 		}
-		case "tool_use":
+		case "tool_use": {
+			// Fetch state early so it's available for toolDescription and validation
+			const state = await cline.providerRef.deref()?.getState()
+			const { mode, customModes, experiments: stateExperiments, apiConfiguration } = state ?? {}
+
 			const toolDescription = (): string => {
 			const toolDescription = (): string => {
 				switch (block.name) {
 				switch (block.name) {
 					case "execute_command":
 					case "execute_command":
@@ -675,30 +680,46 @@ export async function presentAssistantMessage(cline: Task) {
 				TelemetryService.instance.captureToolUsage(cline.taskId, block.name, toolProtocol)
 				TelemetryService.instance.captureToolUsage(cline.taskId, block.name, toolProtocol)
 			}
 			}
 
 
-			// Validate tool use before execution.
-			const {
-				mode,
-				customModes,
-				experiments: stateExperiments,
-				apiConfiguration,
-			} = (await cline.providerRef.deref()?.getState()) ?? {}
-			const modelInfo = cline.api.getModel()
-			const includedTools = modelInfo?.info?.includedTools
-
-			try {
-				validateToolUse(
-					block.name as ToolName,
-					mode ?? defaultModeSlug,
-					customModes ?? [],
-					{ apply_diff: cline.diffEnabled },
-					block.params,
-					stateExperiments,
-					includedTools,
-				)
-			} catch (error) {
-				cline.consecutiveMistakeCount++
-				pushToolResult(formatResponse.toolError(error.message, toolProtocol))
-				break
+			// Validate tool use before execution - ONLY for complete (non-partial) blocks.
+			// Validating partial blocks would cause validation errors to be thrown repeatedly
+			// during streaming, pushing multiple tool_results for the same tool_use_id and
+			// potentially causing the stream to appear frozen.
+			if (!block.partial) {
+				const modelInfo = cline.api.getModel()
+				const includedTools = modelInfo?.info?.includedTools
+
+				try {
+					validateToolUse(
+						block.name as ToolName,
+						mode ?? defaultModeSlug,
+						customModes ?? [],
+						{ apply_diff: cline.diffEnabled },
+						block.params,
+						stateExperiments,
+						includedTools,
+					)
+				} catch (error) {
+					cline.consecutiveMistakeCount++
+					// For validation errors (unknown tool, tool not allowed for mode), we need to:
+					// 1. Send a tool_result with the error (required for native protocol)
+					// 2. NOT set didAlreadyUseTool = true (the tool was never executed, just failed validation)
+					// This prevents the stream from being interrupted with "Response interrupted by tool use result"
+					// which would cause the extension to appear to hang
+					const errorContent = formatResponse.toolError(error.message, toolProtocol)
+					if (toolProtocol === TOOL_PROTOCOL.NATIVE && toolCallId) {
+						// For native protocol, push tool_result directly without setting didAlreadyUseTool
+						cline.userMessageContent.push({
+							type: "tool_result",
+							tool_use_id: toolCallId,
+							content: typeof errorContent === "string" ? errorContent : "(validation error)",
+							is_error: true,
+						} as Anthropic.ToolResultBlockParam)
+					} else {
+						// For XML protocol, use the standard pushToolResult
+						pushToolResult(errorContent)
+					}
+					break
+				}
 			}
 			}
 
 
 			// Check for identical consecutive tool calls.
 			// Check for identical consecutive tool calls.
@@ -995,9 +1016,40 @@ export async function presentAssistantMessage(cline: Task) {
 						toolProtocol,
 						toolProtocol,
 					})
 					})
 					break
 					break
+				default: {
+					// Handle unknown/invalid tool names
+					// This is critical for native protocol where every tool_use MUST have a tool_result
+					// Note: This case should rarely be reached since validateToolUse now checks for unknown tools
+
+					// CRITICAL: Don't process partial blocks for unknown tools - just let them stream in.
+					// If we try to show errors for partial blocks, we'd show the error on every streaming chunk,
+					// creating a loop that appears to freeze the extension. Only handle complete blocks.
+					if (block.partial) {
+						break
+					}
+
+					const errorMessage = `Unknown tool "${block.name}". This tool does not exist. Please use one of the available tools.`
+					cline.consecutiveMistakeCount++
+					cline.recordToolError(block.name as ToolName, errorMessage)
+					await cline.say("error", t("tools:unknownToolError", { toolName: block.name }))
+					// Push tool_result directly for native protocol WITHOUT setting didAlreadyUseTool
+					// This prevents the stream from being interrupted with "Response interrupted by tool use result"
+					if (toolProtocol === TOOL_PROTOCOL.NATIVE && toolCallId) {
+						cline.userMessageContent.push({
+							type: "tool_result",
+							tool_use_id: toolCallId,
+							content: formatResponse.toolError(errorMessage, toolProtocol),
+							is_error: true,
+						} as Anthropic.ToolResultBlockParam)
+					} else {
+						pushToolResult(formatResponse.toolError(errorMessage, toolProtocol))
+					}
+					break
+				}
 			}
 			}
 
 
 			break
 			break
+		}
 	}
 	}
 
 
 	// Seeing out of bounds is fine, it means that the next too call is being
 	// Seeing out of bounds is fine, it means that the next too call is being

+ 10 - 2
src/core/tools/__tests__/validateToolUse.spec.ts

@@ -167,9 +167,17 @@ describe("mode-validator", () => {
 	})
 	})
 
 
 	describe("validateToolUse", () => {
 	describe("validateToolUse", () => {
-		it("throws error for disallowed tools in architect mode", () => {
+		it("throws error for unknown/invalid tools", () => {
+			// Unknown tools should throw with a specific "Unknown tool" error
 			expect(() => validateToolUse("unknown_tool" as any, "architect", [])).toThrow(
 			expect(() => validateToolUse("unknown_tool" as any, "architect", [])).toThrow(
-				'Tool "unknown_tool" is not allowed in architect mode.',
+				'Unknown tool "unknown_tool". This tool does not exist.',
+			)
+		})
+
+		it("throws error for disallowed tools in architect mode", () => {
+			// execute_command is a valid tool but not allowed in architect mode
+			expect(() => validateToolUse("execute_command", "architect", [])).toThrow(
+				'Tool "execute_command" is not allowed in architect mode.',
 			)
 			)
 		})
 		})
 
 

+ 29 - 0
src/core/tools/validateToolUse.ts

@@ -1,7 +1,27 @@
 import type { ToolName, ModeConfig } from "@roo-code/types"
 import type { ToolName, ModeConfig } from "@roo-code/types"
+import { toolNames as validToolNames } from "@roo-code/types"
 
 
 import { Mode, isToolAllowedForMode } from "../../shared/modes"
 import { Mode, isToolAllowedForMode } from "../../shared/modes"
 
 
+/**
+ * Checks if a tool name is a valid, known tool.
+ * Note: This does NOT check if the tool is allowed for a specific mode,
+ * only that the tool actually exists.
+ */
+export function isValidToolName(toolName: string): toolName is ToolName {
+	// Check if it's a valid static tool
+	if ((validToolNames as readonly string[]).includes(toolName)) {
+		return true
+	}
+
+	// Check if it's a dynamic MCP tool (mcp_serverName_toolName format)
+	if (toolName.startsWith("mcp_")) {
+		return true
+	}
+
+	return false
+}
+
 export function validateToolUse(
 export function validateToolUse(
 	toolName: ToolName,
 	toolName: ToolName,
 	mode: Mode,
 	mode: Mode,
@@ -11,6 +31,15 @@ export function validateToolUse(
 	experiments?: Record<string, boolean>,
 	experiments?: Record<string, boolean>,
 	includedTools?: string[],
 	includedTools?: string[],
 ): void {
 ): void {
+	// First, check if the tool name is actually a valid/known tool
+	// This catches completely invalid tool names like "edit_file" that don't exist
+	if (!isValidToolName(toolName)) {
+		throw new Error(
+			`Unknown tool "${toolName}". This tool does not exist. Please use one of the available tools: ${validToolNames.join(", ")}.`,
+		)
+	}
+
+	// Then check if the tool is allowed for the current mode
 	if (
 	if (
 		!isToolAllowedForMode(
 		!isToolAllowedForMode(
 			toolName,
 			toolName,

+ 1 - 0
src/i18n/locales/ca/tools.json

@@ -7,6 +7,7 @@
 		"imageWithSize": "Fitxer d'imatge ({{size}} KB)"
 		"imageWithSize": "Fitxer d'imatge ({{size}} KB)"
 	},
 	},
 	"toolRepetitionLimitReached": "Roo sembla estar atrapat en un bucle, intentant la mateixa acció ({{toolName}}) repetidament. Això podria indicar un problema amb la seva estratègia actual. Considera reformular la tasca, proporcionar instruccions més específiques o guiar-lo cap a un enfocament diferent.",
 	"toolRepetitionLimitReached": "Roo sembla estar atrapat en un bucle, intentant la mateixa acció ({{toolName}}) repetidament. Això podria indicar un problema amb la seva estratègia actual. Considera reformular la tasca, proporcionar instruccions més específiques o guiar-lo cap a un enfocament diferent.",
+	"unknownToolError": "Roo ha intentat utilitzar una eina desconeguda: \"{{toolName}}\". Reintentant...",
 	"codebaseSearch": {
 	"codebaseSearch": {
 		"approval": "Cercant '{{query}}' a la base de codi..."
 		"approval": "Cercant '{{query}}' a la base de codi..."
 	},
 	},

+ 1 - 0
src/i18n/locales/de/tools.json

@@ -7,6 +7,7 @@
 		"imageWithSize": "Bilddatei ({{size}} KB)"
 		"imageWithSize": "Bilddatei ({{size}} KB)"
 	},
 	},
 	"toolRepetitionLimitReached": "Roo scheint in einer Schleife festzustecken und versucht wiederholt dieselbe Aktion ({{toolName}}). Dies könnte auf ein Problem mit der aktuellen Strategie hindeuten. Überlege dir, die Aufgabe umzuformulieren, genauere Anweisungen zu geben oder Roo zu einem anderen Ansatz zu führen.",
 	"toolRepetitionLimitReached": "Roo scheint in einer Schleife festzustecken und versucht wiederholt dieselbe Aktion ({{toolName}}). Dies könnte auf ein Problem mit der aktuellen Strategie hindeuten. Überlege dir, die Aufgabe umzuformulieren, genauere Anweisungen zu geben oder Roo zu einem anderen Ansatz zu führen.",
+	"unknownToolError": "Roo hat versucht, ein unbekanntes Tool zu verwenden: \"{{toolName}}\". Wiederhole Versuch...",
 	"codebaseSearch": {
 	"codebaseSearch": {
 		"approval": "Suche nach '{{query}}' im Codebase..."
 		"approval": "Suche nach '{{query}}' im Codebase..."
 	},
 	},

+ 1 - 0
src/i18n/locales/en/tools.json

@@ -7,6 +7,7 @@
 		"imageWithSize": "Image file ({{size}} KB)"
 		"imageWithSize": "Image file ({{size}} KB)"
 	},
 	},
 	"toolRepetitionLimitReached": "Roo appears to be stuck in a loop, attempting the same action ({{toolName}}) repeatedly. This might indicate a problem with its current strategy. Consider rephrasing the task, providing more specific instructions, or guiding it towards a different approach.",
 	"toolRepetitionLimitReached": "Roo appears to be stuck in a loop, attempting the same action ({{toolName}}) repeatedly. This might indicate a problem with its current strategy. Consider rephrasing the task, providing more specific instructions, or guiding it towards a different approach.",
+	"unknownToolError": "Roo tried to use an unknown tool: \"{{toolName}}\". Retrying...",
 	"codebaseSearch": {
 	"codebaseSearch": {
 		"approval": "Searching for '{{query}}' in codebase..."
 		"approval": "Searching for '{{query}}' in codebase..."
 	},
 	},

+ 1 - 0
src/i18n/locales/es/tools.json

@@ -7,6 +7,7 @@
 		"imageWithSize": "Archivo de imagen ({{size}} KB)"
 		"imageWithSize": "Archivo de imagen ({{size}} KB)"
 	},
 	},
 	"toolRepetitionLimitReached": "Roo parece estar atrapado en un bucle, intentando la misma acción ({{toolName}}) repetidamente. Esto podría indicar un problema con su estrategia actual. Considera reformular la tarea, proporcionar instrucciones más específicas o guiarlo hacia un enfoque diferente.",
 	"toolRepetitionLimitReached": "Roo parece estar atrapado en un bucle, intentando la misma acción ({{toolName}}) repetidamente. Esto podría indicar un problema con su estrategia actual. Considera reformular la tarea, proporcionar instrucciones más específicas o guiarlo hacia un enfoque diferente.",
+	"unknownToolError": "Roo intentó usar una herramienta desconocida: \"{{toolName}}\". Reintentando...",
 	"codebaseSearch": {
 	"codebaseSearch": {
 		"approval": "Buscando '{{query}}' en la base de código..."
 		"approval": "Buscando '{{query}}' en la base de código..."
 	},
 	},

+ 1 - 0
src/i18n/locales/fr/tools.json

@@ -7,6 +7,7 @@
 		"imageWithSize": "Fichier image ({{size}} Ko)"
 		"imageWithSize": "Fichier image ({{size}} Ko)"
 	},
 	},
 	"toolRepetitionLimitReached": "Roo semble être bloqué dans une boucle, tentant la même action ({{toolName}}) de façon répétée. Cela pourrait indiquer un problème avec sa stratégie actuelle. Envisage de reformuler la tâche, de fournir des instructions plus spécifiques ou de le guider vers une approche différente.",
 	"toolRepetitionLimitReached": "Roo semble être bloqué dans une boucle, tentant la même action ({{toolName}}) de façon répétée. Cela pourrait indiquer un problème avec sa stratégie actuelle. Envisage de reformuler la tâche, de fournir des instructions plus spécifiques ou de le guider vers une approche différente.",
+	"unknownToolError": "Roo a tenté d'utiliser un outil inconnu : \"{{toolName}}\". Nouvelle tentative...",
 	"codebaseSearch": {
 	"codebaseSearch": {
 		"approval": "Recherche de '{{query}}' dans la base de code..."
 		"approval": "Recherche de '{{query}}' dans la base de code..."
 	},
 	},

+ 1 - 0
src/i18n/locales/hi/tools.json

@@ -7,6 +7,7 @@
 		"imageWithSize": "छवि फ़ाइल ({{size}} KB)"
 		"imageWithSize": "छवि फ़ाइल ({{size}} KB)"
 	},
 	},
 	"toolRepetitionLimitReached": "Roo एक लूप में फंसा हुआ लगता है, बार-बार एक ही क्रिया ({{toolName}}) को दोहरा रहा है। यह उसकी वर्तमान रणनीति में किसी समस्या का संकेत हो सकता है। कार्य को पुनः परिभाषित करने, अधिक विशिष्ट निर्देश देने, या उसे एक अलग दृष्टिकोण की ओर मार्गदर्शित करने पर विचार करें।",
 	"toolRepetitionLimitReached": "Roo एक लूप में फंसा हुआ लगता है, बार-बार एक ही क्रिया ({{toolName}}) को दोहरा रहा है। यह उसकी वर्तमान रणनीति में किसी समस्या का संकेत हो सकता है। कार्य को पुनः परिभाषित करने, अधिक विशिष्ट निर्देश देने, या उसे एक अलग दृष्टिकोण की ओर मार्गदर्शित करने पर विचार करें।",
+	"unknownToolError": "Roo ने एक अज्ञात उपकरण का उपयोग करने का प्रयास किया: \"{{toolName}}\"। पुनः प्रयास कर रहा है...",
 	"codebaseSearch": {
 	"codebaseSearch": {
 		"approval": "कोडबेस में '{{query}}' खोज रहा है..."
 		"approval": "कोडबेस में '{{query}}' खोज रहा है..."
 	},
 	},

+ 1 - 0
src/i18n/locales/id/tools.json

@@ -7,6 +7,7 @@
 		"imageWithSize": "File gambar ({{size}} KB)"
 		"imageWithSize": "File gambar ({{size}} KB)"
 	},
 	},
 	"toolRepetitionLimitReached": "Roo tampaknya terjebak dalam loop, mencoba aksi yang sama ({{toolName}}) berulang kali. Ini mungkin menunjukkan masalah dengan strategi saat ini. Pertimbangkan untuk mengubah frasa tugas, memberikan instruksi yang lebih spesifik, atau mengarahkannya ke pendekatan yang berbeda.",
 	"toolRepetitionLimitReached": "Roo tampaknya terjebak dalam loop, mencoba aksi yang sama ({{toolName}}) berulang kali. Ini mungkin menunjukkan masalah dengan strategi saat ini. Pertimbangkan untuk mengubah frasa tugas, memberikan instruksi yang lebih spesifik, atau mengarahkannya ke pendekatan yang berbeda.",
+	"unknownToolError": "Roo mencoba menggunakan alat yang tidak dikenal: \"{{toolName}}\". Mencoba lagi...",
 	"codebaseSearch": {
 	"codebaseSearch": {
 		"approval": "Mencari '{{query}}' di codebase..."
 		"approval": "Mencari '{{query}}' di codebase..."
 	},
 	},

+ 1 - 0
src/i18n/locales/it/tools.json

@@ -7,6 +7,7 @@
 		"imageWithSize": "File immagine ({{size}} KB)"
 		"imageWithSize": "File immagine ({{size}} KB)"
 	},
 	},
 	"toolRepetitionLimitReached": "Roo sembra essere bloccato in un ciclo, tentando ripetutamente la stessa azione ({{toolName}}). Questo potrebbe indicare un problema con la sua strategia attuale. Considera di riformulare l'attività, fornire istruzioni più specifiche o guidarlo verso un approccio diverso.",
 	"toolRepetitionLimitReached": "Roo sembra essere bloccato in un ciclo, tentando ripetutamente la stessa azione ({{toolName}}). Questo potrebbe indicare un problema con la sua strategia attuale. Considera di riformulare l'attività, fornire istruzioni più specifiche o guidarlo verso un approccio diverso.",
+	"unknownToolError": "Roo ha provato ad utilizzare uno strumento sconosciuto: \"{{toolName}}\". Nuovo tentativo...",
 	"codebaseSearch": {
 	"codebaseSearch": {
 		"approval": "Ricerca di '{{query}}' nella base di codice..."
 		"approval": "Ricerca di '{{query}}' nella base di codice..."
 	},
 	},

+ 1 - 0
src/i18n/locales/ja/tools.json

@@ -7,6 +7,7 @@
 		"imageWithSize": "画像ファイル({{size}} KB)"
 		"imageWithSize": "画像ファイル({{size}} KB)"
 	},
 	},
 	"toolRepetitionLimitReached": "Rooが同じ操作({{toolName}})を繰り返し試みるループに陥っているようです。これは現在の方法に問題がある可能性を示しています。タスクの言い換え、より具体的な指示の提供、または別のアプローチへの誘導を検討してください。",
 	"toolRepetitionLimitReached": "Rooが同じ操作({{toolName}})を繰り返し試みるループに陥っているようです。これは現在の方法に問題がある可能性を示しています。タスクの言い換え、より具体的な指示の提供、または別のアプローチへの誘導を検討してください。",
+	"unknownToolError": "Rooが不明なツールを使用しようとしました:「{{toolName}}」。再試行中...",
 	"codebaseSearch": {
 	"codebaseSearch": {
 		"approval": "コードベースで '{{query}}' を検索中..."
 		"approval": "コードベースで '{{query}}' を検索中..."
 	},
 	},

+ 1 - 0
src/i18n/locales/ko/tools.json

@@ -7,6 +7,7 @@
 		"imageWithSize": "이미지 파일 ({{size}} KB)"
 		"imageWithSize": "이미지 파일 ({{size}} KB)"
 	},
 	},
 	"toolRepetitionLimitReached": "Roo가 같은 동작({{toolName}})을 반복적으로 시도하면서 루프에 갇힌 것 같습니다. 이는 현재 전략에 문제가 있을 수 있음을 나타냅니다. 작업을 다시 표현하거나, 더 구체적인 지침을 제공하거나, 다른 접근 방식으로 안내해 보세요.",
 	"toolRepetitionLimitReached": "Roo가 같은 동작({{toolName}})을 반복적으로 시도하면서 루프에 갇힌 것 같습니다. 이는 현재 전략에 문제가 있을 수 있음을 나타냅니다. 작업을 다시 표현하거나, 더 구체적인 지침을 제공하거나, 다른 접근 방식으로 안내해 보세요.",
+	"unknownToolError": "Roo가 알 수 없는 도구를 사용하려고 했습니다: \"{{toolName}}\". 다시 시도 중...",
 	"codebaseSearch": {
 	"codebaseSearch": {
 		"approval": "코드베이스에서 '{{query}}' 검색 중..."
 		"approval": "코드베이스에서 '{{query}}' 검색 중..."
 	},
 	},

+ 1 - 0
src/i18n/locales/nl/tools.json

@@ -7,6 +7,7 @@
 		"imageWithSize": "Afbeeldingsbestand ({{size}} KB)"
 		"imageWithSize": "Afbeeldingsbestand ({{size}} KB)"
 	},
 	},
 	"toolRepetitionLimitReached": "Roo lijkt vast te zitten in een lus, waarbij hij herhaaldelijk dezelfde actie ({{toolName}}) probeert. Dit kan duiden op een probleem met de huidige strategie. Overweeg de taak te herformuleren, specifiekere instructies te geven of Roo naar een andere aanpak te leiden.",
 	"toolRepetitionLimitReached": "Roo lijkt vast te zitten in een lus, waarbij hij herhaaldelijk dezelfde actie ({{toolName}}) probeert. Dit kan duiden op een probleem met de huidige strategie. Overweeg de taak te herformuleren, specifiekere instructies te geven of Roo naar een andere aanpak te leiden.",
+	"unknownToolError": "Roo probeerde een onbekende tool te gebruiken: \"{{toolName}}\". Opnieuw proberen...",
 	"codebaseSearch": {
 	"codebaseSearch": {
 		"approval": "Zoeken naar '{{query}}' in codebase..."
 		"approval": "Zoeken naar '{{query}}' in codebase..."
 	},
 	},

+ 1 - 0
src/i18n/locales/pl/tools.json

@@ -7,6 +7,7 @@
 		"imageWithSize": "Plik obrazu ({{size}} KB)"
 		"imageWithSize": "Plik obrazu ({{size}} KB)"
 	},
 	},
 	"toolRepetitionLimitReached": "Wygląda na to, że Roo utknął w pętli, wielokrotnie próbując wykonać tę samą akcję ({{toolName}}). Może to wskazywać na problem z jego obecną strategią. Rozważ przeformułowanie zadania, podanie bardziej szczegółowych instrukcji lub nakierowanie go na inne podejście.",
 	"toolRepetitionLimitReached": "Wygląda na to, że Roo utknął w pętli, wielokrotnie próbując wykonać tę samą akcję ({{toolName}}). Może to wskazywać na problem z jego obecną strategią. Rozważ przeformułowanie zadania, podanie bardziej szczegółowych instrukcji lub nakierowanie go na inne podejście.",
+	"unknownToolError": "Roo próbował użyć nieznanego narzędzia: \"{{toolName}}\". Ponowna próba...",
 	"codebaseSearch": {
 	"codebaseSearch": {
 		"approval": "Wyszukiwanie '{{query}}' w bazie kodu..."
 		"approval": "Wyszukiwanie '{{query}}' w bazie kodu..."
 	},
 	},

+ 1 - 0
src/i18n/locales/pt-BR/tools.json

@@ -7,6 +7,7 @@
 		"imageWithSize": "Arquivo de imagem ({{size}} KB)"
 		"imageWithSize": "Arquivo de imagem ({{size}} KB)"
 	},
 	},
 	"toolRepetitionLimitReached": "Roo parece estar preso em um loop, tentando a mesma ação ({{toolName}}) repetidamente. Isso pode indicar um problema com sua estratégia atual. Considere reformular a tarefa, fornecer instruções mais específicas ou guiá-lo para uma abordagem diferente.",
 	"toolRepetitionLimitReached": "Roo parece estar preso em um loop, tentando a mesma ação ({{toolName}}) repetidamente. Isso pode indicar um problema com sua estratégia atual. Considere reformular a tarefa, fornecer instruções mais específicas ou guiá-lo para uma abordagem diferente.",
+	"unknownToolError": "Roo tentou usar uma ferramenta desconhecida: \"{{toolName}}\". Tentando novamente...",
 	"codebaseSearch": {
 	"codebaseSearch": {
 		"approval": "Pesquisando '{{query}}' na base de código..."
 		"approval": "Pesquisando '{{query}}' na base de código..."
 	},
 	},

+ 1 - 0
src/i18n/locales/ru/tools.json

@@ -7,6 +7,7 @@
 		"imageWithSize": "Файл изображения ({{size}} КБ)"
 		"imageWithSize": "Файл изображения ({{size}} КБ)"
 	},
 	},
 	"toolRepetitionLimitReached": "Похоже, что Roo застрял в цикле, многократно пытаясь выполнить одно и то же действие ({{toolName}}). Это может указывать на проблему с его текущей стратегией. Попробуйте переформулировать задачу, предоставить более конкретные инструкции или направить его к другому подходу.",
 	"toolRepetitionLimitReached": "Похоже, что Roo застрял в цикле, многократно пытаясь выполнить одно и то же действие ({{toolName}}). Это может указывать на проблему с его текущей стратегией. Попробуйте переформулировать задачу, предоставить более конкретные инструкции или направить его к другому подходу.",
+	"unknownToolError": "Roo попытался использовать неизвестный инструмент: \"{{toolName}}\". Повторная попытка...",
 	"codebaseSearch": {
 	"codebaseSearch": {
 		"approval": "Поиск '{{query}}' в кодовой базе..."
 		"approval": "Поиск '{{query}}' в кодовой базе..."
 	},
 	},

+ 1 - 0
src/i18n/locales/tr/tools.json

@@ -7,6 +7,7 @@
 		"imageWithSize": "Görüntü dosyası ({{size}} KB)"
 		"imageWithSize": "Görüntü dosyası ({{size}} KB)"
 	},
 	},
 	"toolRepetitionLimitReached": "Roo bir döngüye takılmış gibi görünüyor, aynı eylemi ({{toolName}}) tekrar tekrar deniyor. Bu, mevcut stratejisinde bir sorun olduğunu gösterebilir. Görevi yeniden ifade etmeyi, daha spesifik talimatlar vermeyi veya onu farklı bir yaklaşıma yönlendirmeyi düşünün.",
 	"toolRepetitionLimitReached": "Roo bir döngüye takılmış gibi görünüyor, aynı eylemi ({{toolName}}) tekrar tekrar deniyor. Bu, mevcut stratejisinde bir sorun olduğunu gösterebilir. Görevi yeniden ifade etmeyi, daha spesifik talimatlar vermeyi veya onu farklı bir yaklaşıma yönlendirmeyi düşünün.",
+	"unknownToolError": "Roo bilinmeyen bir araç kullanmaya çalıştı: \"{{toolName}}\". Yeniden deneniyor...",
 	"codebaseSearch": {
 	"codebaseSearch": {
 		"approval": "Kod tabanında '{{query}}' aranıyor..."
 		"approval": "Kod tabanında '{{query}}' aranıyor..."
 	},
 	},

+ 1 - 0
src/i18n/locales/vi/tools.json

@@ -7,6 +7,7 @@
 		"imageWithSize": "Tệp hình ảnh ({{size}} KB)"
 		"imageWithSize": "Tệp hình ảnh ({{size}} KB)"
 	},
 	},
 	"toolRepetitionLimitReached": "Roo dường như đang bị mắc kẹt trong một vòng lặp, liên tục cố gắng thực hiện cùng một hành động ({{toolName}}). Điều này có thể cho thấy vấn đề với chiến lược hiện tại. Hãy cân nhắc việc diễn đạt lại nhiệm vụ, cung cấp hướng dẫn cụ thể hơn, hoặc hướng Roo theo một cách tiếp cận khác.",
 	"toolRepetitionLimitReached": "Roo dường như đang bị mắc kẹt trong một vòng lặp, liên tục cố gắng thực hiện cùng một hành động ({{toolName}}). Điều này có thể cho thấy vấn đề với chiến lược hiện tại. Hãy cân nhắc việc diễn đạt lại nhiệm vụ, cung cấp hướng dẫn cụ thể hơn, hoặc hướng Roo theo một cách tiếp cận khác.",
+	"unknownToolError": "Roo đã cố gắng sử dụng một công cụ không xác định: \"{{toolName}}\". Đang thử lại...",
 	"codebaseSearch": {
 	"codebaseSearch": {
 		"approval": "Đang tìm kiếm '{{query}}' trong cơ sở mã..."
 		"approval": "Đang tìm kiếm '{{query}}' trong cơ sở mã..."
 	},
 	},

+ 1 - 0
src/i18n/locales/zh-CN/tools.json

@@ -7,6 +7,7 @@
 		"imageWithSize": "图片文件 ({{size}} KB)"
 		"imageWithSize": "图片文件 ({{size}} KB)"
 	},
 	},
 	"toolRepetitionLimitReached": "Roo 似乎陷入循环,反复尝试同一操作 ({{toolName}})。这可能表明当前策略存在问题。请考虑重新描述任务、提供更具体的指示或引导其尝试不同的方法。",
 	"toolRepetitionLimitReached": "Roo 似乎陷入循环,反复尝试同一操作 ({{toolName}})。这可能表明当前策略存在问题。请考虑重新描述任务、提供更具体的指示或引导其尝试不同的方法。",
+	"unknownToolError": "Roo 尝试使用未知工具:\"{{toolName}}\"。正在重试...",
 	"codebaseSearch": {
 	"codebaseSearch": {
 		"approval": "正在搜索代码库中的 '{{query}}'..."
 		"approval": "正在搜索代码库中的 '{{query}}'..."
 	},
 	},

+ 1 - 0
src/i18n/locales/zh-TW/tools.json

@@ -7,6 +7,7 @@
 		"imageWithSize": "圖片檔案 ({{size}} KB)"
 		"imageWithSize": "圖片檔案 ({{size}} KB)"
 	},
 	},
 	"toolRepetitionLimitReached": "Roo 似乎陷入循環,反覆嘗試同一操作 ({{toolName}})。這可能表明目前策略存在問題。請考慮重新描述工作、提供更具體的指示或引導其嘗試不同的方法。",
 	"toolRepetitionLimitReached": "Roo 似乎陷入循環,反覆嘗試同一操作 ({{toolName}})。這可能表明目前策略存在問題。請考慮重新描述工作、提供更具體的指示或引導其嘗試不同的方法。",
+	"unknownToolError": "Roo 嘗試使用未知工具:「{{toolName}}」。正在重試...",
 	"codebaseSearch": {
 	"codebaseSearch": {
 		"approval": "正在搜尋程式碼庫中的「{{query}}」..."
 		"approval": "正在搜尋程式碼庫中的「{{query}}」..."
 	},
 	},