diff --git a/.roo/rules-issue-fixer/1_Workflow.xml b/.roo/rules-issue-fixer/1_Workflow.xml index b0688167e212..72899cdf0bd0 100644 --- a/.roo/rules-issue-fixer/1_Workflow.xml +++ b/.roo/rules-issue-fixer/1_Workflow.xml @@ -191,7 +191,6 @@ Use appropriate tools: - apply_diff for targeted changes - write_to_file for new files - - search_and_replace for systematic updates After each significant change, run relevant tests: - execute_command to run test suites diff --git a/.roo/rules-merge-resolver/2_best_practices.xml b/.roo/rules-merge-resolver/2_best_practices.xml index 5bf1b393eb28..ea6c7cc6a52a 100644 --- a/.roo/rules-merge-resolver/2_best_practices.xml +++ b/.roo/rules-merge-resolver/2_best_practices.xml @@ -33,7 +33,7 @@ Escape Conflict Markers - When using apply_diff or search_and_replace tools, always escape merge + When using apply_diff, always escape merge conflict markers with backslashes to prevent parsing errors. - apply_diff or search_and_replace + apply_diff To resolve conflicts by replacing conflicted sections Precise editing of specific conflict blocks @@ -127,25 +127,6 @@ function mergedImplementation() { ]]> - - - Use for simple conflict resolutions - Enable regex mode for complex patterns - Always escape special characters - - - -src/config.ts -\<<<<<<< HEAD[\s\S]*?\>>>>>>> \w+ -// Resolved configuration -const config = { - // Merged settings from both branches -} -true - - ]]> - @@ -215,7 +196,7 @@ const config = { Conflict markers are incomplete or nested - Use search_and_replace with careful regex patterns + Use apply_diff with precise search blocks; split into multiple targeted edits if needed Manual inspection may be required @@ -232,7 +213,7 @@ const config = { Code contains literal conflict marker strings Extra careful escaping in diffs - Consider using search_and_replace with precise patterns + Prefer apply_diff with precise search blocks diff --git a/apps/vscode-e2e/src/suite/tools/search-and-replace.test.ts b/apps/vscode-e2e/src/suite/tools/search-and-replace.test.ts deleted file mode 100644 index a371253a8e5a..000000000000 --- a/apps/vscode-e2e/src/suite/tools/search-and-replace.test.ts +++ /dev/null @@ -1,631 +0,0 @@ -import * as assert from "assert" -import * as fs from "fs/promises" -import * as path from "path" -import * as vscode from "vscode" - -import { RooCodeEventName, type ClineMessage } from "@roo-code/types" - -import { waitFor, sleep } from "../utils" -import { setDefaultSuiteTimeout } from "../test-utils" - -suite.skip("Roo Code search_and_replace Tool", function () { - setDefaultSuiteTimeout(this) - - let workspaceDir: string - - // Pre-created test files that will be used across tests - const testFiles = { - simpleReplace: { - name: `test-simple-replace-${Date.now()}.txt`, - content: "Hello World\nThis is a test file\nWith multiple lines\nHello again", - path: "", - }, - regexReplace: { - name: `test-regex-replace-${Date.now()}.js`, - content: `function oldFunction() { - console.log("old implementation") - return "old result" -} - -function anotherOldFunction() { - console.log("another old implementation") - return "another old result" -}`, - path: "", - }, - caseInsensitive: { - name: `test-case-insensitive-${Date.now()}.txt`, - content: `Hello World -HELLO UNIVERSE -hello everyone -HeLLo ThErE`, - path: "", - }, - multipleMatches: { - name: `test-multiple-matches-${Date.now()}.txt`, - content: `TODO: Fix this bug -This is some content -TODO: Add more tests -Some more content -TODO: Update documentation -Final content`, - path: "", - }, - noMatches: { - name: `test-no-matches-${Date.now()}.txt`, - content: "This file has no matching patterns\nJust regular content\nNothing special here", - path: "", - }, - } - - // Get the actual workspace directory that VSCode is using and create all test files - suiteSetup(async function () { - // Get the workspace folder from VSCode - const workspaceFolders = vscode.workspace.workspaceFolders - if (!workspaceFolders || workspaceFolders.length === 0) { - throw new Error("No workspace folder found") - } - workspaceDir = workspaceFolders[0]!.uri.fsPath - console.log("Using workspace directory:", workspaceDir) - - // Create all test files before any tests run - console.log("Creating test files in workspace...") - for (const [key, file] of Object.entries(testFiles)) { - file.path = path.join(workspaceDir, file.name) - await fs.writeFile(file.path, file.content) - console.log(`Created ${key} test file at:`, file.path) - } - - // Verify all files exist - for (const [key, file] of Object.entries(testFiles)) { - const exists = await fs - .access(file.path) - .then(() => true) - .catch(() => false) - if (!exists) { - throw new Error(`Failed to create ${key} test file at ${file.path}`) - } - } - }) - - // Clean up after all tests - suiteTeardown(async () => { - // Cancel any running tasks before cleanup - try { - await globalThis.api.cancelCurrentTask() - } catch { - // Task might not be running - } - - // Clean up all test files - console.log("Cleaning up test files...") - for (const [key, file] of Object.entries(testFiles)) { - try { - await fs.unlink(file.path) - console.log(`Cleaned up ${key} test file`) - } catch (error) { - console.log(`Failed to clean up ${key} test file:`, error) - } - } - }) - - // Clean up before each test - setup(async () => { - // Cancel any previous task - try { - await globalThis.api.cancelCurrentTask() - } catch { - // Task might not be running - } - - // Small delay to ensure clean state - await sleep(100) - }) - - // Clean up after each test - teardown(async () => { - // Cancel the current task - try { - await globalThis.api.cancelCurrentTask() - } catch { - // Task might not be running - } - - // Small delay to ensure clean state - await sleep(100) - }) - - test("Should perform simple text replacement", async function () { - const api = globalThis.api - const messages: ClineMessage[] = [] - const testFile = testFiles.simpleReplace - const expectedContent = "Hello Universe\nThis is a test file\nWith multiple lines\nHello again" - let taskStarted = false - let taskCompleted = false - let errorOccurred: string | null = null - let searchReplaceExecuted = false - - // Listen for messages - const messageHandler = ({ message }: { message: ClineMessage }) => { - messages.push(message) - - // Log important messages for debugging - if (message.type === "say" && message.say === "error") { - errorOccurred = message.text || "Unknown error" - console.error("Error:", message.text) - } - if (message.type === "ask" && message.ask === "tool") { - console.log("Tool request:", message.text?.substring(0, 200)) - } - if (message.type === "say" && (message.say === "completion_result" || message.say === "text")) { - console.log("AI response:", message.text?.substring(0, 200)) - } - - // Check for tool execution - if (message.type === "say" && message.say === "api_req_started" && message.text) { - console.log("API request started:", message.text.substring(0, 200)) - try { - const requestData = JSON.parse(message.text) - if (requestData.request && requestData.request.includes("search_and_replace")) { - searchReplaceExecuted = true - console.log("search_and_replace tool executed!") - } - } catch (e) { - console.log("Failed to parse api_req_started message:", e) - } - } - } - api.on(RooCodeEventName.Message, messageHandler) - - // Listen for task events - const taskStartedHandler = (id: string) => { - if (id === taskId) { - taskStarted = true - console.log("Task started:", id) - } - } - api.on(RooCodeEventName.TaskStarted, taskStartedHandler) - - const taskCompletedHandler = (id: string) => { - if (id === taskId) { - taskCompleted = true - console.log("Task completed:", id) - } - } - api.on(RooCodeEventName.TaskCompleted, taskCompletedHandler) - - let taskId: string - try { - // Start task with search_and_replace instruction - taskId = await api.startNewTask({ - configuration: { - mode: "code", - autoApprovalEnabled: true, - alwaysAllowWrite: true, - alwaysAllowReadOnly: true, - alwaysAllowReadOnlyOutsideWorkspace: true, - }, - text: `Use search_and_replace on the file ${testFile.name} to replace "Hello World" with "Hello Universe". - -The file is located at: ${testFile.path} - -The file already exists with this content: -${testFile.content} - -Assume the file exists and you can modify it directly.`, - }) - - console.log("Task ID:", taskId) - console.log("Test filename:", testFile.name) - - // Wait for task to start - await waitFor(() => taskStarted, { timeout: 45_000 }) - - // Check for early errors - if (errorOccurred) { - console.error("Early error detected:", errorOccurred) - } - - // Wait for task completion - await waitFor(() => taskCompleted, { timeout: 45_000 }) - - // Give extra time for file system operations - await sleep(2000) - - // Check if the file was modified correctly - const actualContent = await fs.readFile(testFile.path, "utf-8") - console.log("File content after modification:", actualContent) - - // Verify tool was executed - assert.strictEqual(searchReplaceExecuted, true, "search_and_replace tool should have been executed") - - // Verify file content - assert.strictEqual( - actualContent.trim(), - expectedContent.trim(), - "File content should be modified correctly", - ) - - console.log("Test passed! search_and_replace tool executed and file modified successfully") - } finally { - // Clean up - api.off(RooCodeEventName.Message, messageHandler) - api.off(RooCodeEventName.TaskStarted, taskStartedHandler) - api.off(RooCodeEventName.TaskCompleted, taskCompletedHandler) - } - }) - - test("Should perform regex pattern replacement", async function () { - const api = globalThis.api - const messages: ClineMessage[] = [] - const testFile = testFiles.regexReplace - const expectedContent = `function newFunction() { - console.log("new implementation") - return "new result" -} - -function anotherNewFunction() { - console.log("another new implementation") - return "another new result" -}` - let taskStarted = false - let taskCompleted = false - let errorOccurred: string | null = null - let searchReplaceExecuted = false - - // Listen for messages - const messageHandler = ({ message }: { message: ClineMessage }) => { - messages.push(message) - - // Log important messages for debugging - if (message.type === "say" && message.say === "error") { - errorOccurred = message.text || "Unknown error" - console.error("Error:", message.text) - } - if (message.type === "ask" && message.ask === "tool") { - console.log("Tool request:", message.text?.substring(0, 200)) - } - if (message.type === "say" && (message.say === "completion_result" || message.say === "text")) { - console.log("AI response:", message.text?.substring(0, 200)) - } - - // Check for tool execution - if (message.type === "say" && message.say === "api_req_started" && message.text) { - console.log("API request started:", message.text.substring(0, 200)) - try { - const requestData = JSON.parse(message.text) - if (requestData.request && requestData.request.includes("search_and_replace")) { - searchReplaceExecuted = true - console.log("search_and_replace tool executed!") - } - } catch (e) { - console.log("Failed to parse api_req_started message:", e) - } - } - } - api.on(RooCodeEventName.Message, messageHandler) - - // Listen for task events - const taskStartedHandler = (id: string) => { - if (id === taskId) { - taskStarted = true - console.log("Task started:", id) - } - } - api.on(RooCodeEventName.TaskStarted, taskStartedHandler) - - const taskCompletedHandler = (id: string) => { - if (id === taskId) { - taskCompleted = true - console.log("Task completed:", id) - } - } - api.on(RooCodeEventName.TaskCompleted, taskCompletedHandler) - - let taskId: string - try { - // Start task with search_and_replace instruction - simpler and more direct - taskId = await api.startNewTask({ - configuration: { - mode: "code", - autoApprovalEnabled: true, - alwaysAllowWrite: true, - alwaysAllowReadOnly: true, - alwaysAllowReadOnlyOutsideWorkspace: true, - }, - text: `Use search_and_replace on the file ${testFile.name} to: -1. First, replace "old" with "new" (use_regex: false) -2. Then, replace "Old" with "New" (use_regex: false) - -The file is located at: ${testFile.path} - -Assume the file exists and you can modify it directly. - -Use the search_and_replace tool twice - once for each replacement.`, - }) - - console.log("Task ID:", taskId) - console.log("Test filename:", testFile.name) - - // Wait for task to start - await waitFor(() => taskStarted, { timeout: 90_000 }) - - // Check for early errors - if (errorOccurred) { - console.error("Early error detected:", errorOccurred) - } - - // Wait for task completion - await waitFor(() => taskCompleted, { timeout: 90_000 }) - - // Give extra time for file system operations - await sleep(2000) - - // Check if the file was modified correctly - const actualContent = await fs.readFile(testFile.path, "utf-8") - console.log("File content after modification:", actualContent) - - // Verify tool was executed - assert.strictEqual(searchReplaceExecuted, true, "search_and_replace tool should have been executed") - - // Verify file content - assert.strictEqual( - actualContent.trim(), - expectedContent.trim(), - "File content should be modified with regex replacement", - ) - - console.log("Test passed! search_and_replace tool executed with regex successfully") - } finally { - // Clean up - api.off(RooCodeEventName.Message, messageHandler) - api.off(RooCodeEventName.TaskStarted, taskStartedHandler) - api.off(RooCodeEventName.TaskCompleted, taskCompletedHandler) - } - }) - - test("Should replace multiple matches in file", async function () { - const api = globalThis.api - const messages: ClineMessage[] = [] - const testFile = testFiles.multipleMatches - const expectedContent = `DONE: Fix this bug -This is some content -DONE: Add more tests -Some more content -DONE: Update documentation -Final content` - let taskStarted = false - let taskCompleted = false - let errorOccurred: string | null = null - let searchReplaceExecuted = false - - // Listen for messages - const messageHandler = ({ message }: { message: ClineMessage }) => { - messages.push(message) - - // Log important messages for debugging - if (message.type === "say" && message.say === "error") { - errorOccurred = message.text || "Unknown error" - console.error("Error:", message.text) - } - if (message.type === "ask" && message.ask === "tool") { - console.log("Tool request:", message.text?.substring(0, 200)) - } - if (message.type === "say" && (message.say === "completion_result" || message.say === "text")) { - console.log("AI response:", message.text?.substring(0, 200)) - } - - // Check for tool execution - if (message.type === "say" && message.say === "api_req_started" && message.text) { - console.log("API request started:", message.text.substring(0, 200)) - try { - const requestData = JSON.parse(message.text) - if (requestData.request && requestData.request.includes("search_and_replace")) { - searchReplaceExecuted = true - console.log("search_and_replace tool executed!") - } - } catch (e) { - console.log("Failed to parse api_req_started message:", e) - } - } - } - api.on(RooCodeEventName.Message, messageHandler) - - // Listen for task events - const taskStartedHandler = (id: string) => { - if (id === taskId) { - taskStarted = true - console.log("Task started:", id) - } - } - api.on(RooCodeEventName.TaskStarted, taskStartedHandler) - - const taskCompletedHandler = (id: string) => { - if (id === taskId) { - taskCompleted = true - console.log("Task completed:", id) - } - } - api.on(RooCodeEventName.TaskCompleted, taskCompletedHandler) - - let taskId: string - try { - // Start task with search_and_replace instruction for multiple matches - taskId = await api.startNewTask({ - configuration: { - mode: "code", - autoApprovalEnabled: true, - alwaysAllowWrite: true, - alwaysAllowReadOnly: true, - alwaysAllowReadOnlyOutsideWorkspace: true, - }, - text: `Use search_and_replace on the file ${testFile.name} to replace all occurrences of "TODO" with "DONE". - -The file is located at: ${testFile.path} - -The file already exists with this content: -${testFile.content} - -Assume the file exists and you can modify it directly.`, - }) - - console.log("Task ID:", taskId) - console.log("Test filename:", testFile.name) - - // Wait for task to start - await waitFor(() => taskStarted, { timeout: 45_000 }) - - // Check for early errors - if (errorOccurred) { - console.error("Early error detected:", errorOccurred) - } - - // Wait for task completion - await waitFor(() => taskCompleted, { timeout: 45_000 }) - - // Give extra time for file system operations - await sleep(2000) - - // Check if the file was modified correctly - const actualContent = await fs.readFile(testFile.path, "utf-8") - console.log("File content after modification:", actualContent) - - // Verify tool was executed - assert.strictEqual(searchReplaceExecuted, true, "search_and_replace tool should have been executed") - - // Verify file content - assert.strictEqual( - actualContent.trim(), - expectedContent.trim(), - "All TODO occurrences should be replaced with DONE", - ) - - console.log("Test passed! search_and_replace tool executed and replaced multiple matches successfully") - } finally { - // Clean up - api.off(RooCodeEventName.Message, messageHandler) - api.off(RooCodeEventName.TaskStarted, taskStartedHandler) - api.off(RooCodeEventName.TaskCompleted, taskCompletedHandler) - } - }) - - test("Should handle case when no matches are found", async function () { - const api = globalThis.api - const messages: ClineMessage[] = [] - const testFile = testFiles.noMatches - const expectedContent = testFile.content // Should remain unchanged - let taskStarted = false - let taskCompleted = false - let errorOccurred: string | null = null - let searchReplaceExecuted = false - - // Listen for messages - const messageHandler = ({ message }: { message: ClineMessage }) => { - messages.push(message) - - // Log important messages for debugging - if (message.type === "say" && message.say === "error") { - errorOccurred = message.text || "Unknown error" - console.error("Error:", message.text) - } - if (message.type === "ask" && message.ask === "tool") { - console.log("Tool request:", message.text?.substring(0, 200)) - } - if (message.type === "say" && (message.say === "completion_result" || message.say === "text")) { - console.log("AI response:", message.text?.substring(0, 200)) - } - - // Check for tool execution - if (message.type === "say" && message.say === "api_req_started" && message.text) { - console.log("API request started:", message.text.substring(0, 200)) - try { - const requestData = JSON.parse(message.text) - if (requestData.request && requestData.request.includes("search_and_replace")) { - searchReplaceExecuted = true - console.log("search_and_replace tool executed!") - } - } catch (e) { - console.log("Failed to parse api_req_started message:", e) - } - } - } - api.on(RooCodeEventName.Message, messageHandler) - - // Listen for task events - const taskStartedHandler = (id: string) => { - if (id === taskId) { - taskStarted = true - console.log("Task started:", id) - } - } - api.on(RooCodeEventName.TaskStarted, taskStartedHandler) - - const taskCompletedHandler = (id: string) => { - if (id === taskId) { - taskCompleted = true - console.log("Task completed:", id) - } - } - api.on(RooCodeEventName.TaskCompleted, taskCompletedHandler) - - let taskId: string - try { - // Start task with search_and_replace instruction for pattern that won't match - taskId = await api.startNewTask({ - configuration: { - mode: "code", - autoApprovalEnabled: true, - alwaysAllowWrite: true, - alwaysAllowReadOnly: true, - alwaysAllowReadOnlyOutsideWorkspace: true, - }, - text: `Use search_and_replace on the file ${testFile.name} to replace "NONEXISTENT_PATTERN" with "REPLACEMENT". This pattern should not be found in the file. - -The file is located at: ${testFile.path} - -The file already exists with this content: -${testFile.content} - -Assume the file exists and you can modify it directly.`, - }) - - console.log("Task ID:", taskId) - console.log("Test filename:", testFile.name) - - // Wait for task to start - await waitFor(() => taskStarted, { timeout: 45_000 }) - - // Check for early errors - if (errorOccurred) { - console.error("Early error detected:", errorOccurred) - } - - // Wait for task completion - await waitFor(() => taskCompleted, { timeout: 45_000 }) - - // Give extra time for file system operations - await sleep(2000) - - // Check if the file remains unchanged - const actualContent = await fs.readFile(testFile.path, "utf-8") - console.log("File content after search (should be unchanged):", actualContent) - - // Verify tool was executed - assert.strictEqual(searchReplaceExecuted, true, "search_and_replace tool should have been executed") - - // Verify file content remains unchanged - assert.strictEqual( - actualContent.trim(), - expectedContent.trim(), - "File content should remain unchanged when no matches are found", - ) - - console.log("Test passed! search_and_replace tool executed and handled no matches correctly") - } finally { - // Clean up - api.off(RooCodeEventName.Message, messageHandler) - api.off(RooCodeEventName.TaskStarted, taskStartedHandler) - api.off(RooCodeEventName.TaskCompleted, taskCompletedHandler) - } - }) -}) diff --git a/locales/es/README.md b/locales/es/README.md index 3a5b442c718a..b516cb370749 100644 --- a/locales/es/README.md +++ b/locales/es/README.md @@ -86,10 +86,10 @@ code --install-extension bin/zgsm-.vsix ```sh pnpm vsix ``` -2. Se generará un archivo `.vsix` en el directorio `bin/` (p. ej., `bin/roo-cline-.vsix`). +2. Se generará un archivo `.vsix` en el directorio `bin/` (p. ej., `bin/zgsm-.vsix`). 3. Instálalo manualmente usando la CLI de VSCode: ```sh - code --install-extension bin/roo-cline-.vsix + code --install-extension bin/zgsm-.vsix ``` --- diff --git a/locales/zh-CN/README.md b/locales/zh-CN/README.md index cd40b3a7d474..dd7ccda08113 100644 --- a/locales/zh-CN/README.md +++ b/locales/zh-CN/README.md @@ -86,10 +86,10 @@ code --install-extension bin/zgsm-.vsix ```sh pnpm vsix ``` -2. 将在 `bin/` 目录中生成一个 `.vsix` 文件(例如,`bin/roo-cline-.vsix`)。 +2. 将在 `bin/` 目录中生成一个 `.vsix` 文件(例如,`bin/zgsm-.vsix`)。 3. 使用 VSCode CLI 手动安装 ```sh - code --install-extension bin/roo-cline-.vsix + code --install-extension bin/zgsm-.vsix ``` --- diff --git a/locales/zh-TW/README.md b/locales/zh-TW/README.md index 8e8d54b2dfcc..71a334e6de78 100644 --- a/locales/zh-TW/README.md +++ b/locales/zh-TW/README.md @@ -86,10 +86,10 @@ code --install-extension bin/zgsm-.vsix ```sh pnpm vsix ``` -2. 將在 `bin/` 目錄中產生一個 `.vsix` 檔案(例如 `bin/roo-cline-.vsix`)。 +2. 將在 `bin/` 目錄中產生一個 `.vsix` 檔案(例如 `bin/zgsm-.vsix`)。 3. 使用 VSCode CLI 手動安裝 ```sh - code --install-extension bin/roo-cline-.vsix + code --install-extension bin/zgsm-.vsix ``` --- diff --git a/packages/cloud/src/WebAuthService.ts b/packages/cloud/src/WebAuthService.ts index 6e9c76b46329..58f0e1e70c14 100644 --- a/packages/cloud/src/WebAuthService.ts +++ b/packages/cloud/src/WebAuthService.ts @@ -265,8 +265,8 @@ export class WebAuthService extends EventEmitter implements A const state = crypto.randomBytes(16).toString("hex") await this.context.globalState.update(AUTH_STATE_KEY, state) const packageJSON = this.context.extension?.packageJSON - const publisher = packageJSON?.publisher ?? "RooVeterinaryInc" - const name = packageJSON?.name ?? "roo-cline" + const publisher = packageJSON?.publisher ?? "zgsm-ai" + const name = packageJSON?.name ?? "zgsm" const params = new URLSearchParams({ state, auth_redirect: `${vscode.env.uriScheme}://${publisher}.${name}`, diff --git a/packages/cloud/src/__mocks__/vscode.ts b/packages/cloud/src/__mocks__/vscode.ts index 52585437863f..dbe3043f613d 100644 --- a/packages/cloud/src/__mocks__/vscode.ts +++ b/packages/cloud/src/__mocks__/vscode.ts @@ -56,8 +56,8 @@ export const mockExtensionContext: ExtensionContext = { extension: { packageJSON: { version: "1.0.0", - publisher: "RooVeterinaryInc", - name: "roo-cline", + publisher: "zgsm-ai", + name: "zgsm", }, }, } diff --git a/packages/cloud/src/__tests__/WebAuthService.spec.ts b/packages/cloud/src/__tests__/WebAuthService.spec.ts index fc6bfa90e82c..8739b3d20148 100644 --- a/packages/cloud/src/__tests__/WebAuthService.spec.ts +++ b/packages/cloud/src/__tests__/WebAuthService.spec.ts @@ -84,8 +84,8 @@ describe("WebAuthService", () => { extension: { packageJSON: { version: "1.0.0", - publisher: "RooVeterinaryInc", - name: "roo-cline", + publisher: "zgsm-ai", + name: "zgsm", }, }, } @@ -269,7 +269,7 @@ describe("WebAuthService", () => { await authService.login() const expectedUrl = - "https://api.test.com/extension/sign-in?state=746573742d72616e646f6d2d6279746573&auth_redirect=vscode%3A%2F%2FRooVeterinaryInc.roo-cline" + "https://api.test.com/extension/sign-in?state=746573742d72616e646f6d2d6279746573&auth_redirect=vscode%3A%2F%2Fzgsm-ai.zgsm" expect(mockOpenExternal).toHaveBeenCalledWith( expect.objectContaining({ toString: expect.any(Function), diff --git a/packages/types/src/providers/cerebras.ts b/packages/types/src/providers/cerebras.ts index 4765302a4e62..d578db0ddc20 100644 --- a/packages/types/src/providers/cerebras.ts +++ b/packages/types/src/providers/cerebras.ts @@ -3,9 +3,18 @@ import type { ModelInfo } from "../model.js" // https://inference-docs.cerebras.ai/api-reference/chat-completions export type CerebrasModelId = keyof typeof cerebrasModels -export const cerebrasDefaultModelId: CerebrasModelId = "qwen-3-coder-480b-free" +export const cerebrasDefaultModelId: CerebrasModelId = "gpt-oss-120b" export const cerebrasModels = { + "zai-glm-4.6": { + maxTokens: 16_384, + contextWindow: 128000, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "Highly intelligent general-purpose model with ~2000 tokens/s", + }, "qwen-3-coder-480b-free": { maxTokens: 40000, contextWindow: 64000, @@ -14,7 +23,7 @@ export const cerebrasModels = { inputPrice: 0, outputPrice: 0, description: - "SOTA coding model with ~2000 tokens/s ($0 free tier)\n\n• Use this if you don't have a Cerebras subscription\n• 64K context window\n• Rate limits: 150K TPM, 1M TPH/TPD, 10 RPM, 100 RPH/RPD\n\nUpgrade for higher limits: [https://cloud.cerebras.ai/?utm=roocode](https://cloud.cerebras.ai/?utm=roocode)", + "[SOON TO BE DEPRECATED] SOTA coding model with ~2000 tokens/s ($0 free tier)\n\n• Use this if you don't have a Cerebras subscription\n• 64K context window\n• Rate limits: 150K TPM, 1M TPH/TPD, 10 RPM, 100 RPH/RPD\n\nUpgrade for higher limits: [https://cloud.cerebras.ai/?utm=roocode](https://cloud.cerebras.ai/?utm=roocode)", }, "qwen-3-coder-480b": { maxTokens: 40000, @@ -24,7 +33,7 @@ export const cerebrasModels = { inputPrice: 0, outputPrice: 0, description: - "SOTA coding model with ~2000 tokens/s ($50/$250 paid tiers)\n\n• Use this if you have a Cerebras subscription\n• 131K context window with higher rate limits", + "[SOON TO BE DEPRECATED] SOTA coding model with ~2000 tokens/s ($50/$250 paid tiers)\n\n• Use this if you have a Cerebras subscription\n• 131K context window with higher rate limits", }, "qwen-3-235b-a22b-instruct-2507": { maxTokens: 64000, diff --git a/packages/types/src/tool.ts b/packages/types/src/tool.ts index d33273d1e116..9ed9a8e7769a 100644 --- a/packages/types/src/tool.ts +++ b/packages/types/src/tool.ts @@ -20,7 +20,6 @@ export const toolNames = [ "write_to_file", "apply_diff", "insert_content", - "search_and_replace", "search_files", "list_files", "list_code_definition_names", diff --git a/src/api/providers/__tests__/cerebras.spec.ts b/src/api/providers/__tests__/cerebras.spec.ts index feac707ecfc1..5d7084a8d4ac 100644 --- a/src/api/providers/__tests__/cerebras.spec.ts +++ b/src/api/providers/__tests__/cerebras.spec.ts @@ -56,7 +56,7 @@ describe("CerebrasHandler", () => { it("should fallback to default model when apiModelId is not provided", () => { const handlerWithoutModel = new CerebrasHandler({ cerebrasApiKey: "test" }) const { id } = handlerWithoutModel.getModel() - expect(id).toBe("qwen-3-coder-480b") // cerebrasDefaultModelId (routed) + expect(id).toBe("gpt-oss-120b") // cerebrasDefaultModelId }) }) diff --git a/src/api/providers/__tests__/roo.spec.ts b/src/api/providers/__tests__/roo.spec.ts index cd1ab3330104..7555a49d498e 100644 --- a/src/api/providers/__tests__/roo.spec.ts +++ b/src/api/providers/__tests__/roo.spec.ts @@ -182,6 +182,21 @@ describe("RooHandler", () => { handler = new RooHandler(mockOptions) }) + it("should update API key before making request", async () => { + // Set up a fresh token that will be returned when createMessage is called + const freshToken = "fresh-session-token" + mockGetSessionTokenFn.mockReturnValue(freshToken) + + const stream = handler.createMessage(systemPrompt, messages) + // Consume the stream to trigger the API call + for await (const _chunk of stream) { + // Just consume + } + + // Verify getSessionToken was called to get the fresh token + expect(mockGetSessionTokenFn).toHaveBeenCalled() + }) + it("should handle streaming responses", async () => { const stream = handler.createMessage(systemPrompt, messages) const chunks: any[] = [] @@ -290,6 +305,25 @@ describe("RooHandler", () => { }) }) + it("should update API key before making request", async () => { + // Set up a fresh token that will be returned when completePrompt is called + const freshToken = "fresh-session-token" + mockGetSessionTokenFn.mockReturnValue(freshToken) + + // Access the client's apiKey property to verify it gets updated + const clientApiKeyGetter = vitest.fn() + Object.defineProperty(handler["client"], "apiKey", { + get: clientApiKeyGetter, + set: vitest.fn(), + configurable: true, + }) + + await handler.completePrompt("Test prompt") + + // Verify getSessionToken was called to get the fresh token + expect(mockGetSessionTokenFn).toHaveBeenCalled() + }) + it("should handle API errors", async () => { mockCreate.mockRejectedValueOnce(new Error("API Error")) await expect(handler.completePrompt("Test prompt")).rejects.toThrow( diff --git a/src/api/providers/roo.ts b/src/api/providers/roo.ts index 8af44dba610e..17e883893c1c 100644 --- a/src/api/providers/roo.ts +++ b/src/api/providers/roo.ts @@ -1,7 +1,7 @@ import { Anthropic } from "@anthropic-ai/sdk" import OpenAI from "openai" -import { AuthState, rooDefaultModelId, type ModelInfo } from "@roo-code/types" +import { rooDefaultModelId } from "@roo-code/types" import { CloudService } from "@roo-code/cloud" import type { ApiHandlerOptions, ModelRecord } from "../../shared/api" @@ -12,9 +12,8 @@ import type { RooReasoningParams } from "../transform/reasoning" import { getRooReasoning } from "../transform/reasoning" import type { ApiHandlerCreateMessageMetadata } from "../index" -import { DEFAULT_HEADERS } from "./constants" import { BaseOpenAiCompatibleProvider } from "./base-openai-compatible-provider" -import { getModels, flushModels, getModelsFromCache } from "../providers/fetchers/modelCache" +import { getModels, getModelsFromCache } from "../providers/fetchers/modelCache" import { handleOpenAIError } from "./utils/openai-error-handler" // Extend OpenAI's CompletionUsage to include Roo specific fields @@ -28,16 +27,16 @@ type RooChatCompletionParams = OpenAI.Chat.ChatCompletionCreateParamsStreaming & reasoning?: RooReasoningParams } +function getSessionToken(): string { + const token = CloudService.hasInstance() ? CloudService.instance.authService?.getSessionToken() : undefined + return token ?? "unauthenticated" +} + export class RooHandler extends BaseOpenAiCompatibleProvider { - private authStateListener?: (state: { state: AuthState }) => void private fetcherBaseURL: string constructor(options: ApiHandlerOptions) { - let sessionToken: string | undefined = undefined - - if (CloudService.hasInstance()) { - sessionToken = CloudService.instance.authService?.getSessionToken() - } + const sessionToken = getSessionToken() let baseURL = process.env.ROO_CODE_PROVIDER_URL ?? "https://api.roocode.com/proxy" @@ -52,7 +51,7 @@ export class RooHandler extends BaseOpenAiCompatibleProvider { ...options, providerName: "Roo Code Cloud", baseURL, // Already has /v1 suffix - apiKey: sessionToken || "unauthenticated", // Use a placeholder if no token. + apiKey: sessionToken, defaultProviderModelId: rooDefaultModelId, providerModels: {}, defaultTemperature: 0.7, @@ -63,29 +62,6 @@ export class RooHandler extends BaseOpenAiCompatibleProvider { this.loadDynamicModels(this.fetcherBaseURL, sessionToken).catch((error) => { console.error("[RooHandler] Failed to load dynamic models:", error) }) - - if (CloudService.hasInstance()) { - const cloudService = CloudService.instance - - this.authStateListener = (state: { state: AuthState }) => { - // Update OpenAI client with current auth token - // Note: Model cache flush/reload is handled by extension.ts authStateChangedHandler - const newToken = cloudService.authService?.getSessionToken() - this.client = new OpenAI({ - baseURL: this.baseURL, - apiKey: newToken ?? "unauthenticated", - defaultHeaders: DEFAULT_HEADERS, - }) - } - - cloudService.on("auth-state-changed", this.authStateListener) - } - } - - dispose() { - if (this.authStateListener && CloudService.hasInstance()) { - CloudService.instance.off("auth-state-changed", this.authStateListener) - } } protected override createStream( @@ -127,6 +103,7 @@ export class RooHandler extends BaseOpenAiCompatibleProvider { } try { + this.client.apiKey = getSessionToken() return this.client.chat.completions.create(rooParams, requestOptions) } catch (error) { throw handleOpenAIError(error, this.providerName) @@ -195,6 +172,11 @@ export class RooHandler extends BaseOpenAiCompatibleProvider { } } } + override async completePrompt(prompt: string): Promise { + // Update API key before making request to ensure we use the latest session token + this.client.apiKey = getSessionToken() + return super.completePrompt(prompt) + } private async loadDynamicModels(baseURL: string, apiKey?: string): Promise { try { diff --git a/src/core/assistant-message/presentAssistantMessage.ts b/src/core/assistant-message/presentAssistantMessage.ts index cea077bac406..96b35411a30a 100644 --- a/src/core/assistant-message/presentAssistantMessage.ts +++ b/src/core/assistant-message/presentAssistantMessage.ts @@ -15,7 +15,6 @@ import { shouldUseSingleFileRead } from "@roo-code/types" import { writeToFileTool } from "../tools/writeToFileTool" import { applyDiffTool } from "../tools/multiApplyDiffTool" import { insertContentTool } from "../tools/insertContentTool" -import { searchAndReplaceTool } from "../tools/searchAndReplaceTool" import { listCodeDefinitionNamesTool } from "../tools/listCodeDefinitionNamesTool" import { searchFilesTool } from "../tools/searchFilesTool" import { browserActionTool } from "../tools/browserActionTool" @@ -196,8 +195,6 @@ export async function presentAssistantMessage(cline: Task) { }]` case "insert_content": return `[${block.name} for '${block.params.path}']` - case "search_and_replace": - return `[${block.name} for '${block.params.path}']` case "list_files": return `[${block.name} for '${block.params.path}']` case "list_code_definition_names": @@ -261,7 +258,7 @@ export async function presentAssistantMessage(cline: Task) { const pushToolResult = (content: ToolResponse) => { cline.userMessageContent.push({ type: "text", text: `${toolDescription()} Result:` }) - if (["write_to_file", "apply_diff", "insert_content", "search_and_replace"].includes(block.name) && block.partial === false) { + if (["write_to_file", "apply_diff", "insert_content"].includes(block.name) && block.partial === false) { updateCospecMetadata(cline, block?.params?.path) } if (typeof content === "string") { @@ -466,10 +463,6 @@ export async function presentAssistantMessage(cline: Task) { await checkpointSaveAndMark(cline) await insertContentTool(cline, block, askApproval, handleError, pushToolResult, removeClosingTag) break - case "search_and_replace": - await checkpointSaveAndMark(cline) - await searchAndReplaceTool(cline, block, askApproval, handleError, pushToolResult, removeClosingTag) - break case "read_file": // Check if this model should use the simplified single-file read tool const modelId = cline.api.getModel().id diff --git a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/architect-mode-prompt.snap b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/architect-mode-prompt.snap index 26047327c0a5..a8fecc743766 100644 --- a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/architect-mode-prompt.snap +++ b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/architect-mode-prompt.snap @@ -226,42 +226,6 @@ Example for appending to the end of file: -## search_and_replace -Description: Use this tool to find and replace specific text strings or patterns (using regex) within a file. It's suitable for targeted replacements across multiple locations within the file. Supports literal text and regex patterns, case sensitivity options, and optional line ranges. Shows a diff preview before applying changes. - -Required Parameters: -- path: The path of the file to modify (relative to the current workspace directory /test/path) -- search: The text or pattern to search for -- replace: The text to replace matches with - -Optional Parameters: -- start_line: Starting line number for restricted replacement (1-based) -- end_line: Ending line number for restricted replacement (1-based) -- use_regex: Set to "true" to treat search as a regex pattern (default: false) -- ignore_case: Set to "true" to ignore case when matching (default: false) - -Notes: -- When use_regex is true, the search parameter is treated as a regular expression pattern -- When ignore_case is true, the search is case-insensitive regardless of regex mode - -Examples: - -1. Simple text replacement: - -example.ts -oldText -newText - - -2. Case-insensitive regex pattern: - -example.ts -oldw+ -new$& -true -true - - ## ask_followup_question Description: Ask the user a question to gather additional information needed to complete the task. Use when you need clarification or more details to proceed effectively. @@ -465,9 +429,8 @@ RULES - Before using the execute_command tool, you must first think about the SYSTEM INFORMATION context provided to understand the user's environment and tailor your commands to ensure they are compatible with their system. You must also consider if the command you need to run should be executed in a specific directory outside of the current working directory '/test/path', and if so prepend with `cd`'ing into that directory && then executing the command (as one command since you are stuck operating from '/test/path'). For example, if you needed to run `npm install` in a project outside of '/test/path', you would need to prepend with a `cd` i.e. pseudocode for this would be `cd (path to project) && (command, in this case npm install)`. - When using the search_files tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the search_files tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches before using write_to_file to make informed changes. - When creating a new project (such as an app, website, or any software project), organize all new files within a dedicated project directory unless the user specifies otherwise. Use appropriate file paths when writing files, as the write_to_file tool will automatically create any necessary directories. Structure the project logically, adhering to best practices for the specific type of project being created. Unless otherwise specified, new projects should be easily run without additional setup, for example most projects can be built in HTML, CSS, and JavaScript - which you can open in a browser. -- For editing files, you have access to these tools: write_to_file (for creating new files or complete file rewrites), insert_content (for adding lines to files), search_and_replace (for finding and replacing individual pieces of text). +- For editing files, you have access to these tools: write_to_file (for creating new files or complete file rewrites), insert_content (for adding lines to files). - The insert_content tool adds lines of text to files at a specific line number, such as adding a new function to a JavaScript file or inserting a new route in a Python file. Use line number 0 to append at the end of the file, or any positive number to insert before that line. -- The search_and_replace tool finds and replaces text or regex in files. This tool allows you to search for a specific regex pattern or text and replace it with another value. Be cautious when using this tool to ensure you are replacing the correct text. It can support multiple operations at once. - You should always prefer using other editing tools over write_to_file when making changes to existing files since write_to_file is much slower and cannot handle large files. - When using the write_to_file tool to modify a file, use the tool directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code, severely impacting the user's project. - Some modes have restrictions on which files they can edit. If you attempt to edit a restricted file, the operation will be rejected with a FileRestrictionError that will specify which file patterns are allowed for the current mode. diff --git a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/ask-mode-prompt.snap b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/ask-mode-prompt.snap index 1aecba94c585..fac450cf5640 100644 --- a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/ask-mode-prompt.snap +++ b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/ask-mode-prompt.snap @@ -362,9 +362,8 @@ RULES - Before using the execute_command tool, you must first think about the SYSTEM INFORMATION context provided to understand the user's environment and tailor your commands to ensure they are compatible with their system. You must also consider if the command you need to run should be executed in a specific directory outside of the current working directory '/test/path', and if so prepend with `cd`'ing into that directory && then executing the command (as one command since you are stuck operating from '/test/path'). For example, if you needed to run `npm install` in a project outside of '/test/path', you would need to prepend with a `cd` i.e. pseudocode for this would be `cd (path to project) && (command, in this case npm install)`. - When using the search_files tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the search_files tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches before using write_to_file to make informed changes. - When creating a new project (such as an app, website, or any software project), organize all new files within a dedicated project directory unless the user specifies otherwise. Use appropriate file paths when writing files, as the write_to_file tool will automatically create any necessary directories. Structure the project logically, adhering to best practices for the specific type of project being created. Unless otherwise specified, new projects should be easily run without additional setup, for example most projects can be built in HTML, CSS, and JavaScript - which you can open in a browser. -- For editing files, you have access to these tools: write_to_file (for creating new files or complete file rewrites), insert_content (for adding lines to files), search_and_replace (for finding and replacing individual pieces of text). +- For editing files, you have access to these tools: write_to_file (for creating new files or complete file rewrites), insert_content (for adding lines to files). - The insert_content tool adds lines of text to files at a specific line number, such as adding a new function to a JavaScript file or inserting a new route in a Python file. Use line number 0 to append at the end of the file, or any positive number to insert before that line. -- The search_and_replace tool finds and replaces text or regex in files. This tool allows you to search for a specific regex pattern or text and replace it with another value. Be cautious when using this tool to ensure you are replacing the correct text. It can support multiple operations at once. - You should always prefer using other editing tools over write_to_file when making changes to existing files since write_to_file is much slower and cannot handle large files. - When using the write_to_file tool to modify a file, use the tool directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code, severely impacting the user's project. - Some modes have restrictions on which files they can edit. If you attempt to edit a restricted file, the operation will be rejected with a FileRestrictionError that will specify which file patterns are allowed for the current mode. diff --git a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-disabled.snap b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-disabled.snap index 24f06457a065..1e3f527b3ac0 100644 --- a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-disabled.snap +++ b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-disabled.snap @@ -225,42 +225,6 @@ Example for appending to the end of file: -## search_and_replace -Description: Use this tool to find and replace specific text strings or patterns (using regex) within a file. It's suitable for targeted replacements across multiple locations within the file. Supports literal text and regex patterns, case sensitivity options, and optional line ranges. Shows a diff preview before applying changes. - -Required Parameters: -- path: The path of the file to modify (relative to the current workspace directory /test/path) -- search: The text or pattern to search for -- replace: The text to replace matches with - -Optional Parameters: -- start_line: Starting line number for restricted replacement (1-based) -- end_line: Ending line number for restricted replacement (1-based) -- use_regex: Set to "true" to treat search as a regex pattern (default: false) -- ignore_case: Set to "true" to ignore case when matching (default: false) - -Notes: -- When use_regex is true, the search parameter is treated as a regular expression pattern -- When ignore_case is true, the search is case-insensitive regardless of regex mode - -Examples: - -1. Simple text replacement: - -example.ts -oldText -newText - - -2. Case-insensitive regex pattern: - -example.ts -oldw+ -new$& -true -true - - ## execute_command Description: Request to execute a CLI command on the system. Use this when you need to perform system operations or run specific commands to accomplish any step in the user's task. You must tailor your command to the user's system and provide a clear explanation of what the command does. For command chaining, use the appropriate chaining syntax for the user's shell. Prefer to execute complex CLI commands over creating executable scripts, as they are more flexible and easier to run. Prefer relative commands and paths that avoid location sensitivity for terminal consistency, e.g: `touch ./testdata/example.file`, `dir ./examples/model1/data/yaml`, or `go test ./cmd/front --config ./cmd/front/config.yml`. If directed by the user, you may open a terminal in a different directory by using the `cwd` parameter. Parameters: @@ -508,9 +472,8 @@ RULES - Before using the execute_command tool, you must first think about the SYSTEM INFORMATION context provided to understand the user's environment and tailor your commands to ensure they are compatible with their system. You must also consider if the command you need to run should be executed in a specific directory outside of the current working directory '/test/path', and if so prepend with `cd`'ing into that directory && then executing the command (as one command since you are stuck operating from '/test/path'). For example, if you needed to run `npm install` in a project outside of '/test/path', you would need to prepend with a `cd` i.e. pseudocode for this would be `cd (path to project) && (command, in this case npm install)`. - When using the search_files tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the search_files tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches before using write_to_file to make informed changes. - When creating a new project (such as an app, website, or any software project), organize all new files within a dedicated project directory unless the user specifies otherwise. Use appropriate file paths when writing files, as the write_to_file tool will automatically create any necessary directories. Structure the project logically, adhering to best practices for the specific type of project being created. Unless otherwise specified, new projects should be easily run without additional setup, for example most projects can be built in HTML, CSS, and JavaScript - which you can open in a browser. -- For editing files, you have access to these tools: write_to_file (for creating new files or complete file rewrites), insert_content (for adding lines to files), search_and_replace (for finding and replacing individual pieces of text). +- For editing files, you have access to these tools: write_to_file (for creating new files or complete file rewrites), insert_content (for adding lines to files). - The insert_content tool adds lines of text to files at a specific line number, such as adding a new function to a JavaScript file or inserting a new route in a Python file. Use line number 0 to append at the end of the file, or any positive number to insert before that line. -- The search_and_replace tool finds and replaces text or regex in files. This tool allows you to search for a specific regex pattern or text and replace it with another value. Be cautious when using this tool to ensure you are replacing the correct text. It can support multiple operations at once. - You should always prefer using other editing tools over write_to_file when making changes to existing files since write_to_file is much slower and cannot handle large files. - When using the write_to_file tool to modify a file, use the tool directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code, severely impacting the user's project. - Some modes have restrictions on which files they can edit. If you attempt to edit a restricted file, the operation will be rejected with a FileRestrictionError that will specify which file patterns are allowed for the current mode. diff --git a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-enabled.snap b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-enabled.snap index 6ad6c19242d4..bf00761163a2 100644 --- a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-enabled.snap +++ b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-enabled.snap @@ -226,42 +226,6 @@ Example for appending to the end of file: -## search_and_replace -Description: Use this tool to find and replace specific text strings or patterns (using regex) within a file. It's suitable for targeted replacements across multiple locations within the file. Supports literal text and regex patterns, case sensitivity options, and optional line ranges. Shows a diff preview before applying changes. - -Required Parameters: -- path: The path of the file to modify (relative to the current workspace directory /test/path) -- search: The text or pattern to search for -- replace: The text to replace matches with - -Optional Parameters: -- start_line: Starting line number for restricted replacement (1-based) -- end_line: Ending line number for restricted replacement (1-based) -- use_regex: Set to "true" to treat search as a regex pattern (default: false) -- ignore_case: Set to "true" to ignore case when matching (default: false) - -Notes: -- When use_regex is true, the search parameter is treated as a regular expression pattern -- When ignore_case is true, the search is case-insensitive regardless of regex mode - -Examples: - -1. Simple text replacement: - -example.ts -oldText -newText - - -2. Case-insensitive regex pattern: - -example.ts -oldw+ -new$& -true -true - - ## execute_command Description: Request to execute a CLI command on the system. Use this when you need to perform system operations or run specific commands to accomplish any step in the user's task. You must tailor your command to the user's system and provide a clear explanation of what the command does. For command chaining, use the appropriate chaining syntax for the user's shell. Prefer to execute complex CLI commands over creating executable scripts, as they are more flexible and easier to run. Prefer relative commands and paths that avoid location sensitivity for terminal consistency, e.g: `touch ./testdata/example.file`, `dir ./examples/model1/data/yaml`, or `go test ./cmd/front --config ./cmd/front/config.yml`. If directed by the user, you may open a terminal in a different directory by using the `cwd` parameter. Parameters: @@ -577,9 +541,8 @@ RULES - Before using the execute_command tool, you must first think about the SYSTEM INFORMATION context provided to understand the user's environment and tailor your commands to ensure they are compatible with their system. You must also consider if the command you need to run should be executed in a specific directory outside of the current working directory '/test/path', and if so prepend with `cd`'ing into that directory && then executing the command (as one command since you are stuck operating from '/test/path'). For example, if you needed to run `npm install` in a project outside of '/test/path', you would need to prepend with a `cd` i.e. pseudocode for this would be `cd (path to project) && (command, in this case npm install)`. - When using the search_files tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the search_files tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches before using write_to_file to make informed changes. - When creating a new project (such as an app, website, or any software project), organize all new files within a dedicated project directory unless the user specifies otherwise. Use appropriate file paths when writing files, as the write_to_file tool will automatically create any necessary directories. Structure the project logically, adhering to best practices for the specific type of project being created. Unless otherwise specified, new projects should be easily run without additional setup, for example most projects can be built in HTML, CSS, and JavaScript - which you can open in a browser. -- For editing files, you have access to these tools: write_to_file (for creating new files or complete file rewrites), insert_content (for adding lines to files), search_and_replace (for finding and replacing individual pieces of text). +- For editing files, you have access to these tools: write_to_file (for creating new files or complete file rewrites), insert_content (for adding lines to files). - The insert_content tool adds lines of text to files at a specific line number, such as adding a new function to a JavaScript file or inserting a new route in a Python file. Use line number 0 to append at the end of the file, or any positive number to insert before that line. -- The search_and_replace tool finds and replaces text or regex in files. This tool allows you to search for a specific regex pattern or text and replace it with another value. Be cautious when using this tool to ensure you are replacing the correct text. It can support multiple operations at once. - You should always prefer using other editing tools over write_to_file when making changes to existing files since write_to_file is much slower and cannot handle large files. - When using the write_to_file tool to modify a file, use the tool directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code, severely impacting the user's project. - Some modes have restrictions on which files they can edit. If you attempt to edit a restricted file, the operation will be rejected with a FileRestrictionError that will specify which file patterns are allowed for the current mode. diff --git a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/partial-reads-enabled.snap b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/partial-reads-enabled.snap index 2e570d262120..2a0b685765f8 100644 --- a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/partial-reads-enabled.snap +++ b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/partial-reads-enabled.snap @@ -231,42 +231,6 @@ Example for appending to the end of file: -## search_and_replace -Description: Use this tool to find and replace specific text strings or patterns (using regex) within a file. It's suitable for targeted replacements across multiple locations within the file. Supports literal text and regex patterns, case sensitivity options, and optional line ranges. Shows a diff preview before applying changes. - -Required Parameters: -- path: The path of the file to modify (relative to the current workspace directory /test/path) -- search: The text or pattern to search for -- replace: The text to replace matches with - -Optional Parameters: -- start_line: Starting line number for restricted replacement (1-based) -- end_line: Ending line number for restricted replacement (1-based) -- use_regex: Set to "true" to treat search as a regex pattern (default: false) -- ignore_case: Set to "true" to ignore case when matching (default: false) - -Notes: -- When use_regex is true, the search parameter is treated as a regular expression pattern -- When ignore_case is true, the search is case-insensitive regardless of regex mode - -Examples: - -1. Simple text replacement: - -example.ts -oldText -newText - - -2. Case-insensitive regex pattern: - -example.ts -oldw+ -new$& -true -true - - ## execute_command Description: Request to execute a CLI command on the system. Use this when you need to perform system operations or run specific commands to accomplish any step in the user's task. You must tailor your command to the user's system and provide a clear explanation of what the command does. For command chaining, use the appropriate chaining syntax for the user's shell. Prefer to execute complex CLI commands over creating executable scripts, as they are more flexible and easier to run. Prefer relative commands and paths that avoid location sensitivity for terminal consistency, e.g: `touch ./testdata/example.file`, `dir ./examples/model1/data/yaml`, or `go test ./cmd/front --config ./cmd/front/config.yml`. If directed by the user, you may open a terminal in a different directory by using the `cwd` parameter. Parameters: @@ -514,9 +478,8 @@ RULES - Before using the execute_command tool, you must first think about the SYSTEM INFORMATION context provided to understand the user's environment and tailor your commands to ensure they are compatible with their system. You must also consider if the command you need to run should be executed in a specific directory outside of the current working directory '/test/path', and if so prepend with `cd`'ing into that directory && then executing the command (as one command since you are stuck operating from '/test/path'). For example, if you needed to run `npm install` in a project outside of '/test/path', you would need to prepend with a `cd` i.e. pseudocode for this would be `cd (path to project) && (command, in this case npm install)`. - When using the search_files tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the search_files tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches before using write_to_file to make informed changes. - When creating a new project (such as an app, website, or any software project), organize all new files within a dedicated project directory unless the user specifies otherwise. Use appropriate file paths when writing files, as the write_to_file tool will automatically create any necessary directories. Structure the project logically, adhering to best practices for the specific type of project being created. Unless otherwise specified, new projects should be easily run without additional setup, for example most projects can be built in HTML, CSS, and JavaScript - which you can open in a browser. -- For editing files, you have access to these tools: write_to_file (for creating new files or complete file rewrites), insert_content (for adding lines to files), search_and_replace (for finding and replacing individual pieces of text). +- For editing files, you have access to these tools: write_to_file (for creating new files or complete file rewrites), insert_content (for adding lines to files). - The insert_content tool adds lines of text to files at a specific line number, such as adding a new function to a JavaScript file or inserting a new route in a Python file. Use line number 0 to append at the end of the file, or any positive number to insert before that line. -- The search_and_replace tool finds and replaces text or regex in files. This tool allows you to search for a specific regex pattern or text and replace it with another value. Be cautious when using this tool to ensure you are replacing the correct text. It can support multiple operations at once. - You should always prefer using other editing tools over write_to_file when making changes to existing files since write_to_file is much slower and cannot handle large files. - When using the write_to_file tool to modify a file, use the tool directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code, severely impacting the user's project. - Some modes have restrictions on which files they can edit. If you attempt to edit a restricted file, the operation will be rejected with a FileRestrictionError that will specify which file patterns are allowed for the current mode. diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/consistent-system-prompt.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/consistent-system-prompt.snap index fc57a4d5633a..0a52fade3321 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/consistent-system-prompt.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/consistent-system-prompt.snap @@ -226,42 +226,6 @@ Example for appending to the end of file: -## search_and_replace -Description: Use this tool to find and replace specific text strings or patterns (using regex) within a file. It's suitable for targeted replacements across multiple locations within the file. Supports literal text and regex patterns, case sensitivity options, and optional line ranges. Shows a diff preview before applying changes. - -Required Parameters: -- path: The path of the file to modify (relative to the current workspace directory /test/path) -- search: The text or pattern to search for -- replace: The text to replace matches with - -Optional Parameters: -- start_line: Starting line number for restricted replacement (1-based) -- end_line: Ending line number for restricted replacement (1-based) -- use_regex: Set to "true" to treat search as a regex pattern (default: false) -- ignore_case: Set to "true" to ignore case when matching (default: false) - -Notes: -- When use_regex is true, the search parameter is treated as a regular expression pattern -- When ignore_case is true, the search is case-insensitive regardless of regex mode - -Examples: - -1. Simple text replacement: - -example.ts -oldText -newText - - -2. Case-insensitive regex pattern: - -example.ts -oldw+ -new$& -true -true - - ## execute_command Description: Request to execute a CLI command on the system. Use this when you need to perform system operations or run specific commands to accomplish any step in the user's task. You must tailor your command to the user's system and provide a clear explanation of what the command does. For command chaining, use the appropriate chaining syntax for the user's shell. Prefer to execute complex CLI commands over creating executable scripts, as they are more flexible and easier to run. Prefer relative commands and paths that avoid location sensitivity for terminal consistency, e.g: `touch ./testdata/example.file`, `dir ./examples/model1/data/yaml`, or `go test ./cmd/front --config ./cmd/front/config.yml`. If directed by the user, you may open a terminal in a different directory by using the `cwd` parameter. Parameters: @@ -509,9 +473,8 @@ RULES - Before using the execute_command tool, you must first think about the SYSTEM INFORMATION context provided to understand the user's environment and tailor your commands to ensure they are compatible with their system. You must also consider if the command you need to run should be executed in a specific directory outside of the current working directory '/test/path', and if so prepend with `cd`'ing into that directory && then executing the command (as one command since you are stuck operating from '/test/path'). For example, if you needed to run `npm install` in a project outside of '/test/path', you would need to prepend with a `cd` i.e. pseudocode for this would be `cd (path to project) && (command, in this case npm install)`. - When using the search_files tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the search_files tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches before using write_to_file to make informed changes. - When creating a new project (such as an app, website, or any software project), organize all new files within a dedicated project directory unless the user specifies otherwise. Use appropriate file paths when writing files, as the write_to_file tool will automatically create any necessary directories. Structure the project logically, adhering to best practices for the specific type of project being created. Unless otherwise specified, new projects should be easily run without additional setup, for example most projects can be built in HTML, CSS, and JavaScript - which you can open in a browser. -- For editing files, you have access to these tools: write_to_file (for creating new files or complete file rewrites), insert_content (for adding lines to files), search_and_replace (for finding and replacing individual pieces of text). +- For editing files, you have access to these tools: write_to_file (for creating new files or complete file rewrites), insert_content (for adding lines to files). - The insert_content tool adds lines of text to files at a specific line number, such as adding a new function to a JavaScript file or inserting a new route in a Python file. Use line number 0 to append at the end of the file, or any positive number to insert before that line. -- The search_and_replace tool finds and replaces text or regex in files. This tool allows you to search for a specific regex pattern or text and replace it with another value. Be cautious when using this tool to ensure you are replacing the correct text. It can support multiple operations at once. - You should always prefer using other editing tools over write_to_file when making changes to existing files since write_to_file is much slower and cannot handle large files. - When using the write_to_file tool to modify a file, use the tool directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code, severely impacting the user's project. - Some modes have restrictions on which files they can edit. If you attempt to edit a restricted file, the operation will be rejected with a FileRestrictionError that will specify which file patterns are allowed for the current mode. diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-computer-use-support.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-computer-use-support.snap index 9831062fc8fa..06ef9bcaed24 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-computer-use-support.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-computer-use-support.snap @@ -226,42 +226,6 @@ Example for appending to the end of file: -## search_and_replace -Description: Use this tool to find and replace specific text strings or patterns (using regex) within a file. It's suitable for targeted replacements across multiple locations within the file. Supports literal text and regex patterns, case sensitivity options, and optional line ranges. Shows a diff preview before applying changes. - -Required Parameters: -- path: The path of the file to modify (relative to the current workspace directory /test/path) -- search: The text or pattern to search for -- replace: The text to replace matches with - -Optional Parameters: -- start_line: Starting line number for restricted replacement (1-based) -- end_line: Ending line number for restricted replacement (1-based) -- use_regex: Set to "true" to treat search as a regex pattern (default: false) -- ignore_case: Set to "true" to ignore case when matching (default: false) - -Notes: -- When use_regex is true, the search parameter is treated as a regular expression pattern -- When ignore_case is true, the search is case-insensitive regardless of regex mode - -Examples: - -1. Simple text replacement: - -example.ts -oldText -newText - - -2. Case-insensitive regex pattern: - -example.ts -oldw+ -new$& -true -true - - ## browser_action Description: Request to interact with a Puppeteer-controlled browser. Every action, except `close`, will be responded to with a screenshot of the browser's current state, along with any new console logs. You may only perform one browser action per message, and wait for the user's response including a screenshot and logs to determine the next action. - The sequence of actions **must always start with** launching the browser at a URL, and **must always end with** closing the browser. If you need to visit a new URL that is not possible to navigate to from the current webpage, you must first close the browser, then launch again at the new URL. @@ -564,9 +528,8 @@ RULES - Before using the execute_command tool, you must first think about the SYSTEM INFORMATION context provided to understand the user's environment and tailor your commands to ensure they are compatible with their system. You must also consider if the command you need to run should be executed in a specific directory outside of the current working directory '/test/path', and if so prepend with `cd`'ing into that directory && then executing the command (as one command since you are stuck operating from '/test/path'). For example, if you needed to run `npm install` in a project outside of '/test/path', you would need to prepend with a `cd` i.e. pseudocode for this would be `cd (path to project) && (command, in this case npm install)`. - When using the search_files tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the search_files tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches before using write_to_file to make informed changes. - When creating a new project (such as an app, website, or any software project), organize all new files within a dedicated project directory unless the user specifies otherwise. Use appropriate file paths when writing files, as the write_to_file tool will automatically create any necessary directories. Structure the project logically, adhering to best practices for the specific type of project being created. Unless otherwise specified, new projects should be easily run without additional setup, for example most projects can be built in HTML, CSS, and JavaScript - which you can open in a browser. -- For editing files, you have access to these tools: write_to_file (for creating new files or complete file rewrites), insert_content (for adding lines to files), search_and_replace (for finding and replacing individual pieces of text). +- For editing files, you have access to these tools: write_to_file (for creating new files or complete file rewrites), insert_content (for adding lines to files). - The insert_content tool adds lines of text to files at a specific line number, such as adding a new function to a JavaScript file or inserting a new route in a Python file. Use line number 0 to append at the end of the file, or any positive number to insert before that line. -- The search_and_replace tool finds and replaces text or regex in files. This tool allows you to search for a specific regex pattern or text and replace it with another value. Be cautious when using this tool to ensure you are replacing the correct text. It can support multiple operations at once. - You should always prefer using other editing tools over write_to_file when making changes to existing files since write_to_file is much slower and cannot handle large files. - When using the write_to_file tool to modify a file, use the tool directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code, severely impacting the user's project. - Some modes have restrictions on which files they can edit. If you attempt to edit a restricted file, the operation will be rejected with a FileRestrictionError that will specify which file patterns are allowed for the current mode. diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-false.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-false.snap index fc57a4d5633a..0a52fade3321 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-false.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-false.snap @@ -226,42 +226,6 @@ Example for appending to the end of file: -## search_and_replace -Description: Use this tool to find and replace specific text strings or patterns (using regex) within a file. It's suitable for targeted replacements across multiple locations within the file. Supports literal text and regex patterns, case sensitivity options, and optional line ranges. Shows a diff preview before applying changes. - -Required Parameters: -- path: The path of the file to modify (relative to the current workspace directory /test/path) -- search: The text or pattern to search for -- replace: The text to replace matches with - -Optional Parameters: -- start_line: Starting line number for restricted replacement (1-based) -- end_line: Ending line number for restricted replacement (1-based) -- use_regex: Set to "true" to treat search as a regex pattern (default: false) -- ignore_case: Set to "true" to ignore case when matching (default: false) - -Notes: -- When use_regex is true, the search parameter is treated as a regular expression pattern -- When ignore_case is true, the search is case-insensitive regardless of regex mode - -Examples: - -1. Simple text replacement: - -example.ts -oldText -newText - - -2. Case-insensitive regex pattern: - -example.ts -oldw+ -new$& -true -true - - ## execute_command Description: Request to execute a CLI command on the system. Use this when you need to perform system operations or run specific commands to accomplish any step in the user's task. You must tailor your command to the user's system and provide a clear explanation of what the command does. For command chaining, use the appropriate chaining syntax for the user's shell. Prefer to execute complex CLI commands over creating executable scripts, as they are more flexible and easier to run. Prefer relative commands and paths that avoid location sensitivity for terminal consistency, e.g: `touch ./testdata/example.file`, `dir ./examples/model1/data/yaml`, or `go test ./cmd/front --config ./cmd/front/config.yml`. If directed by the user, you may open a terminal in a different directory by using the `cwd` parameter. Parameters: @@ -509,9 +473,8 @@ RULES - Before using the execute_command tool, you must first think about the SYSTEM INFORMATION context provided to understand the user's environment and tailor your commands to ensure they are compatible with their system. You must also consider if the command you need to run should be executed in a specific directory outside of the current working directory '/test/path', and if so prepend with `cd`'ing into that directory && then executing the command (as one command since you are stuck operating from '/test/path'). For example, if you needed to run `npm install` in a project outside of '/test/path', you would need to prepend with a `cd` i.e. pseudocode for this would be `cd (path to project) && (command, in this case npm install)`. - When using the search_files tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the search_files tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches before using write_to_file to make informed changes. - When creating a new project (such as an app, website, or any software project), organize all new files within a dedicated project directory unless the user specifies otherwise. Use appropriate file paths when writing files, as the write_to_file tool will automatically create any necessary directories. Structure the project logically, adhering to best practices for the specific type of project being created. Unless otherwise specified, new projects should be easily run without additional setup, for example most projects can be built in HTML, CSS, and JavaScript - which you can open in a browser. -- For editing files, you have access to these tools: write_to_file (for creating new files or complete file rewrites), insert_content (for adding lines to files), search_and_replace (for finding and replacing individual pieces of text). +- For editing files, you have access to these tools: write_to_file (for creating new files or complete file rewrites), insert_content (for adding lines to files). - The insert_content tool adds lines of text to files at a specific line number, such as adding a new function to a JavaScript file or inserting a new route in a Python file. Use line number 0 to append at the end of the file, or any positive number to insert before that line. -- The search_and_replace tool finds and replaces text or regex in files. This tool allows you to search for a specific regex pattern or text and replace it with another value. Be cautious when using this tool to ensure you are replacing the correct text. It can support multiple operations at once. - You should always prefer using other editing tools over write_to_file when making changes to existing files since write_to_file is much slower and cannot handle large files. - When using the write_to_file tool to modify a file, use the tool directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code, severely impacting the user's project. - Some modes have restrictions on which files they can edit. If you attempt to edit a restricted file, the operation will be rejected with a FileRestrictionError that will specify which file patterns are allowed for the current mode. diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-true.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-true.snap index cb10ac7fc9b3..bb4989c2f550 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-true.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-true.snap @@ -314,42 +314,6 @@ Example for appending to the end of file: -## search_and_replace -Description: Use this tool to find and replace specific text strings or patterns (using regex) within a file. It's suitable for targeted replacements across multiple locations within the file. Supports literal text and regex patterns, case sensitivity options, and optional line ranges. Shows a diff preview before applying changes. - -Required Parameters: -- path: The path of the file to modify (relative to the current workspace directory /test/path) -- search: The text or pattern to search for -- replace: The text to replace matches with - -Optional Parameters: -- start_line: Starting line number for restricted replacement (1-based) -- end_line: Ending line number for restricted replacement (1-based) -- use_regex: Set to "true" to treat search as a regex pattern (default: false) -- ignore_case: Set to "true" to ignore case when matching (default: false) - -Notes: -- When use_regex is true, the search parameter is treated as a regular expression pattern -- When ignore_case is true, the search is case-insensitive regardless of regex mode - -Examples: - -1. Simple text replacement: - -example.ts -oldText -newText - - -2. Case-insensitive regex pattern: - -example.ts -oldw+ -new$& -true -true - - ## execute_command Description: Request to execute a CLI command on the system. Use this when you need to perform system operations or run specific commands to accomplish any step in the user's task. You must tailor your command to the user's system and provide a clear explanation of what the command does. For command chaining, use the appropriate chaining syntax for the user's shell. Prefer to execute complex CLI commands over creating executable scripts, as they are more flexible and easier to run. Prefer relative commands and paths that avoid location sensitivity for terminal consistency, e.g: `touch ./testdata/example.file`, `dir ./examples/model1/data/yaml`, or `go test ./cmd/front --config ./cmd/front/config.yml`. If directed by the user, you may open a terminal in a different directory by using the `cwd` parameter. Parameters: @@ -597,9 +561,8 @@ RULES - Before using the execute_command tool, you must first think about the SYSTEM INFORMATION context provided to understand the user's environment and tailor your commands to ensure they are compatible with their system. You must also consider if the command you need to run should be executed in a specific directory outside of the current working directory '/test/path', and if so prepend with `cd`'ing into that directory && then executing the command (as one command since you are stuck operating from '/test/path'). For example, if you needed to run `npm install` in a project outside of '/test/path', you would need to prepend with a `cd` i.e. pseudocode for this would be `cd (path to project) && (command, in this case npm install)`. - When using the search_files tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the search_files tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches before using apply_diff or write_to_file to make informed changes. - When creating a new project (such as an app, website, or any software project), organize all new files within a dedicated project directory unless the user specifies otherwise. Use appropriate file paths when writing files, as the write_to_file tool will automatically create any necessary directories. Structure the project logically, adhering to best practices for the specific type of project being created. Unless otherwise specified, new projects should be easily run without additional setup, for example most projects can be built in HTML, CSS, and JavaScript - which you can open in a browser. -- For editing files, you have access to these tools: apply_diff (for surgical edits - targeted changes to specific lines or functions), write_to_file (for creating new files or complete file rewrites), insert_content (for adding lines to files), search_and_replace (for finding and replacing individual pieces of text). +- For editing files, you have access to these tools: apply_diff (for surgical edits - targeted changes to specific lines or functions), write_to_file (for creating new files or complete file rewrites), insert_content (for adding lines to files). - The insert_content tool adds lines of text to files at a specific line number, such as adding a new function to a JavaScript file or inserting a new route in a Python file. Use line number 0 to append at the end of the file, or any positive number to insert before that line. -- The search_and_replace tool finds and replaces text or regex in files. This tool allows you to search for a specific regex pattern or text and replace it with another value. Be cautious when using this tool to ensure you are replacing the correct text. It can support multiple operations at once. - You should always prefer using other editing tools over write_to_file when making changes to existing files since write_to_file is much slower and cannot handle large files. - When using the write_to_file tool to modify a file, use the tool directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code, severely impacting the user's project. - Some modes have restrictions on which files they can edit. If you attempt to edit a restricted file, the operation will be rejected with a FileRestrictionError that will specify which file patterns are allowed for the current mode. diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-undefined.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-undefined.snap index fc57a4d5633a..0a52fade3321 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-undefined.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-undefined.snap @@ -226,42 +226,6 @@ Example for appending to the end of file: -## search_and_replace -Description: Use this tool to find and replace specific text strings or patterns (using regex) within a file. It's suitable for targeted replacements across multiple locations within the file. Supports literal text and regex patterns, case sensitivity options, and optional line ranges. Shows a diff preview before applying changes. - -Required Parameters: -- path: The path of the file to modify (relative to the current workspace directory /test/path) -- search: The text or pattern to search for -- replace: The text to replace matches with - -Optional Parameters: -- start_line: Starting line number for restricted replacement (1-based) -- end_line: Ending line number for restricted replacement (1-based) -- use_regex: Set to "true" to treat search as a regex pattern (default: false) -- ignore_case: Set to "true" to ignore case when matching (default: false) - -Notes: -- When use_regex is true, the search parameter is treated as a regular expression pattern -- When ignore_case is true, the search is case-insensitive regardless of regex mode - -Examples: - -1. Simple text replacement: - -example.ts -oldText -newText - - -2. Case-insensitive regex pattern: - -example.ts -oldw+ -new$& -true -true - - ## execute_command Description: Request to execute a CLI command on the system. Use this when you need to perform system operations or run specific commands to accomplish any step in the user's task. You must tailor your command to the user's system and provide a clear explanation of what the command does. For command chaining, use the appropriate chaining syntax for the user's shell. Prefer to execute complex CLI commands over creating executable scripts, as they are more flexible and easier to run. Prefer relative commands and paths that avoid location sensitivity for terminal consistency, e.g: `touch ./testdata/example.file`, `dir ./examples/model1/data/yaml`, or `go test ./cmd/front --config ./cmd/front/config.yml`. If directed by the user, you may open a terminal in a different directory by using the `cwd` parameter. Parameters: @@ -509,9 +473,8 @@ RULES - Before using the execute_command tool, you must first think about the SYSTEM INFORMATION context provided to understand the user's environment and tailor your commands to ensure they are compatible with their system. You must also consider if the command you need to run should be executed in a specific directory outside of the current working directory '/test/path', and if so prepend with `cd`'ing into that directory && then executing the command (as one command since you are stuck operating from '/test/path'). For example, if you needed to run `npm install` in a project outside of '/test/path', you would need to prepend with a `cd` i.e. pseudocode for this would be `cd (path to project) && (command, in this case npm install)`. - When using the search_files tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the search_files tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches before using write_to_file to make informed changes. - When creating a new project (such as an app, website, or any software project), organize all new files within a dedicated project directory unless the user specifies otherwise. Use appropriate file paths when writing files, as the write_to_file tool will automatically create any necessary directories. Structure the project logically, adhering to best practices for the specific type of project being created. Unless otherwise specified, new projects should be easily run without additional setup, for example most projects can be built in HTML, CSS, and JavaScript - which you can open in a browser. -- For editing files, you have access to these tools: write_to_file (for creating new files or complete file rewrites), insert_content (for adding lines to files), search_and_replace (for finding and replacing individual pieces of text). +- For editing files, you have access to these tools: write_to_file (for creating new files or complete file rewrites), insert_content (for adding lines to files). - The insert_content tool adds lines of text to files at a specific line number, such as adding a new function to a JavaScript file or inserting a new route in a Python file. Use line number 0 to append at the end of the file, or any positive number to insert before that line. -- The search_and_replace tool finds and replaces text or regex in files. This tool allows you to search for a specific regex pattern or text and replace it with another value. Be cautious when using this tool to ensure you are replacing the correct text. It can support multiple operations at once. - You should always prefer using other editing tools over write_to_file when making changes to existing files since write_to_file is much slower and cannot handle large files. - When using the write_to_file tool to modify a file, use the tool directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code, severely impacting the user's project. - Some modes have restrictions on which files they can edit. If you attempt to edit a restricted file, the operation will be rejected with a FileRestrictionError that will specify which file patterns are allowed for the current mode. diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-different-viewport-size.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-different-viewport-size.snap index fc57a4d5633a..0a52fade3321 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-different-viewport-size.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-different-viewport-size.snap @@ -226,42 +226,6 @@ Example for appending to the end of file: -## search_and_replace -Description: Use this tool to find and replace specific text strings or patterns (using regex) within a file. It's suitable for targeted replacements across multiple locations within the file. Supports literal text and regex patterns, case sensitivity options, and optional line ranges. Shows a diff preview before applying changes. - -Required Parameters: -- path: The path of the file to modify (relative to the current workspace directory /test/path) -- search: The text or pattern to search for -- replace: The text to replace matches with - -Optional Parameters: -- start_line: Starting line number for restricted replacement (1-based) -- end_line: Ending line number for restricted replacement (1-based) -- use_regex: Set to "true" to treat search as a regex pattern (default: false) -- ignore_case: Set to "true" to ignore case when matching (default: false) - -Notes: -- When use_regex is true, the search parameter is treated as a regular expression pattern -- When ignore_case is true, the search is case-insensitive regardless of regex mode - -Examples: - -1. Simple text replacement: - -example.ts -oldText -newText - - -2. Case-insensitive regex pattern: - -example.ts -oldw+ -new$& -true -true - - ## execute_command Description: Request to execute a CLI command on the system. Use this when you need to perform system operations or run specific commands to accomplish any step in the user's task. You must tailor your command to the user's system and provide a clear explanation of what the command does. For command chaining, use the appropriate chaining syntax for the user's shell. Prefer to execute complex CLI commands over creating executable scripts, as they are more flexible and easier to run. Prefer relative commands and paths that avoid location sensitivity for terminal consistency, e.g: `touch ./testdata/example.file`, `dir ./examples/model1/data/yaml`, or `go test ./cmd/front --config ./cmd/front/config.yml`. If directed by the user, you may open a terminal in a different directory by using the `cwd` parameter. Parameters: @@ -509,9 +473,8 @@ RULES - Before using the execute_command tool, you must first think about the SYSTEM INFORMATION context provided to understand the user's environment and tailor your commands to ensure they are compatible with their system. You must also consider if the command you need to run should be executed in a specific directory outside of the current working directory '/test/path', and if so prepend with `cd`'ing into that directory && then executing the command (as one command since you are stuck operating from '/test/path'). For example, if you needed to run `npm install` in a project outside of '/test/path', you would need to prepend with a `cd` i.e. pseudocode for this would be `cd (path to project) && (command, in this case npm install)`. - When using the search_files tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the search_files tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches before using write_to_file to make informed changes. - When creating a new project (such as an app, website, or any software project), organize all new files within a dedicated project directory unless the user specifies otherwise. Use appropriate file paths when writing files, as the write_to_file tool will automatically create any necessary directories. Structure the project logically, adhering to best practices for the specific type of project being created. Unless otherwise specified, new projects should be easily run without additional setup, for example most projects can be built in HTML, CSS, and JavaScript - which you can open in a browser. -- For editing files, you have access to these tools: write_to_file (for creating new files or complete file rewrites), insert_content (for adding lines to files), search_and_replace (for finding and replacing individual pieces of text). +- For editing files, you have access to these tools: write_to_file (for creating new files or complete file rewrites), insert_content (for adding lines to files). - The insert_content tool adds lines of text to files at a specific line number, such as adding a new function to a JavaScript file or inserting a new route in a Python file. Use line number 0 to append at the end of the file, or any positive number to insert before that line. -- The search_and_replace tool finds and replaces text or regex in files. This tool allows you to search for a specific regex pattern or text and replace it with another value. Be cautious when using this tool to ensure you are replacing the correct text. It can support multiple operations at once. - You should always prefer using other editing tools over write_to_file when making changes to existing files since write_to_file is much slower and cannot handle large files. - When using the write_to_file tool to modify a file, use the tool directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code, severely impacting the user's project. - Some modes have restrictions on which files they can edit. If you attempt to edit a restricted file, the operation will be rejected with a FileRestrictionError that will specify which file patterns are allowed for the current mode. diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-mcp-hub-provided.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-mcp-hub-provided.snap index 6ad6c19242d4..bf00761163a2 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-mcp-hub-provided.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-mcp-hub-provided.snap @@ -226,42 +226,6 @@ Example for appending to the end of file: -## search_and_replace -Description: Use this tool to find and replace specific text strings or patterns (using regex) within a file. It's suitable for targeted replacements across multiple locations within the file. Supports literal text and regex patterns, case sensitivity options, and optional line ranges. Shows a diff preview before applying changes. - -Required Parameters: -- path: The path of the file to modify (relative to the current workspace directory /test/path) -- search: The text or pattern to search for -- replace: The text to replace matches with - -Optional Parameters: -- start_line: Starting line number for restricted replacement (1-based) -- end_line: Ending line number for restricted replacement (1-based) -- use_regex: Set to "true" to treat search as a regex pattern (default: false) -- ignore_case: Set to "true" to ignore case when matching (default: false) - -Notes: -- When use_regex is true, the search parameter is treated as a regular expression pattern -- When ignore_case is true, the search is case-insensitive regardless of regex mode - -Examples: - -1. Simple text replacement: - -example.ts -oldText -newText - - -2. Case-insensitive regex pattern: - -example.ts -oldw+ -new$& -true -true - - ## execute_command Description: Request to execute a CLI command on the system. Use this when you need to perform system operations or run specific commands to accomplish any step in the user's task. You must tailor your command to the user's system and provide a clear explanation of what the command does. For command chaining, use the appropriate chaining syntax for the user's shell. Prefer to execute complex CLI commands over creating executable scripts, as they are more flexible and easier to run. Prefer relative commands and paths that avoid location sensitivity for terminal consistency, e.g: `touch ./testdata/example.file`, `dir ./examples/model1/data/yaml`, or `go test ./cmd/front --config ./cmd/front/config.yml`. If directed by the user, you may open a terminal in a different directory by using the `cwd` parameter. Parameters: @@ -577,9 +541,8 @@ RULES - Before using the execute_command tool, you must first think about the SYSTEM INFORMATION context provided to understand the user's environment and tailor your commands to ensure they are compatible with their system. You must also consider if the command you need to run should be executed in a specific directory outside of the current working directory '/test/path', and if so prepend with `cd`'ing into that directory && then executing the command (as one command since you are stuck operating from '/test/path'). For example, if you needed to run `npm install` in a project outside of '/test/path', you would need to prepend with a `cd` i.e. pseudocode for this would be `cd (path to project) && (command, in this case npm install)`. - When using the search_files tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the search_files tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches before using write_to_file to make informed changes. - When creating a new project (such as an app, website, or any software project), organize all new files within a dedicated project directory unless the user specifies otherwise. Use appropriate file paths when writing files, as the write_to_file tool will automatically create any necessary directories. Structure the project logically, adhering to best practices for the specific type of project being created. Unless otherwise specified, new projects should be easily run without additional setup, for example most projects can be built in HTML, CSS, and JavaScript - which you can open in a browser. -- For editing files, you have access to these tools: write_to_file (for creating new files or complete file rewrites), insert_content (for adding lines to files), search_and_replace (for finding and replacing individual pieces of text). +- For editing files, you have access to these tools: write_to_file (for creating new files or complete file rewrites), insert_content (for adding lines to files). - The insert_content tool adds lines of text to files at a specific line number, such as adding a new function to a JavaScript file or inserting a new route in a Python file. Use line number 0 to append at the end of the file, or any positive number to insert before that line. -- The search_and_replace tool finds and replaces text or regex in files. This tool allows you to search for a specific regex pattern or text and replace it with another value. Be cautious when using this tool to ensure you are replacing the correct text. It can support multiple operations at once. - You should always prefer using other editing tools over write_to_file when making changes to existing files since write_to_file is much slower and cannot handle large files. - When using the write_to_file tool to modify a file, use the tool directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code, severely impacting the user's project. - Some modes have restrictions on which files they can edit. If you attempt to edit a restricted file, the operation will be rejected with a FileRestrictionError that will specify which file patterns are allowed for the current mode. diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-undefined-mcp-hub.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-undefined-mcp-hub.snap index fc57a4d5633a..0a52fade3321 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-undefined-mcp-hub.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-undefined-mcp-hub.snap @@ -226,42 +226,6 @@ Example for appending to the end of file: -## search_and_replace -Description: Use this tool to find and replace specific text strings or patterns (using regex) within a file. It's suitable for targeted replacements across multiple locations within the file. Supports literal text and regex patterns, case sensitivity options, and optional line ranges. Shows a diff preview before applying changes. - -Required Parameters: -- path: The path of the file to modify (relative to the current workspace directory /test/path) -- search: The text or pattern to search for -- replace: The text to replace matches with - -Optional Parameters: -- start_line: Starting line number for restricted replacement (1-based) -- end_line: Ending line number for restricted replacement (1-based) -- use_regex: Set to "true" to treat search as a regex pattern (default: false) -- ignore_case: Set to "true" to ignore case when matching (default: false) - -Notes: -- When use_regex is true, the search parameter is treated as a regular expression pattern -- When ignore_case is true, the search is case-insensitive regardless of regex mode - -Examples: - -1. Simple text replacement: - -example.ts -oldText -newText - - -2. Case-insensitive regex pattern: - -example.ts -oldw+ -new$& -true -true - - ## execute_command Description: Request to execute a CLI command on the system. Use this when you need to perform system operations or run specific commands to accomplish any step in the user's task. You must tailor your command to the user's system and provide a clear explanation of what the command does. For command chaining, use the appropriate chaining syntax for the user's shell. Prefer to execute complex CLI commands over creating executable scripts, as they are more flexible and easier to run. Prefer relative commands and paths that avoid location sensitivity for terminal consistency, e.g: `touch ./testdata/example.file`, `dir ./examples/model1/data/yaml`, or `go test ./cmd/front --config ./cmd/front/config.yml`. If directed by the user, you may open a terminal in a different directory by using the `cwd` parameter. Parameters: @@ -509,9 +473,8 @@ RULES - Before using the execute_command tool, you must first think about the SYSTEM INFORMATION context provided to understand the user's environment and tailor your commands to ensure they are compatible with their system. You must also consider if the command you need to run should be executed in a specific directory outside of the current working directory '/test/path', and if so prepend with `cd`'ing into that directory && then executing the command (as one command since you are stuck operating from '/test/path'). For example, if you needed to run `npm install` in a project outside of '/test/path', you would need to prepend with a `cd` i.e. pseudocode for this would be `cd (path to project) && (command, in this case npm install)`. - When using the search_files tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the search_files tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches before using write_to_file to make informed changes. - When creating a new project (such as an app, website, or any software project), organize all new files within a dedicated project directory unless the user specifies otherwise. Use appropriate file paths when writing files, as the write_to_file tool will automatically create any necessary directories. Structure the project logically, adhering to best practices for the specific type of project being created. Unless otherwise specified, new projects should be easily run without additional setup, for example most projects can be built in HTML, CSS, and JavaScript - which you can open in a browser. -- For editing files, you have access to these tools: write_to_file (for creating new files or complete file rewrites), insert_content (for adding lines to files), search_and_replace (for finding and replacing individual pieces of text). +- For editing files, you have access to these tools: write_to_file (for creating new files or complete file rewrites), insert_content (for adding lines to files). - The insert_content tool adds lines of text to files at a specific line number, such as adding a new function to a JavaScript file or inserting a new route in a Python file. Use line number 0 to append at the end of the file, or any positive number to insert before that line. -- The search_and_replace tool finds and replaces text or regex in files. This tool allows you to search for a specific regex pattern or text and replace it with another value. Be cautious when using this tool to ensure you are replacing the correct text. It can support multiple operations at once. - You should always prefer using other editing tools over write_to_file when making changes to existing files since write_to_file is much slower and cannot handle large files. - When using the write_to_file tool to modify a file, use the tool directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code, severely impacting the user's project. - Some modes have restrictions on which files they can edit. If you attempt to edit a restricted file, the operation will be rejected with a FileRestrictionError that will specify which file patterns are allowed for the current mode. diff --git a/src/core/prompts/responses.ts b/src/core/prompts/responses.ts index fd51b18feda4..2f3ea87d4c6b 100644 --- a/src/core/prompts/responses.ts +++ b/src/core/prompts/responses.ts @@ -56,8 +56,7 @@ Otherwise, if you have not completed the task and do not need additional informa } existingFileApproaches.push( - `${diffStrategyEnabled ? "3" : "2"}. Or use search_and_replace for specific text replacements`, - `${diffStrategyEnabled ? "4" : "3"}. Or use insert_content to add specific content at particular lines`, + `${diffStrategyEnabled ? "3" : "2"}. Or use insert_content to add specific content at particular lines`, ) const existingFileGuidance = diff --git a/src/core/prompts/sections/rules.ts b/src/core/prompts/sections/rules.ts index a5eaf23ce08f..e8c7534b18a4 100644 --- a/src/core/prompts/sections/rules.ts +++ b/src/core/prompts/sections/rules.ts @@ -16,7 +16,6 @@ function getEditingInstructions(diffStrategy?: DiffStrategy): string { } availableTools.push("insert_content (for adding lines to files)") - availableTools.push("search_and_replace (for finding and replacing individual pieces of text)") // Base editing instruction mentioning all available tools if (availableTools.length > 1) { @@ -28,10 +27,6 @@ function getEditingInstructions(diffStrategy?: DiffStrategy): string { "- The insert_content tool adds lines of text to files at a specific line number, such as adding a new function to a JavaScript file or inserting a new route in a Python file. Use line number 0 to append at the end of the file, or any positive number to insert before that line.", ) - instructions.push( - "- The search_and_replace tool finds and replaces text or regex in files. This tool allows you to search for a specific regex pattern or text and replace it with another value. Be cautious when using this tool to ensure you are replacing the correct text. It can support multiple operations at once.", - ) - if (availableTools.length > 1) { instructions.push( "- You should always prefer using other editing tools over write_to_file when making changes to existing files since write_to_file is much slower and cannot handle large files.", diff --git a/src/core/prompts/tools/index.ts b/src/core/prompts/tools/index.ts index c212b18a3de4..22588d55d128 100644 --- a/src/core/prompts/tools/index.ts +++ b/src/core/prompts/tools/index.ts @@ -14,7 +14,6 @@ import { getWriteToFileDescription } from "./write-to-file" import { getSearchFilesDescription } from "./search-files" import { getListFilesDescription } from "./list-files" import { getInsertContentDescription } from "./insert-content" -import { getSearchAndReplaceDescription } from "./search-and-replace" import { getListCodeDefinitionNamesDescription } from "./list-code-definition-names" import { getBrowserActionDescription } from "./browser-action" import { getAskFollowupQuestionDescription } from "./ask-followup-question" @@ -54,7 +53,6 @@ const toolDescriptionMap: Record string | undefined> switch_mode: () => getSwitchModeDescription(), new_task: (args) => getNewTaskDescription(args), insert_content: (args) => getInsertContentDescription(args), - search_and_replace: (args) => getSearchAndReplaceDescription(args), apply_diff: (args) => args.diffStrategy ? args.diffStrategy.getToolDescription({ cwd: args.cwd, toolOptions: args.toolOptions }) : "", update_todo_list: (args) => getUpdateTodoListDescription(args), @@ -176,7 +174,6 @@ export { getAccessMcpResourceDescription, getSwitchModeDescription, getInsertContentDescription, - getSearchAndReplaceDescription, getCodebaseSearchDescription, getRunSlashCommandDescription, getGenerateImageDescription, diff --git a/src/core/prompts/tools/search-and-replace.ts b/src/core/prompts/tools/search-and-replace.ts deleted file mode 100644 index 357a7058323b..000000000000 --- a/src/core/prompts/tools/search-and-replace.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { ToolArgs } from "./types" - -export function getSearchAndReplaceDescription(args: ToolArgs): string { - return `## search_and_replace -Description: Use this tool to find and replace specific text strings or patterns (using regex) within a file. It's suitable for targeted replacements across multiple locations within the file. Supports literal text and regex patterns, case sensitivity options, and optional line ranges. Shows a diff preview before applying changes. - -Required Parameters: -- path: The path of the file to modify (relative to the current workspace directory ${args.cwd.toPosix()}) -- search: The text or pattern to search for -- replace: The text to replace matches with - -Optional Parameters: -- start_line: Starting line number for restricted replacement (1-based) -- end_line: Ending line number for restricted replacement (1-based) -- use_regex: Set to "true" to treat search as a regex pattern (default: false) -- ignore_case: Set to "true" to ignore case when matching (default: false) - -Notes: -- When use_regex is true, the search parameter is treated as a regular expression pattern -- When ignore_case is true, the search is case-insensitive regardless of regex mode - -Examples: - -1. Simple text replacement: - -example.ts -oldText -newText - - -2. Case-insensitive regex pattern: - -example.ts -old\w+ -new$& -true -true -` -} diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index ac30cc989615..c292a1854a96 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -2268,11 +2268,9 @@ export class Task extends EventEmitter implements TaskLike { // Check if task was aborted during the backoff if (this.abort) { - this.providerRef - .deref() - ?.log( - `[Task#${this.taskId}.${this.instanceId}] Task aborted during mid-stream retry backoff`, - ) + console.log( + `[Task#${this.taskId}.${this.instanceId}] Task aborted during mid-stream retry backoff`, + ) // Abort the entire task this.abortReason = "user_cancelled" await this.abortTask() diff --git a/src/core/tools/__tests__/listCodeDefinitionNamesTool.spec.ts b/src/core/tools/__tests__/listCodeDefinitionNamesTool.spec.ts new file mode 100644 index 000000000000..7a26c2f8eeb7 --- /dev/null +++ b/src/core/tools/__tests__/listCodeDefinitionNamesTool.spec.ts @@ -0,0 +1,351 @@ +// npx vitest src/core/tools/__tests__/listCodeDefinitionNamesTool.spec.ts + +import { describe, it, expect, vi, beforeEach } from "vitest" +import { listCodeDefinitionNamesTool } from "../listCodeDefinitionNamesTool" +import { Task } from "../../task/Task" +import { ToolUse } from "../../../shared/tools" +import * as treeSitter from "../../../services/tree-sitter" +import fs from "fs/promises" + +// Mock the tree-sitter service +vi.mock("../../../services/tree-sitter", () => ({ + parseSourceCodeDefinitionsForFile: vi.fn(), + parseSourceCodeForDefinitionsTopLevel: vi.fn(), +})) + +// Mock fs module +vi.mock("fs/promises", () => ({ + default: { + stat: vi.fn(), + }, +})) + +describe("listCodeDefinitionNamesTool", () => { + let mockTask: Partial + let mockAskApproval: any + let mockHandleError: any + let mockPushToolResult: any + let mockRemoveClosingTag: any + + beforeEach(() => { + vi.clearAllMocks() + + mockTask = { + cwd: "/test/path", + consecutiveMistakeCount: 0, + recordToolError: vi.fn(), + sayAndCreateMissingParamError: vi.fn(), + ask: vi.fn(), + fileContextTracker: { + trackFileContext: vi.fn(), + }, + providerRef: { + deref: vi.fn(() => ({ + getState: vi.fn(async () => ({ maxReadFileLine: -1 })), + })), + }, + rooIgnoreController: undefined, + } as any + + mockAskApproval = vi.fn(async () => true) + mockHandleError = vi.fn() + mockPushToolResult = vi.fn() + mockRemoveClosingTag = vi.fn((tag: string, value: string) => value) + }) + + describe("truncateDefinitionsToLineLimit", () => { + it("should not truncate when maxReadFileLine is -1 (no limit)", async () => { + const mockDefinitions = `# test.ts +10--20 | function foo() { +30--40 | function bar() { +50--60 | function baz() {` + + vi.mocked(treeSitter.parseSourceCodeDefinitionsForFile).mockResolvedValue(mockDefinitions) + + vi.mocked(fs.stat).mockResolvedValue({ + isFile: () => true, + isDirectory: () => false, + } as any) + + mockTask.providerRef = { + deref: vi.fn(() => ({ + getState: vi.fn(async () => ({ maxReadFileLine: -1 })), + })), + } as any + + const block: ToolUse = { + type: "tool_use", + name: "list_code_definition_names", + params: { path: "test.ts" }, + partial: false, + } + + await listCodeDefinitionNamesTool( + mockTask as Task, + block, + mockAskApproval, + mockHandleError, + mockPushToolResult, + mockRemoveClosingTag, + ) + + expect(mockPushToolResult).toHaveBeenCalledWith(mockDefinitions) + }) + + it("should not truncate when maxReadFileLine is 0 (definitions only mode)", async () => { + const mockDefinitions = `# test.ts +10--20 | function foo() { +30--40 | function bar() { +50--60 | function baz() {` + + vi.mocked(treeSitter.parseSourceCodeDefinitionsForFile).mockResolvedValue(mockDefinitions) + + vi.mocked(fs.stat).mockResolvedValue({ + isFile: () => true, + isDirectory: () => false, + } as any) + + mockTask.providerRef = { + deref: vi.fn(() => ({ + getState: vi.fn(async () => ({ maxReadFileLine: 0 })), + })), + } as any + + const block: ToolUse = { + type: "tool_use", + name: "list_code_definition_names", + params: { path: "test.ts" }, + partial: false, + } + + await listCodeDefinitionNamesTool( + mockTask as Task, + block, + mockAskApproval, + mockHandleError, + mockPushToolResult, + mockRemoveClosingTag, + ) + + expect(mockPushToolResult).toHaveBeenCalledWith(mockDefinitions) + }) + + it("should truncate definitions when maxReadFileLine is set", async () => { + const mockDefinitions = `# test.ts +10--20 | function foo() { +30--40 | function bar() { +50--60 | function baz() {` + + vi.mocked(treeSitter.parseSourceCodeDefinitionsForFile).mockResolvedValue(mockDefinitions) + + vi.mocked(fs.stat).mockResolvedValue({ + isFile: () => true, + isDirectory: () => false, + } as any) + + mockTask.providerRef = { + deref: vi.fn(() => ({ + getState: vi.fn(async () => ({ maxReadFileLine: 25 })), + })), + } as any + + const block: ToolUse = { + type: "tool_use", + name: "list_code_definition_names", + params: { path: "test.ts" }, + partial: false, + } + + await listCodeDefinitionNamesTool( + mockTask as Task, + block, + mockAskApproval, + mockHandleError, + mockPushToolResult, + mockRemoveClosingTag, + ) + + // Should only include definitions starting at or before line 25 + const expectedResult = `# test.ts +10--20 | function foo() {` + + expect(mockPushToolResult).toHaveBeenCalledWith(expectedResult) + }) + + it("should include definitions that start within limit even if they end beyond it", async () => { + const mockDefinitions = `# test.ts +10--50 | function foo() { +60--80 | function bar() {` + + vi.mocked(treeSitter.parseSourceCodeDefinitionsForFile).mockResolvedValue(mockDefinitions) + + vi.mocked(fs.stat).mockResolvedValue({ + isFile: () => true, + isDirectory: () => false, + } as any) + + mockTask.providerRef = { + deref: vi.fn(() => ({ + getState: vi.fn(async () => ({ maxReadFileLine: 30 })), + })), + } as any + + const block: ToolUse = { + type: "tool_use", + name: "list_code_definition_names", + params: { path: "test.ts" }, + partial: false, + } + + await listCodeDefinitionNamesTool( + mockTask as Task, + block, + mockAskApproval, + mockHandleError, + mockPushToolResult, + mockRemoveClosingTag, + ) + + // Should include foo (starts at 10) but not bar (starts at 60) + const expectedResult = `# test.ts +10--50 | function foo() {` + + expect(mockPushToolResult).toHaveBeenCalledWith(expectedResult) + }) + + it("should handle single-line definitions", async () => { + const mockDefinitions = `# test.ts +10 | const foo = 1 +20 | const bar = 2 +30 | const baz = 3` + + vi.mocked(treeSitter.parseSourceCodeDefinitionsForFile).mockResolvedValue(mockDefinitions) + + vi.mocked(fs.stat).mockResolvedValue({ + isFile: () => true, + isDirectory: () => false, + } as any) + + mockTask.providerRef = { + deref: vi.fn(() => ({ + getState: vi.fn(async () => ({ maxReadFileLine: 25 })), + })), + } as any + + const block: ToolUse = { + type: "tool_use", + name: "list_code_definition_names", + params: { path: "test.ts" }, + partial: false, + } + + await listCodeDefinitionNamesTool( + mockTask as Task, + block, + mockAskApproval, + mockHandleError, + mockPushToolResult, + mockRemoveClosingTag, + ) + + // Should include foo and bar but not baz + const expectedResult = `# test.ts +10 | const foo = 1 +20 | const bar = 2` + + expect(mockPushToolResult).toHaveBeenCalledWith(expectedResult) + }) + + it("should preserve header line when truncating", async () => { + const mockDefinitions = `# test.ts +100--200 | function foo() {` + + vi.mocked(treeSitter.parseSourceCodeDefinitionsForFile).mockResolvedValue(mockDefinitions) + + vi.mocked(fs.stat).mockResolvedValue({ + isFile: () => true, + isDirectory: () => false, + } as any) + + mockTask.providerRef = { + deref: vi.fn(() => ({ + getState: vi.fn(async () => ({ maxReadFileLine: 50 })), + })), + } as any + + const block: ToolUse = { + type: "tool_use", + name: "list_code_definition_names", + params: { path: "test.ts" }, + partial: false, + } + + await listCodeDefinitionNamesTool( + mockTask as Task, + block, + mockAskApproval, + mockHandleError, + mockPushToolResult, + mockRemoveClosingTag, + ) + + // Should keep header but exclude all definitions beyond line 50 + const expectedResult = `# test.ts` + + expect(mockPushToolResult).toHaveBeenCalledWith(expectedResult) + }) + }) + + it("should handle missing path parameter", async () => { + const block: ToolUse = { + type: "tool_use", + name: "list_code_definition_names", + params: {}, + partial: false, + } + + mockTask.sayAndCreateMissingParamError = vi.fn(async () => "Missing parameter: path") + + await listCodeDefinitionNamesTool( + mockTask as Task, + block, + mockAskApproval, + mockHandleError, + mockPushToolResult, + mockRemoveClosingTag, + ) + + expect(mockTask.consecutiveMistakeCount).toBe(1) + expect(mockTask.recordToolError).toHaveBeenCalledWith("list_code_definition_names") + expect(mockPushToolResult).toHaveBeenCalledWith("Missing parameter: path") + }) + + it("should handle directory path", async () => { + const mockDefinitions = "# Directory definitions" + + vi.mocked(treeSitter.parseSourceCodeForDefinitionsTopLevel).mockResolvedValue(mockDefinitions) + + vi.mocked(fs.stat).mockResolvedValue({ + isFile: () => false, + isDirectory: () => true, + } as any) + + const block: ToolUse = { + type: "tool_use", + name: "list_code_definition_names", + params: { path: "src" }, + partial: false, + } + + await listCodeDefinitionNamesTool( + mockTask as Task, + block, + mockAskApproval, + mockHandleError, + mockPushToolResult, + mockRemoveClosingTag, + ) + + expect(mockPushToolResult).toHaveBeenCalledWith(mockDefinitions) + }) +}) diff --git a/src/core/tools/__tests__/readFileTool.spec.ts b/src/core/tools/__tests__/readFileTool.spec.ts index 04016f708805..12e7d5755054 100644 --- a/src/core/tools/__tests__/readFileTool.spec.ts +++ b/src/core/tools/__tests__/readFileTool.spec.ts @@ -414,6 +414,61 @@ describe("read_file tool with maxReadFileLine setting", () => { expect(result).toContain(``) expect(result).toContain("Showing only 3 of 5 total lines") }) + + it("should truncate code definitions when file exceeds maxReadFileLine", async () => { + // Setup - file with 100 lines but we'll only read first 30 + const content = "Line 1\nLine 2\nLine 3" + const numberedContent = "1 | Line 1\n2 | Line 2\n3 | Line 3" + const fullDefinitions = `# file.txt +10--20 | function foo() { +50--60 | function bar() { +80--90 | function baz() {` + const truncatedDefinitions = `# file.txt +10--20 | function foo() {` + + mockedReadLines.mockResolvedValue(content) + mockedParseSourceCodeDefinitionsForFile.mockResolvedValue(fullDefinitions) + addLineNumbersMock.mockReturnValue(numberedContent) + + // Execute with maxReadFileLine = 30 + const result = await executeReadFileTool({}, { maxReadFileLine: 30, totalLines: 100 }) + + // Verify that only definitions within the first 30 lines are included + expect(result).toContain(`${testFilePath}`) + expect(result).toContain(``) + expect(result).toContain(``) + + // Should include foo (starts at line 10) but not bar (starts at line 50) or baz (starts at line 80) + expect(result).toContain("10--20 | function foo()") + expect(result).not.toContain("50--60 | function bar()") + expect(result).not.toContain("80--90 | function baz()") + + expect(result).toContain("Showing only 30 of 100 total lines") + }) + + it("should handle truncation when all definitions are beyond the line limit", async () => { + // Setup - all definitions start after maxReadFileLine + const content = "Line 1\nLine 2\nLine 3" + const numberedContent = "1 | Line 1\n2 | Line 2\n3 | Line 3" + const fullDefinitions = `# file.txt +50--60 | function foo() { +80--90 | function bar() {` + + mockedReadLines.mockResolvedValue(content) + mockedParseSourceCodeDefinitionsForFile.mockResolvedValue(fullDefinitions) + addLineNumbersMock.mockReturnValue(numberedContent) + + // Execute with maxReadFileLine = 30 + const result = await executeReadFileTool({}, { maxReadFileLine: 30, totalLines: 100 }) + + // Verify that only the header is included (all definitions filtered out) + expect(result).toContain(`${testFilePath}`) + expect(result).toContain(``) + expect(result).toContain(``) + expect(result).toContain("# file.txt") + expect(result).not.toContain("50--60 | function foo()") + expect(result).not.toContain("80--90 | function bar()") + }) }) describe("when maxReadFileLine equals or exceeds file length", () => { diff --git a/src/core/tools/__tests__/truncateDefinitions.spec.ts b/src/core/tools/__tests__/truncateDefinitions.spec.ts new file mode 100644 index 000000000000..06cbb03e9046 --- /dev/null +++ b/src/core/tools/__tests__/truncateDefinitions.spec.ts @@ -0,0 +1,160 @@ +import { describe, it, expect } from "vitest" +import { truncateDefinitionsToLineLimit } from "../helpers/truncateDefinitions" + +describe("truncateDefinitionsToLineLimit", () => { + it("should not truncate when maxReadFileLine is -1 (no limit)", () => { + const definitions = `# test.ts +10--20 | function foo() { +30--40 | function bar() { +50--60 | function baz() {` + + const result = truncateDefinitionsToLineLimit(definitions, -1) + expect(result).toBe(definitions) + }) + + it("should not truncate when maxReadFileLine is 0 (definitions only mode)", () => { + const definitions = `# test.ts +10--20 | function foo() { +30--40 | function bar() { +50--60 | function baz() {` + + const result = truncateDefinitionsToLineLimit(definitions, 0) + expect(result).toBe(definitions) + }) + + it("should truncate definitions beyond the line limit", () => { + const definitions = `# test.ts +10--20 | function foo() { +30--40 | function bar() { +50--60 | function baz() {` + + const result = truncateDefinitionsToLineLimit(definitions, 25) + const expected = `# test.ts +10--20 | function foo() {` + + expect(result).toBe(expected) + }) + + it("should include definitions that start within limit even if they end beyond it", () => { + const definitions = `# test.ts +10--50 | function foo() { +60--80 | function bar() {` + + const result = truncateDefinitionsToLineLimit(definitions, 30) + const expected = `# test.ts +10--50 | function foo() {` + + expect(result).toBe(expected) + }) + + it("should handle single-line definitions", () => { + const definitions = `# test.ts +10 | const foo = 1 +20 | const bar = 2 +30 | const baz = 3` + + const result = truncateDefinitionsToLineLimit(definitions, 25) + const expected = `# test.ts +10 | const foo = 1 +20 | const bar = 2` + + expect(result).toBe(expected) + }) + + it("should preserve header line when all definitions are beyond limit", () => { + const definitions = `# test.ts +100--200 | function foo() {` + + const result = truncateDefinitionsToLineLimit(definitions, 50) + const expected = `# test.ts` + + expect(result).toBe(expected) + }) + + it("should handle empty definitions", () => { + const definitions = `# test.ts` + + const result = truncateDefinitionsToLineLimit(definitions, 50) + expect(result).toBe(definitions) + }) + + it("should handle definitions without header", () => { + const definitions = `10--20 | function foo() { +30--40 | function bar() {` + + const result = truncateDefinitionsToLineLimit(definitions, 25) + const expected = `10--20 | function foo() {` + + expect(result).toBe(expected) + }) + + it("should not preserve empty lines (only definition lines)", () => { + const definitions = `# test.ts +10--20 | function foo() { + +30--40 | function bar() {` + + const result = truncateDefinitionsToLineLimit(definitions, 25) + const expected = `# test.ts +10--20 | function foo() {` + + expect(result).toBe(expected) + }) + + it("should handle mixed single and range definitions", () => { + const definitions = `# test.ts +5 | const x = 1 +10--20 | function foo() { +25 | const y = 2 +30--40 | function bar() {` + + const result = truncateDefinitionsToLineLimit(definitions, 26) + const expected = `# test.ts +5 | const x = 1 +10--20 | function foo() { +25 | const y = 2` + + expect(result).toBe(expected) + }) + + it("should handle definitions at exactly the limit", () => { + const definitions = `# test.ts +10--20 | function foo() { +30--40 | function bar() { +50--60 | function baz() {` + + const result = truncateDefinitionsToLineLimit(definitions, 30) + const expected = `# test.ts +10--20 | function foo() { +30--40 | function bar() {` + + expect(result).toBe(expected) + }) + + it("should handle definitions with leading whitespace", () => { + const definitions = `# test.ts + 10--20 | function foo() { + 30--40 | function bar() { + 50--60 | function baz() {` + + const result = truncateDefinitionsToLineLimit(definitions, 25) + const expected = `# test.ts + 10--20 | function foo() {` + + expect(result).toBe(expected) + }) + + it("should handle definitions with mixed whitespace patterns", () => { + const definitions = `# test.ts +10--20 | function foo() { + 30--40 | function bar() { + 50--60 | function baz() {` + + const result = truncateDefinitionsToLineLimit(definitions, 35) + const expected = `# test.ts +10--20 | function foo() { + 30--40 | function bar() {` + + expect(result).toBe(expected) + }) +}) diff --git a/src/core/tools/helpers/truncateDefinitions.ts b/src/core/tools/helpers/truncateDefinitions.ts new file mode 100644 index 000000000000..7c193ef52a5f --- /dev/null +++ b/src/core/tools/helpers/truncateDefinitions.ts @@ -0,0 +1,44 @@ +/** + * Truncate code definitions to only include those within the line limit + * @param definitions - The full definitions string from parseSourceCodeDefinitionsForFile + * @param maxReadFileLine - Maximum line number to include (-1 for no limit, 0 for definitions only) + * @returns Truncated definitions string + */ +export function truncateDefinitionsToLineLimit(definitions: string, maxReadFileLine: number): string { + // If no limit or definitions-only mode (0), return as-is + if (maxReadFileLine <= 0) { + return definitions + } + + const lines = definitions.split("\n") + const result: string[] = [] + let startIndex = 0 + + // Keep the header line (e.g., "# filename.ts") + if (lines.length > 0 && lines[0].startsWith("#")) { + result.push(lines[0]) + startIndex = 1 + } + + // Process definition lines + for (let i = startIndex; i < lines.length; i++) { + const line = lines[i] + + // Match definition format: "startLine--endLine | content" or "lineNumber | content" + // Allow optional leading whitespace to handle indented output or CRLF artifacts + const rangeMatch = line.match(/^\s*(\d+)(?:--(\d+))?\s*\|/) + + if (rangeMatch) { + const startLine = parseInt(rangeMatch[1], 10) + + // Only include definitions that start within the truncated range + if (startLine <= maxReadFileLine) { + result.push(line) + } + } + // Note: We don't preserve empty lines or other non-definition content + // as they're not part of the actual code definitions + } + + return result.join("\n") +} diff --git a/src/core/tools/listCodeDefinitionNamesTool.ts b/src/core/tools/listCodeDefinitionNamesTool.ts index 6ceec0a7257a..0ec80ce9bd04 100644 --- a/src/core/tools/listCodeDefinitionNamesTool.ts +++ b/src/core/tools/listCodeDefinitionNamesTool.ts @@ -8,6 +8,7 @@ import { getReadablePath } from "../../utils/path" import { isPathOutsideWorkspace } from "../../utils/pathUtils" import { parseSourceCodeForDefinitionsTopLevel, parseSourceCodeDefinitionsForFile } from "../../services/tree-sitter" import { RecordSource } from "../context-tracking/FileContextTrackerTypes" +import { truncateDefinitionsToLineLimit } from "./helpers/truncateDefinitions" export async function listCodeDefinitionNamesTool( cline: Task, @@ -51,7 +52,14 @@ export async function listCodeDefinitionNamesTool( if (stats.isFile()) { const fileResult = await parseSourceCodeDefinitionsForFile(absolutePath, cline.rooIgnoreController) - result = fileResult ?? "No source code definitions found in cline file." + + // Apply truncation based on maxReadFileLine setting + if (fileResult) { + const { maxReadFileLine = -1 } = (await cline.providerRef.deref()?.getState()) ?? {} + result = truncateDefinitionsToLineLimit(fileResult, maxReadFileLine) + } else { + result = "No source code definitions found in file." + } } else if (stats.isDirectory()) { result = await parseSourceCodeForDefinitionsTopLevel(absolutePath, cline.rooIgnoreController) } else { diff --git a/src/core/tools/readFileTool.ts b/src/core/tools/readFileTool.ts index 9dce874336c8..896e5df1319f 100644 --- a/src/core/tools/readFileTool.ts +++ b/src/core/tools/readFileTool.ts @@ -25,6 +25,7 @@ import { } from "./helpers/imageHelpers" import { validateFileTokenBudget, truncateFileContent } from "./helpers/fileTokenBudget" import { DEFAULT_FILE_READ_CHARACTER_LIMIT } from "@roo-code/types" +import { truncateDefinitionsToLineLimit } from "./helpers/truncateDefinitions" export function getReadFileToolDescription(blockName: string, blockParams: any): string { // Handle both single path and multiple files via args @@ -592,7 +593,9 @@ export async function readFileTool( try { const defResult = await parseSourceCodeDefinitionsForFile(fullPath, cline.rooIgnoreController) if (defResult) { - xmlInfo += `${defResult}\n` + // Truncate definitions to match the truncated file content + const truncatedDefs = truncateDefinitionsToLineLimit(defResult, maxReadFileLine) + xmlInfo += `${truncatedDefs}\n` } xmlInfo += `Showing only ${maxReadFileLine} of ${totalLines} total lines. Use line_range if you need to read more lines\n` updateFileResult(relPath, { diff --git a/src/core/tools/searchAndReplaceTool.ts b/src/core/tools/searchAndReplaceTool.ts deleted file mode 100644 index c8cbcad6d5f7..000000000000 --- a/src/core/tools/searchAndReplaceTool.ts +++ /dev/null @@ -1,310 +0,0 @@ -// Core Node.js imports -import path from "path" -import fs from "fs/promises" -import delay from "delay" -import * as vscode from "vscode" - -// Internal imports -import { readFileWithEncodingDetection } from "../../utils/encoding" -import { Task } from "../task/Task" -import { AskApproval, HandleError, PushToolResult, RemoveClosingTag, ToolUse } from "../../shared/tools" -import { formatResponse } from "../prompts/responses" -import { ClineSayTool } from "../../shared/ExtensionMessage" -import { getReadablePath } from "../../utils/path" -import { fileExistsAtPath } from "../../utils/fs" -import { getLanguage } from "../../utils/file" -import { getDiffLines } from "../../utils/diffLines" -import { autoCommit } from "../../utils/git" -import { RecordSource } from "../context-tracking/FileContextTrackerTypes" -import { DEFAULT_WRITE_DELAY_MS } from "@roo-code/types" -import { EXPERIMENT_IDS, experiments } from "../../shared/experiments" -import { TelemetryService } from "@roo-code/telemetry" -import { CodeReviewService } from "../costrict/code-review" - -/** - * Tool for performing search and replace operations on files - * Supports regex and case-sensitive/insensitive matching - */ - -/** - * Validates required parameters for search and replace operation - */ -async function validateParams( - cline: Task, - relPath: string | undefined, - search: string | undefined, - replace: string | undefined, - pushToolResult: PushToolResult, -): Promise { - if (!relPath) { - cline.consecutiveMistakeCount++ - cline.recordToolError("search_and_replace") - pushToolResult(await cline.sayAndCreateMissingParamError("search_and_replace", "path")) - return false - } - - if (!search) { - cline.consecutiveMistakeCount++ - cline.recordToolError("search_and_replace") - pushToolResult(await cline.sayAndCreateMissingParamError("search_and_replace", "search")) - return false - } - - if (replace === undefined) { - cline.consecutiveMistakeCount++ - cline.recordToolError("search_and_replace") - pushToolResult(await cline.sayAndCreateMissingParamError("search_and_replace", "replace")) - return false - } - - return true -} - -/** - * Performs search and replace operations on a file - * @param cline - Cline instance - * @param block - Tool use parameters - * @param askApproval - Function to request user approval - * @param handleError - Function to handle errors - * @param pushToolResult - Function to push tool results - * @param removeClosingTag - Function to remove closing tags - */ -export async function searchAndReplaceTool( - cline: Task, - block: ToolUse, - askApproval: AskApproval, - handleError: HandleError, - pushToolResult: PushToolResult, - removeClosingTag: RemoveClosingTag, -): Promise { - // Extract and validate parameters - const relPath: string | undefined = block.params.path - const search: string | undefined = block.params.search - const replace: string | undefined = block.params.replace - const useRegex: boolean = block.params.use_regex === "true" - const ignoreCase: boolean = block.params.ignore_case === "true" - const startLine: number | undefined = block.params.start_line ? parseInt(block.params.start_line, 10) : undefined - const endLine: number | undefined = block.params.end_line ? parseInt(block.params.end_line, 10) : undefined - - try { - // Handle partial tool use - if (block.partial) { - const partialMessageProps = { - tool: "searchAndReplace" as const, - path: getReadablePath(cline.cwd, removeClosingTag("path", relPath)), - search: removeClosingTag("search", search), - replace: removeClosingTag("replace", replace), - useRegex: block.params.use_regex === "true", - ignoreCase: block.params.ignore_case === "true", - startLine, - endLine, - } - await cline.ask("tool", JSON.stringify(partialMessageProps), block.partial).catch(() => {}) - return - } - - // Validate required parameters - if (!(await validateParams(cline, relPath, search, replace, pushToolResult))) { - return - } - - // At this point we know relPath, search and replace are defined - const validRelPath = relPath as string - const validSearch = search as string - const validReplace = replace as string - - const sharedMessageProps: ClineSayTool = { - tool: "searchAndReplace", - path: getReadablePath(cline.cwd, validRelPath), - search: validSearch, - replace: validReplace, - useRegex: useRegex, - ignoreCase: ignoreCase, - startLine: startLine, - endLine: endLine, - } - - const accessAllowed = cline.rooIgnoreController?.validateAccess(validRelPath) - - if (!accessAllowed) { - await cline.say("rooignore_error", validRelPath) - pushToolResult(formatResponse.toolError(formatResponse.rooIgnoreError(validRelPath))) - return - } - - // Check if file is write-protected - const isWriteProtected = cline.rooProtectedController?.isWriteProtected(validRelPath) || false - - const absolutePath = path.resolve(cline.cwd, validRelPath) - const fileExists = await fileExistsAtPath(absolutePath) - - if (!fileExists) { - cline.consecutiveMistakeCount++ - cline.recordToolError("search_and_replace") - const formattedError = formatResponse.toolError( - `File does not exist at path: ${absolutePath}\nThe specified file could not be found. Please verify the file path and try again.`, - ) - await cline.say("error", formattedError) - pushToolResult(formattedError) - return - } - - // Reset consecutive mistakes since all validations passed - cline.consecutiveMistakeCount = 0 - - // Read and process file content - let fileContent: string - try { - fileContent = await readFileWithEncodingDetection(absolutePath) - } catch (error) { - cline.consecutiveMistakeCount++ - cline.recordToolError("search_and_replace") - const errorMessage = `Error reading file: ${absolutePath}\nFailed to read the file content: ${ - error instanceof Error ? error.message : String(error) - }\nPlease verify file permissions and try again.` - const formattedError = formatResponse.toolError(errorMessage) - await cline.say("error", formattedError) - pushToolResult(formattedError) - return - } - - // Create search pattern and perform replacement - const flags = ignoreCase ? "gi" : "g" - const searchPattern = useRegex ? new RegExp(validSearch, flags) : new RegExp(escapeRegExp(validSearch), flags) - - let newContent: string - if (startLine !== undefined || endLine !== undefined) { - // Handle line-specific replacement - const lines = fileContent.split("\n") - const start = Math.max((startLine ?? 1) - 1, 0) - const end = Math.min((endLine ?? lines.length) - 1, lines.length - 1) - - // Get content before and after target section - const beforeLines = lines.slice(0, start) - const afterLines = lines.slice(end + 1) - - // Get and modify target section - const targetContent = lines.slice(start, end + 1).join("\n") - const modifiedContent = targetContent.replace(searchPattern, validReplace) - const modifiedLines = modifiedContent.split("\n") - - // Reconstruct full content - newContent = [...beforeLines, ...modifiedLines, ...afterLines].join("\n") - } else { - // Global replacement - newContent = fileContent.replace(searchPattern, validReplace) - } - - // Initialize diff view - cline.diffViewProvider.editType = "modify" - cline.diffViewProvider.originalContent = fileContent - - // Generate and validate diff - const diff = formatResponse.createPrettyPatch(validRelPath, fileContent, newContent) - if (!diff) { - pushToolResult(`No changes needed for '${relPath}'`) - await cline.diffViewProvider.reset() - return - } - - // Check if preventFocusDisruption experiment is enabled - const provider = cline.providerRef.deref() - const state = await provider?.getState() - const diagnosticsEnabled = state?.diagnosticsEnabled ?? true - const writeDelayMs = state?.writeDelayMs ?? DEFAULT_WRITE_DELAY_MS - const isPreventFocusDisruptionEnabled = experiments.isEnabled( - state?.experiments ?? {}, - EXPERIMENT_IDS.PREVENT_FOCUS_DISRUPTION, - ) - - const completeMessage = JSON.stringify({ - ...sharedMessageProps, - diff, - isProtected: isWriteProtected, - } satisfies ClineSayTool) - - // Show diff view if focus disruption prevention is disabled - if (!isPreventFocusDisruptionEnabled) { - await cline.diffViewProvider.open(validRelPath) - await cline.diffViewProvider.update(newContent, true) - cline.diffViewProvider.scrollToFirstDiff() - } - - const language = await getLanguage(validRelPath) - const diffLines = getDiffLines(fileContent, newContent) - const didApprove = await askApproval("tool", completeMessage, undefined, isWriteProtected) - - if (!didApprove) { - // Revert changes if diff view was shown - if (!isPreventFocusDisruptionEnabled) { - await cline.diffViewProvider.revertChanges() - } - pushToolResult("Changes were rejected by the user.") - await cline.diffViewProvider.reset() - TelemetryService.instance.captureCodeReject(language, diffLines) - return - } - - // Save the changes - if (isPreventFocusDisruptionEnabled) { - // Direct file write without diff view or opening the file - await cline.diffViewProvider.saveDirectly(validRelPath, newContent, false, diagnosticsEnabled, writeDelayMs) - } else { - // Call saveChanges to update the DiffViewProvider properties - await cline.diffViewProvider.saveChanges(diagnosticsEnabled, writeDelayMs) - } - - // Track file edit operation - if (relPath) { - await cline.fileContextTracker.trackFileContext(relPath, "roo_edited" as RecordSource) - } - - try { - TelemetryService.instance.captureCodeAccept(language, diffLines) - - // Check if AutoCommit is enabled before committing - const autoCommitEnabled = vscode.workspace.getConfiguration().get("AutoCommit", false) - if (autoCommitEnabled) { - autoCommit(relPath as string, cline.cwd, { - model: cline.api.getModel().id, - editorName: vscode.env.appName, - date: new Date().toLocaleString(), - }) - } - } catch (err) { - console.log(err) - } - const codeReviewService = CodeReviewService.getInstance() - codeReviewService.checkAndAcceptIssueByTaskId(cline.taskId) - - cline.didEditFile = true - - // Get the formatted response message - const message = await cline.diffViewProvider.pushToolWriteResult( - cline, - cline.cwd, - false, // Always false for search_and_replace - ) - - pushToolResult(message) - - // Record successful tool usage and cleanup - cline.recordToolUsage("search_and_replace") - await cline.diffViewProvider.reset() - - // Process any queued messages after file edit completes - cline.processQueuedMessages() - } catch (error) { - handleError("search and replace", error) - await cline.diffViewProvider.reset() - } -} - -/** - * Escapes special regex characters in a string - * @param input String to escape regex characters in - * @returns Escaped string safe for regex pattern matching - */ -function escapeRegExp(input: string): string { - return input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") -} diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index 84574c479174..1d6f0443d0e6 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -2842,18 +2842,26 @@ export const webviewMessageHandler = async ( return } if (manager.isFeatureEnabled && manager.isFeatureConfigured) { - if (!manager.isInitialized) { - await manager.initialize(provider.contextProxy) - } - - // startIndexing now handles error recovery internally - manager.startIndexing() - - // If startIndexing recovered from error, we need to reinitialize - if (!manager.isInitialized) { - await manager.initialize(provider.contextProxy) - // Try starting again after initialization + // Mimic extension startup behavior: initialize first, which will + // check if Qdrant container is active and reuse existing collection + await manager.initialize(provider.contextProxy) + + // Only call startIndexing if we're in a state that requires it + // (e.g., Standby or Error). If already Indexed or Indexing, the + // initialize() call above will have already started the watcher. + const currentState = manager.state + if (currentState === "Standby" || currentState === "Error") { + // startIndexing now handles error recovery internally manager.startIndexing() + + // If startIndexing recovered from error, we need to reinitialize + if (!manager.isInitialized) { + await manager.initialize(provider.contextProxy) + // Try starting again after initialization + if (manager.state === "Standby" || manager.state === "Error") { + manager.startIndexing() + } + } } } } catch (error) { diff --git a/src/extension.ts b/src/extension.ts index a277aaee6a7e..12142a9d8101 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -115,13 +115,13 @@ export async function activate(context: vscode.ExtensionContext) { // if (manager) { // codeIndexManagers.push(manager) - // try { - // await manager.initialize(contextProxy) - // } catch (error) { - // outputChannel.appendLine( - // `[CodeIndexManager] Error during background CodeIndexManager configuration/indexing for ${folder.uri.fsPath}: ${error.message || error}`, - // ) - // } + // // Initialize in background; do not block extension activation + // void manager.initialize(contextProxy).catch((error) => { + // const message = error instanceof Error ? error.message : String(error) + // outputChannel.appendLine( + // `[CodeIndexManager] Error during background CodeIndexManager configuration/indexing for ${folder.uri.fsPath}: ${message}`, + // ) + // }) // context.subscriptions.push(manager) // } diff --git a/src/package.json b/src/package.json index 027f569585ed..a8941ac29240 100644 --- a/src/package.json +++ b/src/package.json @@ -706,6 +706,13 @@ "default": "", "description": "%settings.autoImportSettingsPath.description%" }, + "zgsm.maximumIndexedFilesForFileSearch": { + "type": "number", + "default": 10000, + "minimum": 5000, + "maximum": 500000, + "description": "%settings.maximumIndexedFilesForFileSearch.description%" + }, "zgsm.useAgentRules": { "type": "boolean", "default": true, diff --git a/src/package.nls.json b/src/package.nls.json index 089e68c526bd..e9467e67fb3e 100644 --- a/src/package.nls.json +++ b/src/package.nls.json @@ -66,6 +66,7 @@ "settings.customStoragePath.description": "Custom storage path. Leave empty to use the default location. Supports absolute paths (e.g. 'D:\\CostrictCodeStorage')", "settings.enableCodeActions.description": "Enable CoStrict quick fixes", "settings.autoImportSettingsPath.description": "Path to a CoStrict configuration file to automatically import on extension startup. Supports absolute paths and paths relative to the home directory (e.g. '~/Documents/costrict-settings.json'). Leave empty to disable auto-import.", + "settings.maximumIndexedFilesForFileSearch.description": "Maximum number of files to index for the @ file search feature. Higher values provide better search results in large projects but may use more memory. Default: 10,000.", "settings.useAgentRules.description": "Enable loading of AGENTS.md files for agent-specific rules (see https://agent-rules.org/)", "settings.apiRequestTimeout.description": "Maximum time in seconds to wait for API responses (0 = no timeout, 1-3600s, default: 600s). Higher values are recommended for local providers like LM Studio and Ollama that may need more processing time.", "settings.apiResponseRenderMode.description": "API response rendering speed mode (fast = minimal delay, medium = balanced, slow = maximum compatibility). Higher values provide better compatibility but may feel less responsive.", diff --git a/src/package.nls.zh-CN.json b/src/package.nls.zh-CN.json index 7187f74e51fc..dd2bd93186d1 100644 --- a/src/package.nls.zh-CN.json +++ b/src/package.nls.zh-CN.json @@ -66,6 +66,7 @@ "settings.customStoragePath.description": "自定义存储路径。留空以使用默认位置。支持绝对路径(例如:'D:\\CostrictCodeStorage')", "settings.enableCodeActions.description": "启用 CoStrict 快速修复", "settings.autoImportSettingsPath.description": "CoStrict 配置文件的路径,用于在扩展启动时自动导入。支持绝对路径和相对于主目录的路径(例如 '~/Documents/costrict-settings.json')。留空以禁用自动导入。", + "settings.maximumIndexedFilesForFileSearch.description": "为 @ 文件搜索功能建立索引时要索引的最大文件数。较大的值在大型项目中提供更好的搜索结果,但可能占用更多内存。默认值:10,000。", "settings.useAgentRules.description": "为特定于代理的规则启用 AGENTS.md 文件的加载(请参阅 https://agent-rules.org/)", "settings.autoCommit.description": "在接受大模型的变更后,自动提交当前变更文件", "settings.apiResponseRenderMode.description": "API 响应渲染速度模式(fast = 最小延迟,medium = 平衡模式,slow = 最大兼容性)。更高的值提供更好的兼容性但可能感觉响应较慢。", diff --git a/src/package.nls.zh-TW.json b/src/package.nls.zh-TW.json index 60943dcede94..3f02e053bb7b 100644 --- a/src/package.nls.zh-TW.json +++ b/src/package.nls.zh-TW.json @@ -66,6 +66,7 @@ "settings.customStoragePath.description": "自訂儲存路徑。留空以使用預設位置。支援絕對路徑(例如:'D:\\CostrictCodeStorage')", "settings.enableCodeActions.description": "啟用 CoStrict 快速修復。", "settings.autoImportSettingsPath.description": "CoStrict 設定檔案的路徑,用於在擴充功能啟動時自動匯入。支援絕對路徑和相對於主目錄的路徑(例如 '~/Documents/costrict-settings.json')。留空以停用自動匯入。", + "settings.maximumIndexedFilesForFileSearch.description": "為 @ 檔案搜尋功能建立索引時要索引的最大檔案數。較大的值在大型專案中提供更好的搜尋結果,但可能佔用更多記憶體。預設值:10,000。", "settings.useAgentRules.description": "為特定於代理的規則啟用 AGENTS.md 檔案的載入(請參閱 https://agent-rules.org/)", "settings.autoCommit.description": "在接受大模型的變更後,自動提交當前變更檔案", "settings.apiResponseRenderMode.description": "API 回應渲染速度模式(fast = 最小延遲,medium = 平衡模式,slow = 最大相容性)。更高的值提供更好的相容性但可能感覺回應較慢。", diff --git a/src/services/code-index/__tests__/orchestrator.spec.ts b/src/services/code-index/__tests__/orchestrator.spec.ts new file mode 100644 index 000000000000..aab1ef888d3d --- /dev/null +++ b/src/services/code-index/__tests__/orchestrator.spec.ts @@ -0,0 +1,160 @@ +import { describe, it, expect, beforeEach, vi } from "vitest" +import { CodeIndexOrchestrator } from "../orchestrator" + +// Mock vscode workspace so startIndexing passes workspace check +vi.mock("vscode", () => { + const path = require("path") + const testWorkspacePath = path.join(path.sep, "test", "workspace") + return { + window: { + activeTextEditor: null, + }, + workspace: { + workspaceFolders: [ + { + uri: { fsPath: testWorkspacePath }, + name: "test", + index: 0, + }, + ], + createFileSystemWatcher: vi.fn().mockReturnValue({ + onDidCreate: vi.fn().mockReturnValue({ dispose: vi.fn() }), + onDidChange: vi.fn().mockReturnValue({ dispose: vi.fn() }), + onDidDelete: vi.fn().mockReturnValue({ dispose: vi.fn() }), + dispose: vi.fn(), + }), + }, + RelativePattern: vi.fn().mockImplementation((base: string, pattern: string) => ({ base, pattern })), + } +}) + +// Mock TelemetryService +vi.mock("@roo-code/telemetry", () => ({ + TelemetryService: { + instance: { + captureEvent: vi.fn(), + }, + }, +})) + +// Mock i18n translator used in orchestrator messages +vi.mock("../../i18n", () => ({ + t: (key: string, params?: any) => { + if (key === "embeddings:orchestrator.failedDuringInitialScan" && params?.errorMessage) { + return `Failed during initial scan: ${params.errorMessage}` + } + return key + }, +})) + +describe("CodeIndexOrchestrator - error path cleanup gating", () => { + const workspacePath = "/test/workspace" + + let configManager: any + let stateManager: any + let cacheManager: any + let vectorStore: any + let scanner: any + let fileWatcher: any + + beforeEach(() => { + vi.clearAllMocks() + + configManager = { + isFeatureConfigured: true, + } + + // Minimal state manager that tracks state transitions + let currentState = "Standby" + stateManager = { + get state() { + return currentState + }, + setSystemState: vi.fn().mockImplementation((state: string, _msg: string) => { + currentState = state + }), + reportFileQueueProgress: vi.fn(), + reportBlockIndexingProgress: vi.fn(), + } + + cacheManager = { + clearCacheFile: vi.fn().mockResolvedValue(undefined), + } + + vectorStore = { + initialize: vi.fn(), + hasIndexedData: vi.fn(), + markIndexingIncomplete: vi.fn(), + markIndexingComplete: vi.fn(), + clearCollection: vi.fn().mockResolvedValue(undefined), + } + + scanner = { + scanDirectory: vi.fn(), + } + + fileWatcher = { + initialize: vi.fn().mockResolvedValue(undefined), + onDidStartBatchProcessing: vi.fn().mockReturnValue({ dispose: vi.fn() }), + onBatchProgressUpdate: vi.fn().mockReturnValue({ dispose: vi.fn() }), + onDidFinishBatchProcessing: vi.fn().mockReturnValue({ dispose: vi.fn() }), + dispose: vi.fn(), + } + }) + + it("should not call clearCollection() or clear cache when initialize() fails (indexing not started)", async () => { + // Arrange: fail at initialize() + vectorStore.initialize.mockRejectedValue(new Error("Qdrant unreachable")) + + const orchestrator = new CodeIndexOrchestrator( + configManager, + stateManager, + workspacePath, + cacheManager, + vectorStore, + scanner, + fileWatcher, + ) + + // Act + await orchestrator.startIndexing() + + // Assert + expect(vectorStore.clearCollection).not.toHaveBeenCalled() + expect(cacheManager.clearCacheFile).not.toHaveBeenCalled() + + // Error state should be set + expect(stateManager.setSystemState).toHaveBeenCalled() + const lastCall = stateManager.setSystemState.mock.calls[stateManager.setSystemState.mock.calls.length - 1] + expect(lastCall[0]).toBe("Error") + }) + + it("should call clearCollection() and clear cache when an error occurs after initialize() succeeds (indexing started)", async () => { + // Arrange: initialize succeeds; fail soon after to enter error path with indexingStarted=true + vectorStore.initialize.mockResolvedValue(false) // existing collection + vectorStore.hasIndexedData.mockResolvedValue(false) // force full scan path + vectorStore.markIndexingIncomplete.mockRejectedValue(new Error("mark incomplete failure")) + + const orchestrator = new CodeIndexOrchestrator( + configManager, + stateManager, + workspacePath, + cacheManager, + vectorStore, + scanner, + fileWatcher, + ) + + // Act + await orchestrator.startIndexing() + + // Assert: cleanup gated behind indexingStarted should have happened + expect(vectorStore.clearCollection).toHaveBeenCalledTimes(1) + expect(cacheManager.clearCacheFile).toHaveBeenCalledTimes(1) + + // Error state should be set + expect(stateManager.setSystemState).toHaveBeenCalled() + const lastCall = stateManager.setSystemState.mock.calls[stateManager.setSystemState.mock.calls.length - 1] + expect(lastCall[0]).toBe("Error") + }) +}) diff --git a/src/services/code-index/interfaces/vector-store.ts b/src/services/code-index/interfaces/vector-store.ts index dde602fb4d9a..7946563fd57f 100644 --- a/src/services/code-index/interfaces/vector-store.ts +++ b/src/services/code-index/interfaces/vector-store.ts @@ -62,6 +62,24 @@ export interface IVectorStore { * @returns Promise resolving to boolean indicating if the collection exists */ collectionExists(): Promise + + /** + * Checks if the collection exists and has indexed points + * @returns Promise resolving to boolean indicating if the collection exists and has points + */ + hasIndexedData(): Promise + + /** + * Marks the indexing process as complete by storing metadata + * Should be called after a successful full workspace scan or incremental scan + */ + markIndexingComplete(): Promise + + /** + * Marks the indexing process as incomplete by storing metadata + * Should be called at the start of indexing to indicate work in progress + */ + markIndexingIncomplete(): Promise } export interface VectorStoreSearchResult { diff --git a/src/services/code-index/orchestrator.ts b/src/services/code-index/orchestrator.ts index fbc4a2411850..99f317882b84 100644 --- a/src/services/code-index/orchestrator.ts +++ b/src/services/code-index/orchestrator.ts @@ -123,86 +123,164 @@ export class CodeIndexOrchestrator { this._isProcessing = true this.stateManager.setSystemState("Indexing", "Initializing services...") + // Track whether we successfully connected to Qdrant and started indexing + // This helps us decide whether to preserve cache on error + let indexingStarted = false + try { const collectionCreated = await this.vectorStore.initialize() + // Successfully connected to Qdrant + indexingStarted = true + if (collectionCreated) { await this.cacheManager.clearCacheFile() } - this.stateManager.setSystemState("Indexing", "Services ready. Starting workspace scan...") + // Check if the collection already has indexed data + // If it does, we can skip the full scan and just start the watcher + const hasExistingData = await this.vectorStore.hasIndexedData() - let cumulativeBlocksIndexed = 0 - let cumulativeBlocksFoundSoFar = 0 - let batchErrors: Error[] = [] + if (hasExistingData && !collectionCreated) { + // Collection exists with data - run incremental scan to catch any new/changed files + // This handles files added while workspace was closed or Qdrant was inactive + console.log( + "[CodeIndexOrchestrator] Collection already has indexed data. Running incremental scan for new/changed files...", + ) + this.stateManager.setSystemState("Indexing", "Checking for new or modified files...") - const handleFileParsed = (fileBlockCount: number) => { - cumulativeBlocksFoundSoFar += fileBlockCount - this.stateManager.reportBlockIndexingProgress(cumulativeBlocksIndexed, cumulativeBlocksFoundSoFar) - } + // Mark as incomplete at the start of incremental scan + await this.vectorStore.markIndexingIncomplete() - const handleBlocksIndexed = (indexedCount: number) => { - cumulativeBlocksIndexed += indexedCount - this.stateManager.reportBlockIndexingProgress(cumulativeBlocksIndexed, cumulativeBlocksFoundSoFar) - } + let cumulativeBlocksIndexed = 0 + let cumulativeBlocksFoundSoFar = 0 + let batchErrors: Error[] = [] - const result = await this.scanner.scanDirectory( - this.workspacePath, - (batchError: Error) => { - console.error( - `[CodeIndexOrchestrator] Error during initial scan batch: ${batchError.message}`, - batchError, - ) - batchErrors.push(batchError) - }, - handleBlocksIndexed, - handleFileParsed, - ) + const handleFileParsed = (fileBlockCount: number) => { + cumulativeBlocksFoundSoFar += fileBlockCount + this.stateManager.reportBlockIndexingProgress(cumulativeBlocksIndexed, cumulativeBlocksFoundSoFar) + } - if (!result) { - throw new Error("Scan failed, is scanner initialized?") - } + const handleBlocksIndexed = (indexedCount: number) => { + cumulativeBlocksIndexed += indexedCount + this.stateManager.reportBlockIndexingProgress(cumulativeBlocksIndexed, cumulativeBlocksFoundSoFar) + } - const { stats } = result + // Run incremental scan - scanner will skip unchanged files using cache + const result = await this.scanner.scanDirectory( + this.workspacePath, + (batchError: Error) => { + console.error( + `[CodeIndexOrchestrator] Error during incremental scan batch: ${batchError.message}`, + batchError, + ) + batchErrors.push(batchError) + }, + handleBlocksIndexed, + handleFileParsed, + ) - // Check if any blocks were actually indexed successfully - // If no blocks were indexed but blocks were found, it means all batches failed - if (cumulativeBlocksIndexed === 0 && cumulativeBlocksFoundSoFar > 0) { - if (batchErrors.length > 0) { - // Use the first batch error as it's likely representative of the main issue - const firstError = batchErrors[0] - throw new Error(`Indexing failed: ${firstError.message}`) + if (!result) { + throw new Error("Incremental scan failed, is scanner initialized?") + } + + // If new files were found and indexed, log the results + if (cumulativeBlocksFoundSoFar > 0) { + console.log( + `[CodeIndexOrchestrator] Incremental scan completed: ${cumulativeBlocksIndexed} blocks indexed from new/changed files`, + ) } else { - throw new Error(t("embeddings:orchestrator.indexingFailedNoBlocks")) + console.log("[CodeIndexOrchestrator] No new or changed files found") } - } - // Check for partial failures - if a significant portion of blocks failed - const failureRate = (cumulativeBlocksFoundSoFar - cumulativeBlocksIndexed) / cumulativeBlocksFoundSoFar - if (batchErrors.length > 0 && failureRate > 0.1) { - // More than 10% of blocks failed to index - const firstError = batchErrors[0] - throw new Error( - `Indexing partially failed: Only ${cumulativeBlocksIndexed} of ${cumulativeBlocksFoundSoFar} blocks were indexed. ${firstError.message}`, + await this._startWatcher() + + // Mark indexing as complete after successful incremental scan + await this.vectorStore.markIndexingComplete() + + this.stateManager.setSystemState("Indexed", t("embeddings:orchestrator.fileWatcherStarted")) + } else { + // No existing data or collection was just created - do a full scan + this.stateManager.setSystemState("Indexing", "Services ready. Starting workspace scan...") + + // Mark as incomplete at the start of full scan + await this.vectorStore.markIndexingIncomplete() + + let cumulativeBlocksIndexed = 0 + let cumulativeBlocksFoundSoFar = 0 + let batchErrors: Error[] = [] + + const handleFileParsed = (fileBlockCount: number) => { + cumulativeBlocksFoundSoFar += fileBlockCount + this.stateManager.reportBlockIndexingProgress(cumulativeBlocksIndexed, cumulativeBlocksFoundSoFar) + } + + const handleBlocksIndexed = (indexedCount: number) => { + cumulativeBlocksIndexed += indexedCount + this.stateManager.reportBlockIndexingProgress(cumulativeBlocksIndexed, cumulativeBlocksFoundSoFar) + } + + const result = await this.scanner.scanDirectory( + this.workspacePath, + (batchError: Error) => { + console.error( + `[CodeIndexOrchestrator] Error during initial scan batch: ${batchError.message}`, + batchError, + ) + batchErrors.push(batchError) + }, + handleBlocksIndexed, + handleFileParsed, ) - } - // CRITICAL: If there were ANY batch errors and NO blocks were successfully indexed, - // this is a complete failure regardless of the failure rate calculation - if (batchErrors.length > 0 && cumulativeBlocksIndexed === 0) { - const firstError = batchErrors[0] - throw new Error(`Indexing failed completely: ${firstError.message}`) - } + if (!result) { + throw new Error("Scan failed, is scanner initialized?") + } - // Final sanity check: If we found blocks but indexed none and somehow no errors were reported, - // this is still a failure - if (cumulativeBlocksFoundSoFar > 0 && cumulativeBlocksIndexed === 0) { - throw new Error(t("embeddings:orchestrator.indexingFailedCritical")) - } + const { stats } = result + + // Check if any blocks were actually indexed successfully + // If no blocks were indexed but blocks were found, it means all batches failed + if (cumulativeBlocksIndexed === 0 && cumulativeBlocksFoundSoFar > 0) { + if (batchErrors.length > 0) { + // Use the first batch error as it's likely representative of the main issue + const firstError = batchErrors[0] + throw new Error(`Indexing failed: ${firstError.message}`) + } else { + throw new Error(t("embeddings:orchestrator.indexingFailedNoBlocks")) + } + } + + // Check for partial failures - if a significant portion of blocks failed + const failureRate = (cumulativeBlocksFoundSoFar - cumulativeBlocksIndexed) / cumulativeBlocksFoundSoFar + if (batchErrors.length > 0 && failureRate > 0.1) { + // More than 10% of blocks failed to index + const firstError = batchErrors[0] + throw new Error( + `Indexing partially failed: Only ${cumulativeBlocksIndexed} of ${cumulativeBlocksFoundSoFar} blocks were indexed. ${firstError.message}`, + ) + } + + // CRITICAL: If there were ANY batch errors and NO blocks were successfully indexed, + // this is a complete failure regardless of the failure rate calculation + if (batchErrors.length > 0 && cumulativeBlocksIndexed === 0) { + const firstError = batchErrors[0] + throw new Error(`Indexing failed completely: ${firstError.message}`) + } - await this._startWatcher() + // Final sanity check: If we found blocks but indexed none and somehow no errors were reported, + // this is still a failure + if (cumulativeBlocksFoundSoFar > 0 && cumulativeBlocksIndexed === 0) { + throw new Error(t("embeddings:orchestrator.indexingFailedCritical")) + } + + await this._startWatcher() - this.stateManager.setSystemState("Indexed", t("embeddings:orchestrator.fileWatcherStarted")) + // Mark indexing as complete after successful full scan + await this.vectorStore.markIndexingComplete() + + this.stateManager.setSystemState("Indexed", t("embeddings:orchestrator.fileWatcherStarted")) + } } catch (error: any) { console.error("[CodeIndexOrchestrator] Error during indexing:", error) TelemetryService.instance.captureEvent(TelemetryEventName.CODE_INDEX_ERROR, { @@ -210,18 +288,33 @@ export class CodeIndexOrchestrator { stack: error instanceof Error ? error.stack : undefined, location: "startIndexing", }) - try { - await this.vectorStore.clearCollection() - } catch (cleanupError) { - console.error("[CodeIndexOrchestrator] Failed to clean up after error:", cleanupError) - TelemetryService.instance.captureEvent(TelemetryEventName.CODE_INDEX_ERROR, { - error: cleanupError instanceof Error ? cleanupError.message : String(cleanupError), - stack: cleanupError instanceof Error ? cleanupError.stack : undefined, - location: "startIndexing.cleanup", - }) + if (indexingStarted) { + try { + await this.vectorStore.clearCollection() + } catch (cleanupError) { + console.error("[CodeIndexOrchestrator] Failed to clean up after error:", cleanupError) + TelemetryService.instance.captureEvent(TelemetryEventName.CODE_INDEX_ERROR, { + error: cleanupError instanceof Error ? cleanupError.message : String(cleanupError), + stack: cleanupError instanceof Error ? cleanupError.stack : undefined, + location: "startIndexing.cleanup", + }) + } } - await this.cacheManager.clearCacheFile() + // Only clear cache if indexing had started (Qdrant connection succeeded) + // If we never connected to Qdrant, preserve cache for incremental scan when it comes back + if (indexingStarted) { + // Indexing started but failed mid-way - clear cache to avoid cache-Qdrant mismatch + await this.cacheManager.clearCacheFile() + console.log( + "[CodeIndexOrchestrator] Indexing failed after starting. Clearing cache to avoid inconsistency.", + ) + } else { + // Never connected to Qdrant - preserve cache for future incremental scan + console.log( + "[CodeIndexOrchestrator] Failed to connect to Qdrant. Preserving cache for future incremental scan.", + ) + } this.stateManager.setSystemState( "Error", diff --git a/src/services/code-index/vector-store/__tests__/qdrant-client.spec.ts b/src/services/code-index/vector-store/__tests__/qdrant-client.spec.ts index bc1b502be3f5..212487bb96d5 100644 --- a/src/services/code-index/vector-store/__tests__/qdrant-client.spec.ts +++ b/src/services/code-index/vector-store/__tests__/qdrant-client.spec.ts @@ -1261,9 +1261,9 @@ describe("QdrantVectorStore", () => { const results = await vectorStore.search(queryVector) expect(mockQdrantClientInstance.query).toHaveBeenCalledTimes(1) - expect(mockQdrantClientInstance.query).toHaveBeenCalledWith(expectedCollectionName, { + const callArgs = mockQdrantClientInstance.query.mock.calls[0][1] + expect(callArgs).toMatchObject({ query: queryVector, - filter: undefined, score_threshold: DEFAULT_SEARCH_MIN_SCORE, limit: DEFAULT_MAX_SEARCH_RESULTS, params: { @@ -1274,6 +1274,9 @@ describe("QdrantVectorStore", () => { include: ["filePath", "codeChunk", "startLine", "endLine", "pathSegments"], }, }) + expect(callArgs.filter).toEqual({ + must_not: [{ key: "type", match: { value: "metadata" } }], + }) expect(results).toEqual(mockQdrantResults.points) }) @@ -1301,29 +1304,20 @@ describe("QdrantVectorStore", () => { const results = await vectorStore.search(queryVector, directoryPrefix) - expect(mockQdrantClientInstance.query).toHaveBeenCalledWith(expectedCollectionName, { + const callArgs2 = mockQdrantClientInstance.query.mock.calls[0][1] + expect(callArgs2).toMatchObject({ query: queryVector, - filter: { - must: [ - { - key: "pathSegments.0", - match: { value: "src" }, - }, - { - key: "pathSegments.1", - match: { value: "components" }, - }, - ], - }, score_threshold: DEFAULT_SEARCH_MIN_SCORE, limit: DEFAULT_MAX_SEARCH_RESULTS, - params: { - hnsw_ef: 128, - exact: false, - }, - with_payload: { - include: ["filePath", "codeChunk", "startLine", "endLine", "pathSegments"], - }, + params: { hnsw_ef: 128, exact: false }, + with_payload: { include: ["filePath", "codeChunk", "startLine", "endLine", "pathSegments"] }, + }) + expect(callArgs2.filter).toEqual({ + must: [ + { key: "pathSegments.0", match: { value: "src" } }, + { key: "pathSegments.1", match: { value: "components" } }, + ], + must_not: [{ key: "type", match: { value: "metadata" } }], }) expect(results).toEqual(mockQdrantResults.points) @@ -1338,9 +1332,9 @@ describe("QdrantVectorStore", () => { await vectorStore.search(queryVector, undefined, customMinScore) - expect(mockQdrantClientInstance.query).toHaveBeenCalledWith(expectedCollectionName, { + const callArgs3 = mockQdrantClientInstance.query.mock.calls[0][1] + expect(callArgs3).toMatchObject({ query: queryVector, - filter: undefined, score_threshold: customMinScore, limit: DEFAULT_MAX_SEARCH_RESULTS, params: { @@ -1351,6 +1345,9 @@ describe("QdrantVectorStore", () => { include: ["filePath", "codeChunk", "startLine", "endLine", "pathSegments"], }, }) + expect(callArgs3.filter).toEqual({ + must_not: [{ key: "type", match: { value: "metadata" } }], + }) }) it("should use custom maxResults when provided", async () => { @@ -1362,9 +1359,9 @@ describe("QdrantVectorStore", () => { await vectorStore.search(queryVector, undefined, undefined, customMaxResults) - expect(mockQdrantClientInstance.query).toHaveBeenCalledWith(expectedCollectionName, { + const callArgs4 = mockQdrantClientInstance.query.mock.calls[0][1] + expect(callArgs4).toMatchObject({ query: queryVector, - filter: undefined, score_threshold: DEFAULT_SEARCH_MIN_SCORE, limit: customMaxResults, params: { @@ -1375,6 +1372,9 @@ describe("QdrantVectorStore", () => { include: ["filePath", "codeChunk", "startLine", "endLine", "pathSegments"], }, }) + expect(callArgs4.filter).toEqual({ + must_not: [{ key: "type", match: { value: "metadata" } }], + }) }) it("should filter out results with invalid payloads", async () => { @@ -1490,28 +1490,9 @@ describe("QdrantVectorStore", () => { await vectorStore.search(queryVector, directoryPrefix) - expect(mockQdrantClientInstance.query).toHaveBeenCalledWith(expectedCollectionName, { + const callArgs5 = mockQdrantClientInstance.query.mock.calls[0][1] + expect(callArgs5).toMatchObject({ query: queryVector, - filter: { - must: [ - { - key: "pathSegments.0", - match: { value: "src" }, - }, - { - key: "pathSegments.1", - match: { value: "components" }, - }, - { - key: "pathSegments.2", - match: { value: "ui" }, - }, - { - key: "pathSegments.3", - match: { value: "forms" }, - }, - ], - }, score_threshold: DEFAULT_SEARCH_MIN_SCORE, limit: DEFAULT_MAX_SEARCH_RESULTS, params: { @@ -1522,6 +1503,15 @@ describe("QdrantVectorStore", () => { include: ["filePath", "codeChunk", "startLine", "endLine", "pathSegments"], }, }) + expect(callArgs5.filter).toEqual({ + must: [ + { key: "pathSegments.0", match: { value: "src" } }, + { key: "pathSegments.1", match: { value: "components" } }, + { key: "pathSegments.2", match: { value: "ui" } }, + { key: "pathSegments.3", match: { value: "forms" } }, + ], + must_not: [{ key: "type", match: { value: "metadata" } }], + }) }) it("should handle error scenarios when qdrantClient.query fails", async () => { @@ -1574,9 +1564,9 @@ describe("QdrantVectorStore", () => { const results = await vectorStore.search(queryVector, directoryPrefix) - expect(mockQdrantClientInstance.query).toHaveBeenCalledWith(expectedCollectionName, { + const callArgs7 = mockQdrantClientInstance.query.mock.calls[0][1] + expect(callArgs7).toMatchObject({ query: queryVector, - filter: undefined, // Should be undefined for current directory score_threshold: DEFAULT_SEARCH_MIN_SCORE, limit: DEFAULT_MAX_SEARCH_RESULTS, params: { @@ -1587,6 +1577,9 @@ describe("QdrantVectorStore", () => { include: ["filePath", "codeChunk", "startLine", "endLine", "pathSegments"], }, }) + expect(callArgs7.filter).toEqual({ + must_not: [{ key: "type", match: { value: "metadata" } }], + }) expect(results).toEqual(mockQdrantResults.points) }) @@ -1600,9 +1593,9 @@ describe("QdrantVectorStore", () => { await vectorStore.search(queryVector, directoryPrefix) - expect(mockQdrantClientInstance.query).toHaveBeenCalledWith(expectedCollectionName, { + const callArgs6 = mockQdrantClientInstance.query.mock.calls[0][1] + expect(callArgs6).toMatchObject({ query: queryVector, - filter: undefined, // Should be undefined for current directory score_threshold: DEFAULT_SEARCH_MIN_SCORE, limit: DEFAULT_MAX_SEARCH_RESULTS, params: { @@ -1613,6 +1606,9 @@ describe("QdrantVectorStore", () => { include: ["filePath", "codeChunk", "startLine", "endLine", "pathSegments"], }, }) + expect(callArgs6.filter).toEqual({ + must_not: [{ key: "type", match: { value: "metadata" } }], + }) }) it("should not apply filter when directoryPrefix is empty string", async () => { @@ -1624,9 +1620,9 @@ describe("QdrantVectorStore", () => { await vectorStore.search(queryVector, directoryPrefix) - expect(mockQdrantClientInstance.query).toHaveBeenCalledWith(expectedCollectionName, { + const callArgs8 = mockQdrantClientInstance.query.mock.calls[0][1] + expect(callArgs8).toMatchObject({ query: queryVector, - filter: undefined, // Should be undefined for empty string score_threshold: DEFAULT_SEARCH_MIN_SCORE, limit: DEFAULT_MAX_SEARCH_RESULTS, params: { @@ -1637,6 +1633,9 @@ describe("QdrantVectorStore", () => { include: ["filePath", "codeChunk", "startLine", "endLine", "pathSegments"], }, }) + expect(callArgs8.filter).toEqual({ + must_not: [{ key: "type", match: { value: "metadata" } }], + }) }) it("should not apply filter when directoryPrefix is '.\\' (Windows style)", async () => { @@ -1648,9 +1647,9 @@ describe("QdrantVectorStore", () => { await vectorStore.search(queryVector, directoryPrefix) - expect(mockQdrantClientInstance.query).toHaveBeenCalledWith(expectedCollectionName, { + const callArgs9 = mockQdrantClientInstance.query.mock.calls[0][1] + expect(callArgs9).toMatchObject({ query: queryVector, - filter: undefined, // Should be undefined for Windows current directory score_threshold: DEFAULT_SEARCH_MIN_SCORE, limit: DEFAULT_MAX_SEARCH_RESULTS, params: { @@ -1661,6 +1660,9 @@ describe("QdrantVectorStore", () => { include: ["filePath", "codeChunk", "startLine", "endLine", "pathSegments"], }, }) + expect(callArgs9.filter).toEqual({ + must_not: [{ key: "type", match: { value: "metadata" } }], + }) }) it("should not apply filter when directoryPrefix has trailing slashes", async () => { @@ -1672,9 +1674,9 @@ describe("QdrantVectorStore", () => { await vectorStore.search(queryVector, directoryPrefix) - expect(mockQdrantClientInstance.query).toHaveBeenCalledWith(expectedCollectionName, { + const callArgs10 = mockQdrantClientInstance.query.mock.calls[0][1] + expect(callArgs10).toMatchObject({ query: queryVector, - filter: undefined, // Should be undefined after normalizing trailing slashes score_threshold: DEFAULT_SEARCH_MIN_SCORE, limit: DEFAULT_MAX_SEARCH_RESULTS, params: { @@ -1685,6 +1687,9 @@ describe("QdrantVectorStore", () => { include: ["filePath", "codeChunk", "startLine", "endLine", "pathSegments"], }, }) + expect(callArgs10.filter).toEqual({ + must_not: [{ key: "type", match: { value: "metadata" } }], + }) }) it("should still apply filter for relative paths like './src'", async () => { @@ -1696,16 +1701,9 @@ describe("QdrantVectorStore", () => { await vectorStore.search(queryVector, directoryPrefix) - expect(mockQdrantClientInstance.query).toHaveBeenCalledWith(expectedCollectionName, { + const callArgs11 = mockQdrantClientInstance.query.mock.calls[0][1] + expect(callArgs11).toMatchObject({ query: queryVector, - filter: { - must: [ - { - key: "pathSegments.0", - match: { value: "src" }, - }, - ], - }, // Should normalize "./src" to "src" score_threshold: DEFAULT_SEARCH_MIN_SCORE, limit: DEFAULT_MAX_SEARCH_RESULTS, params: { @@ -1716,6 +1714,15 @@ describe("QdrantVectorStore", () => { include: ["filePath", "codeChunk", "startLine", "endLine", "pathSegments"], }, }) + expect(callArgs11.filter).toEqual({ + must: [ + { + key: "pathSegments.0", + match: { value: "src" }, + }, + ], + must_not: [{ key: "type", match: { value: "metadata" } }], + }) // Should normalize "./src" to "src" }) it("should still apply filter for regular directory paths", async () => { @@ -1727,16 +1734,9 @@ describe("QdrantVectorStore", () => { await vectorStore.search(queryVector, directoryPrefix) - expect(mockQdrantClientInstance.query).toHaveBeenCalledWith(expectedCollectionName, { + const callArgs12 = mockQdrantClientInstance.query.mock.calls[0][1] + expect(callArgs12).toMatchObject({ query: queryVector, - filter: { - must: [ - { - key: "pathSegments.0", - match: { value: "src" }, - }, - ], - }, // Should still create filter for regular paths score_threshold: DEFAULT_SEARCH_MIN_SCORE, limit: DEFAULT_MAX_SEARCH_RESULTS, params: { @@ -1747,6 +1747,15 @@ describe("QdrantVectorStore", () => { include: ["filePath", "codeChunk", "startLine", "endLine", "pathSegments"], }, }) + expect(callArgs12.filter).toEqual({ + must: [ + { + key: "pathSegments.0", + match: { value: "src" }, + }, + ], + must_not: [{ key: "type", match: { value: "metadata" } }], + }) // Should still create filter for regular paths }) }) }) diff --git a/src/services/code-index/vector-store/qdrant-client.ts b/src/services/code-index/vector-store/qdrant-client.ts index 5a63001c434e..ec83159face9 100644 --- a/src/services/code-index/vector-store/qdrant-client.ts +++ b/src/services/code-index/vector-store/qdrant-client.ts @@ -1,10 +1,10 @@ import { QdrantClient, Schemas } from "@qdrant/js-client-rest" import { createHash } from "crypto" import * as path from "path" -import { getWorkspacePath } from "../../../utils/path" +import { v5 as uuidv5 } from "uuid" import { IVectorStore } from "../interfaces/vector-store" import { Payload, VectorStoreSearchResult } from "../interfaces" -import { DEFAULT_MAX_SEARCH_RESULTS, DEFAULT_SEARCH_MIN_SCORE } from "../constants" +import { DEFAULT_MAX_SEARCH_RESULTS, DEFAULT_SEARCH_MIN_SCORE, QDRANT_CODE_BLOCK_NAMESPACE } from "../constants" import { t } from "../../../i18n" import { Package } from "../../../shared/package" @@ -387,7 +387,12 @@ export class QdrantVectorStore implements IVectorStore { maxResults?: number, ): Promise { try { - let filter = undefined + let filter: + | { + must: Array<{ key: string; match: { value: string } }> + must_not?: Array<{ key: string; match: { value: string } }> + } + | undefined = undefined if (directoryPrefix) { // Check if the path represents current directory @@ -413,9 +418,18 @@ export class QdrantVectorStore implements IVectorStore { } } + // Always exclude metadata points at query-time to avoid wasting top-k + const metadataExclusion = { + must_not: [{ key: "type", match: { value: "metadata" } }], + } + + const mergedFilter = filter + ? { ...filter, must_not: [...(filter.must_not || []), ...metadataExclusion.must_not] } + : metadataExclusion + const searchRequest = { query: queryVector, - filter, + filter: mergedFilter, score_threshold: minScore ?? DEFAULT_SEARCH_MIN_SCORE, limit: maxResults ?? DEFAULT_MAX_SEARCH_RESULTS, params: { @@ -549,4 +563,106 @@ export class QdrantVectorStore implements IVectorStore { const collectionInfo = await this.getCollectionInfo() return collectionInfo !== null } + + /** + * Checks if the collection exists and has indexed points + * @returns Promise resolving to boolean indicating if the collection exists and has points + */ + async hasIndexedData(): Promise { + try { + const collectionInfo = await this.getCollectionInfo() + if (!collectionInfo) { + return false + } + // Check if the collection has any points indexed + const pointsCount = collectionInfo.points_count ?? 0 + if (pointsCount === 0) { + return false + } + + // Check if the indexing completion marker exists + // Use a deterministic UUID generated from a constant string + const metadataId = uuidv5("__indexing_metadata__", QDRANT_CODE_BLOCK_NAMESPACE) + const metadataPoints = await this.client.retrieve(this.collectionName, { + ids: [metadataId], + }) + + // If marker exists, use it to determine completion status + if (metadataPoints.length > 0) { + return metadataPoints[0].payload?.indexing_complete === true + } + + // Backward compatibility: No marker exists (old index or pre-marker version) + // Fall back to old logic - assume complete if collection has points + console.log( + "[QdrantVectorStore] No indexing metadata marker found. Using backward compatibility mode (checking points_count > 0).", + ) + return pointsCount > 0 + } catch (error) { + console.warn("[QdrantVectorStore] Failed to check if collection has data:", error) + return false + } + } + + /** + * Marks the indexing process as complete by storing metadata + * Should be called after a successful full workspace scan or incremental scan + */ + async markIndexingComplete(): Promise { + try { + // Create a metadata point with a deterministic UUID to mark indexing as complete + // Use uuidv5 to generate a consistent UUID from a constant string + const metadataId = uuidv5("__indexing_metadata__", QDRANT_CODE_BLOCK_NAMESPACE) + + await this.client.upsert(this.collectionName, { + points: [ + { + id: metadataId, + vector: new Array(this.vectorSize).fill(0), + payload: { + type: "metadata", + indexing_complete: true, + completed_at: Date.now(), + }, + }, + ], + wait: true, + }) + console.log("[QdrantVectorStore] Marked indexing as complete") + } catch (error) { + console.error("[QdrantVectorStore] Failed to mark indexing as complete:", error) + throw error + } + } + + /** + * Marks the indexing process as incomplete by storing metadata + * Should be called at the start of indexing to indicate work in progress + */ + async markIndexingIncomplete(): Promise { + try { + // Create a metadata point with a deterministic UUID to mark indexing as incomplete + // Use uuidv5 to generate a consistent UUID from a constant string + const metadataId = uuidv5("__indexing_metadata__", QDRANT_CODE_BLOCK_NAMESPACE) + + await this.client.upsert(this.collectionName, { + points: [ + { + id: metadataId, + vector: new Array(this.vectorSize).fill(0), + payload: { + type: "metadata", + indexing_complete: false, + started_at: Date.now(), + }, + }, + ], + wait: true, + }) + console.log("[QdrantVectorStore] Marked indexing as incomplete (in progress)") + } catch (error) { + console.error("[QdrantVectorStore] Failed to mark indexing as incomplete:", error) + throw error + } + } } diff --git a/src/services/search/__tests__/file-search.spec.ts b/src/services/search/__tests__/file-search.spec.ts new file mode 100644 index 000000000000..6a7fc11df33a --- /dev/null +++ b/src/services/search/__tests__/file-search.spec.ts @@ -0,0 +1,88 @@ +import { describe, it, expect, vi } from "vitest" +import * as vscode from "vscode" + +// Mock vscode +vi.mock("vscode", () => ({ + workspace: { + getConfiguration: vi.fn(), + }, + env: { + appRoot: "/mock/app/root", + }, +})) + +// Mock getBinPath +vi.mock("../ripgrep", () => ({ + getBinPath: vi.fn(async () => null), // Return null to skip actual ripgrep execution +})) + +// Mock child_process +vi.mock("child_process", () => ({ + spawn: vi.fn(), +})) + +describe("file-search", () => { + describe("configuration integration", () => { + it("should read VSCode search configuration settings", async () => { + const mockSearchConfig = { + get: vi.fn((key: string) => { + if (key === "useIgnoreFiles") return false + if (key === "useGlobalIgnoreFiles") return false + if (key === "useParentIgnoreFiles") return false + return undefined + }), + } + const mockRooConfig = { + get: vi.fn(() => 10000), + } + + ;(vscode.workspace.getConfiguration as any).mockImplementation((section: string) => { + if (section === "search") return mockSearchConfig + if (section === "zgsm") return mockRooConfig + return { get: vi.fn() } + }) + + // Import the module - this will call getConfiguration during import + await import("../file-search") + + // Verify that configuration is accessible + expect(vscode.workspace.getConfiguration).toBeDefined() + }) + + it("should read maximumIndexedFilesForFileSearch configuration", async () => { + const mockRooConfig = { + get: vi.fn((key: string, defaultValue: number) => { + if (key === "maximumIndexedFilesForFileSearch") return 50000 + return defaultValue + }), + } + + ;(vscode.workspace.getConfiguration as any).mockImplementation((section: string) => { + if (section === "zgsm") return mockRooConfig + return { get: vi.fn() } + }) + + // The configuration should be readable + const config = vscode.workspace.getConfiguration("zgsm") + const limit = config.get("maximumIndexedFilesForFileSearch", 10000) + + expect(limit).toBe(50000) + }) + + it("should use default limit when configuration is not provided", () => { + const mockRooConfig = { + get: vi.fn((key: string, defaultValue: number) => defaultValue), + } + + ;(vscode.workspace.getConfiguration as any).mockImplementation((section: string) => { + if (section === "zgsm") return mockRooConfig + return { get: vi.fn() } + }) + + const config = vscode.workspace.getConfiguration("zgsm") + const limit = config.get("maximumIndexedFilesForFileSearch", 10000) + + expect(limit).toBe(10000) + }) + }) +}) diff --git a/src/services/search/file-search.ts b/src/services/search/file-search.ts index a25dd4068f97..d8f08a63f6a3 100644 --- a/src/services/search/file-search.ts +++ b/src/services/search/file-search.ts @@ -85,14 +85,44 @@ export async function executeRipgrep({ }) } +/** + * Get extra ripgrep arguments based on VSCode search configuration + */ +function getRipgrepSearchOptions(): string[] { + const config = vscode.workspace.getConfiguration("search") + const extraArgs: string[] = [] + + // Respect VSCode's search.useIgnoreFiles setting + if (config.get("useIgnoreFiles") === false) { + extraArgs.push("--no-ignore") + } + + // Respect VSCode's search.useGlobalIgnoreFiles setting + if (config.get("useGlobalIgnoreFiles") === false) { + extraArgs.push("--no-ignore-global") + } + + // Respect VSCode's search.useParentIgnoreFiles setting + if (config.get("useParentIgnoreFiles") === false) { + extraArgs.push("--no-ignore-parent") + } + + return extraArgs +} + export async function executeRipgrepForFiles( workspacePath: string, - limit: number = 5000, + limit?: number, ): Promise<{ path: string; type: "file" | "folder"; label?: string }[]> { + // Get limit from configuration if not provided + const effectiveLimit = + limit ?? vscode.workspace.getConfiguration("zgsm").get("maximumIndexedFilesForFileSearch", 10000) + const args = [ "--files", "--follow", "--hidden", + ...getRipgrepSearchOptions(), "-g", "!**/node_modules/**", "-g", @@ -104,17 +134,17 @@ export async function executeRipgrepForFiles( workspacePath, ] - return executeRipgrep({ args, workspacePath, limit }) + return executeRipgrep({ args, workspacePath, limit: effectiveLimit }) } export async function searchWorkspaceFiles( query: string, workspacePath: string, - limit: number = 20, + limit: number = 30, ): Promise<{ path: string; type: "file" | "folder"; label?: string }[]> { try { - // Get all files and directories (from our modified function) - const allItems = await executeRipgrepForFiles(workspacePath, 5000) + // Get all files and directories (uses configured limit) + const allItems = await executeRipgrepForFiles(workspacePath) // If no query, just return the top items if (!query.trim()) { diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index e514035bca40..442e69f021c4 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -411,7 +411,6 @@ export interface ClineSayTool { | "switchMode" | "newTask" | "finishTask" - | "searchAndReplace" | "insertContent" | "generateImage" | "imageGenerated" @@ -426,12 +425,6 @@ export interface ClineSayTool { isOutsideWorkspace?: boolean isProtected?: boolean additionalFileCount?: number // Number of additional files in the same read_file request - search?: string - replace?: string - useRegex?: boolean - ignoreCase?: boolean - startLine?: number - endLine?: number lineNumber?: number query?: string batchFiles?: Array<{ diff --git a/src/shared/__tests__/modes.spec.ts b/src/shared/__tests__/modes.spec.ts index 8173f5f2ef97..b00ed986e0ec 100644 --- a/src/shared/__tests__/modes.spec.ts +++ b/src/shared/__tests__/modes.spec.ts @@ -246,16 +246,7 @@ describe("isToolAllowedForMode", () => { expect(isToolAllowedForMode("use_mcp_tool", "architect", [])).toBe(true) }) - it("applies restrictions to all edit tools including search_and_replace and insert_content", () => { - // Test search_and_replace with matching file - expect( - isToolAllowedForMode("search_and_replace", "architect", [], undefined, { - path: "test.md", - search: "old text", - replace: "new text", - }), - ).toBe(true) - + it("applies restrictions to insert_content edit tool", () => { // Test insert_content with matching file expect( isToolAllowedForMode("insert_content", "architect", [], undefined, { @@ -265,22 +256,6 @@ describe("isToolAllowedForMode", () => { }), ).toBe(true) - // Test search_and_replace with non-matching file - should throw error - expect(() => - isToolAllowedForMode("search_and_replace", "architect", [], undefined, { - path: "test.py", - search: "old text", - replace: "new text", - }), - ).toThrow(FileRestrictionError) - expect(() => - isToolAllowedForMode("search_and_replace", "architect", [], undefined, { - path: "test.py", - search: "old text", - replace: "new text", - }), - ).toThrow(/Markdown files only/) - // Test insert_content with non-matching file - should throw error expect(() => isToolAllowedForMode("insert_content", "architect", [], undefined, { diff --git a/src/shared/tools.ts b/src/shared/tools.ts index 608b50752e7d..61e840272726 100644 --- a/src/shared/tools.ts +++ b/src/shared/tools.ts @@ -55,15 +55,10 @@ export const toolParamNames = [ "follow_up", "task", "size", - "search", - "replace", - "use_regex", - "ignore_case", + "query", "args", "start_line", "end_line", - "query", - "args", "todos", "prompt", "image", @@ -165,12 +160,6 @@ export interface RunSlashCommandToolUse extends ToolUse { params: Partial, "command" | "args">> } -export interface SearchAndReplaceToolUse extends ToolUse { - name: "search_and_replace" - params: Required, "path" | "search" | "replace">> & - Partial, "use_regex" | "ignore_case" | "start_line" | "end_line">> -} - export interface GenerateImageToolUse extends ToolUse { name: "generate_image" params: Partial, "prompt" | "path" | "image">> @@ -199,7 +188,6 @@ export const TOOL_DISPLAY_NAMES: Record = { 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", run_slash_command: "run slash command", @@ -219,7 +207,7 @@ export const TOOL_GROUPS: Record = { ], }, edit: { - tools: ["apply_diff", "write_to_file", "insert_content", "search_and_replace", "generate_image"], + tools: ["apply_diff", "write_to_file", "insert_content", "generate_image"], }, browser: { tools: ["browser_action"], diff --git a/webview-ui/src/__mocks__/vscode.ts b/webview-ui/src/__mocks__/vscode.ts index 174bdef9771e..c4612e45aad3 100644 --- a/webview-ui/src/__mocks__/vscode.ts +++ b/webview-ui/src/__mocks__/vscode.ts @@ -250,8 +250,8 @@ export const mockExtensionContext: ExtensionContext = { extension: { packageJSON: { version: "1.0.0", - publisher: "RooVeterinaryInc", - name: "roo-cline", + publisher: "zgsm-ai", + name: "zgsm", }, }, } diff --git a/webview-ui/src/components/chat/Announcement.tsx b/webview-ui/src/components/chat/Announcement.tsx index 20953df765f1..a938031ac87a 100644 --- a/webview-ui/src/components/chat/Announcement.tsx +++ b/webview-ui/src/components/chat/Announcement.tsx @@ -96,6 +96,16 @@ const Announcement = ({ hideAnnouncement }: AnnouncementProps) => { }} /> + + {/* Careers Section */} +
+ , + }} + /> +
@@ -135,4 +145,15 @@ const RedditLink = () => ( ) +const CareersLink = ({ children }: { children?: React.ReactNode }) => ( + { + e.preventDefault() + vscode.postMessage({ type: "openExternal", url: "https://careers.roocode.com" }) + }}> + {children} + +) + export default memo(Announcement) diff --git a/webview-ui/src/components/chat/ChatRow.tsx b/webview-ui/src/components/chat/ChatRow.tsx index 4a274f192744..e69cb94bc4d1 100644 --- a/webview-ui/src/components/chat/ChatRow.tsx +++ b/webview-ui/src/components/chat/ChatRow.tsx @@ -506,39 +506,6 @@ export const ChatRowContent = ({ ) - case "searchAndReplace": - return ( - <> -
- {tool.isProtected ? ( - - ) : ( - toolIcon("replace") - )} - - {tool.isProtected && message.type === "ask" - ? t("chat:fileOperations.wantsToEditProtected") - : message.type === "ask" - ? t("chat:fileOperations.wantsToSearchReplace") - : t("chat:fileOperations.didSearchReplace")} - -
-
- -
- - ) case "codebaseSearch": { return (
diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index 513b83083f6e..74fc22136113 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -1036,14 +1036,9 @@ const ChatViewComponent: React.ForwardRefRenderFunction{t("mcp:instructions")} )} - - {t("mcp:tabs.errors")} ({server.errorHistory?.length || 0}) + + {t("mcp:tabs.logs")} ({server.errorHistory?.length || 0}) @@ -441,7 +441,7 @@ const ServerRow = ({ server, alwaysAllowMcp }: { server: McpServer; alwaysAllowM )} - + {server.errorHistory && server.errorHistory.length > 0 ? (
- {t("mcp:emptyState.noErrors")} + {t("mcp:emptyState.noLogs")}
)}
diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx index 800e5be1a6a6..4dd5e65c2c70 100644 --- a/webview-ui/src/context/ExtensionStateContext.tsx +++ b/webview-ui/src/context/ExtensionStateContext.tsx @@ -500,12 +500,13 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode // Watch for authentication state changes and refresh Roo models useEffect(() => { const currentAuth = state.cloudIsAuthenticated ?? false - if (!prevCloudIsAuthenticated && currentAuth) { - // User just authenticated - refresh Roo models with the new auth token + const currentProvider = state.apiConfiguration?.apiProvider + if (!prevCloudIsAuthenticated && currentAuth && currentProvider === "roo") { + // User just authenticated and Roo is the active provider - refresh Roo models vscode.postMessage({ type: "requestRooModels" }) } setPrevCloudIsAuthenticated(currentAuth) - }, [state.cloudIsAuthenticated, prevCloudIsAuthenticated]) + }, [state.cloudIsAuthenticated, prevCloudIsAuthenticated, state.apiConfiguration?.apiProvider]) const contextValue: ExtensionStateContextType = { ...state, diff --git a/webview-ui/src/context/__tests__/ExtensionStateContext.roo-auth-gate.spec.tsx b/webview-ui/src/context/__tests__/ExtensionStateContext.roo-auth-gate.spec.tsx new file mode 100644 index 000000000000..d62adf26e935 --- /dev/null +++ b/webview-ui/src/context/__tests__/ExtensionStateContext.roo-auth-gate.spec.tsx @@ -0,0 +1,75 @@ +import { render, waitFor } from "@/utils/test-utils" +import React from "react" + +vi.mock("@src/utils/vscode", () => ({ + vscode: { + postMessage: vi.fn(), + }, +})) + +import { ExtensionStateContextProvider } from "@src/context/ExtensionStateContext" +import { vscode } from "@src/utils/vscode" + +describe("ExtensionStateContext Roo auth gate", () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + function postStateMessage(state: any) { + window.dispatchEvent( + new MessageEvent("message", { + data: { + type: "state", + state, + }, + }), + ) + } + + it("does not post requestRooModels when auth flips and provider !== 'roo'", async () => { + render( + +
+ , + ) + + // Flip auth to true with a non-roo provider (anthropic) + postStateMessage({ + cloudIsAuthenticated: true, + apiConfiguration: { apiProvider: "anthropic" }, + }) + + // Should NOT fire auth-driven Roo refresh + await waitFor(() => { + const calls = (vscode.postMessage as any).mock.calls as any[][] + const hasRequest = calls.some((c) => c[0]?.type === "requestRooModels") + expect(hasRequest).toBe(false) + }) + }) + + it("posts requestRooModels when auth flips and provider === 'roo'", async () => { + render( + +
+ , + ) + + // Ensure prev false (explicit) + postStateMessage({ + cloudIsAuthenticated: false, + apiConfiguration: { apiProvider: "roo" }, + }) + + vi.clearAllMocks() + + // Flip to true with provider roo - should trigger + postStateMessage({ + cloudIsAuthenticated: true, + apiConfiguration: { apiProvider: "roo" }, + }) + + await waitFor(() => { + expect(vscode.postMessage).toHaveBeenCalledWith({ type: "requestRooModels" }) + }) + }) +}) diff --git a/webview-ui/src/i18n/costrict-i18n/locales/en/settings.json b/webview-ui/src/i18n/costrict-i18n/locales/en/settings.json index dba5b49998a8..06dcd1dadf1f 100644 --- a/webview-ui/src/i18n/costrict-i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/costrict-i18n/locales/en/settings.json @@ -94,9 +94,6 @@ "autoCondenseContextPercent": { "description": "When the context window reaches this threshold, CoStrict will automatically condense it." }, - "SEARCH_AND_REPLACE": { - "description": "Enable the experimental search and replace tool, allowing CoStrict to replace multiple instances of a search term in one request." - }, "INSERT_BLOCK": { "description": "Enable the experimental insert content tool, allowing CoStrict to insert content at specific line numbers without needing to create a diff." }, diff --git a/webview-ui/src/i18n/costrict-i18n/locales/zh-CN/settings.json b/webview-ui/src/i18n/costrict-i18n/locales/zh-CN/settings.json index 79c63575fced..acb57781ee04 100644 --- a/webview-ui/src/i18n/costrict-i18n/locales/zh-CN/settings.json +++ b/webview-ui/src/i18n/costrict-i18n/locales/zh-CN/settings.json @@ -94,9 +94,6 @@ "autoCondenseContextPercent": { "description": "当上下文窗口达到此阈值时,CoStrict 将自动压缩它。" }, - "SEARCH_AND_REPLACE": { - "description": "启用实验性的搜索和替换工具,允许 CoStrict 在一个请求中替换搜索词的多实例。" - }, "INSERT_BLOCK": { "description": "启用实验性的插入内容工具,允许 CoStrict 在特定行号插入内容,无需创建差异。" }, diff --git a/webview-ui/src/i18n/costrict-i18n/locales/zh-TW/settings.json b/webview-ui/src/i18n/costrict-i18n/locales/zh-TW/settings.json index 1a5a75046f5b..9e8b7a61f63b 100644 --- a/webview-ui/src/i18n/costrict-i18n/locales/zh-TW/settings.json +++ b/webview-ui/src/i18n/costrict-i18n/locales/zh-TW/settings.json @@ -94,9 +94,6 @@ "autoCondenseContextPercent": { "description": "當上下文窗口達到此閾值時,CoStrict 將自動壓縮它。" }, - "SEARCH_AND_REPLACE": { - "description": "啟用實驗性的搜索和替換工具,允許 CoStrict 在一個請求中替換搜索詞的多實例。" - }, "INSERT_BLOCK": { "description": "啟用實驗性的插入內容工具,允許 CoStrict 在特定行號插入內容,無需創建差異。" }, diff --git a/webview-ui/src/i18n/locales/en/chat.json b/webview-ui/src/i18n/locales/en/chat.json index afacf116f644..a327185b8639 100644 --- a/webview-ui/src/i18n/locales/en/chat.json +++ b/webview-ui/src/i18n/locales/en/chat.json @@ -194,8 +194,6 @@ "wantsToGenerateImageProtected": "Roo wants to generate an image in a protected location", "didGenerateImage": "Roo generated an image", "wantsToCreate": "Roo wants to create a new file", - "wantsToSearchReplace": "Roo wants to search and replace in this file", - "didSearchReplace": "Roo performed search and replace on this file", "wantsToInsert": "Roo wants to insert content into this file", "wantsToInsertWithLineNumber": "Roo wants to insert content into this file at line {{lineNumber}}", "wantsToInsertAtEnd": "Roo wants to append content to the end of this file" @@ -320,6 +318,7 @@ "description": "It runs Roo in the cloud, giving extremely high quality code reviews instantly. We've been using it heavily to build Roo and now it's also available to the community.", "createAgentButton": "Try out PR Reviewer" }, + "careers": "Also, we're hiring!", "socialLinks": "Join us on X, Discord, or r/RooCode 🚀" }, "reasoning": { diff --git a/webview-ui/src/i18n/locales/en/mcp.json b/webview-ui/src/i18n/locales/en/mcp.json index 5bc64a70dca2..2fcae2840c78 100644 --- a/webview-ui/src/i18n/locales/en/mcp.json +++ b/webview-ui/src/i18n/locales/en/mcp.json @@ -26,12 +26,12 @@ "tabs": { "tools": "Tools", "resources": "Resources", - "errors": "Errors" + "logs": "Logs" }, "emptyState": { "noTools": "No tools found", "noResources": "No resources found", - "noErrors": "No errors found" + "noLogs": "No logs yet" }, "networkTimeout": { "label": "Network Timeout", diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index c6692fb15b62..4126441fc1e5 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -733,10 +733,6 @@ "name": "Use experimental unified diff strategy", "description": "Enable the experimental unified diff strategy. This strategy might reduce the number of retries caused by model errors but may cause unexpected behavior or incorrect edits. Only enable if you understand the risks and are willing to carefully review all changes." }, - "SEARCH_AND_REPLACE": { - "name": "Use experimental search and replace tool", - "description": "Enable the experimental search and replace tool, allowing Roo to replace multiple instances of a search term in one request." - }, "INSERT_BLOCK": { "name": "Use experimental insert content tool", "description": "Enable the experimental insert content tool, allowing Roo to insert content at specific line numbers without needing to create a diff." diff --git a/webview-ui/src/i18n/locales/zh-CN/chat.json b/webview-ui/src/i18n/locales/zh-CN/chat.json index 011ec70fbfff..808bd4eed32f 100644 --- a/webview-ui/src/i18n/locales/zh-CN/chat.json +++ b/webview-ui/src/i18n/locales/zh-CN/chat.json @@ -313,6 +313,7 @@ "description": "在云端运行 Roo,即时提供极高质量的代码审查。我们在构建 Roo 时大量使用它,现在也向社区开放。", "createAgentButton": "试用 PR 审查员" }, + "careers": "此外,我们正在招聘!", "socialLinks": "在 XDiscordr/RooCode 上关注我们 🚀" }, "browser": { diff --git a/webview-ui/src/i18n/locales/zh-CN/mcp.json b/webview-ui/src/i18n/locales/zh-CN/mcp.json index 775250b19f04..601d1b2a1350 100644 --- a/webview-ui/src/i18n/locales/zh-CN/mcp.json +++ b/webview-ui/src/i18n/locales/zh-CN/mcp.json @@ -25,12 +25,12 @@ "tabs": { "tools": "工具", "resources": "资源", - "errors": "错误" + "logs": "日志" }, "emptyState": { "noTools": "未找到工具", "noResources": "未找到资源", - "noErrors": "未找到错误" + "noLogs": "暂无日志" }, "networkTimeout": { "label": "网络超时", diff --git a/webview-ui/src/i18n/locales/zh-CN/settings.json b/webview-ui/src/i18n/locales/zh-CN/settings.json index 9573ed4a2c37..e23c9a3e6652 100644 --- a/webview-ui/src/i18n/locales/zh-CN/settings.json +++ b/webview-ui/src/i18n/locales/zh-CN/settings.json @@ -735,10 +735,6 @@ "name": "启用diff更新工具", "description": "可减少因模型错误导致的重复尝试,但可能引发意外操作。启用前请确保理解风险并会仔细检查所有修改。" }, - "SEARCH_AND_REPLACE": { - "name": "启用搜索和替换工具", - "description": "启用实验性搜索和替换工具,允许 Roo 在一个请求中替换搜索词的多个实例。" - }, "INSERT_BLOCK": { "name": "启用插入内容工具", "description": "允许 Roo 在特定行号插入内容,无需处理差异。" diff --git a/webview-ui/src/i18n/locales/zh-TW/chat.json b/webview-ui/src/i18n/locales/zh-TW/chat.json index 33c63a11f399..e1bb99bd09f3 100644 --- a/webview-ui/src/i18n/locales/zh-TW/chat.json +++ b/webview-ui/src/i18n/locales/zh-TW/chat.json @@ -322,6 +322,7 @@ "description": "它在雲端執行 Roo,立即提供極高品質的程式碼審查。我們在建立 Roo 時大量使用它,現在它也可供社群使用。", "createAgentButton": "試用 PR Reviewer" }, + "careers": "此外,我們正在招募!", "socialLinks": "在 XDiscordr/RooCode 上關注我們 🚀" }, "reasoning": { diff --git a/webview-ui/src/i18n/locales/zh-TW/mcp.json b/webview-ui/src/i18n/locales/zh-TW/mcp.json index e5fc6f66e5aa..44aaa1686cbb 100644 --- a/webview-ui/src/i18n/locales/zh-TW/mcp.json +++ b/webview-ui/src/i18n/locales/zh-TW/mcp.json @@ -26,12 +26,12 @@ "tabs": { "tools": "工具", "resources": "資源", - "errors": "錯誤" + "logs": "日誌" }, "emptyState": { "noTools": "找不到工具", "noResources": "找不到資源", - "noErrors": "找不到錯誤" + "noLogs": "暫無日誌" }, "networkTimeout": { "label": "網路逾時", diff --git a/webview-ui/src/i18n/locales/zh-TW/settings.json b/webview-ui/src/i18n/locales/zh-TW/settings.json index c0e524c24051..83e07831e666 100644 --- a/webview-ui/src/i18n/locales/zh-TW/settings.json +++ b/webview-ui/src/i18n/locales/zh-TW/settings.json @@ -735,10 +735,6 @@ "name": "使用實驗性統一差異比對策略", "description": "啟用實驗性的統一差異比對策略。此策略可能減少因模型錯誤而導致的重試次數,但也可能導致意外行為或錯誤的編輯。請務必了解風險,並願意仔細檢查所有變更後再啟用。" }, - "SEARCH_AND_REPLACE": { - "name": "使用實驗性搜尋與取代工具", - "description": "啟用實驗性的搜尋與取代工具,允許 Roo 在單一請求中取代多個符合的內容。" - }, "INSERT_BLOCK": { "name": "使用實驗性插入內容工具", "description": "啟用實驗性的插入內容工具,允許 Roo 直接在指定行號插入內容,而無需產生差異比對。"