Matt Rubens 7 месяцев назад
Родитель
Сommit
cd423d3a5e

+ 45 - 0
src/core/Cline.ts

@@ -40,6 +40,7 @@ import { ToolParamName, ToolResponse, DiffStrategy } from "../shared/tools"
 import { UrlContentFetcher } from "../services/browser/UrlContentFetcher"
 import { BrowserSession } from "../services/browser/BrowserSession"
 import { McpHub } from "../services/mcp/McpHub"
+import { ToolRepetitionDetector } from "./ToolRepetitionDetector"
 import { McpServerManager } from "../services/mcp/McpServerManager"
 import { telemetryService } from "../services/telemetry/TelemetryService"
 import { CheckpointServiceOptions, RepoPerTaskCheckpointService } from "../services/checkpoints"
@@ -165,6 +166,9 @@ export class Cline extends EventEmitter<ClineEvents> {
 	consecutiveMistakeLimit: number
 	consecutiveMistakeCountForApplyDiff: Map<string, number> = new Map()
 
+	// For tracking identical consecutive tool calls
+	private toolRepetitionDetector: ToolRepetitionDetector
+
 	// Not private since it needs to be accessible by tools.
 	providerRef: WeakRef<ClineProvider>
 	private readonly globalStoragePath: string
@@ -263,6 +267,7 @@ export class Cline extends EventEmitter<ClineEvents> {
 		}
 
 		this.diffStrategy = new MultiSearchReplaceDiffStrategy(this.fuzzyMatchThreshold)
+		this.toolRepetitionDetector = new ToolRepetitionDetector(this.consecutiveMistakeLimit)
 
 		onCreated?.(this)
 
@@ -1857,6 +1862,46 @@ export class Cline extends EventEmitter<ClineEvents> {
 					break
 				}
 
+				// Check for identical consecutive tool calls
+				if (!block.partial) {
+					// Use the detector to check for repetition, passing the ToolUse block directly
+					const repetitionCheck = this.toolRepetitionDetector.check(block)
+
+					// If execution is not allowed, notify user and break
+					if (!repetitionCheck.allowExecution && repetitionCheck.askUser) {
+						// Handle repetition similar to mistake_limit_reached pattern
+						const { response, text, images } = await this.ask(
+							repetitionCheck.askUser.messageKey as ClineAsk,
+							repetitionCheck.askUser.messageDetail.replace("{toolName}", block.name),
+						)
+
+						if (response === "messageResponse") {
+							// Add user feedback to userContent
+							this.userMessageContent.push(
+								{
+									type: "text" as const,
+									text: `Tool repetition limit reached. User feedback: ${text}`,
+								},
+								...formatResponse.imageBlocks(images),
+							)
+
+							// Add user feedback to chat
+							await this.say("user_feedback", text, images)
+
+							// Track tool repetition in telemetry
+							telemetryService.captureConsecutiveMistakeError(this.taskId) // Using existing telemetry method
+						}
+
+						// Return tool result message about the repetition
+						pushToolResult(
+							formatResponse.toolError(
+								`Tool call repetition limit reached for ${block.name}. Please try a different approach.`,
+							),
+						)
+						break
+					}
+				}
+
 				switch (block.name) {
 					case "write_to_file":
 						await writeToFileTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag)

+ 95 - 0
src/core/ToolRepetitionDetector.ts

@@ -0,0 +1,95 @@
+import { ToolUse } from "../shared/tools"
+import { t } from "../i18n"
+
+/**
+ * Class for detecting consecutive identical tool calls
+ * to prevent the AI from getting stuck in a loop.
+ */
+export class ToolRepetitionDetector {
+	private previousToolCallJson: string | null = null
+	private consecutiveIdenticalToolCallCount: number = 0
+	private readonly consecutiveIdenticalToolCallLimit: number
+
+	/**
+	 * Creates a new ToolRepetitionDetector
+	 * @param limit The maximum number of identical consecutive tool calls allowed
+	 */
+	constructor(limit: number = 3) {
+		this.consecutiveIdenticalToolCallLimit = limit
+	}
+
+	/**
+	 * Checks if the current tool call is identical to the previous one
+	 * and determines if execution should be allowed
+	 *
+	 * @param currentToolCallBlock ToolUse object representing the current tool call
+	 * @returns Object indicating if execution is allowed and a message to show if not
+	 */
+	public check(currentToolCallBlock: ToolUse): {
+		allowExecution: boolean
+		askUser?: {
+			messageKey: string
+			messageDetail: string
+		}
+	} {
+		// Serialize the block to a canonical JSON string for comparison
+		const currentToolCallJson = this.serializeToolUse(currentToolCallBlock)
+
+		// Compare with previous tool call
+		if (this.previousToolCallJson === currentToolCallJson) {
+			this.consecutiveIdenticalToolCallCount++
+		} else {
+			this.consecutiveIdenticalToolCallCount = 1 // Start with 1 for the first occurrence
+			this.previousToolCallJson = currentToolCallJson
+		}
+
+		// Check if limit is reached
+		if (this.consecutiveIdenticalToolCallCount >= this.consecutiveIdenticalToolCallLimit) {
+			// Reset counters to allow recovery if user guides the AI past this point
+			this.consecutiveIdenticalToolCallCount = 0
+			this.previousToolCallJson = null
+
+			// Return result indicating execution should not be allowed
+			return {
+				allowExecution: false,
+				askUser: {
+					messageKey: "mistake_limit_reached",
+					messageDetail: t("tools:toolRepetitionLimitReached", { toolName: currentToolCallBlock.name }),
+				},
+			}
+		}
+
+		// Execution is allowed
+		return { allowExecution: true }
+	}
+
+	/**
+	 * Serializes a ToolUse object into a canonical JSON string for comparison
+	 *
+	 * @param toolUse The ToolUse object to serialize
+	 * @returns JSON string representation of the tool use with sorted parameter keys
+	 */
+	private serializeToolUse(toolUse: ToolUse): string {
+		// Create a new parameters object with alphabetically sorted keys
+		const sortedParams: Record<string, unknown> = {}
+
+		// Get parameter keys and sort them alphabetically
+		const sortedKeys = Object.keys(toolUse.params).sort()
+
+		// Populate the sorted parameters object in a type-safe way
+		for (const key of sortedKeys) {
+			if (Object.prototype.hasOwnProperty.call(toolUse.params, key)) {
+				sortedParams[key] = toolUse.params[key as keyof typeof toolUse.params]
+			}
+		}
+
+		// Create the object with the tool name and sorted parameters
+		const toolObject = {
+			name: toolUse.name,
+			parameters: sortedParams,
+		}
+
+		// Convert to a canonical JSON string
+		return JSON.stringify(toolObject)
+	}
+}

+ 303 - 0
src/core/__tests__/ToolRepetitionDetector.test.ts

@@ -0,0 +1,303 @@
+import { ToolRepetitionDetector } from "../ToolRepetitionDetector"
+import { ToolUse } from "../../shared/tools"
+import { ToolName } from "../../schemas"
+
+// Mock the i18n system
+jest.mock("../../i18n", () => ({
+	t: jest.fn((key, options) => {
+		// For toolRepetitionLimitReached key, return a message with the tool name
+		if (key === "tools:toolRepetitionLimitReached" && options?.toolName) {
+			return `Roo appears to be stuck in a loop, attempting the same action (${options.toolName}) repeatedly. This might indicate a problem with its current strategy.`
+		}
+		return key
+	}),
+}))
+
+// Helper function to create a mock ToolUse
+function createToolUse(name: string, displayName?: string, params: Record<string, string> = {}): ToolUse {
+	return {
+		type: "tool_use",
+		name: (displayName || name) as ToolName,
+		params,
+		partial: false,
+	}
+}
+
+describe("ToolRepetitionDetector", () => {
+	// ===== Initialization tests =====
+	describe("initialization", () => {
+		it("should default to a limit of 3 if no argument provided", () => {
+			const detector = new ToolRepetitionDetector()
+			// We'll verify this through behavior in subsequent tests
+
+			// First call (counter = 1)
+			const result1 = detector.check(createToolUse("test", "test-tool"))
+			expect(result1.allowExecution).toBe(true)
+
+			// Second identical call (counter = 2)
+			const result2 = detector.check(createToolUse("test", "test-tool"))
+			expect(result2.allowExecution).toBe(true)
+
+			// Third identical call (counter = 3) reaches the default limit
+			const result3 = detector.check(createToolUse("test", "test-tool"))
+			expect(result3.allowExecution).toBe(false)
+		})
+
+		it("should use the custom limit when provided", () => {
+			const customLimit = 2
+			const detector = new ToolRepetitionDetector(customLimit)
+
+			// First call (counter = 1)
+			const result1 = detector.check(createToolUse("test", "test-tool"))
+			expect(result1.allowExecution).toBe(true)
+
+			// Second identical call (counter = 2) reaches the custom limit
+			const result2 = detector.check(createToolUse("test", "test-tool"))
+			expect(result2.allowExecution).toBe(false)
+		})
+	})
+
+	// ===== No Repetition tests =====
+	describe("no repetition", () => {
+		it("should allow execution for different tool calls", () => {
+			const detector = new ToolRepetitionDetector()
+
+			const result1 = detector.check(createToolUse("first", "first-tool"))
+			expect(result1.allowExecution).toBe(true)
+			expect(result1.askUser).toBeUndefined()
+
+			const result2 = detector.check(createToolUse("second", "second-tool"))
+			expect(result2.allowExecution).toBe(true)
+			expect(result2.askUser).toBeUndefined()
+
+			const result3 = detector.check(createToolUse("third", "third-tool"))
+			expect(result3.allowExecution).toBe(true)
+			expect(result3.askUser).toBeUndefined()
+		})
+
+		it("should reset the counter when different tool calls are made", () => {
+			const detector = new ToolRepetitionDetector(2)
+
+			// First call
+			detector.check(createToolUse("same", "same-tool"))
+
+			// Second identical call would reach limit of 2, but we'll make a different call
+			detector.check(createToolUse("different", "different-tool"))
+
+			// Back to the first tool - should be allowed since counter was reset
+			const result = detector.check(createToolUse("same", "same-tool"))
+			expect(result.allowExecution).toBe(true)
+		})
+	})
+
+	// ===== Repetition Below Limit tests =====
+	describe("repetition below limit", () => {
+		it("should allow execution when repetition is below limit and block when limit reached", () => {
+			const detector = new ToolRepetitionDetector(3)
+
+			// First call (counter = 1)
+			const result1 = detector.check(createToolUse("repeat", "repeat-tool"))
+			expect(result1.allowExecution).toBe(true)
+
+			// Second identical call (counter = 2)
+			const result2 = detector.check(createToolUse("repeat", "repeat-tool"))
+			expect(result2.allowExecution).toBe(true)
+
+			// Third identical call (counter = 3) reaches limit
+			const result3 = detector.check(createToolUse("repeat", "repeat-tool"))
+			expect(result3.allowExecution).toBe(false)
+		})
+	})
+
+	// ===== Repetition Reaches Limit tests =====
+	describe("repetition reaches limit", () => {
+		it("should block execution when repetition reaches the limit", () => {
+			const detector = new ToolRepetitionDetector(3)
+
+			// First call (counter = 1)
+			detector.check(createToolUse("repeat", "repeat-tool"))
+
+			// Second identical call (counter = 2)
+			detector.check(createToolUse("repeat", "repeat-tool"))
+
+			// Third identical call (counter = 3) - should reach limit
+			const result = detector.check(createToolUse("repeat", "repeat-tool"))
+
+			expect(result.allowExecution).toBe(false)
+			expect(result.askUser).toBeDefined()
+			expect(result.askUser?.messageKey).toBe("mistake_limit_reached")
+			expect(result.askUser?.messageDetail).toContain("repeat-tool")
+		})
+
+		it("should reset internal state after limit is reached", () => {
+			const detector = new ToolRepetitionDetector(2)
+
+			// Reach the limit
+			detector.check(createToolUse("repeat", "repeat-tool"))
+			const limitResult = detector.check(createToolUse("repeat", "repeat-tool")) // This reaches limit
+			expect(limitResult.allowExecution).toBe(false)
+
+			// Use a new tool call - should be allowed since state was reset
+			const result = detector.check(createToolUse("new", "new-tool"))
+			expect(result.allowExecution).toBe(true)
+		})
+	})
+
+	// ===== Repetition After Limit (Post-Reset) tests =====
+	describe("repetition after limit", () => {
+		it("should allow execution of previously problematic tool after reset", () => {
+			const detector = new ToolRepetitionDetector(2)
+
+			// Reach the limit with a specific tool
+			detector.check(createToolUse("problem", "problem-tool"))
+			const limitResult = detector.check(createToolUse("problem", "problem-tool")) // This reaches limit
+			expect(limitResult.allowExecution).toBe(false)
+
+			// The same tool that previously caused problems should now be allowed
+			const result = detector.check(createToolUse("problem", "problem-tool"))
+			expect(result.allowExecution).toBe(true)
+		})
+
+		it("should require reaching the limit again after reset", () => {
+			const detector = new ToolRepetitionDetector(2)
+
+			// Reach the limit
+			detector.check(createToolUse("repeat", "repeat-tool"))
+			const limitResult = detector.check(createToolUse("repeat", "repeat-tool")) // This reaches limit
+			expect(limitResult.allowExecution).toBe(false)
+
+			// First call after reset
+			detector.check(createToolUse("repeat", "repeat-tool"))
+
+			// Second identical call (counter = 2) should reach limit again
+			const result = detector.check(createToolUse("repeat", "repeat-tool"))
+			expect(result.allowExecution).toBe(false)
+			expect(result.askUser).toBeDefined()
+		})
+	})
+
+	// ===== Tool Name Interpolation tests =====
+	describe("tool name interpolation", () => {
+		it("should include tool name in the error message", () => {
+			const detector = new ToolRepetitionDetector(2)
+			const toolName = "special-tool-name"
+
+			// Reach the limit
+			detector.check(createToolUse("test", toolName))
+			const result = detector.check(createToolUse("test", toolName))
+
+			expect(result.allowExecution).toBe(false)
+			expect(result.askUser?.messageDetail).toContain(toolName)
+		})
+	})
+
+	// ===== Edge Cases =====
+	describe("edge cases", () => {
+		it("should handle empty tool call", () => {
+			const detector = new ToolRepetitionDetector(2)
+
+			// Create an empty tool call - a tool with no parameters
+			// Use the empty tool directly in the check calls
+			detector.check(createToolUse("empty-tool", "empty-tool"))
+			const result = detector.check(createToolUse("empty-tool"))
+
+			expect(result.allowExecution).toBe(false)
+			expect(result.askUser).toBeDefined()
+		})
+
+		it("should handle different tool names with identical serialized JSON", () => {
+			const detector = new ToolRepetitionDetector(2)
+
+			// First, call with tool-name-1 twice to set up the counter
+			const toolUse1 = createToolUse("tool-name-1", "tool-name-1", { param: "value" })
+			detector.check(toolUse1)
+
+			// Create a tool that will serialize to the same JSON as toolUse1
+			// We need to mock the serializeToolUse method to return the same value
+			const toolUse2 = createToolUse("tool-name-2", "tool-name-2", { param: "value" })
+
+			// Override the private method to force identical serialization
+			const originalSerialize = (detector as any).serializeToolUse
+			;(detector as any).serializeToolUse = (tool: ToolUse) => {
+				// Use string comparison for the name since it's technically an enum
+				if (String(tool.name) === "tool-name-2") {
+					return (detector as any).serializeToolUse(toolUse1) // Return the same JSON as toolUse1
+				}
+				return originalSerialize(tool)
+			}
+
+			// This should detect as a repetition now
+			const result = detector.check(toolUse2)
+
+			// Restore the original method
+			;(detector as any).serializeToolUse = originalSerialize
+
+			// Since we're directly manipulating the internal state for testing,
+			// we still expect it to consider this a repetition
+			expect(result.allowExecution).toBe(false)
+			expect(result.askUser).toBeDefined()
+		})
+
+		it("should treat tools with same parameters in different order as identical", () => {
+			const detector = new ToolRepetitionDetector(2)
+
+			// First call with parameters in one order
+			const toolUse1 = createToolUse("same-tool", "same-tool", { a: "1", b: "2", c: "3" })
+			detector.check(toolUse1)
+
+			// Create tool with same parameters but in different order
+			const toolUse2 = createToolUse("same-tool", "same-tool", { c: "3", a: "1", b: "2" })
+
+			// This should still detect as a repetition due to canonical JSON with sorted keys
+			const result = detector.check(toolUse2)
+
+			// Since parameters are sorted alphabetically in the serialized JSON,
+			// these should be considered identical
+			expect(result.allowExecution).toBe(false)
+			expect(result.askUser).toBeDefined()
+		})
+	})
+
+	// ===== Explicit Nth Call Blocking tests =====
+	describe("explicit Nth call blocking behavior", () => {
+		it("should block on the 1st call for limit 1", () => {
+			const detector = new ToolRepetitionDetector(1)
+
+			// First call (counter = 1) should be blocked
+			const result = detector.check(createToolUse("tool", "tool-name"))
+
+			expect(result.allowExecution).toBe(false)
+			expect(result.askUser).toBeDefined()
+		})
+
+		it("should block on the 2nd call for limit 2", () => {
+			const detector = new ToolRepetitionDetector(2)
+
+			// First call (counter = 1)
+			const result1 = detector.check(createToolUse("tool", "tool-name"))
+			expect(result1.allowExecution).toBe(true)
+
+			// Second call (counter = 2) should be blocked
+			const result2 = detector.check(createToolUse("tool", "tool-name"))
+			expect(result2.allowExecution).toBe(false)
+			expect(result2.askUser).toBeDefined()
+		})
+
+		it("should block on the 3rd call for limit 3 (default)", () => {
+			const detector = new ToolRepetitionDetector(3)
+
+			// First call (counter = 1)
+			const result1 = detector.check(createToolUse("tool", "tool-name"))
+			expect(result1.allowExecution).toBe(true)
+
+			// Second call (counter = 2)
+			const result2 = detector.check(createToolUse("tool", "tool-name"))
+			expect(result2.allowExecution).toBe(true)
+
+			// Third call (counter = 3) should be blocked
+			const result3 = detector.check(createToolUse("tool", "tool-name"))
+			expect(result3.allowExecution).toBe(false)
+			expect(result3.askUser).toBeDefined()
+		})
+	})
+})

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

@@ -5,5 +5,6 @@
 		"linesFromStartTo": " (línies 1-{{end}})",
 		"definitionsOnly": " (només definicions)",
 		"maxLines": " (màxim {{max}} línies)"
-	}
+	},
+	"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."
 }

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

