Browse Source

Limit search_files to only look within the workspace (#4642)

Matt Rubens 8 months ago
parent
commit
ca0338af15

+ 300 - 0
src/core/tools/__tests__/searchFilesTool.spec.ts

@@ -0,0 +1,300 @@
+import path from "path"
+import { describe, it, expect, beforeEach, vi, type Mock, type MockedFunction } from "vitest"
+import { searchFilesTool } from "../searchFilesTool"
+import { Task } from "../../task/Task"
+import { SearchFilesToolUse } from "../../../shared/tools"
+import { isPathOutsideWorkspace } from "../../../utils/pathUtils"
+import { regexSearchFiles } from "../../../services/ripgrep"
+import { RooIgnoreController } from "../../ignore/RooIgnoreController"
+
+// Mock dependencies
+vi.mock("../../../utils/pathUtils", () => ({
+	isPathOutsideWorkspace: vi.fn(),
+}))
+
+vi.mock("../../../services/ripgrep", () => ({
+	regexSearchFiles: vi.fn(),
+}))
+
+vi.mock("../../../utils/path", () => ({
+	getReadablePath: vi.fn((cwd: string, relPath: string) => relPath),
+}))
+
+vi.mock("../../ignore/RooIgnoreController")
+
+vi.mock("../../../i18n", () => ({
+	t: vi.fn((key: string, params?: any) => {
+		if (key === "tools:searchFiles.workspaceBoundaryError") {
+			return `Cannot search outside workspace. Path '${params?.path}' is outside the current workspace.`
+		}
+		return key
+	}),
+}))
+
+const mockedIsPathOutsideWorkspace = isPathOutsideWorkspace as MockedFunction<typeof isPathOutsideWorkspace>
+const mockedRegexSearchFiles = regexSearchFiles as MockedFunction<typeof regexSearchFiles>
+
+describe("searchFilesTool", () => {
+	let mockTask: Partial<Task>
+	let mockAskApproval: Mock
+	let mockHandleError: Mock
+	let mockPushToolResult: Mock
+	let mockRemoveClosingTag: Mock
+
+	beforeEach(() => {
+		vi.clearAllMocks()
+
+		mockTask = {
+			cwd: "/workspace",
+			consecutiveMistakeCount: 0,
+			recordToolError: vi.fn(),
+			sayAndCreateMissingParamError: vi.fn().mockResolvedValue("Missing parameter error"),
+			say: vi.fn().mockResolvedValue(undefined),
+			rooIgnoreController: new RooIgnoreController("/workspace"),
+		}
+
+		mockAskApproval = vi.fn().mockResolvedValue(true)
+		mockHandleError = vi.fn()
+		mockPushToolResult = vi.fn()
+		mockRemoveClosingTag = vi.fn((tag: string, value: string | undefined) => value || "")
+
+		mockedRegexSearchFiles.mockResolvedValue("Search results")
+	})
+
+	describe("workspace boundary validation", () => {
+		it("should allow search within workspace", async () => {
+			const block: SearchFilesToolUse = {
+				type: "tool_use",
+				name: "search_files",
+				params: {
+					path: "src",
+					regex: "test",
+					file_pattern: "*.ts",
+				},
+				partial: false,
+			}
+
+			mockedIsPathOutsideWorkspace.mockReturnValue(false)
+
+			await searchFilesTool(
+				mockTask as Task,
+				block,
+				mockAskApproval,
+				mockHandleError,
+				mockPushToolResult,
+				mockRemoveClosingTag,
+			)
+
+			expect(mockedIsPathOutsideWorkspace).toHaveBeenCalledWith(path.resolve("/workspace", "src"))
+			expect(mockedRegexSearchFiles).toHaveBeenCalled()
+			expect(mockPushToolResult).toHaveBeenCalledWith("Search results")
+		})
+
+		it("should block search outside workspace", async () => {
+			const block: SearchFilesToolUse = {
+				type: "tool_use",
+				name: "search_files",
+				params: {
+					path: "../external",
+					regex: "test",
+					file_pattern: "*.ts",
+				},
+				partial: false,
+			}
+
+			mockedIsPathOutsideWorkspace.mockReturnValue(true)
+
+			await searchFilesTool(
+				mockTask as Task,
+				block,
+				mockAskApproval,
+				mockHandleError,
+				mockPushToolResult,
+				mockRemoveClosingTag,
+			)
+
+			expect(mockedIsPathOutsideWorkspace).toHaveBeenCalledWith(path.resolve("/workspace", "../external"))
+			expect(mockedRegexSearchFiles).not.toHaveBeenCalled()
+			expect(mockTask.say).toHaveBeenCalledWith(
+				"error",
+				"Cannot search outside workspace. Path '../external' is outside the current workspace.",
+			)
+			expect(mockPushToolResult).toHaveBeenCalledWith(
+				"Cannot search outside workspace. Path '../external' is outside the current workspace.",
+			)
+			expect(mockTask.consecutiveMistakeCount).toBe(1)
+			expect(mockTask.recordToolError).toHaveBeenCalledWith("search_files")
+		})
+
+		it("should block search with absolute path outside workspace", async () => {
+			const block: SearchFilesToolUse = {
+				type: "tool_use",
+				name: "search_files",
+				params: {
+					path: "/etc/passwd",
+					regex: "root",
+				},
+				partial: false,
+			}
+
+			mockedIsPathOutsideWorkspace.mockReturnValue(true)
+
+			await searchFilesTool(
+				mockTask as Task,
+				block,
+				mockAskApproval,
+				mockHandleError,
+				mockPushToolResult,
+				mockRemoveClosingTag,
+			)
+
+			expect(mockedIsPathOutsideWorkspace).toHaveBeenCalledWith(path.resolve("/workspace", "/etc/passwd"))
+			expect(mockedRegexSearchFiles).not.toHaveBeenCalled()
+			expect(mockTask.say).toHaveBeenCalledWith(
+				"error",
+				"Cannot search outside workspace. Path '/etc/passwd' is outside the current workspace.",
+			)
+			expect(mockPushToolResult).toHaveBeenCalledWith(
+				"Cannot search outside workspace. Path '/etc/passwd' is outside the current workspace.",
+			)
+		})
+
+		it("should handle relative paths that resolve outside workspace", async () => {
+			const block: SearchFilesToolUse = {
+				type: "tool_use",
+				name: "search_files",
+				params: {
+					path: "../../..",
+					regex: "sensitive",
+				},
+				partial: false,
+			}
+
+			mockedIsPathOutsideWorkspace.mockReturnValue(true)
+
+			await searchFilesTool(
+				mockTask as Task,
+				block,
+				mockAskApproval,
+				mockHandleError,
+				mockPushToolResult,
+				mockRemoveClosingTag,
+			)
+
+			expect(mockedIsPathOutsideWorkspace).toHaveBeenCalledWith(path.resolve("/workspace", "../../.."))
+			expect(mockedRegexSearchFiles).not.toHaveBeenCalled()
+			expect(mockTask.say).toHaveBeenCalledWith(
+				"error",
+				"Cannot search outside workspace. Path '../../..' is outside the current workspace.",
+			)
+			expect(mockPushToolResult).toHaveBeenCalledWith(
+				"Cannot search outside workspace. Path '../../..' is outside the current workspace.",
+			)
+		})
+	})
+
+	describe("existing functionality", () => {
+		beforeEach(() => {
+			mockedIsPathOutsideWorkspace.mockReturnValue(false)
+		})
+
+		it("should handle missing path parameter", async () => {
+			const block: SearchFilesToolUse = {
+				type: "tool_use",
+				name: "search_files",
+				params: {
+					regex: "test",
+				},
+				partial: false,
+			}
+
+			await searchFilesTool(
+				mockTask as Task,
+				block,
+				mockAskApproval,
+				mockHandleError,
+				mockPushToolResult,
+				mockRemoveClosingTag,
+			)
+
+			expect(mockTask.sayAndCreateMissingParamError).toHaveBeenCalledWith("search_files", "path")
+			expect(mockedRegexSearchFiles).not.toHaveBeenCalled()
+		})
+
+		it("should handle missing regex parameter", async () => {
+			const block: SearchFilesToolUse = {
+				type: "tool_use",
+				name: "search_files",
+				params: {
+					path: "src",
+				},
+				partial: false,
+			}
+
+			await searchFilesTool(
+				mockTask as Task,
+				block,
+				mockAskApproval,
+				mockHandleError,
+				mockPushToolResult,
+				mockRemoveClosingTag,
+			)
+
+			expect(mockTask.sayAndCreateMissingParamError).toHaveBeenCalledWith("search_files", "regex")
+			expect(mockedRegexSearchFiles).not.toHaveBeenCalled()
+		})
+
+		it("should handle partial blocks", async () => {
+			const block: SearchFilesToolUse = {
+				type: "tool_use",
+				name: "search_files",
+				params: {
+					path: "src",
+					regex: "test",
+				},
+				partial: true,
+			}
+
+			const mockAsk = vi.fn()
+			mockTask.ask = mockAsk
+
+			await searchFilesTool(
+				mockTask as Task,
+				block,
+				mockAskApproval,
+				mockHandleError,
+				mockPushToolResult,
+				mockRemoveClosingTag,
+			)
+
+			expect(mockAsk).toHaveBeenCalled()
+			expect(mockedRegexSearchFiles).not.toHaveBeenCalled()
+		})
+
+		it("should handle user rejection", async () => {
+			const block: SearchFilesToolUse = {
+				type: "tool_use",
+				name: "search_files",
+				params: {
+					path: "src",
+					regex: "test",
+				},
+				partial: false,
+			}
+
+			mockAskApproval.mockResolvedValue(false)
+
+			await searchFilesTool(
+				mockTask as Task,
+				block,
+				mockAskApproval,
+				mockHandleError,
+				mockPushToolResult,
+				mockRemoveClosingTag,
+			)
+
+			expect(mockedRegexSearchFiles).toHaveBeenCalled()
+			expect(mockPushToolResult).not.toHaveBeenCalled()
+		})
+	})
+})

+ 13 - 0
src/core/tools/searchFilesTool.ts

@@ -4,7 +4,9 @@ import { Task } from "../task/Task"
 import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools"
 import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools"
 import { ClineSayTool } from "../../shared/ExtensionMessage"
 import { ClineSayTool } from "../../shared/ExtensionMessage"
 import { getReadablePath } from "../../utils/path"
 import { getReadablePath } from "../../utils/path"
+import { isPathOutsideWorkspace } from "../../utils/pathUtils"
 import { regexSearchFiles } from "../../services/ripgrep"
 import { regexSearchFiles } from "../../services/ripgrep"
+import { t } from "../../i18n"
 
 
 export async function searchFilesTool(
 export async function searchFilesTool(
 	cline: Task,
 	cline: Task,
@@ -49,6 +51,17 @@ export async function searchFilesTool(
 
 
 			const absolutePath = path.resolve(cline.cwd, relDirPath)
 			const absolutePath = path.resolve(cline.cwd, relDirPath)
 
 
+			// Check if path is outside workspace
+			if (isPathOutsideWorkspace(absolutePath)) {
+				const userErrorMessage = t("tools:searchFiles.workspaceBoundaryError", { path: relDirPath })
+				const llmErrorMessage = `Cannot search outside workspace. Path '${relDirPath}' is outside the current workspace.`
+				cline.consecutiveMistakeCount++
+				cline.recordToolError("search_files")
+				await cline.say("error", userErrorMessage)
+				pushToolResult(llmErrorMessage)
+				return
+			}
+
 			const results = await regexSearchFiles(
 			const results = await regexSearchFiles(
 				cline.cwd,
 				cline.cwd,
 				absolutePath,
 				absolutePath,

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

@@ -7,5 +7,8 @@
 	"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.",
 	"codebaseSearch": {
 	"codebaseSearch": {
 		"approval": "Cercant '{{query}}' a la base de codi..."
 		"approval": "Cercant '{{query}}' a la base de codi..."
+	},
+	"searchFiles": {
+		"workspaceBoundaryError": "No es pot cercar fora de l'espai de treball. El camí '{{path}}' està fora de l'espai de treball actual."
 	}
 	}
 }
 }

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

@@ -7,5 +7,8 @@
 	"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.",
 	"codebaseSearch": {
 	"codebaseSearch": {
 		"approval": "Suche nach '{{query}}' im Codebase..."
 		"approval": "Suche nach '{{query}}' im Codebase..."
+	},
+	"searchFiles": {
+		"workspaceBoundaryError": "Kann nicht außerhalb des Arbeitsbereichs suchen. Pfad '{{path}}' liegt außerhalb des aktuellen Arbeitsbereichs."
 	}
 	}
 }
 }

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

@@ -7,5 +7,8 @@
 	"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.",
 	"codebaseSearch": {
 	"codebaseSearch": {
 		"approval": "Searching for '{{query}}' in codebase..."
 		"approval": "Searching for '{{query}}' in codebase..."
+	},
+	"searchFiles": {
+		"workspaceBoundaryError": "Cannot search outside workspace. Path '{{path}}' is outside the current workspace."
 	}
 	}
 }
 }

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

