From 2b7c2665466d949b045adcb72c31918510e71da2 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Wed, 9 Jul 2025 15:03:04 -0400 Subject: [PATCH 01/27] Update icons in chattextarea (#5520) * Update icons in chattextarea * fix: update icons in ChatTextArea and IndexingStatusBadge components * fix: update Camera icon to Image and fix alignment - Changed Camera icon to Image icon from lucide-react - Fixed alignment issue by adjusting gap and removing extra margin - Updated tests to work with new Lucide icon structure * fix: revert alignment changes for Image icon - Keep original alignment with gap-0.5 and mr-1 to match send and enhance buttons --------- Co-authored-by: Daniel Riccio --- .../src/components/chat/ChatTextArea.tsx | 78 ++++++++++++++----- .../components/chat/IndexingStatusBadge.tsx | 24 +++--- .../chat/__tests__/ChatTextArea.spec.tsx | 9 ++- .../__tests__/IndexingStatusBadge.spec.tsx | 6 +- 4 files changed, 81 insertions(+), 36 deletions(-) diff --git a/webview-ui/src/components/chat/ChatTextArea.tsx b/webview-ui/src/components/chat/ChatTextArea.tsx index b522b43ecc..3d70f4a4ca 100644 --- a/webview-ui/src/components/chat/ChatTextArea.tsx +++ b/webview-ui/src/components/chat/ChatTextArea.tsx @@ -25,9 +25,8 @@ import Thumbnails from "../common/Thumbnails" import ModeSelector from "./ModeSelector" import { MAX_IMAGES_PER_MESSAGE } from "./ChatView" import ContextMenu from "./ContextMenu" -import { VolumeX, Pin, Check } from "lucide-react" -import { IconButton } from "./IconButton" -import { IndexingStatusDot } from "./IndexingStatusBadge" +import { VolumeX, Pin, Check, Image, WandSparkles, SendHorizontal } from "lucide-react" +import { IndexingStatusBadge } from "./IndexingStatusBadge" import { cn } from "@/lib/utils" import { usePromptHistory } from "./hooks/usePromptHistory" @@ -962,24 +961,49 @@ const ChatTextArea = forwardRef( )}
- + onClick={!sendingDisabled ? handleEnhancePrompt : undefined} + className={cn( + "relative inline-flex items-center justify-center", + "bg-transparent border-none p-1.5", + "rounded-md min-w-[28px] min-h-[28px]", + "opacity-60 hover:opacity-100 text-vscode-descriptionForeground hover:text-vscode-foreground", + "transition-all duration-150", + "hover:bg-[rgba(255,255,255,0.03)] hover:border-[rgba(255,255,255,0.15)]", + "focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder", + "active:bg-[rgba(255,255,255,0.1)]", + !sendingDisabled && "cursor-pointer", + sendingDisabled && + "opacity-40 cursor-not-allowed grayscale-[30%] hover:bg-transparent hover:border-[rgba(255,255,255,0.08)] active:bg-transparent", + )}> + +
- + onClick={!sendingDisabled ? onSend : undefined} + className={cn( + "relative inline-flex items-center justify-center", + "bg-transparent border-none p-1.5", + "rounded-md min-w-[28px] min-h-[28px]", + "opacity-60 hover:opacity-100 text-vscode-descriptionForeground hover:text-vscode-foreground", + "transition-all duration-150", + "hover:bg-[rgba(255,255,255,0.03)] hover:border-[rgba(255,255,255,0.15)]", + "focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder", + "active:bg-[rgba(255,255,255,0.1)]", + !sendingDisabled && "cursor-pointer", + sendingDisabled && + "opacity-40 cursor-not-allowed grayscale-[30%] hover:bg-transparent hover:border-[rgba(255,255,255,0.08)] active:bg-transparent", + )}> + +
{!inputValue && ( @@ -1145,14 +1169,28 @@ const ChatTextArea = forwardRef(
- - +
diff --git a/webview-ui/src/components/chat/IndexingStatusBadge.tsx b/webview-ui/src/components/chat/IndexingStatusBadge.tsx index f3c268c2ba..0a42b97abf 100644 --- a/webview-ui/src/components/chat/IndexingStatusBadge.tsx +++ b/webview-ui/src/components/chat/IndexingStatusBadge.tsx @@ -1,4 +1,5 @@ import React, { useState, useEffect, useMemo } from "react" +import { Database } from "lucide-react" import { cn } from "@src/lib/utils" import { vscode } from "@src/utils/vscode" import { useAppTranslation } from "@/i18n/TranslationContext" @@ -6,11 +7,11 @@ import { useTooltip } from "@/hooks/useTooltip" import { CodeIndexPopover } from "./CodeIndexPopover" import type { IndexingStatus, IndexingStatusUpdateMessage } from "@roo/ExtensionMessage" -interface IndexingStatusDotProps { +interface IndexingStatusBadgeProps { className?: string } -export const IndexingStatusDot: React.FC = ({ className }) => { +export const IndexingStatusBadge: React.FC = ({ className }) => { const { t } = useAppTranslation() const { showTooltip, handleMouseEnter, handleMouseLeave, cleanup } = useTooltip({ delay: 300 }) const [isHovered, setIsHovered] = useState(false) @@ -77,23 +78,23 @@ export const IndexingStatusDot: React.FC = ({ className handleMouseLeave() } - // Get status color classes based on status and hover state + // Get status color classes for the badge dot const getStatusColorClass = () => { const statusColors = { Standby: { - default: "bg-vscode-descriptionForeground/40", - hover: "bg-vscode-descriptionForeground/60", + default: "bg-vscode-descriptionForeground/60", + hover: "bg-vscode-descriptionForeground/80", }, Indexing: { - default: "bg-yellow-500/40 animate-pulse", + default: "bg-yellow-500 animate-pulse", hover: "bg-yellow-500 animate-pulse", }, Indexed: { - default: "bg-green-500/40", + default: "bg-green-500", hover: "bg-green-500", }, Error: { - default: "bg-red-500/40", + default: "bg-red-500", hover: "bg-red-500", }, } @@ -117,12 +118,17 @@ export const IndexingStatusDot: React.FC = ({ className "hover:opacity-100 hover:bg-[rgba(255,255,255,0.03)] hover:border-[rgba(255,255,255,0.15)]", "focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder", "active:bg-[rgba(255,255,255,0.1)]", + "cursor-pointer", className, )} aria-label={getTooltipText()}> + {/* File search icon */} + + + {/* Status dot badge */} diff --git a/webview-ui/src/components/chat/__tests__/ChatTextArea.spec.tsx b/webview-ui/src/components/chat/__tests__/ChatTextArea.spec.tsx index 86e200f0a3..75324c97f4 100644 --- a/webview-ui/src/components/chat/__tests__/ChatTextArea.spec.tsx +++ b/webview-ui/src/components/chat/__tests__/ChatTextArea.spec.tsx @@ -38,8 +38,8 @@ vi.mock("@src/context/ExtensionStateContext") const getEnhancePromptButton = () => { return screen.getByRole("button", { name: (_, element) => { - // Find the button with the sparkle icon - return element.querySelector(".codicon-sparkle") !== null + // Find the button with the wand sparkles icon (Lucide React) + return element.querySelector(".lucide-wand-sparkles") !== null }, }) } @@ -154,8 +154,9 @@ describe("ChatTextArea", () => { const enhanceButton = getEnhancePromptButton() fireEvent.click(enhanceButton) - const loadingSpinner = screen.getByText("", { selector: ".codicon-loading" }) - expect(loadingSpinner).toBeInTheDocument() + // Check if the WandSparkles icon has the animate-spin class + const animatingIcon = enhanceButton.querySelector(".animate-spin") + expect(animatingIcon).toBeInTheDocument() }) }) diff --git a/webview-ui/src/components/chat/__tests__/IndexingStatusBadge.spec.tsx b/webview-ui/src/components/chat/__tests__/IndexingStatusBadge.spec.tsx index 8a44082f58..37eb291530 100644 --- a/webview-ui/src/components/chat/__tests__/IndexingStatusBadge.spec.tsx +++ b/webview-ui/src/components/chat/__tests__/IndexingStatusBadge.spec.tsx @@ -3,7 +3,7 @@ import { render, screen, fireEvent, waitFor, act } from "@/utils/test-utils" import { vscode } from "@src/utils/vscode" -import { IndexingStatusDot } from "../IndexingStatusBadge" +import { IndexingStatusBadge } from "../IndexingStatusBadge" vi.mock("@/i18n/setup", () => ({ __esModule: true, @@ -104,9 +104,9 @@ vi.mock("@/i18n/TranslationContext", () => ({ }), })) -describe("IndexingStatusDot", () => { +describe("IndexingStatusBadge", () => { const renderComponent = (props = {}) => { - return render() + return render() } beforeEach(() => { From a732f18ea59960f232c6bcff53fdd688e4ca67c5 Mon Sep 17 00:00:00 2001 From: Roomote Bot Date: Wed, 9 Jul 2025 13:02:56 -0700 Subject: [PATCH 02/27] fix: respect .gitignore patterns for directories in list_files tool (#5393) (#5394) Co-authored-by: Matt Rubens Co-authored-by: Daniel <57051444+daniel-lxs@users.noreply.github.com> --- .../__tests__/gitignore-integration.spec.ts | 209 ++++++++++++++++++ .../glob/__tests__/gitignore-test.spec.ts | 147 ++++++++++++ src/services/glob/list-files.ts | 132 +++++------ 3 files changed, 424 insertions(+), 64 deletions(-) create mode 100644 src/services/glob/__tests__/gitignore-integration.spec.ts create mode 100644 src/services/glob/__tests__/gitignore-test.spec.ts diff --git a/src/services/glob/__tests__/gitignore-integration.spec.ts b/src/services/glob/__tests__/gitignore-integration.spec.ts new file mode 100644 index 0000000000..1361816b17 --- /dev/null +++ b/src/services/glob/__tests__/gitignore-integration.spec.ts @@ -0,0 +1,209 @@ +import { vi, describe, it, expect, beforeEach, afterEach } from "vitest" +import * as path from "path" +import * as fs from "fs" +import * as os from "os" + +// Mock ripgrep to avoid filesystem dependencies +vi.mock("../../ripgrep", () => ({ + getBinPath: vi.fn().mockResolvedValue("/mock/path/to/rg"), +})) + +// Mock vscode +vi.mock("vscode", () => ({ + env: { + appRoot: "/mock/app/root", + }, +})) + +// Mock child_process to simulate ripgrep behavior +vi.mock("child_process", () => ({ + spawn: vi.fn(), +})) + +vi.mock("../../path", () => ({ + arePathsEqual: vi.fn().mockReturnValue(false), +})) + +import { listFiles } from "../list-files" +import * as childProcess from "child_process" + +describe("list-files gitignore integration", () => { + let tempDir: string + let originalCwd: string + + beforeEach(async () => { + vi.clearAllMocks() + + // Create a temporary directory for testing + tempDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), "roo-gitignore-test-")) + originalCwd = process.cwd() + }) + + afterEach(async () => { + process.chdir(originalCwd) + // Clean up temp directory + await fs.promises.rm(tempDir, { recursive: true, force: true }) + }) + + it("should properly filter directories based on .gitignore patterns", async () => { + // Setup test directory structure + await fs.promises.mkdir(path.join(tempDir, "src")) + await fs.promises.mkdir(path.join(tempDir, "node_modules")) + await fs.promises.mkdir(path.join(tempDir, "build")) + await fs.promises.mkdir(path.join(tempDir, "dist")) + await fs.promises.mkdir(path.join(tempDir, "allowed-dir")) + + // Create .gitignore file + await fs.promises.writeFile(path.join(tempDir, ".gitignore"), "node_modules/\nbuild/\ndist/\n*.log\n") + + // Create some files + await fs.promises.writeFile(path.join(tempDir, "src", "index.ts"), "console.log('hello')") + await fs.promises.writeFile(path.join(tempDir, "allowed-dir", "file.txt"), "content") + + // Mock ripgrep to return files that would not be gitignored + const mockSpawn = vi.mocked(childProcess.spawn) + const mockProcess = { + stdout: { + on: vi.fn((event, callback) => { + if (event === "data") { + // Simulate ripgrep output (files that are not gitignored) + const files = + [path.join(tempDir, "src", "index.ts"), path.join(tempDir, "allowed-dir", "file.txt")].join( + "\n", + ) + "\n" + setTimeout(() => callback(files), 10) + } + }), + }, + stderr: { + on: vi.fn(), + }, + on: vi.fn((event, callback) => { + if (event === "close") { + setTimeout(() => callback(0), 20) + } + }), + kill: vi.fn(), + } + + mockSpawn.mockReturnValue(mockProcess as any) + + // Call listFiles in recursive mode + const [files, didHitLimit] = await listFiles(tempDir, true, 100) + + // Filter out only directories from the results + const directoriesInResult = files.filter((f) => f.endsWith("/")) + + // Verify that gitignored directories are NOT included + expect(directoriesInResult).not.toContain(path.join(tempDir, "node_modules") + "/") + expect(directoriesInResult).not.toContain(path.join(tempDir, "build") + "/") + expect(directoriesInResult).not.toContain(path.join(tempDir, "dist") + "/") + + // Verify that allowed directories ARE included + expect(directoriesInResult).toContain(path.join(tempDir, "src") + "/") + expect(directoriesInResult).toContain(path.join(tempDir, "allowed-dir") + "/") + }) + + it("should handle nested .gitignore files correctly", async () => { + // Setup nested directory structure + await fs.promises.mkdir(path.join(tempDir, "src"), { recursive: true }) + await fs.promises.mkdir(path.join(tempDir, "src", "components")) + await fs.promises.mkdir(path.join(tempDir, "src", "temp")) + await fs.promises.mkdir(path.join(tempDir, "src", "utils")) + + // Create root .gitignore + await fs.promises.writeFile(path.join(tempDir, ".gitignore"), "node_modules/\n") + + // Create nested .gitignore in src/ + await fs.promises.writeFile(path.join(tempDir, "src", ".gitignore"), "temp/\n") + + // Mock ripgrep + const mockSpawn = vi.mocked(childProcess.spawn) + const mockProcess = { + stdout: { + on: vi.fn((event, callback) => { + if (event === "data") { + setTimeout(() => callback(""), 10) + } + }), + }, + stderr: { + on: vi.fn(), + }, + on: vi.fn((event, callback) => { + if (event === "close") { + setTimeout(() => callback(0), 20) + } + }), + kill: vi.fn(), + } + + mockSpawn.mockReturnValue(mockProcess as any) + + // Call listFiles in recursive mode + const [files, didHitLimit] = await listFiles(tempDir, true, 100) + + // Filter out only directories from the results + const directoriesInResult = files.filter((f) => f.endsWith("/")) + + // Verify that nested gitignored directories are NOT included + expect(directoriesInResult).not.toContain(path.join(tempDir, "src", "temp") + "/") + + // Verify that allowed directories ARE included + expect(directoriesInResult).toContain(path.join(tempDir, "src") + "/") + expect(directoriesInResult).toContain(path.join(tempDir, "src", "components") + "/") + expect(directoriesInResult).toContain(path.join(tempDir, "src", "utils") + "/") + }) + + it("should respect .gitignore in non-recursive mode too", async () => { + // Setup test directory structure + await fs.promises.mkdir(path.join(tempDir, "src")) + await fs.promises.mkdir(path.join(tempDir, "node_modules")) + await fs.promises.mkdir(path.join(tempDir, "allowed-dir")) + + // Create .gitignore file + await fs.promises.writeFile(path.join(tempDir, ".gitignore"), "node_modules/\n") + + // Mock ripgrep for non-recursive mode + const mockSpawn = vi.mocked(childProcess.spawn) + const mockProcess = { + stdout: { + on: vi.fn((event, callback) => { + if (event === "data") { + // In non-recursive mode, ripgrep should now respect .gitignore + const files = [path.join(tempDir, "src"), path.join(tempDir, "allowed-dir")].join("\n") + "\n" + setTimeout(() => callback(files), 10) + } + }), + }, + stderr: { + on: vi.fn(), + }, + on: vi.fn((event, callback) => { + if (event === "close") { + setTimeout(() => callback(0), 20) + } + }), + kill: vi.fn(), + } + + mockSpawn.mockReturnValue(mockProcess as any) + + // Call listFiles in NON-recursive mode + const [files, didHitLimit] = await listFiles(tempDir, false, 100) + + // Verify ripgrep was called without --no-ignore-vcs (should respect .gitignore) + const [rgPath, args] = mockSpawn.mock.calls[0] + expect(args).not.toContain("--no-ignore-vcs") + + // Filter out only directories from the results + const directoriesInResult = files.filter((f) => f.endsWith("/")) + + // Verify that gitignored directories are NOT included even in non-recursive mode + expect(directoriesInResult).not.toContain(path.join(tempDir, "node_modules") + "/") + + // Verify that allowed directories ARE included + expect(directoriesInResult).toContain(path.join(tempDir, "src") + "/") + expect(directoriesInResult).toContain(path.join(tempDir, "allowed-dir") + "/") + }) +}) diff --git a/src/services/glob/__tests__/gitignore-test.spec.ts b/src/services/glob/__tests__/gitignore-test.spec.ts new file mode 100644 index 0000000000..cef884b584 --- /dev/null +++ b/src/services/glob/__tests__/gitignore-test.spec.ts @@ -0,0 +1,147 @@ +import { vi, describe, it, expect, beforeEach, afterEach } from "vitest" +import * as path from "path" +import * as fs from "fs" +import * as os from "os" + +// Mock ripgrep to avoid filesystem dependencies +vi.mock("../../ripgrep", () => ({ + getBinPath: vi.fn().mockResolvedValue("/mock/path/to/rg"), +})) + +// Mock vscode +vi.mock("vscode", () => ({ + env: { + appRoot: "/mock/app/root", + }, +})) + +vi.mock("child_process", () => ({ + spawn: vi.fn(), +})) + +vi.mock("../../path", () => ({ + arePathsEqual: vi.fn().mockReturnValue(false), +})) + +import { listFiles } from "../list-files" +import * as childProcess from "child_process" + +describe("list-files gitignore support", () => { + let tempDir: string + let originalCwd: string + + beforeEach(async () => { + vi.clearAllMocks() + + // Create a temporary directory for testing + tempDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), "roo-test-")) + originalCwd = process.cwd() + process.chdir(tempDir) + }) + + afterEach(async () => { + process.chdir(originalCwd) + // Clean up temp directory + await fs.promises.rm(tempDir, { recursive: true, force: true }) + }) + + it("should respect .gitignore patterns for directories in recursive mode", async () => { + // Setup test directory structure + await fs.promises.mkdir(path.join(tempDir, "src")) + await fs.promises.mkdir(path.join(tempDir, "node_modules")) + await fs.promises.mkdir(path.join(tempDir, "build")) + await fs.promises.mkdir(path.join(tempDir, "ignored-dir")) + + // Create .gitignore file + await fs.promises.writeFile(path.join(tempDir, ".gitignore"), "node_modules/\nbuild/\nignored-dir/\n") + + // Create some files + await fs.promises.writeFile(path.join(tempDir, "src", "index.ts"), "") + await fs.promises.writeFile(path.join(tempDir, "node_modules", "package.json"), "") + await fs.promises.writeFile(path.join(tempDir, "build", "output.js"), "") + await fs.promises.writeFile(path.join(tempDir, "ignored-dir", "file.txt"), "") + + // Mock ripgrep to return only non-ignored files + const mockSpawn = vi.mocked(childProcess.spawn) + const mockProcess = { + stdout: { + on: vi.fn((event, callback) => { + if (event === "data") { + // Ripgrep should respect .gitignore and only return src/index.ts + setTimeout(() => callback(`${path.join(tempDir, "src", "index.ts")}\n`), 10) + } + }), + }, + stderr: { + on: vi.fn(), + }, + on: vi.fn((event, callback) => { + if (event === "close") { + setTimeout(() => callback(0), 20) + } + }), + kill: vi.fn(), + } + + mockSpawn.mockReturnValue(mockProcess as any) + + // Call listFiles in recursive mode + const [files, didHitLimit] = await listFiles(tempDir, true, 100) + + // Verify that gitignored directories are not included + const directoriesInResult = files.filter((f) => f.endsWith("/")) + + expect(directoriesInResult).not.toContain(path.join(tempDir, "node_modules") + "/") + expect(directoriesInResult).not.toContain(path.join(tempDir, "build") + "/") + expect(directoriesInResult).not.toContain(path.join(tempDir, "ignored-dir") + "/") + + // But src/ should be included + expect(directoriesInResult).toContain(path.join(tempDir, "src") + "/") + }) + + it("should handle nested .gitignore files", async () => { + // Setup nested directory structure + await fs.promises.mkdir(path.join(tempDir, "src"), { recursive: true }) + await fs.promises.mkdir(path.join(tempDir, "src", "components")) + await fs.promises.mkdir(path.join(tempDir, "src", "temp")) + + // Create root .gitignore + await fs.promises.writeFile(path.join(tempDir, ".gitignore"), "node_modules/\n") + + // Create nested .gitignore in src/ + await fs.promises.writeFile(path.join(tempDir, "src", ".gitignore"), "temp/\n") + + // Mock ripgrep + const mockSpawn = vi.mocked(childProcess.spawn) + const mockProcess = { + stdout: { + on: vi.fn((event, callback) => { + if (event === "data") { + setTimeout(() => callback(""), 10) + } + }), + }, + stderr: { + on: vi.fn(), + }, + on: vi.fn((event, callback) => { + if (event === "close") { + setTimeout(() => callback(0), 20) + } + }), + kill: vi.fn(), + } + + mockSpawn.mockReturnValue(mockProcess as any) + + // Call listFiles in recursive mode + const [files, didHitLimit] = await listFiles(tempDir, true, 100) + + // Verify that nested gitignored directories are not included + const directoriesInResult = files.filter((f) => f.endsWith("/")) + + expect(directoriesInResult).not.toContain(path.join(tempDir, "src", "temp") + "/") + expect(directoriesInResult).toContain(path.join(tempDir, "src") + "/") + expect(directoriesInResult).toContain(path.join(tempDir, "src", "components") + "/") + }) +}) diff --git a/src/services/glob/list-files.ts b/src/services/glob/list-files.ts index 3fb1b2e154..3164ed1eb0 100644 --- a/src/services/glob/list-files.ts +++ b/src/services/glob/list-files.ts @@ -3,6 +3,7 @@ import * as path from "path" import * as fs from "fs" import * as childProcess from "child_process" import * as vscode from "vscode" +import ignore from "ignore" import { arePathsEqual } from "../../utils/path" import { getBinPath } from "../../services/ripgrep" import { DIRS_TO_IGNORE } from "./constants" @@ -34,9 +35,9 @@ export async function listFiles(dirPath: string, recursive: boolean, limit: numb // Get files using ripgrep const files = await listFilesWithRipgrep(rgPath, dirPath, recursive, limit) - // Get directories with proper filtering - const gitignorePatterns = await parseGitignoreFile(dirPath, recursive) - const directories = await listFilteredDirectories(dirPath, recursive, gitignorePatterns) + // Get directories with proper filtering using ignore library + const ignoreInstance = await createIgnoreInstance(dirPath) + const directories = await listFilteredDirectories(dirPath, recursive, ignoreInstance) // Combine and format the results return formatAndCombineResults(files, directories, limit) @@ -134,8 +135,8 @@ function buildNonRecursiveArgs(): string[] { args.push("-g", "*") args.push("--maxdepth", "1") // ripgrep uses maxdepth, not max-depth - // Don't respect .gitignore in non-recursive mode (consistent with original behavior) - args.push("--no-ignore-vcs") + // Respect .gitignore in non-recursive mode too + // (ripgrep respects .gitignore by default) // Apply directory exclusions for non-recursive searches for (const dir of DIRS_TO_IGNORE) { @@ -153,37 +154,61 @@ function buildNonRecursiveArgs(): string[] { } /** - * Parse the .gitignore file if it exists and is relevant + * Create an ignore instance that handles .gitignore files properly + * This replaces the custom gitignore parsing with the proper ignore library */ -async function parseGitignoreFile(dirPath: string, recursive: boolean): Promise { - if (!recursive) { - return [] // Only needed for recursive mode +async function createIgnoreInstance(dirPath: string): Promise> { + const ignoreInstance = ignore() + const absolutePath = path.resolve(dirPath) + + // Find all .gitignore files from the target directory up to the root + const gitignoreFiles = await findGitignoreFiles(absolutePath) + + // Add patterns from all .gitignore files + for (const gitignoreFile of gitignoreFiles) { + try { + const content = await fs.promises.readFile(gitignoreFile, "utf8") + ignoreInstance.add(content) + } catch (err) { + // Continue if we can't read a .gitignore file + console.warn(`Error reading .gitignore at ${gitignoreFile}: ${err}`) + } } - const absolutePath = path.resolve(dirPath) - const gitignorePath = path.join(absolutePath, ".gitignore") + // Always ignore .gitignore files themselves + ignoreInstance.add(".gitignore") - try { - // Check if .gitignore exists - const exists = await fs.promises - .access(gitignorePath) - .then(() => true) - .catch(() => false) + return ignoreInstance +} - if (!exists) { - return [] +/** + * Find all .gitignore files from the given directory up to the workspace root + */ +async function findGitignoreFiles(startPath: string): Promise { + const gitignoreFiles: string[] = [] + let currentPath = startPath + + // Walk up the directory tree looking for .gitignore files + while (currentPath && currentPath !== path.dirname(currentPath)) { + const gitignorePath = path.join(currentPath, ".gitignore") + + try { + await fs.promises.access(gitignorePath) + gitignoreFiles.push(gitignorePath) + } catch { + // .gitignore doesn't exist at this level, continue } - // Read and parse .gitignore file - const content = await fs.promises.readFile(gitignorePath, "utf8") - return content - .split("\n") - .map((line) => line.trim()) - .filter((line) => line && !line.startsWith("#")) - } catch (err) { - console.warn(`Error reading .gitignore: ${err}`) - return [] // Continue without gitignore patterns on error + // Move up one directory + const parentPath = path.dirname(currentPath) + if (parentPath === currentPath) { + break // Reached root + } + currentPath = parentPath } + + // Return in reverse order (root .gitignore first, then more specific ones) + return gitignoreFiles.reverse() } /** @@ -192,7 +217,7 @@ async function parseGitignoreFile(dirPath: string, recursive: boolean): Promise< async function listFilteredDirectories( dirPath: string, recursive: boolean, - gitignorePatterns: string[], + ignoreInstance: ReturnType, ): Promise { const absolutePath = path.resolve(dirPath) const directories: string[] = [] @@ -209,7 +234,7 @@ async function listFilteredDirectories( const fullDirPath = path.join(currentPath, dirName) // Check if this directory should be included - if (shouldIncludeDirectory(dirName, recursive, gitignorePatterns)) { + if (shouldIncludeDirectory(dirName, fullDirPath, dirPath, ignoreInstance)) { // Add the directory to our results (with trailing slash) const formattedPath = fullDirPath.endsWith("/") ? fullDirPath : `${fullDirPath}/` directories.push(formattedPath) @@ -236,7 +261,12 @@ async function listFilteredDirectories( /** * Determine if a directory should be included in results based on filters */ -function shouldIncludeDirectory(dirName: string, recursive: boolean, gitignorePatterns: string[]): boolean { +function shouldIncludeDirectory( + dirName: string, + fullDirPath: string, + basePath: string, + ignoreInstance: ReturnType, +): boolean { // Skip hidden directories if configured to ignore them if (dirName.startsWith(".") && DIRS_TO_IGNORE.includes(".*")) { return false @@ -247,8 +277,13 @@ function shouldIncludeDirectory(dirName: string, recursive: boolean, gitignorePa return false } - // Check against gitignore patterns in recursive mode - if (recursive && gitignorePatterns.length > 0 && isIgnoredByGitignore(dirName, gitignorePatterns)) { + // Check against gitignore patterns using the ignore library + // Calculate relative path from the base directory + const relativePath = path.relative(basePath, fullDirPath) + const normalizedPath = relativePath.replace(/\\/g, "/") + + // Check if the directory is ignored by .gitignore + if (ignoreInstance.ignores(normalizedPath) || ignoreInstance.ignores(normalizedPath + "/")) { return false } @@ -277,37 +312,6 @@ function isDirectoryExplicitlyIgnored(dirName: string): boolean { return false } -/** - * Check if a directory matches any gitignore patterns - */ -function isIgnoredByGitignore(dirName: string, gitignorePatterns: string[]): boolean { - for (const pattern of gitignorePatterns) { - // Directory patterns (ending with /) - if (pattern.endsWith("/")) { - const dirPattern = pattern.slice(0, -1) - if (dirName === dirPattern) { - return true - } - if (pattern.startsWith("**/") && dirName === dirPattern.slice(3)) { - return true - } - } - // Simple name patterns - else if (dirName === pattern) { - return true - } - // Wildcard patterns - else if (pattern.includes("*")) { - const regexPattern = pattern.replace(/\\/g, "\\\\").replace(/\./g, "\\.").replace(/\*/g, ".*") - const regex = new RegExp(`^${regexPattern}$`) - if (regex.test(dirName)) { - return true - } - } - } - - return false -} /** * Combine file and directory results and format them properly From dfcba654a6fd4c57156a8a6e008e35239705657a Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Wed, 9 Jul 2025 16:03:20 -0400 Subject: [PATCH 03/27] Tweak alignment of indexing dot (#5523) --- webview-ui/src/components/chat/IndexingStatusBadge.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webview-ui/src/components/chat/IndexingStatusBadge.tsx b/webview-ui/src/components/chat/IndexingStatusBadge.tsx index 0a42b97abf..ff5a0171b5 100644 --- a/webview-ui/src/components/chat/IndexingStatusBadge.tsx +++ b/webview-ui/src/components/chat/IndexingStatusBadge.tsx @@ -128,7 +128,7 @@ export const IndexingStatusBadge: React.FC = ({ classN {/* Status dot badge */} From 76b13dfd7ac611b96a676aaf62304fd7cfe04147 Mon Sep 17 00:00:00 2001 From: dlab-anton Date: Thu, 10 Jul 2025 03:07:18 +0700 Subject: [PATCH 04/27] feat ui/ux: overflow button in header actions (#3060) Co-authored-by: hannesrudolph Co-authored-by: Daniel Riccio --- src/package.json | 73 ++++++++++++++++++++++-------------------------- 1 file changed, 34 insertions(+), 39 deletions(-) diff --git a/src/package.json b/src/package.json index 58c62c207f..2bbe9eabb2 100644 --- a/src/package.json +++ b/src/package.json @@ -218,34 +218,29 @@ "group": "navigation@1", "when": "view == roo-cline.SidebarProvider" }, - { - "command": "roo-cline.mcpButtonClicked", - "group": "navigation@2", - "when": "view == roo-cline.SidebarProvider" - }, - { - "command": "roo-cline.marketplaceButtonClicked", - "group": "navigation@3", - "when": "view == roo-cline.SidebarProvider" - }, { "command": "roo-cline.historyButtonClicked", - "group": "navigation@4", - "when": "view == roo-cline.SidebarProvider" - }, - { - "command": "roo-cline.popoutButtonClicked", - "group": "navigation@5", - "when": "view == roo-cline.SidebarProvider" - }, - { - "command": "roo-cline.accountButtonClicked", - "group": "navigation@6", "when": "view == roo-cline.SidebarProvider" }, { "command": "roo-cline.settingsButtonClicked", - "group": "navigation@7", + "group": "navigation@3", + "when": "view == roo-cline.SidebarProvider" + }, + { + "command": "roo-cline.mcpButtonClicked", + "when": "view == roo-cline.SidebarProvider" + }, + { + "command": "roo-cline.marketplaceButtonClicked", + "when": "view == roo-cline.SidebarProvider" + }, + { + "command": "roo-cline.popoutButtonClicked", + "when": "view == roo-cline.SidebarProvider" + }, + { + "command": "roo-cline.accountButtonClicked", "when": "view == roo-cline.SidebarProvider" } ], @@ -255,29 +250,29 @@ "group": "navigation@1", "when": "activeWebviewPanelId == roo-cline.TabPanelProvider" }, - { - "command": "roo-cline.mcpButtonClicked", - "group": "navigation@2", - "when": "activeWebviewPanelId == roo-cline.TabPanelProvider" - }, - { - "command": "roo-cline.marketplaceButtonClicked", - "group": "navigation@3", - "when": "activeWebviewPanelId == roo-cline.TabPanelProvider" - }, { "command": "roo-cline.historyButtonClicked", - "group": "navigation@4", - "when": "activeWebviewPanelId == roo-cline.TabPanelProvider" - }, - { - "command": "roo-cline.accountButtonClicked", - "group": "navigation@5", "when": "activeWebviewPanelId == roo-cline.TabPanelProvider" }, { "command": "roo-cline.settingsButtonClicked", - "group": "navigation@6", + "group": "navigation@3", + "when": "activeWebviewPanelId == roo-cline.TabPanelProvider" + }, + { + "command": "roo-cline.mcpButtonClicked", + "when": "activeWebviewPanelId == roo-cline.TabPanelProvider" + }, + { + "command": "roo-cline.marketplaceButtonClicked", + "when": "activeWebviewPanelId == roo-cline.TabPanelProvider" + }, + { + "command": "roo-cline.popoutButtonClicked", + "when": "activeWebviewPanelId == roo-cline.TabPanelProvider" + }, + { + "command": "roo-cline.accountButtonClicked", "when": "activeWebviewPanelId == roo-cline.TabPanelProvider" } ] From c6412b4339789fefdbd7b521490c04ba91484fa4 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Wed, 9 Jul 2025 16:27:55 -0400 Subject: [PATCH 05/27] Add modes to the overflow menu (#5525) --- src/package.json | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/package.json b/src/package.json index 2bbe9eabb2..aac0e5f0bc 100644 --- a/src/package.json +++ b/src/package.json @@ -75,6 +75,11 @@ "title": "%command.newTask.title%", "icon": "$(add)" }, + { + "command": "roo-cline.promptsButtonClicked", + "title": "%command.prompts.title%", + "icon": "$(organization)" + }, { "command": "roo-cline.mcpButtonClicked", "title": "%command.mcpServers.title%", @@ -227,20 +232,29 @@ "group": "navigation@3", "when": "view == roo-cline.SidebarProvider" }, + { + "command": "roo-cline.promptsButtonClicked", + "group": "overflow", + "when": "view == roo-cline.SidebarProvider" + }, { "command": "roo-cline.mcpButtonClicked", + "group": "overflow", "when": "view == roo-cline.SidebarProvider" }, { "command": "roo-cline.marketplaceButtonClicked", + "group": "overflow", "when": "view == roo-cline.SidebarProvider" }, { "command": "roo-cline.popoutButtonClicked", + "group": "overflow", "when": "view == roo-cline.SidebarProvider" }, { "command": "roo-cline.accountButtonClicked", + "group": "overflow", "when": "view == roo-cline.SidebarProvider" } ], @@ -259,20 +273,29 @@ "group": "navigation@3", "when": "activeWebviewPanelId == roo-cline.TabPanelProvider" }, + { + "command": "roo-cline.promptsButtonClicked", + "group": "overflow", + "when": "activeWebviewPanelId == roo-cline.TabPanelProvider" + }, { "command": "roo-cline.mcpButtonClicked", + "group": "overflow", "when": "activeWebviewPanelId == roo-cline.TabPanelProvider" }, { "command": "roo-cline.marketplaceButtonClicked", + "group": "overflow", "when": "activeWebviewPanelId == roo-cline.TabPanelProvider" }, { "command": "roo-cline.popoutButtonClicked", + "group": "overflow", "when": "activeWebviewPanelId == roo-cline.TabPanelProvider" }, { "command": "roo-cline.accountButtonClicked", + "group": "overflow", "when": "activeWebviewPanelId == roo-cline.TabPanelProvider" } ] From 12ae900660139c113f91a06a75e6f61cbff5d464 Mon Sep 17 00:00:00 2001 From: Daniel <57051444+daniel-lxs@users.noreply.github.com> Date: Wed, 9 Jul 2025 15:49:32 -0500 Subject: [PATCH 06/27] fix: handle null/empty custom modes files to prevent 'Cannot read properties of null' error (#5526) * fix: handle null/empty custom modes files to prevent 'Cannot read properties of null' error - Fix SimpleInstaller to ensure existingData is always an object after yaml.parse - Fix CustomModesManager.parseYamlSafely to return empty object instead of null - Ensure customModes array is always initialized in both install and remove operations - Add tests for empty/null file handling scenarios - Update existing tests to match correct behavior * fix: handle null/undefined settings in updateModesInFile and loadModesFromFile - Ensure settings object exists before accessing customModes property - Initialize customModes as empty array if undefined - Prevent 'Cannot read properties of null' error during mode import - Add proper validation in loadModesFromFile before schema check --- src/core/config/CustomModesManager.ts | 20 +++- src/services/marketplace/SimpleInstaller.ts | 51 +++++---- .../__tests__/SimpleInstaller.spec.ts | 100 ++++++++++++++++++ 3 files changed, 147 insertions(+), 24 deletions(-) diff --git a/src/core/config/CustomModesManager.ts b/src/core/config/CustomModesManager.ts index 9f29185eba..b4bcfa62d6 100644 --- a/src/core/config/CustomModesManager.ts +++ b/src/core/config/CustomModesManager.ts @@ -148,7 +148,9 @@ export class CustomModesManager { cleanedContent = this.cleanInvisibleCharacters(cleanedContent) try { - return yaml.parse(cleanedContent) + const parsed = yaml.parse(cleanedContent) + // Ensure we never return null or undefined + return parsed ?? {} } catch (yamlError) { // For .roomodes files, try JSON as fallback if (filePath.endsWith(ROOMODES_FILENAME)) { @@ -180,6 +182,12 @@ export class CustomModesManager { try { const content = await fs.readFile(filePath, "utf-8") const settings = this.parseYamlSafely(content, filePath) + + // Ensure settings has customModes property + if (!settings || typeof settings !== "object" || !settings.customModes) { + return [] + } + const result = customModesSettingsSchema.safeParse(settings) if (!result.success) { @@ -458,7 +466,15 @@ export class CustomModesManager { settings = { customModes: [] } } - settings.customModes = operation(settings.customModes || []) + // Ensure settings is an object and has customModes property + if (!settings || typeof settings !== "object") { + settings = { customModes: [] } + } + if (!settings.customModes) { + settings.customModes = [] + } + + settings.customModes = operation(settings.customModes) await fs.writeFile(filePath, yaml.stringify(settings, { lineWidth: 0 }), "utf-8") } diff --git a/src/services/marketplace/SimpleInstaller.ts b/src/services/marketplace/SimpleInstaller.ts index 91f5bcad26..2274b65343 100644 --- a/src/services/marketplace/SimpleInstaller.ts +++ b/src/services/marketplace/SimpleInstaller.ts @@ -47,7 +47,9 @@ export class SimpleInstaller { let existingData: any = { customModes: [] } try { const existing = await fs.readFile(filePath, "utf-8") - existingData = yaml.parse(existing) || { customModes: [] } + const parsed = yaml.parse(existing) + // Ensure we have a valid object with customModes array + existingData = parsed && typeof parsed === "object" ? parsed : { customModes: [] } } catch (error: any) { if (error.code === "ENOENT") { // File doesn't exist, use default structure - this is fine @@ -253,7 +255,9 @@ export class SimpleInstaller { let existingData: any try { - existingData = yaml.parse(existing) + const parsed = yaml.parse(existing) + // Ensure we have a valid object + existingData = parsed && typeof parsed === "object" ? parsed : {} } catch (parseError) { // If we can't parse the file, we can't safely remove a mode const fileName = target === "project" ? ".roomodes" : "custom-modes.yaml" @@ -263,27 +267,30 @@ export class SimpleInstaller { ) } - if (existingData?.customModes) { - // Parse the item content to get the slug - let content: string - if (Array.isArray(item.content)) { - // Array of McpInstallationMethod objects - use first method - content = item.content[0].content - } else { - content = item.content - } - const modeData = yaml.parse(content || "") - - if (!modeData.slug) { - return // Nothing to remove if no slug - } - - // Remove mode with matching slug - existingData.customModes = existingData.customModes.filter((mode: any) => mode.slug !== modeData.slug) - - // Always write back the file, even if empty - await fs.writeFile(filePath, yaml.stringify(existingData, { lineWidth: 0 }), "utf-8") + // Ensure customModes array exists + if (!existingData.customModes) { + existingData.customModes = [] } + + // Parse the item content to get the slug + let content: string + if (Array.isArray(item.content)) { + // Array of McpInstallationMethod objects - use first method + content = item.content[0].content + } else { + content = item.content + } + const modeData = yaml.parse(content || "") + + if (!modeData.slug) { + return // Nothing to remove if no slug + } + + // Remove mode with matching slug + existingData.customModes = existingData.customModes.filter((mode: any) => mode.slug !== modeData.slug) + + // Always write back the file, even if empty + await fs.writeFile(filePath, yaml.stringify(existingData, { lineWidth: 0 }), "utf-8") } catch (error: any) { if (error.code === "ENOENT") { // File doesn't exist, nothing to remove diff --git a/src/services/marketplace/__tests__/SimpleInstaller.spec.ts b/src/services/marketplace/__tests__/SimpleInstaller.spec.ts index 4934d0a6bc..546eb16f9a 100644 --- a/src/services/marketplace/__tests__/SimpleInstaller.spec.ts +++ b/src/services/marketplace/__tests__/SimpleInstaller.spec.ts @@ -89,6 +89,59 @@ describe("SimpleInstaller", () => { expect(writtenData.customModes.find((m: any) => m.slug === "test")).toBeDefined() }) + it("should handle empty .roomodes file", async () => { + // Empty file content + mockFs.readFile.mockResolvedValueOnce("") + mockFs.writeFile.mockResolvedValueOnce(undefined as any) + + const result = await installer.installItem(mockModeItem, { target: "project" }) + + expect(result.filePath).toBe(path.join("/test/workspace", ".roomodes")) + expect(mockFs.writeFile).toHaveBeenCalled() + + // Verify the written content contains the new mode + const writtenContent = mockFs.writeFile.mock.calls[0][1] as string + const writtenData = yaml.parse(writtenContent) + expect(writtenData.customModes).toHaveLength(1) + expect(writtenData.customModes[0].slug).toBe("test") + }) + + it("should handle .roomodes file with null content", async () => { + // File exists but yaml.parse returns null + mockFs.readFile.mockResolvedValueOnce("---\n") + mockFs.writeFile.mockResolvedValueOnce(undefined as any) + + const result = await installer.installItem(mockModeItem, { target: "project" }) + + expect(result.filePath).toBe(path.join("/test/workspace", ".roomodes")) + expect(mockFs.writeFile).toHaveBeenCalled() + + // Verify the written content contains the new mode + const writtenContent = mockFs.writeFile.mock.calls[0][1] as string + const writtenData = yaml.parse(writtenContent) + expect(writtenData.customModes).toHaveLength(1) + expect(writtenData.customModes[0].slug).toBe("test") + }) + + it("should handle .roomodes file without customModes property", async () => { + // File has valid YAML but no customModes property + const contentWithoutCustomModes = yaml.stringify({ someOtherProperty: "value" }) + mockFs.readFile.mockResolvedValueOnce(contentWithoutCustomModes) + mockFs.writeFile.mockResolvedValueOnce(undefined as any) + + const result = await installer.installItem(mockModeItem, { target: "project" }) + + expect(result.filePath).toBe(path.join("/test/workspace", ".roomodes")) + expect(mockFs.writeFile).toHaveBeenCalled() + + // Verify the written content contains the new mode and preserves other properties + const writtenContent = mockFs.writeFile.mock.calls[0][1] as string + const writtenData = yaml.parse(writtenContent) + expect(writtenData.customModes).toHaveLength(1) + expect(writtenData.customModes[0].slug).toBe("test") + expect(writtenData.someOtherProperty).toBe("value") + }) + it("should throw error when .roomodes contains invalid YAML", async () => { const invalidYaml = "invalid: yaml: content: {" @@ -224,5 +277,52 @@ describe("SimpleInstaller", () => { expect(mockFs.writeFile).not.toHaveBeenCalled() }) + + it("should handle empty .roomodes file during removal", async () => { + // Empty file content + mockFs.readFile.mockResolvedValueOnce("") + mockFs.writeFile.mockResolvedValueOnce(undefined as any) + + // Should not throw + await installer.removeItem(mockModeItem, { target: "project" }) + + // Should write back a valid structure with empty customModes + expect(mockFs.writeFile).toHaveBeenCalled() + const writtenContent = mockFs.writeFile.mock.calls[0][1] as string + const writtenData = yaml.parse(writtenContent) + expect(writtenData.customModes).toEqual([]) + }) + + it("should handle .roomodes file with null content during removal", async () => { + // File exists but yaml.parse returns null + mockFs.readFile.mockResolvedValueOnce("---\n") + mockFs.writeFile.mockResolvedValueOnce(undefined as any) + + // Should not throw + await installer.removeItem(mockModeItem, { target: "project" }) + + // Should write back a valid structure with empty customModes + expect(mockFs.writeFile).toHaveBeenCalled() + const writtenContent = mockFs.writeFile.mock.calls[0][1] as string + const writtenData = yaml.parse(writtenContent) + expect(writtenData.customModes).toEqual([]) + }) + + it("should handle .roomodes file without customModes property during removal", async () => { + // File has valid YAML but no customModes property + const contentWithoutCustomModes = yaml.stringify({ someOtherProperty: "value" }) + mockFs.readFile.mockResolvedValueOnce(contentWithoutCustomModes) + mockFs.writeFile.mockResolvedValueOnce(undefined as any) + + // Should not throw + await installer.removeItem(mockModeItem, { target: "project" }) + + // Should write back the file with the same content (no modes to remove) + expect(mockFs.writeFile).toHaveBeenCalled() + const writtenContent = mockFs.writeFile.mock.calls[0][1] as string + const writtenData = yaml.parse(writtenContent) + expect(writtenData.customModes).toEqual([]) + expect(writtenData.someOtherProperty).toBe("value") + }) }) }) From 8824a719ebdc4577edb422c1e50434c73d793787 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Wed, 9 Jul 2025 16:50:29 -0400 Subject: [PATCH 07/27] Fix nightly build again (#5527) --- src/package.json | 66 +++++++++++++++++++++++++----------------------- 1 file changed, 34 insertions(+), 32 deletions(-) diff --git a/src/package.json b/src/package.json index aac0e5f0bc..79c5a54caa 100644 --- a/src/package.json +++ b/src/package.json @@ -223,38 +223,39 @@ "group": "navigation@1", "when": "view == roo-cline.SidebarProvider" }, - { - "command": "roo-cline.historyButtonClicked", - "when": "view == roo-cline.SidebarProvider" - }, { "command": "roo-cline.settingsButtonClicked", - "group": "navigation@3", + "group": "navigation@2", "when": "view == roo-cline.SidebarProvider" }, { - "command": "roo-cline.promptsButtonClicked", - "group": "overflow", - "when": "view == roo-cline.SidebarProvider" - }, - { - "command": "roo-cline.mcpButtonClicked", - "group": "overflow", + "command": "roo-cline.historyButtonClicked", + "group": "overflow@1", "when": "view == roo-cline.SidebarProvider" }, { "command": "roo-cline.marketplaceButtonClicked", - "group": "overflow", + "group": "overflow@2", "when": "view == roo-cline.SidebarProvider" }, { - "command": "roo-cline.popoutButtonClicked", - "group": "overflow", + "command": "roo-cline.promptsButtonClicked", + "group": "overflow@3", + "when": "view == roo-cline.SidebarProvider" + }, + { + "command": "roo-cline.mcpButtonClicked", + "group": "overflow@4", "when": "view == roo-cline.SidebarProvider" }, { "command": "roo-cline.accountButtonClicked", - "group": "overflow", + "group": "overflow@5", + "when": "view == roo-cline.SidebarProvider" + }, + { + "command": "roo-cline.popoutButtonClicked", + "group": "overflow@6", "when": "view == roo-cline.SidebarProvider" } ], @@ -264,38 +265,39 @@ "group": "navigation@1", "when": "activeWebviewPanelId == roo-cline.TabPanelProvider" }, - { - "command": "roo-cline.historyButtonClicked", - "when": "activeWebviewPanelId == roo-cline.TabPanelProvider" - }, { "command": "roo-cline.settingsButtonClicked", - "group": "navigation@3", + "group": "navigation@2", "when": "activeWebviewPanelId == roo-cline.TabPanelProvider" }, { - "command": "roo-cline.promptsButtonClicked", - "group": "overflow", - "when": "activeWebviewPanelId == roo-cline.TabPanelProvider" - }, - { - "command": "roo-cline.mcpButtonClicked", - "group": "overflow", + "command": "roo-cline.historyButtonClicked", + "group": "overflow@1", "when": "activeWebviewPanelId == roo-cline.TabPanelProvider" }, { "command": "roo-cline.marketplaceButtonClicked", - "group": "overflow", + "group": "overflow@2", "when": "activeWebviewPanelId == roo-cline.TabPanelProvider" }, { - "command": "roo-cline.popoutButtonClicked", - "group": "overflow", + "command": "roo-cline.promptsButtonClicked", + "group": "overflow@3", + "when": "activeWebviewPanelId == roo-cline.TabPanelProvider" + }, + { + "command": "roo-cline.mcpButtonClicked", + "group": "overflow@4", "when": "activeWebviewPanelId == roo-cline.TabPanelProvider" }, { "command": "roo-cline.accountButtonClicked", - "group": "overflow", + "group": "overflow@5", + "when": "activeWebviewPanelId == roo-cline.TabPanelProvider" + }, + { + "command": "roo-cline.popoutButtonClicked", + "group": "overflow@6", "when": "activeWebviewPanelId == roo-cline.TabPanelProvider" } ] From dd00fc8a0f0823878aa5a6be737ca0cca4fd7c21 Mon Sep 17 00:00:00 2001 From: Daniel <57051444+daniel-lxs@users.noreply.github.com> Date: Wed, 9 Jul 2025 16:24:53 -0500 Subject: [PATCH 08/27] feat: Replace title attributes with StandardTooltip in ChatTextArea (#5528) feat: Replace title attributes with StandardTooltip in ChatTextArea buttons - Added StandardTooltip wrapper for Stop TTS button - Replaced title attributes with StandardTooltip for Enhance Prompt button - Replaced title attributes with StandardTooltip for Send Message button - Replaced title attributes with StandardTooltip for Add Images button Note: The stopTts translation key needs to be added to the localization files --- .../src/components/chat/ChatTextArea.tsx | 141 +++++++++--------- webview-ui/src/i18n/locales/ca/chat.json | 1 + webview-ui/src/i18n/locales/de/chat.json | 1 + webview-ui/src/i18n/locales/en/chat.json | 1 + webview-ui/src/i18n/locales/es/chat.json | 1 + webview-ui/src/i18n/locales/fr/chat.json | 1 + webview-ui/src/i18n/locales/hi/chat.json | 1 + webview-ui/src/i18n/locales/id/chat.json | 1 + webview-ui/src/i18n/locales/it/chat.json | 1 + webview-ui/src/i18n/locales/ja/chat.json | 1 + webview-ui/src/i18n/locales/ko/chat.json | 1 + webview-ui/src/i18n/locales/nl/chat.json | 1 + webview-ui/src/i18n/locales/pl/chat.json | 1 + webview-ui/src/i18n/locales/pt-BR/chat.json | 1 + webview-ui/src/i18n/locales/ru/chat.json | 1 + webview-ui/src/i18n/locales/tr/chat.json | 1 + webview-ui/src/i18n/locales/vi/chat.json | 1 + webview-ui/src/i18n/locales/zh-CN/chat.json | 1 + webview-ui/src/i18n/locales/zh-TW/chat.json | 1 + 19 files changed, 91 insertions(+), 68 deletions(-) diff --git a/webview-ui/src/components/chat/ChatTextArea.tsx b/webview-ui/src/components/chat/ChatTextArea.tsx index 3d70f4a4ca..ee622239e2 100644 --- a/webview-ui/src/components/chat/ChatTextArea.tsx +++ b/webview-ui/src/components/chat/ChatTextArea.tsx @@ -951,59 +951,63 @@ const ChatTextArea = forwardRef( /> {isTtsPlaying && ( - + + + )}
- + + +
- + + +
{!inputValue && ( @@ -1170,27 +1174,28 @@ const ChatTextArea = forwardRef(
- + + +
diff --git a/webview-ui/src/i18n/locales/ca/chat.json b/webview-ui/src/i18n/locales/ca/chat.json index 848306a1cf..8ad87049e3 100644 --- a/webview-ui/src/i18n/locales/ca/chat.json +++ b/webview-ui/src/i18n/locales/ca/chat.json @@ -105,6 +105,7 @@ "enhancePrompt": "Millora la sol·licitud amb context addicional", "addImages": "Afegeix imatges al missatge", "sendMessage": "Envia el missatge", + "stopTts": "Atura la síntesi de veu", "typeMessage": "Escriu un missatge...", "typeTask": "Escriu la teva tasca aquí...", "addContext": "@ per afegir context, / per canviar de mode", diff --git a/webview-ui/src/i18n/locales/de/chat.json b/webview-ui/src/i18n/locales/de/chat.json index f28448ce54..88f6a46dbb 100644 --- a/webview-ui/src/i18n/locales/de/chat.json +++ b/webview-ui/src/i18n/locales/de/chat.json @@ -105,6 +105,7 @@ "enhancePrompt": "Prompt mit zusätzlichem Kontext verbessern", "addImages": "Bilder zur Nachricht hinzufügen", "sendMessage": "Nachricht senden", + "stopTts": "Text-in-Sprache beenden", "typeMessage": "Nachricht eingeben...", "typeTask": "Gib deine Aufgabe hier ein...", "addContext": "@ für Kontext, / zum Moduswechsel", diff --git a/webview-ui/src/i18n/locales/en/chat.json b/webview-ui/src/i18n/locales/en/chat.json index 0c49fbaac7..6117ad431e 100644 --- a/webview-ui/src/i18n/locales/en/chat.json +++ b/webview-ui/src/i18n/locales/en/chat.json @@ -120,6 +120,7 @@ "enhancePromptDescription": "The 'Enhance Prompt' button helps improve your prompt by providing additional context, clarification, or rephrasing. Try typing a prompt in here and clicking the button again to see how it works.", "addImages": "Add images to message", "sendMessage": "Send message", + "stopTts": "Stop text-to-speech", "typeMessage": "Type a message...", "typeTask": "Type your task here...", "addContext": "@ to add context, / to switch modes", diff --git a/webview-ui/src/i18n/locales/es/chat.json b/webview-ui/src/i18n/locales/es/chat.json index 71a26f6c10..086fd92f9d 100644 --- a/webview-ui/src/i18n/locales/es/chat.json +++ b/webview-ui/src/i18n/locales/es/chat.json @@ -105,6 +105,7 @@ "enhancePrompt": "Mejorar el mensaje con contexto adicional", "addImages": "Agregar imágenes al mensaje", "sendMessage": "Enviar mensaje", + "stopTts": "Detener texto a voz", "typeMessage": "Escribe un mensaje...", "typeTask": "Escribe tu tarea aquí...", "addContext": "@ para agregar contexto, / para cambiar modos", diff --git a/webview-ui/src/i18n/locales/fr/chat.json b/webview-ui/src/i18n/locales/fr/chat.json index 19531f2554..8e917d1f21 100644 --- a/webview-ui/src/i18n/locales/fr/chat.json +++ b/webview-ui/src/i18n/locales/fr/chat.json @@ -105,6 +105,7 @@ "enhancePrompt": "Améliorer la requête avec un contexte supplémentaire", "addImages": "Ajouter des images au message", "sendMessage": "Envoyer le message", + "stopTts": "Arrêter la synthèse vocale", "typeMessage": "Écrivez un message...", "typeTask": "Écrivez votre tâche ici...", "addContext": "@ pour ajouter du contexte, / pour changer de mode", diff --git a/webview-ui/src/i18n/locales/hi/chat.json b/webview-ui/src/i18n/locales/hi/chat.json index a45317d2da..385549f987 100644 --- a/webview-ui/src/i18n/locales/hi/chat.json +++ b/webview-ui/src/i18n/locales/hi/chat.json @@ -105,6 +105,7 @@ "enhancePrompt": "अतिरिक्त संदर्भ के साथ प्रॉम्प्ट बढ़ाएँ", "addImages": "संदेश में चित्र जोड़ें", "sendMessage": "संदेश भेजें", + "stopTts": "टेक्स्ट-टू-स्पीच बंद करें", "typeMessage": "एक संदेश लिखें...", "typeTask": "अपना कार्य यहां लिखें...", "addContext": "संदर्भ जोड़ने के लिए @, मोड बदलने के लिए /", diff --git a/webview-ui/src/i18n/locales/id/chat.json b/webview-ui/src/i18n/locales/id/chat.json index 7df325b7f9..74722db3ba 100644 --- a/webview-ui/src/i18n/locales/id/chat.json +++ b/webview-ui/src/i18n/locales/id/chat.json @@ -126,6 +126,7 @@ }, "addImages": "Tambahkan gambar ke pesan", "sendMessage": "Kirim pesan", + "stopTts": "Hentikan text-to-speech", "typeMessage": "Ketik pesan...", "typeTask": "Bangun, cari, tanya sesuatu", "addContext": "@ untuk menambah konteks, / untuk ganti mode", diff --git a/webview-ui/src/i18n/locales/it/chat.json b/webview-ui/src/i18n/locales/it/chat.json index 4b368f214f..be4dde8aeb 100644 --- a/webview-ui/src/i18n/locales/it/chat.json +++ b/webview-ui/src/i18n/locales/it/chat.json @@ -105,6 +105,7 @@ "enhancePrompt": "Migliora prompt con contesto aggiuntivo", "addImages": "Aggiungi immagini al messaggio", "sendMessage": "Invia messaggio", + "stopTts": "Interrompi sintesi vocale", "typeMessage": "Scrivi un messaggio...", "typeTask": "Scrivi la tua attività qui...", "addContext": "@ per aggiungere contesto, / per cambiare modalità", diff --git a/webview-ui/src/i18n/locales/ja/chat.json b/webview-ui/src/i18n/locales/ja/chat.json index 5a19b64945..c2466cc046 100644 --- a/webview-ui/src/i18n/locales/ja/chat.json +++ b/webview-ui/src/i18n/locales/ja/chat.json @@ -105,6 +105,7 @@ "enhancePrompt": "追加コンテキストでプロンプトを強化", "addImages": "メッセージに画像を追加", "sendMessage": "メッセージを送信", + "stopTts": "テキスト読み上げを停止", "typeMessage": "メッセージを入力...", "typeTask": "ここにタスクを入力...", "addContext": "コンテキスト追加は@、モード切替は/", diff --git a/webview-ui/src/i18n/locales/ko/chat.json b/webview-ui/src/i18n/locales/ko/chat.json index f3ed7699ca..4649806225 100644 --- a/webview-ui/src/i18n/locales/ko/chat.json +++ b/webview-ui/src/i18n/locales/ko/chat.json @@ -105,6 +105,7 @@ "enhancePrompt": "추가 컨텍스트로 프롬프트 향상", "addImages": "메시지에 이미지 추가", "sendMessage": "메시지 보내기", + "stopTts": "텍스트 음성 변환 중지", "typeMessage": "메시지 입력...", "typeTask": "여기에 작업 입력...", "addContext": "컨텍스트 추가는 @, 모드 전환은 /", diff --git a/webview-ui/src/i18n/locales/nl/chat.json b/webview-ui/src/i18n/locales/nl/chat.json index 874fb74bc7..c9df4af471 100644 --- a/webview-ui/src/i18n/locales/nl/chat.json +++ b/webview-ui/src/i18n/locales/nl/chat.json @@ -112,6 +112,7 @@ }, "addImages": "Afbeeldingen toevoegen aan bericht", "sendMessage": "Bericht verzenden", + "stopTts": "Stop tekst-naar-spraak", "typeMessage": "Typ een bericht...", "typeTask": "Typ hier je taak...", "addContext": "@ om context toe te voegen, / om van modus te wisselen", diff --git a/webview-ui/src/i18n/locales/pl/chat.json b/webview-ui/src/i18n/locales/pl/chat.json index 254f18c9ba..1cf631935c 100644 --- a/webview-ui/src/i18n/locales/pl/chat.json +++ b/webview-ui/src/i18n/locales/pl/chat.json @@ -105,6 +105,7 @@ "enhancePrompt": "Ulepsz podpowiedź dodatkowym kontekstem", "addImages": "Dodaj obrazy do wiadomości", "sendMessage": "Wyślij wiadomość", + "stopTts": "Zatrzymaj syntezę mowy", "typeMessage": "Wpisz wiadomość...", "typeTask": "Wpisz swoje zadanie tutaj...", "addContext": "@ aby dodać kontekst, / aby zmienić tryb", diff --git a/webview-ui/src/i18n/locales/pt-BR/chat.json b/webview-ui/src/i18n/locales/pt-BR/chat.json index bebfe0c722..acc828a139 100644 --- a/webview-ui/src/i18n/locales/pt-BR/chat.json +++ b/webview-ui/src/i18n/locales/pt-BR/chat.json @@ -105,6 +105,7 @@ "enhancePrompt": "Aprimorar prompt com contexto adicional", "addImages": "Adicionar imagens à mensagem", "sendMessage": "Enviar mensagem", + "stopTts": "Parar conversão de texto em fala", "typeMessage": "Digite uma mensagem...", "typeTask": "Digite sua tarefa aqui...", "addContext": "@ para adicionar contexto, / para alternar modos", diff --git a/webview-ui/src/i18n/locales/ru/chat.json b/webview-ui/src/i18n/locales/ru/chat.json index 4ac8f4854a..94172ef9f0 100644 --- a/webview-ui/src/i18n/locales/ru/chat.json +++ b/webview-ui/src/i18n/locales/ru/chat.json @@ -112,6 +112,7 @@ }, "addImages": "Добавить изображения к сообщению", "sendMessage": "Отправить сообщение", + "stopTts": "Остановить синтез речи", "typeMessage": "Введите сообщение...", "typeTask": "Введите вашу задачу здесь...", "addContext": "@ для добавления контекста, / для смены режима", diff --git a/webview-ui/src/i18n/locales/tr/chat.json b/webview-ui/src/i18n/locales/tr/chat.json index 56a9f54cb7..3776eaca92 100644 --- a/webview-ui/src/i18n/locales/tr/chat.json +++ b/webview-ui/src/i18n/locales/tr/chat.json @@ -105,6 +105,7 @@ "enhancePrompt": "Ek bağlamla istemi geliştir", "addImages": "Mesaja resim ekle", "sendMessage": "Mesaj gönder", + "stopTts": "Metin okumayı durdur", "typeMessage": "Bir mesaj yazın...", "typeTask": "Görevinizi buraya yazın...", "addContext": "Bağlam eklemek için @, mod değiştirmek için /", diff --git a/webview-ui/src/i18n/locales/vi/chat.json b/webview-ui/src/i18n/locales/vi/chat.json index 574df08da1..0e069124af 100644 --- a/webview-ui/src/i18n/locales/vi/chat.json +++ b/webview-ui/src/i18n/locales/vi/chat.json @@ -105,6 +105,7 @@ "enhancePrompt": "Nâng cao yêu cầu với ngữ cảnh bổ sung", "addImages": "Thêm hình ảnh vào tin nhắn", "sendMessage": "Gửi tin nhắn", + "stopTts": "Dừng chuyển văn bản thành giọng nói", "typeMessage": "Nhập tin nhắn...", "typeTask": "Nhập nhiệm vụ của bạn tại đây...", "addContext": "@ để thêm ngữ cảnh, / để chuyển chế độ", diff --git a/webview-ui/src/i18n/locales/zh-CN/chat.json b/webview-ui/src/i18n/locales/zh-CN/chat.json index f6e51cfa49..e8dbbb97dd 100644 --- a/webview-ui/src/i18n/locales/zh-CN/chat.json +++ b/webview-ui/src/i18n/locales/zh-CN/chat.json @@ -105,6 +105,7 @@ "enhancePrompt": "增强提示词", "addImages": "添加图片到消息", "sendMessage": "发送消息", + "stopTts": "停止文本转语音", "typeMessage": "输入消息...", "typeTask": "在此处输入您的任务...", "addContext": "@添加上下文,/切换模式", diff --git a/webview-ui/src/i18n/locales/zh-TW/chat.json b/webview-ui/src/i18n/locales/zh-TW/chat.json index 54aeacf15c..323238eb25 100644 --- a/webview-ui/src/i18n/locales/zh-TW/chat.json +++ b/webview-ui/src/i18n/locales/zh-TW/chat.json @@ -105,6 +105,7 @@ "enhancePrompt": "使用額外內容增強提示", "addImages": "新增圖片到訊息中", "sendMessage": "傳送訊息", + "stopTts": "停止文字轉語音", "typeMessage": "輸入訊息...", "typeTask": "在此處輸入您的工作...", "addContext": "輸入 @ 新增內容,輸入 / 切換模式", From ffb998bb11acd56cebe3ece0141de1caa561aba7 Mon Sep 17 00:00:00 2001 From: Chris Estreich Date: Wed, 9 Jul 2025 14:41:24 -0700 Subject: [PATCH 09/27] v3.23.4 (#5529) --- .changeset/neat-ends-stick.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/neat-ends-stick.md diff --git a/.changeset/neat-ends-stick.md b/.changeset/neat-ends-stick.md new file mode 100644 index 0000000000..487d4537ea --- /dev/null +++ b/.changeset/neat-ends-stick.md @@ -0,0 +1,5 @@ +--- +"roo-cline": patch +--- + +v3.23.4 From 5f0860798e1fc62fe6268dd10179412059a3efd0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 9 Jul 2025 15:01:52 -0700 Subject: [PATCH 10/27] Changeset version bump (#5531) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Chris Estreich --- .changeset/neat-ends-stick.md | 5 ----- CHANGELOG.md | 8 ++++++++ src/package.json | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) delete mode 100644 .changeset/neat-ends-stick.md diff --git a/.changeset/neat-ends-stick.md b/.changeset/neat-ends-stick.md deleted file mode 100644 index 487d4537ea..0000000000 --- a/.changeset/neat-ends-stick.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"roo-cline": patch ---- - -v3.23.4 diff --git a/CHANGELOG.md b/CHANGELOG.md index e94c518a66..06dd526e25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Roo Code Changelog +## [3.23.4] - 2025-07-09 + +- Update chat area icons for better discoverability & consistency +- Fix a bug that allowed `list_files` to return directory results that should be excluded by .gitignore +- Add an overflow header menu to make the UI a little tidier (thanks @dlab-anton) +- Fix a bug the issue where null custom modes configuration files cause a 'Cannot read properties of null' error (thanks @daniel-lxs!) +- Replace native title attributes with StandardTooltip component for consistency (thanks @daniel-lxs!) + ## [3.23.3] - 2025-07-09 - Remove erroneous line from announcement modal diff --git a/src/package.json b/src/package.json index 79c5a54caa..77a4d5b19c 100644 --- a/src/package.json +++ b/src/package.json @@ -3,7 +3,7 @@ "displayName": "%extension.displayName%", "description": "%extension.description%", "publisher": "RooVeterinaryInc", - "version": "3.23.3", + "version": "3.23.4", "icon": "assets/icons/icon.png", "galleryBanner": { "color": "#617A91", From 7a8848de3be3b0e8efa11e0119e121c8f32f6a18 Mon Sep 17 00:00:00 2001 From: Vivek Soni Date: Thu, 10 Jul 2025 04:38:35 +0530 Subject: [PATCH 11/27] fix: use decodeURIComponent in openFile (#5504) * fix: use decodeURIComponent in openFile * feat: add error handling for decodeURIComponent and tests - Added try-catch block around decodeURIComponent to handle invalid escape sequences - Falls back to original path if decoding fails - Added comprehensive unit tests for the openFile function - Tests cover invalid URI encoding, valid encoding, and various edge cases * fix: update test to handle dynamic workspace paths in CI * fix: handle Windows path separators in open-file tests --------- Co-authored-by: Vivek Soni Co-authored-by: Daniel Riccio --- .../misc/__tests__/open-file.spec.ts | 240 ++++++++++++++++++ src/integrations/misc/open-file.ts | 12 +- 2 files changed, 251 insertions(+), 1 deletion(-) create mode 100644 src/integrations/misc/__tests__/open-file.spec.ts diff --git a/src/integrations/misc/__tests__/open-file.spec.ts b/src/integrations/misc/__tests__/open-file.spec.ts new file mode 100644 index 0000000000..e8f9be259d --- /dev/null +++ b/src/integrations/misc/__tests__/open-file.spec.ts @@ -0,0 +1,240 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest" +import * as vscode from "vscode" +import * as path from "path" +import * as os from "os" +import { openFile } from "../open-file" + +// Mock vscode module +vi.mock("vscode", () => ({ + Uri: { + file: vi.fn((path: string) => ({ fsPath: path })), + }, + workspace: { + fs: { + stat: vi.fn(), + writeFile: vi.fn(), + }, + openTextDocument: vi.fn(), + }, + window: { + showTextDocument: vi.fn(), + showErrorMessage: vi.fn(), + tabGroups: { + all: [], + }, + activeTextEditor: undefined, + }, + commands: { + executeCommand: vi.fn(), + }, + FileType: { + Directory: 2, + File: 1, + }, + Selection: vi.fn((startLine: number, startChar: number, endLine: number, endChar: number) => ({ + start: { line: startLine, character: startChar }, + end: { line: endLine, character: endChar }, + })), + TabInputText: vi.fn(), +})) + +// Mock utils +vi.mock("../../utils/path", () => { + const nodePath = require("path") + return { + arePathsEqual: vi.fn((a: string, b: string) => a === b), + getWorkspacePath: vi.fn(() => { + // In tests, we need to return a consistent workspace path + // The actual workspace is /Users/roocode/rc2 in local, but varies in CI + const cwd = process.cwd() + // If we're in the src directory, go up one level to get workspace root + if (cwd.endsWith("/src")) { + return nodePath.dirname(cwd) + } + return cwd + }), + } +}) + +// Mock i18n +vi.mock("../../i18n", () => ({ + t: vi.fn((key: string, params?: any) => { + // Return the key without namespace prefix to match actual behavior + if (key.startsWith("common:")) { + return key.replace("common:", "") + } + return key + }), +})) + +describe("openFile", () => { + beforeEach(() => { + vi.clearAllMocks() + vi.spyOn(console, "warn").mockImplementation(() => {}) + }) + + afterEach(() => { + vi.restoreAllMocks() + }) + + describe("decodeURIComponent error handling", () => { + it("should handle invalid URI encoding gracefully", async () => { + const invalidPath = "test%ZZinvalid.txt" // Invalid percent encoding + const mockDocument = { uri: { fsPath: invalidPath } } + + vi.mocked(vscode.workspace.fs.stat).mockResolvedValue({ + type: vscode.FileType.File, + ctime: 0, + mtime: 0, + size: 0, + }) + vi.mocked(vscode.workspace.openTextDocument).mockResolvedValue(mockDocument as any) + vi.mocked(vscode.window.showTextDocument).mockResolvedValue({} as any) + + await openFile(invalidPath) + + // Should log a warning about decode failure + expect(console.warn).toHaveBeenCalledWith( + "Failed to decode file path: URIError: URI malformed. Using original path.", + ) + + // Should still attempt to open the file with the original path + expect(vscode.workspace.openTextDocument).toHaveBeenCalled() + expect(vscode.window.showErrorMessage).not.toHaveBeenCalled() + }) + + it("should successfully decode valid URI-encoded paths", async () => { + const encodedPath = "./%5Btest%5D/file.txt" // [test] encoded + const decodedPath = "./[test]/file.txt" + const mockDocument = { uri: { fsPath: decodedPath } } + + vi.mocked(vscode.workspace.fs.stat).mockResolvedValue({ + type: vscode.FileType.File, + ctime: 0, + mtime: 0, + size: 0, + }) + vi.mocked(vscode.workspace.openTextDocument).mockResolvedValue(mockDocument as any) + vi.mocked(vscode.window.showTextDocument).mockResolvedValue({} as any) + + await openFile(encodedPath) + + // Should not log any warnings + expect(console.warn).not.toHaveBeenCalled() + + // Should use the decoded path - verify it contains the decoded brackets + // On Windows, the path will include backslashes instead of forward slashes + const expectedPathSegment = process.platform === "win32" ? "[test]\\file.txt" : "[test]/file.txt" + expect(vscode.Uri.file).toHaveBeenCalledWith(expect.stringContaining(expectedPathSegment)) + expect(vscode.workspace.openTextDocument).toHaveBeenCalled() + expect(vscode.window.showErrorMessage).not.toHaveBeenCalled() + }) + + it("should handle paths with special characters that need encoding", async () => { + const pathWithSpecialChars = "./[brackets]/file with spaces.txt" + const mockDocument = { uri: { fsPath: pathWithSpecialChars } } + + vi.mocked(vscode.workspace.fs.stat).mockResolvedValue({ + type: vscode.FileType.File, + ctime: 0, + mtime: 0, + size: 0, + }) + vi.mocked(vscode.workspace.openTextDocument).mockResolvedValue(mockDocument as any) + vi.mocked(vscode.window.showTextDocument).mockResolvedValue({} as any) + + await openFile(pathWithSpecialChars) + + // Should work without errors + expect(console.warn).not.toHaveBeenCalled() + expect(vscode.workspace.openTextDocument).toHaveBeenCalled() + expect(vscode.window.showErrorMessage).not.toHaveBeenCalled() + }) + + it("should handle already decoded paths without double-decoding", async () => { + const normalPath = "./normal/file.txt" + const mockDocument = { uri: { fsPath: normalPath } } + + vi.mocked(vscode.workspace.fs.stat).mockResolvedValue({ + type: vscode.FileType.File, + ctime: 0, + mtime: 0, + size: 0, + }) + vi.mocked(vscode.workspace.openTextDocument).mockResolvedValue(mockDocument as any) + vi.mocked(vscode.window.showTextDocument).mockResolvedValue({} as any) + + await openFile(normalPath) + + // Should work without errors + expect(console.warn).not.toHaveBeenCalled() + expect(vscode.workspace.openTextDocument).toHaveBeenCalled() + expect(vscode.window.showErrorMessage).not.toHaveBeenCalled() + }) + }) + + describe("error handling", () => { + it("should show error message when file does not exist", async () => { + const nonExistentPath = "./does/not/exist.txt" + + vi.mocked(vscode.workspace.fs.stat).mockRejectedValue(new Error("File not found")) + + await openFile(nonExistentPath) + + expect(vscode.window.showErrorMessage).toHaveBeenCalledWith("errors.could_not_open_file") + }) + + it("should handle generic errors", async () => { + const testPath = "./test.txt" + + vi.mocked(vscode.workspace.fs.stat).mockRejectedValue("Not an Error object") + + await openFile(testPath) + + expect(vscode.window.showErrorMessage).toHaveBeenCalledWith("errors.could_not_open_file") + }) + }) + + describe("directory handling", () => { + it("should reveal directories in explorer", async () => { + const dirPath = "./components" + + vi.mocked(vscode.workspace.fs.stat).mockResolvedValue({ + type: vscode.FileType.Directory, + ctime: 0, + mtime: 0, + size: 0, + }) + + await openFile(dirPath) + + expect(vscode.commands.executeCommand).toHaveBeenCalledWith( + "revealInExplorer", + expect.objectContaining({ fsPath: expect.stringContaining("components") }), + ) + expect(vscode.commands.executeCommand).toHaveBeenCalledWith("list.expand") + expect(vscode.workspace.openTextDocument).not.toHaveBeenCalled() + }) + }) + + describe("file creation", () => { + it("should create new files when create option is true", async () => { + const newFilePath = "./new/file.txt" + const content = "Hello, world!" + + vi.mocked(vscode.workspace.fs.stat).mockRejectedValue(new Error("File not found")) + vi.mocked(vscode.workspace.openTextDocument).mockResolvedValue({} as any) + vi.mocked(vscode.window.showTextDocument).mockResolvedValue({} as any) + + await openFile(newFilePath, { create: true, content }) + + // On Windows, the path will include backslashes instead of forward slashes + const expectedPathSegment = process.platform === "win32" ? "new\\file.txt" : "new/file.txt" + expect(vscode.workspace.fs.writeFile).toHaveBeenCalledWith( + expect.objectContaining({ fsPath: expect.stringContaining(expectedPathSegment) }), + Buffer.from(content, "utf8"), + ) + expect(vscode.workspace.openTextDocument).toHaveBeenCalled() + }) + }) +}) diff --git a/src/integrations/misc/open-file.ts b/src/integrations/misc/open-file.ts index f05c10dc96..a9ab44f7e5 100644 --- a/src/integrations/misc/open-file.ts +++ b/src/integrations/misc/open-file.ts @@ -12,9 +12,19 @@ interface OpenFileOptions { export async function openFile(filePath: string, options: OpenFileOptions = {}) { try { + // Store the original path for error messages before any modifications + const originalFilePathForError = filePath + + // Try to decode the URI component, but if it fails, use the original path + try { + filePath = decodeURIComponent(filePath) + } catch (decodeError) { + // If decoding fails (e.g., invalid escape sequences), continue with the original path + console.warn(`Failed to decode file path: ${decodeError}. Using original path.`) + } + const workspaceRoot = getWorkspacePath() const homeDir = os.homedir() - const originalFilePathForError = filePath // Keep original for error messages const attemptPaths: string[] = [] From be1eaa51eb4651b6a824afb8290cb7a06769b478 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Wed, 9 Jul 2025 19:21:47 -0400 Subject: [PATCH 12/27] Make account tab visible (#5534) * Make account tab visible * Fix order --- src/package.json | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/package.json b/src/package.json index 77a4d5b19c..2da9d7d554 100644 --- a/src/package.json +++ b/src/package.json @@ -228,6 +228,11 @@ "group": "navigation@2", "when": "view == roo-cline.SidebarProvider" }, + { + "command": "roo-cline.accountButtonClicked", + "group": "navigation@3", + "when": "view == roo-cline.SidebarProvider" + }, { "command": "roo-cline.historyButtonClicked", "group": "overflow@1", @@ -248,14 +253,9 @@ "group": "overflow@4", "when": "view == roo-cline.SidebarProvider" }, - { - "command": "roo-cline.accountButtonClicked", - "group": "overflow@5", - "when": "view == roo-cline.SidebarProvider" - }, { "command": "roo-cline.popoutButtonClicked", - "group": "overflow@6", + "group": "overflow@5", "when": "view == roo-cline.SidebarProvider" } ], @@ -270,6 +270,11 @@ "group": "navigation@2", "when": "activeWebviewPanelId == roo-cline.TabPanelProvider" }, + { + "command": "roo-cline.accountButtonClicked", + "group": "navigation@3", + "when": "activeWebviewPanelId == roo-cline.TabPanelProvider" + }, { "command": "roo-cline.historyButtonClicked", "group": "overflow@1", @@ -290,14 +295,9 @@ "group": "overflow@4", "when": "activeWebviewPanelId == roo-cline.TabPanelProvider" }, - { - "command": "roo-cline.accountButtonClicked", - "group": "overflow@5", - "when": "activeWebviewPanelId == roo-cline.TabPanelProvider" - }, { "command": "roo-cline.popoutButtonClicked", - "group": "overflow@6", + "group": "overflow@5", "when": "activeWebviewPanelId == roo-cline.TabPanelProvider" } ] From 45f3d88ddf3bd99dbf13c8c14584b3610388bdb1 Mon Sep 17 00:00:00 2001 From: Daniel <57051444+daniel-lxs@users.noreply.github.com> Date: Wed, 9 Jul 2025 18:22:05 -0500 Subject: [PATCH 13/27] fix(embeddings): Translate error messages before sending to UI (#5535) fix(embeddings): translate error messages before sending to UI - Import t() function from i18n module - Wrap error messages with t() translation function in _initializeEmbedder() - Ensures proper localization of error messages in the UI - Falls back to original message if no translation exists --- src/services/code-index/manager.ts | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/services/code-index/manager.ts b/src/services/code-index/manager.ts index 7002283226..bd782da84c 100644 --- a/src/services/code-index/manager.ts +++ b/src/services/code-index/manager.ts @@ -12,6 +12,7 @@ import { CacheManager } from "./cache-manager" import fs from "fs/promises" import ignore from "ignore" import path from "path" +import { t } from "../../i18n" export class CodeIndexManager { // --- Singleton Implementation --- @@ -261,12 +262,16 @@ export class CodeIndexManager { // Validate embedder configuration before proceeding const validationResult = await this._serviceFactory.validateEmbedder(embedder) if (!validationResult.valid) { - // Set error state with clear message - this._stateManager.setSystemState( - "Error", - validationResult.error || "Embedder configuration validation failed", - ) - throw new Error(validationResult.error || "Invalid embedder configuration") + const errorMessage = validationResult.error || "Embedder configuration validation failed" + // Always attempt translation, use original as fallback + let translatedMessage = t(errorMessage) + // If translation returns a different value (stripped namespace), use original + if (translatedMessage !== errorMessage && !translatedMessage.includes(":")) { + translatedMessage = errorMessage + } + + this._stateManager.setSystemState("Error", translatedMessage) + throw new Error(translatedMessage) } // (Re)Initialize orchestrator From e64fb150b524a068e47a34d29c7418ae028d0f13 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Wed, 9 Jul 2025 19:31:50 -0400 Subject: [PATCH 14/27] chore: add changeset for v3.23.5 patch release (#5536) --- .changeset/v3.23.5.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/v3.23.5.md diff --git a/.changeset/v3.23.5.md b/.changeset/v3.23.5.md new file mode 100644 index 0000000000..fcb48941a7 --- /dev/null +++ b/.changeset/v3.23.5.md @@ -0,0 +1,7 @@ +--- +"roo-cline": patch +--- + +- Fix: use decodeURIComponent in openFile (thanks @vivekfyi!) +- Fix(embeddings): Translate error messages before sending to UI (thanks @daniel-lxs!) +- Make account tab visible From 6e473f5449880080e57173707a64ccedd4988e4e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 9 Jul 2025 19:39:48 -0400 Subject: [PATCH 15/27] Changeset version bump (#5537) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Matt Rubens --- .changeset/v3.23.5.md | 7 ------- CHANGELOG.md | 6 ++++++ src/package.json | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) delete mode 100644 .changeset/v3.23.5.md diff --git a/.changeset/v3.23.5.md b/.changeset/v3.23.5.md deleted file mode 100644 index fcb48941a7..0000000000 --- a/.changeset/v3.23.5.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"roo-cline": patch ---- - -- Fix: use decodeURIComponent in openFile (thanks @vivekfyi!) -- Fix(embeddings): Translate error messages before sending to UI (thanks @daniel-lxs!) -- Make account tab visible diff --git a/CHANGELOG.md b/CHANGELOG.md index 06dd526e25..8250c4466a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Roo Code Changelog +## [3.23.5] - 2025-07-09 + +- Fix: use decodeURIComponent in openFile (thanks @vivekfyi!) +- Fix(embeddings): Translate error messages before sending to UI (thanks @daniel-lxs!) +- Make account tab visible + ## [3.23.4] - 2025-07-09 - Update chat area icons for better discoverability & consistency diff --git a/src/package.json b/src/package.json index 2da9d7d554..1fac66f4e1 100644 --- a/src/package.json +++ b/src/package.json @@ -3,7 +3,7 @@ "displayName": "%extension.displayName%", "description": "%extension.description%", "publisher": "RooVeterinaryInc", - "version": "3.23.4", + "version": "3.23.5", "icon": "assets/icons/icon.png", "galleryBanner": { "color": "#617A91", From 08c9420350369cf478f3303a8a5e80462bb75f0f Mon Sep 17 00:00:00 2001 From: Will Li Date: Wed, 9 Jul 2025 17:57:10 -0700 Subject: [PATCH 16/27] fix (#5540) --- src/activate/registerCommands.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/activate/registerCommands.ts b/src/activate/registerCommands.ts index 8e84981d8a..bd925b0e90 100644 --- a/src/activate/registerCommands.ts +++ b/src/activate/registerCommands.ts @@ -14,6 +14,7 @@ import { registerHumanRelayCallback, unregisterHumanRelayCallback, handleHumanRe import { handleNewTask } from "./handleTask" import { CodeIndexManager } from "../services/code-index/manager" import { importSettingsWithFeedback } from "../core/config/importExport" +import { MdmService } from "../services/mdm/MdmService" import { t } from "../i18n" /** @@ -226,7 +227,17 @@ export const openClineInNewTab = async ({ context, outputChannel }: Omit editor.viewColumn || 0)) // Check if there are any visible text editors, otherwise open a new group From cdde7b3aa82655d251b6a28de592e3673b05a8f7 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Thu, 10 Jul 2025 07:07:16 -0400 Subject: [PATCH 17/27] Grok 4 (#5559) --- .changeset/red-symbols-grab.md | 5 ++++ packages/types/src/providers/xai.ts | 43 ++++++++++++++++++----------- 2 files changed, 32 insertions(+), 16 deletions(-) create mode 100644 .changeset/red-symbols-grab.md diff --git a/.changeset/red-symbols-grab.md b/.changeset/red-symbols-grab.md new file mode 100644 index 0000000000..30e4e4ef27 --- /dev/null +++ b/.changeset/red-symbols-grab.md @@ -0,0 +1,5 @@ +--- +"roo-cline": patch +--- + +Grok 4 diff --git a/packages/types/src/providers/xai.ts b/packages/types/src/providers/xai.ts index 4f2cedd14b..12dc8a145c 100644 --- a/packages/types/src/providers/xai.ts +++ b/packages/types/src/providers/xai.ts @@ -3,26 +3,19 @@ import type { ModelInfo } from "../model.js" // https://docs.x.ai/docs/api-reference export type XAIModelId = keyof typeof xaiModels -export const xaiDefaultModelId: XAIModelId = "grok-3" +export const xaiDefaultModelId: XAIModelId = "grok-4" export const xaiModels = { - "grok-2-1212": { + "grok-4": { maxTokens: 8192, - contextWindow: 131072, - supportsImages: false, - supportsPromptCache: false, - inputPrice: 2.0, - outputPrice: 10.0, - description: "xAI's Grok-2 model (version 1212) with 128K context window", - }, - "grok-2-vision-1212": { - maxTokens: 8192, - contextWindow: 32768, + contextWindow: 256000, supportsImages: true, - supportsPromptCache: false, - inputPrice: 2.0, - outputPrice: 10.0, - description: "xAI's Grok-2 Vision model (version 1212) with image support and 32K context window", + supportsPromptCache: true, + inputPrice: 3.0, + outputPrice: 15.0, + cacheWritesPrice: 0.75, + cacheReadsPrice: 0.75, + description: "xAI's Grok-4 model with 256K context window", }, "grok-3": { maxTokens: 8192, @@ -70,4 +63,22 @@ export const xaiModels = { description: "xAI's Grok-3 mini fast model with 128K context window", supportsReasoningEffort: true, }, + "grok-2-1212": { + maxTokens: 8192, + contextWindow: 131072, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 2.0, + outputPrice: 10.0, + description: "xAI's Grok-2 model (version 1212) with 128K context window", + }, + "grok-2-vision-1212": { + maxTokens: 8192, + contextWindow: 32768, + supportsImages: true, + supportsPromptCache: false, + inputPrice: 2.0, + outputPrice: 10.0, + description: "xAI's Grok-2 Vision model (version 1212) with image support and 32K context window", + }, } as const satisfies Record From 5cfd98dc3350a002e323f5634c619d068985fe54 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 10 Jul 2025 07:10:05 -0400 Subject: [PATCH 18/27] Changeset version bump (#5560) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Matt Rubens --- .changeset/red-symbols-grab.md | 5 ----- CHANGELOG.md | 4 ++++ src/package.json | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) delete mode 100644 .changeset/red-symbols-grab.md diff --git a/.changeset/red-symbols-grab.md b/.changeset/red-symbols-grab.md deleted file mode 100644 index 30e4e4ef27..0000000000 --- a/.changeset/red-symbols-grab.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"roo-cline": patch ---- - -Grok 4 diff --git a/CHANGELOG.md b/CHANGELOG.md index 8250c4466a..ca9b46d596 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Roo Code Changelog +## [3.23.6] - 2025-07-10 + +- Grok 4 + ## [3.23.5] - 2025-07-09 - Fix: use decodeURIComponent in openFile (thanks @vivekfyi!) diff --git a/src/package.json b/src/package.json index 1fac66f4e1..14f6607742 100644 --- a/src/package.json +++ b/src/package.json @@ -3,7 +3,7 @@ "displayName": "%extension.displayName%", "description": "%extension.description%", "publisher": "RooVeterinaryInc", - "version": "3.23.5", + "version": "3.23.6", "icon": "assets/icons/icon.png", "galleryBanner": { "color": "#617A91", From 4771864a8c05eb7b146a04ea9ad474a8091ab253 Mon Sep 17 00:00:00 2001 From: shubhamgupta731 Date: Thu, 10 Jul 2025 18:57:28 +0530 Subject: [PATCH 19/27] Expand Vertex AI region config to include all available regions in GCP Vertex AI (#5557) Co-authored-by: Shubham Gupta Co-authored-by: Daniel Riccio --- packages/types/src/providers/vertex.ts | 28 ++++++++- .../providers/__tests__/Vertex.spec.tsx | 61 +++++++++++++++++++ 2 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 webview-ui/src/components/settings/providers/__tests__/Vertex.spec.tsx diff --git a/packages/types/src/providers/vertex.ts b/packages/types/src/providers/vertex.ts index b048c19403..c405621f82 100644 --- a/packages/types/src/providers/vertex.ts +++ b/packages/types/src/providers/vertex.ts @@ -277,9 +277,35 @@ export const vertexModels = { export const VERTEX_REGIONS = [ { value: "global", label: "global" }, - { value: "us-east5", label: "us-east5" }, { value: "us-central1", label: "us-central1" }, + { value: "us-east1", label: "us-east1" }, + { value: "us-east4", label: "us-east4" }, + { value: "us-east5", label: "us-east5" }, + { value: "us-west1", label: "us-west1" }, + { value: "us-west2", label: "us-west2" }, + { value: "us-west3", label: "us-west3" }, + { value: "us-west4", label: "us-west4" }, + { value: "northamerica-northeast1", label: "northamerica-northeast1" }, + { value: "northamerica-northeast2", label: "northamerica-northeast2" }, + { value: "southamerica-east1", label: "southamerica-east1" }, { value: "europe-west1", label: "europe-west1" }, + { value: "europe-west2", label: "europe-west2" }, + { value: "europe-west3", label: "europe-west3" }, { value: "europe-west4", label: "europe-west4" }, + { value: "europe-west6", label: "europe-west6" }, + { value: "europe-central2", label: "europe-central2" }, + { value: "asia-east1", label: "asia-east1" }, + { value: "asia-east2", label: "asia-east2" }, + { value: "asia-northeast1", label: "asia-northeast1" }, + { value: "asia-northeast2", label: "asia-northeast2" }, + { value: "asia-northeast3", label: "asia-northeast3" }, + { value: "asia-south1", label: "asia-south1" }, + { value: "asia-south2", label: "asia-south2" }, { value: "asia-southeast1", label: "asia-southeast1" }, + { value: "asia-southeast2", label: "asia-southeast2" }, + { value: "australia-southeast1", label: "australia-southeast1" }, + { value: "australia-southeast2", label: "australia-southeast2" }, + { value: "me-west1", label: "me-west1" }, + { value: "me-central1", label: "me-central1" }, + { value: "africa-south1", label: "africa-south1" }, ] diff --git a/webview-ui/src/components/settings/providers/__tests__/Vertex.spec.tsx b/webview-ui/src/components/settings/providers/__tests__/Vertex.spec.tsx new file mode 100644 index 0000000000..4ef949f9e0 --- /dev/null +++ b/webview-ui/src/components/settings/providers/__tests__/Vertex.spec.tsx @@ -0,0 +1,61 @@ +// Tests for VERTEX_REGIONS "global" region handling + +import { describe, it, expect } from "vitest" +import { VERTEX_REGIONS } from "../../../../../../packages/types/src/providers/vertex" + +describe("VERTEX_REGIONS", () => { + it('should include the "global" region as the first entry', () => { + expect(VERTEX_REGIONS[0]).toEqual({ value: "global", label: "global" }) + }) + + it('should contain "global" region exactly once', () => { + const globalRegions = VERTEX_REGIONS.filter((r: { value: string; label: string }) => r.value === "global") + expect(globalRegions).toHaveLength(1) + }) + + it('should contain all expected regions including "global"', () => { + // The expected list is the imported VERTEX_REGIONS itself + expect(VERTEX_REGIONS).toEqual([ + { value: "global", label: "global" }, + { value: "us-central1", label: "us-central1" }, + { value: "us-east1", label: "us-east1" }, + { value: "us-east4", label: "us-east4" }, + { value: "us-east5", label: "us-east5" }, + { value: "us-west1", label: "us-west1" }, + { value: "us-west2", label: "us-west2" }, + { value: "us-west3", label: "us-west3" }, + { value: "us-west4", label: "us-west4" }, + { value: "northamerica-northeast1", label: "northamerica-northeast1" }, + { value: "northamerica-northeast2", label: "northamerica-northeast2" }, + { value: "southamerica-east1", label: "southamerica-east1" }, + { value: "europe-west1", label: "europe-west1" }, + { value: "europe-west2", label: "europe-west2" }, + { value: "europe-west3", label: "europe-west3" }, + { value: "europe-west4", label: "europe-west4" }, + { value: "europe-west6", label: "europe-west6" }, + { value: "europe-central2", label: "europe-central2" }, + { value: "asia-east1", label: "asia-east1" }, + { value: "asia-east2", label: "asia-east2" }, + { value: "asia-northeast1", label: "asia-northeast1" }, + { value: "asia-northeast2", label: "asia-northeast2" }, + { value: "asia-northeast3", label: "asia-northeast3" }, + { value: "asia-south1", label: "asia-south1" }, + { value: "asia-south2", label: "asia-south2" }, + { value: "asia-southeast1", label: "asia-southeast1" }, + { value: "asia-southeast2", label: "asia-southeast2" }, + { value: "australia-southeast1", label: "australia-southeast1" }, + { value: "australia-southeast2", label: "australia-southeast2" }, + { value: "me-west1", label: "me-west1" }, + { value: "me-central1", label: "me-central1" }, + { value: "africa-south1", label: "africa-south1" }, + ]) + }) + + it('should contain "asia-east1" region exactly once', () => { + const asiaEast1Regions = VERTEX_REGIONS.filter( + (r: { value: string; label: string }) => r.value === "asia-east1" && r.label === "asia-east1", + ) + expect(asiaEast1Regions).toHaveLength(1) + expect(asiaEast1Regions[0]).toEqual({ value: "asia-east1", label: "asia-east1" }) + }) +}) From a7b5cbd722d5cc908c4356af9ffe4535b0bd7806 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Thu, 10 Jul 2025 09:47:47 -0400 Subject: [PATCH 20/27] Soften the language for incompatible profiles (#5561) --- src/i18n/locales/ca/common.json | 2 +- src/i18n/locales/de/common.json | 2 +- src/i18n/locales/en/common.json | 2 +- src/i18n/locales/es/common.json | 2 +- src/i18n/locales/fr/common.json | 2 +- src/i18n/locales/hi/common.json | 2 +- src/i18n/locales/id/common.json | 2 +- src/i18n/locales/it/common.json | 2 +- src/i18n/locales/ja/common.json | 2 +- src/i18n/locales/ko/common.json | 2 +- src/i18n/locales/nl/common.json | 2 +- src/i18n/locales/pl/common.json | 2 +- src/i18n/locales/pt-BR/common.json | 2 +- src/i18n/locales/ru/common.json | 2 +- src/i18n/locales/tr/common.json | 2 +- src/i18n/locales/vi/common.json | 2 +- src/i18n/locales/zh-CN/common.json | 2 +- src/i18n/locales/zh-TW/common.json | 2 +- webview-ui/src/i18n/locales/ca/chat.json | 2 +- webview-ui/src/i18n/locales/de/chat.json | 2 +- webview-ui/src/i18n/locales/en/chat.json | 2 +- webview-ui/src/i18n/locales/es/chat.json | 2 +- webview-ui/src/i18n/locales/fr/chat.json | 2 +- webview-ui/src/i18n/locales/hi/chat.json | 2 +- webview-ui/src/i18n/locales/id/chat.json | 2 +- webview-ui/src/i18n/locales/it/chat.json | 2 +- webview-ui/src/i18n/locales/ja/chat.json | 2 +- webview-ui/src/i18n/locales/ko/chat.json | 2 +- webview-ui/src/i18n/locales/nl/chat.json | 2 +- webview-ui/src/i18n/locales/pl/chat.json | 2 +- webview-ui/src/i18n/locales/pt-BR/chat.json | 2 +- webview-ui/src/i18n/locales/ru/chat.json | 2 +- webview-ui/src/i18n/locales/tr/chat.json | 2 +- webview-ui/src/i18n/locales/vi/chat.json | 2 +- webview-ui/src/i18n/locales/zh-CN/chat.json | 2 +- webview-ui/src/i18n/locales/zh-TW/chat.json | 2 +- 36 files changed, 36 insertions(+), 36 deletions(-) diff --git a/src/i18n/locales/ca/common.json b/src/i18n/locales/ca/common.json index 04938e3625..0caa3fca47 100644 --- a/src/i18n/locales/ca/common.json +++ b/src/i18n/locales/ca/common.json @@ -58,7 +58,7 @@ "cannot_access_path": "No es pot accedir a la ruta {{path}}: {{error}}", "settings_import_failed": "Ha fallat la importació de la configuració: {{error}}.", "mistake_limit_guidance": "Això pot indicar un error en el procés de pensament del model o la incapacitat d'utilitzar una eina correctament, que es pot mitigar amb orientació de l'usuari (p. ex. \"Prova de dividir la tasca en passos més petits\").", - "violated_organization_allowlist": "Ha fallat l'execució de la tasca: el perfil actual infringeix la configuració de la teva organització", + "violated_organization_allowlist": "Ha fallat l'execució de la tasca: el perfil actual no és compatible amb la configuració de la teva organització", "condense_failed": "Ha fallat la condensació del context", "condense_not_enough_messages": "No hi ha prou missatges per condensar el context", "condensed_recently": "El context s'ha condensat recentment; s'omet aquest intent", diff --git a/src/i18n/locales/de/common.json b/src/i18n/locales/de/common.json index 9fa27c89c6..bf9af547ca 100644 --- a/src/i18n/locales/de/common.json +++ b/src/i18n/locales/de/common.json @@ -54,7 +54,7 @@ "cannot_access_path": "Zugriff auf Pfad {{path}} nicht möglich: {{error}}", "settings_import_failed": "Fehler beim Importieren der Einstellungen: {{error}}.", "mistake_limit_guidance": "Dies kann auf einen Fehler im Denkprozess des Modells oder die Unfähigkeit hinweisen, ein Tool richtig zu verwenden, was durch Benutzerführung behoben werden kann (z.B. \"Versuche, die Aufgabe in kleinere Schritte zu unterteilen\").", - "violated_organization_allowlist": "Aufgabe konnte nicht ausgeführt werden: Das aktuelle Profil verstößt gegen die Einstellungen deiner Organisation", + "violated_organization_allowlist": "Aufgabe konnte nicht ausgeführt werden: Das aktuelle Profil ist nicht kompatibel mit den Einstellungen deiner Organisation", "condense_failed": "Fehler beim Verdichten des Kontexts", "condense_not_enough_messages": "Nicht genügend Nachrichten zum Verdichten des Kontexts", "condensed_recently": "Kontext wurde kürzlich verdichtet; dieser Versuch wird übersprungen", diff --git a/src/i18n/locales/en/common.json b/src/i18n/locales/en/common.json index f907a5745b..3004038d42 100644 --- a/src/i18n/locales/en/common.json +++ b/src/i18n/locales/en/common.json @@ -54,7 +54,7 @@ "cannot_access_path": "Cannot access path {{path}}: {{error}}", "settings_import_failed": "Settings import failed: {{error}}.", "mistake_limit_guidance": "This may indicate a failure in the model's thought process or inability to use a tool properly, which can be mitigated with some user guidance (e.g. \"Try breaking down the task into smaller steps\").", - "violated_organization_allowlist": "Failed to run task: the current profile violates your organization settings", + "violated_organization_allowlist": "Failed to run task: the current profile isn't compatible with your organization settings", "condense_failed": "Failed to condense context", "condense_not_enough_messages": "Not enough messages to condense context", "condensed_recently": "Context was condensed recently; skipping this attempt", diff --git a/src/i18n/locales/es/common.json b/src/i18n/locales/es/common.json index 68750e888f..1a16dbf1ae 100644 --- a/src/i18n/locales/es/common.json +++ b/src/i18n/locales/es/common.json @@ -54,7 +54,7 @@ "cannot_access_path": "No se puede acceder a la ruta {{path}}: {{error}}", "settings_import_failed": "Error al importar la configuración: {{error}}.", "mistake_limit_guidance": "Esto puede indicar un fallo en el proceso de pensamiento del modelo o la incapacidad de usar una herramienta correctamente, lo cual puede mitigarse con orientación del usuario (ej. \"Intenta dividir la tarea en pasos más pequeños\").", - "violated_organization_allowlist": "Error al ejecutar la tarea: el perfil actual infringe la configuración de tu organización", + "violated_organization_allowlist": "Error al ejecutar la tarea: el perfil actual no es compatible con la configuración de tu organización", "condense_failed": "Error al condensar el contexto", "condense_not_enough_messages": "No hay suficientes mensajes para condensar el contexto", "condensed_recently": "El contexto se condensó recientemente; se omite este intento", diff --git a/src/i18n/locales/fr/common.json b/src/i18n/locales/fr/common.json index e985fe9468..98945f305d 100644 --- a/src/i18n/locales/fr/common.json +++ b/src/i18n/locales/fr/common.json @@ -54,7 +54,7 @@ "cannot_access_path": "Impossible d'accéder au chemin {{path}} : {{error}}", "settings_import_failed": "Échec de l'importation des paramètres : {{error}}", "mistake_limit_guidance": "Cela peut indiquer un échec dans le processus de réflexion du modèle ou une incapacité à utiliser un outil correctement, ce qui peut être atténué avec des conseils de l'utilisateur (par ex. \"Essaie de diviser la tâche en étapes plus petites\").", - "violated_organization_allowlist": "Échec de l'exécution de la tâche : le profil actuel enfreint les paramètres de votre organisation", + "violated_organization_allowlist": "Échec de l'exécution de la tâche : le profil actuel n'est pas compatible avec les paramètres de votre organisation", "condense_failed": "Échec de la condensation du contexte", "condense_not_enough_messages": "Pas assez de messages pour condenser le contexte", "condensed_recently": "Le contexte a été condensé récemment ; cette tentative est ignorée", diff --git a/src/i18n/locales/hi/common.json b/src/i18n/locales/hi/common.json index a16a22ece5..7daa0046ec 100644 --- a/src/i18n/locales/hi/common.json +++ b/src/i18n/locales/hi/common.json @@ -54,7 +54,7 @@ "cannot_access_path": "पाथ {{path}} तक पहुंच नहीं पा रहे हैं: {{error}}", "settings_import_failed": "सेटिंग्स इम्पोर्ट करने में विफल: {{error}}।", "mistake_limit_guidance": "यह मॉडल की सोच प्रक्रिया में विफलता या किसी टूल का सही उपयोग न कर पाने का संकेत हो सकता है, जिसे उपयोगकर्ता के मार्गदर्शन से ठीक किया जा सकता है (जैसे \"कार्य को छोटे चरणों में बांटने की कोशिश करें\")।", - "violated_organization_allowlist": "कार्य चलाने में विफल: वर्तमान प्रोफ़ाइल आपके संगठन की सेटिंग्स का उल्लंघन करती है", + "violated_organization_allowlist": "कार्य चलाने में विफल: वर्तमान प्रोफ़ाइल आपके संगठन की सेटिंग्स के साथ संगत नहीं है", "condense_failed": "संदर्भ को संक्षिप्त करने में विफल", "condense_not_enough_messages": "संदर्भ को संक्षिप्त करने के लिए पर्याप्त संदेश नहीं हैं", "condensed_recently": "संदर्भ हाल ही में संक्षिप्त किया गया था; इस प्रयास को छोड़ा जा रहा है", diff --git a/src/i18n/locales/id/common.json b/src/i18n/locales/id/common.json index d40bce9186..c021dab4cd 100644 --- a/src/i18n/locales/id/common.json +++ b/src/i18n/locales/id/common.json @@ -54,7 +54,7 @@ "cannot_access_path": "Tidak dapat mengakses path {{path}}: {{error}}", "settings_import_failed": "Impor pengaturan gagal: {{error}}.", "mistake_limit_guidance": "Ini mungkin menunjukkan kegagalan dalam proses pemikiran model atau ketidakmampuan untuk menggunakan tool dengan benar, yang dapat diatasi dengan beberapa panduan pengguna (misalnya \"Coba bagi tugas menjadi langkah-langkah yang lebih kecil\").", - "violated_organization_allowlist": "Gagal menjalankan tugas: profil saat ini melanggar pengaturan organisasi kamu", + "violated_organization_allowlist": "Gagal menjalankan tugas: profil saat ini tidak kompatibel dengan pengaturan organisasi kamu", "condense_failed": "Gagal mengompres konteks", "condense_not_enough_messages": "Tidak cukup pesan untuk mengompres konteks", "condensed_recently": "Konteks baru saja dikompres; melewati percobaan ini", diff --git a/src/i18n/locales/it/common.json b/src/i18n/locales/it/common.json index a6782267ce..ff45cd8f1e 100644 --- a/src/i18n/locales/it/common.json +++ b/src/i18n/locales/it/common.json @@ -54,7 +54,7 @@ "cannot_access_path": "Impossibile accedere al percorso {{path}}: {{error}}", "settings_import_failed": "Importazione delle impostazioni fallita: {{error}}.", "mistake_limit_guidance": "Questo può indicare un fallimento nel processo di pensiero del modello o l'incapacità di utilizzare correttamente uno strumento, che può essere mitigato con la guida dell'utente (ad es. \"Prova a suddividere l'attività in passaggi più piccoli\").", - "violated_organization_allowlist": "Impossibile eseguire l'attività: il profilo corrente viola le impostazioni della tua organizzazione", + "violated_organization_allowlist": "Impossibile eseguire l'attività: il profilo corrente non è compatibile con le impostazioni della tua organizzazione", "condense_failed": "Impossibile condensare il contesto", "condense_not_enough_messages": "Non ci sono abbastanza messaggi per condensare il contesto", "condensed_recently": "Il contesto è stato condensato di recente; questo tentativo viene saltato", diff --git a/src/i18n/locales/ja/common.json b/src/i18n/locales/ja/common.json index 9722281d0c..4d9b88d114 100644 --- a/src/i18n/locales/ja/common.json +++ b/src/i18n/locales/ja/common.json @@ -54,7 +54,7 @@ "cannot_access_path": "パス {{path}} にアクセスできません:{{error}}", "settings_import_failed": "設定のインポートに失敗しました:{{error}}", "mistake_limit_guidance": "これは、モデルの思考プロセスの失敗やツールを適切に使用できないことを示している可能性があり、ユーザーのガイダンスによって軽減できます(例:「タスクをより小さなステップに分割してみてください」)。", - "violated_organization_allowlist": "タスクの実行に失敗しました: 現在のプロファイルは組織の設定に違反しています", + "violated_organization_allowlist": "タスクの実行に失敗しました: 現在のプロファイルは組織の設定と互換性がありません", "condense_failed": "コンテキストの圧縮に失敗しました", "condense_not_enough_messages": "コンテキストを圧縮するのに十分なメッセージがありません", "condensed_recently": "コンテキストは最近圧縮されました;この試行をスキップします", diff --git a/src/i18n/locales/ko/common.json b/src/i18n/locales/ko/common.json index 81c80154d0..34d9bced71 100644 --- a/src/i18n/locales/ko/common.json +++ b/src/i18n/locales/ko/common.json @@ -54,7 +54,7 @@ "cannot_access_path": "경로 {{path}}에 접근할 수 없습니다: {{error}}", "settings_import_failed": "설정 가져오기 실패: {{error}}.", "mistake_limit_guidance": "이는 모델의 사고 과정 실패나 도구를 제대로 사용하지 못하는 것을 나타낼 수 있으며, 사용자 가이드를 통해 완화할 수 있습니다 (예: \"작업을 더 작은 단계로 나누어 시도해보세요\").", - "violated_organization_allowlist": "작업 실행 실패: 현재 프로필이 조직 설정을 위반합니다", + "violated_organization_allowlist": "작업 실행 실패: 현재 프로필이 조직 설정과 호환되지 않습니다", "condense_failed": "컨텍스트 압축에 실패했습니다", "condense_not_enough_messages": "컨텍스트를 압축할 메시지가 충분하지 않습니다", "condensed_recently": "컨텍스트가 최근 압축되었습니다; 이 시도를 건너뜁니다", diff --git a/src/i18n/locales/nl/common.json b/src/i18n/locales/nl/common.json index 68ad31e728..dff1bb83f7 100644 --- a/src/i18n/locales/nl/common.json +++ b/src/i18n/locales/nl/common.json @@ -54,7 +54,7 @@ "cannot_access_path": "Kan pad {{path}} niet openen: {{error}}", "settings_import_failed": "Importeren van instellingen mislukt: {{error}}.", "mistake_limit_guidance": "Dit kan duiden op een fout in het denkproces van het model of het onvermogen om een tool correct te gebruiken, wat kan worden verminderd met gebruikersbegeleiding (bijv. \"Probeer de taak op te delen in kleinere stappen\").", - "violated_organization_allowlist": "Taak uitvoeren mislukt: het huidige profiel schendt de instellingen van uw organisatie", + "violated_organization_allowlist": "Taak uitvoeren mislukt: het huidige profiel is niet compatibel met de instellingen van uw organisatie", "condense_failed": "Comprimeren van context mislukt", "condense_not_enough_messages": "Niet genoeg berichten om context te comprimeren", "condensed_recently": "Context is recent gecomprimeerd; deze poging wordt overgeslagen", diff --git a/src/i18n/locales/pl/common.json b/src/i18n/locales/pl/common.json index 7dc510e71c..33c58b4752 100644 --- a/src/i18n/locales/pl/common.json +++ b/src/i18n/locales/pl/common.json @@ -54,7 +54,7 @@ "cannot_access_path": "Nie można uzyskać dostępu do ścieżki {{path}}: {{error}}", "settings_import_failed": "Nie udało się zaimportować ustawień: {{error}}.", "mistake_limit_guidance": "To może wskazywać na błąd w procesie myślowym modelu lub niezdolność do prawidłowego użycia narzędzia, co można złagodzić poprzez wskazówki użytkownika (np. \"Spróbuj podzielić zadanie na mniejsze kroki\").", - "violated_organization_allowlist": "Nie udało się uruchomić zadania: bieżący profil narusza ustawienia Twojej organizacji", + "violated_organization_allowlist": "Nie udało się uruchomić zadania: bieżący profil nie jest kompatybilny z ustawieniami Twojej organizacji", "condense_failed": "Nie udało się skondensować kontekstu", "condense_not_enough_messages": "Za mało wiadomości do skondensowania kontekstu", "condensed_recently": "Kontekst został niedawno skondensowany; pomijanie tej próby", diff --git a/src/i18n/locales/pt-BR/common.json b/src/i18n/locales/pt-BR/common.json index 7ee3b8a658..ce9dc113e2 100644 --- a/src/i18n/locales/pt-BR/common.json +++ b/src/i18n/locales/pt-BR/common.json @@ -58,7 +58,7 @@ "cannot_access_path": "Não é possível acessar o caminho {{path}}: {{error}}", "settings_import_failed": "Falha ao importar configurações: {{error}}", "mistake_limit_guidance": "Isso pode indicar uma falha no processo de pensamento do modelo ou incapacidade de usar uma ferramenta adequadamente, o que pode ser mitigado com orientação do usuário (ex. \"Tente dividir a tarefa em etapas menores\").", - "violated_organization_allowlist": "Falha ao executar a tarefa: o perfil atual viola as configurações da sua organização", + "violated_organization_allowlist": "Falha ao executar a tarefa: o perfil atual não é compatível com as configurações da sua organização", "condense_failed": "Falha ao condensar o contexto", "condense_not_enough_messages": "Não há mensagens suficientes para condensar o contexto", "condensed_recently": "O contexto foi condensado recentemente; pulando esta tentativa", diff --git a/src/i18n/locales/ru/common.json b/src/i18n/locales/ru/common.json index 8e100bff9a..a3b6c322b2 100644 --- a/src/i18n/locales/ru/common.json +++ b/src/i18n/locales/ru/common.json @@ -54,7 +54,7 @@ "cannot_access_path": "Невозможно получить доступ к пути {{path}}: {{error}}", "settings_import_failed": "Не удалось импортировать настройки: {{error}}.", "mistake_limit_guidance": "Это может указывать на сбой в процессе мышления модели или неспособность правильно использовать инструмент, что можно смягчить с помощью руководства пользователя (например, \"Попробуйте разбить задачу на более мелкие шаги\").", - "violated_organization_allowlist": "Не удалось выполнить задачу: текущий профиль нарушает настройки вашей организации", + "violated_organization_allowlist": "Не удалось выполнить задачу: текущий профиль несовместим с настройками вашей организации", "condense_failed": "Не удалось сжать контекст", "condense_not_enough_messages": "Недостаточно сообщений для сжатия контекста", "condensed_recently": "Контекст был недавно сжат; пропускаем эту попытку", diff --git a/src/i18n/locales/tr/common.json b/src/i18n/locales/tr/common.json index df8212cf3c..042fa88d15 100644 --- a/src/i18n/locales/tr/common.json +++ b/src/i18n/locales/tr/common.json @@ -54,7 +54,7 @@ "cannot_access_path": "{{path}} yoluna erişilemiyor: {{error}}", "settings_import_failed": "Ayarlar içe aktarılamadı: {{error}}.", "mistake_limit_guidance": "Bu, modelin düşünce sürecindeki bir başarısızlığı veya bir aracı düzgün kullanamama durumunu gösterebilir, bu da kullanıcı rehberliği ile hafifletilebilir (örn. \"Görevi daha küçük adımlara bölmeyi deneyin\").", - "violated_organization_allowlist": "Görev yürütülemedi: Geçerli profil kuruluşunuzun ayarlarını ihlal ediyor", + "violated_organization_allowlist": "Görev yürütülemedi: Geçerli profil kuruluşunuzun ayarlarıyla uyumlu değil", "condense_failed": "Bağlam sıkıştırılamadı", "condense_not_enough_messages": "Bağlamı sıkıştırmak için yeterli mesaj yok", "condensed_recently": "Bağlam yakın zamanda sıkıştırıldı; bu deneme atlanıyor", diff --git a/src/i18n/locales/vi/common.json b/src/i18n/locales/vi/common.json index 6710cd136d..183ae7b41a 100644 --- a/src/i18n/locales/vi/common.json +++ b/src/i18n/locales/vi/common.json @@ -54,7 +54,7 @@ "cannot_access_path": "Không thể truy cập đường dẫn {{path}}: {{error}}", "settings_import_failed": "Nhập cài đặt thất bại: {{error}}.", "mistake_limit_guidance": "Điều này có thể cho thấy sự thất bại trong quá trình suy nghĩ của mô hình hoặc không thể sử dụng công cụ đúng cách, có thể được giảm thiểu bằng hướng dẫn của người dùng (ví dụ: \"Hãy thử chia nhỏ nhiệm vụ thành các bước nhỏ hơn\").", - "violated_organization_allowlist": "Không thể chạy tác vụ: hồ sơ hiện tại vi phạm cài đặt của tổ chức của bạn", + "violated_organization_allowlist": "Không thể chạy tác vụ: hồ sơ hiện tại không tương thích với cài đặt của tổ chức của bạn", "condense_failed": "Không thể nén ngữ cảnh", "condense_not_enough_messages": "Không đủ tin nhắn để nén ngữ cảnh", "condensed_recently": "Ngữ cảnh đã được nén gần đây; bỏ qua lần thử này", diff --git a/src/i18n/locales/zh-CN/common.json b/src/i18n/locales/zh-CN/common.json index 5a8fc51ef1..a45efa4b1c 100644 --- a/src/i18n/locales/zh-CN/common.json +++ b/src/i18n/locales/zh-CN/common.json @@ -59,7 +59,7 @@ "cannot_access_path": "无法访问路径 {{path}}:{{error}}", "settings_import_failed": "设置导入失败:{{error}}。", "mistake_limit_guidance": "这可能表明模型思维过程失败或无法正确使用工具,可通过用户指导来缓解(例如\"尝试将任务分解为更小的步骤\")。", - "violated_organization_allowlist": "执行任务失败:当前配置文件违反了您的组织设置", + "violated_organization_allowlist": "执行任务失败:当前配置文件与您的组织设置不兼容", "condense_failed": "压缩上下文失败", "condense_not_enough_messages": "没有足够的对话来压缩上下文", "condensed_recently": "上下文最近已压缩;跳过此次尝试", diff --git a/src/i18n/locales/zh-TW/common.json b/src/i18n/locales/zh-TW/common.json index 2bfad5e00f..3fbbc050f4 100644 --- a/src/i18n/locales/zh-TW/common.json +++ b/src/i18n/locales/zh-TW/common.json @@ -54,7 +54,7 @@ "cannot_access_path": "無法存取路徑 {{path}}:{{error}}", "settings_import_failed": "設定匯入失敗:{{error}}。", "mistake_limit_guidance": "這可能表明模型思維過程失敗或無法正確使用工具,可透過使用者指導來緩解(例如「嘗試將工作分解為更小的步驟」)。", - "violated_organization_allowlist": "執行工作失敗:目前設定檔違反了您的組織設定", + "violated_organization_allowlist": "執行工作失敗:目前設定檔與您的組織設定不相容", "condense_failed": "壓縮上下文失敗", "condense_not_enough_messages": "沒有足夠的訊息來壓縮上下文", "condensed_recently": "上下文最近已壓縮;跳過此次嘗試", diff --git a/webview-ui/src/i18n/locales/ca/chat.json b/webview-ui/src/i18n/locales/ca/chat.json index 8ad87049e3..6accf68fcb 100644 --- a/webview-ui/src/i18n/locales/ca/chat.json +++ b/webview-ui/src/i18n/locales/ca/chat.json @@ -281,7 +281,7 @@ } }, "systemPromptWarning": "ADVERTÈNCIA: S'ha activat una substitució personalitzada d'instruccions del sistema. Això pot trencar greument la funcionalitat i causar un comportament impredictible.", - "profileViolationWarning": "El perfil actual infringeix la configuració de la teva organització", + "profileViolationWarning": "El perfil actual no és compatible amb la configuració de la teva organització", "shellIntegration": { "title": "Advertència d'execució d'ordres", "description": "La teva ordre s'està executant sense la integració de shell del terminal VSCode. Per suprimir aquest advertiment, pots desactivar la integració de shell a la secció Terminal de la configuració de Roo Code o solucionar problemes d'integració del terminal VSCode utilitzant l'enllaç a continuació.", diff --git a/webview-ui/src/i18n/locales/de/chat.json b/webview-ui/src/i18n/locales/de/chat.json index 88f6a46dbb..fcbbdb2b68 100644 --- a/webview-ui/src/i18n/locales/de/chat.json +++ b/webview-ui/src/i18n/locales/de/chat.json @@ -281,7 +281,7 @@ } }, "systemPromptWarning": "WARNUNG: Benutzerdefinierte Systemaufforderung aktiv. Dies kann die Funktionalität erheblich beeinträchtigen und zu unvorhersehbarem Verhalten führen.", - "profileViolationWarning": "Das aktuelle Profil verstößt gegen die Einstellungen deiner Organisation", + "profileViolationWarning": "Das aktuelle Profil ist nicht kompatibel mit den Einstellungen deiner Organisation", "shellIntegration": { "title": "Befehlsausführungswarnung", "description": "Dein Befehl wird ohne VSCode Terminal-Shell-Integration ausgeführt. Um diese Warnung zu unterdrücken, kannst du die Shell-Integration im Abschnitt Terminal der Roo Code Einstellungen deaktivieren oder die VSCode Terminal-Integration mit dem Link unten beheben.", diff --git a/webview-ui/src/i18n/locales/en/chat.json b/webview-ui/src/i18n/locales/en/chat.json index 6117ad431e..326f52c74d 100644 --- a/webview-ui/src/i18n/locales/en/chat.json +++ b/webview-ui/src/i18n/locales/en/chat.json @@ -294,7 +294,7 @@ } }, "systemPromptWarning": "WARNING: Custom system prompt override active. This can severely break functionality and cause unpredictable behavior.", - "profileViolationWarning": "The current profile violates your organization's settings", + "profileViolationWarning": "The current profile isn't compatible with your organization's settings", "shellIntegration": { "title": "Command Execution Warning", "description": "Your command is being executed without VSCode terminal shell integration. To suppress this warning you can disable shell integration in the Terminal section of the Roo Code settings or troubleshoot VSCode terminal integration using the link below.", diff --git a/webview-ui/src/i18n/locales/es/chat.json b/webview-ui/src/i18n/locales/es/chat.json index 086fd92f9d..9bea6bd2c4 100644 --- a/webview-ui/src/i18n/locales/es/chat.json +++ b/webview-ui/src/i18n/locales/es/chat.json @@ -281,7 +281,7 @@ } }, "systemPromptWarning": "ADVERTENCIA: Anulación de instrucciones del sistema personalizada activa. Esto puede romper gravemente la funcionalidad y causar un comportamiento impredecible.", - "profileViolationWarning": "El perfil actual infringe la configuración de tu organización", + "profileViolationWarning": "El perfil actual no es compatible con la configuración de tu organización", "shellIntegration": { "title": "Advertencia de ejecución de comandos", "description": "Tu comando se está ejecutando sin la integración de shell de terminal de VSCode. Para suprimir esta advertencia, puedes desactivar la integración de shell en la sección Terminal de la configuración de Roo Code o solucionar problemas de integración de terminal de VSCode usando el enlace de abajo.", diff --git a/webview-ui/src/i18n/locales/fr/chat.json b/webview-ui/src/i18n/locales/fr/chat.json index 8e917d1f21..d7748fb15c 100644 --- a/webview-ui/src/i18n/locales/fr/chat.json +++ b/webview-ui/src/i18n/locales/fr/chat.json @@ -281,7 +281,7 @@ } }, "systemPromptWarning": "AVERTISSEMENT : Remplacement d'instructions système personnalisées actif. Cela peut gravement perturber la fonctionnalité et provoquer un comportement imprévisible.", - "profileViolationWarning": "Le profil actuel enfreint les paramètres de votre organisation", + "profileViolationWarning": "Le profil actuel n'est pas compatible avec les paramètres de votre organisation", "shellIntegration": { "title": "Avertissement d'exécution de commande", "description": "Votre commande est exécutée sans l'intégration shell du terminal VSCode. Pour supprimer cet avertissement, vous pouvez désactiver l'intégration shell dans la section Terminal des paramètres de Roo Code ou résoudre les problèmes d'intégration du terminal VSCode en utilisant le lien ci-dessous.", diff --git a/webview-ui/src/i18n/locales/hi/chat.json b/webview-ui/src/i18n/locales/hi/chat.json index 385549f987..7979f624e0 100644 --- a/webview-ui/src/i18n/locales/hi/chat.json +++ b/webview-ui/src/i18n/locales/hi/chat.json @@ -281,7 +281,7 @@ } }, "systemPromptWarning": "चेतावनी: कस्टम सिस्टम प्रॉम्प्ट ओवरराइड सक्रिय है। यह कार्यक्षमता को गंभीर रूप से बाधित कर सकता है और अनियमित व्यवहार का कारण बन सकता है.", - "profileViolationWarning": "वर्तमान प्रोफ़ाइल आपके संगठन की सेटिंग्स का उल्लंघन करती है", + "profileViolationWarning": "वर्तमान प्रोफ़ाइल आपके संगठन की सेटिंग्स के साथ संगत नहीं है", "shellIntegration": { "title": "कमांड निष्पादन चेतावनी", "description": "आपका कमांड VSCode टर्मिनल शेल इंटीग्रेशन के बिना निष्पादित हो रहा है। इस चेतावनी को दबाने के लिए आप Roo Code सेटिंग्स के Terminal अनुभाग में शेल इंटीग्रेशन को अक्षम कर सकते हैं या नीचे दिए गए लिंक का उपयोग करके VSCode टर्मिनल इंटीग्रेशन की समस्या का समाधान कर सकते हैं।", diff --git a/webview-ui/src/i18n/locales/id/chat.json b/webview-ui/src/i18n/locales/id/chat.json index 74722db3ba..2134b48bfc 100644 --- a/webview-ui/src/i18n/locales/id/chat.json +++ b/webview-ui/src/i18n/locales/id/chat.json @@ -301,7 +301,7 @@ } }, "systemPromptWarning": "PERINGATAN: Override system prompt kustom aktif. Ini dapat merusak fungsionalitas secara serius dan menyebabkan perilaku yang tidak terduga.", - "profileViolationWarning": "Profil saat ini melanggar pengaturan organisasi kamu", + "profileViolationWarning": "Profil saat ini tidak kompatibel dengan pengaturan organisasi kamu", "shellIntegration": { "title": "Peringatan Eksekusi Perintah", "description": "Perintah kamu dijalankan tanpa integrasi shell terminal VSCode. Untuk menekan peringatan ini kamu bisa menonaktifkan integrasi shell di bagian Terminal dari pengaturan Roo Code atau troubleshoot integrasi terminal VSCode menggunakan link di bawah.", diff --git a/webview-ui/src/i18n/locales/it/chat.json b/webview-ui/src/i18n/locales/it/chat.json index be4dde8aeb..ed886e6ef9 100644 --- a/webview-ui/src/i18n/locales/it/chat.json +++ b/webview-ui/src/i18n/locales/it/chat.json @@ -281,7 +281,7 @@ } }, "systemPromptWarning": "ATTENZIONE: Sovrascrittura personalizzata delle istruzioni di sistema attiva. Questo può compromettere gravemente le funzionalità e causare comportamenti imprevedibili.", - "profileViolationWarning": "Il profilo corrente viola le impostazioni della tua organizzazione", + "profileViolationWarning": "Il profilo corrente non è compatibile con le impostazioni della tua organizzazione", "shellIntegration": { "title": "Avviso di esecuzione comando", "description": "Il tuo comando viene eseguito senza l'integrazione shell del terminale VSCode. Per sopprimere questo avviso puoi disattivare l'integrazione shell nella sezione Terminal delle impostazioni di Roo Code o risolvere i problemi di integrazione del terminale VSCode utilizzando il link qui sotto.", diff --git a/webview-ui/src/i18n/locales/ja/chat.json b/webview-ui/src/i18n/locales/ja/chat.json index c2466cc046..61c2820299 100644 --- a/webview-ui/src/i18n/locales/ja/chat.json +++ b/webview-ui/src/i18n/locales/ja/chat.json @@ -281,7 +281,7 @@ } }, "systemPromptWarning": "警告:カスタムシステムプロンプトの上書きが有効です。これにより機能が深刻に損なわれ、予測不可能な動作が発生する可能性があります。", - "profileViolationWarning": "現在のプロファイルは組織の設定に違反しています", + "profileViolationWarning": "現在のプロファイルは組織の設定と互換性がありません", "shellIntegration": { "title": "コマンド実行警告", "description": "コマンドはVSCodeターミナルシェル統合なしで実行されています。この警告を非表示にするには、Roo Code設定Terminalセクションでシェル統合を無効にするか、以下のリンクを使用してVSCodeターミナル統合のトラブルシューティングを行ってください。", diff --git a/webview-ui/src/i18n/locales/ko/chat.json b/webview-ui/src/i18n/locales/ko/chat.json index 4649806225..9ac8e90644 100644 --- a/webview-ui/src/i18n/locales/ko/chat.json +++ b/webview-ui/src/i18n/locales/ko/chat.json @@ -281,7 +281,7 @@ } }, "systemPromptWarning": "경고: 사용자 정의 시스템 프롬프트 재정의가 활성화되었습니다. 이로 인해 기능이 심각하게 손상되고 예측할 수 없는 동작이 발생할 수 있습니다.", - "profileViolationWarning": "현재 프로필이 조직 설정을 위반합니다", + "profileViolationWarning": "현재 프로필이 조직 설정과 호환되지 않습니다", "shellIntegration": { "title": "명령 실행 경고", "description": "명령이 VSCode 터미널 쉘 통합 없이 실행되고 있습니다. 이 경고를 숨기려면 Roo Code 설정Terminal 섹션에서 쉘 통합을 비활성화하거나 아래 링크를 사용하여 VSCode 터미널 통합 문제를 해결하세요.", diff --git a/webview-ui/src/i18n/locales/nl/chat.json b/webview-ui/src/i18n/locales/nl/chat.json index c9df4af471..5b2da04160 100644 --- a/webview-ui/src/i18n/locales/nl/chat.json +++ b/webview-ui/src/i18n/locales/nl/chat.json @@ -281,7 +281,7 @@ } }, "systemPromptWarning": "WAARSCHUWING: Aangepaste systeemprompt actief. Dit kan de functionaliteit ernstig verstoren en onvoorspelbaar gedrag veroorzaken.", - "profileViolationWarning": "Het huidige profiel schendt de instellingen van uw organisatie", + "profileViolationWarning": "Het huidige profiel is niet compatibel met de instellingen van uw organisatie", "shellIntegration": { "title": "Waarschuwing commando-uitvoering", "description": "Je commando wordt uitgevoerd zonder VSCode-terminal shell-integratie. Om deze waarschuwing te onderdrukken kun je shell-integratie uitschakelen in het gedeelte Terminal van de Roo Code-instellingen of de VSCode-terminalintegratie oplossen via de onderstaande link.", diff --git a/webview-ui/src/i18n/locales/pl/chat.json b/webview-ui/src/i18n/locales/pl/chat.json index 1cf631935c..5226ef51ea 100644 --- a/webview-ui/src/i18n/locales/pl/chat.json +++ b/webview-ui/src/i18n/locales/pl/chat.json @@ -281,7 +281,7 @@ } }, "systemPromptWarning": "OSTRZEŻENIE: Aktywne niestandardowe zastąpienie instrukcji systemowych. Może to poważnie zakłócić funkcjonalność i powodować nieprzewidywalne zachowanie.", - "profileViolationWarning": "Bieżący profil narusza ustawienia Twojej organizacji", + "profileViolationWarning": "Bieżący profil nie jest kompatybilny z ustawieniami Twojej organizacji", "shellIntegration": { "title": "Ostrzeżenie wykonania polecenia", "description": "Twoje polecenie jest wykonywane bez integracji powłoki terminala VSCode. Aby ukryć to ostrzeżenie, możesz wyłączyć integrację powłoki w sekcji Terminal w ustawieniach Roo Code lub rozwiązać problemy z integracją terminala VSCode korzystając z poniższego linku.", diff --git a/webview-ui/src/i18n/locales/pt-BR/chat.json b/webview-ui/src/i18n/locales/pt-BR/chat.json index acc828a139..eca50fbd33 100644 --- a/webview-ui/src/i18n/locales/pt-BR/chat.json +++ b/webview-ui/src/i18n/locales/pt-BR/chat.json @@ -281,7 +281,7 @@ } }, "systemPromptWarning": "AVISO: Substituição personalizada de instrução do sistema ativa. Isso pode comprometer gravemente a funcionalidade e causar comportamento imprevisível.", - "profileViolationWarning": "O perfil atual viola as configurações da sua organização", + "profileViolationWarning": "O perfil atual não é compatível com as configurações da sua organização", "shellIntegration": { "title": "Aviso de execução de comando", "description": "Seu comando está sendo executado sem a integração de shell do terminal VSCode. Para suprimir este aviso, você pode desativar a integração de shell na seção Terminal das configurações do Roo Code ou solucionar problemas de integração do terminal VSCode usando o link abaixo.", diff --git a/webview-ui/src/i18n/locales/ru/chat.json b/webview-ui/src/i18n/locales/ru/chat.json index 94172ef9f0..72ae883703 100644 --- a/webview-ui/src/i18n/locales/ru/chat.json +++ b/webview-ui/src/i18n/locales/ru/chat.json @@ -281,7 +281,7 @@ } }, "systemPromptWarning": "ПРЕДУПРЕЖДЕНИЕ: Активна пользовательская системная подсказка. Это может серьезно нарушить работу и вызвать непредсказуемое поведение.", - "profileViolationWarning": "Текущий профиль нарушает настройки вашей организации", + "profileViolationWarning": "Текущий профиль несовместим с настройками вашей организации", "shellIntegration": { "title": "Предупреждение о выполнении команды", "description": "Ваша команда выполняется без интеграции оболочки терминала VSCode. Чтобы скрыть это предупреждение, вы можете отключить интеграцию оболочки в разделе Terminal в настройках Roo Code или устранить проблемы с интеграцией терминала VSCode, используя ссылку ниже.", diff --git a/webview-ui/src/i18n/locales/tr/chat.json b/webview-ui/src/i18n/locales/tr/chat.json index 3776eaca92..4f108d7ac3 100644 --- a/webview-ui/src/i18n/locales/tr/chat.json +++ b/webview-ui/src/i18n/locales/tr/chat.json @@ -281,7 +281,7 @@ } }, "systemPromptWarning": "UYARI: Özel sistem komut geçersiz kılma aktif. Bu işlevselliği ciddi şekilde bozabilir ve öngörülemeyen davranışlara neden olabilir.", - "profileViolationWarning": "Geçerli profil kuruluşunuzun ayarlarını ihlal ediyor", + "profileViolationWarning": "Geçerli profil kuruluşunuzun ayarlarıyla uyumlu değil", "shellIntegration": { "title": "Komut Çalıştırma Uyarısı", "description": "Komutunuz VSCode terminal kabuk entegrasyonu olmadan çalıştırılıyor. Bu uyarıyı gizlemek için Roo Code ayarları'nın Terminal bölümünden kabuk entegrasyonunu devre dışı bırakabilir veya aşağıdaki bağlantıyı kullanarak VSCode terminal entegrasyonu sorunlarını giderebilirsiniz.", diff --git a/webview-ui/src/i18n/locales/vi/chat.json b/webview-ui/src/i18n/locales/vi/chat.json index 0e069124af..0f0ea71ec3 100644 --- a/webview-ui/src/i18n/locales/vi/chat.json +++ b/webview-ui/src/i18n/locales/vi/chat.json @@ -281,7 +281,7 @@ } }, "systemPromptWarning": "CẢNH BÁO: Đã kích hoạt ghi đè lệnh nhắc hệ thống tùy chỉnh. Điều này có thể phá vỡ nghiêm trọng chức năng và gây ra hành vi không thể dự đoán.", - "profileViolationWarning": "Hồ sơ hiện tại vi phạm cài đặt của tổ chức của bạn", + "profileViolationWarning": "Hồ sơ hiện tại không tương thích với cài đặt của tổ chức của bạn", "shellIntegration": { "title": "Cảnh báo thực thi lệnh", "description": "Lệnh của bạn đang được thực thi mà không có tích hợp shell terminal VSCode. Để ẩn cảnh báo này, bạn có thể vô hiệu hóa tích hợp shell trong phần Terminal của cài đặt Roo Code hoặc khắc phục sự cố tích hợp terminal VSCode bằng liên kết bên dưới.", diff --git a/webview-ui/src/i18n/locales/zh-CN/chat.json b/webview-ui/src/i18n/locales/zh-CN/chat.json index e8dbbb97dd..6e883b751a 100644 --- a/webview-ui/src/i18n/locales/zh-CN/chat.json +++ b/webview-ui/src/i18n/locales/zh-CN/chat.json @@ -281,7 +281,7 @@ } }, "systemPromptWarning": "警告:自定义系统提示词覆盖已激活。这可能严重破坏功能并导致不可预测的行为。", - "profileViolationWarning": "当前配置文件违反了您的组织设置", + "profileViolationWarning": "当前配置文件与您的组织设置不兼容", "shellIntegration": { "title": "命令执行警告", "description": "您的命令正在没有 VSCode 终端 shell 集成的情况下执行。要隐藏此警告,您可以在 Roo Code 设置Terminal 部分禁用 shell 集成,或使用下方链接排查 VSCode 终端集成问题。", diff --git a/webview-ui/src/i18n/locales/zh-TW/chat.json b/webview-ui/src/i18n/locales/zh-TW/chat.json index 323238eb25..75d214db03 100644 --- a/webview-ui/src/i18n/locales/zh-TW/chat.json +++ b/webview-ui/src/i18n/locales/zh-TW/chat.json @@ -281,7 +281,7 @@ } }, "systemPromptWarning": "警告:自訂系統提示詞覆蓋已啟用。這可能嚴重破壞功能並導致不可預測的行為。", - "profileViolationWarning": "目前設定檔違反了您的組織設定", + "profileViolationWarning": "目前設定檔與您的組織設定不相容", "shellIntegration": { "title": "命令執行警告", "description": "您的命令正在沒有 VSCode 終端機 shell 整合的情況下執行。要隱藏此警告,您可以在 Roo Code 設定Terminal 部分停用 shell 整合,或使用下方連結排查 VSCode 終端機整合問題。", From 356d421344a92c555b4b38768e8d1891539472d4 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Thu, 10 Jul 2025 10:12:16 -0400 Subject: [PATCH 21/27] Run nightly build on push (#5563) --- .github/workflows/nightly-publish.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/nightly-publish.yml b/.github/workflows/nightly-publish.yml index 93471d0790..8ce4c5ca6a 100644 --- a/.github/workflows/nightly-publish.yml +++ b/.github/workflows/nightly-publish.yml @@ -1,10 +1,7 @@ name: Nightly Publish on: - workflow_run: - workflows: ["Code QA Roo Code"] - types: - - completed + push: branches: [main] workflow_dispatch: # Allows manual triggering. From 97b917eac8b78205bac2a2029f08d35493cb8faf Mon Sep 17 00:00:00 2001 From: Daniel <57051444+daniel-lxs@users.noreply.github.com> Date: Thu, 10 Jul 2025 10:29:25 -0500 Subject: [PATCH 22/27] fix: handle Qdrant vector dimension mismatch when switching embedding models (#5562) (#5565) --- src/i18n/locales/ca/embeddings.json | 3 +- src/i18n/locales/de/embeddings.json | 3 +- src/i18n/locales/en/embeddings.json | 3 +- src/i18n/locales/es/embeddings.json | 3 +- src/i18n/locales/fr/embeddings.json | 3 +- src/i18n/locales/hi/embeddings.json | 3 +- src/i18n/locales/id/embeddings.json | 3 +- src/i18n/locales/it/embeddings.json | 3 +- src/i18n/locales/ja/embeddings.json | 3 +- src/i18n/locales/ko/embeddings.json | 3 +- src/i18n/locales/nl/embeddings.json | 3 +- src/i18n/locales/pl/embeddings.json | 3 +- src/i18n/locales/pt-BR/embeddings.json | 3 +- src/i18n/locales/ru/embeddings.json | 3 +- src/i18n/locales/tr/embeddings.json | 3 +- src/i18n/locales/vi/embeddings.json | 3 +- src/i18n/locales/zh-CN/embeddings.json | 3 +- src/i18n/locales/zh-TW/embeddings.json | 3 +- .../__tests__/qdrant-client.spec.ts | 65 +++++++++++++++++-- .../code-index/vector-store/qdrant-client.ts | 45 +++++++++---- 20 files changed, 129 insertions(+), 35 deletions(-) diff --git a/src/i18n/locales/ca/embeddings.json b/src/i18n/locales/ca/embeddings.json index 1d3c5f2476..709b77ac0a 100644 --- a/src/i18n/locales/ca/embeddings.json +++ b/src/i18n/locales/ca/embeddings.json @@ -23,7 +23,8 @@ "failedToProcessBatchWithError": "No s'ha pogut processar el lot després de {{maxRetries}} intents: {{errorMessage}}" }, "vectorStore": { - "qdrantConnectionFailed": "No s'ha pogut connectar a la base de dades vectorial Qdrant. Assegura't que Qdrant estigui funcionant i sigui accessible a {{qdrantUrl}}. Error: {{errorMessage}}" + "qdrantConnectionFailed": "No s'ha pogut connectar a la base de dades vectorial Qdrant. Assegura't que Qdrant estigui funcionant i sigui accessible a {{qdrantUrl}}. Error: {{errorMessage}}", + "vectorDimensionMismatch": "No s'ha pogut actualitzar l'índex de vectors per al nou model. Prova d'esborrar l'índex i tornar a començar. Detalls: {{errorMessage}}" }, "validation": { "authenticationFailed": "Ha fallat l'autenticació. Comproveu la vostra clau d'API a la configuració.", diff --git a/src/i18n/locales/de/embeddings.json b/src/i18n/locales/de/embeddings.json index f4abcb3e15..c26975b6c6 100644 --- a/src/i18n/locales/de/embeddings.json +++ b/src/i18n/locales/de/embeddings.json @@ -23,7 +23,8 @@ "failedToProcessBatchWithError": "Verarbeitung des Batches nach {{maxRetries}} Versuchen fehlgeschlagen: {{errorMessage}}" }, "vectorStore": { - "qdrantConnectionFailed": "Verbindung zur Qdrant-Vektordatenbank fehlgeschlagen. Stelle sicher, dass Qdrant läuft und unter {{qdrantUrl}} erreichbar ist. Fehler: {{errorMessage}}" + "qdrantConnectionFailed": "Verbindung zur Qdrant-Vektordatenbank fehlgeschlagen. Stelle sicher, dass Qdrant läuft und unter {{qdrantUrl}} erreichbar ist. Fehler: {{errorMessage}}", + "vectorDimensionMismatch": "Aktualisierung des Vektorindex für neues Modell fehlgeschlagen. Bitte versuche, den Index zu löschen und von vorne zu beginnen. Details: {{errorMessage}}" }, "validation": { "authenticationFailed": "Authentifizierung fehlgeschlagen. Bitte überprüfe deinen API-Schlüssel in den Einstellungen.", diff --git a/src/i18n/locales/en/embeddings.json b/src/i18n/locales/en/embeddings.json index b2ed2a64c4..f7ed0232f7 100644 --- a/src/i18n/locales/en/embeddings.json +++ b/src/i18n/locales/en/embeddings.json @@ -23,7 +23,8 @@ "failedToProcessBatchWithError": "Failed to process batch after {{maxRetries}} attempts: {{errorMessage}}" }, "vectorStore": { - "qdrantConnectionFailed": "Failed to connect to Qdrant vector database. Please ensure Qdrant is running and accessible at {{qdrantUrl}}. Error: {{errorMessage}}" + "qdrantConnectionFailed": "Failed to connect to Qdrant vector database. Please ensure Qdrant is running and accessible at {{qdrantUrl}}. Error: {{errorMessage}}", + "vectorDimensionMismatch": "Failed to update vector index for new model. Please try clearing the index and starting again. Details: {{errorMessage}}" }, "validation": { "authenticationFailed": "Authentication failed. Please check your API key in the settings.", diff --git a/src/i18n/locales/es/embeddings.json b/src/i18n/locales/es/embeddings.json index 8cb0dd7f1f..6109693135 100644 --- a/src/i18n/locales/es/embeddings.json +++ b/src/i18n/locales/es/embeddings.json @@ -23,7 +23,8 @@ "failedToProcessBatchWithError": "Error al procesar lote después de {{maxRetries}} intentos: {{errorMessage}}" }, "vectorStore": { - "qdrantConnectionFailed": "Error al conectar con la base de datos vectorial Qdrant. Asegúrate de que Qdrant esté funcionando y sea accesible en {{qdrantUrl}}. Error: {{errorMessage}}" + "qdrantConnectionFailed": "Error al conectar con la base de datos vectorial Qdrant. Asegúrate de que Qdrant esté funcionando y sea accesible en {{qdrantUrl}}. Error: {{errorMessage}}", + "vectorDimensionMismatch": "No se pudo actualizar el índice de vectores para el nuevo modelo. Intenta borrar el índice y empezar de nuevo. Detalles: {{errorMessage}}" }, "validation": { "authenticationFailed": "Error de autenticación. Comprueba tu clave de API en los ajustes.", diff --git a/src/i18n/locales/fr/embeddings.json b/src/i18n/locales/fr/embeddings.json index 23d70650e8..854046de9a 100644 --- a/src/i18n/locales/fr/embeddings.json +++ b/src/i18n/locales/fr/embeddings.json @@ -23,7 +23,8 @@ "failedToProcessBatchWithError": "Échec du traitement du lot après {{maxRetries}} tentatives : {{errorMessage}}" }, "vectorStore": { - "qdrantConnectionFailed": "Échec de la connexion à la base de données vectorielle Qdrant. Veuillez vous assurer que Qdrant fonctionne et est accessible à {{qdrantUrl}}. Erreur : {{errorMessage}}" + "qdrantConnectionFailed": "Échec de la connexion à la base de données vectorielle Qdrant. Veuillez vous assurer que Qdrant fonctionne et est accessible à {{qdrantUrl}}. Erreur : {{errorMessage}}", + "vectorDimensionMismatch": "Échec de la mise à jour de l'index vectoriel pour le nouveau modèle. Veuillez essayer de vider l'index et de recommencer. Détails : {{errorMessage}}" }, "validation": { "authenticationFailed": "Échec de l'authentification. Veuillez vérifier votre clé API dans les paramètres.", diff --git a/src/i18n/locales/hi/embeddings.json b/src/i18n/locales/hi/embeddings.json index e6a0aa2bbc..75d11b4b9c 100644 --- a/src/i18n/locales/hi/embeddings.json +++ b/src/i18n/locales/hi/embeddings.json @@ -23,7 +23,8 @@ "failedToProcessBatchWithError": "{{maxRetries}} प्रयासों के बाद बैच प्रसंस्करण विफल: {{errorMessage}}" }, "vectorStore": { - "qdrantConnectionFailed": "Qdrant वेक्टर डेटाबेस से कनेक्ट करने में विफल। कृपया सुनिश्चित करें कि Qdrant चल रहा है और {{qdrantUrl}} पर पहुंच योग्य है। त्रुटि: {{errorMessage}}" + "qdrantConnectionFailed": "Qdrant वेक्टर डेटाबेस से कनेक्ट करने में विफल। कृपया सुनिश्चित करें कि Qdrant चल रहा है और {{qdrantUrl}} पर पहुंच योग्य है। त्रुटि: {{errorMessage}}", + "vectorDimensionMismatch": "नए मॉडल के लिए वेक्टर इंडेक्स को अपडेट करने में विफल। कृपया इंडेक्स को साफ़ करने और फिर से शुरू करने का प्रयास करें। विवरण: {{errorMessage}}" }, "validation": { "authenticationFailed": "प्रमाणीकरण विफल। कृपया सेटिंग्स में अपनी एपीआई कुंजी जांचें।", diff --git a/src/i18n/locales/id/embeddings.json b/src/i18n/locales/id/embeddings.json index 5ba042e7ef..3c07852fe2 100644 --- a/src/i18n/locales/id/embeddings.json +++ b/src/i18n/locales/id/embeddings.json @@ -23,7 +23,8 @@ "failedToProcessBatchWithError": "Gagal memproses batch setelah {{maxRetries}} percobaan: {{errorMessage}}" }, "vectorStore": { - "qdrantConnectionFailed": "Gagal terhubung ke database vektor Qdrant. Pastikan Qdrant berjalan dan dapat diakses di {{qdrantUrl}}. Error: {{errorMessage}}" + "qdrantConnectionFailed": "Gagal terhubung ke database vektor Qdrant. Pastikan Qdrant berjalan dan dapat diakses di {{qdrantUrl}}. Error: {{errorMessage}}", + "vectorDimensionMismatch": "Gagal memperbarui indeks vektor untuk model baru. Silakan coba bersihkan indeks dan mulai lagi. Detail: {{errorMessage}}" }, "validation": { "authenticationFailed": "Autentikasi gagal. Silakan periksa kunci API Anda di pengaturan.", diff --git a/src/i18n/locales/it/embeddings.json b/src/i18n/locales/it/embeddings.json index 38f502ae78..ae84909911 100644 --- a/src/i18n/locales/it/embeddings.json +++ b/src/i18n/locales/it/embeddings.json @@ -23,7 +23,8 @@ "failedToProcessBatchWithError": "Elaborazione del batch fallita dopo {{maxRetries}} tentativi: {{errorMessage}}" }, "vectorStore": { - "qdrantConnectionFailed": "Impossibile connettersi al database vettoriale Qdrant. Assicurati che Qdrant sia in esecuzione e accessibile su {{qdrantUrl}}. Errore: {{errorMessage}}" + "qdrantConnectionFailed": "Impossibile connettersi al database vettoriale Qdrant. Assicurati che Qdrant sia in esecuzione e accessibile su {{qdrantUrl}}. Errore: {{errorMessage}}", + "vectorDimensionMismatch": "Impossibile aggiornare l'indice vettoriale per il nuovo modello. Prova a cancellare l'indice e a ricominciare. Dettagli: {{errorMessage}}" }, "validation": { "authenticationFailed": "Autenticazione fallita. Controlla la tua chiave API nelle impostazioni.", diff --git a/src/i18n/locales/ja/embeddings.json b/src/i18n/locales/ja/embeddings.json index 817287cff9..042ae6930e 100644 --- a/src/i18n/locales/ja/embeddings.json +++ b/src/i18n/locales/ja/embeddings.json @@ -23,7 +23,8 @@ "failedToProcessBatchWithError": "{{maxRetries}}回の試行後、バッチ処理に失敗しました:{{errorMessage}}" }, "vectorStore": { - "qdrantConnectionFailed": "Qdrantベクターデータベースへの接続に失敗しました。Qdrantが実行中で{{qdrantUrl}}でアクセス可能であることを確認してください。エラー:{{errorMessage}}" + "qdrantConnectionFailed": "Qdrantベクターデータベースへの接続に失敗しました。Qdrantが実行中で{{qdrantUrl}}でアクセス可能であることを確認してください。エラー:{{errorMessage}}", + "vectorDimensionMismatch": "新しいモデルのベクトルインデックスの更新に失敗しました。インデックスをクリアして再試行してください。詳細:{{errorMessage}}" }, "validation": { "authenticationFailed": "認証に失敗しました。設定でAPIキーを確認してください。", diff --git a/src/i18n/locales/ko/embeddings.json b/src/i18n/locales/ko/embeddings.json index 272ac74cc7..da3fa67590 100644 --- a/src/i18n/locales/ko/embeddings.json +++ b/src/i18n/locales/ko/embeddings.json @@ -23,7 +23,8 @@ "failedToProcessBatchWithError": "{{maxRetries}}번 시도 후 배치 처리 실패: {{errorMessage}}" }, "vectorStore": { - "qdrantConnectionFailed": "Qdrant 벡터 데이터베이스에 연결하지 못했습니다. Qdrant가 실행 중이고 {{qdrantUrl}}에서 접근 가능한지 확인하세요. 오류: {{errorMessage}}" + "qdrantConnectionFailed": "Qdrant 벡터 데이터베이스에 연결하지 못했습니다. Qdrant가 실행 중이고 {{qdrantUrl}}에서 접근 가능한지 확인하세요. 오류: {{errorMessage}}", + "vectorDimensionMismatch": "새 모델의 벡터 인덱스를 업데이트하지 못했습니다. 인덱스를 지우고 다시 시작해 보세요. 세부 정보: {{errorMessage}}" }, "validation": { "authenticationFailed": "인증에 실패했습니다. 설정에서 API 키를 확인하세요.", diff --git a/src/i18n/locales/nl/embeddings.json b/src/i18n/locales/nl/embeddings.json index 055d27b607..d7b68b336b 100644 --- a/src/i18n/locales/nl/embeddings.json +++ b/src/i18n/locales/nl/embeddings.json @@ -23,7 +23,8 @@ "failedToProcessBatchWithError": "Verwerken van batch mislukt na {{maxRetries}} pogingen: {{errorMessage}}" }, "vectorStore": { - "qdrantConnectionFailed": "Kan geen verbinding maken met Qdrant vectordatabase. Zorg ervoor dat Qdrant draait en toegankelijk is op {{qdrantUrl}}. Fout: {{errorMessage}}" + "qdrantConnectionFailed": "Kan geen verbinding maken met Qdrant vectordatabase. Zorg ervoor dat Qdrant draait en toegankelijk is op {{qdrantUrl}}. Fout: {{errorMessage}}", + "vectorDimensionMismatch": "Kan de vectorindex voor het nieuwe model niet bijwerken. Probeer de index te wissen en opnieuw te beginnen. Details: {{errorMessage}}" }, "validation": { "authenticationFailed": "Authenticatie mislukt. Controleer je API-sleutel in de instellingen.", diff --git a/src/i18n/locales/pl/embeddings.json b/src/i18n/locales/pl/embeddings.json index 874fcf7e00..49b27c51d4 100644 --- a/src/i18n/locales/pl/embeddings.json +++ b/src/i18n/locales/pl/embeddings.json @@ -23,7 +23,8 @@ "failedToProcessBatchWithError": "Nie udało się przetworzyć partii po {{maxRetries}} próbach: {{errorMessage}}" }, "vectorStore": { - "qdrantConnectionFailed": "Nie udało się połączyć z bazą danych wektorowych Qdrant. Upewnij się, że Qdrant jest uruchomiony i dostępny pod adresem {{qdrantUrl}}. Błąd: {{errorMessage}}" + "qdrantConnectionFailed": "Nie udało się połączyć z bazą danych wektorowych Qdrant. Upewnij się, że Qdrant jest uruchomiony i dostępny pod adresem {{qdrantUrl}}. Błąd: {{errorMessage}}", + "vectorDimensionMismatch": "Nie udało się zaktualizować indeksu wektorowego dla nowego modelu. Spróbuj wyczyścić indeks i zacząć od nowa. Szczegóły: {{errorMessage}}" }, "validation": { "authenticationFailed": "Uwierzytelnianie nie powiodło się. Sprawdź swój klucz API w ustawieniach.", diff --git a/src/i18n/locales/pt-BR/embeddings.json b/src/i18n/locales/pt-BR/embeddings.json index 741a10423e..9c88b170d9 100644 --- a/src/i18n/locales/pt-BR/embeddings.json +++ b/src/i18n/locales/pt-BR/embeddings.json @@ -23,7 +23,8 @@ "failedToProcessBatchWithError": "Falha ao processar lote após {{maxRetries}} tentativas: {{errorMessage}}" }, "vectorStore": { - "qdrantConnectionFailed": "Falha ao conectar com o banco de dados vetorial Qdrant. Certifique-se de que o Qdrant esteja rodando e acessível em {{qdrantUrl}}. Erro: {{errorMessage}}" + "qdrantConnectionFailed": "Falha ao conectar com o banco de dados vetorial Qdrant. Certifique-se de que o Qdrant esteja rodando e acessível em {{qdrantUrl}}. Erro: {{errorMessage}}", + "vectorDimensionMismatch": "Falha ao atualizar o índice de vetores para o novo modelo. Tente limpar o índice e começar novamente. Detalhes: {{errorMessage}}" }, "validation": { "authenticationFailed": "Falha na autenticação. Verifique sua chave de API nas configurações.", diff --git a/src/i18n/locales/ru/embeddings.json b/src/i18n/locales/ru/embeddings.json index 78a21872c6..eb7c129a01 100644 --- a/src/i18n/locales/ru/embeddings.json +++ b/src/i18n/locales/ru/embeddings.json @@ -23,7 +23,8 @@ "failedToProcessBatchWithError": "Не удалось обработать пакет после {{maxRetries}} попыток: {{errorMessage}}" }, "vectorStore": { - "qdrantConnectionFailed": "Не удалось подключиться к векторной базе данных Qdrant. Убедитесь, что Qdrant запущен и доступен по адресу {{qdrantUrl}}. Ошибка: {{errorMessage}}" + "qdrantConnectionFailed": "Не удалось подключиться к векторной базе данных Qdrant. Убедитесь, что Qdrant запущен и доступен по адресу {{qdrantUrl}}. Ошибка: {{errorMessage}}", + "vectorDimensionMismatch": "Не удалось обновить векторный индекс для новой модели. Попробуйте очистить индекс и начать сначала. Подробности: {{errorMessage}}" }, "validation": { "authenticationFailed": "Ошибка аутентификации. Проверьте свой ключ API в настройках.", diff --git a/src/i18n/locales/tr/embeddings.json b/src/i18n/locales/tr/embeddings.json index 411bad5eb3..223707a880 100644 --- a/src/i18n/locales/tr/embeddings.json +++ b/src/i18n/locales/tr/embeddings.json @@ -23,7 +23,8 @@ "failedToProcessBatchWithError": "{{maxRetries}} denemeden sonra toplu işlem başarısız oldu: {{errorMessage}}" }, "vectorStore": { - "qdrantConnectionFailed": "Qdrant vektör veritabanına bağlanılamadı. Qdrant'ın çalıştığından ve {{qdrantUrl}} adresinde erişilebilir olduğundan emin olun. Hata: {{errorMessage}}" + "qdrantConnectionFailed": "Qdrant vektör veritabanına bağlanılamadı. Qdrant'ın çalıştığından ve {{qdrantUrl}} adresinde erişilebilir olduğundan emin olun. Hata: {{errorMessage}}", + "vectorDimensionMismatch": "Yeni model için vektör dizini güncellenemedi. Lütfen dizini temizleyip yeniden başlatmayı deneyin. Detaylar: {{errorMessage}}" }, "validation": { "authenticationFailed": "Kimlik doğrulama başarısız oldu. Lütfen ayarlardan API anahtarınızı kontrol edin.", diff --git a/src/i18n/locales/vi/embeddings.json b/src/i18n/locales/vi/embeddings.json index c645199046..859d17fe98 100644 --- a/src/i18n/locales/vi/embeddings.json +++ b/src/i18n/locales/vi/embeddings.json @@ -23,7 +23,8 @@ "failedToProcessBatchWithError": "Không thể xử lý lô sau {{maxRetries}} lần thử: {{errorMessage}}" }, "vectorStore": { - "qdrantConnectionFailed": "Không thể kết nối với cơ sở dữ liệu vector Qdrant. Vui lòng đảm bảo Qdrant đang chạy và có thể truy cập tại {{qdrantUrl}}. Lỗi: {{errorMessage}}" + "qdrantConnectionFailed": "Không thể kết nối với cơ sở dữ liệu vector Qdrant. Vui lòng đảm bảo Qdrant đang chạy và có thể truy cập tại {{qdrantUrl}}. Lỗi: {{errorMessage}}", + "vectorDimensionMismatch": "Không thể cập nhật chỉ mục vector cho mô hình mới. Vui lòng thử xóa chỉ mục và bắt đầu lại. Chi tiết: {{errorMessage}}" }, "validation": { "authenticationFailed": "Xác thực không thành công. Vui lòng kiểm tra khóa API của bạn trong cài đặt.", diff --git a/src/i18n/locales/zh-CN/embeddings.json b/src/i18n/locales/zh-CN/embeddings.json index 8eb4ae4d2d..4d40e33818 100644 --- a/src/i18n/locales/zh-CN/embeddings.json +++ b/src/i18n/locales/zh-CN/embeddings.json @@ -23,7 +23,8 @@ "failedToProcessBatchWithError": "尝试 {{maxRetries}} 次后批次处理失败:{{errorMessage}}" }, "vectorStore": { - "qdrantConnectionFailed": "连接 Qdrant 向量数据库失败。请确保 Qdrant 正在运行并可在 {{qdrantUrl}} 访问。错误:{{errorMessage}}" + "qdrantConnectionFailed": "连接 Qdrant 向量数据库失败。请确保 Qdrant 正在运行并可在 {{qdrantUrl}} 访问。错误:{{errorMessage}}", + "vectorDimensionMismatch": "无法更新新模型的向量索引。请尝试清除索引并重新开始。详细信息:{{errorMessage}}" }, "validation": { "authenticationFailed": "身份验证失败。请在设置中检查您的 API 密钥。", diff --git a/src/i18n/locales/zh-TW/embeddings.json b/src/i18n/locales/zh-TW/embeddings.json index 7bd4dfeba3..10c87c18b2 100644 --- a/src/i18n/locales/zh-TW/embeddings.json +++ b/src/i18n/locales/zh-TW/embeddings.json @@ -23,7 +23,8 @@ "failedToProcessBatchWithError": "嘗試 {{maxRetries}} 次後批次處理失敗:{{errorMessage}}" }, "vectorStore": { - "qdrantConnectionFailed": "連接 Qdrant 向量資料庫失敗。請確保 Qdrant 正在執行並可在 {{qdrantUrl}} 存取。錯誤:{{errorMessage}}" + "qdrantConnectionFailed": "連接 Qdrant 向量資料庫失敗。請確保 Qdrant 正在執行並可在 {{qdrantUrl}} 存取。錯誤:{{errorMessage}}", + "vectorDimensionMismatch": "無法更新新模型的向量索引。請嘗試清除索引並重新開始。詳細資訊: {{errorMessage}}" }, "validation": { "authenticationFailed": "驗證失敗。請在設定中檢查您的 API 金鑰。", 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 bc4381edf2..8bd145ac40 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 @@ -9,6 +9,9 @@ import { DEFAULT_MAX_SEARCH_RESULTS, DEFAULT_SEARCH_MIN_SCORE } from "../../cons vitest.mock("@qdrant/js-client-rest") vitest.mock("crypto") vitest.mock("../../../../utils/path") +vitest.mock("../../../../i18n", () => ({ + t: (key: string) => key, // Just return the key for testing +})) vitest.mock("path", () => ({ ...vitest.importActual("path"), sep: "/", @@ -674,7 +677,7 @@ describe("QdrantVectorStore", () => { ;(console.warn as any).mockRestore() }) - it("should re-throw error from deleteCollection when recreating collection with mismatched vectorSize", async () => { + it("should throw vectorDimensionMismatch error when deleteCollection fails during recreation", async () => { const differentVectorSize = 768 mockQdrantClientInstance.getCollection.mockResolvedValue({ config: { @@ -691,15 +694,67 @@ describe("QdrantVectorStore", () => { vitest.spyOn(console, "error").mockImplementation(() => {}) vitest.spyOn(console, "warn").mockImplementation(() => {}) - // The actual error message includes the URL and error details - await expect(vectorStore.initialize()).rejects.toThrow( - /Failed to connect to Qdrant vector database|vectorStore\.qdrantConnectionFailed/, - ) + // The error should have a cause property set to the original error + let caughtError: any + try { + await vectorStore.initialize() + } catch (error: any) { + caughtError = error + } + + expect(caughtError).toBeDefined() + expect(caughtError.message).toContain("embeddings:vectorStore.vectorDimensionMismatch") + expect(caughtError.cause).toBe(deleteError) expect(mockQdrantClientInstance.getCollection).toHaveBeenCalledTimes(1) expect(mockQdrantClientInstance.deleteCollection).toHaveBeenCalledTimes(1) expect(mockQdrantClientInstance.createCollection).not.toHaveBeenCalled() expect(mockQdrantClientInstance.createPayloadIndex).not.toHaveBeenCalled() + // Should log both the warning and the critical error + expect(console.warn).toHaveBeenCalledTimes(1) + expect(console.error).toHaveBeenCalledTimes(2) // One for the critical error, one for the outer catch + ;(console.error as any).mockRestore() + ;(console.warn as any).mockRestore() + }) + + it("should throw vectorDimensionMismatch error when createCollection fails during recreation", async () => { + const differentVectorSize = 768 + mockQdrantClientInstance.getCollection.mockResolvedValue({ + config: { + params: { + vectors: { + size: differentVectorSize, + }, + }, + }, + } as any) + + // Delete succeeds but create fails + mockQdrantClientInstance.deleteCollection.mockResolvedValue(true as any) + const createError = new Error("Create Collection Failed") + mockQdrantClientInstance.createCollection.mockRejectedValue(createError) + vitest.spyOn(console, "error").mockImplementation(() => {}) + vitest.spyOn(console, "warn").mockImplementation(() => {}) + + // Should throw an error with cause property set to the original error + let caughtError: any + try { + await vectorStore.initialize() + } catch (error: any) { + caughtError = error + } + + expect(caughtError).toBeDefined() + expect(caughtError.message).toContain("embeddings:vectorStore.vectorDimensionMismatch") + expect(caughtError.cause).toBe(createError) + + expect(mockQdrantClientInstance.getCollection).toHaveBeenCalledTimes(1) + expect(mockQdrantClientInstance.deleteCollection).toHaveBeenCalledTimes(1) + expect(mockQdrantClientInstance.createCollection).toHaveBeenCalledTimes(1) + expect(mockQdrantClientInstance.createPayloadIndex).not.toHaveBeenCalled() + // Should log warning, critical error, and outer error + expect(console.warn).toHaveBeenCalledTimes(1) + expect(console.error).toHaveBeenCalledTimes(2) ;(console.error as any).mockRestore() ;(console.warn as any).mockRestore() }) diff --git a/src/services/code-index/vector-store/qdrant-client.ts b/src/services/code-index/vector-store/qdrant-client.ts index c8883959d5..b23f5bca8a 100644 --- a/src/services/code-index/vector-store/qdrant-client.ts +++ b/src/services/code-index/vector-store/qdrant-client.ts @@ -165,17 +165,33 @@ export class QdrantVectorStore implements IVectorStore { created = false // Exists and correct } else { // Exists but wrong vector size, recreate - console.warn( - `[QdrantVectorStore] Collection ${this.collectionName} exists with vector size ${existingVectorSize}, but expected ${this.vectorSize}. Recreating collection.`, - ) - await this.client.deleteCollection(this.collectionName) // Known to exist - await this.client.createCollection(this.collectionName, { - vectors: { - size: this.vectorSize, - distance: this.DISTANCE_METRIC, - }, - }) - created = true + try { + console.warn( + `[QdrantVectorStore] Collection ${this.collectionName} exists with vector size ${existingVectorSize}, but expected ${this.vectorSize}. Recreating collection.`, + ) + await this.client.deleteCollection(this.collectionName) + await this.client.createCollection(this.collectionName, { + vectors: { + size: this.vectorSize, + distance: this.DISTANCE_METRIC, + }, + }) + created = true + } catch (recreationError) { + const errorMessage = + recreationError instanceof Error ? recreationError.message : String(recreationError) + console.error( + `[QdrantVectorStore] CRITICAL: Failed to recreate collection ${this.collectionName} for new vector size. Error: ${errorMessage}`, + ) + const dimensionMismatchError = new Error( + t("embeddings:vectorStore.vectorDimensionMismatch", { + errorMessage, + }), + ) + // Use error.cause to preserve the original error context + dimensionMismatchError.cause = recreationError + throw dimensionMismatchError + } } } @@ -204,7 +220,12 @@ export class QdrantVectorStore implements IVectorStore { errorMessage, ) - // Provide a more user-friendly error message that includes the original error + // If this is already a vector dimension mismatch error (identified by cause), re-throw it as-is + if (error instanceof Error && error.cause !== undefined) { + throw error + } + + // Otherwise, provide a more user-friendly error message that includes the original error throw new Error( t("embeddings:vectorStore.qdrantConnectionFailed", { qdrantUrl: this.qdrantUrl, errorMessage }), ) From ab55854702e6b21e5434e186714e969f27fd5030 Mon Sep 17 00:00:00 2001 From: Noritaka Kobayashi Date: Fri, 11 Jul 2025 00:33:26 +0900 Subject: [PATCH 23/27] chore: fix typos in comment & document (#5569) --- CHANGELOG.md | 4 ++-- apps/vscode-e2e/src/suite/tools/apply-diff.test.ts | 2 +- apps/web-roo-code/src/app/evals/evals.tsx | 2 +- packages/evals/README.md | 2 +- src/core/tools/readFileTool.ts | 2 +- webview-ui/src/utils/vscode.ts | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca9b46d596..342e209180 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -383,7 +383,7 @@ - Fix vscode-material-icons in the filer picker - Fix global settings export - Respect user-configured terminal integration timeout (thanks @KJ7LNW) -- Contex condensing enhancements (thanks @SannidhyaSah) +- Context condensing enhancements (thanks @SannidhyaSah) ## [3.18.1] - 2025-05-22 @@ -895,7 +895,7 @@ ## [3.10.1] - 2025-03-20 -- Make the suggested responses optional to not break overriden system prompts +- Make the suggested responses optional to not break overridden system prompts ## [3.10.0] - 2025-03-20 diff --git a/apps/vscode-e2e/src/suite/tools/apply-diff.test.ts b/apps/vscode-e2e/src/suite/tools/apply-diff.test.ts index 64d019aef0..6e6dbc5995 100644 --- a/apps/vscode-e2e/src/suite/tools/apply-diff.test.ts +++ b/apps/vscode-e2e/src/suite/tools/apply-diff.test.ts @@ -224,7 +224,7 @@ function validateInput(input) { }, text: `Use apply_diff on the file ${testFile.name} to change "Hello World" to "Hello Universe". The file already exists with this content: ${testFile.content}\nAssume the file exists and you can modify it directly.`, - }) //Temporary meassure since list_files ignores all the files inside a tmp workspace + }) //Temporary measure since list_files ignores all the files inside a tmp workspace console.log("Task ID:", taskId) console.log("Test filename:", testFile.name) diff --git a/apps/web-roo-code/src/app/evals/evals.tsx b/apps/web-roo-code/src/app/evals/evals.tsx index b94bc62224..5921112c46 100644 --- a/apps/web-roo-code/src/app/evals/evals.tsx +++ b/apps/web-roo-code/src/app/evals/evals.tsx @@ -203,7 +203,7 @@ export function Evals({
- (Note: Very expensive models are exluded from the scatter plot.) + (Note: Very expensive models are excluded from the scatter plot.)
diff --git a/packages/evals/README.md b/packages/evals/README.md index a33c7a81cf..b26945dc8c 100644 --- a/packages/evals/README.md +++ b/packages/evals/README.md @@ -29,7 +29,7 @@ Start the evals service: docker compose -f packages/evals/docker-compose.yml --profile server --profile runner up --build --scale runner=0 ``` -The initial build process can take a minute or two. Upon success you should see ouput indicating that a web service is running on [localhost:3000](http://localhost:3000/): +The initial build process can take a minute or two. Upon success you should see output indicating that a web service is running on [localhost:3000](http://localhost:3000/): Screenshot 2025-06-05 at 12 05 38 PM Additionally, you'll find in Docker Desktop that database and redis services are running: diff --git a/src/core/tools/readFileTool.ts b/src/core/tools/readFileTool.ts index 1459838fe0..6de8dd5642 100644 --- a/src/core/tools/readFileTool.ts +++ b/src/core/tools/readFileTool.ts @@ -36,7 +36,7 @@ export function getReadFileToolDescription(blockName: string, blockParams: any): } } catch (error) { console.error("Failed to parse read_file args XML for description:", error) - return `[${blockName} with unparseable args]` + return `[${blockName} with unparsable args]` } } else if (blockParams.path) { // Fallback for legacy single-path usage diff --git a/webview-ui/src/utils/vscode.ts b/webview-ui/src/utils/vscode.ts index 2b2b25593b..2cc0a58909 100644 --- a/webview-ui/src/utils/vscode.ts +++ b/webview-ui/src/utils/vscode.ts @@ -28,7 +28,7 @@ class VSCodeAPIWrapper { * @remarks When running webview code inside a web browser, postMessage will instead * log the given message to the console. * - * @param message Abitrary data (must be JSON serializable) to send to the extension context. + * @param message Arbitrary data (must be JSON serializable) to send to the extension context. */ public postMessage(message: WebviewMessage) { if (this.vsCodeApi) { From 6fa918c2754a9392974f2438c3f17b3d0544c94a Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Thu, 10 Jul 2025 12:30:06 -0400 Subject: [PATCH 24/27] Improve the display of codebase search results (#5571) --- webview-ui/src/components/chat/ChatRow.tsx | 4 ++-- .../src/components/chat/CodebaseSearchResult.tsx | 6 +++--- .../chat/CodebaseSearchResultsDisplay.tsx | 13 ++++++------- webview-ui/src/i18n/locales/ca/chat.json | 3 ++- webview-ui/src/i18n/locales/de/chat.json | 3 ++- webview-ui/src/i18n/locales/en/chat.json | 3 ++- webview-ui/src/i18n/locales/es/chat.json | 3 ++- webview-ui/src/i18n/locales/fr/chat.json | 3 ++- webview-ui/src/i18n/locales/hi/chat.json | 3 ++- webview-ui/src/i18n/locales/id/chat.json | 3 ++- webview-ui/src/i18n/locales/it/chat.json | 3 ++- webview-ui/src/i18n/locales/ja/chat.json | 3 ++- webview-ui/src/i18n/locales/ko/chat.json | 3 ++- webview-ui/src/i18n/locales/nl/chat.json | 3 ++- webview-ui/src/i18n/locales/pl/chat.json | 3 ++- webview-ui/src/i18n/locales/pt-BR/chat.json | 3 ++- webview-ui/src/i18n/locales/ru/chat.json | 3 ++- webview-ui/src/i18n/locales/tr/chat.json | 3 ++- webview-ui/src/i18n/locales/vi/chat.json | 3 ++- webview-ui/src/i18n/locales/zh-CN/chat.json | 3 ++- webview-ui/src/i18n/locales/zh-TW/chat.json | 3 ++- 21 files changed, 47 insertions(+), 30 deletions(-) diff --git a/webview-ui/src/components/chat/ChatRow.tsx b/webview-ui/src/components/chat/ChatRow.tsx index 4fd7977fc2..c508f7e906 100644 --- a/webview-ui/src/components/chat/ChatRow.tsx +++ b/webview-ui/src/components/chat/ChatRow.tsx @@ -1166,9 +1166,9 @@ export const ChatRowContent = ({ return
Error displaying search results.
} - const { query = "", results = [] } = parsed?.content || {} + const { results = [] } = parsed?.content || {} - return + return case "user_edit_todos": return {}} /> default: diff --git a/webview-ui/src/components/chat/CodebaseSearchResult.tsx b/webview-ui/src/components/chat/CodebaseSearchResult.tsx index 4d48749ecd..8280ea3d47 100644 --- a/webview-ui/src/components/chat/CodebaseSearchResult.tsx +++ b/webview-ui/src/components/chat/CodebaseSearchResult.tsx @@ -30,15 +30,15 @@ const CodebaseSearchResult: React.FC = ({ filePath, s
+ className="p-2 border border-[var(--vscode-editorGroup-border)] cursor-pointer hover:bg-secondary hover:text-white">
- {filePath.split("/").at(-1)}:{startLine}-{endLine} + {filePath.split("/").at(-1)}:{startLine === endLine ? startLine : `${startLine}-${endLine}`} {filePath.split("/").slice(0, -1).join("/")} - + {score.toFixed(3)}
diff --git a/webview-ui/src/components/chat/CodebaseSearchResultsDisplay.tsx b/webview-ui/src/components/chat/CodebaseSearchResultsDisplay.tsx index f20324b7cc..93d8fa7da8 100644 --- a/webview-ui/src/components/chat/CodebaseSearchResultsDisplay.tsx +++ b/webview-ui/src/components/chat/CodebaseSearchResultsDisplay.tsx @@ -3,7 +3,6 @@ import CodebaseSearchResult from "./CodebaseSearchResult" import { Trans } from "react-i18next" interface CodebaseSearchResultsDisplayProps { - query: string results: Array<{ filePath: string score: number @@ -13,26 +12,26 @@ interface CodebaseSearchResultsDisplayProps { }> } -const CodebaseSearchResultsDisplay: React.FC = ({ query, results }) => { +const CodebaseSearchResultsDisplay: React.FC = ({ results }) => { const [codebaseSearchResultsExpanded, setCodebaseSearchResultsExpanded] = useState(false) return ( -
+
setCodebaseSearchResultsExpanded(!codebaseSearchResultsExpanded)} - className="font-bold cursor-pointer flex items-center justify-between px-2 py-2 rounded border bg-[var(--vscode-editor-background)] border-[var(--vscode-editorGroup-border)]"> + className="cursor-pointer flex items-center justify-between px-2 py-2 border bg-[var(--vscode-editor-background)] border-[var(--vscode-editorGroup-border)]"> }} - values={{ query, count: results.length }} + count={results.length} + values={{ count: results.length }} />
{codebaseSearchResultsExpanded && ( -
+
{results.map((result, idx) => ( {{query}}:", "wantsToSearchWithPath": "Roo vol cercar a la base de codi {{query}} a {{path}}:", - "didSearch": "S'han trobat {{count}} resultat(s) per a {{query}}:", + "didSearch_one": "S'ha trobat 1 resultat", + "didSearch_other": "S'han trobat {{count}} resultats", "resultTooltip": "Puntuació de similitud: {{score}} (fes clic per obrir el fitxer)" }, "read-batch": { diff --git a/webview-ui/src/i18n/locales/de/chat.json b/webview-ui/src/i18n/locales/de/chat.json index fcbbdb2b68..13f0f64e45 100644 --- a/webview-ui/src/i18n/locales/de/chat.json +++ b/webview-ui/src/i18n/locales/de/chat.json @@ -297,7 +297,8 @@ "codebaseSearch": { "wantsToSearch": "Roo möchte den Codebase nach {{query}} durchsuchen:", "wantsToSearchWithPath": "Roo möchte den Codebase nach {{query}} in {{path}} durchsuchen:", - "didSearch": "{{count}} Ergebnis(se) für {{query}} gefunden:", + "didSearch_one": "1 Ergebnis gefunden", + "didSearch_other": "{{count}} Ergebnisse gefunden", "resultTooltip": "Ähnlichkeitswert: {{score}} (klicken zum Öffnen der Datei)" }, "read-batch": { diff --git a/webview-ui/src/i18n/locales/en/chat.json b/webview-ui/src/i18n/locales/en/chat.json index 326f52c74d..ef40ba854b 100644 --- a/webview-ui/src/i18n/locales/en/chat.json +++ b/webview-ui/src/i18n/locales/en/chat.json @@ -202,7 +202,8 @@ "codebaseSearch": { "wantsToSearch": "Roo wants to search the codebase for {{query}}:", "wantsToSearchWithPath": "Roo wants to search the codebase for {{query}} in {{path}}:", - "didSearch": "Found {{count}} result(s) for {{query}}:", + "didSearch_one": "Found 1 result", + "didSearch_other": "Found {{count}} results", "resultTooltip": "Similarity score: {{score}} (click to open file)" }, "commandOutput": "Command Output", diff --git a/webview-ui/src/i18n/locales/es/chat.json b/webview-ui/src/i18n/locales/es/chat.json index 9bea6bd2c4..200939d34b 100644 --- a/webview-ui/src/i18n/locales/es/chat.json +++ b/webview-ui/src/i18n/locales/es/chat.json @@ -297,7 +297,8 @@ "codebaseSearch": { "wantsToSearch": "Roo quiere buscar en la base de código {{query}}:", "wantsToSearchWithPath": "Roo quiere buscar en la base de código {{query}} en {{path}}:", - "didSearch": "Se encontraron {{count}} resultado(s) para {{query}}:", + "didSearch_one": "Se encontró 1 resultado", + "didSearch_other": "Se encontraron {{count}} resultados", "resultTooltip": "Puntuación de similitud: {{score}} (haz clic para abrir el archivo)" }, "read-batch": { diff --git a/webview-ui/src/i18n/locales/fr/chat.json b/webview-ui/src/i18n/locales/fr/chat.json index d7748fb15c..2858400e78 100644 --- a/webview-ui/src/i18n/locales/fr/chat.json +++ b/webview-ui/src/i18n/locales/fr/chat.json @@ -297,7 +297,8 @@ "codebaseSearch": { "wantsToSearch": "Roo veut rechercher dans la base de code {{query}} :", "wantsToSearchWithPath": "Roo veut rechercher dans la base de code {{query}} dans {{path}} :", - "didSearch": "{{count}} résultat(s) trouvé(s) pour {{query}} :", + "didSearch_one": "1 résultat trouvé", + "didSearch_other": "{{count}} résultats trouvés", "resultTooltip": "Score de similarité : {{score}} (cliquer pour ouvrir le fichier)" }, "read-batch": { diff --git a/webview-ui/src/i18n/locales/hi/chat.json b/webview-ui/src/i18n/locales/hi/chat.json index 7979f624e0..c2db235a6b 100644 --- a/webview-ui/src/i18n/locales/hi/chat.json +++ b/webview-ui/src/i18n/locales/hi/chat.json @@ -297,7 +297,8 @@ "codebaseSearch": { "wantsToSearch": "Roo कोडबेस में {{query}} खोजना चाहता है:", "wantsToSearchWithPath": "Roo {{path}} में कोडबेस में {{query}} खोजना चाहता है:", - "didSearch": "{{query}} के लिए {{count}} परिणाम मिले:", + "didSearch_one": "1 परिणाम मिला", + "didSearch_other": "{{count}} परिणाम मिले", "resultTooltip": "समानता स्कोर: {{score}} (फ़ाइल खोलने के लिए क्लिक करें)" }, "read-batch": { diff --git a/webview-ui/src/i18n/locales/id/chat.json b/webview-ui/src/i18n/locales/id/chat.json index 2134b48bfc..dce0426832 100644 --- a/webview-ui/src/i18n/locales/id/chat.json +++ b/webview-ui/src/i18n/locales/id/chat.json @@ -208,7 +208,8 @@ "codebaseSearch": { "wantsToSearch": "Roo ingin mencari codebase untuk {{query}}:", "wantsToSearchWithPath": "Roo ingin mencari codebase untuk {{query}} di {{path}}:", - "didSearch": "Ditemukan {{count}} hasil untuk {{query}}:", + "didSearch_one": "Ditemukan 1 hasil", + "didSearch_other": "Ditemukan {{count}} hasil", "resultTooltip": "Skor kemiripan: {{score}} (klik untuk membuka file)" }, "commandOutput": "Output Perintah", diff --git a/webview-ui/src/i18n/locales/it/chat.json b/webview-ui/src/i18n/locales/it/chat.json index ed886e6ef9..084606605f 100644 --- a/webview-ui/src/i18n/locales/it/chat.json +++ b/webview-ui/src/i18n/locales/it/chat.json @@ -297,7 +297,8 @@ "codebaseSearch": { "wantsToSearch": "Roo vuole cercare nella base di codice {{query}}:", "wantsToSearchWithPath": "Roo vuole cercare nella base di codice {{query}} in {{path}}:", - "didSearch": "Trovato {{count}} risultato/i per {{query}}:", + "didSearch_one": "Trovato 1 risultato", + "didSearch_other": "Trovati {{count}} risultati", "resultTooltip": "Punteggio di somiglianza: {{score}} (clicca per aprire il file)" }, "read-batch": { diff --git a/webview-ui/src/i18n/locales/ja/chat.json b/webview-ui/src/i18n/locales/ja/chat.json index 61c2820299..dc69fdf742 100644 --- a/webview-ui/src/i18n/locales/ja/chat.json +++ b/webview-ui/src/i18n/locales/ja/chat.json @@ -297,7 +297,8 @@ "codebaseSearch": { "wantsToSearch": "Rooはコードベースで {{query}} を検索したい:", "wantsToSearchWithPath": "Rooは {{path}} 内のコードベースで {{query}} を検索したい:", - "didSearch": "{{query}} の検索結果: {{count}} 件", + "didSearch_one": "1件の結果が見つかりました", + "didSearch_other": "{{count}}件の結果が見つかりました", "resultTooltip": "類似度スコア: {{score}} (クリックしてファイルを開く)" }, "read-batch": { diff --git a/webview-ui/src/i18n/locales/ko/chat.json b/webview-ui/src/i18n/locales/ko/chat.json index 9ac8e90644..1be0b4cb1b 100644 --- a/webview-ui/src/i18n/locales/ko/chat.json +++ b/webview-ui/src/i18n/locales/ko/chat.json @@ -297,7 +297,8 @@ "codebaseSearch": { "wantsToSearch": "Roo가 코드베이스에서 {{query}}을(를) 검색하고 싶어합니다:", "wantsToSearchWithPath": "Roo가 {{path}}에서 {{query}}을(를) 검색하고 싶어합니다:", - "didSearch": "{{query}}에 대한 검색 결과 {{count}}개 찾음:", + "didSearch_one": "1개의 결과를 찾았습니다", + "didSearch_other": "{{count}}개의 결과를 찾았습니다", "resultTooltip": "유사도 점수: {{score}} (클릭하여 파일 열기)" }, "read-batch": { diff --git a/webview-ui/src/i18n/locales/nl/chat.json b/webview-ui/src/i18n/locales/nl/chat.json index 5b2da04160..d2834c700f 100644 --- a/webview-ui/src/i18n/locales/nl/chat.json +++ b/webview-ui/src/i18n/locales/nl/chat.json @@ -297,7 +297,8 @@ "codebaseSearch": { "wantsToSearch": "Roo wil de codebase doorzoeken op {{query}}:", "wantsToSearchWithPath": "Roo wil de codebase doorzoeken op {{query}} in {{path}}:", - "didSearch": "{{count}} resultaat/resultaten gevonden voor {{query}}:", + "didSearch_one": "1 resultaat gevonden", + "didSearch_other": "{{count}} resultaten gevonden", "resultTooltip": "Gelijkenisscore: {{score}} (klik om bestand te openen)" }, "read-batch": { diff --git a/webview-ui/src/i18n/locales/pl/chat.json b/webview-ui/src/i18n/locales/pl/chat.json index 5226ef51ea..e8020219c0 100644 --- a/webview-ui/src/i18n/locales/pl/chat.json +++ b/webview-ui/src/i18n/locales/pl/chat.json @@ -297,7 +297,8 @@ "codebaseSearch": { "wantsToSearch": "Roo chce przeszukać bazę kodu w poszukiwaniu {{query}}:", "wantsToSearchWithPath": "Roo chce przeszukać bazę kodu w poszukiwaniu {{query}} w {{path}}:", - "didSearch": "Znaleziono {{count}} wynik(ów) dla {{query}}:", + "didSearch_one": "Znaleziono 1 wynik", + "didSearch_other": "Znaleziono {{count}} wyników", "resultTooltip": "Wynik podobieństwa: {{score}} (kliknij, aby otworzyć plik)" }, "read-batch": { diff --git a/webview-ui/src/i18n/locales/pt-BR/chat.json b/webview-ui/src/i18n/locales/pt-BR/chat.json index eca50fbd33..a0b39e6c96 100644 --- a/webview-ui/src/i18n/locales/pt-BR/chat.json +++ b/webview-ui/src/i18n/locales/pt-BR/chat.json @@ -297,7 +297,8 @@ "codebaseSearch": { "wantsToSearch": "Roo quer pesquisar na base de código por {{query}}:", "wantsToSearchWithPath": "Roo quer pesquisar na base de código por {{query}} em {{path}}:", - "didSearch": "Encontrado {{count}} resultado(s) para {{query}}:", + "didSearch_one": "Encontrado 1 resultado", + "didSearch_other": "Encontrados {{count}} resultados", "resultTooltip": "Pontuação de similaridade: {{score}} (clique para abrir o arquivo)" }, "read-batch": { diff --git a/webview-ui/src/i18n/locales/ru/chat.json b/webview-ui/src/i18n/locales/ru/chat.json index 72ae883703..9c751fba8c 100644 --- a/webview-ui/src/i18n/locales/ru/chat.json +++ b/webview-ui/src/i18n/locales/ru/chat.json @@ -297,7 +297,8 @@ "codebaseSearch": { "wantsToSearch": "Roo хочет выполнить поиск в кодовой базе по {{query}}:", "wantsToSearchWithPath": "Roo хочет выполнить поиск в кодовой базе по {{query}} в {{path}}:", - "didSearch": "Найдено {{count}} результат(ов) для {{query}}:", + "didSearch_one": "Найден 1 результат", + "didSearch_other": "Найдено {{count}} результатов", "resultTooltip": "Оценка схожести: {{score}} (нажмите, чтобы открыть файл)" }, "read-batch": { diff --git a/webview-ui/src/i18n/locales/tr/chat.json b/webview-ui/src/i18n/locales/tr/chat.json index 4f108d7ac3..951877d65e 100644 --- a/webview-ui/src/i18n/locales/tr/chat.json +++ b/webview-ui/src/i18n/locales/tr/chat.json @@ -297,7 +297,8 @@ "codebaseSearch": { "wantsToSearch": "Roo kod tabanında {{query}} aramak istiyor:", "wantsToSearchWithPath": "Roo {{path}} içinde kod tabanında {{query}} aramak istiyor:", - "didSearch": "{{query}} için {{count}} sonuç bulundu:", + "didSearch_one": "1 sonuç bulundu", + "didSearch_other": "{{count}} sonuç bulundu", "resultTooltip": "Benzerlik puanı: {{score}} (dosyayı açmak için tıklayın)" }, "read-batch": { diff --git a/webview-ui/src/i18n/locales/vi/chat.json b/webview-ui/src/i18n/locales/vi/chat.json index 0f0ea71ec3..7588595280 100644 --- a/webview-ui/src/i18n/locales/vi/chat.json +++ b/webview-ui/src/i18n/locales/vi/chat.json @@ -297,7 +297,8 @@ "codebaseSearch": { "wantsToSearch": "Roo muốn tìm kiếm trong cơ sở mã cho {{query}}:", "wantsToSearchWithPath": "Roo muốn tìm kiếm trong cơ sở mã cho {{query}} trong {{path}}:", - "didSearch": "Đã tìm thấy {{count}} kết quả cho {{query}}:", + "didSearch_one": "Đã tìm thấy 1 kết quả", + "didSearch_other": "Đã tìm thấy {{count}} kết quả", "resultTooltip": "Điểm tương tự: {{score}} (nhấp để mở tệp)" }, "read-batch": { diff --git a/webview-ui/src/i18n/locales/zh-CN/chat.json b/webview-ui/src/i18n/locales/zh-CN/chat.json index 6e883b751a..352da4c9d2 100644 --- a/webview-ui/src/i18n/locales/zh-CN/chat.json +++ b/webview-ui/src/i18n/locales/zh-CN/chat.json @@ -297,7 +297,8 @@ "codebaseSearch": { "wantsToSearch": "Roo 需要搜索代码库: {{query}}", "wantsToSearchWithPath": "Roo 需要在 {{path}} 中搜索: {{query}}", - "didSearch": "找到 {{count}} 个结果: {{query}}", + "didSearch_one": "找到 1 个结果", + "didSearch_other": "找到 {{count}} 个结果", "resultTooltip": "相似度评分: {{score}} (点击打开文件)" }, "read-batch": { diff --git a/webview-ui/src/i18n/locales/zh-TW/chat.json b/webview-ui/src/i18n/locales/zh-TW/chat.json index 75d214db03..ff0a541aa1 100644 --- a/webview-ui/src/i18n/locales/zh-TW/chat.json +++ b/webview-ui/src/i18n/locales/zh-TW/chat.json @@ -297,7 +297,8 @@ "codebaseSearch": { "wantsToSearch": "Roo 想要搜尋程式碼庫:{{query}}", "wantsToSearchWithPath": "Roo 想要在 {{path}} 中搜尋:{{query}}", - "didSearch": "找到 {{count}} 個結果:{{query}}", + "didSearch_one": "找到 1 個結果", + "didSearch_other": "找到 {{count}} 個結果", "resultTooltip": "相似度評分:{{score}} (點擊開啟檔案)" }, "read-batch": { From 76a3fdc256adb0ff3a56d0f650c7d0b0c97cad5a Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Thu, 10 Jul 2025 14:36:09 -0400 Subject: [PATCH 25/27] Clean up MCP tool disabling (#5576) --- webview-ui/src/components/mcp/McpToolRow.tsx | 58 ++++---- webview-ui/src/components/mcp/McpView.tsx | 64 +++------ .../mcp/__tests__/McpToolRow.spec.tsx | 130 +++++++++++++++--- .../ui/__tests__/toggle-switch.spec.tsx | 101 ++++++++++++++ webview-ui/src/components/ui/index.ts | 1 + .../src/components/ui/toggle-switch.tsx | 68 +++++++++ 6 files changed, 334 insertions(+), 88 deletions(-) create mode 100644 webview-ui/src/components/ui/__tests__/toggle-switch.spec.tsx create mode 100644 webview-ui/src/components/ui/toggle-switch.tsx diff --git a/webview-ui/src/components/mcp/McpToolRow.tsx b/webview-ui/src/components/mcp/McpToolRow.tsx index 58b938f9f1..aa57b18fd9 100644 --- a/webview-ui/src/components/mcp/McpToolRow.tsx +++ b/webview-ui/src/components/mcp/McpToolRow.tsx @@ -4,7 +4,7 @@ import { McpTool } from "@roo/mcp" import { useAppTranslation } from "@src/i18n/TranslationContext" import { vscode } from "@src/utils/vscode" -import { StandardTooltip } from "@/components/ui" +import { StandardTooltip, ToggleSwitch } from "@/components/ui" type McpToolRowProps = { tool: McpTool @@ -16,6 +16,8 @@ type McpToolRowProps = { const McpToolRow = ({ tool, serverName, serverSource, alwaysAllowMcp, isInChatContext = false }: McpToolRowProps) => { const { t } = useAppTranslation() + const isToolEnabled = tool.enabledForPrompt ?? true + const handleAlwaysAllowChange = () => { if (!serverName) return vscode.postMessage({ @@ -46,17 +48,29 @@ const McpToolRow = ({ tool, serverName, serverSource, alwaysAllowMcp, isInChatCo onClick={(e) => e.stopPropagation()}> {/* Tool name section */}
- + - {tool.name} + + {tool.name} +
{/* Controls section */} {serverName && (
- {/* Always Allow checkbox */} - {alwaysAllowMcp && ( + {/* Always Allow checkbox - only show when tool is enabled */} + {alwaysAllowMcp && isToolEnabled && ( )} - {/* Enabled eye button - only show in settings context */} + {/* Enabled toggle switch - only show in settings context */} {!isInChatContext && ( - + data-testid={`tool-prompt-toggle-${tool.name}`} + /> )}
)}
{tool.description && ( -
{tool.description}
+
+ {tool.description} +
)} - {tool.inputSchema && + {isToolEnabled && + tool.inputSchema && "properties" in tool.inputSchema && Object.keys(tool.inputSchema.properties as Record).length > 0 && (
diff --git a/webview-ui/src/components/mcp/McpView.tsx b/webview-ui/src/components/mcp/McpView.tsx index 2c83b432cc..b95ed2608c 100644 --- a/webview-ui/src/components/mcp/McpView.tsx +++ b/webview-ui/src/components/mcp/McpView.tsx @@ -22,6 +22,7 @@ import { DialogTitle, DialogDescription, DialogFooter, + ToggleSwitch, } from "@src/components/ui" import { buildDocLink } from "@src/utils/docLinks" @@ -295,54 +296,6 @@ const ServerRow = ({ server, alwaysAllowMcp }: { server: McpServer; alwaysAllowM style={{ marginRight: "8px" }}> -
{ - vscode.postMessage({ - type: "toggleMcpServer", - serverName: server.name, - source: server.source || "global", - disabled: !server.disabled, - }) - }} - onKeyDown={(e) => { - if (e.key === "Enter" || e.key === " ") { - e.preventDefault() - vscode.postMessage({ - type: "toggleMcpServer", - serverName: server.name, - source: server.source || "global", - disabled: !server.disabled, - }) - } - }}> -
-
+
+ { + vscode.postMessage({ + type: "toggleMcpServer", + serverName: server.name, + source: server.source || "global", + disabled: !server.disabled, + }) + }} + size="medium" + aria-label={`Toggle ${server.name} server`} + /> +
{server.status === "connected" ? ( diff --git a/webview-ui/src/components/mcp/__tests__/McpToolRow.spec.tsx b/webview-ui/src/components/mcp/__tests__/McpToolRow.spec.tsx index 2bfe3b1338..f686a23f00 100644 --- a/webview-ui/src/components/mcp/__tests__/McpToolRow.spec.tsx +++ b/webview-ui/src/components/mcp/__tests__/McpToolRow.spec.tsx @@ -144,40 +144,40 @@ describe("McpToolRow", () => { expect(screen.getByText("Second parameter")).toBeInTheDocument() }) - it("shows eye button when serverName is provided and not in chat context", () => { + it("shows toggle switch when serverName is provided and not in chat context", () => { render() - const eyeButton = screen.getByRole("button", { name: "Toggle prompt inclusion" }) - expect(eyeButton).toBeInTheDocument() + const toggleSwitch = screen.getByRole("switch", { name: "Toggle prompt inclusion" }) + expect(toggleSwitch).toBeInTheDocument() }) - it("hides eye button when isInChatContext is true", () => { + it("hides toggle switch when isInChatContext is true", () => { render() - const eyeButton = screen.queryByRole("button", { name: "Toggle prompt inclusion" }) - expect(eyeButton).not.toBeInTheDocument() + const toggleSwitch = screen.queryByRole("switch", { name: "Toggle prompt inclusion" }) + expect(toggleSwitch).not.toBeInTheDocument() }) - it("shows correct eye icon based on enabledForPrompt state", () => { - // Test when enabled (should show eye-closed icon) + it("shows correct toggle switch state based on enabledForPrompt", () => { + // Test when enabled (should be checked) const { rerender } = render() - let eyeIcon = screen.getByRole("button", { name: "Toggle prompt inclusion" }).querySelector("span") - expect(eyeIcon).toHaveClass("codicon-eye-closed") + let toggleSwitch = screen.getByRole("switch", { name: "Toggle prompt inclusion" }) + expect(toggleSwitch).toHaveAttribute("aria-checked", "true") - // Test when disabled (should show eye icon) + // Test when disabled (should not be checked) const disabledTool = { ...mockTool, enabledForPrompt: false } rerender() - eyeIcon = screen.getByRole("button", { name: "Toggle prompt inclusion" }).querySelector("span") - expect(eyeIcon).toHaveClass("codicon-eye") + toggleSwitch = screen.getByRole("switch", { name: "Toggle prompt inclusion" }) + expect(toggleSwitch).toHaveAttribute("aria-checked", "false") }) - it("sends message to toggle enabledForPrompt when eye button is clicked", () => { + it("sends message to toggle enabledForPrompt when toggle switch is clicked", () => { render() - const eyeButton = screen.getByRole("button", { name: "Toggle prompt inclusion" }) - fireEvent.click(eyeButton) + const toggleSwitch = screen.getByRole("switch", { name: "Toggle prompt inclusion" }) + fireEvent.click(toggleSwitch) expect(vscode.postMessage).toHaveBeenCalledWith({ type: "toggleToolEnabledForPrompt", @@ -187,4 +187,102 @@ describe("McpToolRow", () => { isEnabled: false, }) }) + + it("hides always allow checkbox when tool is disabled", () => { + const disabledTool = { ...mockTool, enabledForPrompt: false } + render() + + expect(screen.queryByText("Always allow")).not.toBeInTheDocument() + }) + + it("shows always allow checkbox when tool is enabled", () => { + const enabledTool = { ...mockTool, enabledForPrompt: true } + render() + + expect(screen.getByText("Always allow")).toBeInTheDocument() + }) + + it("hides parameters section when tool is disabled", () => { + const disabledToolWithSchema = { + ...mockTool, + enabledForPrompt: false, + inputSchema: { + type: "object", + properties: { + param1: { + type: "string", + description: "First parameter", + }, + }, + required: ["param1"], + }, + } + + render() + + expect(screen.queryByText("Parameters")).not.toBeInTheDocument() + expect(screen.queryByText("param1")).not.toBeInTheDocument() + expect(screen.queryByText("First parameter")).not.toBeInTheDocument() + }) + + it("shows parameters section when tool is enabled", () => { + const enabledToolWithSchema = { + ...mockTool, + enabledForPrompt: true, + inputSchema: { + type: "object", + properties: { + param1: { + type: "string", + description: "First parameter", + }, + }, + required: ["param1"], + }, + } + + render() + + expect(screen.getByText("Parameters")).toBeInTheDocument() + expect(screen.getByText("param1")).toBeInTheDocument() + expect(screen.getByText("First parameter")).toBeInTheDocument() + }) + + it("grays out tool name and description when tool is disabled", () => { + const disabledTool = { + ...mockTool, + enabledForPrompt: false, + description: "A disabled tool", + } + render() + + const toolName = screen.getByText("test-tool") + const toolDescription = screen.getByText("A disabled tool") + + // Check that the tool name has the grayed out classes + expect(toolName).toHaveClass("text-vscode-descriptionForeground", "opacity-60") + + // Check that the description has reduced opacity + expect(toolDescription).toHaveClass("opacity-40") + }) + + it("shows normal styling for tool name and description when tool is enabled", () => { + const enabledTool = { + ...mockTool, + enabledForPrompt: true, + description: "An enabled tool", + } + render() + + const toolName = screen.getByText("test-tool") + const toolDescription = screen.getByText("An enabled tool") + + // Check that the tool name has normal styling + expect(toolName).toHaveClass("text-vscode-foreground") + expect(toolName).not.toHaveClass("text-vscode-descriptionForeground", "opacity-60") + + // Check that the description has normal opacity + expect(toolDescription).toHaveClass("opacity-80") + expect(toolDescription).not.toHaveClass("opacity-40") + }) }) diff --git a/webview-ui/src/components/ui/__tests__/toggle-switch.spec.tsx b/webview-ui/src/components/ui/__tests__/toggle-switch.spec.tsx new file mode 100644 index 0000000000..e394e76139 --- /dev/null +++ b/webview-ui/src/components/ui/__tests__/toggle-switch.spec.tsx @@ -0,0 +1,101 @@ +import React from "react" +import { render, fireEvent, screen } from "@/utils/test-utils" + +import { ToggleSwitch } from "../toggle-switch" + +describe("ToggleSwitch", () => { + it("renders with correct initial state", () => { + const onChange = vi.fn() + render() + + const toggle = screen.getByRole("switch") + expect(toggle).toBeInTheDocument() + expect(toggle).toHaveAttribute("aria-checked", "true") + expect(toggle).toHaveAttribute("aria-label", "Test toggle") + }) + + it("renders unchecked state correctly", () => { + const onChange = vi.fn() + render() + + const toggle = screen.getByRole("switch") + expect(toggle).toHaveAttribute("aria-checked", "false") + }) + + it("calls onChange when clicked", () => { + const onChange = vi.fn() + render() + + const toggle = screen.getByRole("switch") + fireEvent.click(toggle) + + expect(onChange).toHaveBeenCalledTimes(1) + }) + + it("calls onChange when Enter key is pressed", () => { + const onChange = vi.fn() + render() + + const toggle = screen.getByRole("switch") + fireEvent.keyDown(toggle, { key: "Enter" }) + + expect(onChange).toHaveBeenCalledTimes(1) + }) + + it("calls onChange when Space key is pressed", () => { + const onChange = vi.fn() + render() + + const toggle = screen.getByRole("switch") + fireEvent.keyDown(toggle, { key: " " }) + + expect(onChange).toHaveBeenCalledTimes(1) + }) + + it("does not call onChange when disabled", () => { + const onChange = vi.fn() + render() + + const toggle = screen.getByRole("switch") + fireEvent.click(toggle) + fireEvent.keyDown(toggle, { key: "Enter" }) + + expect(onChange).not.toHaveBeenCalled() + }) + + it("has correct tabIndex when disabled", () => { + const onChange = vi.fn() + render() + + const toggle = screen.getByRole("switch") + expect(toggle).toHaveAttribute("tabindex", "-1") + }) + + it("renders with custom data-testid", () => { + const onChange = vi.fn() + render() + + const toggle = screen.getByTestId("custom-toggle") + expect(toggle).toBeInTheDocument() + }) + + it("supports medium size", () => { + const onChange = vi.fn() + render() + + const toggle = screen.getByRole("switch") + expect(toggle).toBeInTheDocument() + // Medium size should be 20px x 10px + expect(toggle).toHaveStyle({ width: "20px", height: "10px" }) + }) + + it("defaults to small size", () => { + const onChange = vi.fn() + render() + + const toggle = screen.getByRole("switch") + expect(toggle).toBeInTheDocument() + // Small size should be 16px x 8px + expect(toggle).toHaveStyle({ width: "16px", height: "8px" }) + }) +}) diff --git a/webview-ui/src/components/ui/index.ts b/webview-ui/src/components/ui/index.ts index c36d3b4769..ee28b964c5 100644 --- a/webview-ui/src/components/ui/index.ts +++ b/webview-ui/src/components/ui/index.ts @@ -18,3 +18,4 @@ export * from "./select" export * from "./textarea" export * from "./tooltip" export * from "./standard-tooltip" +export * from "./toggle-switch" diff --git a/webview-ui/src/components/ui/toggle-switch.tsx b/webview-ui/src/components/ui/toggle-switch.tsx new file mode 100644 index 0000000000..c2488851b5 --- /dev/null +++ b/webview-ui/src/components/ui/toggle-switch.tsx @@ -0,0 +1,68 @@ +import React from "react" + +export interface ToggleSwitchProps { + checked: boolean + onChange: () => void + disabled?: boolean + size?: "small" | "medium" + "aria-label"?: string + "data-testid"?: string +} + +export const ToggleSwitch: React.FC = ({ + checked, + onChange, + disabled = false, + size = "small", + "aria-label": ariaLabel, + "data-testid": dataTestId, +}) => { + const dimensions = size === "small" ? { width: 16, height: 8, dotSize: 4 } : { width: 20, height: 10, dotSize: 6 } + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault() + if (!disabled) { + onChange() + } + } + } + + return ( +
+
+
+ ) +} From 50598b22b589cb2dea0791067790ddefbfc9a936 Mon Sep 17 00:00:00 2001 From: Daniel <57051444+daniel-lxs@users.noreply.github.com> Date: Thu, 10 Jul 2025 14:47:47 -0500 Subject: [PATCH 26/27] fix(i18n): Correct translation fallback logic for embedding errors (#5574) Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> --- src/i18n/locales/ca/embeddings.json | 4 +++- src/i18n/locales/de/embeddings.json | 4 +++- src/i18n/locales/en/embeddings.json | 4 +++- src/i18n/locales/es/embeddings.json | 4 +++- src/i18n/locales/fr/embeddings.json | 4 +++- src/i18n/locales/hi/embeddings.json | 4 +++- src/i18n/locales/id/embeddings.json | 4 +++- src/i18n/locales/it/embeddings.json | 4 +++- src/i18n/locales/ja/embeddings.json | 4 +++- src/i18n/locales/ko/embeddings.json | 4 +++- src/i18n/locales/nl/embeddings.json | 4 +++- src/i18n/locales/pl/embeddings.json | 4 +++- src/i18n/locales/pt-BR/embeddings.json | 4 +++- src/i18n/locales/ru/embeddings.json | 4 +++- src/i18n/locales/tr/embeddings.json | 4 +++- src/i18n/locales/vi/embeddings.json | 4 +++- src/i18n/locales/zh-CN/embeddings.json | 4 +++- src/i18n/locales/zh-TW/embeddings.json | 4 +++- .../embedders/__tests__/gemini.spec.ts | 6 +++--- .../__tests__/openai-compatible.spec.ts | 6 +++--- src/services/code-index/embedders/gemini.ts | 3 ++- .../code-index/embedders/openai-compatible.ts | 4 ++-- src/services/code-index/manager.ts | 11 ++--------- .../code-index/shared/validation-helpers.ts | 16 ++++++++-------- 24 files changed, 74 insertions(+), 44 deletions(-) diff --git a/src/i18n/locales/ca/embeddings.json b/src/i18n/locales/ca/embeddings.json index 709b77ac0a..5deed252bf 100644 --- a/src/i18n/locales/ca/embeddings.json +++ b/src/i18n/locales/ca/embeddings.json @@ -37,7 +37,9 @@ "invalidApiKey": "Clau d'API no vàlida. Comproveu la vostra configuració de clau d'API.", "invalidBaseUrl": "URL base no vàlida. Comproveu la vostra configuració d'URL.", "invalidModel": "Model no vàlid. Comproveu la vostra configuració de model.", - "invalidResponse": "Resposta no vàlida del servei d'incrustació. Comproveu la vostra configuració." + "invalidResponse": "Resposta no vàlida del servei d'incrustació. Comproveu la vostra configuració.", + "apiKeyRequired": "Es requereix una clau d'API per a aquest incrustador", + "baseUrlRequired": "Es requereix una URL base per a aquest incrustador" }, "serviceFactory": { "openAiConfigMissing": "Falta la configuració d'OpenAI per crear l'embedder", diff --git a/src/i18n/locales/de/embeddings.json b/src/i18n/locales/de/embeddings.json index c26975b6c6..74381747e1 100644 --- a/src/i18n/locales/de/embeddings.json +++ b/src/i18n/locales/de/embeddings.json @@ -37,7 +37,9 @@ "invalidApiKey": "Ungültiger API-Schlüssel. Bitte überprüfe deine API-Schlüssel-Konfiguration.", "invalidBaseUrl": "Ungültige Basis-URL. Bitte überprüfe deine URL-Konfiguration.", "invalidModel": "Ungültiges Modell. Bitte überprüfe deine Modellkonfiguration.", - "invalidResponse": "Ungültige Antwort vom Embedder-Dienst. Bitte überprüfe deine Konfiguration." + "invalidResponse": "Ungültige Antwort vom Embedder-Dienst. Bitte überprüfe deine Konfiguration.", + "apiKeyRequired": "API-Schlüssel ist für diesen Embedder erforderlich", + "baseUrlRequired": "Basis-URL ist für diesen Embedder erforderlich" }, "serviceFactory": { "openAiConfigMissing": "OpenAI-Konfiguration fehlt für die Erstellung des Embedders", diff --git a/src/i18n/locales/en/embeddings.json b/src/i18n/locales/en/embeddings.json index f7ed0232f7..96b3b2dbea 100644 --- a/src/i18n/locales/en/embeddings.json +++ b/src/i18n/locales/en/embeddings.json @@ -37,7 +37,9 @@ "invalidApiKey": "Invalid API key. Please check your API key configuration.", "invalidBaseUrl": "Invalid base URL. Please check your URL configuration.", "invalidModel": "Invalid model. Please check your model configuration.", - "invalidResponse": "Invalid response from embedder service. Please check your configuration." + "invalidResponse": "Invalid response from embedder service. Please check your configuration.", + "apiKeyRequired": "API key is required for this embedder", + "baseUrlRequired": "Base URL is required for this embedder" }, "serviceFactory": { "openAiConfigMissing": "OpenAI configuration missing for embedder creation", diff --git a/src/i18n/locales/es/embeddings.json b/src/i18n/locales/es/embeddings.json index 6109693135..e47db420eb 100644 --- a/src/i18n/locales/es/embeddings.json +++ b/src/i18n/locales/es/embeddings.json @@ -37,7 +37,9 @@ "invalidApiKey": "Clave de API no válida. Comprueba la configuración de tu clave de API.", "invalidBaseUrl": "URL base no válida. Comprueba la configuración de tu URL.", "invalidModel": "Modelo no válido. Comprueba la configuración de tu modelo.", - "invalidResponse": "Respuesta no válida del servicio de embedder. Comprueba tu configuración." + "invalidResponse": "Respuesta no válida del servicio de embedder. Comprueba tu configuración.", + "apiKeyRequired": "Se requiere una clave de API para este embedder", + "baseUrlRequired": "Se requiere una URL base para este embedder" }, "serviceFactory": { "openAiConfigMissing": "Falta la configuración de OpenAI para crear el incrustador", diff --git a/src/i18n/locales/fr/embeddings.json b/src/i18n/locales/fr/embeddings.json index 854046de9a..c63d3a7fbc 100644 --- a/src/i18n/locales/fr/embeddings.json +++ b/src/i18n/locales/fr/embeddings.json @@ -37,7 +37,9 @@ "invalidApiKey": "Clé API invalide. Veuillez vérifier votre configuration de clé API.", "invalidBaseUrl": "URL de base invalide. Veuillez vérifier votre configuration d'URL.", "invalidModel": "Modèle invalide. Veuillez vérifier votre configuration de modèle.", - "invalidResponse": "Réponse invalide du service d'embedder. Veuillez vérifier votre configuration." + "invalidResponse": "Réponse invalide du service d'embedder. Veuillez vérifier votre configuration.", + "apiKeyRequired": "Une clé API est requise pour cet embedder.", + "baseUrlRequired": "Une URL de base est requise pour cet embedder" }, "serviceFactory": { "openAiConfigMissing": "Configuration OpenAI manquante pour la création de l'embedder", diff --git a/src/i18n/locales/hi/embeddings.json b/src/i18n/locales/hi/embeddings.json index 75d11b4b9c..15709fd700 100644 --- a/src/i18n/locales/hi/embeddings.json +++ b/src/i18n/locales/hi/embeddings.json @@ -37,7 +37,9 @@ "invalidApiKey": "अमान्य एपीआई कुंजी। कृपया अपनी एपीआई कुंजी कॉन्फ़िगरेशन जांचें।", "invalidBaseUrl": "अमान्य बेस यूआरएल। कृपया अपनी यूआरएल कॉन्फ़िगरेशन जांचें।", "invalidModel": "अमान्य मॉडल। कृपया अपनी मॉडल कॉन्फ़िगरेशन जांचें।", - "invalidResponse": "एम्बेडर सेवा से अमान्य प्रतिक्रिया। कृपया अपनी कॉन्फ़िगरेशन जांचें।" + "invalidResponse": "एम्बेडर सेवा से अमान्य प्रतिक्रिया। कृपया अपनी कॉन्फ़िगरेशन जांचें।", + "apiKeyRequired": "इस एम्बेडर के लिए API कुंजी आवश्यक है।", + "baseUrlRequired": "इस एम्बेडर के लिए बेस यूआरएल आवश्यक है" }, "serviceFactory": { "openAiConfigMissing": "एम्बेडर बनाने के लिए OpenAI कॉन्फ़िगरेशन गायब है", diff --git a/src/i18n/locales/id/embeddings.json b/src/i18n/locales/id/embeddings.json index 3c07852fe2..e78d39d1ab 100644 --- a/src/i18n/locales/id/embeddings.json +++ b/src/i18n/locales/id/embeddings.json @@ -37,7 +37,9 @@ "invalidApiKey": "Kunci API tidak valid. Silakan periksa konfigurasi kunci API Anda.", "invalidBaseUrl": "URL dasar tidak valid. Silakan periksa konfigurasi URL Anda.", "invalidModel": "Model tidak valid. Silakan periksa konfigurasi model Anda.", - "invalidResponse": "Respons tidak valid dari layanan embedder. Silakan periksa konfigurasi Anda." + "invalidResponse": "Respons tidak valid dari layanan embedder. Silakan periksa konfigurasi Anda.", + "apiKeyRequired": "Kunci API diperlukan untuk embedder ini", + "baseUrlRequired": "URL dasar diperlukan untuk embedder ini" }, "serviceFactory": { "openAiConfigMissing": "Konfigurasi OpenAI tidak ada untuk membuat embedder", diff --git a/src/i18n/locales/it/embeddings.json b/src/i18n/locales/it/embeddings.json index ae84909911..679b17a25e 100644 --- a/src/i18n/locales/it/embeddings.json +++ b/src/i18n/locales/it/embeddings.json @@ -37,7 +37,9 @@ "invalidApiKey": "Chiave API non valida. Controlla la configurazione della tua chiave API.", "invalidBaseUrl": "URL di base non valido. Controlla la configurazione del tuo URL.", "invalidModel": "Modello non valido. Controlla la configurazione del tuo modello.", - "invalidResponse": "Risposta non valida dal servizio embedder. Controlla la tua configurazione." + "invalidResponse": "Risposta non valida dal servizio embedder. Controlla la tua configurazione.", + "apiKeyRequired": "È richiesta una chiave API per questo embedder", + "baseUrlRequired": "È richiesto un URL di base per questo embedder" }, "serviceFactory": { "openAiConfigMissing": "Configurazione OpenAI mancante per la creazione dell'embedder", diff --git a/src/i18n/locales/ja/embeddings.json b/src/i18n/locales/ja/embeddings.json index 042ae6930e..89136eb1cc 100644 --- a/src/i18n/locales/ja/embeddings.json +++ b/src/i18n/locales/ja/embeddings.json @@ -37,7 +37,9 @@ "invalidApiKey": "無効なAPIキーです。APIキー構成を確認してください。", "invalidBaseUrl": "無効なベースURLです。URL構成を確認してください。", "invalidModel": "無効なモデルです。モデル構成を確認してください。", - "invalidResponse": "エンベッダーサービスからの無効な応答です。設定を確認してください。" + "invalidResponse": "エンベッダーサービスからの無効な応答です。設定を確認してください。", + "apiKeyRequired": "このエンベッダーにはAPIキーが必要です。", + "baseUrlRequired": "このエンベッダーにはベースURLが必要です" }, "serviceFactory": { "openAiConfigMissing": "エンベッダー作成のためのOpenAI設定がありません", diff --git a/src/i18n/locales/ko/embeddings.json b/src/i18n/locales/ko/embeddings.json index da3fa67590..7129883ad7 100644 --- a/src/i18n/locales/ko/embeddings.json +++ b/src/i18n/locales/ko/embeddings.json @@ -37,7 +37,9 @@ "invalidApiKey": "잘못된 API 키입니다. API 키 구성을 확인하세요.", "invalidBaseUrl": "잘못된 기본 URL입니다. URL 구성을 확인하세요.", "invalidModel": "잘못된 모델입니다. 모델 구성을 확인하세요.", - "invalidResponse": "임베더 서비스에서 잘못된 응답이 왔습니다. 구성을 확인하세요." + "invalidResponse": "임베더 서비스에서 잘못된 응답이 왔습니다. 구성을 확인하세요.", + "apiKeyRequired": "이 임베더에는 API 키가 필요합니다", + "baseUrlRequired": "이 임베더에는 기본 URL이 필요합니다" }, "serviceFactory": { "openAiConfigMissing": "임베더 생성을 위한 OpenAI 구성이 누락되었습니다", diff --git a/src/i18n/locales/nl/embeddings.json b/src/i18n/locales/nl/embeddings.json index d7b68b336b..ede20774ac 100644 --- a/src/i18n/locales/nl/embeddings.json +++ b/src/i18n/locales/nl/embeddings.json @@ -37,7 +37,9 @@ "invalidApiKey": "Ongeldige API-sleutel. Controleer je API-sleutelconfiguratie.", "invalidBaseUrl": "Ongeldige basis-URL. Controleer je URL-configuratie.", "invalidModel": "Ongeldig model. Controleer je modelconfiguratie.", - "invalidResponse": "Ongeldige reactie van embedder-service. Controleer je configuratie." + "invalidResponse": "Ongeldige reactie van embedder-service. Controleer je configuratie.", + "apiKeyRequired": "API-sleutel is vereist voor deze embedder", + "baseUrlRequired": "Basis-URL is vereist voor deze embedder" }, "serviceFactory": { "openAiConfigMissing": "OpenAI-configuratie ontbreekt voor het maken van embedder", diff --git a/src/i18n/locales/pl/embeddings.json b/src/i18n/locales/pl/embeddings.json index 49b27c51d4..70279021bd 100644 --- a/src/i18n/locales/pl/embeddings.json +++ b/src/i18n/locales/pl/embeddings.json @@ -37,7 +37,9 @@ "invalidApiKey": "Nieprawidłowy klucz API. Sprawdź konfigurację klucza API.", "invalidBaseUrl": "Nieprawidłowy podstawowy adres URL. Sprawdź konfigurację adresu URL.", "invalidModel": "Nieprawidłowy model. Sprawdź konfigurację modelu.", - "invalidResponse": "Nieprawidłowa odpowiedź z usługi embedder. Sprawdź swoją konfigurację." + "invalidResponse": "Nieprawidłowa odpowiedź z usługi embedder. Sprawdź swoją konfigurację.", + "apiKeyRequired": "Klucz API jest wymagany dla tego embeddera", + "baseUrlRequired": "Podstawowy adres URL jest wymagany dla tego embeddera" }, "serviceFactory": { "openAiConfigMissing": "Brak konfiguracji OpenAI do utworzenia embeddera", diff --git a/src/i18n/locales/pt-BR/embeddings.json b/src/i18n/locales/pt-BR/embeddings.json index 9c88b170d9..aea1bb5007 100644 --- a/src/i18n/locales/pt-BR/embeddings.json +++ b/src/i18n/locales/pt-BR/embeddings.json @@ -37,7 +37,9 @@ "invalidApiKey": "Chave de API inválida. Verifique sua configuração de chave de API.", "invalidBaseUrl": "URL base inválida. Verifique sua configuração de URL.", "invalidModel": "Modelo inválido. Verifique a configuração do seu modelo.", - "invalidResponse": "Resposta inválida do serviço de embedder. Verifique sua configuração." + "invalidResponse": "Resposta inválida do serviço de embedder. Verifique sua configuração.", + "apiKeyRequired": "A chave de API é necessária para este embedder", + "baseUrlRequired": "A URL base é necessária para este embedder" }, "serviceFactory": { "openAiConfigMissing": "Configuração do OpenAI ausente para criação do embedder", diff --git a/src/i18n/locales/ru/embeddings.json b/src/i18n/locales/ru/embeddings.json index eb7c129a01..a724539b76 100644 --- a/src/i18n/locales/ru/embeddings.json +++ b/src/i18n/locales/ru/embeddings.json @@ -37,7 +37,9 @@ "invalidApiKey": "Неверный ключ API. Проверьте конфигурацию ключа API.", "invalidBaseUrl": "Неверный базовый URL. Проверьте конфигурацию URL.", "invalidModel": "Неверная модель. Проверьте конфигурацию модели.", - "invalidResponse": "Неверный ответ от службы embedder. Проверьте вашу конфигурацию." + "invalidResponse": "Неверный ответ от службы embedder. Проверьте вашу конфигурацию.", + "apiKeyRequired": "Для этого встраивателя требуется ключ API", + "baseUrlRequired": "Для этого встраивателя требуется базовый URL" }, "serviceFactory": { "openAiConfigMissing": "Отсутствует конфигурация OpenAI для создания эмбеддера", diff --git a/src/i18n/locales/tr/embeddings.json b/src/i18n/locales/tr/embeddings.json index 223707a880..3e115ce103 100644 --- a/src/i18n/locales/tr/embeddings.json +++ b/src/i18n/locales/tr/embeddings.json @@ -37,7 +37,9 @@ "invalidApiKey": "Geçersiz API anahtarı. Lütfen API anahtarı yapılandırmanızı kontrol edin.", "invalidBaseUrl": "Geçersiz temel URL. Lütfen URL yapılandırmanızı kontrol edin.", "invalidModel": "Geçersiz model. Lütfen model yapılandırmanızı kontrol edin.", - "invalidResponse": "Embedder hizmetinden geçersiz yanıt. Lütfen yapılandırmanızı kontrol edin." + "invalidResponse": "Embedder hizmetinden geçersiz yanıt. Lütfen yapılandırmanızı kontrol edin.", + "apiKeyRequired": "Bu gömücü için API anahtarı gereklidir", + "baseUrlRequired": "Bu gömücü için temel URL gereklidir" }, "serviceFactory": { "openAiConfigMissing": "Gömücü oluşturmak için OpenAI yapılandırması eksik", diff --git a/src/i18n/locales/vi/embeddings.json b/src/i18n/locales/vi/embeddings.json index 859d17fe98..9ef61105fa 100644 --- a/src/i18n/locales/vi/embeddings.json +++ b/src/i18n/locales/vi/embeddings.json @@ -37,7 +37,9 @@ "invalidApiKey": "Khóa API không hợp lệ. Vui lòng kiểm tra cấu hình khóa API của bạn.", "invalidBaseUrl": "URL cơ sở không hợp lệ. Vui lòng kiểm tra cấu hình URL của bạn.", "invalidModel": "Mô hình không hợp lệ. Vui lòng kiểm tra cấu hình mô hình của bạn.", - "invalidResponse": "Phản hồi không hợp lệ từ dịch vụ embedder. Vui lòng kiểm tra cấu hình của bạn." + "invalidResponse": "Phản hồi không hợp lệ từ dịch vụ embedder. Vui lòng kiểm tra cấu hình của bạn.", + "apiKeyRequired": "Cần có khóa API cho trình nhúng này", + "baseUrlRequired": "Cần có URL cơ sở cho trình nhúng này" }, "serviceFactory": { "openAiConfigMissing": "Thiếu cấu hình OpenAI để tạo embedder", diff --git a/src/i18n/locales/zh-CN/embeddings.json b/src/i18n/locales/zh-CN/embeddings.json index 4d40e33818..d3ded6e5a2 100644 --- a/src/i18n/locales/zh-CN/embeddings.json +++ b/src/i18n/locales/zh-CN/embeddings.json @@ -37,7 +37,9 @@ "invalidApiKey": "API 密钥无效。请检查您的 API 密钥配置。", "invalidBaseUrl": "基础 URL 无效。请检查您的 URL 配置。", "invalidModel": "模型无效。请检查您的模型配置。", - "invalidResponse": "嵌入服务响应无效。请检查您的配置。" + "invalidResponse": "嵌入服务响应无效。请检查您的配置。", + "apiKeyRequired": "此嵌入器需要 API 密钥", + "baseUrlRequired": "此嵌入器需要基础 URL" }, "serviceFactory": { "openAiConfigMissing": "创建嵌入器缺少 OpenAI 配置", diff --git a/src/i18n/locales/zh-TW/embeddings.json b/src/i18n/locales/zh-TW/embeddings.json index 10c87c18b2..5ab5dcb292 100644 --- a/src/i18n/locales/zh-TW/embeddings.json +++ b/src/i18n/locales/zh-TW/embeddings.json @@ -37,7 +37,9 @@ "invalidApiKey": "無效的 API 金鑰。請檢查您的 API 金鑰組態。", "invalidBaseUrl": "無效的基礎 URL。請檢查您的 URL 組態。", "invalidModel": "無效的模型。請檢查您的模型組態。", - "invalidResponse": "內嵌服務回應無效。請檢查您的組態。" + "invalidResponse": "內嵌服務回應無效。請檢查您的組態。", + "apiKeyRequired": "此嵌入器需要 API 金鑰", + "baseUrlRequired": "此嵌入器需要基礎 URL" }, "serviceFactory": { "openAiConfigMissing": "建立嵌入器缺少 OpenAI 設定", diff --git a/src/services/code-index/embedders/__tests__/gemini.spec.ts b/src/services/code-index/embedders/__tests__/gemini.spec.ts index 856f5bf7c6..3fe4b1421b 100644 --- a/src/services/code-index/embedders/__tests__/gemini.spec.ts +++ b/src/services/code-index/embedders/__tests__/gemini.spec.ts @@ -34,9 +34,9 @@ describe("GeminiEmbedder", () => { it("should throw error when API key is not provided", () => { // Act & Assert - expect(() => new GeminiEmbedder("")).toThrow("API key is required for Gemini embedder") - expect(() => new GeminiEmbedder(null as any)).toThrow("API key is required for Gemini embedder") - expect(() => new GeminiEmbedder(undefined as any)).toThrow("API key is required for Gemini embedder") + expect(() => new GeminiEmbedder("")).toThrow("validation.apiKeyRequired") + expect(() => new GeminiEmbedder(null as any)).toThrow("validation.apiKeyRequired") + expect(() => new GeminiEmbedder(undefined as any)).toThrow("validation.apiKeyRequired") }) }) diff --git a/src/services/code-index/embedders/__tests__/openai-compatible.spec.ts b/src/services/code-index/embedders/__tests__/openai-compatible.spec.ts index d1f45d75ca..f3e811acf0 100644 --- a/src/services/code-index/embedders/__tests__/openai-compatible.spec.ts +++ b/src/services/code-index/embedders/__tests__/openai-compatible.spec.ts @@ -80,19 +80,19 @@ describe("OpenAICompatibleEmbedder", () => { it("should throw error when baseUrl is missing", () => { expect(() => new OpenAICompatibleEmbedder("", testApiKey, testModelId)).toThrow( - "Base URL is required for OpenAI Compatible embedder", + "embeddings:validation.baseUrlRequired", ) }) it("should throw error when apiKey is missing", () => { expect(() => new OpenAICompatibleEmbedder(testBaseUrl, "", testModelId)).toThrow( - "API key is required for OpenAI Compatible embedder", + "embeddings:validation.apiKeyRequired", ) }) it("should throw error when both baseUrl and apiKey are missing", () => { expect(() => new OpenAICompatibleEmbedder("", "", testModelId)).toThrow( - "Base URL is required for OpenAI Compatible embedder", + "embeddings:validation.baseUrlRequired", ) }) }) diff --git a/src/services/code-index/embedders/gemini.ts b/src/services/code-index/embedders/gemini.ts index f99ae4c1d7..f03714f3e9 100644 --- a/src/services/code-index/embedders/gemini.ts +++ b/src/services/code-index/embedders/gemini.ts @@ -1,6 +1,7 @@ import { OpenAICompatibleEmbedder } from "./openai-compatible" import { IEmbedder, EmbeddingResponse, EmbedderInfo } from "../interfaces/embedder" import { GEMINI_MAX_ITEM_TOKENS } from "../constants" +import { t } from "../../../i18n" /** * Gemini embedder implementation that wraps the OpenAI Compatible embedder @@ -23,7 +24,7 @@ export class GeminiEmbedder implements IEmbedder { */ constructor(apiKey: string) { if (!apiKey) { - throw new Error("API key is required for Gemini embedder") + throw new Error(t("embeddings:validation.apiKeyRequired")) } // Create an OpenAI Compatible embedder with Gemini's fixed configuration diff --git a/src/services/code-index/embedders/openai-compatible.ts b/src/services/code-index/embedders/openai-compatible.ts index b378bbe7ac..b1fd976b0a 100644 --- a/src/services/code-index/embedders/openai-compatible.ts +++ b/src/services/code-index/embedders/openai-compatible.ts @@ -45,10 +45,10 @@ export class OpenAICompatibleEmbedder implements IEmbedder { */ constructor(baseUrl: string, apiKey: string, modelId?: string, maxItemTokens?: number) { if (!baseUrl) { - throw new Error("Base URL is required for OpenAI Compatible embedder") + throw new Error(t("embeddings:validation.baseUrlRequired")) } if (!apiKey) { - throw new Error("API key is required for OpenAI Compatible embedder") + throw new Error(t("embeddings:validation.apiKeyRequired")) } this.baseUrl = baseUrl diff --git a/src/services/code-index/manager.ts b/src/services/code-index/manager.ts index bd782da84c..6fa86b4e4f 100644 --- a/src/services/code-index/manager.ts +++ b/src/services/code-index/manager.ts @@ -263,15 +263,8 @@ export class CodeIndexManager { const validationResult = await this._serviceFactory.validateEmbedder(embedder) if (!validationResult.valid) { const errorMessage = validationResult.error || "Embedder configuration validation failed" - // Always attempt translation, use original as fallback - let translatedMessage = t(errorMessage) - // If translation returns a different value (stripped namespace), use original - if (translatedMessage !== errorMessage && !translatedMessage.includes(":")) { - translatedMessage = errorMessage - } - - this._stateManager.setSystemState("Error", translatedMessage) - throw new Error(translatedMessage) + this._stateManager.setSystemState("Error", errorMessage) + throw new Error(errorMessage) } // (Re)Initialize orchestrator diff --git a/src/services/code-index/shared/validation-helpers.ts b/src/services/code-index/shared/validation-helpers.ts index c210c8ec17..0182264579 100644 --- a/src/services/code-index/shared/validation-helpers.ts +++ b/src/services/code-index/shared/validation-helpers.ts @@ -28,16 +28,16 @@ export function getErrorMessageForStatus(status: number | undefined, embedderTyp switch (status) { case 401: case 403: - return "embeddings:validation.authenticationFailed" + return t("embeddings:validation.authenticationFailed") case 404: return embedderType === "openai" - ? "embeddings:validation.modelNotAvailable" - : "embeddings:validation.invalidEndpoint" + ? t("embeddings:validation.modelNotAvailable") + : t("embeddings:validation.invalidEndpoint") case 429: - return "embeddings:validation.serviceUnavailable" + return t("embeddings:validation.serviceUnavailable") default: if (status && status >= 400 && status < 600) { - return "embeddings:validation.configurationError" + return t("embeddings:validation.configurationError") } return undefined } @@ -138,11 +138,11 @@ export function handleValidationError( errorMessage.includes("HTTP 0:") || errorMessage === "No response" ) { - return { valid: false, error: "embeddings:validation.connectionFailed" } + return { valid: false, error: t("embeddings:validation.connectionFailed") } } if (errorMessage.includes("Failed to parse response JSON")) { - return { valid: false, error: "embeddings:validation.invalidResponse" } + return { valid: false, error: t("embeddings:validation.invalidResponse") } } } @@ -152,7 +152,7 @@ export function handleValidationError( } // Fallback to generic error - return { valid: false, error: "embeddings:validation.configurationError" } + return { valid: false, error: t("embeddings:validation.configurationError") } } /** From 406b366f4aa7f4cf94b79754e7d22aef6231d0c0 Mon Sep 17 00:00:00 2001 From: sensei-woo <168141084+sensei-woo@users.noreply.github.com> Date: Thu, 10 Jul 2025 20:00:31 -0400 Subject: [PATCH 27/27] Update ChatTextArea.tsx (#5586) * Update ChatTextArea.tsx fix issue 5583 * refactor(ui): move TTS stop button to bottom control bar --------- Co-authored-by: Daniel Riccio --- .../src/components/chat/ChatTextArea.tsx | 32 ++++++++++++------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/webview-ui/src/components/chat/ChatTextArea.tsx b/webview-ui/src/components/chat/ChatTextArea.tsx index ee622239e2..a38b4538d0 100644 --- a/webview-ui/src/components/chat/ChatTextArea.tsx +++ b/webview-ui/src/components/chat/ChatTextArea.tsx @@ -950,18 +950,6 @@ const ChatTextArea = forwardRef( onScroll={() => updateHighlights()} /> - {isTtsPlaying && ( - - - - )} -
+ {isTtsPlaying && ( + + + + )}