@@ -5,5 +5,6 @@
 		"linesFromStartTo": " (Zeilen 1-{{end}})",
 		"definitionsOnly": " (nur Definitionen)",
 		"maxLines": " (maximal {{max}} Zeilen)"
-	}
+	},
+	"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."
 }

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

@@ -5,5 +5,6 @@
 		"linesFromStartTo": " (lines 1-{{end}})",
 		"definitionsOnly": " (definitions only)",
 		"maxLines": " (max {{max}} lines)"
-	}
+	},
+	"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."
 }

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

@@ -5,5 +5,6 @@
 		"linesFromStartTo": " (líneas 1-{{end}})",
 		"definitionsOnly": " (solo definiciones)",
 		"maxLines": " (máximo {{max}} líneas)"
-	}
+	},
+	"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."
 }

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

@@ -5,5 +5,6 @@
 		"linesFromStartTo": " (lignes 1-{{end}})",
 		"definitionsOnly": " (définitions uniquement)",
 		"maxLines": " (max {{max}} lignes)"
-	}
+	},
+	"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."
 }

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

@@ -5,5 +5,6 @@
 		"linesFromStartTo": " (पंक्तियाँ 1-{{end}})",
 		"definitionsOnly": " (केवल परिभाषाएँ)",
 		"maxLines": " (अधिकतम {{max}} पंक्तियाँ)"