@@ -7,5 +7,8 @@
 	"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.",
 	"codebaseSearch": {
 	"codebaseSearch": {
 		"approval": "Buscando '{{query}}' en la base de código..."
 		"approval": "Buscando '{{query}}' en la base de código..."
+	},
+	"searchFiles": {
+		"workspaceBoundaryError": "No se puede buscar fuera del espacio de trabajo. La ruta '{{path}}' está fuera del espacio de trabajo actual."
 	}
 	}
 }
 }

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

@@ -7,5 +7,8 @@
 	"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.",
 	"codebaseSearch": {
 	"codebaseSearch": {
 		"approval": "Recherche de '{{query}}' dans la base de code..."
 		"approval": "Recherche de '{{query}}' dans la base de code..."
+	},
+	"searchFiles": {
+		"workspaceBoundaryError": "Impossible de rechercher en dehors de l'espace de travail. Le chemin '{{path}}' est en dehors de l'espace de travail actuel."
 	}
 	}
 }
 }

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

@@ -7,5 +7,8 @@
 	"toolRepetitionLimitReached": "Roo एक लूप में फंसा हुआ लगता है, बार-बार एक ही क्रिया ({{toolName}}) को दोहरा रहा है। यह उसकी वर्तमान रणनीति में किसी समस्या का संकेत हो सकता है। कार्य को पुनः परिभाषित करने, अधिक विशिष्ट निर्देश देने, या उसे एक अलग दृष्टिकोण की ओर मार्गदर्शित करने पर विचार करें।",
 	"toolRepetitionLimitReached": "Roo एक लूप में फंसा हुआ लगता है, बार-बार एक ही क्रिया ({{toolName}}) को दोहरा रहा है। यह उसकी वर्तमान रणनीति में किसी समस्या का संकेत हो सकता है। कार्य को पुनः परिभाषित करने, अधिक विशिष्ट निर्देश देने, या उसे एक अलग दृष्टिकोण की ओर मार्गदर्शित करने पर विचार करें।",
 	"codebaseSearch": {
 	"codebaseSearch": {
 		"approval": "कोडबेस में '{{query}}' खोज रहा है..."
 		"approval": "कोडबेस में '{{query}}' खोज रहा है..."
+	},
+	"searchFiles": {
+		"workspaceBoundaryError": "वर्कस्पेस के बाहर खोज नहीं की जा सकती। पथ '{{path}}' वर्तमान वर्कस्पेस के बाहर है।"
 	}
 	}
 }
 }

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

