From 0500894b34e7d6650635ef836eb6eb0577d0a390 Mon Sep 17 00:00:00 2001 From: "roomote[bot]" <219738659+roomote[bot]@users.noreply.github.com> Date: Mon, 21 Jul 2025 19:37:42 -0400 Subject: [PATCH 01/92] fix: sort symlinked rules files by symlink names, not target names (#5903) * fix: sort symlinked rules files alphabetically - Add alphabetical sorting to readTextFilesFromDirectory function - Sort by basename of filename (case-insensitive) for consistent order - Fixes issue where symlinked rules were read in random order - Add test case to verify alphabetical sorting behavior Fixes #4131 * chore: remove solution-indicating comment per PR feedback * fix: sort symlinks by their symlink names, not target names - Modified readTextFilesFromDirectory to store both original symlink path and resolved target path - Updated resolveDirectoryEntry and resolveSymLink to track both paths - Sort files by original path (symlink name) but read content from resolved path - Added test to verify symlinks are sorted by their names, not their target names - This ensures consistent alphabetical ordering when using symlinks in rules directories --------- Co-authored-by: Roo Code --- .../__tests__/custom-instructions.spec.ts | 151 ++++++++++++++++++ .../prompts/sections/custom-instructions.ts | 55 ++++--- 2 files changed, 188 insertions(+), 18 deletions(-) diff --git a/src/core/prompts/sections/__tests__/custom-instructions.spec.ts b/src/core/prompts/sections/__tests__/custom-instructions.spec.ts index 9c8e003143..f6fb8ea1f4 100644 --- a/src/core/prompts/sections/__tests__/custom-instructions.spec.ts +++ b/src/core/prompts/sections/__tests__/custom-instructions.spec.ts @@ -1033,6 +1033,157 @@ describe("Rules directory reading", () => { expect(result).toContain("content of file3") }) + it("should return files in alphabetical order by filename", async () => { + // Simulate .roo/rules directory exists + statMock.mockResolvedValueOnce({ + isDirectory: vi.fn().mockReturnValue(true), + } as any) + + // Simulate listing files in non-alphabetical order to test sorting + readdirMock.mockResolvedValueOnce([ + { name: "zebra.txt", isFile: () => true, parentPath: "/fake/path/.roo/rules" }, + { name: "alpha.txt", isFile: () => true, parentPath: "/fake/path/.roo/rules" }, + { name: "Beta.txt", isFile: () => true, parentPath: "/fake/path/.roo/rules" }, // Test case-insensitive sorting + ] as any) + + statMock.mockImplementation((path) => { + return Promise.resolve({ + isFile: vi.fn().mockReturnValue(true), + }) as any + }) + + readFileMock.mockImplementation((filePath: PathLike) => { + const pathStr = filePath.toString() + const normalizedPath = pathStr.replace(/\\/g, "/") + if (normalizedPath === "/fake/path/.roo/rules/zebra.txt") { + return Promise.resolve("zebra content") + } + if (normalizedPath === "/fake/path/.roo/rules/alpha.txt") { + return Promise.resolve("alpha content") + } + if (normalizedPath === "/fake/path/.roo/rules/Beta.txt") { + return Promise.resolve("beta content") + } + return Promise.reject({ code: "ENOENT" }) + }) + + const result = await loadRuleFiles("/fake/path") + + // Files should appear in alphabetical order: alpha.txt, Beta.txt, zebra.txt + const alphaIndex = result.indexOf("alpha content") + const betaIndex = result.indexOf("beta content") + const zebraIndex = result.indexOf("zebra content") + + expect(alphaIndex).toBeLessThan(betaIndex) + expect(betaIndex).toBeLessThan(zebraIndex) + + // Verify the expected file paths are in the result + const expectedAlphaPath = + process.platform === "win32" ? "\\fake\\path\\.roo\\rules\\alpha.txt" : "/fake/path/.roo/rules/alpha.txt" + const expectedBetaPath = + process.platform === "win32" ? "\\fake\\path\\.roo\\rules\\Beta.txt" : "/fake/path/.roo/rules/Beta.txt" + const expectedZebraPath = + process.platform === "win32" ? "\\fake\\path\\.roo\\rules\\zebra.txt" : "/fake/path/.roo/rules/zebra.txt" + + expect(result).toContain(`# Rules from ${expectedAlphaPath}:`) + expect(result).toContain(`# Rules from ${expectedBetaPath}:`) + expect(result).toContain(`# Rules from ${expectedZebraPath}:`) + }) + + it("should sort symlinks by their symlink names, not target names", async () => { + // Reset mocks + statMock.mockReset() + readdirMock.mockReset() + readlinkMock.mockReset() + readFileMock.mockReset() + + // First call: check if .roo/rules directory exists + statMock.mockResolvedValueOnce({ + isDirectory: vi.fn().mockReturnValue(true), + } as any) + + // Simulate listing files with symlinks that point to files with different names + readdirMock.mockResolvedValueOnce([ + { + name: "01-first.link", + isFile: () => false, + isSymbolicLink: () => true, + parentPath: "/fake/path/.roo/rules", + }, + { + name: "02-second.link", + isFile: () => false, + isSymbolicLink: () => true, + parentPath: "/fake/path/.roo/rules", + }, + { + name: "03-third.link", + isFile: () => false, + isSymbolicLink: () => true, + parentPath: "/fake/path/.roo/rules", + }, + ] as any) + + // Mock readlink to return target paths that would sort differently than symlink names + readlinkMock + .mockResolvedValueOnce("../../targets/zzz-last.txt") // 01-first.link -> zzz-last.txt + .mockResolvedValueOnce("../../targets/aaa-first.txt") // 02-second.link -> aaa-first.txt + .mockResolvedValueOnce("../../targets/mmm-middle.txt") // 03-third.link -> mmm-middle.txt + + // Set up stat mock for the remaining calls + statMock.mockImplementation((path) => { + const normalizedPath = path.toString().replace(/\\/g, "/") + // Target files exist and are files + if (normalizedPath.endsWith(".txt")) { + return Promise.resolve({ + isFile: vi.fn().mockReturnValue(true), + isDirectory: vi.fn().mockReturnValue(false), + } as any) + } + return Promise.resolve({ + isFile: vi.fn().mockReturnValue(false), + isDirectory: vi.fn().mockReturnValue(false), + } as any) + }) + + readFileMock.mockImplementation((filePath: PathLike) => { + const pathStr = filePath.toString() + const normalizedPath = pathStr.replace(/\\/g, "/") + if (normalizedPath.endsWith("zzz-last.txt")) { + return Promise.resolve("content from zzz-last.txt") + } + if (normalizedPath.endsWith("aaa-first.txt")) { + return Promise.resolve("content from aaa-first.txt") + } + if (normalizedPath.endsWith("mmm-middle.txt")) { + return Promise.resolve("content from mmm-middle.txt") + } + return Promise.reject({ code: "ENOENT" }) + }) + + const result = await loadRuleFiles("/fake/path") + + // Content should appear in order of symlink names (01-first, 02-second, 03-third) + // NOT in order of target names (aaa-first, mmm-middle, zzz-last) + const firstIndex = result.indexOf("content from zzz-last.txt") // from 01-first.link + const secondIndex = result.indexOf("content from aaa-first.txt") // from 02-second.link + const thirdIndex = result.indexOf("content from mmm-middle.txt") // from 03-third.link + + // All content should be found + expect(firstIndex).toBeGreaterThan(-1) + expect(secondIndex).toBeGreaterThan(-1) + expect(thirdIndex).toBeGreaterThan(-1) + + // And they should be in the order of symlink names, not target names + expect(firstIndex).toBeLessThan(secondIndex) + expect(secondIndex).toBeLessThan(thirdIndex) + + // Verify the target paths are shown (not symlink paths) + expect(result).toContain("zzz-last.txt") + expect(result).toContain("aaa-first.txt") + expect(result).toContain("mmm-middle.txt") + }) + it("should handle empty file list gracefully", async () => { // Simulate .roo/rules directory exists statMock.mockResolvedValueOnce({ diff --git a/src/core/prompts/sections/custom-instructions.ts b/src/core/prompts/sections/custom-instructions.ts index 3c8558a57f..71613ff94a 100644 --- a/src/core/prompts/sections/custom-instructions.ts +++ b/src/core/prompts/sections/custom-instructions.ts @@ -44,7 +44,7 @@ const MAX_DEPTH = 5 async function resolveDirectoryEntry( entry: Dirent, dirPath: string, - filePaths: string[], + fileInfo: Array<{ originalPath: string; resolvedPath: string }>, depth: number, ): Promise { // Avoid cyclic symlinks @@ -54,44 +54,49 @@ async function resolveDirectoryEntry( const fullPath = path.resolve(entry.parentPath || dirPath, entry.name) if (entry.isFile()) { - // Regular file - filePaths.push(fullPath) + // Regular file - both original and resolved paths are the same + fileInfo.push({ originalPath: fullPath, resolvedPath: fullPath }) } else if (entry.isSymbolicLink()) { // Await the resolution of the symbolic link - await resolveSymLink(fullPath, filePaths, depth + 1) + await resolveSymLink(fullPath, fileInfo, depth + 1) } } /** * Recursively resolve a symbolic link and collect file paths */ -async function resolveSymLink(fullPath: string, filePaths: string[], depth: number): Promise { +async function resolveSymLink( + symlinkPath: string, + fileInfo: Array<{ originalPath: string; resolvedPath: string }>, + depth: number, +): Promise { // Avoid cyclic symlinks if (depth > MAX_DEPTH) { return } try { // Get the symlink target - const linkTarget = await fs.readlink(fullPath) + const linkTarget = await fs.readlink(symlinkPath) // Resolve the target path (relative to the symlink location) - const resolvedTarget = path.resolve(path.dirname(fullPath), linkTarget) + const resolvedTarget = path.resolve(path.dirname(symlinkPath), linkTarget) // Check if the target is a file const stats = await fs.stat(resolvedTarget) if (stats.isFile()) { - filePaths.push(resolvedTarget) + // For symlinks to files, store the symlink path as original and target as resolved + fileInfo.push({ originalPath: symlinkPath, resolvedPath: resolvedTarget }) } else if (stats.isDirectory()) { const anotherEntries = await fs.readdir(resolvedTarget, { withFileTypes: true, recursive: true }) // Collect promises for recursive calls within the directory const directoryPromises: Promise[] = [] for (const anotherEntry of anotherEntries) { - directoryPromises.push(resolveDirectoryEntry(anotherEntry, resolvedTarget, filePaths, depth + 1)) + directoryPromises.push(resolveDirectoryEntry(anotherEntry, resolvedTarget, fileInfo, depth + 1)) } // Wait for all entries in the resolved directory to be processed await Promise.all(directoryPromises) } else if (stats.isSymbolicLink()) { // Handle nested symlinks by awaiting the recursive call - await resolveSymLink(resolvedTarget, filePaths, depth + 1) + await resolveSymLink(resolvedTarget, fileInfo, depth + 1) } } catch (err) { // Skip invalid symlinks @@ -106,29 +111,31 @@ async function readTextFilesFromDirectory(dirPath: string): Promise = [] // Collect promises for the initial resolution calls const initialPromises: Promise[] = [] for (const entry of entries) { - initialPromises.push(resolveDirectoryEntry(entry, dirPath, filePaths, 0)) + initialPromises.push(resolveDirectoryEntry(entry, dirPath, fileInfo, 0)) } // Wait for all asynchronous operations (including recursive ones) to complete await Promise.all(initialPromises) const fileContents = await Promise.all( - filePaths.map(async (file) => { + fileInfo.map(async ({ originalPath, resolvedPath }) => { try { // Check if it's a file (not a directory) - const stats = await fs.stat(file) + const stats = await fs.stat(resolvedPath) if (stats.isFile()) { // Filter out cache files and system files that shouldn't be in rules - if (!shouldIncludeRuleFile(file)) { + if (!shouldIncludeRuleFile(resolvedPath)) { return null } - const content = await safeReadFile(file) - return { filename: file, content } + const content = await safeReadFile(resolvedPath) + // Use resolvedPath for display to maintain existing behavior + return { filename: resolvedPath, content, sortKey: originalPath } } return null } catch (err) { @@ -138,7 +145,19 @@ async function readTextFilesFromDirectory(dirPath: string): Promise item !== null) + const filteredFiles = fileContents.filter( + (item): item is { filename: string; content: string; sortKey: string } => item !== null, + ) + + // Sort files alphabetically by the original filename (case-insensitive) to ensure consistent order + // For symlinks, this will use the symlink name, not the target name + return filteredFiles + .sort((a, b) => { + const filenameA = path.basename(a.sortKey).toLowerCase() + const filenameB = path.basename(b.sortKey).toLowerCase() + return filenameA.localeCompare(filenameB) + }) + .map(({ filename, content }) => ({ filename, content })) } catch (err) { return [] } From 8334f0869ff8f576d299e065d2cc729d2746ecab Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Mon, 21 Jul 2025 22:26:39 -0400 Subject: [PATCH 02/92] Update the max_tokens fallback logic in the sliding window (#5993) --- .../sliding-window/__tests__/sliding-window.spec.ts | 12 ++++++------ src/core/sliding-window/index.ts | 3 ++- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/core/sliding-window/__tests__/sliding-window.spec.ts b/src/core/sliding-window/__tests__/sliding-window.spec.ts index 3bda5351d4..393d50307e 100644 --- a/src/core/sliding-window/__tests__/sliding-window.spec.ts +++ b/src/core/sliding-window/__tests__/sliding-window.spec.ts @@ -1103,9 +1103,9 @@ describe("Sliding Window", () => { expect(result2.prevContextTokens).toBe(50001) }) - it("should use 20% of context window as buffer when maxTokens is undefined", async () => { + it("should use ANTHROPIC_DEFAULT_MAX_TOKENS as buffer when maxTokens is undefined", async () => { const modelInfo = createModelInfo(100000, undefined) - // Max tokens = 100000 - (100000 * 0.2) = 80000 + // Max tokens = 100000 - ANTHROPIC_DEFAULT_MAX_TOKENS = 100000 - 8192 = 91808 // Create messages with very small content in the last one to avoid token overflow const messagesWithSmallContent = [ @@ -1117,7 +1117,7 @@ describe("Sliding Window", () => { // Below max tokens and buffer - no truncation const result1 = await truncateConversationIfNeeded({ messages: messagesWithSmallContent, - totalTokens: 69999, // Well below threshold + dynamic buffer + totalTokens: 81807, // Well below threshold + dynamic buffer (91808 - 10000 = 81808) contextWindow: modelInfo.contextWindow, maxTokens: modelInfo.maxTokens, apiHandler: mockApiHandler, @@ -1132,13 +1132,13 @@ describe("Sliding Window", () => { messages: messagesWithSmallContent, summary: "", cost: 0, - prevContextTokens: 69999, + prevContextTokens: 81807, }) // Above max tokens - truncate const result2 = await truncateConversationIfNeeded({ messages: messagesWithSmallContent, - totalTokens: 80001, // Above threshold + totalTokens: 81809, // Above threshold (81808) contextWindow: modelInfo.contextWindow, maxTokens: modelInfo.maxTokens, apiHandler: mockApiHandler, @@ -1153,7 +1153,7 @@ describe("Sliding Window", () => { expect(result2.messages.length).toBe(3) // Truncated with 0.5 fraction expect(result2.summary).toBe("") expect(result2.cost).toBe(0) - expect(result2.prevContextTokens).toBe(80001) + expect(result2.prevContextTokens).toBe(81809) }) it("should handle small context windows appropriately", async () => { diff --git a/src/core/sliding-window/index.ts b/src/core/sliding-window/index.ts index ae26f51a52..1e518c9a56 100644 --- a/src/core/sliding-window/index.ts +++ b/src/core/sliding-window/index.ts @@ -5,6 +5,7 @@ import { TelemetryService } from "@roo-code/telemetry" import { ApiHandler } from "../../api" import { MAX_CONDENSE_THRESHOLD, MIN_CONDENSE_THRESHOLD, summarizeConversation, SummarizeResponse } from "../condense" import { ApiMessage } from "../task-persistence/apiMessages" +import { ANTHROPIC_DEFAULT_MAX_TOKENS } from "@roo-code/types" /** * Default percentage of the context window to use as a buffer when deciding when to truncate @@ -105,7 +106,7 @@ export async function truncateConversationIfNeeded({ let error: string | undefined let cost = 0 // Calculate the maximum tokens reserved for response - const reservedTokens = maxTokens || contextWindow * 0.2 + const reservedTokens = maxTokens || ANTHROPIC_DEFAULT_MAX_TOKENS // Estimate tokens for the last message (which is always a user message) const lastMessage = messages[messages.length - 1] From 7e34fbcb0c1a808745b3ac0a2d933b55e5979e1c Mon Sep 17 00:00:00 2001 From: Daniel <57051444+daniel-lxs@users.noreply.github.com> Date: Mon, 21 Jul 2025 21:27:20 -0500 Subject: [PATCH 03/92] fix: add bedrock to ANTHROPIC_STYLE_PROVIDERS and restore vertex Claude model checking (#6019) --- .../src/__tests__/provider-settings.test.ts | 34 ++++++------------- packages/types/src/provider-settings.ts | 11 ++---- 2 files changed, 14 insertions(+), 31 deletions(-) diff --git a/packages/types/src/__tests__/provider-settings.test.ts b/packages/types/src/__tests__/provider-settings.test.ts index 87c5bbcc1c..8277320289 100644 --- a/packages/types/src/__tests__/provider-settings.test.ts +++ b/packages/types/src/__tests__/provider-settings.test.ts @@ -12,6 +12,12 @@ describe("getApiProtocol", () => { expect(getApiProtocol("claude-code")).toBe("anthropic") expect(getApiProtocol("claude-code", "some-model")).toBe("anthropic") }) + + it("should return 'anthropic' for bedrock provider", () => { + expect(getApiProtocol("bedrock")).toBe("anthropic") + expect(getApiProtocol("bedrock", "gpt-4")).toBe("anthropic") + expect(getApiProtocol("bedrock", "claude-3-opus")).toBe("anthropic") + }) }) describe("Vertex provider with Claude models", () => { @@ -27,25 +33,14 @@ describe("getApiProtocol", () => { expect(getApiProtocol("vertex", "gemini-pro")).toBe("openai") expect(getApiProtocol("vertex", "llama-2")).toBe("openai") }) - }) - describe("Bedrock provider with Claude models", () => { - it("should return 'anthropic' for bedrock provider with claude models", () => { - expect(getApiProtocol("bedrock", "claude-3-opus")).toBe("anthropic") - expect(getApiProtocol("bedrock", "Claude-3-Sonnet")).toBe("anthropic") - expect(getApiProtocol("bedrock", "CLAUDE-instant")).toBe("anthropic") - expect(getApiProtocol("bedrock", "anthropic.claude-v2")).toBe("anthropic") - }) - - it("should return 'openai' for bedrock provider with non-claude models", () => { - expect(getApiProtocol("bedrock", "gpt-4")).toBe("openai") - expect(getApiProtocol("bedrock", "titan-text")).toBe("openai") - expect(getApiProtocol("bedrock", "llama-2")).toBe("openai") + it("should return 'openai' for vertex provider without model", () => { + expect(getApiProtocol("vertex")).toBe("openai") }) }) - describe("Other providers with Claude models", () => { - it("should return 'openai' for non-vertex/bedrock providers with claude models", () => { + describe("Other providers", () => { + it("should return 'openai' for non-anthropic providers regardless of model", () => { expect(getApiProtocol("openrouter", "claude-3-opus")).toBe("openai") expect(getApiProtocol("openai", "claude-3-sonnet")).toBe("openai") expect(getApiProtocol("litellm", "claude-instant")).toBe("openai") @@ -59,20 +54,13 @@ describe("getApiProtocol", () => { expect(getApiProtocol(undefined, "claude-3-opus")).toBe("openai") }) - it("should return 'openai' when model is undefined", () => { - expect(getApiProtocol("openai")).toBe("openai") - expect(getApiProtocol("vertex")).toBe("openai") - expect(getApiProtocol("bedrock")).toBe("openai") - }) - it("should handle empty strings", () => { expect(getApiProtocol("vertex", "")).toBe("openai") - expect(getApiProtocol("bedrock", "")).toBe("openai") }) it("should be case-insensitive for claude detection", () => { expect(getApiProtocol("vertex", "CLAUDE-3-OPUS")).toBe("anthropic") - expect(getApiProtocol("bedrock", "claude-3-opus")).toBe("anthropic") + expect(getApiProtocol("vertex", "claude-3-opus")).toBe("anthropic") expect(getApiProtocol("vertex", "ClAuDe-InStAnT")).toBe("anthropic") }) }) diff --git a/packages/types/src/provider-settings.ts b/packages/types/src/provider-settings.ts index be74ae6bb4..511e803cbd 100644 --- a/packages/types/src/provider-settings.ts +++ b/packages/types/src/provider-settings.ts @@ -301,7 +301,7 @@ export const getModelId = (settings: ProviderSettings): string | undefined => { } // Providers that use Anthropic-style API protocol -export const ANTHROPIC_STYLE_PROVIDERS: ProviderName[] = ["anthropic", "claude-code"] +export const ANTHROPIC_STYLE_PROVIDERS: ProviderName[] = ["anthropic", "claude-code", "bedrock"] // Helper function to determine API protocol for a provider and model export const getApiProtocol = (provider: ProviderName | undefined, modelId?: string): "anthropic" | "openai" => { @@ -310,13 +310,8 @@ export const getApiProtocol = (provider: ProviderName | undefined, modelId?: str return "anthropic" } - // For vertex and bedrock providers, check if the model ID contains "claude" (case-insensitive) - if ( - provider && - (provider === "vertex" || provider === "bedrock") && - modelId && - modelId.toLowerCase().includes("claude") - ) { + // For vertex provider, check if the model ID contains "claude" (case-insensitive) + if (provider && provider === "vertex" && modelId && modelId.toLowerCase().includes("claude")) { return "anthropic" } From b1bc085aa610be19265868bc1657e193884bcd3c Mon Sep 17 00:00:00 2001 From: "roomote[bot]" <219738659+roomote[bot]@users.noreply.github.com> Date: Mon, 21 Jul 2025 23:27:28 -0400 Subject: [PATCH 04/92] Add todo list tool enable checkbox to provider advanced settings (#6032) Co-authored-by: Roo Code Co-authored-by: Daniel Riccio Co-authored-by: Matt Rubens --- packages/types/src/provider-settings.ts | 1 + src/core/config/ProviderSettingsManager.ts | 21 +++++ .../__tests__/ProviderSettingsManager.spec.ts | 43 +++++++++ .../architect-mode-prompt.snap | 2 + .../mcp-server-creation-disabled.snap | 2 + .../mcp-server-creation-enabled.snap | 2 + .../partial-reads-enabled.snap | 2 + .../consistent-system-prompt.snap | 2 + .../with-computer-use-support.snap | 2 + .../with-diff-enabled-false.snap | 2 + .../system-prompt/with-diff-enabled-true.snap | 2 + .../with-diff-enabled-undefined.snap | 2 + .../with-different-viewport-size.snap | 2 + .../system-prompt/with-mcp-hub-provided.snap | 2 + .../system-prompt/with-undefined-mcp-hub.snap | 2 + .../prompts/__tests__/system-prompt.spec.ts | 90 ++++++++++++++++++- .../prompts/sections/custom-instructions.ts | 2 +- src/core/prompts/system.ts | 4 +- src/core/prompts/tools/index.ts | 5 ++ src/shared/modes.ts | 2 +- .../src/components/settings/ApiOptions.tsx | 5 ++ .../settings/TodoListSettingsControl.tsx | 35 ++++++++ .../settings/__tests__/ApiOptions.spec.tsx | 26 ++++++ .../TodoListSettingsControl.spec.tsx | 77 ++++++++++++++++ webview-ui/src/i18n/locales/ca/settings.json | 4 + webview-ui/src/i18n/locales/de/settings.json | 4 + webview-ui/src/i18n/locales/en/settings.json | 4 + webview-ui/src/i18n/locales/es/settings.json | 4 + webview-ui/src/i18n/locales/fr/settings.json | 4 + webview-ui/src/i18n/locales/hi/settings.json | 4 + webview-ui/src/i18n/locales/id/settings.json | 4 + webview-ui/src/i18n/locales/it/settings.json | 4 + webview-ui/src/i18n/locales/ja/settings.json | 4 + webview-ui/src/i18n/locales/ko/settings.json | 4 + webview-ui/src/i18n/locales/nl/settings.json | 4 + webview-ui/src/i18n/locales/pl/settings.json | 4 + .../src/i18n/locales/pt-BR/settings.json | 4 + webview-ui/src/i18n/locales/ru/settings.json | 4 + webview-ui/src/i18n/locales/tr/settings.json | 4 + webview-ui/src/i18n/locales/vi/settings.json | 4 + .../src/i18n/locales/zh-CN/settings.json | 4 + .../src/i18n/locales/zh-TW/settings.json | 4 + 42 files changed, 402 insertions(+), 5 deletions(-) create mode 100644 webview-ui/src/components/settings/TodoListSettingsControl.tsx create mode 100644 webview-ui/src/components/settings/__tests__/TodoListSettingsControl.spec.tsx diff --git a/packages/types/src/provider-settings.ts b/packages/types/src/provider-settings.ts index 511e803cbd..7df1b27db7 100644 --- a/packages/types/src/provider-settings.ts +++ b/packages/types/src/provider-settings.ts @@ -61,6 +61,7 @@ export const DEFAULT_CONSECUTIVE_MISTAKE_LIMIT = 3 const baseProviderSettingsSchema = z.object({ includeMaxTokens: z.boolean().optional(), diffEnabled: z.boolean().optional(), + todoListEnabled: z.boolean().optional(), fuzzyMatchThreshold: z.number().optional(), modelTemperature: z.number().nullish(), rateLimitSeconds: z.number().optional(), diff --git a/src/core/config/ProviderSettingsManager.ts b/src/core/config/ProviderSettingsManager.ts index 7823a3040a..350c8136f2 100644 --- a/src/core/config/ProviderSettingsManager.ts +++ b/src/core/config/ProviderSettingsManager.ts @@ -28,6 +28,7 @@ export const providerProfilesSchema = z.object({ diffSettingsMigrated: z.boolean().optional(), openAiHeadersMigrated: z.boolean().optional(), consecutiveMistakeLimitMigrated: z.boolean().optional(), + todoListEnabledMigrated: z.boolean().optional(), }) .optional(), }) @@ -51,6 +52,7 @@ export class ProviderSettingsManager { diffSettingsMigrated: true, // Mark as migrated on fresh installs openAiHeadersMigrated: true, // Mark as migrated on fresh installs consecutiveMistakeLimitMigrated: true, // Mark as migrated on fresh installs + todoListEnabledMigrated: true, // Mark as migrated on fresh installs }, } @@ -117,6 +119,7 @@ export class ProviderSettingsManager { diffSettingsMigrated: false, openAiHeadersMigrated: false, consecutiveMistakeLimitMigrated: false, + todoListEnabledMigrated: false, } // Initialize with default values isDirty = true } @@ -145,6 +148,12 @@ export class ProviderSettingsManager { isDirty = true } + if (!providerProfiles.migrations.todoListEnabledMigrated) { + await this.migrateTodoListEnabled(providerProfiles) + providerProfiles.migrations.todoListEnabledMigrated = true + isDirty = true + } + if (isDirty) { await this.store(providerProfiles) } @@ -250,6 +259,18 @@ export class ProviderSettingsManager { } } + private async migrateTodoListEnabled(providerProfiles: ProviderProfiles) { + try { + for (const [_name, apiConfig] of Object.entries(providerProfiles.apiConfigs)) { + if (apiConfig.todoListEnabled === undefined) { + apiConfig.todoListEnabled = true + } + } + } catch (error) { + console.error(`[MigrateTodoListEnabled] Failed to migrate todo list enabled setting:`, error) + } + } + /** * List all available configs with metadata. */ diff --git a/src/core/config/__tests__/ProviderSettingsManager.spec.ts b/src/core/config/__tests__/ProviderSettingsManager.spec.ts index 6d24c63101..e52c1974b6 100644 --- a/src/core/config/__tests__/ProviderSettingsManager.spec.ts +++ b/src/core/config/__tests__/ProviderSettingsManager.spec.ts @@ -67,6 +67,7 @@ describe("ProviderSettingsManager", () => { diffSettingsMigrated: true, openAiHeadersMigrated: true, consecutiveMistakeLimitMigrated: true, + todoListEnabledMigrated: true, }, }), ) @@ -186,6 +187,48 @@ describe("ProviderSettingsManager", () => { expect(storedConfig.migrations.consecutiveMistakeLimitMigrated).toEqual(true) }) + it("should call migrateTodoListEnabled if it has not done so already", async () => { + mockSecrets.get.mockResolvedValue( + JSON.stringify({ + currentApiConfigName: "default", + apiConfigs: { + default: { + config: {}, + id: "default", + todoListEnabled: undefined, + }, + test: { + apiProvider: "anthropic", + todoListEnabled: undefined, + }, + existing: { + apiProvider: "anthropic", + // this should not really be possible, unless someone has loaded a hand edited config, + // but we don't overwrite so we'll check that + todoListEnabled: false, + }, + }, + migrations: { + rateLimitSecondsMigrated: true, + diffSettingsMigrated: true, + openAiHeadersMigrated: true, + consecutiveMistakeLimitMigrated: true, + todoListEnabledMigrated: false, + }, + }), + ) + + await providerSettingsManager.initialize() + + // Get the last call to store, which should contain the migrated config + const calls = mockSecrets.store.mock.calls + const storedConfig = JSON.parse(calls[calls.length - 1][1]) + expect(storedConfig.apiConfigs.default.todoListEnabled).toEqual(true) + expect(storedConfig.apiConfigs.test.todoListEnabled).toEqual(true) + expect(storedConfig.apiConfigs.existing.todoListEnabled).toEqual(false) + expect(storedConfig.migrations.todoListEnabledMigrated).toEqual(true) + }) + it("should throw error if secrets storage fails", async () => { mockSecrets.get.mockRejectedValue(new Error("Storage failed")) diff --git a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/architect-mode-prompt.snap b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/architect-mode-prompt.snap index e80857d354..273e43d20b 100644 --- a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/architect-mode-prompt.snap +++ b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/architect-mode-prompt.snap @@ -555,6 +555,8 @@ Mode-specific Instructions: - Focused on a single, well-defined outcome - Clear enough that another mode could execute it independently + **Note:** If the `update_todo_list` tool is not available, write the plan to a markdown file (e.g., `plan.md` or `todo.md`) instead. + 4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished. 5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list. diff --git a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-disabled.snap b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-disabled.snap index e80857d354..273e43d20b 100644 --- a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-disabled.snap +++ b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-disabled.snap @@ -555,6 +555,8 @@ Mode-specific Instructions: - Focused on a single, well-defined outcome - Clear enough that another mode could execute it independently + **Note:** If the `update_todo_list` tool is not available, write the plan to a markdown file (e.g., `plan.md` or `todo.md`) instead. + 4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished. 5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list. diff --git a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-enabled.snap b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-enabled.snap index 4dce0f264a..8e1b90a1cf 100644 --- a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-enabled.snap +++ b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-enabled.snap @@ -623,6 +623,8 @@ Mode-specific Instructions: - Focused on a single, well-defined outcome - Clear enough that another mode could execute it independently + **Note:** If the `update_todo_list` tool is not available, write the plan to a markdown file (e.g., `plan.md` or `todo.md`) instead. + 4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished. 5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list. diff --git a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/partial-reads-enabled.snap b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/partial-reads-enabled.snap index 0d172d756b..7ee1ab207f 100644 --- a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/partial-reads-enabled.snap +++ b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/partial-reads-enabled.snap @@ -560,6 +560,8 @@ Mode-specific Instructions: - Focused on a single, well-defined outcome - Clear enough that another mode could execute it independently + **Note:** If the `update_todo_list` tool is not available, write the plan to a markdown file (e.g., `plan.md` or `todo.md`) instead. + 4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished. 5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list. diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/consistent-system-prompt.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/consistent-system-prompt.snap index e80857d354..273e43d20b 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/consistent-system-prompt.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/consistent-system-prompt.snap @@ -555,6 +555,8 @@ Mode-specific Instructions: - Focused on a single, well-defined outcome - Clear enough that another mode could execute it independently + **Note:** If the `update_todo_list` tool is not available, write the plan to a markdown file (e.g., `plan.md` or `todo.md`) instead. + 4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished. 5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list. diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-computer-use-support.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-computer-use-support.snap index 576bf0ddd5..e23dc220b4 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-computer-use-support.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-computer-use-support.snap @@ -611,6 +611,8 @@ Mode-specific Instructions: - Focused on a single, well-defined outcome - Clear enough that another mode could execute it independently + **Note:** If the `update_todo_list` tool is not available, write the plan to a markdown file (e.g., `plan.md` or `todo.md`) instead. + 4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished. 5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list. diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-false.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-false.snap index e80857d354..273e43d20b 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-false.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-false.snap @@ -555,6 +555,8 @@ Mode-specific Instructions: - Focused on a single, well-defined outcome - Clear enough that another mode could execute it independently + **Note:** If the `update_todo_list` tool is not available, write the plan to a markdown file (e.g., `plan.md` or `todo.md`) instead. + 4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished. 5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list. diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-true.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-true.snap index 6fc7ad69ce..ad90a58850 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-true.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-true.snap @@ -643,6 +643,8 @@ Mode-specific Instructions: - Focused on a single, well-defined outcome - Clear enough that another mode could execute it independently + **Note:** If the `update_todo_list` tool is not available, write the plan to a markdown file (e.g., `plan.md` or `todo.md`) instead. + 4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished. 5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list. diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-undefined.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-undefined.snap index e80857d354..273e43d20b 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-undefined.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-undefined.snap @@ -555,6 +555,8 @@ Mode-specific Instructions: - Focused on a single, well-defined outcome - Clear enough that another mode could execute it independently + **Note:** If the `update_todo_list` tool is not available, write the plan to a markdown file (e.g., `plan.md` or `todo.md`) instead. + 4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished. 5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list. diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-different-viewport-size.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-different-viewport-size.snap index 6ccccedefe..005813848f 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-different-viewport-size.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-different-viewport-size.snap @@ -611,6 +611,8 @@ Mode-specific Instructions: - Focused on a single, well-defined outcome - Clear enough that another mode could execute it independently + **Note:** If the `update_todo_list` tool is not available, write the plan to a markdown file (e.g., `plan.md` or `todo.md`) instead. + 4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished. 5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list. diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-mcp-hub-provided.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-mcp-hub-provided.snap index 4dce0f264a..8e1b90a1cf 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-mcp-hub-provided.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-mcp-hub-provided.snap @@ -623,6 +623,8 @@ Mode-specific Instructions: - Focused on a single, well-defined outcome - Clear enough that another mode could execute it independently + **Note:** If the `update_todo_list` tool is not available, write the plan to a markdown file (e.g., `plan.md` or `todo.md`) instead. + 4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished. 5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list. diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-undefined-mcp-hub.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-undefined-mcp-hub.snap index e80857d354..273e43d20b 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-undefined-mcp-hub.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-undefined-mcp-hub.snap @@ -555,6 +555,8 @@ Mode-specific Instructions: - Focused on a single, well-defined outcome - Clear enough that another mode could execute it independently + **Note:** If the `update_todo_list` tool is not available, write the plan to a markdown file (e.g., `plan.md` or `todo.md`) instead. + 4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished. 5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list. diff --git a/src/core/prompts/__tests__/system-prompt.spec.ts b/src/core/prompts/__tests__/system-prompt.spec.ts index 175f3ba265..7589790efe 100644 --- a/src/core/prompts/__tests__/system-prompt.spec.ts +++ b/src/core/prompts/__tests__/system-prompt.spec.ts @@ -79,7 +79,7 @@ __setMockImplementation( globalCustomInstructions: string, cwd: string, mode: string, - options?: { language?: string }, + options?: { language?: string; rooIgnoreInstructions?: string; settings?: Record }, ) => { const sections = [] @@ -575,6 +575,94 @@ describe("SYSTEM_PROMPT", () => { expect(prompt.indexOf(modes[0].roleDefinition)).toBeLessThan(prompt.indexOf("TOOL USE")) }) + it("should exclude update_todo_list tool when todoListEnabled is false", async () => { + const settings = { + todoListEnabled: false, + } + + const prompt = await SYSTEM_PROMPT( + mockContext, + "/test/path", + false, // supportsComputerUse + undefined, // mcpHub + undefined, // diffStrategy + undefined, // browserViewportSize + defaultModeSlug, // mode + undefined, // customModePrompts + undefined, // customModes + undefined, // globalCustomInstructions + undefined, // diffEnabled + experiments, + true, // enableMcpServerCreation + undefined, // language + undefined, // rooIgnoreInstructions + undefined, // partialReadsEnabled + settings, // settings + ) + + // Should not contain the tool description + expect(prompt).not.toContain("## update_todo_list") + // Mode instructions will still reference the tool with a fallback to markdown + }) + + it("should include update_todo_list tool when todoListEnabled is true", async () => { + const settings = { + todoListEnabled: true, + } + + const prompt = await SYSTEM_PROMPT( + mockContext, + "/test/path", + false, // supportsComputerUse + undefined, // mcpHub + undefined, // diffStrategy + undefined, // browserViewportSize + defaultModeSlug, // mode + undefined, // customModePrompts + undefined, // customModes + undefined, // globalCustomInstructions + undefined, // diffEnabled + experiments, + true, // enableMcpServerCreation + undefined, // language + undefined, // rooIgnoreInstructions + undefined, // partialReadsEnabled + settings, // settings + ) + + expect(prompt).toContain("update_todo_list") + expect(prompt).toContain("## update_todo_list") + }) + + it("should include update_todo_list tool when todoListEnabled is undefined", async () => { + const settings = { + // todoListEnabled not set + } + + const prompt = await SYSTEM_PROMPT( + mockContext, + "/test/path", + false, // supportsComputerUse + undefined, // mcpHub + undefined, // diffStrategy + undefined, // browserViewportSize + defaultModeSlug, // mode + undefined, // customModePrompts + undefined, // customModes + undefined, // globalCustomInstructions + undefined, // diffEnabled + experiments, + true, // enableMcpServerCreation + undefined, // language + undefined, // rooIgnoreInstructions + undefined, // partialReadsEnabled + settings, // settings + ) + + expect(prompt).toContain("update_todo_list") + expect(prompt).toContain("## update_todo_list") + }) + afterAll(() => { vi.restoreAllMocks() }) diff --git a/src/core/prompts/sections/custom-instructions.ts b/src/core/prompts/sections/custom-instructions.ts index 71613ff94a..a78882cc90 100644 --- a/src/core/prompts/sections/custom-instructions.ts +++ b/src/core/prompts/sections/custom-instructions.ts @@ -219,7 +219,7 @@ export async function addCustomInstructions( globalCustomInstructions: string, cwd: string, mode: string, - options: { language?: string; rooIgnoreInstructions?: string } = {}, + options: { language?: string; rooIgnoreInstructions?: string; settings?: Record } = {}, ): Promise { const sections = [] diff --git a/src/core/prompts/system.ts b/src/core/prompts/system.ts index 92653cafd2..ea4f43823e 100644 --- a/src/core/prompts/system.ts +++ b/src/core/prompts/system.ts @@ -119,7 +119,7 @@ ${getSystemInfoSection(cwd)} ${getObjectiveSection(codeIndexManager, experiments)} -${await addCustomInstructions(baseInstructions, globalCustomInstructions || "", cwd, mode, { language: language ?? formatLanguage(vscode.env.language), rooIgnoreInstructions })}` +${await addCustomInstructions(baseInstructions, globalCustomInstructions || "", cwd, mode, { language: language ?? formatLanguage(vscode.env.language), rooIgnoreInstructions, settings })}` return basePrompt } @@ -177,7 +177,7 @@ export const SYSTEM_PROMPT = async ( globalCustomInstructions || "", cwd, mode, - { language: language ?? formatLanguage(vscode.env.language), rooIgnoreInstructions }, + { language: language ?? formatLanguage(vscode.env.language), rooIgnoreInstructions, settings }, ) // For file-based prompts, don't include the tool sections diff --git a/src/core/prompts/tools/index.ts b/src/core/prompts/tools/index.ts index 3fd5a636a4..9f4af7f312 100644 --- a/src/core/prompts/tools/index.ts +++ b/src/core/prompts/tools/index.ts @@ -109,6 +109,11 @@ export function getToolDescriptionsForMode( tools.delete("codebase_search") } + // Conditionally exclude update_todo_list if disabled in settings + if (settings?.todoListEnabled === false) { + tools.delete("update_todo_list") + } + // Map tool descriptions for allowed tools const descriptions = Array.from(tools).map((toolName) => { const descriptionFn = toolDescriptionMap[toolName] diff --git a/src/shared/modes.ts b/src/shared/modes.ts index 9c790236eb..168f4ff867 100644 --- a/src/shared/modes.ts +++ b/src/shared/modes.ts @@ -72,7 +72,7 @@ export const modes: readonly ModeConfig[] = [ description: "Plan and design before implementation", groups: ["read", ["edit", { fileRegex: "\\.md$", description: "Markdown files only" }], "browser", "mcp"], customInstructions: - "1. Do some information gathering (using provided tools) to get more context about the task.\n\n2. You should also ask the user clarifying questions to get a better understanding of the task.\n\n3. Once you've gained more context about the user's request, break down the task into clear, actionable steps and create a todo list using the `update_todo_list` tool. Each todo item should be:\n - Specific and actionable\n - Listed in logical execution order\n - Focused on a single, well-defined outcome\n - Clear enough that another mode could execute it independently\n\n4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished.\n\n5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list.\n\n6. Include Mermaid diagrams if they help clarify complex workflows or system architecture. Please avoid using double quotes (\"\") and parentheses () inside square brackets ([]) in Mermaid diagrams, as this can cause parsing errors.\n\n7. Use the switch_mode tool to request that the user switch to another mode to implement the solution.\n\n**IMPORTANT: Focus on creating clear, actionable todo lists rather than lengthy markdown documents. Use the todo list as your primary planning tool to track and organize the work that needs to be done.**", + "1. Do some information gathering (using provided tools) to get more context about the task.\n\n2. You should also ask the user clarifying questions to get a better understanding of the task.\n\n3. Once you've gained more context about the user's request, break down the task into clear, actionable steps and create a todo list using the `update_todo_list` tool. Each todo item should be:\n - Specific and actionable\n - Listed in logical execution order\n - Focused on a single, well-defined outcome\n - Clear enough that another mode could execute it independently\n\n **Note:** If the `update_todo_list` tool is not available, write the plan to a markdown file (e.g., `plan.md` or `todo.md`) instead.\n\n4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished.\n\n5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list.\n\n6. Include Mermaid diagrams if they help clarify complex workflows or system architecture. Please avoid using double quotes (\"\") and parentheses () inside square brackets ([]) in Mermaid diagrams, as this can cause parsing errors.\n\n7. Use the switch_mode tool to request that the user switch to another mode to implement the solution.\n\n**IMPORTANT: Focus on creating clear, actionable todo lists rather than lengthy markdown documents. Use the todo list as your primary planning tool to track and organize the work that needs to be done.**", }, { slug: "code", diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index 06994b16b9..681268a787 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -78,6 +78,7 @@ import { ModelInfoView } from "./ModelInfoView" import { ApiErrorMessage } from "./ApiErrorMessage" import { ThinkingBudget } from "./ThinkingBudget" import { DiffSettingsControl } from "./DiffSettingsControl" +import { TodoListSettingsControl } from "./TodoListSettingsControl" import { TemperatureControl } from "./TemperatureControl" import { RateLimitSecondsControl } from "./RateLimitSecondsControl" import { ConsecutiveMistakeLimitControl } from "./ConsecutiveMistakeLimitControl" @@ -564,6 +565,10 @@ const ApiOptions = ({ {t("settings:advancedSettings.title")} + setApiConfigurationField(field, value)} + /> void +} + +export const TodoListSettingsControl: React.FC = ({ + todoListEnabled = true, + onChange, +}) => { + const { t } = useAppTranslation() + + const handleTodoListEnabledChange = useCallback( + (e: any) => { + onChange("todoListEnabled", e.target.checked) + }, + [onChange], + ) + + return ( +
+
+ + {t("settings:advanced.todoList.label")} + +
+ {t("settings:advanced.todoList.description")} +
+
+
+ ) +} diff --git a/webview-ui/src/components/settings/__tests__/ApiOptions.spec.tsx b/webview-ui/src/components/settings/__tests__/ApiOptions.spec.tsx index bf02840429..7b7f9b33e4 100644 --- a/webview-ui/src/components/settings/__tests__/ApiOptions.spec.tsx +++ b/webview-ui/src/components/settings/__tests__/ApiOptions.spec.tsx @@ -21,6 +21,16 @@ vi.mock("@vscode/webview-ui-toolkit/react", () => ({ VSCodeRadio: ({ value, checked }: any) => , VSCodeRadioGroup: ({ children }: any) =>
{children}
, VSCodeButton: ({ children }: any) =>
{children}
, + VSCodeCheckbox: ({ children, checked, onChange }: any) => ( + + ), })) // Mock other components @@ -173,6 +183,22 @@ vi.mock("../DiffSettingsControl", () => ({ ), })) +// Mock TodoListSettingsControl for tests +vi.mock("../TodoListSettingsControl", () => ({ + TodoListSettingsControl: ({ todoListEnabled, onChange }: any) => ( +
+ +
+ ), +})) + // Mock ThinkingBudget component vi.mock("../ThinkingBudget", () => ({ ThinkingBudget: ({ modelInfo }: any) => { diff --git a/webview-ui/src/components/settings/__tests__/TodoListSettingsControl.spec.tsx b/webview-ui/src/components/settings/__tests__/TodoListSettingsControl.spec.tsx new file mode 100644 index 0000000000..432b2c9c61 --- /dev/null +++ b/webview-ui/src/components/settings/__tests__/TodoListSettingsControl.spec.tsx @@ -0,0 +1,77 @@ +import React from "react" +import { render, screen, fireEvent } from "@testing-library/react" +import { describe, it, expect, vi } from "vitest" +import { TodoListSettingsControl } from "../TodoListSettingsControl" + +// Mock the translation hook +vi.mock("@/i18n/TranslationContext", () => ({ + useAppTranslation: () => ({ + t: (key: string) => { + const translations: Record = { + "settings:advanced.todoList.label": "Enable todo list tool", + "settings:advanced.todoList.description": + "When enabled, Roo can create and manage todo lists to track task progress. This helps organize complex tasks into manageable steps.", + } + return translations[key] || key + }, + }), +})) + +// Mock VSCodeCheckbox +vi.mock("@vscode/webview-ui-toolkit/react", () => ({ + VSCodeCheckbox: ({ children, onChange, checked, ...props }: any) => ( + + ), +})) + +describe("TodoListSettingsControl", () => { + it("renders with default props", () => { + const onChange = vi.fn() + render() + + const checkbox = screen.getByRole("checkbox") + const label = screen.getByText("Enable todo list tool") + const description = screen.getByText(/When enabled, Roo can create and manage todo lists/) + + expect(checkbox).toBeInTheDocument() + expect(checkbox).toBeChecked() // Default is true + expect(label).toBeInTheDocument() + expect(description).toBeInTheDocument() + }) + + it("renders with todoListEnabled set to false", () => { + const onChange = vi.fn() + render() + + const checkbox = screen.getByRole("checkbox") + expect(checkbox).not.toBeChecked() + }) + + it("calls onChange when checkbox is clicked", () => { + const onChange = vi.fn() + render() + + const checkbox = screen.getByRole("checkbox") + fireEvent.click(checkbox) + + expect(onChange).toHaveBeenCalledWith("todoListEnabled", false) + }) + + it("toggles from unchecked to checked", () => { + const onChange = vi.fn() + render() + + const checkbox = screen.getByRole("checkbox") + fireEvent.click(checkbox) + + expect(onChange).toHaveBeenCalledWith("todoListEnabled", true) + }) +}) diff --git a/webview-ui/src/i18n/locales/ca/settings.json b/webview-ui/src/i18n/locales/ca/settings.json index eaa83b1b0d..8f270ee4ce 100644 --- a/webview-ui/src/i18n/locales/ca/settings.json +++ b/webview-ui/src/i18n/locales/ca/settings.json @@ -577,6 +577,10 @@ "label": "Precisió de coincidència", "description": "Aquest control lliscant controla amb quina precisió han de coincidir les seccions de codi en aplicar diffs. Valors més baixos permeten coincidències més flexibles però augmenten el risc de reemplaçaments incorrectes. Utilitzeu valors per sota del 100% amb extrema precaució." } + }, + "todoList": { + "label": "Habilitar eina de llista de tasques", + "description": "Quan està habilitat, Roo pot crear i gestionar llistes de tasques per fer el seguiment del progrés de les tasques. Això ajuda a organitzar tasques complexes en passos manejables." } }, "experimental": { diff --git a/webview-ui/src/i18n/locales/de/settings.json b/webview-ui/src/i18n/locales/de/settings.json index 7f7c2c22b7..73a4ede3d8 100644 --- a/webview-ui/src/i18n/locales/de/settings.json +++ b/webview-ui/src/i18n/locales/de/settings.json @@ -577,6 +577,10 @@ "label": "Übereinstimmungspräzision", "description": "Dieser Schieberegler steuert, wie genau Codeabschnitte bei der Anwendung von Diffs übereinstimmen müssen. Niedrigere Werte ermöglichen eine flexiblere Übereinstimmung, erhöhen aber das Risiko falscher Ersetzungen. Verwenden Sie Werte unter 100 % mit äußerster Vorsicht." } + }, + "todoList": { + "label": "Todo-Listen-Tool aktivieren", + "description": "Wenn aktiviert, kann Roo Todo-Listen erstellen und verwalten, um den Aufgabenfortschritt zu verfolgen. Dies hilft, komplexe Aufgaben in überschaubare Schritte zu organisieren." } }, "experimental": { diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index 7e3c2e3fcc..457b4dbda9 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -577,6 +577,10 @@ "label": "Match precision", "description": "This slider controls how precisely code sections must match when applying diffs. Lower values allow more flexible matching but increase the risk of incorrect replacements. Use values below 100% with extreme caution." } + }, + "todoList": { + "label": "Enable todo list tool", + "description": "When enabled, Roo can create and manage todo lists to track task progress. This helps organize complex tasks into manageable steps." } }, "experimental": { diff --git a/webview-ui/src/i18n/locales/es/settings.json b/webview-ui/src/i18n/locales/es/settings.json index f00c2c9b42..d217e66226 100644 --- a/webview-ui/src/i18n/locales/es/settings.json +++ b/webview-ui/src/i18n/locales/es/settings.json @@ -577,6 +577,10 @@ "label": "Precisión de coincidencia", "description": "Este control deslizante controla cuán precisamente deben coincidir las secciones de código al aplicar diffs. Valores más bajos permiten coincidencias más flexibles pero aumentan el riesgo de reemplazos incorrectos. Use valores por debajo del 100% con extrema precaución." } + }, + "todoList": { + "label": "Habilitar herramienta de lista de tareas", + "description": "Cuando está habilitado, Roo puede crear y gestionar listas de tareas para hacer seguimiento del progreso. Esto ayuda a organizar tareas complejas en pasos manejables." } }, "experimental": { diff --git a/webview-ui/src/i18n/locales/fr/settings.json b/webview-ui/src/i18n/locales/fr/settings.json index 02dfac3552..f7f9a2683a 100644 --- a/webview-ui/src/i18n/locales/fr/settings.json +++ b/webview-ui/src/i18n/locales/fr/settings.json @@ -577,6 +577,10 @@ "label": "Précision de correspondance", "description": "Ce curseur contrôle la précision avec laquelle les sections de code doivent correspondre lors de l'application des diffs. Des valeurs plus basses permettent des correspondances plus flexibles mais augmentent le risque de remplacements incorrects. Utilisez des valeurs inférieures à 100 % avec une extrême prudence." } + }, + "todoList": { + "label": "Activer l'outil de liste de tâches", + "description": "Lorsqu'activé, Roo peut créer et gérer des listes de tâches pour suivre la progression. Cela aide à organiser les tâches complexes en étapes gérables." } }, "experimental": { diff --git a/webview-ui/src/i18n/locales/hi/settings.json b/webview-ui/src/i18n/locales/hi/settings.json index 2b7fd03d56..6a3679e090 100644 --- a/webview-ui/src/i18n/locales/hi/settings.json +++ b/webview-ui/src/i18n/locales/hi/settings.json @@ -577,6 +577,10 @@ "label": "मिलान सटीकता", "description": "यह स्लाइडर नियंत्रित करता है कि diffs लागू करते समय कोड अनुभागों को कितनी सटीकता से मेल खाना चाहिए। निम्न मान अधिक लचीले मिलान की अनुमति देते हैं लेकिन गलत प्रतिस्थापन का जोखिम बढ़ाते हैं। 100% से नीचे के मानों का उपयोग अत्यधिक सावधानी के साथ करें।" } + }, + "todoList": { + "label": "टूडू सूची टूल सक्षम करें", + "description": "जब सक्षम हो, तो Roo कार्य प्रगति को ट्रैक करने के लिए टूडू सूचियाँ बना और प्रबंधित कर सकता है। यह जटिल कार्यों को प्रबंधनीय चरणों में व्यवस्थित करने में मदद करता है।" } }, "experimental": { diff --git a/webview-ui/src/i18n/locales/id/settings.json b/webview-ui/src/i18n/locales/id/settings.json index c6ba2728a7..09d7e8254a 100644 --- a/webview-ui/src/i18n/locales/id/settings.json +++ b/webview-ui/src/i18n/locales/id/settings.json @@ -581,6 +581,10 @@ "label": "Presisi pencocokan", "description": "Slider ini mengontrol seberapa tepat bagian kode harus cocok saat menerapkan diff. Nilai yang lebih rendah memungkinkan pencocokan yang lebih fleksibel tetapi meningkatkan risiko penggantian yang salah. Gunakan nilai di bawah 100% dengan sangat hati-hati." } + }, + "todoList": { + "label": "Aktifkan alat daftar tugas", + "description": "Saat diaktifkan, Roo dapat membuat dan mengelola daftar tugas untuk melacak kemajuan tugas. Ini membantu mengatur tugas kompleks menjadi langkah-langkah yang dapat dikelola." } }, "experimental": { diff --git a/webview-ui/src/i18n/locales/it/settings.json b/webview-ui/src/i18n/locales/it/settings.json index 26c776d60d..c2836f2c71 100644 --- a/webview-ui/src/i18n/locales/it/settings.json +++ b/webview-ui/src/i18n/locales/it/settings.json @@ -577,6 +577,10 @@ "label": "Precisione corrispondenza", "description": "Questo cursore controlla quanto precisamente le sezioni di codice devono corrispondere quando si applicano i diff. Valori più bassi consentono corrispondenze più flessibili ma aumentano il rischio di sostituzioni errate. Usa valori inferiori al 100% con estrema cautela." } + }, + "todoList": { + "label": "Abilita strumento lista di cose da fare", + "description": "Quando abilitato, Roo può creare e gestire liste di cose da fare per tracciare il progresso delle attività. Questo aiuta a organizzare attività complesse in passaggi gestibili." } }, "experimental": { diff --git a/webview-ui/src/i18n/locales/ja/settings.json b/webview-ui/src/i18n/locales/ja/settings.json index 9eb4328c12..01233ef505 100644 --- a/webview-ui/src/i18n/locales/ja/settings.json +++ b/webview-ui/src/i18n/locales/ja/settings.json @@ -577,6 +577,10 @@ "label": "マッチ精度", "description": "このスライダーは、diffを適用する際にコードセクションがどれだけ正確に一致する必要があるかを制御します。低い値はより柔軟なマッチングを可能にしますが、誤った置換のリスクが高まります。100%未満の値は細心の注意を払って使用してください。" } + }, + "todoList": { + "label": "ToDoリストツールを有効にする", + "description": "有効にすると、Rooはタスクの進捗を追跡するためのToDoリストを作成・管理できます。これにより、複雑なタスクを管理しやすいステップに整理できます。" } }, "experimental": { diff --git a/webview-ui/src/i18n/locales/ko/settings.json b/webview-ui/src/i18n/locales/ko/settings.json index 18b054bbb8..806b11f022 100644 --- a/webview-ui/src/i18n/locales/ko/settings.json +++ b/webview-ui/src/i18n/locales/ko/settings.json @@ -577,6 +577,10 @@ "label": "일치 정확도", "description": "이 슬라이더는 diff를 적용할 때 코드 섹션이 얼마나 정확하게 일치해야 하는지 제어합니다. 낮은 값은 더 유연한 일치를 허용하지만 잘못된 교체 위험이 증가합니다. 100% 미만의 값은 극도로 주의해서 사용하세요." } + }, + "todoList": { + "label": "할 일 목록 도구 활성화", + "description": "활성화하면 Roo가 작업 진행 상황을 추적하기 위한 할 일 목록을 만들고 관리할 수 있습니다. 이는 복잡한 작업을 관리 가능한 단계로 구성하는 데 도움이 됩니다." } }, "experimental": { diff --git a/webview-ui/src/i18n/locales/nl/settings.json b/webview-ui/src/i18n/locales/nl/settings.json index 73ecf87d34..28edfdfd52 100644 --- a/webview-ui/src/i18n/locales/nl/settings.json +++ b/webview-ui/src/i18n/locales/nl/settings.json @@ -577,6 +577,10 @@ "label": "Matchnauwkeurigheid", "description": "Deze schuifregelaar bepaalt hoe nauwkeurig codeblokken moeten overeenkomen bij het toepassen van diffs. Lagere waarden laten flexibelere matching toe maar verhogen het risico op verkeerde vervangingen. Gebruik waarden onder 100% met uiterste voorzichtigheid." } + }, + "todoList": { + "label": "Takenlijst-tool inschakelen", + "description": "Wanneer ingeschakeld, kan Roo takenlijsten maken en beheren om de voortgang van taken bij te houden. Dit helpt complexe taken te organiseren in beheersbare stappen." } }, "experimental": { diff --git a/webview-ui/src/i18n/locales/pl/settings.json b/webview-ui/src/i18n/locales/pl/settings.json index b0fb8efac3..d034830c1a 100644 --- a/webview-ui/src/i18n/locales/pl/settings.json +++ b/webview-ui/src/i18n/locales/pl/settings.json @@ -577,6 +577,10 @@ "label": "Precyzja dopasowania", "description": "Ten suwak kontroluje, jak dokładnie sekcje kodu muszą pasować podczas stosowania różnic. Niższe wartości umożliwiają bardziej elastyczne dopasowywanie, ale zwiększają ryzyko nieprawidłowych zamian. Używaj wartości poniżej 100% z najwyższą ostrożnością." } + }, + "todoList": { + "label": "Włącz narzędzie listy zadań", + "description": "Po włączeniu Roo może tworzyć i zarządzać listami zadań do śledzenia postępu zadań. Pomaga to organizować złożone zadania w łatwe do zarządzania kroki." } }, "experimental": { diff --git a/webview-ui/src/i18n/locales/pt-BR/settings.json b/webview-ui/src/i18n/locales/pt-BR/settings.json index 4167ade974..140042c773 100644 --- a/webview-ui/src/i18n/locales/pt-BR/settings.json +++ b/webview-ui/src/i18n/locales/pt-BR/settings.json @@ -577,6 +577,10 @@ "label": "Precisão de correspondência", "description": "Este controle deslizante controla quão precisamente as seções de código devem corresponder ao aplicar diffs. Valores mais baixos permitem correspondências mais flexíveis, mas aumentam o risco de substituições incorretas. Use valores abaixo de 100% com extrema cautela." } + }, + "todoList": { + "label": "Habilitar ferramenta de lista de tarefas", + "description": "Quando habilitado, o Roo pode criar e gerenciar listas de tarefas para acompanhar o progresso das tarefas. Isso ajuda a organizar tarefas complexas em etapas gerenciáveis." } }, "experimental": { diff --git a/webview-ui/src/i18n/locales/ru/settings.json b/webview-ui/src/i18n/locales/ru/settings.json index c07bc1d98a..895655fbdf 100644 --- a/webview-ui/src/i18n/locales/ru/settings.json +++ b/webview-ui/src/i18n/locales/ru/settings.json @@ -577,6 +577,10 @@ "label": "Точность совпадения", "description": "Этот ползунок управляет точностью совпадения секций кода при применении диффов. Меньшие значения позволяют более гибкое совпадение, но увеличивают риск неверной замены. Используйте значения ниже 100% с осторожностью." } + }, + "todoList": { + "label": "Включить инструмент списка задач", + "description": "При включении Roo может создавать и управлять списками задач для отслеживания прогресса. Это помогает организовать сложные задачи в управляемые шаги." } }, "experimental": { diff --git a/webview-ui/src/i18n/locales/tr/settings.json b/webview-ui/src/i18n/locales/tr/settings.json index ae6fad364d..06878be920 100644 --- a/webview-ui/src/i18n/locales/tr/settings.json +++ b/webview-ui/src/i18n/locales/tr/settings.json @@ -577,6 +577,10 @@ "label": "Eşleşme hassasiyeti", "description": "Bu kaydırıcı, diff'ler uygulanırken kod bölümlerinin ne kadar hassas bir şekilde eşleşmesi gerektiğini kontrol eder. Daha düşük değerler daha esnek eşleşmeye izin verir ancak yanlış değiştirme riskini artırır. %100'ün altındaki değerleri son derece dikkatli kullanın." } + }, + "todoList": { + "label": "Yapılacaklar listesi aracını etkinleştir", + "description": "Etkinleştirildiğinde, Roo görev ilerlemesini takip etmek için yapılacaklar listeleri oluşturabilir ve yönetebilir. Bu, karmaşık görevleri yönetilebilir adımlara organize etmeye yardımcı olur." } }, "experimental": { diff --git a/webview-ui/src/i18n/locales/vi/settings.json b/webview-ui/src/i18n/locales/vi/settings.json index 6505d7df0e..c83b7a6642 100644 --- a/webview-ui/src/i18n/locales/vi/settings.json +++ b/webview-ui/src/i18n/locales/vi/settings.json @@ -577,6 +577,10 @@ "label": "Độ chính xác khớp", "description": "Thanh trượt này kiểm soát mức độ chính xác các phần mã phải khớp khi áp dụng diff. Giá trị thấp hơn cho phép khớp linh hoạt hơn nhưng tăng nguy cơ thay thế không chính xác. Sử dụng giá trị dưới 100% với sự thận trọng cao." } + }, + "todoList": { + "label": "Bật công cụ danh sách việc cần làm", + "description": "Khi được bật, Roo có thể tạo và quản lý danh sách việc cần làm để theo dõi tiến độ công việc. Điều này giúp tổ chức các tác vụ phức tạp thành các bước có thể quản lý được." } }, "experimental": { diff --git a/webview-ui/src/i18n/locales/zh-CN/settings.json b/webview-ui/src/i18n/locales/zh-CN/settings.json index bf3eea0294..11c469bf77 100644 --- a/webview-ui/src/i18n/locales/zh-CN/settings.json +++ b/webview-ui/src/i18n/locales/zh-CN/settings.json @@ -577,6 +577,10 @@ "label": "匹配精度", "description": "控制代码匹配的精确程度。数值越低匹配越宽松(容错率高但风险大),建议保持100%以确保安全。" } + }, + "todoList": { + "label": "启用任务清单工具", + "description": "启用后,Roo 可以创建和管理任务清单来跟踪任务进度。这有助于将复杂任务组织成可管理的步骤。" } }, "experimental": { diff --git a/webview-ui/src/i18n/locales/zh-TW/settings.json b/webview-ui/src/i18n/locales/zh-TW/settings.json index e2a896dce4..dab70b88b1 100644 --- a/webview-ui/src/i18n/locales/zh-TW/settings.json +++ b/webview-ui/src/i18n/locales/zh-TW/settings.json @@ -577,6 +577,10 @@ "label": "比對精確度", "description": "此滑桿控制套用差異時程式碼區段的比對精確度。較低的數值允許更彈性的比對,但也會增加錯誤取代的風險。使用低於 100% 的數值時請特別謹慎。" } + }, + "todoList": { + "label": "啟用待辦事項清單工具", + "description": "啟用後,Roo 可以建立和管理待辦事項清單來追蹤任務進度。這有助於將複雜任務組織成可管理的步驟。" } }, "experimental": { From df6c57d2930f32f511ad92f3fdbe98df21e5dfda Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Tue, 22 Jul 2025 00:37:24 -0400 Subject: [PATCH 05/92] feat: add moonshot provider (#6046) Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> Co-authored-by: CellenLee <99465814+CellenLee@users.noreply.github.com> --- packages/types/src/global-settings.ts | 1 + packages/types/src/provider-settings.ts | 10 + packages/types/src/providers/index.ts | 1 + packages/types/src/providers/moonshot.ts | 22 ++ src/api/index.ts | 4 + src/api/providers/__tests__/moonshot.spec.ts | 297 ++++++++++++++++++ src/api/providers/index.ts | 1 + src/api/providers/moonshot.ts | 39 +++ .../__tests__/checkExistApiConfig.spec.ts | 1 + .../src/components/settings/ApiOptions.tsx | 7 + .../src/components/settings/constants.ts | 3 + .../settings/providers/Moonshot.tsx | 73 +++++ .../components/settings/providers/index.ts | 1 + .../components/ui/hooks/useSelectedModel.ts | 8 + webview-ui/src/i18n/locales/ca/settings.json | 3 + webview-ui/src/i18n/locales/de/settings.json | 3 + webview-ui/src/i18n/locales/en/settings.json | 3 + webview-ui/src/i18n/locales/es/settings.json | 3 + webview-ui/src/i18n/locales/fr/settings.json | 3 + webview-ui/src/i18n/locales/hi/settings.json | 3 + webview-ui/src/i18n/locales/id/settings.json | 3 + webview-ui/src/i18n/locales/it/settings.json | 3 + webview-ui/src/i18n/locales/ja/settings.json | 3 + webview-ui/src/i18n/locales/ko/settings.json | 3 + webview-ui/src/i18n/locales/nl/settings.json | 3 + webview-ui/src/i18n/locales/pl/settings.json | 3 + .../src/i18n/locales/pt-BR/settings.json | 3 + webview-ui/src/i18n/locales/ru/settings.json | 3 + webview-ui/src/i18n/locales/tr/settings.json | 3 + webview-ui/src/i18n/locales/vi/settings.json | 3 + .../src/i18n/locales/zh-CN/settings.json | 3 + .../src/i18n/locales/zh-TW/settings.json | 3 + 32 files changed, 522 insertions(+) create mode 100644 packages/types/src/providers/moonshot.ts create mode 100644 src/api/providers/__tests__/moonshot.spec.ts create mode 100644 src/api/providers/moonshot.ts create mode 100644 webview-ui/src/components/settings/providers/Moonshot.tsx diff --git a/packages/types/src/global-settings.ts b/packages/types/src/global-settings.ts index a30550dce1..30521f2c68 100644 --- a/packages/types/src/global-settings.ts +++ b/packages/types/src/global-settings.ts @@ -159,6 +159,7 @@ export const SECRET_STATE_KEYS = [ "geminiApiKey", "openAiNativeApiKey", "deepSeekApiKey", + "moonshotApiKey", "mistralApiKey", "unboundApiKey", "requestyApiKey", diff --git a/packages/types/src/provider-settings.ts b/packages/types/src/provider-settings.ts index 7df1b27db7..884337767f 100644 --- a/packages/types/src/provider-settings.ts +++ b/packages/types/src/provider-settings.ts @@ -22,6 +22,7 @@ export const providerNames = [ "gemini-cli", "openai-native", "mistral", + "moonshot", "deepseek", "unbound", "requesty", @@ -187,6 +188,13 @@ const deepSeekSchema = apiModelIdProviderModelSchema.extend({ deepSeekApiKey: z.string().optional(), }) +const moonshotSchema = apiModelIdProviderModelSchema.extend({ + moonshotBaseUrl: z + .union([z.literal("https://api.moonshot.ai/v1"), z.literal("https://api.moonshot.cn/v1")]) + .optional(), + moonshotApiKey: z.string().optional(), +}) + const unboundSchema = baseProviderSettingsSchema.extend({ unboundApiKey: z.string().optional(), unboundModelId: z.string().optional(), @@ -241,6 +249,7 @@ export const providerSettingsSchemaDiscriminated = z.discriminatedUnion("apiProv openAiNativeSchema.merge(z.object({ apiProvider: z.literal("openai-native") })), mistralSchema.merge(z.object({ apiProvider: z.literal("mistral") })), deepSeekSchema.merge(z.object({ apiProvider: z.literal("deepseek") })), + moonshotSchema.merge(z.object({ apiProvider: z.literal("moonshot") })), unboundSchema.merge(z.object({ apiProvider: z.literal("unbound") })), requestySchema.merge(z.object({ apiProvider: z.literal("requesty") })), humanRelaySchema.merge(z.object({ apiProvider: z.literal("human-relay") })), @@ -269,6 +278,7 @@ export const providerSettingsSchema = z.object({ ...openAiNativeSchema.shape, ...mistralSchema.shape, ...deepSeekSchema.shape, + ...moonshotSchema.shape, ...unboundSchema.shape, ...requestySchema.shape, ...humanRelaySchema.shape, diff --git a/packages/types/src/providers/index.ts b/packages/types/src/providers/index.ts index 267401bd91..e4e506b8a7 100644 --- a/packages/types/src/providers/index.ts +++ b/packages/types/src/providers/index.ts @@ -9,6 +9,7 @@ export * from "./groq.js" export * from "./lite-llm.js" export * from "./lm-studio.js" export * from "./mistral.js" +export * from "./moonshot.js" export * from "./ollama.js" export * from "./openai.js" export * from "./openrouter.js" diff --git a/packages/types/src/providers/moonshot.ts b/packages/types/src/providers/moonshot.ts new file mode 100644 index 0000000000..d7e542077d --- /dev/null +++ b/packages/types/src/providers/moonshot.ts @@ -0,0 +1,22 @@ +import type { ModelInfo } from "../model.js" + +// https://platform.moonshot.ai/ +export type MoonshotModelId = keyof typeof moonshotModels + +export const moonshotDefaultModelId: MoonshotModelId = "kimi-k2-0711-preview" + +export const moonshotModels = { + "kimi-k2-0711-preview": { + maxTokens: 32_000, + contextWindow: 131_072, + supportsImages: false, + supportsPromptCache: true, + inputPrice: 0.6, // $0.60 per million tokens (cache miss) + outputPrice: 2.5, // $2.50 per million tokens + cacheWritesPrice: 0, // $0 per million tokens (cache miss) + cacheReadsPrice: 0.15, // $0.15 per million tokens (cache hit) + description: `Kimi K2 is a state-of-the-art mixture-of-experts (MoE) language model with 32 billion activated parameters and 1 trillion total parameters.`, + }, +} as const satisfies Record + +export const MOONSHOT_DEFAULT_TEMPERATURE = 0.6 diff --git a/src/api/index.ts b/src/api/index.ts index 960209f714..4598a711b2 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -17,6 +17,7 @@ import { GeminiHandler, OpenAiNativeHandler, DeepSeekHandler, + MoonshotHandler, MistralHandler, VsCodeLmHandler, UnboundHandler, @@ -89,6 +90,8 @@ export function buildApiHandler(configuration: ProviderSettings): ApiHandler { return new OpenAiNativeHandler(options) case "deepseek": return new DeepSeekHandler(options) + case "moonshot": + return new MoonshotHandler(options) case "vscode-lm": return new VsCodeLmHandler(options) case "mistral": @@ -110,6 +113,7 @@ export function buildApiHandler(configuration: ProviderSettings): ApiHandler { case "litellm": return new LiteLLMHandler(options) default: + apiProvider satisfies "gemini-cli" | undefined return new AnthropicHandler(options) } } diff --git a/src/api/providers/__tests__/moonshot.spec.ts b/src/api/providers/__tests__/moonshot.spec.ts new file mode 100644 index 0000000000..586dd5598e --- /dev/null +++ b/src/api/providers/__tests__/moonshot.spec.ts @@ -0,0 +1,297 @@ +// Mocks must come first, before imports +const mockCreate = vi.fn() +vi.mock("openai", () => { + return { + __esModule: true, + default: vi.fn().mockImplementation(() => ({ + chat: { + completions: { + create: mockCreate.mockImplementation(async (options) => { + if (!options.stream) { + return { + id: "test-completion", + choices: [ + { + message: { role: "assistant", content: "Test response", refusal: null }, + finish_reason: "stop", + index: 0, + }, + ], + usage: { + prompt_tokens: 10, + completion_tokens: 5, + total_tokens: 15, + cached_tokens: 2, + }, + } + } + + // Return async iterator for streaming + return { + [Symbol.asyncIterator]: async function* () { + yield { + choices: [ + { + delta: { content: "Test response" }, + index: 0, + }, + ], + usage: null, + } + yield { + choices: [ + { + delta: {}, + index: 0, + }, + ], + usage: { + prompt_tokens: 10, + completion_tokens: 5, + total_tokens: 15, + cached_tokens: 2, + }, + } + }, + } + }), + }, + }, + })), + } +}) + +import OpenAI from "openai" +import type { Anthropic } from "@anthropic-ai/sdk" + +import { moonshotDefaultModelId } from "@roo-code/types" + +import type { ApiHandlerOptions } from "../../../shared/api" + +import { MoonshotHandler } from "../moonshot" + +describe("MoonshotHandler", () => { + let handler: MoonshotHandler + let mockOptions: ApiHandlerOptions + + beforeEach(() => { + mockOptions = { + moonshotApiKey: "test-api-key", + apiModelId: "moonshot-chat", + moonshotBaseUrl: "https://api.moonshot.ai/v1", + } + handler = new MoonshotHandler(mockOptions) + vi.clearAllMocks() + }) + + describe("constructor", () => { + it("should initialize with provided options", () => { + expect(handler).toBeInstanceOf(MoonshotHandler) + expect(handler.getModel().id).toBe(mockOptions.apiModelId) + }) + + it.skip("should throw error if API key is missing", () => { + expect(() => { + new MoonshotHandler({ + ...mockOptions, + moonshotApiKey: undefined, + }) + }).toThrow("Moonshot API key is required") + }) + + it("should use default model ID if not provided", () => { + const handlerWithoutModel = new MoonshotHandler({ + ...mockOptions, + apiModelId: undefined, + }) + expect(handlerWithoutModel.getModel().id).toBe(moonshotDefaultModelId) + }) + + it("should use default base URL if not provided", () => { + const handlerWithoutBaseUrl = new MoonshotHandler({ + ...mockOptions, + moonshotBaseUrl: undefined, + }) + expect(handlerWithoutBaseUrl).toBeInstanceOf(MoonshotHandler) + // The base URL is passed to OpenAI client internally + expect(OpenAI).toHaveBeenCalledWith( + expect.objectContaining({ + baseURL: "https://api.moonshot.ai/v1", + }), + ) + }) + + it("should use chinese base URL if provided", () => { + const customBaseUrl = "https://api.moonshot.cn/v1" + const handlerWithCustomUrl = new MoonshotHandler({ + ...mockOptions, + moonshotBaseUrl: customBaseUrl, + }) + expect(handlerWithCustomUrl).toBeInstanceOf(MoonshotHandler) + // The custom base URL is passed to OpenAI client + expect(OpenAI).toHaveBeenCalledWith( + expect.objectContaining({ + baseURL: customBaseUrl, + }), + ) + }) + + it("should set includeMaxTokens to true", () => { + // Create a new handler and verify OpenAI client was called with includeMaxTokens + const _handler = new MoonshotHandler(mockOptions) + expect(OpenAI).toHaveBeenCalledWith(expect.objectContaining({ apiKey: mockOptions.moonshotApiKey })) + }) + }) + + describe("getModel", () => { + it("should return model info for valid model ID", () => { + const model = handler.getModel() + expect(model.id).toBe(mockOptions.apiModelId) + expect(model.info).toBeDefined() + expect(model.info.maxTokens).toBe(32_000) + expect(model.info.contextWindow).toBe(131_072) + expect(model.info.supportsImages).toBe(false) + expect(model.info.supportsPromptCache).toBe(true) // Should be true now + }) + + it("should return provided model ID with default model info if model does not exist", () => { + const handlerWithInvalidModel = new MoonshotHandler({ + ...mockOptions, + apiModelId: "invalid-model", + }) + const model = handlerWithInvalidModel.getModel() + expect(model.id).toBe("invalid-model") // Returns provided ID + expect(model.info).toBeDefined() + // With the current implementation, it's the same object reference when using default model info + expect(model.info).toBe(handler.getModel().info) + // Should have the same base properties + expect(model.info.contextWindow).toBe(handler.getModel().info.contextWindow) + // And should have supportsPromptCache set to true + expect(model.info.supportsPromptCache).toBe(true) + }) + + it("should return default model if no model ID is provided", () => { + const handlerWithoutModel = new MoonshotHandler({ + ...mockOptions, + apiModelId: undefined, + }) + const model = handlerWithoutModel.getModel() + expect(model.id).toBe(moonshotDefaultModelId) + expect(model.info).toBeDefined() + expect(model.info.supportsPromptCache).toBe(true) + }) + + it("should include model parameters from getModelParams", () => { + const model = handler.getModel() + expect(model).toHaveProperty("temperature") + expect(model).toHaveProperty("maxTokens") + }) + }) + + describe("createMessage", () => { + const systemPrompt = "You are a helpful assistant." + const messages: Anthropic.Messages.MessageParam[] = [ + { + role: "user", + content: [ + { + type: "text" as const, + text: "Hello!", + }, + ], + }, + ] + + it("should handle streaming responses", async () => { + const stream = handler.createMessage(systemPrompt, messages) + const chunks: any[] = [] + for await (const chunk of stream) { + chunks.push(chunk) + } + + expect(chunks.length).toBeGreaterThan(0) + const textChunks = chunks.filter((chunk) => chunk.type === "text") + expect(textChunks).toHaveLength(1) + expect(textChunks[0].text).toBe("Test response") + }) + + it("should include usage information", async () => { + const stream = handler.createMessage(systemPrompt, messages) + const chunks: any[] = [] + for await (const chunk of stream) { + chunks.push(chunk) + } + + const usageChunks = chunks.filter((chunk) => chunk.type === "usage") + expect(usageChunks.length).toBeGreaterThan(0) + expect(usageChunks[0].inputTokens).toBe(10) + expect(usageChunks[0].outputTokens).toBe(5) + }) + + it("should include cache metrics in usage information", async () => { + const stream = handler.createMessage(systemPrompt, messages) + const chunks: any[] = [] + for await (const chunk of stream) { + chunks.push(chunk) + } + + const usageChunks = chunks.filter((chunk) => chunk.type === "usage") + expect(usageChunks.length).toBeGreaterThan(0) + expect(usageChunks[0].cacheWriteTokens).toBe(0) + expect(usageChunks[0].cacheReadTokens).toBe(2) + }) + }) + + describe("processUsageMetrics", () => { + it("should correctly process usage metrics including cache information", () => { + // We need to access the protected method, so we'll create a test subclass + class TestMoonshotHandler extends MoonshotHandler { + public testProcessUsageMetrics(usage: any) { + return this.processUsageMetrics(usage) + } + } + + const testHandler = new TestMoonshotHandler(mockOptions) + + const usage = { + prompt_tokens: 100, + completion_tokens: 50, + total_tokens: 150, + cached_tokens: 20, + } + + const result = testHandler.testProcessUsageMetrics(usage) + + expect(result.type).toBe("usage") + expect(result.inputTokens).toBe(100) + expect(result.outputTokens).toBe(50) + expect(result.cacheWriteTokens).toBe(0) + expect(result.cacheReadTokens).toBe(20) + }) + + it("should handle missing cache metrics gracefully", () => { + class TestMoonshotHandler extends MoonshotHandler { + public testProcessUsageMetrics(usage: any) { + return this.processUsageMetrics(usage) + } + } + + const testHandler = new TestMoonshotHandler(mockOptions) + + const usage = { + prompt_tokens: 100, + completion_tokens: 50, + total_tokens: 150, + // No cached_tokens + } + + const result = testHandler.testProcessUsageMetrics(usage) + + expect(result.type).toBe("usage") + expect(result.inputTokens).toBe(100) + expect(result.outputTokens).toBe(50) + expect(result.cacheWriteTokens).toBe(0) + expect(result.cacheReadTokens).toBeUndefined() + }) + }) +}) diff --git a/src/api/providers/index.ts b/src/api/providers/index.ts index 93df5c58ff..89d4c203ad 100644 --- a/src/api/providers/index.ts +++ b/src/api/providers/index.ts @@ -4,6 +4,7 @@ export { AwsBedrockHandler } from "./bedrock" export { ChutesHandler } from "./chutes" export { ClaudeCodeHandler } from "./claude-code" export { DeepSeekHandler } from "./deepseek" +export { MoonshotHandler } from "./moonshot" export { FakeAIHandler } from "./fake-ai" export { GeminiHandler } from "./gemini" export { GlamaHandler } from "./glama" diff --git a/src/api/providers/moonshot.ts b/src/api/providers/moonshot.ts new file mode 100644 index 0000000000..f04b369d1c --- /dev/null +++ b/src/api/providers/moonshot.ts @@ -0,0 +1,39 @@ +import { moonshotModels, moonshotDefaultModelId } from "@roo-code/types" + +import type { ApiHandlerOptions } from "../../shared/api" + +import type { ApiStreamUsageChunk } from "../transform/stream" +import { getModelParams } from "../transform/model-params" + +import { OpenAiHandler } from "./openai" + +export class MoonshotHandler extends OpenAiHandler { + constructor(options: ApiHandlerOptions) { + super({ + ...options, + openAiApiKey: options.moonshotApiKey ?? "not-provided", + openAiModelId: options.apiModelId ?? moonshotDefaultModelId, + openAiBaseUrl: options.moonshotBaseUrl ?? "https://api.moonshot.ai/v1", + openAiStreamingEnabled: true, + includeMaxTokens: true, + }) + } + + override getModel() { + const id = this.options.apiModelId ?? moonshotDefaultModelId + const info = moonshotModels[id as keyof typeof moonshotModels] || moonshotModels[moonshotDefaultModelId] + const params = getModelParams({ format: "openai", modelId: id, model: info, settings: this.options }) + return { id, info, ...params } + } + + // Override to handle Moonshot's usage metrics, including caching. + protected override processUsageMetrics(usage: any): ApiStreamUsageChunk { + return { + type: "usage", + inputTokens: usage?.prompt_tokens || 0, + outputTokens: usage?.completion_tokens || 0, + cacheWriteTokens: 0, + cacheReadTokens: usage?.cached_tokens, + } + } +} diff --git a/src/shared/__tests__/checkExistApiConfig.spec.ts b/src/shared/__tests__/checkExistApiConfig.spec.ts index 7bc9e1d576..7696f00cc0 100644 --- a/src/shared/__tests__/checkExistApiConfig.spec.ts +++ b/src/shared/__tests__/checkExistApiConfig.spec.ts @@ -53,6 +53,7 @@ describe("checkExistKey", () => { geminiApiKey: undefined, openAiNativeApiKey: undefined, deepSeekApiKey: undefined, + moonshotApiKey: undefined, mistralApiKey: undefined, vsCodeLmModelSelector: undefined, requestyApiKey: undefined, diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index 681268a787..6c6c621956 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -18,6 +18,7 @@ import { claudeCodeDefaultModelId, geminiDefaultModelId, deepSeekDefaultModelId, + moonshotDefaultModelId, mistralDefaultModelId, xaiDefaultModelId, groqDefaultModelId, @@ -61,6 +62,7 @@ import { LMStudio, LiteLLM, Mistral, + Moonshot, Ollama, OpenAI, OpenAICompatible, @@ -287,6 +289,7 @@ const ApiOptions = ({ "openai-native": { field: "apiModelId", default: openAiNativeDefaultModelId }, gemini: { field: "apiModelId", default: geminiDefaultModelId }, deepseek: { field: "apiModelId", default: deepSeekDefaultModelId }, + moonshot: { field: "apiModelId", default: moonshotDefaultModelId }, mistral: { field: "apiModelId", default: mistralDefaultModelId }, xai: { field: "apiModelId", default: xaiDefaultModelId }, groq: { field: "apiModelId", default: groqDefaultModelId }, @@ -464,6 +467,10 @@ const ApiOptions = ({ )} + {selectedProvider === "moonshot" && ( + + )} + {selectedProvider === "vscode-lm" && ( )} diff --git a/webview-ui/src/components/settings/constants.ts b/webview-ui/src/components/settings/constants.ts index bbee8f990d..1140e4c0bc 100644 --- a/webview-ui/src/components/settings/constants.ts +++ b/webview-ui/src/components/settings/constants.ts @@ -5,6 +5,7 @@ import { bedrockModels, claudeCodeModels, deepSeekModels, + moonshotModels, geminiModels, mistralModels, openAiNativeModels, @@ -19,6 +20,7 @@ export const MODELS_BY_PROVIDER: Partial void +} + +export const Moonshot = ({ apiConfiguration, setApiConfigurationField }: MoonshotProps) => { + const { t } = useAppTranslation() + + const handleInputChange = useCallback( + ( + field: K, + transform: (event: E) => ProviderSettings[K] = inputEventTransform, + ) => + (event: E | Event) => { + setApiConfigurationField(field, transform(event as E)) + }, + [setApiConfigurationField], + ) + + return ( + <> +
+ + + + api.moonshot.ai + + + api.moonshot.cn + + +
+
+ + + +
+ {t("settings:providers.apiKeyStorageNotice")} +
+ {!apiConfiguration?.moonshotApiKey && ( + + {t("settings:providers.getMoonshotApiKey")} + + )} +
+ + ) +} diff --git a/webview-ui/src/components/settings/providers/index.ts b/webview-ui/src/components/settings/providers/index.ts index b195607430..54974f7200 100644 --- a/webview-ui/src/components/settings/providers/index.ts +++ b/webview-ui/src/components/settings/providers/index.ts @@ -8,6 +8,7 @@ export { Glama } from "./Glama" export { Groq } from "./Groq" export { LMStudio } from "./LMStudio" export { Mistral } from "./Mistral" +export { Moonshot } from "./Moonshot" export { Ollama } from "./Ollama" export { OpenAI } from "./OpenAI" export { OpenAICompatible } from "./OpenAICompatible" diff --git a/webview-ui/src/components/ui/hooks/useSelectedModel.ts b/webview-ui/src/components/ui/hooks/useSelectedModel.ts index 40c1ff2431..928ebb42f4 100644 --- a/webview-ui/src/components/ui/hooks/useSelectedModel.ts +++ b/webview-ui/src/components/ui/hooks/useSelectedModel.ts @@ -8,6 +8,8 @@ import { bedrockModels, deepSeekDefaultModelId, deepSeekModels, + moonshotDefaultModelId, + moonshotModels, geminiDefaultModelId, geminiModels, mistralDefaultModelId, @@ -162,6 +164,11 @@ function getSelectedModel({ const info = deepSeekModels[id as keyof typeof deepSeekModels] return { id, info } } + case "moonshot": { + const id = apiConfiguration.apiModelId ?? moonshotDefaultModelId + const info = moonshotModels[id as keyof typeof moonshotModels] + return { id, info } + } case "openai-native": { const id = apiConfiguration.apiModelId ?? openAiNativeDefaultModelId const info = openAiNativeModels[id as keyof typeof openAiNativeModels] @@ -211,6 +218,7 @@ function getSelectedModel({ // case "human-relay": // case "fake-ai": default: { + provider satisfies "anthropic" | "gemini-cli" | "human-relay" | "fake-ai" const id = apiConfiguration.apiModelId ?? anthropicDefaultModelId const info = anthropicModels[id as keyof typeof anthropicModels] return { id, info } diff --git a/webview-ui/src/i18n/locales/ca/settings.json b/webview-ui/src/i18n/locales/ca/settings.json index 8f270ee4ce..cf17a6f919 100644 --- a/webview-ui/src/i18n/locales/ca/settings.json +++ b/webview-ui/src/i18n/locales/ca/settings.json @@ -253,6 +253,9 @@ "getChutesApiKey": "Obtenir clau API de Chutes", "deepSeekApiKey": "Clau API de DeepSeek", "getDeepSeekApiKey": "Obtenir clau API de DeepSeek", + "moonshotApiKey": "Clau API de Moonshot", + "getMoonshotApiKey": "Obtenir clau API de Moonshot", + "moonshotBaseUrl": "Punt d'entrada de Moonshot", "geminiApiKey": "Clau API de Gemini", "getGroqApiKey": "Obtenir clau API de Groq", "groqApiKey": "Clau API de Groq", diff --git a/webview-ui/src/i18n/locales/de/settings.json b/webview-ui/src/i18n/locales/de/settings.json index 73a4ede3d8..20ffaec525 100644 --- a/webview-ui/src/i18n/locales/de/settings.json +++ b/webview-ui/src/i18n/locales/de/settings.json @@ -253,6 +253,9 @@ "getChutesApiKey": "Chutes API-Schlüssel erhalten", "deepSeekApiKey": "DeepSeek API-Schlüssel", "getDeepSeekApiKey": "DeepSeek API-Schlüssel erhalten", + "moonshotApiKey": "Moonshot API-Schlüssel", + "getMoonshotApiKey": "Moonshot API-Schlüssel erhalten", + "moonshotBaseUrl": "Moonshot-Einstiegspunkt", "geminiApiKey": "Gemini API-Schlüssel", "getGroqApiKey": "Groq API-Schlüssel erhalten", "groqApiKey": "Groq API-Schlüssel", diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index 457b4dbda9..4a826bddab 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -253,6 +253,9 @@ "getChutesApiKey": "Get Chutes API Key", "deepSeekApiKey": "DeepSeek API Key", "getDeepSeekApiKey": "Get DeepSeek API Key", + "moonshotApiKey": "Moonshot API Key", + "getMoonshotApiKey": "Get Moonshot API Key", + "moonshotBaseUrl": "Moonshot Entrypoint", "geminiApiKey": "Gemini API Key", "getGroqApiKey": "Get Groq API Key", "groqApiKey": "Groq API Key", diff --git a/webview-ui/src/i18n/locales/es/settings.json b/webview-ui/src/i18n/locales/es/settings.json index d217e66226..4c4f24bb0f 100644 --- a/webview-ui/src/i18n/locales/es/settings.json +++ b/webview-ui/src/i18n/locales/es/settings.json @@ -253,6 +253,9 @@ "getChutesApiKey": "Obtener clave API de Chutes", "deepSeekApiKey": "Clave API de DeepSeek", "getDeepSeekApiKey": "Obtener clave API de DeepSeek", + "moonshotApiKey": "Clave API de Moonshot", + "getMoonshotApiKey": "Obtener clave API de Moonshot", + "moonshotBaseUrl": "Punto de entrada de Moonshot", "geminiApiKey": "Clave API de Gemini", "getGroqApiKey": "Obtener clave API de Groq", "groqApiKey": "Clave API de Groq", diff --git a/webview-ui/src/i18n/locales/fr/settings.json b/webview-ui/src/i18n/locales/fr/settings.json index f7f9a2683a..6e7a964694 100644 --- a/webview-ui/src/i18n/locales/fr/settings.json +++ b/webview-ui/src/i18n/locales/fr/settings.json @@ -253,6 +253,9 @@ "getChutesApiKey": "Obtenir la clé API Chutes", "deepSeekApiKey": "Clé API DeepSeek", "getDeepSeekApiKey": "Obtenir la clé API DeepSeek", + "moonshotApiKey": "Clé API Moonshot", + "getMoonshotApiKey": "Obtenir la clé API Moonshot", + "moonshotBaseUrl": "Point d'entrée Moonshot", "geminiApiKey": "Clé API Gemini", "getGroqApiKey": "Obtenir la clé API Groq", "groqApiKey": "Clé API Groq", diff --git a/webview-ui/src/i18n/locales/hi/settings.json b/webview-ui/src/i18n/locales/hi/settings.json index 6a3679e090..ca255ca44e 100644 --- a/webview-ui/src/i18n/locales/hi/settings.json +++ b/webview-ui/src/i18n/locales/hi/settings.json @@ -253,6 +253,9 @@ "getChutesApiKey": "Chutes API कुंजी प्राप्त करें", "deepSeekApiKey": "DeepSeek API कुंजी", "getDeepSeekApiKey": "DeepSeek API कुंजी प्राप्त करें", + "moonshotApiKey": "Moonshot API कुंजी", + "getMoonshotApiKey": "Moonshot API कुंजी प्राप्त करें", + "moonshotBaseUrl": "Moonshot प्रवेश बिंदु", "geminiApiKey": "Gemini API कुंजी", "getGroqApiKey": "Groq API कुंजी प्राप्त करें", "groqApiKey": "Groq API कुंजी", diff --git a/webview-ui/src/i18n/locales/id/settings.json b/webview-ui/src/i18n/locales/id/settings.json index 09d7e8254a..0bff5ef4a1 100644 --- a/webview-ui/src/i18n/locales/id/settings.json +++ b/webview-ui/src/i18n/locales/id/settings.json @@ -257,6 +257,9 @@ "getChutesApiKey": "Dapatkan Chutes API Key", "deepSeekApiKey": "DeepSeek API Key", "getDeepSeekApiKey": "Dapatkan DeepSeek API Key", + "moonshotApiKey": "Kunci API Moonshot", + "getMoonshotApiKey": "Dapatkan Kunci API Moonshot", + "moonshotBaseUrl": "Titik Masuk Moonshot", "geminiApiKey": "Gemini API Key", "getGroqApiKey": "Dapatkan Groq API Key", "groqApiKey": "Groq API Key", diff --git a/webview-ui/src/i18n/locales/it/settings.json b/webview-ui/src/i18n/locales/it/settings.json index c2836f2c71..e5cd37a110 100644 --- a/webview-ui/src/i18n/locales/it/settings.json +++ b/webview-ui/src/i18n/locales/it/settings.json @@ -253,6 +253,9 @@ "getChutesApiKey": "Ottieni chiave API Chutes", "deepSeekApiKey": "Chiave API DeepSeek", "getDeepSeekApiKey": "Ottieni chiave API DeepSeek", + "moonshotApiKey": "Chiave API Moonshot", + "getMoonshotApiKey": "Ottieni chiave API Moonshot", + "moonshotBaseUrl": "Punto di ingresso Moonshot", "geminiApiKey": "Chiave API Gemini", "getGroqApiKey": "Ottieni chiave API Groq", "groqApiKey": "Chiave API Groq", diff --git a/webview-ui/src/i18n/locales/ja/settings.json b/webview-ui/src/i18n/locales/ja/settings.json index 01233ef505..e32fac776a 100644 --- a/webview-ui/src/i18n/locales/ja/settings.json +++ b/webview-ui/src/i18n/locales/ja/settings.json @@ -253,6 +253,9 @@ "getChutesApiKey": "Chutes APIキーを取得", "deepSeekApiKey": "DeepSeek APIキー", "getDeepSeekApiKey": "DeepSeek APIキーを取得", + "moonshotApiKey": "Moonshot APIキー", + "getMoonshotApiKey": "Moonshot APIキーを取得", + "moonshotBaseUrl": "Moonshot エントリーポイント", "geminiApiKey": "Gemini APIキー", "getGroqApiKey": "Groq APIキーを取得", "groqApiKey": "Groq APIキー", diff --git a/webview-ui/src/i18n/locales/ko/settings.json b/webview-ui/src/i18n/locales/ko/settings.json index 806b11f022..f48860b0ae 100644 --- a/webview-ui/src/i18n/locales/ko/settings.json +++ b/webview-ui/src/i18n/locales/ko/settings.json @@ -253,6 +253,9 @@ "getChutesApiKey": "Chutes API 키 받기", "deepSeekApiKey": "DeepSeek API 키", "getDeepSeekApiKey": "DeepSeek API 키 받기", + "moonshotApiKey": "Moonshot API 키", + "getMoonshotApiKey": "Moonshot API 키 받기", + "moonshotBaseUrl": "Moonshot 엔트리포인트", "geminiApiKey": "Gemini API 키", "getGroqApiKey": "Groq API 키 받기", "groqApiKey": "Groq API 키", diff --git a/webview-ui/src/i18n/locales/nl/settings.json b/webview-ui/src/i18n/locales/nl/settings.json index 28edfdfd52..bf6e65c995 100644 --- a/webview-ui/src/i18n/locales/nl/settings.json +++ b/webview-ui/src/i18n/locales/nl/settings.json @@ -253,6 +253,9 @@ "getChutesApiKey": "Chutes API-sleutel ophalen", "deepSeekApiKey": "DeepSeek API-sleutel", "getDeepSeekApiKey": "DeepSeek API-sleutel ophalen", + "moonshotApiKey": "Moonshot API-sleutel", + "getMoonshotApiKey": "Moonshot API-sleutel ophalen", + "moonshotBaseUrl": "Moonshot-ingangspunt", "geminiApiKey": "Gemini API-sleutel", "getGroqApiKey": "Groq API-sleutel ophalen", "groqApiKey": "Groq API-sleutel", diff --git a/webview-ui/src/i18n/locales/pl/settings.json b/webview-ui/src/i18n/locales/pl/settings.json index d034830c1a..d42e4f51a9 100644 --- a/webview-ui/src/i18n/locales/pl/settings.json +++ b/webview-ui/src/i18n/locales/pl/settings.json @@ -253,6 +253,9 @@ "getChutesApiKey": "Uzyskaj klucz API Chutes", "deepSeekApiKey": "Klucz API DeepSeek", "getDeepSeekApiKey": "Uzyskaj klucz API DeepSeek", + "moonshotApiKey": "Klucz API Moonshot", + "getMoonshotApiKey": "Uzyskaj klucz API Moonshot", + "moonshotBaseUrl": "Punkt wejścia Moonshot", "geminiApiKey": "Klucz API Gemini", "getGroqApiKey": "Uzyskaj klucz API Groq", "groqApiKey": "Klucz API Groq", diff --git a/webview-ui/src/i18n/locales/pt-BR/settings.json b/webview-ui/src/i18n/locales/pt-BR/settings.json index 140042c773..924542cf09 100644 --- a/webview-ui/src/i18n/locales/pt-BR/settings.json +++ b/webview-ui/src/i18n/locales/pt-BR/settings.json @@ -253,6 +253,9 @@ "getChutesApiKey": "Obter chave de API Chutes", "deepSeekApiKey": "Chave de API DeepSeek", "getDeepSeekApiKey": "Obter chave de API DeepSeek", + "moonshotApiKey": "Chave de API Moonshot", + "getMoonshotApiKey": "Obter chave de API Moonshot", + "moonshotBaseUrl": "Ponto de entrada Moonshot", "geminiApiKey": "Chave de API Gemini", "getGroqApiKey": "Obter chave de API Groq", "groqApiKey": "Chave de API Groq", diff --git a/webview-ui/src/i18n/locales/ru/settings.json b/webview-ui/src/i18n/locales/ru/settings.json index 895655fbdf..cf719b0976 100644 --- a/webview-ui/src/i18n/locales/ru/settings.json +++ b/webview-ui/src/i18n/locales/ru/settings.json @@ -253,6 +253,9 @@ "getChutesApiKey": "Получить Chutes API-ключ", "deepSeekApiKey": "DeepSeek API-ключ", "getDeepSeekApiKey": "Получить DeepSeek API-ключ", + "moonshotApiKey": "Moonshot API-ключ", + "getMoonshotApiKey": "Получить Moonshot API-ключ", + "moonshotBaseUrl": "Точка входа Moonshot", "geminiApiKey": "Gemini API-ключ", "getGroqApiKey": "Получить Groq API-ключ", "groqApiKey": "Groq API-ключ", diff --git a/webview-ui/src/i18n/locales/tr/settings.json b/webview-ui/src/i18n/locales/tr/settings.json index 06878be920..fc8fc9c677 100644 --- a/webview-ui/src/i18n/locales/tr/settings.json +++ b/webview-ui/src/i18n/locales/tr/settings.json @@ -253,6 +253,9 @@ "getChutesApiKey": "Chutes API Anahtarı Al", "deepSeekApiKey": "DeepSeek API Anahtarı", "getDeepSeekApiKey": "DeepSeek API Anahtarı Al", + "moonshotApiKey": "Moonshot API Anahtarı", + "getMoonshotApiKey": "Moonshot API Anahtarı Al", + "moonshotBaseUrl": "Moonshot Giriş Noktası", "geminiApiKey": "Gemini API Anahtarı", "getGroqApiKey": "Groq API Anahtarı Al", "groqApiKey": "Groq API Anahtarı", diff --git a/webview-ui/src/i18n/locales/vi/settings.json b/webview-ui/src/i18n/locales/vi/settings.json index c83b7a6642..7d3e2803ad 100644 --- a/webview-ui/src/i18n/locales/vi/settings.json +++ b/webview-ui/src/i18n/locales/vi/settings.json @@ -253,6 +253,9 @@ "getChutesApiKey": "Lấy khóa API Chutes", "deepSeekApiKey": "Khóa API DeepSeek", "getDeepSeekApiKey": "Lấy khóa API DeepSeek", + "moonshotApiKey": "Khóa API Moonshot", + "getMoonshotApiKey": "Lấy khóa API Moonshot", + "moonshotBaseUrl": "Điểm vào Moonshot", "geminiApiKey": "Khóa API Gemini", "getGroqApiKey": "Lấy khóa API Groq", "groqApiKey": "Khóa API Groq", diff --git a/webview-ui/src/i18n/locales/zh-CN/settings.json b/webview-ui/src/i18n/locales/zh-CN/settings.json index 11c469bf77..eae71ac706 100644 --- a/webview-ui/src/i18n/locales/zh-CN/settings.json +++ b/webview-ui/src/i18n/locales/zh-CN/settings.json @@ -253,6 +253,9 @@ "getChutesApiKey": "获取 Chutes API 密钥", "deepSeekApiKey": "DeepSeek API 密钥", "getDeepSeekApiKey": "获取 DeepSeek API 密钥", + "moonshotApiKey": "Moonshot API 密钥", + "getMoonshotApiKey": "获取 Moonshot API 密钥", + "moonshotBaseUrl": "Moonshot 服务站点", "geminiApiKey": "Gemini API 密钥", "getGroqApiKey": "获取 Groq API 密钥", "groqApiKey": "Groq API 密钥", diff --git a/webview-ui/src/i18n/locales/zh-TW/settings.json b/webview-ui/src/i18n/locales/zh-TW/settings.json index dab70b88b1..420ece916e 100644 --- a/webview-ui/src/i18n/locales/zh-TW/settings.json +++ b/webview-ui/src/i18n/locales/zh-TW/settings.json @@ -253,6 +253,9 @@ "getChutesApiKey": "取得 Chutes API 金鑰", "deepSeekApiKey": "DeepSeek API 金鑰", "getDeepSeekApiKey": "取得 DeepSeek API 金鑰", + "moonshotApiKey": "Moonshot API 金鑰", + "getMoonshotApiKey": "取得 Moonshot API 金鑰", + "moonshotBaseUrl": "Moonshot 服務站點", "geminiApiKey": "Gemini API 金鑰", "getGroqApiKey": "取得 Groq API 金鑰", "groqApiKey": "Groq API 金鑰", From e78d9541ca7cf72415bbc521165b9853576bae01 Mon Sep 17 00:00:00 2001 From: John Richmond <5629+jr@users.noreply.github.com> Date: Tue, 22 Jul 2025 07:16:15 -0700 Subject: [PATCH 06/92] Bugfix: Cloud: be more specific about session error codes (#6051) InvalidClientTokenError indicates an unrecoverable state for the session, so we need to be more exact about triggering it. A recent Clerk outage resulted in a lot of 429 responses which should really cause inactive-session, not a full clear to logged-out. --- packages/cloud/src/auth/WebAuthService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cloud/src/auth/WebAuthService.ts b/packages/cloud/src/auth/WebAuthService.ts index d14cbe67d8..82d3122426 100644 --- a/packages/cloud/src/auth/WebAuthService.ts +++ b/packages/cloud/src/auth/WebAuthService.ts @@ -494,7 +494,7 @@ export class WebAuthService extends EventEmitter implements A signal: AbortSignal.timeout(10000), }) - if (response.status >= 400 && response.status < 500) { + if (response.status === 401 || response.status === 404) { throw new InvalidClientTokenError() } else if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`) From 984d368f7ac024919a1707ceb632a2ddd5d1369b Mon Sep 17 00:00:00 2001 From: Chris Estreich Date: Tue, 22 Jul 2025 09:18:38 -0700 Subject: [PATCH 07/92] Fix evals; broken by #5865 (#6065) --- apps/web-evals/scripts/check-services.sh | 4 +- apps/web-evals/src/app/runs/new/new-run.tsx | 4 +- package.json | 2 +- packages/evals/package.json | 11 +- .../migrations/0001_lowly_captain_flint.sql | 1 + .../src/db/migrations/meta/0001_snapshot.json | 417 ++++++++++++++++++ .../src/db/migrations/meta/_journal.json | 7 + 7 files changed, 436 insertions(+), 10 deletions(-) create mode 100644 packages/evals/src/db/migrations/0001_lowly_captain_flint.sql create mode 100644 packages/evals/src/db/migrations/meta/0001_snapshot.json diff --git a/apps/web-evals/scripts/check-services.sh b/apps/web-evals/scripts/check-services.sh index fd1e74997c..104a472208 100755 --- a/apps/web-evals/scripts/check-services.sh +++ b/apps/web-evals/scripts/check-services.sh @@ -7,13 +7,13 @@ fi if ! nc -z localhost 5432 2>/dev/null; then echo "❌ PostgreSQL is not running on port 5432" - echo "💡 Start it with: pnpm --filter @roo-code/evals db:start" + echo "💡 Start it with: pnpm --filter @roo-code/evals db:up" exit 1 fi if ! nc -z localhost 6379 2>/dev/null; then echo "❌ Redis is not running on port 6379" - echo "💡 Start it with: pnpm --filter @roo-code/evals redis:start" + echo "💡 Start it with: pnpm --filter @roo-code/evals redis:up" exit 1 fi diff --git a/apps/web-evals/src/app/runs/new/new-run.tsx b/apps/web-evals/src/app/runs/new/new-run.tsx index 90717d6fec..f8633611b6 100644 --- a/apps/web-evals/src/app/runs/new/new-run.tsx +++ b/apps/web-evals/src/app/runs/new/new-run.tsx @@ -350,7 +350,7 @@ export function NewRun() { name="timeout" render={({ field }) => ( - Timeout (minutes) + Timeout (Minutes)
field.onChange(value[0])} /> -
{field.value} min
+
{field.value}
diff --git a/package.json b/package.json index 61f1f6cdaf..99becf0a0c 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "changeset:version": "cp CHANGELOG.md src/CHANGELOG.md && changeset version && cp -vf src/CHANGELOG.md .", "knip": "knip --include files", "update-contributors": "node scripts/update-contributors.js", - "evals": "docker compose -f packages/evals/docker-compose.yml --profile server --profile runner up --build --scale runner=0" + "evals": "dotenvx run -f packages/evals/.env.development packages/evals/.env.local -- docker compose -f packages/evals/docker-compose.yml --profile server --profile runner up --build --scale runner=0" }, "devDependencies": { "@changesets/cli": "^2.27.10", diff --git a/packages/evals/package.json b/packages/evals/package.json index 3d1cfb3e92..83690a99c4 100644 --- a/packages/evals/package.json +++ b/packages/evals/package.json @@ -18,11 +18,12 @@ "db:push": "pnpm drizzle-kit push", "db:test:push": "pnpm drizzle-kit:test push", "db:production:push": "pnpm drizzle-kit:production push", - "db:start": "docker compose up -d db", - "db:stop": "docker compose down db", - "redis:start": "docker compose up -d redis", - "redis:stop": "docker compose down redis", - "services:start": "docker compose up -d db redis" + "db:up": "dotenvx run -f .env.development .env.local -- docker compose up -d db", + "db:down": "dotenvx run -f .env.development .env.local -- docker compose down db", + "redis:up": "dotenvx run -f .env.development .env.local -- docker compose up -d redis", + "redis:down": "dotenvx run -f .env.development .env.local -- docker compose down redis", + "services:up": "dotenvx run -f .env.development .env.local -- docker compose up -d db redis", + "services:down": "dotenvx run -f .env.development .env.local -- docker compose down db redis" }, "dependencies": { "@roo-code/ipc": "workspace:^", diff --git a/packages/evals/src/db/migrations/0001_lowly_captain_flint.sql b/packages/evals/src/db/migrations/0001_lowly_captain_flint.sql new file mode 100644 index 0000000000..16d3cc1bdd --- /dev/null +++ b/packages/evals/src/db/migrations/0001_lowly_captain_flint.sql @@ -0,0 +1 @@ +ALTER TABLE "runs" ADD COLUMN "timeout" integer DEFAULT 5 NOT NULL; \ No newline at end of file diff --git a/packages/evals/src/db/migrations/meta/0001_snapshot.json b/packages/evals/src/db/migrations/meta/0001_snapshot.json new file mode 100644 index 0000000000..194fd6055c --- /dev/null +++ b/packages/evals/src/db/migrations/meta/0001_snapshot.json @@ -0,0 +1,417 @@ +{ + "id": "43b197c4-ff4f-48c1-908b-a330e66a162d", + "prevId": "b50d5e6a-0f3f-4605-a5e7-9351711fc5e4", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.runs": { + "name": "runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "runs_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "task_metrics_id": { + "name": "task_metrics_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "settings": { + "name": "settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "pid": { + "name": "pid", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "socket_path": { + "name": "socket_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "concurrency": { + "name": "concurrency", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 2 + }, + "timeout": { + "name": "timeout", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 5 + }, + "passed": { + "name": "passed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "failed": { + "name": "failed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "runs_task_metrics_id_taskMetrics_id_fk": { + "name": "runs_task_metrics_id_taskMetrics_id_fk", + "tableFrom": "runs", + "tableTo": "taskMetrics", + "columnsFrom": ["task_metrics_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.taskMetrics": { + "name": "taskMetrics", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "taskMetrics_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "tokens_in": { + "name": "tokens_in", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "tokens_out": { + "name": "tokens_out", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "tokens_context": { + "name": "tokens_context", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "cache_writes": { + "name": "cache_writes", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "cache_reads": { + "name": "cache_reads", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "cost": { + "name": "cost", + "type": "real", + "primaryKey": false, + "notNull": true + }, + "duration": { + "name": "duration", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "tool_usage": { + "name": "tool_usage", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tasks": { + "name": "tasks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "tasks_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "run_id": { + "name": "run_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "task_metrics_id": { + "name": "task_metrics_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "language": { + "name": "language", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "exercise": { + "name": "exercise", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "passed": { + "name": "passed", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "tasks_language_exercise_idx": { + "name": "tasks_language_exercise_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "language", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "exercise", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tasks_run_id_runs_id_fk": { + "name": "tasks_run_id_runs_id_fk", + "tableFrom": "tasks", + "tableTo": "runs", + "columnsFrom": ["run_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "tasks_task_metrics_id_taskMetrics_id_fk": { + "name": "tasks_task_metrics_id_taskMetrics_id_fk", + "tableFrom": "tasks", + "tableTo": "taskMetrics", + "columnsFrom": ["task_metrics_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.toolErrors": { + "name": "toolErrors", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "toolErrors_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "run_id": { + "name": "run_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "task_id": { + "name": "task_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "toolErrors_run_id_runs_id_fk": { + "name": "toolErrors_run_id_runs_id_fk", + "tableFrom": "toolErrors", + "tableTo": "runs", + "columnsFrom": ["run_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "toolErrors_task_id_tasks_id_fk": { + "name": "toolErrors_task_id_tasks_id_fk", + "tableFrom": "toolErrors", + "tableTo": "tasks", + "columnsFrom": ["task_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/packages/evals/src/db/migrations/meta/_journal.json b/packages/evals/src/db/migrations/meta/_journal.json index b26aac5417..e20425b105 100644 --- a/packages/evals/src/db/migrations/meta/_journal.json +++ b/packages/evals/src/db/migrations/meta/_journal.json @@ -8,6 +8,13 @@ "when": 1748937674449, "tag": "0000_young_trauma", "breakpoints": true + }, + { + "idx": 1, + "version": "7", + "when": 1753198630651, + "tag": "0001_lowly_captain_flint", + "breakpoints": true } ] } From 5629199d5134850f80a9045458d34d9e6fa38f29 Mon Sep 17 00:00:00 2001 From: "roomote[bot]" <219738659+roomote[bot]@users.noreply.github.com> Date: Tue, 22 Jul 2025 13:26:32 -0400 Subject: [PATCH 08/92] Add jump icon for newly created files (#5738) * feat: add jump icon for newly created files - Add jump icon to newFileCreated tool case in ChatRow.tsx - Matches existing pattern from readFile case for consistent UX - Allows users to quickly open newly created files - Fixes issue #5736 * fix: remove duplicate file path display in newFileCreated case - Removed redundant ToolUseBlock that was showing file path twice - Added onJumpToFile prop to CodeAccordian component to support jump icon - Jump icon now appears in CodeAccordian header for newFileCreated files - Maintains consistent UX with existing file operations while avoiding duplication Fixes feedback from @daniel-lxs about duplicate elements being shown * fix: address PR feedback for jump icon on new files - Fix openFile message to use correct path format with './' prefix - Remove duplicate chevron icon when jump icon is present - Add aria-label for accessibility - Fix styling: use mr-1 to match progressStatus icon - Remove redundant margin style from jump icon --------- Co-authored-by: Roo Code Co-authored-by: Roo Code Co-authored-by: Daniel Riccio --- webview-ui/src/components/chat/ChatRow.tsx | 1 + .../src/components/common/CodeAccordian.tsx | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/webview-ui/src/components/chat/ChatRow.tsx b/webview-ui/src/components/chat/ChatRow.tsx index 926bd400f0..4fa921f443 100644 --- a/webview-ui/src/components/chat/ChatRow.tsx +++ b/webview-ui/src/components/chat/ChatRow.tsx @@ -533,6 +533,7 @@ export const ChatRowContent = ({ isLoading={message.partial} isExpanded={isExpanded} onToggleExpand={handleToggleExpand} + onJumpToFile={() => vscode.postMessage({ type: "openFile", text: "./" + tool.path })} /> ) diff --git a/webview-ui/src/components/common/CodeAccordian.tsx b/webview-ui/src/components/common/CodeAccordian.tsx index b07461c70e..7dcef11e10 100644 --- a/webview-ui/src/components/common/CodeAccordian.tsx +++ b/webview-ui/src/components/common/CodeAccordian.tsx @@ -17,6 +17,7 @@ interface CodeAccordianProps { isFeedback?: boolean onToggleExpand: () => void header?: string + onJumpToFile?: () => void } const CodeAccordian = ({ @@ -29,6 +30,7 @@ const CodeAccordian = ({ isFeedback, onToggleExpand, header, + onJumpToFile, }: CodeAccordianProps) => { const inferredLanguage = useMemo(() => language ?? (path ? getLanguageFromPath(path) : "txt"), [path, language]) const source = useMemo(() => code.trim(), [code]) @@ -68,7 +70,18 @@ const CodeAccordian = ({ )} - + {onJumpToFile && path && ( + { + e.stopPropagation() + onJumpToFile() + }} + aria-label={`Open file: ${path}`} + /> + )} + {!onJumpToFile && } )} {(!hasHeader || isExpanded) && ( From dbde23c84e2191e6106e562a52d9ec60ea0d7efa Mon Sep 17 00:00:00 2001 From: "roomote[bot]" <219738659+roomote[bot]@users.noreply.github.com> Date: Tue, 22 Jul 2025 14:15:28 -0700 Subject: [PATCH 09/92] fix: add case sensitivity mention to suggested fixes in apply_diff error message (#6076) Co-authored-by: Roo Code --- src/core/tools/multiApplyDiffTool.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/tools/multiApplyDiffTool.ts b/src/core/tools/multiApplyDiffTool.ts index b41d409dbb..4ddef4880b 100644 --- a/src/core/tools/multiApplyDiffTool.ts +++ b/src/core/tools/multiApplyDiffTool.ts @@ -451,7 +451,7 @@ Diff ${i + 1} failed for file: ${relPath} Error: ${failPart.error} Suggested fixes: -1. Verify the search content exactly matches the file content (including whitespace) +1. Verify the search content exactly matches the file content (including whitespace and case) 2. Check for correct indentation and line endings 3. Use to see the current file content 4. Consider breaking complex changes into smaller diffs From 2b8228ef0c1459692798afae3f38f806d80f9d7d Mon Sep 17 00:00:00 2001 From: Hannes Rudolph Date: Tue, 22 Jul 2025 15:37:27 -0600 Subject: [PATCH 10/92] docs: clarify when to use update_todo_list tool (#5926) * docs: clarify when to use update_todo_list tool Added 'complicated' to the condition for when to use the update_todo_list tool, making it clearer that the tool should be used for tasks that are either complicated OR involve multiple steps. * fix: update vscode mock and snapshots for update_todo_list tool changes - Add missing RelativePattern export to vscode mock - Fix onDidChangeWorkspaceFolders function in workspace mock - Update test snapshots to reflect new "complicated" text in update_todo_list tool documentation - Build tree-sitter WASM files to fix parsing tests Fixes failing CI tests related to PR #5926 documentation changes. * Delete package-lock.json * revert: remove unrelated changes to src/__mocks__/vscode.js --------- Co-authored-by: Roo Code Co-authored-by: Daniel <57051444+daniel-lxs@users.noreply.github.com> --- .../add-custom-instructions/architect-mode-prompt.snap | 2 +- .../__snapshots__/add-custom-instructions/ask-mode-prompt.snap | 2 +- .../add-custom-instructions/mcp-server-creation-disabled.snap | 2 +- .../add-custom-instructions/mcp-server-creation-enabled.snap | 2 +- .../add-custom-instructions/partial-reads-enabled.snap | 2 +- .../__snapshots__/system-prompt/consistent-system-prompt.snap | 2 +- .../__snapshots__/system-prompt/with-computer-use-support.snap | 2 +- .../__snapshots__/system-prompt/with-diff-enabled-false.snap | 2 +- .../__snapshots__/system-prompt/with-diff-enabled-true.snap | 2 +- .../system-prompt/with-diff-enabled-undefined.snap | 2 +- .../system-prompt/with-different-viewport-size.snap | 2 +- .../__snapshots__/system-prompt/with-mcp-hub-provided.snap | 2 +- .../__snapshots__/system-prompt/with-undefined-mcp-hub.snap | 2 +- src/core/prompts/tools/update-todo-list.ts | 2 +- 14 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/architect-mode-prompt.snap b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/architect-mode-prompt.snap index 273e43d20b..632273dea0 100644 --- a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/architect-mode-prompt.snap +++ b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/architect-mode-prompt.snap @@ -420,7 +420,7 @@ Replace the entire TODO list with an updated checklist reflecting the current st **When to Use:** -- The task involves multiple steps or requires ongoing tracking. +- The task is complicated or involves multiple steps or requires ongoing tracking. - You need to update the status of several todos at once. - New actionable items are discovered during task execution. - The user requests a todo list or provides multiple tasks. diff --git a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/ask-mode-prompt.snap b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/ask-mode-prompt.snap index a169a1f3af..09b6b04348 100644 --- a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/ask-mode-prompt.snap +++ b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/ask-mode-prompt.snap @@ -317,7 +317,7 @@ Replace the entire TODO list with an updated checklist reflecting the current st **When to Use:** -- The task involves multiple steps or requires ongoing tracking. +- The task is complicated or involves multiple steps or requires ongoing tracking. - You need to update the status of several todos at once. - New actionable items are discovered during task execution. - The user requests a todo list or provides multiple tasks. diff --git a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-disabled.snap b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-disabled.snap index 273e43d20b..632273dea0 100644 --- a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-disabled.snap +++ b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-disabled.snap @@ -420,7 +420,7 @@ Replace the entire TODO list with an updated checklist reflecting the current st **When to Use:** -- The task involves multiple steps or requires ongoing tracking. +- The task is complicated or involves multiple steps or requires ongoing tracking. - You need to update the status of several todos at once. - New actionable items are discovered during task execution. - The user requests a todo list or provides multiple tasks. diff --git a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-enabled.snap b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-enabled.snap index 8e1b90a1cf..7ca32b80a1 100644 --- a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-enabled.snap +++ b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-enabled.snap @@ -469,7 +469,7 @@ Replace the entire TODO list with an updated checklist reflecting the current st **When to Use:** -- The task involves multiple steps or requires ongoing tracking. +- The task is complicated or involves multiple steps or requires ongoing tracking. - You need to update the status of several todos at once. - New actionable items are discovered during task execution. - The user requests a todo list or provides multiple tasks. diff --git a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/partial-reads-enabled.snap b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/partial-reads-enabled.snap index 7ee1ab207f..7dce6219f3 100644 --- a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/partial-reads-enabled.snap +++ b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/partial-reads-enabled.snap @@ -425,7 +425,7 @@ Replace the entire TODO list with an updated checklist reflecting the current st **When to Use:** -- The task involves multiple steps or requires ongoing tracking. +- The task is complicated or involves multiple steps or requires ongoing tracking. - You need to update the status of several todos at once. - New actionable items are discovered during task execution. - The user requests a todo list or provides multiple tasks. diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/consistent-system-prompt.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/consistent-system-prompt.snap index 273e43d20b..632273dea0 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/consistent-system-prompt.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/consistent-system-prompt.snap @@ -420,7 +420,7 @@ Replace the entire TODO list with an updated checklist reflecting the current st **When to Use:** -- The task involves multiple steps or requires ongoing tracking. +- The task is complicated or involves multiple steps or requires ongoing tracking. - You need to update the status of several todos at once. - New actionable items are discovered during task execution. - The user requests a todo list or provides multiple tasks. diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-computer-use-support.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-computer-use-support.snap index e23dc220b4..419049609e 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-computer-use-support.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-computer-use-support.snap @@ -473,7 +473,7 @@ Replace the entire TODO list with an updated checklist reflecting the current st **When to Use:** -- The task involves multiple steps or requires ongoing tracking. +- The task is complicated or involves multiple steps or requires ongoing tracking. - You need to update the status of several todos at once. - New actionable items are discovered during task execution. - The user requests a todo list or provides multiple tasks. diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-false.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-false.snap index 273e43d20b..632273dea0 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-false.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-false.snap @@ -420,7 +420,7 @@ Replace the entire TODO list with an updated checklist reflecting the current st **When to Use:** -- The task involves multiple steps or requires ongoing tracking. +- The task is complicated or involves multiple steps or requires ongoing tracking. - You need to update the status of several todos at once. - New actionable items are discovered during task execution. - The user requests a todo list or provides multiple tasks. diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-true.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-true.snap index ad90a58850..4390b95519 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-true.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-true.snap @@ -508,7 +508,7 @@ Replace the entire TODO list with an updated checklist reflecting the current st **When to Use:** -- The task involves multiple steps or requires ongoing tracking. +- The task is complicated or involves multiple steps or requires ongoing tracking. - You need to update the status of several todos at once. - New actionable items are discovered during task execution. - The user requests a todo list or provides multiple tasks. diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-undefined.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-undefined.snap index 273e43d20b..632273dea0 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-undefined.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-undefined.snap @@ -420,7 +420,7 @@ Replace the entire TODO list with an updated checklist reflecting the current st **When to Use:** -- The task involves multiple steps or requires ongoing tracking. +- The task is complicated or involves multiple steps or requires ongoing tracking. - You need to update the status of several todos at once. - New actionable items are discovered during task execution. - The user requests a todo list or provides multiple tasks. diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-different-viewport-size.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-different-viewport-size.snap index 005813848f..191816f180 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-different-viewport-size.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-different-viewport-size.snap @@ -473,7 +473,7 @@ Replace the entire TODO list with an updated checklist reflecting the current st **When to Use:** -- The task involves multiple steps or requires ongoing tracking. +- The task is complicated or involves multiple steps or requires ongoing tracking. - You need to update the status of several todos at once. - New actionable items are discovered during task execution. - The user requests a todo list or provides multiple tasks. diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-mcp-hub-provided.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-mcp-hub-provided.snap index 8e1b90a1cf..7ca32b80a1 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-mcp-hub-provided.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-mcp-hub-provided.snap @@ -469,7 +469,7 @@ Replace the entire TODO list with an updated checklist reflecting the current st **When to Use:** -- The task involves multiple steps or requires ongoing tracking. +- The task is complicated or involves multiple steps or requires ongoing tracking. - You need to update the status of several todos at once. - New actionable items are discovered during task execution. - The user requests a todo list or provides multiple tasks. diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-undefined-mcp-hub.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-undefined-mcp-hub.snap index 273e43d20b..632273dea0 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-undefined-mcp-hub.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-undefined-mcp-hub.snap @@ -420,7 +420,7 @@ Replace the entire TODO list with an updated checklist reflecting the current st **When to Use:** -- The task involves multiple steps or requires ongoing tracking. +- The task is complicated or involves multiple steps or requires ongoing tracking. - You need to update the status of several todos at once. - New actionable items are discovered during task execution. - The user requests a todo list or provides multiple tasks. diff --git a/src/core/prompts/tools/update-todo-list.ts b/src/core/prompts/tools/update-todo-list.ts index 528d5a1b51..30100617df 100644 --- a/src/core/prompts/tools/update-todo-list.ts +++ b/src/core/prompts/tools/update-todo-list.ts @@ -56,7 +56,7 @@ Replace the entire TODO list with an updated checklist reflecting the current st **When to Use:** -- The task involves multiple steps or requires ongoing tracking. +- The task is complicated or involves multiple steps or requires ongoing tracking. - You need to update the status of several todos at once. - New actionable items are discovered during task execution. - The user requests a todo list or provides multiple tasks. From c6a29f3f27e04a498fba1aa5634ca30d7be3d234 Mon Sep 17 00:00:00 2001 From: Murilo Pires <50873657+MuriloFP@users.noreply.github.com> Date: Tue, 22 Jul 2025 20:26:18 -0300 Subject: [PATCH 11/92] feat: add llama-4-maverick model to Vertex AI provider (#5808) (#6023) * feat: add llama-4-maverick model to Vertex AI provider (#5808) * fix: update llama-4-maverick pricing to correct values --- packages/types/src/providers/vertex.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/types/src/providers/vertex.ts b/packages/types/src/providers/vertex.ts index c405621f82..a48ebacdfb 100644 --- a/packages/types/src/providers/vertex.ts +++ b/packages/types/src/providers/vertex.ts @@ -273,6 +273,15 @@ export const vertexModels = { maxThinkingTokens: 24_576, supportsReasoningBudget: true, }, + "llama-4-maverick-17b-128e-instruct-maas": { + maxTokens: 8192, + contextWindow: 131072, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0.35, + outputPrice: 1.15, + description: "Meta Llama 4 Maverick 17B Instruct model, 128K context.", + }, } as const satisfies Record export const VERTEX_REGIONS = [ From 8dcc078d85c76420cebc6e03867096334fa5e1c0 Mon Sep 17 00:00:00 2001 From: "roomote[bot]" <219738659+roomote[bot]@users.noreply.github.com> Date: Tue, 22 Jul 2025 21:17:14 -0400 Subject: [PATCH 12/92] feat: add Qwen/Qwen3-235B-A22B-Instruct-2507 model to Chutes AI provider (#6052) Co-authored-by: Roo Code Co-authored-by: Matt Rubens --- packages/types/src/providers/chutes.ts | 10 ++++++++++ src/api/providers/__tests__/chutes.spec.ts | 22 ++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/packages/types/src/providers/chutes.ts b/packages/types/src/providers/chutes.ts index 524f842059..5d81799223 100644 --- a/packages/types/src/providers/chutes.ts +++ b/packages/types/src/providers/chutes.ts @@ -18,6 +18,7 @@ export type ChutesModelId = | "deepseek-ai/DeepSeek-R1-Zero" | "deepseek-ai/DeepSeek-V3-0324" | "Qwen/Qwen3-235B-A22B" + | "Qwen/Qwen3-235B-A22B-Instruct-2507" | "Qwen/Qwen3-32B" | "Qwen/Qwen3-30B-A3B" | "Qwen/Qwen3-14B" @@ -163,6 +164,15 @@ export const chutesModels = { outputPrice: 0, description: "DeepSeek V3 (0324) model.", }, + "Qwen/Qwen3-235B-A22B-Instruct-2507": { + maxTokens: 32768, + contextWindow: 262144, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "Qwen3 235B A22B Instruct 2507 model with 262K context window.", + }, "Qwen/Qwen3-235B-A22B": { maxTokens: 32768, contextWindow: 40960, diff --git a/src/api/providers/__tests__/chutes.spec.ts b/src/api/providers/__tests__/chutes.spec.ts index cf8d9a6e13..419ac50dfd 100644 --- a/src/api/providers/__tests__/chutes.spec.ts +++ b/src/api/providers/__tests__/chutes.spec.ts @@ -163,6 +163,28 @@ describe("ChutesHandler", () => { expect(model.info).toEqual(expect.objectContaining(chutesModels[testModelId])) }) + it("should return Qwen3-235B-A22B-Instruct-2507 model with correct configuration", () => { + const testModelId: ChutesModelId = "Qwen/Qwen3-235B-A22B-Instruct-2507" + const handlerWithModel = new ChutesHandler({ + apiModelId: testModelId, + chutesApiKey: "test-chutes-api-key", + }) + const model = handlerWithModel.getModel() + expect(model.id).toBe(testModelId) + expect(model.info).toEqual( + expect.objectContaining({ + maxTokens: 32768, + contextWindow: 262144, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "Qwen3 235B A22B Instruct 2507 model with 262K context window.", + temperature: 0.5, // Default temperature for non-DeepSeek models + }), + ) + }) + it("completePrompt method should return text from Chutes API", async () => { const expectedResponse = "This is a test response from Chutes" mockCreate.mockResolvedValueOnce({ choices: [{ message: { content: expectedResponse } }] }) From 0cb76d9ae9b321293aabb63002d53caa466c3352 Mon Sep 17 00:00:00 2001 From: Murilo Pires <50873657+MuriloFP@users.noreply.github.com> Date: Tue, 22 Jul 2025 22:31:20 -0300 Subject: [PATCH 13/92] fix: add Git installation check for checkpoints feature (#3109) (#5920) Co-authored-by: Daniel Riccio --- src/core/checkpoints/index.ts | 50 +++++++++++++++++++++++++++--- src/i18n/locales/ca/common.json | 4 ++- src/i18n/locales/de/common.json | 4 ++- src/i18n/locales/en/common.json | 4 ++- src/i18n/locales/es/common.json | 4 ++- src/i18n/locales/fr/common.json | 4 ++- src/i18n/locales/hi/common.json | 4 ++- src/i18n/locales/id/common.json | 4 ++- src/i18n/locales/it/common.json | 4 ++- src/i18n/locales/ja/common.json | 4 ++- src/i18n/locales/ko/common.json | 4 ++- src/i18n/locales/nl/common.json | 4 ++- src/i18n/locales/pl/common.json | 4 ++- src/i18n/locales/pt-BR/common.json | 4 ++- src/i18n/locales/ru/common.json | 4 ++- src/i18n/locales/tr/common.json | 4 ++- src/i18n/locales/vi/common.json | 4 ++- src/i18n/locales/zh-CN/common.json | 4 ++- src/i18n/locales/zh-TW/common.json | 4 ++- src/utils/__tests__/git.spec.ts | 49 +++++++++++++++++++++++++++++ src/utils/git.ts | 11 ++++++- 21 files changed, 159 insertions(+), 23 deletions(-) diff --git a/src/core/checkpoints/index.ts b/src/core/checkpoints/index.ts index dcbe796eb7..02fb5dfc5a 100644 --- a/src/core/checkpoints/index.ts +++ b/src/core/checkpoints/index.ts @@ -6,6 +6,8 @@ import { TelemetryService } from "@roo-code/telemetry" import { Task } from "../task/Task" import { getWorkspacePath } from "../../utils/path" +import { checkGitInstalled } from "../../utils/git" +import { t } from "../../i18n" import { ClineApiReqInfo } from "../../shared/ExtensionMessage" import { getApiMetrics } from "../../shared/getApiMetrics" @@ -70,6 +72,47 @@ export function getCheckpointService(cline: Task) { cline.checkpointServiceInitializing = true + // Check if Git is installed before initializing the service + // Note: This is intentionally fire-and-forget to match the original IIFE pattern + // The service is returned immediately while Git check happens asynchronously + checkGitInstallation(cline, service, log, provider) + + return service + } catch (err) { + log(`[Task#getCheckpointService] ${err.message}`) + cline.enableCheckpoints = false + return undefined + } +} + +async function checkGitInstallation( + cline: Task, + service: RepoPerTaskCheckpointService, + log: (message: string) => void, + provider: any, +) { + try { + const gitInstalled = await checkGitInstalled() + + if (!gitInstalled) { + log("[Task#getCheckpointService] Git is not installed, disabling checkpoints") + cline.enableCheckpoints = false + cline.checkpointServiceInitializing = false + + // Show user-friendly notification + const selection = await vscode.window.showWarningMessage( + t("common:errors.git_not_installed"), + t("common:buttons.learn_more"), + ) + + if (selection === t("common:buttons.learn_more")) { + await vscode.env.openExternal(vscode.Uri.parse("https://git-scm.com/downloads")) + } + + return + } + + // Git is installed, proceed with initialization service.on("initialize", () => { log("[Task#getCheckpointService] service initialized") @@ -115,12 +158,11 @@ export function getCheckpointService(cline: Task) { log(`[Task#getCheckpointService] initShadowGit -> ${err.message}`) cline.enableCheckpoints = false }) - - return service } catch (err) { - log(`[Task#getCheckpointService] ${err.message}`) + log(`[Task#getCheckpointService] Unexpected error during Git check: ${err.message}`) + console.error("Git check error:", err) cline.enableCheckpoints = false - return undefined + cline.checkpointServiceInitializing = false } } diff --git a/src/i18n/locales/ca/common.json b/src/i18n/locales/ca/common.json index 633208d4bc..8ca5ae09a5 100644 --- a/src/i18n/locales/ca/common.json +++ b/src/i18n/locales/ca/common.json @@ -32,6 +32,7 @@ "could_not_open_file_generic": "No s'ha pogut obrir el fitxer!", "checkpoint_timeout": "S'ha esgotat el temps en intentar restaurar el punt de control.", "checkpoint_failed": "Ha fallat la restauració del punt de control.", + "git_not_installed": "Git és necessari per a la funció de punts de control. Si us plau, instal·la Git per activar els punts de control.", "no_workspace": "Si us plau, obre primer una carpeta de projecte", "update_support_prompt": "Ha fallat l'actualització del missatge de suport", "reset_support_prompt": "Ha fallat el restabliment del missatge de suport", @@ -111,7 +112,8 @@ }, "buttons": { "save": "Desar", - "edit": "Editar" + "edit": "Editar", + "learn_more": "Més informació" }, "tasks": { "canceled": "Error de tasca: Ha estat aturada i cancel·lada per l'usuari.", diff --git a/src/i18n/locales/de/common.json b/src/i18n/locales/de/common.json index 71155b1ebe..8853f4da41 100644 --- a/src/i18n/locales/de/common.json +++ b/src/i18n/locales/de/common.json @@ -28,6 +28,7 @@ "could_not_open_file_generic": "Datei konnte nicht geöffnet werden!", "checkpoint_timeout": "Zeitüberschreitung beim Versuch, den Checkpoint wiederherzustellen.", "checkpoint_failed": "Fehler beim Wiederherstellen des Checkpoints.", + "git_not_installed": "Git ist für die Checkpoint-Funktion erforderlich. Bitte installiere Git, um Checkpoints zu aktivieren.", "no_workspace": "Bitte öffne zuerst einen Projektordner", "update_support_prompt": "Fehler beim Aktualisieren der Support-Nachricht", "reset_support_prompt": "Fehler beim Zurücksetzen der Support-Nachricht", @@ -107,7 +108,8 @@ }, "buttons": { "save": "Speichern", - "edit": "Bearbeiten" + "edit": "Bearbeiten", + "learn_more": "Mehr erfahren" }, "tasks": { "canceled": "Aufgabenfehler: Die Aufgabe wurde vom Benutzer gestoppt und abgebrochen.", diff --git a/src/i18n/locales/en/common.json b/src/i18n/locales/en/common.json index 6bab0ab9a9..8adcbfa8cc 100644 --- a/src/i18n/locales/en/common.json +++ b/src/i18n/locales/en/common.json @@ -28,6 +28,7 @@ "could_not_open_file_generic": "Could not open file!", "checkpoint_timeout": "Timed out when attempting to restore checkpoint.", "checkpoint_failed": "Failed to restore checkpoint.", + "git_not_installed": "Git is required for the checkpoints feature. Please install Git to enable checkpoints.", "no_workspace": "Please open a project folder first", "update_support_prompt": "Failed to update support prompt", "reset_support_prompt": "Failed to reset support prompt", @@ -107,7 +108,8 @@ }, "buttons": { "save": "Save", - "edit": "Edit" + "edit": "Edit", + "learn_more": "Learn More" }, "tasks": { "canceled": "Task error: It was stopped and canceled by the user.", diff --git a/src/i18n/locales/es/common.json b/src/i18n/locales/es/common.json index d307800c79..666aa4ec0b 100644 --- a/src/i18n/locales/es/common.json +++ b/src/i18n/locales/es/common.json @@ -28,6 +28,7 @@ "could_not_open_file_generic": "¡No se pudo abrir el archivo!", "checkpoint_timeout": "Se agotó el tiempo al intentar restaurar el punto de control.", "checkpoint_failed": "Error al restaurar el punto de control.", + "git_not_installed": "Git es necesario para la función de puntos de control. Por favor, instala Git para activar los puntos de control.", "no_workspace": "Por favor, abre primero una carpeta de proyecto", "update_support_prompt": "Error al actualizar el mensaje de soporte", "reset_support_prompt": "Error al restablecer el mensaje de soporte", @@ -107,7 +108,8 @@ }, "buttons": { "save": "Guardar", - "edit": "Editar" + "edit": "Editar", + "learn_more": "Más información" }, "tasks": { "canceled": "Error de tarea: Fue detenida y cancelada por el usuario.", diff --git a/src/i18n/locales/fr/common.json b/src/i18n/locales/fr/common.json index b0571e3714..1a29a4c374 100644 --- a/src/i18n/locales/fr/common.json +++ b/src/i18n/locales/fr/common.json @@ -28,6 +28,7 @@ "could_not_open_file_generic": "Impossible d'ouvrir le fichier !", "checkpoint_timeout": "Expiration du délai lors de la tentative de rétablissement du checkpoint.", "checkpoint_failed": "Échec du rétablissement du checkpoint.", + "git_not_installed": "Git est requis pour la fonctionnalité des points de contrôle. Veuillez installer Git pour activer les points de contrôle.", "no_workspace": "Veuillez d'abord ouvrir un espace de travail", "update_support_prompt": "Erreur lors de la mise à jour du prompt de support", "reset_support_prompt": "Erreur lors de la réinitialisation du prompt de support", @@ -107,7 +108,8 @@ }, "buttons": { "save": "Enregistrer", - "edit": "Modifier" + "edit": "Modifier", + "learn_more": "En savoir plus" }, "tasks": { "canceled": "Erreur de tâche : Elle a été arrêtée et annulée par l'utilisateur.", diff --git a/src/i18n/locales/hi/common.json b/src/i18n/locales/hi/common.json index ca4efea535..34331f6683 100644 --- a/src/i18n/locales/hi/common.json +++ b/src/i18n/locales/hi/common.json @@ -28,6 +28,7 @@ "could_not_open_file_generic": "फ़ाइल नहीं खोली जा सकी!", "checkpoint_timeout": "चेकपॉइंट को पुनर्स्थापित करने का प्रयास करते समय टाइमआउट हो गया।", "checkpoint_failed": "चेकपॉइंट पुनर्स्थापित करने में विफल।", + "git_not_installed": "चेकपॉइंट सुविधा के लिए Git आवश्यक है। कृपया चेकपॉइंट সক্ষম करने के लिए Git इंस्टॉल करें।", "no_workspace": "कृपया पहले प्रोजेक्ट फ़ोल्डर खोलें", "update_support_prompt": "सपोर्ट प्रॉम्प्ट अपडेट करने में विफल", "reset_support_prompt": "सपोर्ट प्रॉम्प्ट रीसेट करने में विफल", @@ -107,7 +108,8 @@ }, "buttons": { "save": "सहेजें", - "edit": "संपादित करें" + "edit": "संपादित करें", + "learn_more": "और अधिक जानें" }, "tasks": { "canceled": "टास्क त्रुटि: इसे उपयोगकर्ता द्वारा रोका और रद्द किया गया था।", diff --git a/src/i18n/locales/id/common.json b/src/i18n/locales/id/common.json index 46ce587e61..25e70b3540 100644 --- a/src/i18n/locales/id/common.json +++ b/src/i18n/locales/id/common.json @@ -28,6 +28,7 @@ "could_not_open_file_generic": "Tidak dapat membuka file!", "checkpoint_timeout": "Timeout saat mencoba memulihkan checkpoint.", "checkpoint_failed": "Gagal memulihkan checkpoint.", + "git_not_installed": "Git diperlukan untuk fitur checkpoint. Silakan instal Git untuk mengaktifkan checkpoint.", "no_workspace": "Silakan buka folder proyek terlebih dahulu", "update_support_prompt": "Gagal memperbarui support prompt", "reset_support_prompt": "Gagal mereset support prompt", @@ -107,7 +108,8 @@ }, "buttons": { "save": "Simpan", - "edit": "Edit" + "edit": "Edit", + "learn_more": "Pelajari Lebih Lanjut" }, "tasks": { "canceled": "Error tugas: Dihentikan dan dibatalkan oleh pengguna.", diff --git a/src/i18n/locales/it/common.json b/src/i18n/locales/it/common.json index 11bae26eb3..775175e3d8 100644 --- a/src/i18n/locales/it/common.json +++ b/src/i18n/locales/it/common.json @@ -28,6 +28,7 @@ "could_not_open_file_generic": "Impossibile aprire il file!", "checkpoint_timeout": "Timeout durante il tentativo di ripristinare il checkpoint.", "checkpoint_failed": "Impossibile ripristinare il checkpoint.", + "git_not_installed": "Git è richiesto per la funzione di checkpoint. Per favore, installa Git per abilitare i checkpoint.", "no_workspace": "Per favore, apri prima una cartella di progetto", "update_support_prompt": "Errore durante l'aggiornamento del messaggio di supporto", "reset_support_prompt": "Errore durante il ripristino del messaggio di supporto", @@ -107,7 +108,8 @@ }, "buttons": { "save": "Salva", - "edit": "Modifica" + "edit": "Modifica", + "learn_more": "Scopri di più" }, "tasks": { "canceled": "Errore attività: È stata interrotta e annullata dall'utente.", diff --git a/src/i18n/locales/ja/common.json b/src/i18n/locales/ja/common.json index 52ab094633..ecd60699f8 100644 --- a/src/i18n/locales/ja/common.json +++ b/src/i18n/locales/ja/common.json @@ -28,6 +28,7 @@ "could_not_open_file_generic": "ファイルを開けませんでした!", "checkpoint_timeout": "チェックポイントの復元を試みる際にタイムアウトしました。", "checkpoint_failed": "チェックポイントの復元に失敗しました。", + "git_not_installed": "チェックポイント機能にはGitが必要です。チェックポイントを有効にするにはGitをインストールしてください。", "no_workspace": "まずプロジェクトフォルダを開いてください", "update_support_prompt": "サポートメッセージの更新に失敗しました", "reset_support_prompt": "サポートメッセージのリセットに失敗しました", @@ -107,7 +108,8 @@ }, "buttons": { "save": "保存", - "edit": "編集" + "edit": "編集", + "learn_more": "詳細" }, "tasks": { "canceled": "タスクエラー:ユーザーによって停止およびキャンセルされました。", diff --git a/src/i18n/locales/ko/common.json b/src/i18n/locales/ko/common.json index 63566f946b..e96f728199 100644 --- a/src/i18n/locales/ko/common.json +++ b/src/i18n/locales/ko/common.json @@ -28,6 +28,7 @@ "could_not_open_file_generic": "파일을 열 수 없습니다!", "checkpoint_timeout": "체크포인트 복원을 시도하는 중 시간 초과되었습니다.", "checkpoint_failed": "체크포인트 복원에 실패했습니다.", + "git_not_installed": "체크포인트 기능을 사용하려면 Git이 필요합니다. 체크포인트를 활성화하려면 Git을 설치하세요.", "no_workspace": "먼저 프로젝트 폴더를 열어주세요", "update_support_prompt": "지원 프롬프트 업데이트에 실패했습니다", "reset_support_prompt": "지원 프롬프트 재설정에 실패했습니다", @@ -107,7 +108,8 @@ }, "buttons": { "save": "저장", - "edit": "편집" + "edit": "편집", + "learn_more": "더 알아보기" }, "tasks": { "canceled": "작업 오류: 사용자에 의해 중지 및 취소되었습니다.", diff --git a/src/i18n/locales/nl/common.json b/src/i18n/locales/nl/common.json index fc3c1ce018..b99e8f2e81 100644 --- a/src/i18n/locales/nl/common.json +++ b/src/i18n/locales/nl/common.json @@ -28,6 +28,7 @@ "could_not_open_file_generic": "Kon bestand niet openen!", "checkpoint_timeout": "Time-out bij het herstellen van checkpoint.", "checkpoint_failed": "Herstellen van checkpoint mislukt.", + "git_not_installed": "Git is vereist voor de checkpoint-functie. Installeer Git om checkpoints in te schakelen.", "no_workspace": "Open eerst een projectmap", "update_support_prompt": "Bijwerken van ondersteuningsprompt mislukt", "reset_support_prompt": "Resetten van ondersteuningsprompt mislukt", @@ -107,7 +108,8 @@ }, "buttons": { "save": "Opslaan", - "edit": "Bewerken" + "edit": "Bewerken", + "learn_more": "Meer informatie" }, "tasks": { "canceled": "Taakfout: gestopt en geannuleerd door gebruiker.", diff --git a/src/i18n/locales/pl/common.json b/src/i18n/locales/pl/common.json index ef756ec1ce..7ba7e93514 100644 --- a/src/i18n/locales/pl/common.json +++ b/src/i18n/locales/pl/common.json @@ -28,6 +28,7 @@ "could_not_open_file_generic": "Nie można otworzyć pliku!", "checkpoint_timeout": "Upłynął limit czasu podczas próby przywrócenia punktu kontrolnego.", "checkpoint_failed": "Nie udało się przywrócić punktu kontrolnego.", + "git_not_installed": "Funkcja punktów kontrolnych wymaga oprogramowania Git. Zainstaluj Git, aby włączyć punkty kontrolne.", "no_workspace": "Najpierw otwórz folder projektu", "update_support_prompt": "Nie udało się zaktualizować komunikatu wsparcia", "reset_support_prompt": "Nie udało się zresetować komunikatu wsparcia", @@ -107,7 +108,8 @@ }, "buttons": { "save": "Zapisz", - "edit": "Edytuj" + "edit": "Edytuj", + "learn_more": "Dowiedz się więcej" }, "tasks": { "canceled": "Błąd zadania: Zostało zatrzymane i anulowane przez użytkownika.", diff --git a/src/i18n/locales/pt-BR/common.json b/src/i18n/locales/pt-BR/common.json index 8856e541be..753f7ae5bb 100644 --- a/src/i18n/locales/pt-BR/common.json +++ b/src/i18n/locales/pt-BR/common.json @@ -32,6 +32,7 @@ "could_not_open_file_generic": "Não foi possível abrir o arquivo!", "checkpoint_timeout": "Tempo esgotado ao tentar restaurar o ponto de verificação.", "checkpoint_failed": "Falha ao restaurar o ponto de verificação.", + "git_not_installed": "O Git é necessário para o recurso de checkpoints. Por favor, instale o Git para habilitar os checkpoints.", "no_workspace": "Por favor, abra primeiro uma pasta de projeto", "update_support_prompt": "Falha ao atualizar o prompt de suporte", "reset_support_prompt": "Falha ao redefinir o prompt de suporte", @@ -111,7 +112,8 @@ }, "buttons": { "save": "Salvar", - "edit": "Editar" + "edit": "Editar", + "learn_more": "Saiba Mais" }, "tasks": { "canceled": "Erro na tarefa: Foi interrompida e cancelada pelo usuário.", diff --git a/src/i18n/locales/ru/common.json b/src/i18n/locales/ru/common.json index fd23dffe2a..6431bf0ca9 100644 --- a/src/i18n/locales/ru/common.json +++ b/src/i18n/locales/ru/common.json @@ -28,6 +28,7 @@ "could_not_open_file_generic": "Не удалось открыть файл!", "checkpoint_timeout": "Превышено время ожидания при попытке восстановления контрольной точки.", "checkpoint_failed": "Не удалось восстановить контрольную точку.", + "git_not_installed": "Для функции контрольных точек требуется Git. Пожалуйста, установите Git, чтобы включить контрольные точки.", "no_workspace": "Пожалуйста, сначала откройте папку проекта", "update_support_prompt": "Не удалось обновить промпт поддержки", "reset_support_prompt": "Не удалось сбросить промпт поддержки", @@ -107,7 +108,8 @@ }, "buttons": { "save": "Сохранить", - "edit": "Редактировать" + "edit": "Редактировать", + "learn_more": "Узнать больше" }, "tasks": { "canceled": "Ошибка задачи: Она была остановлена и отменена пользователем.", diff --git a/src/i18n/locales/tr/common.json b/src/i18n/locales/tr/common.json index 9eeae720ef..cfc2a37591 100644 --- a/src/i18n/locales/tr/common.json +++ b/src/i18n/locales/tr/common.json @@ -28,6 +28,7 @@ "could_not_open_file_generic": "Dosya açılamadı!", "checkpoint_timeout": "Kontrol noktasını geri yüklemeye çalışırken zaman aşımına uğradı.", "checkpoint_failed": "Kontrol noktası geri yüklenemedi.", + "git_not_installed": "Kontrol noktaları özelliği için Git gereklidir. Kontrol noktalarını etkinleştirmek için lütfen Git'i yükleyin.", "no_workspace": "Lütfen önce bir proje klasörü açın", "update_support_prompt": "Destek istemi güncellenemedi", "reset_support_prompt": "Destek istemi sıfırlanamadı", @@ -107,7 +108,8 @@ }, "buttons": { "save": "Kaydet", - "edit": "Düzenle" + "edit": "Düzenle", + "learn_more": "Daha Fazla Bilgi" }, "tasks": { "canceled": "Görev hatası: Kullanıcı tarafından durduruldu ve iptal edildi.", diff --git a/src/i18n/locales/vi/common.json b/src/i18n/locales/vi/common.json index bd66d623bb..b4593a3476 100644 --- a/src/i18n/locales/vi/common.json +++ b/src/i18n/locales/vi/common.json @@ -28,6 +28,7 @@ "could_not_open_file_generic": "Không thể mở tệp!", "checkpoint_timeout": "Đã hết thời gian khi cố gắng khôi phục điểm kiểm tra.", "checkpoint_failed": "Không thể khôi phục điểm kiểm tra.", + "git_not_installed": "Yêu cầu Git cho tính năng điểm kiểm tra. Vui lòng cài đặt Git để bật điểm kiểm tra.", "no_workspace": "Vui lòng mở thư mục dự án trước", "update_support_prompt": "Không thể cập nhật lời nhắc hỗ trợ", "reset_support_prompt": "Không thể đặt lại lời nhắc hỗ trợ", @@ -107,7 +108,8 @@ }, "buttons": { "save": "Lưu", - "edit": "Chỉnh sửa" + "edit": "Chỉnh sửa", + "learn_more": "Tìm hiểu thêm" }, "tasks": { "canceled": "Lỗi nhiệm vụ: Nó đã bị dừng và hủy bởi người dùng.", diff --git a/src/i18n/locales/zh-CN/common.json b/src/i18n/locales/zh-CN/common.json index 515ee7d048..182ab29f33 100644 --- a/src/i18n/locales/zh-CN/common.json +++ b/src/i18n/locales/zh-CN/common.json @@ -33,6 +33,7 @@ "could_not_open_file_generic": "无法打开文件!", "checkpoint_timeout": "尝试恢复检查点时超时。", "checkpoint_failed": "恢复检查点失败。", + "git_not_installed": "存档点功能需要 Git。请安装 Git 以启用存档点。", "no_workspace": "请先打开项目文件夹", "update_support_prompt": "更新支持消息失败", "reset_support_prompt": "重置支持消息失败", @@ -112,7 +113,8 @@ }, "buttons": { "save": "保存", - "edit": "编辑" + "edit": "编辑", + "learn_more": "了解更多" }, "tasks": { "canceled": "任务错误:它已被用户停止并取消。", diff --git a/src/i18n/locales/zh-TW/common.json b/src/i18n/locales/zh-TW/common.json index cceb53e5f3..2746e16ee1 100644 --- a/src/i18n/locales/zh-TW/common.json +++ b/src/i18n/locales/zh-TW/common.json @@ -28,6 +28,7 @@ "could_not_open_file_generic": "無法開啟檔案!", "checkpoint_timeout": "嘗試恢復檢查點時超時。", "checkpoint_failed": "恢復檢查點失敗。", + "git_not_installed": "存檔點功能需要 Git。請安裝 Git 以啟用存檔點。", "no_workspace": "請先開啟專案資料夾", "update_support_prompt": "更新支援訊息失敗", "reset_support_prompt": "重設支援訊息失敗", @@ -107,7 +108,8 @@ }, "buttons": { "save": "儲存", - "edit": "編輯" + "edit": "編輯", + "learn_more": "了解更多" }, "tasks": { "canceled": "工作錯誤:它已被使用者停止並取消。", diff --git a/src/utils/__tests__/git.spec.ts b/src/utils/__tests__/git.spec.ts index 3ab306feec..f87ae5667b 100644 --- a/src/utils/__tests__/git.spec.ts +++ b/src/utils/__tests__/git.spec.ts @@ -4,6 +4,7 @@ import * as fs from "fs" import * as path from "path" import { + checkGitInstalled, searchCommits, getCommitInfo, getWorkingState, @@ -83,6 +84,54 @@ describe("git utils", () => { vitest.clearAllMocks() }) + describe("checkGitInstalled", () => { + it("should return true when git --version succeeds", async () => { + vitest.mocked(exec).mockImplementation((command: string, options: any, callback: any) => { + if (command === "git --version") { + callback(null, { stdout: "git version 2.39.2", stderr: "" }) + return {} as any + } + callback(new Error("Unexpected command")) + return {} as any + }) + + const result = await checkGitInstalled() + expect(result).toBe(true) + expect(vitest.mocked(exec)).toHaveBeenCalledWith("git --version", {}, expect.any(Function)) + }) + + it("should return false when git --version fails", async () => { + vitest.mocked(exec).mockImplementation((command: string, options: any, callback: any) => { + if (command === "git --version") { + callback(new Error("git not found")) + return {} as any + } + callback(new Error("Unexpected command")) + return {} as any + }) + + const result = await checkGitInstalled() + expect(result).toBe(false) + expect(vitest.mocked(exec)).toHaveBeenCalledWith("git --version", {}, expect.any(Function)) + }) + + it("should handle unexpected errors gracefully", async () => { + vitest.mocked(exec).mockImplementation((command: string, options: any, callback: any) => { + if (command === "git --version") { + // Simulate an unexpected error + callback(new Error("Unexpected system error")) + return {} as any + } + callback(new Error("Unexpected command")) + return {} as any + }) + + const result = await checkGitInstalled() + expect(result).toBe(false) + expect(vitest.mocked(exec)).toHaveBeenCalledWith("git --version", {}, expect.any(Function)) + }) + }) + describe("searchCommits", () => { const mockCommitData = [ "abc123def456", diff --git a/src/utils/git.ts b/src/utils/git.ts index fd6abfa309..42d069416e 100644 --- a/src/utils/git.ts +++ b/src/utils/git.ts @@ -210,7 +210,16 @@ async function checkGitRepo(cwd: string): Promise { } } -async function checkGitInstalled(): Promise { +/** + * Checks if Git is installed on the system by attempting to run git --version + * @returns {Promise} True if Git is installed and accessible, false otherwise + * @example + * const isGitInstalled = await checkGitInstalled(); + * if (!isGitInstalled) { + * console.log("Git is not installed"); + * } + */ +export async function checkGitInstalled(): Promise { try { await execAsync("git --version") return true From aa0e6d31d8abdc9b40388f65b22298980745f40f Mon Sep 17 00:00:00 2001 From: Daniel <57051444+daniel-lxs@users.noreply.github.com> Date: Tue, 22 Jul 2025 20:31:36 -0500 Subject: [PATCH 14/92] feat: add merge-resolver mode for intelligent conflict resolution (#6090) --- .roo/rules-merge-resolver/1_workflow.xml | 142 ++++++++ .../rules-merge-resolver/2_best_practices.xml | 165 +++++++++ .roo/rules-merge-resolver/3_tool_usage.xml | 228 +++++++++++++ .../4_complete_example.xml | 315 ++++++++++++++++++ .roo/rules-merge-resolver/5_communication.xml | 153 +++++++++ .roo/rules-pr-fixer/1_workflow.xml | 2 +- .roo/rules-pr-fixer/2_best_practices.xml | 36 +- .roo/rules-pr-fixer/3_common_patterns.xml | 32 +- .roo/rules-pr-fixer/4_tool_usage.xml | 26 +- .roo/rules-pr-fixer/5_examples.xml | 101 ++++++ .roomodes | 32 ++ 11 files changed, 1187 insertions(+), 45 deletions(-) create mode 100644 .roo/rules-merge-resolver/1_workflow.xml create mode 100644 .roo/rules-merge-resolver/2_best_practices.xml create mode 100644 .roo/rules-merge-resolver/3_tool_usage.xml create mode 100644 .roo/rules-merge-resolver/4_complete_example.xml create mode 100644 .roo/rules-merge-resolver/5_communication.xml diff --git a/.roo/rules-merge-resolver/1_workflow.xml b/.roo/rules-merge-resolver/1_workflow.xml new file mode 100644 index 0000000000..a63809db70 --- /dev/null +++ b/.roo/rules-merge-resolver/1_workflow.xml @@ -0,0 +1,142 @@ + + + This mode resolves merge conflicts for a specific pull request by analyzing git history, + commit messages, and code changes to make intelligent resolution decisions. It receives + a PR number (e.g., "#123") and handles the entire conflict resolution process. + + + + + Parse PR number from user input +
+ Extract the PR number from input like "#123" or "PR #123" + Validate that a PR number was provided +
+
+ + + Fetch PR information + + gh pr view [PR_NUMBER] --json title,body,headRefName,baseRefName + +
+ Get PR title and description to understand the intent + Identify the source and target branches +
+
+ + + Checkout PR branch and prepare for rebase + + gh pr checkout [PR_NUMBER] --force + git fetch origin main + git rebase origin/main + +
+ Force checkout the PR branch to ensure clean state + Fetch the latest main branch + Attempt to rebase onto main to reveal conflicts +
+
+ + + Check for merge conflicts + + git status --porcelain + git diff --name-only --diff-filter=U + +
+ Identify files with merge conflicts (marked with 'UU') + Create a list of files that need resolution +
+
+
+ + + + Analyze each conflicted file to understand the changes + + Read the conflicted file to identify conflict markers + Extract the conflicting sections between <<<<<<< and >>>>>>> + Run git blame on both sides of the conflict + Fetch commit messages and diffs for relevant commits + Analyze the intent behind each change + + + + + Determine the best resolution strategy for each conflict + + Categorize changes by intent (bugfix, feature, refactor, etc.) + Evaluate recency and relevance of changes + Check for structural overlap vs formatting differences + Identify if changes can be combined or if one should override + Consider test updates and related changes + + + + + Apply the resolution strategy to resolve conflicts + + For each conflict, apply the chosen resolution + Ensure proper escaping of conflict markers in diffs + Validate that resolved code is syntactically correct + Stage resolved files with git add + + + + + Verify the resolution and prepare for commit + + Run git status to confirm all conflicts are resolved + Check for any compilation or syntax errors + Review the final diff to ensure sensible resolutions + Prepare a summary of resolution decisions + + + + + + + gh pr checkout [PR_NUMBER] --force + Force checkout the PR branch to ensure clean state + + + + git fetch origin main + Get the latest main branch from origin + + + + git rebase origin/main + Rebase current branch onto main to reveal conflicts + + + + git blame -L [start_line],[end_line] [commit_sha] -- [file_path] + Get commit information for specific lines + + + + git show --format="%H%n%an%n%ae%n%ad%n%s%n%b" --no-patch [commit_sha] + Get commit metadata including message + + + + git show [commit_sha] -- [file_path] + Get the actual changes made in a commit + + + + git ls-files -u + List unmerged files with stage information + + + + + All merge conflicts have been resolved + Resolved files have been staged + No syntax errors in resolved code + Resolution decisions are documented + +
\ No newline at end of file diff --git a/.roo/rules-merge-resolver/2_best_practices.xml b/.roo/rules-merge-resolver/2_best_practices.xml new file mode 100644 index 0000000000..5bf1b393eb --- /dev/null +++ b/.roo/rules-merge-resolver/2_best_practices.xml @@ -0,0 +1,165 @@ + + + + Intent-Based Resolution + + Always prioritize understanding the intent behind changes rather than + just looking at the code differences. Commit messages, PR descriptions, + and issue references provide crucial context. + + + Code changes have purpose - bugfixes should be preserved, features + should be integrated properly, and refactors should maintain consistency. + + + Conflict between a bugfix and a refactor + Apply the bugfix logic within the refactored structure + Simply choose one side without considering both intents + + + + + Preserve All Valuable Changes + + When possible, combine non-conflicting changes from both sides rather + than discarding one side entirely. + + + Both sides of a conflict often contain valuable changes that can coexist + if properly integrated. + + + + + Escape Conflict Markers + + When using apply_diff or search_and_replace tools, always escape merge + conflict markers with backslashes to prevent parsing errors. + + + + + + Consider Related Changes + + Look beyond the immediate conflict to understand related changes in + tests, documentation, or dependent code. + + + A change might seem isolated but could be part of a larger feature + or fix that spans multiple files. + + + + + + + Bugfixes generally take precedence over features + + Bugfixes address existing problems and should be preserved, + while features can be reintegrated around the fix. + + + + + More recent changes are often more relevant + + Recent changes likely reflect the current understanding of + requirements and may supersede older implementations. + + + When older changes are bugfixes or security patches that + haven't been addressed in newer code. + + + + + Changes that include test updates are likely more complete + + Developers who update tests alongside code changes demonstrate + thoroughness and understanding of the impact. + + + + + Logic changes take precedence over formatting changes + + Formatting can be reapplied, but logic changes represent + functional improvements or fixes. + + + + + + + Blindly choosing one side without analysis + + You might lose important changes or introduce regressions + + + Always analyze both sides using git blame and commit history + + + + + Ignoring the PR description and context + + The PR description often explains the why behind changes, + which is crucial for proper resolution + + + Always fetch and read the PR information before resolving + + + + + Not validating the resolved code + + Merged code might be syntactically incorrect or introduce + logical errors + + + Always check for syntax errors and review the final diff + + + + + Not escaping conflict markers in diffs + + Unescaped conflict markers (<<<<<<, =======, >>>>>>) in SEARCH + or REPLACE sections will be interpreted as actual diff syntax, + causing the apply_diff tool to fail or produce incorrect results + + + Always escape conflict markers with a backslash (\) when they + appear in the content you're searching for or replacing. + Example: \<<<<<<< HEAD instead of <<<<<<< HEAD + + + + + + + Fetch PR title and description for context + Identify all files with conflicts + Understand the overall change being merged + + + + Run git blame on conflicting sections + Read commit messages for intent + Consider if changes can be combined + Escape conflict markers in diffs + + + + Verify no conflict markers remain + Check for syntax/compilation errors + Review the complete diff + Document resolution decisions + + + \ No newline at end of file diff --git a/.roo/rules-merge-resolver/3_tool_usage.xml b/.roo/rules-merge-resolver/3_tool_usage.xml new file mode 100644 index 0000000000..35f3b5da75 --- /dev/null +++ b/.roo/rules-merge-resolver/3_tool_usage.xml @@ -0,0 +1,228 @@ + + + + execute_command + For all git and gh CLI operations + Git commands provide the historical context needed for intelligent resolution + + + + read_file + To examine conflicted files and understand the conflict structure + Need to see the actual conflict markers and code + + + + apply_diff or search_and_replace + To resolve conflicts by replacing conflicted sections + Precise editing of specific conflict blocks + + + + + + + Always use gh CLI for GitHub operations instead of MCP tools + Chain git commands with && for efficiency + Use --format options for structured output + Capture command output for parsing + + + + + Get PR information + gh pr view [PR_NUMBER] --json title,body,headRefName,baseRefName + + + + Checkout PR branch + gh pr checkout [PR_NUMBER] --force + + + + Fetch latest main branch + git fetch origin main + + + + Rebase onto main to reveal conflicts + git rebase origin/main + + + + Check conflict status + git status --porcelain | grep "^UU" + + + + Get blame for specific lines + git blame -L [start],[end] HEAD -- [file] | cut -d' ' -f1 + + + + Get commit message + git log -1 --format="%s%n%n%b" [commit_sha] + + + + Stage resolved file + git add [file_path] + + + + Continue rebase after resolution + git rebase --continue + + + + + + + Read the entire conflicted file first to understand structure + Note line numbers of conflict markers for precise editing + Identify the pattern of conflicts (multiple vs single) + + + + <<<<<<< HEAD - Start of current branch changes + ======= - Separator between versions + >>>>>>> [branch] - End of incoming changes + + + + + + Always escape conflict markers with backslash + Include enough context to ensure unique matches + Use :start_line: for precision + Combine multiple resolutions in one diff when possible + + + +src/feature.ts + +<<<<<<< SEARCH +:start_line:45 +------- +\<<<<<<< HEAD +function oldImplementation() { + return "old"; +} +\======= +function newImplementation() { + return "new"; +} +\>>>>>>> feature-branch +======= +function mergedImplementation() { + // Combining both approaches + return "merged"; +} +>>>>>>> REPLACE + + + ]]> + + + + + Use for simple conflict resolutions + Enable regex mode for complex patterns + Always escape special characters + + + +src/config.ts +\<<<<<<< HEAD[\s\S]*?\>>>>>>> \w+ +// Resolved configuration +const config = { + // Merged settings from both branches +} +true + + ]]> + + + + + + + execute_command - Get PR info with gh CLI + execute_command - Checkout PR with gh pr checkout --force + execute_command - Fetch origin main + execute_command - Rebase onto origin/main + execute_command - Check for conflicts with git status + + + + + + execute_command - List conflicted files + read_file - Examine conflict structure + execute_command - Git blame on conflict regions + execute_command - Fetch commit messages + + + + + + read_file - Get exact conflict content + apply_diff - Replace conflict with resolution + execute_command - Stage resolved file + execute_command - Verify resolution status + + + + + + execute_command - Check all conflicts resolved + execute_command - Continue rebase with git rebase --continue + execute_command - Verify clean status + + + + + + + Rebase completes without conflicts + + Inform user that PR can be merged without conflicts + No resolution needed + + + + + A rebase is already in progress + + Check status with git status + Either continue existing rebase or abort with git rebase --abort + + + + + Conflict markers are incomplete or nested + + Use search_and_replace with careful regex patterns + Manual inspection may be required + + + + + Binary files cannot be merged automatically + + Identify which version to keep based on PR intent + Use git checkout --theirs or --ours + + + + + Code contains literal conflict marker strings + + Extra careful escaping in diffs + Consider using search_and_replace with precise patterns + + + + \ No newline at end of file diff --git a/.roo/rules-merge-resolver/4_complete_example.xml b/.roo/rules-merge-resolver/4_complete_example.xml new file mode 100644 index 0000000000..dae8587997 --- /dev/null +++ b/.roo/rules-merge-resolver/4_complete_example.xml @@ -0,0 +1,315 @@ + + + User provides PR #123 which has merge conflicts between a bugfix branch + and a feature branch that refactored the same code. + + + + #123 + + + + + Parse PR number and fetch PR information + +gh pr view 123 --json title,body,headRefName,baseRefName + + ]]> + + + The PR is a bugfix for a memory leak issue. This context will help + prioritize preserving the fix during conflict resolution. + + + + + Checkout PR branch and prepare for rebase + +gh pr checkout 123 --force + + ]]> + + + +git fetch origin main + + ]]> + FETCH_HEAD + ]]> + + +git rebase origin/main + + ]]> + ", then run "git rebase --continue". + ]]> + + + + Check for merge conflicts + +git status --porcelain | grep "^UU" + + ]]> + + + Two files have conflicts: the main processor file and its test file. + Both need to be resolved before continuing the rebase. + + + + + Examine the conflicted processor file + +src/data/processor.ts +
+ ]]> + = new Map(); +47 | +48 | <<<<<<< HEAD +49 | async processData(data: DataInput): Promise { +50 | // Refactored to use async/await +51 | const result = await this.transform(data); +52 | return result; +53 | } +54 | ======= +55 | processData(data: any[]): ProcessedData { +56 | const listener = this.createListener(); +57 | this.listeners.set(data.id, listener); +58 | +59 | return this.transform(data); +60 | } +61 | >>>>>>> abc123 (Fix memory leak in data processor) +62 | +63 | cleanup() { +64 | <<<<<<< HEAD +65 | // TODO: Implement cleanup +66 | ======= +67 | // Fix: Clear all listeners to prevent memory leak +68 | this.listeners.forEach(listener => { +69 | listener.removeAllListeners(); +70 | }); +71 | this.listeners.clear(); +72 | >>>>>>> abc123 (Fix memory leak in data processor) +73 | } +74 | } + ]]> + + + + Analyze the conflicts using git blame + +git blame -L 49,53 HEAD -- src/data/processor.ts | cut -d' ' -f1 | sort -u | xargs -I {} git log -1 --format="%h %s" {} + + ]]> + + + +git log -1 --format="%h %s" abc123 + + ]]> + + + + HEAD (main after rebase): Has async/await refactor but missing listener logic + Incoming (bugfix branch): Adds listener tracking and cleanup for memory leak fix + + Resolution strategy: Combine both changes - keep async/await refactor + but include the listener tracking and cleanup logic. + + + + + Resolve the conflicts + +src/data/processor.ts + +<<<<<<< SEARCH +:start_line:48 +------- +\<<<<<<< HEAD + async processData(data: DataInput): Promise { + // Refactored to use async/await + const result = await this.transform(data); + return result; + } +\======= + processData(data: any[]): ProcessedData { + const listener = this.createListener(); + this.listeners.set(data.id, listener); + + return this.transform(data); + } +\>>>>>>> abc123 (Fix memory leak in data processor) +======= + async processData(data: DataInput): Promise { + // Create and track listener for cleanup (memory leak fix) + const listener = this.createListener(); + this.listeners.set(data.id, listener); + + // Refactored to use async/await + const result = await this.transform(data); + return result; + } +>>>>>>> REPLACE + +<<<<<<< SEARCH +:start_line:63 +------- + cleanup() { +\<<<<<<< HEAD + // TODO: Implement cleanup +\======= + // Fix: Clear all listeners to prevent memory leak + this.listeners.forEach(listener => { + listener.removeAllListeners(); + }); + this.listeners.clear(); +\>>>>>>> abc123 (Fix memory leak in data processor) + } +======= + cleanup() { + // Fix: Clear all listeners to prevent memory leak + this.listeners.forEach(listener => { + listener.removeAllListeners(); + }); + this.listeners.clear(); + } +>>>>>>> REPLACE + + + ]]> + + Combined both changes: kept the async/await refactor from main + while preserving the memory leak fix (listener tracking and cleanup) + from the bugfix branch. + + + + + Resolve test file conflicts + +src/data/processor.test.ts + + ]]> + + +src/data/processor.test.ts + + + + + ]]> + + + + Stage resolved files and continue rebase + +git add src/data/processor.ts src/data/processor.test.ts + + ]]> + + +git rebase --continue + + ]]> + + + + + Final validation + +git status + + ]]> + + + +git diff origin/main --stat + + ]]> + + + + + + +Successfully resolved merge conflicts for PR #123 "Fix memory leak in data processor". + +Resolution Summary: +- src/data/processor.ts: Combined the async/await refactor with the memory leak fix by preserving listener tracking and cleanup logic within the refactored async method +- src/data/processor.test.ts: Updated tests to work with the async implementation while maintaining memory leak test coverage + +Both the feature refactor and the critical bugfix have been preserved in the resolution. The rebase has been completed successfully and the branch is ready to be pushed. + + + ]]> + + + Always checkout PR with --force and rebase to reveal conflicts + Fetch PR context to understand the intent of changes + Use git blame and commit messages to understand the history + Combine non-conflicting improvements when possible + Prioritize bugfixes while accommodating refactors + Complete the rebase process with git rebase --continue + Validate that both sets of changes work together + + \ No newline at end of file diff --git a/.roo/rules-merge-resolver/5_communication.xml b/.roo/rules-merge-resolver/5_communication.xml new file mode 100644 index 0000000000..18594d5269 --- /dev/null +++ b/.roo/rules-merge-resolver/5_communication.xml @@ -0,0 +1,153 @@ + + + Be direct and technical when explaining resolution decisions + Focus on the rationale behind each conflict resolution + Provide clear summaries of what was merged and why + + + I'll help you resolve these conflicts... + Let me handle this for you... + Don't worry about the conflicts... + + + + Analyzing PR #123 for merge conflicts... + Resolving conflicts based on commit history analysis... + Applied resolution strategy: [specific strategy] + + + + + + Acknowledge the PR number + State that you're fetching PR information + Indicate the analysis will begin + + + + Fetching information for PR #123 to understand the context and identify merge conflicts... + + + + + During each major phase of resolution + + Analyzing [X] conflicted files... + Running git blame on [file] to understand change history... + Resolving conflicts in [file] by [strategy]... + Validating resolved changes... + + + + Number of conflicts found + Files being processed + Resolution strategy being applied + + + + + Explain each significant resolution decision + Reference specific commits when relevant + Justify why certain changes were kept or merged + + + + Conflict in [file]: + - HEAD: [brief description of changes] + - Incoming: [brief description of changes] + - Resolution: [what was decided and why] + + + + + + + + Expected a PR number (e.g., "#123" or "123"). Please provide the PR number to resolve conflicts for. + + + + + + PR #[number] does not have any merge conflicts. The branch can be merged without conflict resolution. + + + + + + Could not find PR #[number]. Please verify the PR number and ensure you have access to the repository. + + + + + + Found complex conflicts in [file] that require careful analysis. Examining commit history to determine the best resolution strategy... + + + + + + + State that conflicts are resolved + Provide resolution summary + List files that were resolved + Mention key decisions made + + + + + + Questions about next steps + Offers to do additional work + Uncertain language about the resolution + + + + + Document why specific resolutions were chosen + Reference commit SHAs when they influenced decisions + Explain trade-offs when both sides had valid changes + + + + Preserved bugfix from commit abc123 while adapting it to the refactored structure from def456 + + + Combined both implementations as they addressed different aspects of the same feature + + + Chose the more recent implementation as it included additional error handling + + + + + + + + Binary file conflict in [file]. Based on PR intent "[title]", choosing [which version] version. + + + + + + Conflict: [file] was deleted in one branch but modified in another. Based on the changes, [keeping/removing] the file because [reason]. + + + + + + Conflict in [file] involves only whitespace/formatting. Applying consistent formatting from [which] branch. + + + + \ No newline at end of file diff --git a/.roo/rules-pr-fixer/1_workflow.xml b/.roo/rules-pr-fixer/1_workflow.xml index db74ead7ee..fb487e5fdd 100644 --- a/.roo/rules-pr-fixer/1_workflow.xml +++ b/.roo/rules-pr-fixer/1_workflow.xml @@ -45,7 +45,7 @@ Determine if the PR is from a fork by checking 'gh pr view [PR_NUMBER] --repo [owner]/[repo] --json isCrossRepository'. Apply code changes based on review feedback using file editing tools. Fix failing tests by modifying test files or source code as needed. - For conflict resolution: Use GIT_EDITOR=true for non-interactive rebases, then resolve conflicts via file editing. + For conflict resolution: Delegate to merge-resolver mode using new_task with the PR number. If changes affect user-facing content (i18n files, UI components, announcements), delegate translation updates using the new_task tool with translate mode. Review modified files with 'git status --porcelain' to ensure no temporary files are included. Stage files selectively using 'git add -u' (for modified tracked files) or 'git add ' (for new files). diff --git a/.roo/rules-pr-fixer/2_best_practices.xml b/.roo/rules-pr-fixer/2_best_practices.xml index 50a8395b9c..2dc5775ced 100644 --- a/.roo/rules-pr-fixer/2_best_practices.xml +++ b/.roo/rules-pr-fixer/2_best_practices.xml @@ -41,33 +41,25 @@ - How to correctly escape conflict markers when using apply_diff. + Delegate merge conflict resolution to the merge-resolver mode. diff --git a/.roo/rules-pr-fixer/3_common_patterns.xml b/.roo/rules-pr-fixer/3_common_patterns.xml index 1c6c0bcf65..4ef2a34b9e 100644 --- a/.roo/rules-pr-fixer/3_common_patterns.xml +++ b/.roo/rules-pr-fixer/3_common_patterns.xml @@ -27,32 +27,26 @@ Commands to detect merge conflicts. - - Rebase operations using GIT_EDITOR to prevent interactive prompts. + + Delegate merge conflict resolution to the merge-resolver mode. - - Check current conflict status without interactive input. - - Check out a pull request branch locally.