-	}
+	},
+	"toolRepetitionLimitReached": "Roo एक लूप में फंसा हुआ लगता है, बार-बार एक ही क्रिया ({{toolName}}) को दोहरा रहा है। यह उसकी वर्तमान रणनीति में किसी समस्या का संकेत हो सकता है। कार्य को पुनः परिभाषित करने, अधिक विशिष्ट निर्देश देने, या उसे एक अलग दृष्टिकोण की ओर मार्गदर्शित करने पर विचार करें।"
 }

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

@@ -5,5 +5,6 @@
 		"linesFromStartTo": " (righe 1-{{end}})",
 		"definitionsOnly": " (solo definizioni)",
 		"maxLines": " (max {{max}} righe)"
-	}
+	},
+	"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."
 }

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

@@ -5,5 +5,6 @@
 		"linesFromStartTo": " (1-{{end}}行目)",
 		"definitionsOnly": " (定義のみ)",
 		"maxLines": " (最大{{max}}行)"
-	}
+	},
+	"toolRepetitionLimitReached": "Rooが同じ操作({{toolName}})を繰り返し試みるループに陥っているようです。これは現在の方法に問題がある可能性を示しています。タスクの言い換え、より具体的な指示の提供、または別のアプローチへの誘導を検討してください。"
 }

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