@@ -7,5 +7,8 @@
 	"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.",
 	"codebaseSearch": {
 	"codebaseSearch": {
 		"approval": "Ricerca di '{{query}}' nella base di codice..."
 		"approval": "Ricerca di '{{query}}' nella base di codice..."
+	},
+	"searchFiles": {
+		"workspaceBoundaryError": "Impossibile cercare al di fuori dell'area di lavoro. Il percorso '{{path}}' è al di fuori dell'area di lavoro corrente."
 	}
 	}
 }
 }

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

@@ -7,5 +7,8 @@
 	"toolRepetitionLimitReached": "Rooが同じ操作({{toolName}})を繰り返し試みるループに陥っているようです。これは現在の方法に問題がある可能性を示しています。タスクの言い換え、より具体的な指示の提供、または別のアプローチへの誘導を検討してください。",
 	"toolRepetitionLimitReached": "Rooが同じ操作({{toolName}})を繰り返し試みるループに陥っているようです。これは現在の方法に問題がある可能性を示しています。タスクの言い換え、より具体的な指示の提供、または別のアプローチへの誘導を検討してください。",
 	"codebaseSearch": {
 	"codebaseSearch": {
 		"approval": "コードベースで '{{query}}' を検索中..."
 		"approval": "コードベースで '{{query}}' を検索中..."
+	},
+	"searchFiles": {
+		"workspaceBoundaryError": "ワークスペース外では検索できません。パス '{{path}}' は現在のワークスペース外にあります。"
 	}
 	}
 }
 }

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

@@ -7,5 +7,8 @@
 	"toolRepetitionLimitReached": "Roo가 같은 동작({{toolName}})을 반복적으로 시도하면서 루프에 갇힌 것 같습니다. 이는 현재 전략에 문제가 있을 수 있음을 나타냅니다. 작업을 다시 표현하거나, 더 구체적인 지침을 제공하거나, 다른 접근 방식으로 안내해 보세요.",
 	"toolRepetitionLimitReached": "Roo가 같은 동작({{toolName}})을 반복적으로 시도하면서 루프에 갇힌 것 같습니다. 이는 현재 전략에 문제가 있을 수 있음을 나타냅니다. 작업을 다시 표현하거나, 더 구체적인 지침을 제공하거나, 다른 접근 방식으로 안내해 보세요.",
 	"codebaseSearch": {
 	"codebaseSearch": {
 		"approval": "코드베이스에서 '{{query}}' 검색 중..."
 		"approval": "코드베이스에서 '{{query}}' 검색 중..."
+	},
+	"searchFiles": {
+		"workspaceBoundaryError": "워크스페이스 외부에서는 검색할 수 없습니다. 경로 '{{path}}'는 현재 워크스페이스 외부에 있습니다."
 	}
 	}
 }
 }

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

@@ -7,5 +7,8 @@
 	"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.",
 	"codebaseSearch": {
 	"codebaseSearch": {
 		"approval": "Zoeken naar '{{query}}' in codebase..."
 		"approval": "Zoeken naar '{{query}}' in codebase..."
+	},
+	"searchFiles": {
+		"workspaceBoundaryError": "Kan niet zoeken buiten de werkruimte. Pad '{{path}}' ligt buiten de huidige werkruimte."
 	}
 	}
 }
 }

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