@@ -5,5 +5,6 @@
 		"linesFromStartTo": " (1-{{end}}행)",
 		"definitionsOnly": " (정의만)",
 		"maxLines": " (최대 {{max}}행)"
-	}
+	},
+	"toolRepetitionLimitReached": "Roo가 같은 동작({{toolName}})을 반복적으로 시도하면서 루프에 갇힌 것 같습니다. 이는 현재 전략에 문제가 있을 수 있음을 나타냅니다. 작업을 다시 표현하거나, 더 구체적인 지침을 제공하거나, 다른 접근 방식으로 안내해 보세요."
 }

+ 8 - 7
src/i18n/locales/nl/tools.json

@@ -1,9 +1,10 @@
 {
-  "readFile": {
-    "linesRange": " (regels {{start}}-{{end}})",
-    "linesFromToEnd": " (regels {{start}}-einde)",
-    "linesFromStartTo": " (regels 1-{{end}})",
-    "definitionsOnly": " (alleen definities)",
-    "maxLines": " (max {{max}} regels)"
-  }
+	"readFile": {
+		"linesRange": " (regels {{start}}-{{end}})",
+		"linesFromToEnd": " (regels {{start}}-einde)",
+		"linesFromStartTo": " (regels 1-{{end}})",
+		"definitionsOnly": " (alleen definities)",
+		"maxLines": " (max {{max}} regels)"
+	},
+	"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."
 }

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