@@ -7,5 +7,8 @@
 	"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.",
 	"codebaseSearch": {
 	"codebaseSearch": {
 		"approval": "Wyszukiwanie '{{query}}' w bazie kodu..."
 		"approval": "Wyszukiwanie '{{query}}' w bazie kodu..."
+	},
+	"searchFiles": {
+		"workspaceBoundaryError": "Nie można wyszukiwać poza obszarem roboczym. Ścieżka '{{path}}' znajduje się poza bieżącym obszarem roboczym."
 	}
 	}
 }
 }

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

@@ -7,5 +7,8 @@
 	"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.",
 	"codebaseSearch": {
 	"codebaseSearch": {
 		"approval": "Pesquisando '{{query}}' na base de código..."
 		"approval": "Pesquisando '{{query}}' na base de código..."
+	},
+	"searchFiles": {
+		"workspaceBoundaryError": "Não é possível pesquisar fora do espaço de trabalho. O caminho '{{path}}' está fora do espaço de trabalho atual."
 	}
 	}
 }
 }

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

@@ -7,5 +7,8 @@
 	"toolRepetitionLimitReached": "Похоже, что Roo застрял в цикле, многократно пытаясь выполнить одно и то же действие ({{toolName}}). Это может указывать на проблему с его текущей стратегией. Попробуйте переформулировать задачу, предоставить более конкретные инструкции или направить его к другому подходу.",
 	"toolRepetitionLimitReached": "Похоже, что Roo застрял в цикле, многократно пытаясь выполнить одно и то же действие ({{toolName}}). Это может указывать на проблему с его текущей стратегией. Попробуйте переформулировать задачу, предоставить более конкретные инструкции или направить его к другому подходу.",
 	"codebaseSearch": {
 	"codebaseSearch": {
 		"approval": "Поиск '{{query}}' в кодовой базе..."
 		"approval": "Поиск '{{query}}' в кодовой базе..."
+	},
+	"searchFiles": {
+		"workspaceBoundaryError": "Невозможно выполнить поиск за пределами рабочего пространства. Путь '{{path}}' находится за пределами текущего рабочего пространства."
 	}
 	}
 }
 }

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

@@ -7,5 +7,8 @@
 	"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.",
 	"codebaseSearch": {
 	"codebaseSearch": {
 		"approval": "Kod tabanında '{{query}}' aranıyor..."
 		"approval": "Kod tabanında '{{query}}' aranıyor..."
+	},
+	"searchFiles": {
+		"workspaceBoundaryError": "Çalışma alanı dışında arama yapılamaz. '{{path}}' yolu mevcut çalışma alanının dışında."
 	}
 	}
 }
 }

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

@@ -7,5 +7,8 @@
 	"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.",
 	"codebaseSearch": {
 	"codebaseSearch": {
 		"approval": "Đang tìm kiếm '{{query}}' trong cơ sở mã..."
 		"approval": "Đang tìm kiếm '{{query}}' trong cơ sở mã..."
+	},
+	"searchFiles": {
+		"workspaceBoundaryError": "Không thể tìm kiếm bên ngoài không gian làm việc. Đường dẫn '{{path}}' nằm ngoài không gian làm việc hiện tại."
 	}
 	}
 }
 }

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

@@ -7,5 +7,8 @@
 	"toolRepetitionLimitReached": "Roo 似乎陷入循环,反复尝试同一操作 ({{toolName}})。这可能表明当前策略存在问题。请考虑重新描述任务、提供更具体的指示或引导其尝试不同的方法。",
 	"toolRepetitionLimitReached": "Roo 似乎陷入循环,反复尝试同一操作 ({{toolName}})。这可能表明当前策略存在问题。请考虑重新描述任务、提供更具体的指示或引导其尝试不同的方法。",
 	"codebaseSearch": {
 	"codebaseSearch": {
 		"approval": "正在搜索代码库中的 '{{query}}'..."
 		"approval": "正在搜索代码库中的 '{{query}}'..."
+	},
+	"searchFiles": {
+		"workspaceBoundaryError": "无法在工作区外搜索。路径 '{{path}}' 位于当前工作区外。"
 	}
 	}
 }
 }

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

@@ -7,5 +7,8 @@
 	"toolRepetitionLimitReached": "Roo 似乎陷入循環,反覆嘗試同一操作 ({{toolName}})。這可能表明目前策略存在問題。請考慮重新描述工作、提供更具體的指示或引導其嘗試不同的方法。",
 	"toolRepetitionLimitReached": "Roo 似乎陷入循環,反覆嘗試同一操作 ({{toolName}})。這可能表明目前策略存在問題。請考慮重新描述工作、提供更具體的指示或引導其嘗試不同的方法。",
 	"codebaseSearch": {
 	"codebaseSearch": {
 		"approval": "正在搜尋程式碼庫中的「{{query}}」..."
 		"approval": "正在搜尋程式碼庫中的「{{query}}」..."
+	},
+	"searchFiles": {
+		"workspaceBoundaryError": "無法在工作區外搜尋。路徑「{{path}}」位於目前工作區外。"
 	}
 	}
 }
 }