@@ -5,5 +5,6 @@
 		"linesFromStartTo": " (linie 1-{{end}})",
 		"definitionsOnly": " (tylko definicje)",
 		"maxLines": " (maks. {{max}} linii)"
-	}
+	},
+	"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."
 }

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

@@ -5,5 +5,6 @@
 		"linesFromStartTo": " (linhas 1-{{end}})",
 		"definitionsOnly": " (apenas definições)",
 		"maxLines": " (máx. {{max}} linhas)"
-	}
+	},
+	"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."
 }

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

@@ -5,5 +5,6 @@
 		"linesFromStartTo": " (строки 1-{{end}})",
 		"definitionsOnly": " (только определения)",
 		"maxLines": " (макс. {{max}} строк)"
-	}
+	},
+	"toolRepetitionLimitReached": "Похоже, что Roo застрял в цикле, многократно пытаясь выполнить одно и то же действие ({{toolName}}). Это может указывать на проблему с его текущей стратегией. Попробуйте переформулировать задачу, предоставить более конкретные инструкции или направить его к другому подходу."
 }

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

@@ -5,5 +5,6 @@
 		"linesFromStartTo": " (satır 1-{{end}})",
 		"definitionsOnly": " (sadece tanımlar)",
 		"maxLines": " (maks. {{max}} satır)"
-	}
+	},
+	"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."
 }

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

@@ -5,5 +5,6 @@
 		"linesFromStartTo": " (dòng 1-{{end}})",
 		"definitionsOnly": " (chỉ định nghĩa)",
 		"maxLines": " (tối đa {{max}} dòng)"
-	}
+	},
+	"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."
 }

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

@@ -5,5 +5,6 @@
 		"linesFromStartTo": " (第 1-{{end}} 行)",
 		"definitionsOnly": " (仅定义)",
 		"maxLines": " (最多 {{max}} 行)"
-	}
+	},
+	"toolRepetitionLimitReached": "Roo 似乎陷入循环,反复尝试同一操作 ({{toolName}})。这可能表明当前策略存在问题。请考虑重新描述任务、提供更具体的指示或引导其尝试不同的方法。"
 }

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

@@ -5,5 +5,6 @@
 		"linesFromStartTo": " (第 1-{{end}} 行)",
 		"definitionsOnly": " (僅定義)",
 		"maxLines": " (最多 {{max}} 行)"
-	}
+	},
+	"toolRepetitionLimitReached": "Roo 似乎陷入循環,反覆嘗試同一操作 ({{toolName}})。這可能表明目前策略存在問題。請考慮重新描述工作、提供更具體的指示或引導其嘗試不同的方法。"
 }