From 048863e03991a3b49429a77827da2f96b32889f9 Mon Sep 17 00:00:00 2001 From: Piotr Rogowski Date: Sun, 26 Jan 2025 08:31:07 +0100 Subject: [PATCH 01/42] Do not exclude whole project dir when listing in case where project is places inside excluded dir (like /tmp or ~/tmp) --- src/services/glob/list-files.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/glob/list-files.ts b/src/services/glob/list-files.ts index 8578b914d7..c7e3d41cf0 100644 --- a/src/services/glob/list-files.ts +++ b/src/services/glob/list-files.ts @@ -34,7 +34,7 @@ export async function listFiles(dirPath: string, recursive: boolean, limit: numb "pkg", "Pods", ".*", // '!**/.*' excludes hidden directories, while '!**/.*/**' excludes only their contents. This way we are at least aware of the existence of hidden directories. - ].map((dir) => `**/${dir}/**`) + ].map((dir) => `${dirPath}/**/${dir}/**`) const options = { cwd: dirPath, From 1cd90a655b0db197b6dd248e854eef366d148849 Mon Sep 17 00:00:00 2001 From: kohii Date: Sun, 2 Feb 2025 10:17:57 +0900 Subject: [PATCH 02/42] feat: Add Kotlin support in list_code_definition_names --- esbuild.js | 1 + .../tree-sitter/__tests__/index.test.ts | 6 ++++ .../__tests__/languageParser.test.ts | 11 ++++++++ src/services/tree-sitter/index.ts | 3 ++ src/services/tree-sitter/languageParser.ts | 6 ++++ src/services/tree-sitter/queries/index.ts | 1 + src/services/tree-sitter/queries/kotlin.ts | 28 +++++++++++++++++++ 7 files changed, 56 insertions(+) create mode 100644 src/services/tree-sitter/queries/kotlin.ts diff --git a/esbuild.js b/esbuild.js index 8b203076e4..7907dd1c39 100644 --- a/esbuild.js +++ b/esbuild.js @@ -52,6 +52,7 @@ const copyWasmFiles = { "java", "php", "swift", + "kotlin", ] languages.forEach((lang) => { diff --git a/src/services/tree-sitter/__tests__/index.test.ts b/src/services/tree-sitter/__tests__/index.test.ts index 4a5782dcb1..8372e7e580 100644 --- a/src/services/tree-sitter/__tests__/index.test.ts +++ b/src/services/tree-sitter/__tests__/index.test.ts @@ -169,6 +169,8 @@ describe("Tree-sitter Service", () => { "/test/path/main.rs", "/test/path/program.cpp", "/test/path/code.go", + "/test/path/app.kt", + "/test/path/script.kts", ] ;(listFiles as jest.Mock).mockResolvedValue([mockFiles, new Set()]) @@ -197,6 +199,8 @@ describe("Tree-sitter Service", () => { rs: { parser: mockParser, query: mockQuery }, cpp: { parser: mockParser, query: mockQuery }, go: { parser: mockParser, query: mockQuery }, + kt: { parser: mockParser, query: mockQuery }, + kts: { parser: mockParser, query: mockQuery }, }) ;(fs.readFile as jest.Mock).mockResolvedValue("function test() {}") @@ -207,6 +211,8 @@ describe("Tree-sitter Service", () => { expect(result).toContain("main.rs") expect(result).toContain("program.cpp") expect(result).toContain("code.go") + expect(result).toContain("app.kt") + expect(result).toContain("script.kts") }) it("should normalize paths in output", async () => { diff --git a/src/services/tree-sitter/__tests__/languageParser.test.ts b/src/services/tree-sitter/__tests__/languageParser.test.ts index 1b92d81b6b..54271e30e8 100644 --- a/src/services/tree-sitter/__tests__/languageParser.test.ts +++ b/src/services/tree-sitter/__tests__/languageParser.test.ts @@ -92,6 +92,17 @@ describe("Language Parser", () => { expect(parsers.hpp).toBeDefined() }) + it("should handle Kotlin files correctly", async () => { + const files = ["test.kt", "test.kts"] + const parsers = await loadRequiredLanguageParsers(files) + + expect(ParserMock.Language.load).toHaveBeenCalledWith(expect.stringContaining("tree-sitter-kotlin.wasm")) + expect(parsers.kt).toBeDefined() + expect(parsers.kts).toBeDefined() + expect(parsers.kt.query).toBeDefined() + expect(parsers.kts.query).toBeDefined() + }) + it("should throw error for unsupported file extensions", async () => { const files = ["test.unsupported"] diff --git a/src/services/tree-sitter/index.ts b/src/services/tree-sitter/index.ts index 83e02ac615..5b48da885d 100644 --- a/src/services/tree-sitter/index.ts +++ b/src/services/tree-sitter/index.ts @@ -73,6 +73,9 @@ function separateFiles(allFiles: string[]): { filesToParse: string[]; remainingF "java", "php", "swift", + // Kotlin + "kt", + "kts", ].map((e) => `.${e}`) const filesToParse = allFiles.filter((file) => extensions.includes(path.extname(file))).slice(0, 50) // 50 files max const remainingFiles = allFiles.filter((file) => !filesToParse.includes(file)) diff --git a/src/services/tree-sitter/languageParser.ts b/src/services/tree-sitter/languageParser.ts index 2d791b39a8..f256b0b62a 100644 --- a/src/services/tree-sitter/languageParser.ts +++ b/src/services/tree-sitter/languageParser.ts @@ -13,6 +13,7 @@ import { javaQuery, phpQuery, swiftQuery, + kotlinQuery, } from "./queries" export interface LanguageParser { @@ -120,6 +121,11 @@ export async function loadRequiredLanguageParsers(filesToParse: string[]): Promi language = await loadLanguage("swift") query = language.query(swiftQuery) break + case "kt": + case "kts": + language = await loadLanguage("kotlin") + query = language.query(kotlinQuery) + break default: throw new Error(`Unsupported language: ${ext}`) } diff --git a/src/services/tree-sitter/queries/index.ts b/src/services/tree-sitter/queries/index.ts index 889210a8e5..818eacca01 100644 --- a/src/services/tree-sitter/queries/index.ts +++ b/src/services/tree-sitter/queries/index.ts @@ -10,3 +10,4 @@ export { default as cQuery } from "./c" export { default as csharpQuery } from "./c-sharp" export { default as goQuery } from "./go" export { default as swiftQuery } from "./swift" +export { default as kotlinQuery } from "./kotlin" diff --git a/src/services/tree-sitter/queries/kotlin.ts b/src/services/tree-sitter/queries/kotlin.ts new file mode 100644 index 0000000000..61eb112448 --- /dev/null +++ b/src/services/tree-sitter/queries/kotlin.ts @@ -0,0 +1,28 @@ +/* +- class declarations (including interfaces) +- function declarations +- object declarations +- property declarations +- type alias declarations +*/ +export default ` +(class_declaration + (type_identifier) @name.definition.class +) @definition.class + +(function_declaration + (simple_identifier) @name.definition.function +) @definition.function + +(object_declaration + (type_identifier) @name.definition.object +) @definition.object + +(property_declaration + (simple_identifier) @name.definition.property +) @definition.property + +(type_alias + (type_identifier) @name.definition.type +) @definition.type +` From fbf65bfc6c1eeef107e9aa27245ea7bd64a5adf7 Mon Sep 17 00:00:00 2001 From: axb Date: Wed, 12 Feb 2025 17:32:55 +0800 Subject: [PATCH 03/42] Reduce the probability of errors when the model tries to fix the problem due to mismatched line numbers after applying diff --- src/core/mentions/index.ts | 4 ++-- src/integrations/diagnostics/index.ts | 10 +++++++--- src/integrations/editor/DiffViewProvider.ts | 2 +- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/core/mentions/index.ts b/src/core/mentions/index.ts index cf5bdeaae0..cf87241f23 100644 --- a/src/core/mentions/index.ts +++ b/src/core/mentions/index.ts @@ -186,9 +186,9 @@ async function getFileOrFolderContent(mentionPath: string, cwd: string): Promise } } -function getWorkspaceProblems(cwd: string): string { +async function getWorkspaceProblems(cwd: string): Promise { const diagnostics = vscode.languages.getDiagnostics() - const result = diagnosticsToProblemsString( + const result = await diagnosticsToProblemsString( diagnostics, [vscode.DiagnosticSeverity.Error, vscode.DiagnosticSeverity.Warning], cwd, diff --git a/src/integrations/diagnostics/index.ts b/src/integrations/diagnostics/index.ts index ad4ee7755c..2d829f26e7 100644 --- a/src/integrations/diagnostics/index.ts +++ b/src/integrations/diagnostics/index.ts @@ -70,11 +70,12 @@ export function getNewDiagnostics( // // - New error in file3 (1:1) // will return empty string if no problems with the given severity are found -export function diagnosticsToProblemsString( +export async function diagnosticsToProblemsString( diagnostics: [vscode.Uri, vscode.Diagnostic[]][], severities: vscode.DiagnosticSeverity[], cwd: string, -): string { +): Promise { + const documents = new Map() let result = "" for (const [uri, fileDiagnostics] of diagnostics) { const problems = fileDiagnostics.filter((d) => severities.includes(d.severity)) @@ -100,7 +101,10 @@ export function diagnosticsToProblemsString( } const line = diagnostic.range.start.line + 1 // VSCode lines are 0-indexed const source = diagnostic.source ? `${diagnostic.source} ` : "" - result += `\n- [${source}${label}] Line ${line}: ${diagnostic.message}` + const document = documents.get(uri) || (await vscode.workspace.openTextDocument(uri)) + documents.set(uri, document) + const lineContent = document.lineAt(diagnostic.range.start.line).text + result += `\n- [${source}${label}] ${line} | ${lineContent} : ${diagnostic.message}` } } } diff --git a/src/integrations/editor/DiffViewProvider.ts b/src/integrations/editor/DiffViewProvider.ts index ee24d7db4e..8f7e387c7a 100644 --- a/src/integrations/editor/DiffViewProvider.ts +++ b/src/integrations/editor/DiffViewProvider.ts @@ -172,7 +172,7 @@ export class DiffViewProvider { initial fix is usually correct and it may just take time for linters to catch up. */ const postDiagnostics = vscode.languages.getDiagnostics() - const newProblems = diagnosticsToProblemsString( + const newProblems = await diagnosticsToProblemsString( getNewDiagnostics(this.preDiagnostics, postDiagnostics), [ vscode.DiagnosticSeverity.Error, // only including errors since warnings can be distracting (if user wants to fix warnings they can use the @problems mention) From e14b1b2daba6d2ef7060846f32ca5d747bac630e Mon Sep 17 00:00:00 2001 From: Daniel Trugman Date: Fri, 7 Mar 2025 13:53:32 +0000 Subject: [PATCH 04/42] Add OpenAI-style cost calculation --- src/api/providers/vscode-lm.ts | 4 +- src/core/Cline.ts | 6 +- src/utils/__tests__/cost.test.ts | 113 ++++++++++++++++++++++++++++--- src/utils/cost.ts | 57 ++++++++++++---- 4 files changed, 152 insertions(+), 28 deletions(-) diff --git a/src/api/providers/vscode-lm.ts b/src/api/providers/vscode-lm.ts index bf1215e238..0ce2a6e26a 100644 --- a/src/api/providers/vscode-lm.ts +++ b/src/api/providers/vscode-lm.ts @@ -2,7 +2,7 @@ import { Anthropic } from "@anthropic-ai/sdk" import * as vscode from "vscode" import { SingleCompletionHandler } from "../" -import { calculateApiCost } from "../../utils/cost" +import { calculateApiCostAnthropic } from "../../utils/cost" import { ApiStream } from "../transform/stream" import { convertToVsCodeLmMessages } from "../transform/vscode-lm-format" import { SELECTOR_SEPARATOR, stringifyVsCodeLmModelSelector } from "../../shared/vsCodeSelectorUtils" @@ -462,7 +462,7 @@ export class VsCodeLmHandler extends BaseProvider implements SingleCompletionHan type: "usage", inputTokens: totalInputTokens, outputTokens: totalOutputTokens, - totalCost: calculateApiCost(this.getModel().info, totalInputTokens, totalOutputTokens), + totalCost: calculateApiCostAnthropic(this.getModel().info, totalInputTokens, totalOutputTokens), } } catch (error: unknown) { this.ensureCleanState() diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 16f1d4e99d..4f27a89cc0 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -55,7 +55,7 @@ import { ClineAskResponse } from "../shared/WebviewMessage" import { GlobalFileNames } from "../shared/globalFileNames" import { defaultModeSlug, getModeBySlug, getFullModeDetails } from "../shared/modes" import { EXPERIMENT_IDS, experiments as Experiments, ExperimentId } from "../shared/experiments" -import { calculateApiCost } from "../utils/cost" +import { calculateApiCostAnthropic } from "../utils/cost" import { fileExistsAtPath } from "../utils/fs" import { arePathsEqual, getReadablePath } from "../utils/path" import { parseMentions } from "./mentions" @@ -875,7 +875,7 @@ export class Cline { // The way this agentic loop works is that cline will be given a task that he then calls tools to complete. unless there's an attempt_completion call, we keep responding back to him with his tool's responses until he either attempt_completion or does not use anymore tools. If he does not use anymore tools, we ask him to consider if he's completed the task and then call attempt_completion, otherwise proceed with completing the task. // There is a MAX_REQUESTS_PER_TASK limit to prevent infinite requests, but Cline is prompted to finish the task as efficiently as he can. - //const totalCost = this.calculateApiCost(totalInputTokens, totalOutputTokens) + //const totalCost = this.calculateApiCostAntrhopic(totalInputTokens, totalOutputTokens) if (didEndLoop) { // For now a task never 'completes'. This will only happen if the user hits max requests and denies resetting the count. //this.say("task_completed", `Task completed. Total API usage cost: ${totalCost}`) @@ -3159,7 +3159,7 @@ export class Cline { cacheReads: cacheReadTokens, cost: totalCost ?? - calculateApiCost( + calculateApiCostAnthropic( this.api.getModel().info, inputTokens, outputTokens, diff --git a/src/utils/__tests__/cost.test.ts b/src/utils/__tests__/cost.test.ts index e390c4af7f..4501f86b88 100644 --- a/src/utils/__tests__/cost.test.ts +++ b/src/utils/__tests__/cost.test.ts @@ -1,8 +1,8 @@ -import { calculateApiCost } from "../cost" +import { calculateApiCostAnthropic, calculateApiCostOpenAI } from "../cost" import { ModelInfo } from "../../shared/api" describe("Cost Utility", () => { - describe("calculateApiCost", () => { + describe("calculateApiCostAnthropic", () => { const mockModelInfo: ModelInfo = { maxTokens: 8192, contextWindow: 200_000, @@ -14,7 +14,7 @@ describe("Cost Utility", () => { } it("should calculate basic input/output costs correctly", () => { - const cost = calculateApiCost(mockModelInfo, 1000, 500) + const cost = calculateApiCostAnthropic(mockModelInfo, 1000, 500) // Input cost: (3.0 / 1_000_000) * 1000 = 0.003 // Output cost: (15.0 / 1_000_000) * 500 = 0.0075 @@ -23,7 +23,7 @@ describe("Cost Utility", () => { }) it("should handle cache writes cost", () => { - const cost = calculateApiCost(mockModelInfo, 1000, 500, 2000) + const cost = calculateApiCostAnthropic(mockModelInfo, 1000, 500, 2000) // Input cost: (3.0 / 1_000_000) * 1000 = 0.003 // Output cost: (15.0 / 1_000_000) * 500 = 0.0075 @@ -33,7 +33,7 @@ describe("Cost Utility", () => { }) it("should handle cache reads cost", () => { - const cost = calculateApiCost(mockModelInfo, 1000, 500, undefined, 3000) + const cost = calculateApiCostAnthropic(mockModelInfo, 1000, 500, undefined, 3000) // Input cost: (3.0 / 1_000_000) * 1000 = 0.003 // Output cost: (15.0 / 1_000_000) * 500 = 0.0075 @@ -43,7 +43,7 @@ describe("Cost Utility", () => { }) it("should handle all cost components together", () => { - const cost = calculateApiCost(mockModelInfo, 1000, 500, 2000, 3000) + const cost = calculateApiCostAnthropic(mockModelInfo, 1000, 500, 2000, 3000) // Input cost: (3.0 / 1_000_000) * 1000 = 0.003 // Output cost: (15.0 / 1_000_000) * 500 = 0.0075 @@ -60,17 +60,17 @@ describe("Cost Utility", () => { supportsPromptCache: true, } - const cost = calculateApiCost(modelWithoutPrices, 1000, 500, 2000, 3000) + const cost = calculateApiCostAnthropic(modelWithoutPrices, 1000, 500, 2000, 3000) expect(cost).toBe(0) }) it("should handle zero tokens", () => { - const cost = calculateApiCost(mockModelInfo, 0, 0, 0, 0) + const cost = calculateApiCostAnthropic(mockModelInfo, 0, 0, 0, 0) expect(cost).toBe(0) }) it("should handle undefined cache values", () => { - const cost = calculateApiCost(mockModelInfo, 1000, 500) + const cost = calculateApiCostAnthropic(mockModelInfo, 1000, 500) // Input cost: (3.0 / 1_000_000) * 1000 = 0.003 // Output cost: (15.0 / 1_000_000) * 500 = 0.0075 @@ -85,7 +85,7 @@ describe("Cost Utility", () => { cacheReadsPrice: undefined, } - const cost = calculateApiCost(modelWithoutCachePrices, 1000, 500, 2000, 3000) + const cost = calculateApiCostAnthropic(modelWithoutCachePrices, 1000, 500, 2000, 3000) // Should only include input and output costs // Input cost: (3.0 / 1_000_000) * 1000 = 0.003 @@ -94,4 +94,97 @@ describe("Cost Utility", () => { expect(cost).toBe(0.0105) }) }) + + describe("calculateApiCostOpenAI", () => { + const mockModelInfo: ModelInfo = { + maxTokens: 8192, + contextWindow: 200_000, + supportsPromptCache: true, + inputPrice: 3.0, // $3 per million tokens + outputPrice: 15.0, // $15 per million tokens + cacheWritesPrice: 3.75, // $3.75 per million tokens + cacheReadsPrice: 0.3, // $0.30 per million tokens + } + + it("should calculate basic input/output costs correctly", () => { + const cost = calculateApiCostOpenAI(mockModelInfo, 1000, 500) + + // Input cost: (3.0 / 1_000_000) * 1000 = 0.003 + // Output cost: (15.0 / 1_000_000) * 500 = 0.0075 + // Total: 0.003 + 0.0075 = 0.0105 + expect(cost).toBe(0.0105) + }) + + it("should handle cache writes cost", () => { + const cost = calculateApiCostOpenAI(mockModelInfo, 3000, 500, 2000) + + // Input cost: (3.0 / 1_000_000) * (3000 - 2000) = 0.003 + // Output cost: (15.0 / 1_000_000) * 500 = 0.0075 + // Cache writes: (3.75 / 1_000_000) * 2000 = 0.0075 + // Total: 0.003 + 0.0075 + 0.0075 = 0.018 + expect(cost).toBeCloseTo(0.018, 6) + }) + + it("should handle cache reads cost", () => { + const cost = calculateApiCostOpenAI(mockModelInfo, 4000, 500, undefined, 3000) + + // Input cost: (3.0 / 1_000_000) * (4000 - 3000) = 0.003 + // Output cost: (15.0 / 1_000_000) * 500 = 0.0075 + // Cache reads: (0.3 / 1_000_000) * 3000 = 0.0009 + // Total: 0.003 + 0.0075 + 0.0009 = 0.0114 + expect(cost).toBe(0.0114) + }) + + it("should handle all cost components together", () => { + const cost = calculateApiCostOpenAI(mockModelInfo, 6000, 500, 2000, 3000) + + // Input cost: (3.0 / 1_000_000) * (6000 - 2000 - 3000) = 0.003 + // Output cost: (15.0 / 1_000_000) * 500 = 0.0075 + // Cache writes: (3.75 / 1_000_000) * 2000 = 0.0075 + // Cache reads: (0.3 / 1_000_000) * 3000 = 0.0009 + // Total: 0.003 + 0.0075 + 0.0075 + 0.0009 = 0.0189 + expect(cost).toBe(0.0189) + }) + + it("should handle missing prices gracefully", () => { + const modelWithoutPrices: ModelInfo = { + maxTokens: 8192, + contextWindow: 200_000, + supportsPromptCache: true, + } + + const cost = calculateApiCostOpenAI(modelWithoutPrices, 1000, 500, 2000, 3000) + expect(cost).toBe(0) + }) + + it("should handle zero tokens", () => { + const cost = calculateApiCostOpenAI(mockModelInfo, 0, 0, 0, 0) + expect(cost).toBe(0) + }) + + it("should handle undefined cache values", () => { + const cost = calculateApiCostOpenAI(mockModelInfo, 1000, 500) + + // Input cost: (3.0 / 1_000_000) * 1000 = 0.003 + // Output cost: (15.0 / 1_000_000) * 500 = 0.0075 + // Total: 0.003 + 0.0075 = 0.0105 + expect(cost).toBe(0.0105) + }) + + it("should handle missing cache prices", () => { + const modelWithoutCachePrices: ModelInfo = { + ...mockModelInfo, + cacheWritesPrice: undefined, + cacheReadsPrice: undefined, + } + + const cost = calculateApiCostOpenAI(modelWithoutCachePrices, 6000, 500, 2000, 3000) + + // Should only include input and output costs + // Input cost: (3.0 / 1_000_000) * (6000 - 2000 - 3000) = 0.003 + // Output cost: (15.0 / 1_000_000) * 500 = 0.0075 + // Total: 0.003 + 0.0075 = 0.0105 + expect(cost).toBe(0.0105) + }) + }) }) diff --git a/src/utils/cost.ts b/src/utils/cost.ts index adc2ded0a8..48108b6348 100644 --- a/src/utils/cost.ts +++ b/src/utils/cost.ts @@ -1,26 +1,57 @@ import { ModelInfo } from "../shared/api" -export function calculateApiCost( +function calculateApiCostInternal( modelInfo: ModelInfo, inputTokens: number, outputTokens: number, - cacheCreationInputTokens?: number, - cacheReadInputTokens?: number, + cacheCreationInputTokens: number, + cacheReadInputTokens: number, ): number { - const modelCacheWritesPrice = modelInfo.cacheWritesPrice - let cacheWritesCost = 0 - if (cacheCreationInputTokens && modelCacheWritesPrice) { - cacheWritesCost = (modelCacheWritesPrice / 1_000_000) * cacheCreationInputTokens - } - const modelCacheReadsPrice = modelInfo.cacheReadsPrice - let cacheReadsCost = 0 - if (cacheReadInputTokens && modelCacheReadsPrice) { - cacheReadsCost = (modelCacheReadsPrice / 1_000_000) * cacheReadInputTokens - } + const cacheWritesCost = ((modelInfo.cacheWritesPrice || 0) / 1_000_000) * cacheCreationInputTokens + const cacheReadsCost = ((modelInfo.cacheReadsPrice || 0) / 1_000_000) * cacheReadInputTokens const baseInputCost = ((modelInfo.inputPrice || 0) / 1_000_000) * inputTokens const outputCost = ((modelInfo.outputPrice || 0) / 1_000_000) * outputTokens const totalCost = cacheWritesCost + cacheReadsCost + baseInputCost + outputCost return totalCost } +// For Anthropic compliant usage, the input tokens count does NOT include the cached tokens +export function calculateApiCostAnthropic( + modelInfo: ModelInfo, + inputTokens: number, + outputTokens: number, + cacheCreationInputTokens?: number, + cacheReadInputTokens?: number, +): number { + const cacheCreationInputTokensNum = cacheCreationInputTokens || 0 + const cacheReadInputTokensNum = cacheReadInputTokens || 0 + return calculateApiCostInternal( + modelInfo, + inputTokens, + outputTokens, + cacheCreationInputTokensNum, + cacheReadInputTokensNum, + ) +} + +// For OpenAI compliant usage, the input tokens count INCLUDES the cached tokens +export function calculateApiCostOpenAI( + modelInfo: ModelInfo, + inputTokens: number, + outputTokens: number, + cacheCreationInputTokens?: number, + cacheReadInputTokens?: number, +): number { + const cacheCreationInputTokensNum = cacheCreationInputTokens || 0 + const cacheReadInputTokensNum = cacheReadInputTokens || 0 + const nonCachedInputTokens = Math.max(0, inputTokens - cacheCreationInputTokensNum - cacheReadInputTokensNum) + return calculateApiCostInternal( + modelInfo, + nonCachedInputTokens, + outputTokens, + cacheCreationInputTokensNum, + cacheReadInputTokensNum, + ) +} + export const parseApiPrice = (price: any) => (price ? parseFloat(price) * 1_000_000 : undefined) From c51f59e50b6642494ed5c5addf6897e9a9827ae5 Mon Sep 17 00:00:00 2001 From: Daniel Trugman Date: Fri, 7 Mar 2025 14:57:11 +0000 Subject: [PATCH 05/42] Requesty: Correctly calculate request costs --- src/api/providers/__tests__/requesty.test.ts | 26 +++++++++++----- src/api/providers/openai.ts | 6 ++-- src/api/providers/requesty.ts | 32 ++++++++++++++++---- 3 files changed, 47 insertions(+), 17 deletions(-) diff --git a/src/api/providers/__tests__/requesty.test.ts b/src/api/providers/__tests__/requesty.test.ts index 7867b15ebc..47921a1c53 100644 --- a/src/api/providers/__tests__/requesty.test.ts +++ b/src/api/providers/__tests__/requesty.test.ts @@ -22,8 +22,10 @@ describe("RequestyHandler", () => { contextWindow: 4000, supportsPromptCache: false, supportsImages: true, - inputPrice: 0, - outputPrice: 0, + inputPrice: 1, + outputPrice: 10, + cacheReadsPrice: 0.1, + cacheWritesPrice: 1.5, }, openAiStreamingEnabled: true, includeMaxTokens: true, // Add this to match the implementation @@ -83,8 +85,12 @@ describe("RequestyHandler", () => { yield { choices: [{ delta: { content: " world" } }], usage: { - prompt_tokens: 10, - completion_tokens: 5, + prompt_tokens: 30, + completion_tokens: 10, + prompt_tokens_details: { + cached_tokens: 15, + caching_tokens: 5, + }, }, } }, @@ -105,10 +111,11 @@ describe("RequestyHandler", () => { { type: "text", text: " world" }, { type: "usage", - inputTokens: 10, - outputTokens: 5, - cacheWriteTokens: undefined, - cacheReadTokens: undefined, + inputTokens: 30, + outputTokens: 10, + cacheWriteTokens: 5, + cacheReadTokens: 15, + totalCost: 0.000119, // (10 * 1 / 1,000,000) + (5 * 1.5 / 1,000,000) + (15 * 0.1 / 1,000,000) + (10 * 10 / 1,000,000) }, ]) @@ -182,6 +189,9 @@ describe("RequestyHandler", () => { type: "usage", inputTokens: 10, outputTokens: 5, + cacheWriteTokens: 0, + cacheReadTokens: 0, + totalCost: 0.00006, // (10 * 1 / 1,000,000) + (5 * 10 / 1,000,000) }, ]) diff --git a/src/api/providers/openai.ts b/src/api/providers/openai.ts index 9262f3b75a..5d85a86a5a 100644 --- a/src/api/providers/openai.ts +++ b/src/api/providers/openai.ts @@ -111,7 +111,7 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl } } if (chunk.usage) { - yield this.processUsageMetrics(chunk.usage) + yield this.processUsageMetrics(chunk.usage, modelInfo) } } } else { @@ -134,11 +134,11 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl type: "text", text: response.choices[0]?.message.content || "", } - yield this.processUsageMetrics(response.usage) + yield this.processUsageMetrics(response.usage, modelInfo) } } - protected processUsageMetrics(usage: any): ApiStreamUsageChunk { + protected processUsageMetrics(usage: any, modelInfo?: ModelInfo): ApiStreamUsageChunk { return { type: "usage", inputTokens: usage?.prompt_tokens || 0, diff --git a/src/api/providers/requesty.ts b/src/api/providers/requesty.ts index 0060bfc5d8..434d6f4316 100644 --- a/src/api/providers/requesty.ts +++ b/src/api/providers/requesty.ts @@ -1,9 +1,20 @@ import axios from "axios" import { ModelInfo, requestyModelInfoSaneDefaults, requestyDefaultModelId } from "../../shared/api" -import { parseApiPrice } from "../../utils/cost" +import { calculateApiCostOpenAI, parseApiPrice } from "../../utils/cost" import { ApiStreamUsageChunk } from "../transform/stream" import { OpenAiHandler, OpenAiHandlerOptions } from "./openai" +import OpenAI from "openai" + +// Requesty usage includes an extra field for Anthropic use cases. +// Safely cast the prompt token details section to the appropriate structure. +interface RequestyUsage extends OpenAI.CompletionUsage { + prompt_tokens_details?: { + caching_tokens?: number + cached_tokens?: number + } + total_cost?: number +} export class RequestyHandler extends OpenAiHandler { constructor(options: OpenAiHandlerOptions) { @@ -27,13 +38,22 @@ export class RequestyHandler extends OpenAiHandler { } } - protected override processUsageMetrics(usage: any): ApiStreamUsageChunk { + protected override processUsageMetrics(usage: any, modelInfo?: ModelInfo): ApiStreamUsageChunk { + const requestyUsage = usage as RequestyUsage + const inputTokens = requestyUsage?.prompt_tokens || 0 + const outputTokens = requestyUsage?.completion_tokens || 0 + const cacheWriteTokens = requestyUsage?.prompt_tokens_details?.caching_tokens || 0 + const cacheReadTokens = requestyUsage?.prompt_tokens_details?.cached_tokens || 0 + const totalCost = modelInfo + ? calculateApiCostOpenAI(modelInfo, inputTokens, outputTokens, cacheWriteTokens, cacheReadTokens) + : 0 return { type: "usage", - inputTokens: usage?.prompt_tokens || 0, - outputTokens: usage?.completion_tokens || 0, - cacheWriteTokens: usage?.cache_creation_input_tokens, - cacheReadTokens: usage?.cache_read_input_tokens, + inputTokens: inputTokens, + outputTokens: outputTokens, + cacheWriteTokens: cacheWriteTokens, + cacheReadTokens: cacheReadTokens, + totalCost: totalCost, } } } From 1456db95028f90b75bb521ad036a6fcc9d7bdd45 Mon Sep 17 00:00:00 2001 From: ShayBC Date: Sat, 8 Mar 2025 06:01:49 +0200 Subject: [PATCH 06/42] added approve finish task button and auto approve button for subtasks --- src/core/Cline.ts | 26 +++++++++++++++++-- src/shared/ExtensionMessage.ts | 3 +++ src/shared/WebviewMessage.ts | 1 + .../src/components/chat/AutoApproveMenu.tsx | 16 ++++++++++++ webview-ui/src/components/chat/ChatRow.tsx | 12 +++++++++ webview-ui/src/components/chat/ChatView.tsx | 5 +++- .../src/context/ExtensionStateContext.tsx | 2 ++ 7 files changed, 62 insertions(+), 3 deletions(-) diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 51fb5265d4..c3e90d4300 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -1414,6 +1414,18 @@ export class Cline { return true } + const askFinishSubTaskApproval = async () => { + // ask the user to approve this task has completed, and he has reviewd it, and we can declare task is finished + // and return control to the parent task to continue running the rest of the sub-tasks + const toolMessage = JSON.stringify({ + tool: "finishTask", + content: + "Task completed! You can review the results and suggest any corrections or next steps. If everything looks good, confirm to continue with the next task.", + }) + + return await askApproval("tool", toolMessage) + } + const handleError = async (action: string, error: Error) => { const errorString = `Error ${action}: ${JSON.stringify(serializeError(error))}` await this.say( @@ -2941,8 +2953,13 @@ export class Cline { if (lastMessage && lastMessage.ask !== "command") { // havent sent a command message yet so first send completion_result then command await this.say("completion_result", result, undefined, false) - telemetryService.captureTaskCompleted(this.taskId) + // telemetryService.captureTaskCompleted(this.taskId) if (this.isSubTask) { + const didApprove = await askFinishSubTaskApproval() + if (!didApprove) { + break + } + // tell the provider to remove the current subtask and resume the previous task in the stack await this.providerRef .deref() @@ -2966,8 +2983,13 @@ export class Cline { commandResult = execCommandResult } else { await this.say("completion_result", result, undefined, false) - telemetryService.captureTaskCompleted(this.taskId) + // telemetryService.captureTaskCompleted(this.taskId) if (this.isSubTask) { + const didApprove = await askFinishSubTaskApproval() + if (!didApprove) { + break + } + // tell the provider to remove the current subtask and resume the previous task in the stack await this.providerRef .deref() diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index 98ff9b36e1..dcdaf017f3 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -109,6 +109,7 @@ export interface ExtensionState { alwaysAllowMcp?: boolean alwaysApproveResubmit?: boolean alwaysAllowModeSwitch?: boolean + alwaysAllowFinishTask?: boolean browserToolEnabled?: boolean requestDelaySeconds: number rateLimitSeconds: number // Minimum time between successive requests (0 = disabled) @@ -168,6 +169,7 @@ export type ClineAsk = | "mistake_limit_reached" | "browser_action_launch" | "use_mcp_server" + | "finishTask" export type ClineSay = | "task" @@ -207,6 +209,7 @@ export interface ClineSayTool { | "searchFiles" | "switchMode" | "newTask" + | "finishTask" path?: string diff?: string content?: string diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index 10af6f7a94..086701a43f 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -48,6 +48,7 @@ export interface WebviewMessage { | "alwaysAllowBrowser" | "alwaysAllowMcp" | "alwaysAllowModeSwitch" + | "alwaysAllowFinishTask" | "playSound" | "soundEnabled" | "soundVolume" diff --git a/webview-ui/src/components/chat/AutoApproveMenu.tsx b/webview-ui/src/components/chat/AutoApproveMenu.tsx index 161f3032b0..02f75cd28a 100644 --- a/webview-ui/src/components/chat/AutoApproveMenu.tsx +++ b/webview-ui/src/components/chat/AutoApproveMenu.tsx @@ -30,6 +30,8 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => { setAlwaysAllowMcp, alwaysAllowModeSwitch, setAlwaysAllowModeSwitch, + alwaysAllowFinishTask, + setAlwaysAllowFinishTask, alwaysApproveResubmit, setAlwaysApproveResubmit, autoApprovalEnabled, @@ -81,6 +83,13 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => { description: "Allows automatic switching between different AI modes and creating new tasks without requiring approval.", }, + { + id: "finishTask", + label: "Finish subtasks tasks", + shortName: "Finish", + enabled: alwaysAllowFinishTask ?? false, + description: "Allows automatic completeing a sub-task without requiring user review or approval.", + }, { id: "retryRequests", label: "Retry failed requests", @@ -136,6 +145,12 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => { vscode.postMessage({ type: "alwaysAllowModeSwitch", bool: newValue }) }, [alwaysAllowModeSwitch, setAlwaysAllowModeSwitch]) + const handleFinishTaskChange = useCallback(() => { + const newValue = !(alwaysAllowFinishTask ?? false) + setAlwaysAllowFinishTask(newValue) + vscode.postMessage({ type: "alwaysAllowFinishTask", bool: newValue }) + }, [alwaysAllowFinishTask, setAlwaysAllowFinishTask]) + const handleRetryChange = useCallback(() => { const newValue = !(alwaysApproveResubmit ?? false) setAlwaysApproveResubmit(newValue) @@ -150,6 +165,7 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => { useBrowser: handleBrowserChange, useMcp: handleMcpChange, switchModes: handleModeSwitchChange, + finishTask: handleFinishTaskChange, retryRequests: handleRetryChange, } diff --git a/webview-ui/src/components/chat/ChatRow.tsx b/webview-ui/src/components/chat/ChatRow.tsx index 1533bba3a8..6f19df665e 100644 --- a/webview-ui/src/components/chat/ChatRow.tsx +++ b/webview-ui/src/components/chat/ChatRow.tsx @@ -459,6 +459,18 @@ export const ChatRowContent = ({ ) + case "finishTask": + return ( + <> +
+ {toolIcon("new-file")} + Roo wants to finish this task +
+
+ {tool.content} +
+ + ) default: return null } diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index 35e63dd332..0f352819ef 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -61,6 +61,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie setMode, autoApprovalEnabled, alwaysAllowModeSwitch, + alwaysAllowFinishTask, customModes, telemetrySetting, } = useExtensionState() @@ -642,7 +643,8 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie (alwaysAllowModeSwitch && message.ask === "tool" && (JSON.parse(message.text || "{}")?.tool === "switchMode" || - JSON.parse(message.text || "{}")?.tool === "newTask")) + JSON.parse(message.text || "{}")?.tool === "newTask")) || + (alwaysAllowFinishTask && message.ask === "finishTask") ) }, [ @@ -657,6 +659,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie alwaysAllowMcp, isMcpToolAlwaysAllowed, alwaysAllowModeSwitch, + alwaysAllowFinishTask, ], ) diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx index c4daf426ca..aa132919c0 100644 --- a/webview-ui/src/context/ExtensionStateContext.tsx +++ b/webview-ui/src/context/ExtensionStateContext.tsx @@ -31,6 +31,7 @@ export interface ExtensionStateContextType extends ExtensionState { setAlwaysAllowBrowser: (value: boolean) => void setAlwaysAllowMcp: (value: boolean) => void setAlwaysAllowModeSwitch: (value: boolean) => void + setAlwaysAllowFinishTask: (value: boolean) => void setBrowserToolEnabled: (value: boolean) => void setShowRooIgnoredFiles: (value: boolean) => void setShowAnnouncement: (value: boolean) => void @@ -247,6 +248,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode setAlwaysAllowBrowser: (value) => setState((prevState) => ({ ...prevState, alwaysAllowBrowser: value })), setAlwaysAllowMcp: (value) => setState((prevState) => ({ ...prevState, alwaysAllowMcp: value })), setAlwaysAllowModeSwitch: (value) => setState((prevState) => ({ ...prevState, alwaysAllowModeSwitch: value })), + setAlwaysAllowFinishTask: (value) => setState((prevState) => ({ ...prevState, alwaysAllowFinishTask: value })), setShowAnnouncement: (value) => setState((prevState) => ({ ...prevState, shouldShowAnnouncement: value })), setAllowedCommands: (value) => setState((prevState) => ({ ...prevState, allowedCommands: value })), setSoundEnabled: (value) => setState((prevState) => ({ ...prevState, soundEnabled: value })), From 27624a25a54be1f124ab5f7e765e7cb0b62c97a0 Mon Sep 17 00:00:00 2001 From: ShayBC Date: Sat, 8 Mar 2025 16:25:45 +0200 Subject: [PATCH 07/42] fixed the missing case of cmd execution at the end of a subyask and added auto aprove option for finish task (and continue to the next task) --- src/core/Cline.ts | 33 ++++++------------- src/core/webview/ClineProvider.ts | 7 ++++ src/shared/globalState.ts | 1 + .../src/components/chat/AutoApproveMenu.tsx | 6 ++-- webview-ui/src/components/chat/ChatView.tsx | 8 ++++- .../settings/AutoApproveSettings.tsx | 14 ++++++++ .../src/components/settings/SettingsView.tsx | 3 ++ 7 files changed, 45 insertions(+), 27 deletions(-) diff --git a/src/core/Cline.ts b/src/core/Cline.ts index c3e90d4300..cd1ce7031d 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -2953,19 +2953,7 @@ export class Cline { if (lastMessage && lastMessage.ask !== "command") { // havent sent a command message yet so first send completion_result then command await this.say("completion_result", result, undefined, false) - // telemetryService.captureTaskCompleted(this.taskId) - if (this.isSubTask) { - const didApprove = await askFinishSubTaskApproval() - if (!didApprove) { - break - } - - // tell the provider to remove the current subtask and resume the previous task in the stack - await this.providerRef - .deref() - ?.finishSubTask(`Task complete: ${lastMessage?.text}`) - break - } + telemetryService.captureTaskCompleted(this.taskId) } // complete command message @@ -2983,19 +2971,18 @@ export class Cline { commandResult = execCommandResult } else { await this.say("completion_result", result, undefined, false) - // telemetryService.captureTaskCompleted(this.taskId) - if (this.isSubTask) { - const didApprove = await askFinishSubTaskApproval() - if (!didApprove) { - break - } + telemetryService.captureTaskCompleted(this.taskId) + } - // tell the provider to remove the current subtask and resume the previous task in the stack - await this.providerRef - .deref() - ?.finishSubTask(`Task complete: ${lastMessage?.text}`) + if (this.isSubTask) { + const didApprove = await askFinishSubTaskApproval() + if (!didApprove) { break } + + // tell the provider to remove the current subtask and resume the previous task in the stack + await this.providerRef.deref()?.finishSubTask(`Task complete: ${lastMessage?.text}`) + break } // we already sent completion_result says, an empty string asks relinquishes control over button and field diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 70feb45c2f..cc0b6f4f04 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -984,6 +984,10 @@ export class ClineProvider implements vscode.WebviewViewProvider { await this.updateGlobalState("alwaysAllowModeSwitch", message.bool) await this.postStateToWebview() break + case "alwaysAllowFinishTask": + await this.updateGlobalState("alwaysAllowFinishTask", message.bool) + await this.postStateToWebview() + break case "askResponse": this.getCurrentCline()?.handleWebviewAskResponse( message.askResponse!, @@ -2177,6 +2181,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { alwaysAllowBrowser, alwaysAllowMcp, alwaysAllowModeSwitch, + alwaysAllowFinishTask, soundEnabled, diffEnabled, enableCheckpoints, @@ -2224,6 +2229,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { alwaysAllowBrowser: alwaysAllowBrowser ?? false, alwaysAllowMcp: alwaysAllowMcp ?? false, alwaysAllowModeSwitch: alwaysAllowModeSwitch ?? false, + alwaysAllowFinishTask: alwaysAllowFinishTask ?? false, uriScheme: vscode.env.uriScheme, currentTaskItem: this.getCurrentCline()?.taskId ? (taskHistory || []).find((item: HistoryItem) => item.id === this.getCurrentCline()?.taskId) @@ -2385,6 +2391,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { alwaysAllowBrowser: stateValues.alwaysAllowBrowser ?? false, alwaysAllowMcp: stateValues.alwaysAllowMcp ?? false, alwaysAllowModeSwitch: stateValues.alwaysAllowModeSwitch ?? false, + alwaysAllowFinishTask: stateValues.alwaysAllowFinishTask ?? false, taskHistory: stateValues.taskHistory, allowedCommands: stateValues.allowedCommands, soundEnabled: stateValues.soundEnabled ?? false, diff --git a/src/shared/globalState.ts b/src/shared/globalState.ts index bfd24f4298..739fa11dad 100644 --- a/src/shared/globalState.ts +++ b/src/shared/globalState.ts @@ -40,6 +40,7 @@ export const GLOBAL_STATE_KEYS = [ "alwaysAllowBrowser", "alwaysAllowMcp", "alwaysAllowModeSwitch", + "alwaysAllowFinishTask", "taskHistory", "openAiBaseUrl", "openAiModelId", diff --git a/webview-ui/src/components/chat/AutoApproveMenu.tsx b/webview-ui/src/components/chat/AutoApproveMenu.tsx index 02f75cd28a..fba97f6c7d 100644 --- a/webview-ui/src/components/chat/AutoApproveMenu.tsx +++ b/webview-ui/src/components/chat/AutoApproveMenu.tsx @@ -85,10 +85,10 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => { }, { id: "finishTask", - label: "Finish subtasks tasks", - shortName: "Finish", + label: "Continue to next task", + shortName: "Continue", enabled: alwaysAllowFinishTask ?? false, - description: "Allows automatic completeing a sub-task without requiring user review or approval.", + description: "Allow tasks to end execution and continue to the next task, without user review or approval.", }, { id: "retryRequests", diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index 0f352819ef..b92604e157 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -149,6 +149,10 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie setPrimaryButtonText("Save") setSecondaryButtonText("Reject") break + case "finishTask": + setPrimaryButtonText("Approve & Continue to the next Task") + setSecondaryButtonText(undefined) + break default: setPrimaryButtonText("Approve") setSecondaryButtonText("Reject") @@ -644,7 +648,9 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie message.ask === "tool" && (JSON.parse(message.text || "{}")?.tool === "switchMode" || JSON.parse(message.text || "{}")?.tool === "newTask")) || - (alwaysAllowFinishTask && message.ask === "finishTask") + (alwaysAllowFinishTask && + message.ask === "tool" && + JSON.parse(message.text || "{}")?.tool === "finishTask") ) }, [ diff --git a/webview-ui/src/components/settings/AutoApproveSettings.tsx b/webview-ui/src/components/settings/AutoApproveSettings.tsx index b2da2cab75..1c8e6c9ea9 100644 --- a/webview-ui/src/components/settings/AutoApproveSettings.tsx +++ b/webview-ui/src/components/settings/AutoApproveSettings.tsx @@ -18,6 +18,7 @@ type AutoApproveSettingsProps = HTMLAttributes & { requestDelaySeconds: number alwaysAllowMcp?: boolean alwaysAllowModeSwitch?: boolean + alwaysAllowFinishTask?: boolean alwaysAllowExecute?: boolean allowedCommands?: string[] setCachedStateField: SetCachedStateField @@ -32,6 +33,7 @@ export const AutoApproveSettings = ({ requestDelaySeconds, alwaysAllowMcp, alwaysAllowModeSwitch, + alwaysAllowFinishTask, alwaysAllowExecute, allowedCommands, setCachedStateField, @@ -180,6 +182,18 @@ export const AutoApproveSettings = ({

+
+ setCachedStateField("alwaysAllowFinishTask", e.target.checked)}> + Always approve finish & continue to next task + +

+ Automatically approve tasks to finish execution and continue to the next task, without user + review or approval +

+
+
(({ onDone }, alwaysAllowExecute, alwaysAllowMcp, alwaysAllowModeSwitch, + alwaysAllowFinishTask, alwaysAllowWrite, alwaysApproveResubmit, browserToolEnabled, @@ -184,6 +185,7 @@ const SettingsView = forwardRef(({ onDone }, vscode.postMessage({ type: "currentApiConfigName", text: currentApiConfigName }) vscode.postMessage({ type: "updateExperimental", values: experiments }) vscode.postMessage({ type: "alwaysAllowModeSwitch", bool: alwaysAllowModeSwitch }) + vscode.postMessage({ type: "alwaysAllowFinishTask", bool: alwaysAllowFinishTask }) vscode.postMessage({ type: "upsertApiConfiguration", text: currentApiConfigName, apiConfiguration }) vscode.postMessage({ type: "telemetrySetting", text: telemetrySetting }) setChangeDetected(false) @@ -364,6 +366,7 @@ const SettingsView = forwardRef(({ onDone }, requestDelaySeconds={requestDelaySeconds} alwaysAllowMcp={alwaysAllowMcp} alwaysAllowModeSwitch={alwaysAllowModeSwitch} + alwaysAllowFinishTask={alwaysAllowFinishTask} alwaysAllowExecute={alwaysAllowExecute} allowedCommands={allowedCommands} setCachedStateField={setCachedStateField} From 80139d88d7d7ab3baee492955a7380ade17550cc Mon Sep 17 00:00:00 2001 From: axb Date: Sun, 9 Mar 2025 00:02:40 +0800 Subject: [PATCH 08/42] support tool progress status --- src/core/Cline.ts | 26 ++++++++++++++++++- .../diff/strategies/multi-search-replace.ts | 23 ++++++++++++++++ src/core/diff/types.ts | 5 ++++ src/shared/ExtensionMessage.ts | 5 ++++ webview-ui/src/components/chat/ChatRow.tsx | 1 + .../src/components/common/CodeAccordian.tsx | 13 ++++++++++ 6 files changed, 72 insertions(+), 1 deletion(-) diff --git a/src/core/Cline.ts b/src/core/Cline.ts index fd8ce3e9a2..232a8f4de7 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -48,6 +48,7 @@ import { ClineSay, ClineSayBrowserAction, ClineSayTool, + ToolProgressStatus, } from "../shared/ExtensionMessage" import { getApiMetrics } from "../shared/getApiMetrics" import { HistoryItem } from "../shared/HistoryItem" @@ -408,6 +409,7 @@ export class Cline { type: ClineAsk, text?: string, partial?: boolean, + progressStatus?: ToolProgressStatus, ): Promise<{ response: ClineAskResponse; text?: string; images?: string[] }> { // If this Cline instance was aborted by the provider, then the only thing keeping us alive is a promise still running in the background, in which case we don't want to send its result to the webview as it is attached to a new instance of Cline now. So we can safely ignore the result of any active promises, and this class will be deallocated. (Although we set Cline = undefined in provider, that simply removes the reference to this instance, but the instance is still alive until this promise resolves or rejects.) if (this.abort) { @@ -423,6 +425,7 @@ export class Cline { // existing partial message, so update it lastMessage.text = text lastMessage.partial = partial + lastMessage.progressStatus = progressStatus // todo be more efficient about saving and posting only new data or one whole message at a time so ignore partial for saves, and only post parts of partial message instead of whole array in new listener // await this.saveClineMessages() // await this.providerRef.deref()?.postStateToWebview() @@ -460,6 +463,8 @@ export class Cline { // lastMessage.ts = askTs lastMessage.text = text lastMessage.partial = false + lastMessage.progressStatus = progressStatus + await this.saveClineMessages() // await this.providerRef.deref()?.postStateToWebview() await this.providerRef @@ -511,6 +516,7 @@ export class Cline { images?: string[], partial?: boolean, checkpoint?: Record, + progressStatus?: ToolProgressStatus, ): Promise { if (this.abort) { throw new Error(`Task: ${this.taskNumber} Roo Code instance aborted (#2)`) @@ -526,6 +532,7 @@ export class Cline { lastMessage.text = text lastMessage.images = images lastMessage.partial = partial + lastMessage.progressStatus = progressStatus await this.providerRef .deref() ?.postMessageToWebview({ type: "partialMessage", partialMessage: lastMessage }) @@ -545,6 +552,7 @@ export class Cline { lastMessage.text = text lastMessage.images = images lastMessage.partial = false + lastMessage.progressStatus = progressStatus // instead of streaming partialMessage events, we do a save and post like normal to persist to disk await this.saveClineMessages() @@ -1691,8 +1699,16 @@ export class Cline { try { if (block.partial) { // update gui message + let toolProgressStatus + if (this.diffStrategy && this.diffStrategy.getProgressStatus) { + toolProgressStatus = this.diffStrategy.getProgressStatus(block) + } + const partialMessage = JSON.stringify(sharedMessageProps) - await this.ask("tool", partialMessage, block.partial).catch(() => {}) + + await this.ask("tool", partialMessage, block.partial, toolProgressStatus).catch( + () => {}, + ) break } else { if (!relPath) { @@ -1787,6 +1803,14 @@ export class Cline { diff: diffContent, } satisfies ClineSayTool) + let toolProgressStatus + if (this.diffStrategy && this.diffStrategy.getProgressStatus) { + toolProgressStatus = this.diffStrategy.getProgressStatus(block, diffResult) + } + await this.ask("tool", completeMessage, block.partial, toolProgressStatus).catch( + () => {}, + ) + const didApprove = await askApproval("tool", completeMessage) if (!didApprove) { await this.diffViewProvider.revertChanges() // This likely handles closing the diff view diff --git a/src/core/diff/strategies/multi-search-replace.ts b/src/core/diff/strategies/multi-search-replace.ts index 99c22a31df..0462629b9b 100644 --- a/src/core/diff/strategies/multi-search-replace.ts +++ b/src/core/diff/strategies/multi-search-replace.ts @@ -1,6 +1,8 @@ import { DiffStrategy, DiffResult } from "../types" import { addLineNumbers, everyLineHasLineNumbers, stripLineNumbers } from "../../../integrations/misc/extract-text" import { distance } from "fastest-levenshtein" +import { ToolProgressStatus } from "../../../shared/ExtensionMessage" +import { ToolUse } from "../../assistant-message" const BUFFER_LINES = 40 // Number of extra context lines to show before and after matches @@ -362,4 +364,25 @@ Only use a single line of '=======' between search and replacement content, beca failParts: diffResults, } } + + getProgressStatus(toolUse: ToolUse, result?: DiffResult): ToolProgressStatus { + const diffContent = toolUse.params.diff + if (diffContent) { + if (toolUse.partial) { + if (diffContent.length < 1000 || (diffContent.length / 50) % 10 === 0) { + return { text: `progressing ${(diffContent.match(/SEARCH/g) || []).length} blocks...` } + } + } else if (result) { + const searchBlockCount = (diffContent.match(/SEARCH/g) || []).length + if (result.failParts) { + return { + text: `progressed ${searchBlockCount - result.failParts.length}/${searchBlockCount} blocks.`, + } + } else { + return { text: `progressed ${searchBlockCount} blocks.` } + } + } + } + return {} + } } diff --git a/src/core/diff/types.ts b/src/core/diff/types.ts index be6d8cd311..e12a47762d 100644 --- a/src/core/diff/types.ts +++ b/src/core/diff/types.ts @@ -2,6 +2,9 @@ * Interface for implementing different diff strategies */ +import { ToolProgressStatus } from "../../shared/ExtensionMessage" +import { ToolUse } from "../assistant-message" + export type DiffResult = | { success: true; content: string; failParts?: DiffResult[] } | ({ @@ -34,4 +37,6 @@ export interface DiffStrategy { * @returns A DiffResult object containing either the successful result or error details */ applyDiff(originalContent: string, diffContent: string, startLine?: number, endLine?: number): Promise + + getProgressStatus?(toolUse: ToolUse, result?: any): ToolProgressStatus } diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index 98ff9b36e1..0f65d17199 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -154,6 +154,7 @@ export interface ClineMessage { reasoning?: string conversationHistoryIndex?: number checkpoint?: Record + progressStatus?: ToolProgressStatus } export type ClineAsk = @@ -271,3 +272,7 @@ export interface HumanRelayCancelMessage { } export type ClineApiReqCancelReason = "streaming_failed" | "user_cancelled" + +export type ToolProgressStatus = { + text?: string +} diff --git a/webview-ui/src/components/chat/ChatRow.tsx b/webview-ui/src/components/chat/ChatRow.tsx index 1533bba3a8..41c5863c96 100644 --- a/webview-ui/src/components/chat/ChatRow.tsx +++ b/webview-ui/src/components/chat/ChatRow.tsx @@ -258,6 +258,7 @@ export const ChatRowContent = ({ Roo wants to edit this file:
void isLoading?: boolean + progressStatus?: ToolProgressStatus } /* @@ -32,6 +34,7 @@ const CodeAccordian = ({ isExpanded, onToggleExpand, isLoading, + progressStatus, }: CodeAccordianProps) => { const inferredLanguage = useMemo( () => code && (language ?? (path ? getLanguageFromPath(path) : undefined)), @@ -95,6 +98,16 @@ const CodeAccordian = ({ )}
+ {progressStatus && progressStatus.text && ( + + {progressStatus.text} + + )} )} From 4cf7754e655b069b05e30ee7255456b0babf3a65 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Sat, 8 Mar 2025 17:24:34 -0500 Subject: [PATCH 09/42] Strip BOM when applying diffs --- jest.config.js | 3 ++- package-lock.json | 22 ++++++++++++++++----- package.json | 3 ++- src/__mocks__/strip-bom.js | 13 ++++++++++++ src/integrations/editor/DiffViewProvider.ts | 9 +++++++-- 5 files changed, 41 insertions(+), 9 deletions(-) create mode 100644 src/__mocks__/strip-bom.js diff --git a/jest.config.js b/jest.config.js index dbe5ee54eb..c18b6e9eff 100644 --- a/jest.config.js +++ b/jest.config.js @@ -30,9 +30,10 @@ module.exports = { "^strip-ansi$": "/src/__mocks__/strip-ansi.js", "^default-shell$": "/src/__mocks__/default-shell.js", "^os-name$": "/src/__mocks__/os-name.js", + "^strip-bom$": "/src/__mocks__/strip-bom.js", }, transformIgnorePatterns: [ - "node_modules/(?!(@modelcontextprotocol|delay|p-wait-for|globby|serialize-error|strip-ansi|default-shell|os-name)/)", + "node_modules/(?!(@modelcontextprotocol|delay|p-wait-for|globby|serialize-error|strip-ansi|default-shell|os-name|strip-bom)/)", ], roots: ["/src", "/webview-ui/src"], modulePathIgnorePatterns: [".vscode-test"], diff --git a/package-lock.json b/package-lock.json index 3b8f47c2e2..7c8935b791 100644 --- a/package-lock.json +++ b/package-lock.json @@ -51,6 +51,7 @@ "sound-play": "^1.1.0", "string-similarity": "^4.0.4", "strip-ansi": "^7.1.0", + "strip-bom": "^5.0.0", "tmp": "^0.2.3", "tree-sitter-wasms": "^0.1.11", "turndown": "^7.2.0", @@ -10782,6 +10783,15 @@ "node": ">=8" } }, + "node_modules/jest-runtime/node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/jest-simple-dot-reporter": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/jest-simple-dot-reporter/-/jest-simple-dot-reporter-1.0.5.tgz", @@ -14170,12 +14180,14 @@ } }, "node_modules/strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true, + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-5.0.0.tgz", + "integrity": "sha512-p+byADHF7SzEcVnLvc/r3uognM1hUhObuHXxJcgLCfD194XAkaLbjq3Wzb0N5G2tgIjH0dgT708Z51QxMeu60A==", "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/strip-final-newline": { diff --git a/package.json b/package.json index 5ddd9320f1..926218b21c 100644 --- a/package.json +++ b/package.json @@ -265,8 +265,8 @@ "@anthropic-ai/sdk": "^0.37.0", "@anthropic-ai/vertex-sdk": "^0.7.0", "@aws-sdk/client-bedrock-runtime": "^3.706.0", - "@google/generative-ai": "^0.18.0", "@google-cloud/vertexai": "^1.9.3", + "@google/generative-ai": "^0.18.0", "@mistralai/mistralai": "^1.3.6", "@modelcontextprotocol/sdk": "^1.0.1", "@types/clone-deep": "^4.0.4", @@ -304,6 +304,7 @@ "sound-play": "^1.1.0", "string-similarity": "^4.0.4", "strip-ansi": "^7.1.0", + "strip-bom": "^5.0.0", "tmp": "^0.2.3", "tree-sitter-wasms": "^0.1.11", "turndown": "^7.2.0", diff --git a/src/__mocks__/strip-bom.js b/src/__mocks__/strip-bom.js new file mode 100644 index 0000000000..64bb0dac4f --- /dev/null +++ b/src/__mocks__/strip-bom.js @@ -0,0 +1,13 @@ +// Mock implementation of strip-bom +module.exports = function stripBom(string) { + if (typeof string !== "string") { + throw new TypeError("Expected a string") + } + + // Removes UTF-8 BOM + if (string.charCodeAt(0) === 0xfeff) { + return string.slice(1) + } + + return string +} diff --git a/src/integrations/editor/DiffViewProvider.ts b/src/integrations/editor/DiffViewProvider.ts index ee24d7db4e..5058ca0252 100644 --- a/src/integrations/editor/DiffViewProvider.ts +++ b/src/integrations/editor/DiffViewProvider.ts @@ -7,6 +7,7 @@ import { formatResponse } from "../../core/prompts/responses" import { DecorationController } from "./DecorationController" import * as diff from "diff" import { diagnosticsToProblemsString, getNewDiagnostics } from "../diagnostics" +import stripBom from "strip-bom" export const DIFF_VIEW_URI_SCHEME = "cline-diff" @@ -104,7 +105,7 @@ export class DiffViewProvider { const edit = new vscode.WorkspaceEdit() const rangeToReplace = new vscode.Range(0, 0, endLine + 1, 0) const contentToReplace = accumulatedLines.slice(0, endLine + 1).join("\n") + "\n" - edit.replace(document.uri, rangeToReplace, contentToReplace) + edit.replace(document.uri, rangeToReplace, stripBom(stripBom(contentToReplace))) await vscode.workspace.applyEdit(edit) // Update decorations this.activeLineController.setActiveLine(endLine) @@ -128,7 +129,11 @@ export class DiffViewProvider { } // Apply the final content const finalEdit = new vscode.WorkspaceEdit() - finalEdit.replace(document.uri, new vscode.Range(0, 0, document.lineCount, 0), accumulatedContent) + finalEdit.replace( + document.uri, + new vscode.Range(0, 0, document.lineCount, 0), + stripBom(stripBom(accumulatedContent)), + ) await vscode.workspace.applyEdit(finalEdit) // Clear all decorations at the end (after applying final edit) this.fadedOverlayController.clear() From 585d5aba5ac0f3188cdb7b8e78f22ab8d088efd7 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Sat, 8 Mar 2025 18:44:03 -0500 Subject: [PATCH 10/42] Strip all BOMs --- src/integrations/editor/DiffViewProvider.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/integrations/editor/DiffViewProvider.ts b/src/integrations/editor/DiffViewProvider.ts index 5058ca0252..2aab8f9115 100644 --- a/src/integrations/editor/DiffViewProvider.ts +++ b/src/integrations/editor/DiffViewProvider.ts @@ -105,7 +105,7 @@ export class DiffViewProvider { const edit = new vscode.WorkspaceEdit() const rangeToReplace = new vscode.Range(0, 0, endLine + 1, 0) const contentToReplace = accumulatedLines.slice(0, endLine + 1).join("\n") + "\n" - edit.replace(document.uri, rangeToReplace, stripBom(stripBom(contentToReplace))) + edit.replace(document.uri, rangeToReplace, this.stripAllBOMs(contentToReplace)) await vscode.workspace.applyEdit(edit) // Update decorations this.activeLineController.setActiveLine(endLine) @@ -132,7 +132,7 @@ export class DiffViewProvider { finalEdit.replace( document.uri, new vscode.Range(0, 0, document.lineCount, 0), - stripBom(stripBom(accumulatedContent)), + this.stripAllBOMs(accumulatedContent), ) await vscode.workspace.applyEdit(finalEdit) // Clear all decorations at the end (after applying final edit) @@ -341,6 +341,16 @@ export class DiffViewProvider { } } + private stripAllBOMs(input: string): string { + let result = input + let previous + do { + previous = result + result = stripBom(result) + } while (result !== previous) + return result + } + // close editor if open? async reset() { this.editType = undefined From 1c8071bbb371f019994c0bf7849ba37df2ef67e7 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Sat, 8 Mar 2025 22:55:04 -0500 Subject: [PATCH 11/42] Clean up the tool progress UX --- src/core/diff/strategies/multi-search-replace.ts | 12 +++++++----- src/shared/ExtensionMessage.ts | 1 + webview-ui/src/components/common/CodeAccordian.tsx | 14 ++++++-------- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/core/diff/strategies/multi-search-replace.ts b/src/core/diff/strategies/multi-search-replace.ts index 0462629b9b..bcf2f65430 100644 --- a/src/core/diff/strategies/multi-search-replace.ts +++ b/src/core/diff/strategies/multi-search-replace.ts @@ -368,18 +368,20 @@ Only use a single line of '=======' between search and replacement content, beca getProgressStatus(toolUse: ToolUse, result?: DiffResult): ToolProgressStatus { const diffContent = toolUse.params.diff if (diffContent) { + const icon = "diff-multiple" + const searchBlockCount = (diffContent.match(/SEARCH/g) || []).length if (toolUse.partial) { if (diffContent.length < 1000 || (diffContent.length / 50) % 10 === 0) { - return { text: `progressing ${(diffContent.match(/SEARCH/g) || []).length} blocks...` } + return { icon, text: `${searchBlockCount}` } } } else if (result) { - const searchBlockCount = (diffContent.match(/SEARCH/g) || []).length - if (result.failParts) { + if (result.failParts?.length) { return { - text: `progressed ${searchBlockCount - result.failParts.length}/${searchBlockCount} blocks.`, + icon, + text: `${searchBlockCount - result.failParts.length}/${searchBlockCount}`, } } else { - return { text: `progressed ${searchBlockCount} blocks.` } + return { icon, text: `${searchBlockCount}` } } } } diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index c051aab5a6..4e76b0abbc 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -277,5 +277,6 @@ export interface HumanRelayCancelMessage { export type ClineApiReqCancelReason = "streaming_failed" | "user_cancelled" export type ToolProgressStatus = { + icon?: string text?: string } diff --git a/webview-ui/src/components/common/CodeAccordian.tsx b/webview-ui/src/components/common/CodeAccordian.tsx index defafdc869..9d2f224ffb 100644 --- a/webview-ui/src/components/common/CodeAccordian.tsx +++ b/webview-ui/src/components/common/CodeAccordian.tsx @@ -99,14 +99,12 @@ const CodeAccordian = ({ )}
{progressStatus && progressStatus.text && ( - - {progressStatus.text} - + <> + {progressStatus.icon && } + + {progressStatus.text} + + )} From e10757d6fadabeaae58441ebbadfa3e7ec5e3e2b Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Sat, 8 Mar 2025 23:46:28 -0500 Subject: [PATCH 12/42] Iterate on subtasks UX --- src/core/Cline.ts | 2 +- src/core/webview/ClineProvider.ts | 14 ++++----- src/shared/ExtensionMessage.ts | 2 +- src/shared/WebviewMessage.ts | 2 +- src/shared/globalState.ts | 2 +- .../src/components/chat/AutoApproveMenu.tsx | 31 +++++++++---------- webview-ui/src/components/chat/ChatRow.tsx | 6 ++-- webview-ui/src/components/chat/ChatView.tsx | 13 ++++---- .../settings/AutoApproveSettings.tsx | 17 +++++----- .../src/components/settings/SettingsView.tsx | 6 ++-- .../src/context/ExtensionStateContext.tsx | 4 +-- 11 files changed, 47 insertions(+), 52 deletions(-) diff --git a/src/core/Cline.ts b/src/core/Cline.ts index be5306dd28..3d27a50255 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -1431,7 +1431,7 @@ export class Cline { const toolMessage = JSON.stringify({ tool: "finishTask", content: - "Task completed! You can review the results and suggest any corrections or next steps. If everything looks good, confirm to continue with the next task.", + "Subtask completed! You can review the results and suggest any corrections or next steps. If everything looks good, confirm to return the result to the parent task.", }) return await askApproval("tool", toolMessage) diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 90885cec43..657e4a9ab6 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -984,8 +984,8 @@ export class ClineProvider implements vscode.WebviewViewProvider { await this.updateGlobalState("alwaysAllowModeSwitch", message.bool) await this.postStateToWebview() break - case "alwaysAllowFinishTask": - await this.updateGlobalState("alwaysAllowFinishTask", message.bool) + case "alwaysAllowSubtasks": + await this.updateGlobalState("alwaysAllowSubtasks", message.bool) await this.postStateToWebview() break case "askResponse": @@ -997,9 +997,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { break case "clearTask": // clear task resets the current session and allows for a new task to be started, if this session is a subtask - it allows the parent task to be resumed - await this.finishSubTask( - `new_task finished with an error!, it was stopped and canceled by the user.`, - ) + await this.finishSubTask(`Task error: It was stopped and canceled by the user.`) await this.postStateToWebview() break case "didShowAnnouncement": @@ -2181,7 +2179,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { alwaysAllowBrowser, alwaysAllowMcp, alwaysAllowModeSwitch, - alwaysAllowFinishTask, + alwaysAllowSubtasks, soundEnabled, diffEnabled, enableCheckpoints, @@ -2229,7 +2227,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { alwaysAllowBrowser: alwaysAllowBrowser ?? false, alwaysAllowMcp: alwaysAllowMcp ?? false, alwaysAllowModeSwitch: alwaysAllowModeSwitch ?? false, - alwaysAllowFinishTask: alwaysAllowFinishTask ?? false, + alwaysAllowSubtasks: alwaysAllowSubtasks ?? false, uriScheme: vscode.env.uriScheme, currentTaskItem: this.getCurrentCline()?.taskId ? (taskHistory || []).find((item: HistoryItem) => item.id === this.getCurrentCline()?.taskId) @@ -2391,7 +2389,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { alwaysAllowBrowser: stateValues.alwaysAllowBrowser ?? false, alwaysAllowMcp: stateValues.alwaysAllowMcp ?? false, alwaysAllowModeSwitch: stateValues.alwaysAllowModeSwitch ?? false, - alwaysAllowFinishTask: stateValues.alwaysAllowFinishTask ?? false, + alwaysAllowSubtasks: stateValues.alwaysAllowSubtasks ?? false, taskHistory: stateValues.taskHistory, allowedCommands: stateValues.allowedCommands, soundEnabled: stateValues.soundEnabled ?? false, diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index 4e76b0abbc..b7e3d850cf 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -109,7 +109,7 @@ export interface ExtensionState { alwaysAllowMcp?: boolean alwaysApproveResubmit?: boolean alwaysAllowModeSwitch?: boolean - alwaysAllowFinishTask?: boolean + alwaysAllowSubtasks?: boolean browserToolEnabled?: boolean requestDelaySeconds: number rateLimitSeconds: number // Minimum time between successive requests (0 = disabled) diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index 086701a43f..216c7588d7 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -48,7 +48,7 @@ export interface WebviewMessage { | "alwaysAllowBrowser" | "alwaysAllowMcp" | "alwaysAllowModeSwitch" - | "alwaysAllowFinishTask" + | "alwaysAllowSubtasks" | "playSound" | "soundEnabled" | "soundVolume" diff --git a/src/shared/globalState.ts b/src/shared/globalState.ts index 739fa11dad..35e53bbe9c 100644 --- a/src/shared/globalState.ts +++ b/src/shared/globalState.ts @@ -40,7 +40,7 @@ export const GLOBAL_STATE_KEYS = [ "alwaysAllowBrowser", "alwaysAllowMcp", "alwaysAllowModeSwitch", - "alwaysAllowFinishTask", + "alwaysAllowSubtasks", "taskHistory", "openAiBaseUrl", "openAiModelId", diff --git a/webview-ui/src/components/chat/AutoApproveMenu.tsx b/webview-ui/src/components/chat/AutoApproveMenu.tsx index fba97f6c7d..692cf1d44c 100644 --- a/webview-ui/src/components/chat/AutoApproveMenu.tsx +++ b/webview-ui/src/components/chat/AutoApproveMenu.tsx @@ -30,8 +30,8 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => { setAlwaysAllowMcp, alwaysAllowModeSwitch, setAlwaysAllowModeSwitch, - alwaysAllowFinishTask, - setAlwaysAllowFinishTask, + alwaysAllowSubtasks, + setAlwaysAllowSubtasks, alwaysApproveResubmit, setAlwaysApproveResubmit, autoApprovalEnabled, @@ -77,18 +77,17 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => { }, { id: "switchModes", - label: "Switch modes & create tasks", + label: "Switch modes", shortName: "Modes", enabled: alwaysAllowModeSwitch ?? false, - description: - "Allows automatic switching between different AI modes and creating new tasks without requiring approval.", + description: "Allows automatic switching between different modes without requiring approval.", }, { - id: "finishTask", - label: "Continue to next task", - shortName: "Continue", - enabled: alwaysAllowFinishTask ?? false, - description: "Allow tasks to end execution and continue to the next task, without user review or approval.", + id: "subtasks", + label: "Create & complete subtasks", + shortName: "Subtasks", + enabled: alwaysAllowSubtasks ?? false, + description: "Allow creation and completion of subtasks without requiring approval.", }, { id: "retryRequests", @@ -145,11 +144,11 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => { vscode.postMessage({ type: "alwaysAllowModeSwitch", bool: newValue }) }, [alwaysAllowModeSwitch, setAlwaysAllowModeSwitch]) - const handleFinishTaskChange = useCallback(() => { - const newValue = !(alwaysAllowFinishTask ?? false) - setAlwaysAllowFinishTask(newValue) - vscode.postMessage({ type: "alwaysAllowFinishTask", bool: newValue }) - }, [alwaysAllowFinishTask, setAlwaysAllowFinishTask]) + const handleSubtasksChange = useCallback(() => { + const newValue = !(alwaysAllowSubtasks ?? false) + setAlwaysAllowSubtasks(newValue) + vscode.postMessage({ type: "alwaysAllowSubtasks", bool: newValue }) + }, [alwaysAllowSubtasks, setAlwaysAllowSubtasks]) const handleRetryChange = useCallback(() => { const newValue = !(alwaysApproveResubmit ?? false) @@ -165,7 +164,7 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => { useBrowser: handleBrowserChange, useMcp: handleMcpChange, switchModes: handleModeSwitchChange, - finishTask: handleFinishTaskChange, + subtasks: handleSubtasksChange, retryRequests: handleRetryChange, } diff --git a/webview-ui/src/components/chat/ChatRow.tsx b/webview-ui/src/components/chat/ChatRow.tsx index bb5237dd23..b19d67dc05 100644 --- a/webview-ui/src/components/chat/ChatRow.tsx +++ b/webview-ui/src/components/chat/ChatRow.tsx @@ -452,7 +452,7 @@ export const ChatRowContent = ({
{toolIcon("new-file")} - Roo wants to create a new task in {tool.mode} mode: + Roo wants to create a new subtask in {tool.mode} mode:
@@ -464,8 +464,8 @@ export const ChatRowContent = ({ return ( <>
- {toolIcon("new-file")} - Roo wants to finish this task + {toolIcon("checklist")} + Roo wants to finish this subtask
{tool.content} diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index b92604e157..5ac7f50559 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -61,7 +61,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie setMode, autoApprovalEnabled, alwaysAllowModeSwitch, - alwaysAllowFinishTask, + alwaysAllowSubtasks, customModes, telemetrySetting, } = useExtensionState() @@ -150,7 +150,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie setSecondaryButtonText("Reject") break case "finishTask": - setPrimaryButtonText("Approve & Continue to the next Task") + setPrimaryButtonText("Complete Subtask and Return") setSecondaryButtonText(undefined) break default: @@ -646,11 +646,10 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie (alwaysAllowMcp && message.ask === "use_mcp_server" && isMcpToolAlwaysAllowed(message)) || (alwaysAllowModeSwitch && message.ask === "tool" && - (JSON.parse(message.text || "{}")?.tool === "switchMode" || - JSON.parse(message.text || "{}")?.tool === "newTask")) || - (alwaysAllowFinishTask && + JSON.parse(message.text || "{}")?.tool === "switchMode") || + (alwaysAllowSubtasks && message.ask === "tool" && - JSON.parse(message.text || "{}")?.tool === "finishTask") + ["newTask", "finishTask"].includes(JSON.parse(message.text || "{}")?.tool)) ) }, [ @@ -665,7 +664,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie alwaysAllowMcp, isMcpToolAlwaysAllowed, alwaysAllowModeSwitch, - alwaysAllowFinishTask, + alwaysAllowSubtasks, ], ) diff --git a/webview-ui/src/components/settings/AutoApproveSettings.tsx b/webview-ui/src/components/settings/AutoApproveSettings.tsx index 1c8e6c9ea9..d26fc33a7c 100644 --- a/webview-ui/src/components/settings/AutoApproveSettings.tsx +++ b/webview-ui/src/components/settings/AutoApproveSettings.tsx @@ -18,7 +18,7 @@ type AutoApproveSettingsProps = HTMLAttributes & { requestDelaySeconds: number alwaysAllowMcp?: boolean alwaysAllowModeSwitch?: boolean - alwaysAllowFinishTask?: boolean + alwaysAllowSubtasks?: boolean alwaysAllowExecute?: boolean allowedCommands?: string[] setCachedStateField: SetCachedStateField @@ -33,7 +33,7 @@ export const AutoApproveSettings = ({ requestDelaySeconds, alwaysAllowMcp, alwaysAllowModeSwitch, - alwaysAllowFinishTask, + alwaysAllowSubtasks, alwaysAllowExecute, allowedCommands, setCachedStateField, @@ -175,22 +175,21 @@ export const AutoApproveSettings = ({ setCachedStateField("alwaysAllowModeSwitch", e.target.checked)}> - Always approve mode switching & task creation + Always approve mode switching

- Automatically switch between different AI modes and create new tasks without requiring approval + Automatically switch between different modes without requiring approval

setCachedStateField("alwaysAllowFinishTask", e.target.checked)}> - Always approve finish & continue to next task + checked={alwaysAllowSubtasks} + onChange={(e: any) => setCachedStateField("alwaysAllowSubtasks", e.target.checked)}> + Always approve creation & completion of subtasks

- Automatically approve tasks to finish execution and continue to the next task, without user - review or approval + Allow creation and completion of subtasks without requiring approval

diff --git a/webview-ui/src/components/settings/SettingsView.tsx b/webview-ui/src/components/settings/SettingsView.tsx index 550fa73ca8..7cafbd6663 100644 --- a/webview-ui/src/components/settings/SettingsView.tsx +++ b/webview-ui/src/components/settings/SettingsView.tsx @@ -63,7 +63,7 @@ const SettingsView = forwardRef(({ onDone }, alwaysAllowExecute, alwaysAllowMcp, alwaysAllowModeSwitch, - alwaysAllowFinishTask, + alwaysAllowSubtasks, alwaysAllowWrite, alwaysApproveResubmit, browserToolEnabled, @@ -185,7 +185,7 @@ const SettingsView = forwardRef(({ onDone }, vscode.postMessage({ type: "currentApiConfigName", text: currentApiConfigName }) vscode.postMessage({ type: "updateExperimental", values: experiments }) vscode.postMessage({ type: "alwaysAllowModeSwitch", bool: alwaysAllowModeSwitch }) - vscode.postMessage({ type: "alwaysAllowFinishTask", bool: alwaysAllowFinishTask }) + vscode.postMessage({ type: "alwaysAllowSubtasks", bool: alwaysAllowSubtasks }) vscode.postMessage({ type: "upsertApiConfiguration", text: currentApiConfigName, apiConfiguration }) vscode.postMessage({ type: "telemetrySetting", text: telemetrySetting }) setChangeDetected(false) @@ -366,7 +366,7 @@ const SettingsView = forwardRef(({ onDone }, requestDelaySeconds={requestDelaySeconds} alwaysAllowMcp={alwaysAllowMcp} alwaysAllowModeSwitch={alwaysAllowModeSwitch} - alwaysAllowFinishTask={alwaysAllowFinishTask} + alwaysAllowSubtasks={alwaysAllowSubtasks} alwaysAllowExecute={alwaysAllowExecute} allowedCommands={allowedCommands} setCachedStateField={setCachedStateField} diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx index aa132919c0..8d16f2e0f0 100644 --- a/webview-ui/src/context/ExtensionStateContext.tsx +++ b/webview-ui/src/context/ExtensionStateContext.tsx @@ -31,7 +31,7 @@ export interface ExtensionStateContextType extends ExtensionState { setAlwaysAllowBrowser: (value: boolean) => void setAlwaysAllowMcp: (value: boolean) => void setAlwaysAllowModeSwitch: (value: boolean) => void - setAlwaysAllowFinishTask: (value: boolean) => void + setAlwaysAllowSubtasks: (value: boolean) => void setBrowserToolEnabled: (value: boolean) => void setShowRooIgnoredFiles: (value: boolean) => void setShowAnnouncement: (value: boolean) => void @@ -248,7 +248,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode setAlwaysAllowBrowser: (value) => setState((prevState) => ({ ...prevState, alwaysAllowBrowser: value })), setAlwaysAllowMcp: (value) => setState((prevState) => ({ ...prevState, alwaysAllowMcp: value })), setAlwaysAllowModeSwitch: (value) => setState((prevState) => ({ ...prevState, alwaysAllowModeSwitch: value })), - setAlwaysAllowFinishTask: (value) => setState((prevState) => ({ ...prevState, alwaysAllowFinishTask: value })), + setAlwaysAllowSubtasks: (value) => setState((prevState) => ({ ...prevState, alwaysAllowSubtasks: value })), setShowAnnouncement: (value) => setState((prevState) => ({ ...prevState, shouldShowAnnouncement: value })), setAllowedCommands: (value) => setState((prevState) => ({ ...prevState, allowedCommands: value })), setSoundEnabled: (value) => setState((prevState) => ({ ...prevState, soundEnabled: value })), From 91d16896b1457122fdfd3dc7e4045ecb1764e27c Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Sat, 8 Mar 2025 23:46:49 -0500 Subject: [PATCH 13/42] Changeset --- .changeset/modern-pillows-visit.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/modern-pillows-visit.md diff --git a/.changeset/modern-pillows-visit.md b/.changeset/modern-pillows-visit.md new file mode 100644 index 0000000000..4e5188175d --- /dev/null +++ b/.changeset/modern-pillows-visit.md @@ -0,0 +1,5 @@ +--- +"roo-cline": patch +--- + +v3.8.2 From 148e0560a1e0c6c70283d2c2c767b7fb6e125ba3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 9 Mar 2025 05:02:18 +0000 Subject: [PATCH 14/42] changeset version bump --- .changeset/modern-pillows-visit.md | 5 ----- .changeset/wild-dragons-leave.md | 5 ----- CHANGELOG.md | 7 +++++++ package-lock.json | 4 ++-- package.json | 2 +- 5 files changed, 10 insertions(+), 13 deletions(-) delete mode 100644 .changeset/modern-pillows-visit.md delete mode 100644 .changeset/wild-dragons-leave.md diff --git a/.changeset/modern-pillows-visit.md b/.changeset/modern-pillows-visit.md deleted file mode 100644 index 4e5188175d..0000000000 --- a/.changeset/modern-pillows-visit.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"roo-cline": patch ---- - -v3.8.2 diff --git a/.changeset/wild-dragons-leave.md b/.changeset/wild-dragons-leave.md deleted file mode 100644 index 05320a4aa2..0000000000 --- a/.changeset/wild-dragons-leave.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"roo-cline": patch ---- - -Add o3-mini support to openai compatible diff --git a/CHANGELOG.md b/CHANGELOG.md index ff2aa8de27..ba5d4ca8a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Roo Code Changelog +## 3.8.2 + +### Patch Changes + +- v3.8.2 +- Add o3-mini support to openai compatible + ## [3.8.1] - 2025-03-07 - Show the reserved output tokens in the context window visualization diff --git a/package-lock.json b/package-lock.json index 7c8935b791..61b8eea298 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "roo-cline", - "version": "3.8.1", + "version": "3.8.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "roo-cline", - "version": "3.8.1", + "version": "3.8.2", "dependencies": { "@anthropic-ai/bedrock-sdk": "^0.10.2", "@anthropic-ai/sdk": "^0.37.0", diff --git a/package.json b/package.json index 926218b21c..c3adb234b3 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "displayName": "Roo Code (prev. Roo Cline)", "description": "A whole dev team of AI agents in your editor.", "publisher": "RooVeterinaryInc", - "version": "3.8.1", + "version": "3.8.2", "icon": "assets/icons/rocket.png", "galleryBanner": { "color": "#617A91", From f4df55f020b310ec002e361cfb996c20ee7c40bf Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Sun, 9 Mar 2025 00:04:30 -0500 Subject: [PATCH 15/42] Update CHANGELOG.md --- CHANGELOG.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ba5d4ca8a7..ac03780cc8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,12 @@ # Roo Code Changelog -## 3.8.2 +## [3.8.2] - 2025-03-08 -### Patch Changes - -- v3.8.2 -- Add o3-mini support to openai compatible +- Create an auto-approval toggle for subtask creation and completion (thanks @shaybc!) +- Show a progress indicator when using the multi-diff editing strategy (thanks @qdaxb!) +- Add o3-mini support to the OpenAI-compatible provider (thanks @yt3trees!) +- Fix encoding issue where unreadable characters were sometimes getting added to the beginning of files +- Fix issue where settings dropdowns were getting truncated in some cases ## [3.8.1] - 2025-03-07 From 2a08c7c3dbbcaac5fc8b005109a0929a1ed8e9ef Mon Sep 17 00:00:00 2001 From: Roo Code Date: Sat, 8 Mar 2025 22:05:27 -0700 Subject: [PATCH 16/42] feat: Add toggle for custom mode creation This commit adds a new setting to allow users to disable custom mode creation, which can help reduce token usage in Roo's prompts. Key changes: Add enableCustomModeCreation setting to global state Conditionally include custom modes documentation in prompt only when enabled Add UI toggle in PromptsView with explanatory text Default the setting to enabled (true) for backward compatibility Update necessary interfaces and message handlers for the new setting The setting is placed in PromptsView rather than SettingsView since it directly relates to the modes functionality managed in that component. --- src/core/prompts/sections/modes.ts | 16 +++++++- src/core/webview/ClineProvider.ts | 4 ++ src/shared/ExtensionMessage.ts | 1 + src/shared/WebviewMessage.ts | 1 + src/shared/globalState.ts | 1 + .../src/components/prompts/PromptsView.tsx | 40 ++++++++++++++++++- .../src/context/ExtensionStateContext.tsx | 5 +++ 7 files changed, 65 insertions(+), 3 deletions(-) diff --git a/src/core/prompts/sections/modes.ts b/src/core/prompts/sections/modes.ts index f3863870db..d561e47a84 100644 --- a/src/core/prompts/sections/modes.ts +++ b/src/core/prompts/sections/modes.ts @@ -11,12 +11,21 @@ export async function getModesSection(context: vscode.ExtensionContext): Promise // Get all modes with their overrides from extension state const allModes = await getAllModesWithPrompts(context) - return `==== + // Get enableCustomModeCreation setting from extension state + const enableCustomModeCreation = await context.globalState.get("enableCustomModeCreation") + // Default to true if undefined + const shouldEnableCustomModeCreation = enableCustomModeCreation !== undefined ? enableCustomModeCreation : true + + let modesContent = `==== MODES - These are the currently available modes: -${allModes.map((mode: ModeConfig) => ` * "${mode.name}" mode (${mode.slug}) - ${mode.roleDefinition.split(".")[0]}`).join("\n")} +${allModes.map((mode: ModeConfig) => ` * "${mode.name}" mode (${mode.slug}) - ${mode.roleDefinition.split(".")[0]}`).join("\n")}` + + // Only include custom modes documentation if the feature is enabled + if (shouldEnableCustomModeCreation) { + modesContent += ` - Custom modes can be configured in two ways: 1. Globally via '${customModesPath}' (created automatically on startup) @@ -56,4 +65,7 @@ Both files should follow this structure: } ] }` + } + + return modesContent } diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index e1d67b5a28..75689e2450 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -1476,6 +1476,10 @@ export class ClineProvider implements vscode.WebviewViewProvider { await this.updateGlobalState("enhancementApiConfigId", message.text) await this.postStateToWebview() break + case "enableCustomModeCreation": + await this.updateGlobalState("enableCustomModeCreation", message.bool ?? true) + await this.postStateToWebview() + break case "autoApprovalEnabled": await this.updateGlobalState("autoApprovalEnabled", message.bool ?? false) await this.postStateToWebview() diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index 98ff9b36e1..78c60acc97 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -128,6 +128,7 @@ export interface ExtensionState { terminalOutputLimit?: number mcpEnabled: boolean enableMcpServerCreation: boolean + enableCustomModeCreation?: boolean mode: Mode modeApiConfigs?: Record enhancementApiConfigId?: string diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index 10af6f7a94..37328cd95a 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -71,6 +71,7 @@ export interface WebviewMessage { | "terminalOutputLimit" | "mcpEnabled" | "enableMcpServerCreation" + | "enableCustomModeCreation" | "searchCommits" | "alwaysApproveResubmit" | "requestDelaySeconds" diff --git a/src/shared/globalState.ts b/src/shared/globalState.ts index bfd24f4298..579bf1df86 100644 --- a/src/shared/globalState.ts +++ b/src/shared/globalState.ts @@ -84,6 +84,7 @@ export const GLOBAL_STATE_KEYS = [ "enhancementApiConfigId", "experiments", // Map of experiment IDs to their enabled state "autoApprovalEnabled", + "enableCustomModeCreation", // Enable the ability to create custom modes "customModes", // Array of custom modes "unboundModelId", "requestyModelId", diff --git a/webview-ui/src/components/prompts/PromptsView.tsx b/webview-ui/src/components/prompts/PromptsView.tsx index ccf1e6d700..e14ce8f939 100644 --- a/webview-ui/src/components/prompts/PromptsView.tsx +++ b/webview-ui/src/components/prompts/PromptsView.tsx @@ -71,6 +71,8 @@ const PromptsView = ({ onDone }: PromptsViewProps) => { preferredLanguage, setPreferredLanguage, customModes, + enableCustomModeCreation, + setEnableCustomModeCreation, } = useExtensionState() // Memoize modes to preserve array order @@ -341,6 +343,17 @@ const PromptsView = ({ onDone }: PromptsViewProps) => { return () => document.removeEventListener("click", handleClickOutside) }, [showConfigMenu]) + // Add effect to sync enableCustomModeCreation with backend + useEffect(() => { + if (enableCustomModeCreation !== undefined) { + // Send the value to the extension's global state + vscode.postMessage({ + type: "enableCustomModeCreation", // Using dedicated message type + bool: enableCustomModeCreation, + }) + } + }, [enableCustomModeCreation]) + useEffect(() => { const handler = (event: MessageEvent) => { const message = event.data @@ -541,8 +554,33 @@ const PromptsView = ({ onDone }: PromptsViewProps) => { in your workspace.
-
+ {/* + NOTE: This setting is placed in PromptsView rather than SettingsView since it + directly affects the functionality related to modes and custom mode creation, + which are managed in this component. This is an intentional deviation from + the standard pattern described in cline_docs/settings.md. + */} +
+ { + // Just update the local state through React context + // The React context will update the global state + setEnableCustomModeCreation(e.target.checked) + }}> + Enable Custom Mode Creation + +

+ When enabled, Roo can help you create project-level custom modes. You can disable this to + reduce Roo's token usage. +

+
e.stopPropagation()} className="flex justify-between items-center mb-3">

Modes

diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx index c4daf426ca..3ed42b8586 100644 --- a/webview-ui/src/context/ExtensionStateContext.tsx +++ b/webview-ui/src/context/ExtensionStateContext.tsx @@ -52,6 +52,8 @@ export interface ExtensionStateContextType extends ExtensionState { setMcpEnabled: (value: boolean) => void enableMcpServerCreation: boolean setEnableMcpServerCreation: (value: boolean) => void + enableCustomModeCreation?: boolean + setEnableCustomModeCreation: (value: boolean) => void alwaysApproveResubmit?: boolean setAlwaysApproveResubmit: (value: boolean) => void requestDelaySeconds: number @@ -117,6 +119,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode checkpointStorage: "task", fuzzyMatchThreshold: 1.0, preferredLanguage: "English", + enableCustomModeCreation: true, writeDelayMs: 1000, browserViewportSize: "900x600", screenshotQuality: 75, @@ -273,6 +276,8 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode setCustomSupportPrompts: (value) => setState((prevState) => ({ ...prevState, customSupportPrompts: value })), setEnhancementApiConfigId: (value) => setState((prevState) => ({ ...prevState, enhancementApiConfigId: value })), + setEnableCustomModeCreation: (value) => + setState((prevState) => ({ ...prevState, enableCustomModeCreation: value })), setAutoApprovalEnabled: (value) => setState((prevState) => ({ ...prevState, autoApprovalEnabled: value })), setCustomModes: (value) => setState((prevState) => ({ ...prevState, customModes: value })), setMaxOpenTabsContext: (value) => setState((prevState) => ({ ...prevState, maxOpenTabsContext: value })), From f6efa2b589b5ebda5204c64cbca9eba346483c25 Mon Sep 17 00:00:00 2001 From: Hannes Rudolph <49103247+hannesrudolph@users.noreply.github.com> Date: Sat, 8 Mar 2025 22:31:10 -0700 Subject: [PATCH 17/42] Update src/core/prompts/sections/modes.ts Co-authored-by: Matt Rubens --- src/core/prompts/sections/modes.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/core/prompts/sections/modes.ts b/src/core/prompts/sections/modes.ts index d561e47a84..788c7d7ebe 100644 --- a/src/core/prompts/sections/modes.ts +++ b/src/core/prompts/sections/modes.ts @@ -12,9 +12,7 @@ export async function getModesSection(context: vscode.ExtensionContext): Promise const allModes = await getAllModesWithPrompts(context) // Get enableCustomModeCreation setting from extension state - const enableCustomModeCreation = await context.globalState.get("enableCustomModeCreation") - // Default to true if undefined - const shouldEnableCustomModeCreation = enableCustomModeCreation !== undefined ? enableCustomModeCreation : true + const shouldEnableCustomModeCreation = await context.globalState.get("enableCustomModeCreation") ?? true let modesContent = `==== From bb87b7896b595093da4d1731112fe00662a16575 Mon Sep 17 00:00:00 2001 From: Hannes Rudolph <49103247+hannesrudolph@users.noreply.github.com> Date: Sat, 8 Mar 2025 22:31:20 -0700 Subject: [PATCH 18/42] Update src/shared/globalState.ts Co-authored-by: Matt Rubens --- src/shared/globalState.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/globalState.ts b/src/shared/globalState.ts index 579bf1df86..fd21d1c669 100644 --- a/src/shared/globalState.ts +++ b/src/shared/globalState.ts @@ -84,7 +84,7 @@ export const GLOBAL_STATE_KEYS = [ "enhancementApiConfigId", "experiments", // Map of experiment IDs to their enabled state "autoApprovalEnabled", - "enableCustomModeCreation", // Enable the ability to create custom modes + "enableCustomModeCreation", // Enable the ability for Roo to create custom modes "customModes", // Array of custom modes "unboundModelId", "requestyModelId", From 74a0dcebf6b61ecb97fb512ae6a55360c5b0c92e Mon Sep 17 00:00:00 2001 From: Hannes Rudolph <49103247+hannesrudolph@users.noreply.github.com> Date: Sat, 8 Mar 2025 22:32:14 -0700 Subject: [PATCH 19/42] Update webview-ui/src/components/prompts/PromptsView.tsx Co-authored-by: Matt Rubens --- webview-ui/src/components/prompts/PromptsView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webview-ui/src/components/prompts/PromptsView.tsx b/webview-ui/src/components/prompts/PromptsView.tsx index e14ce8f939..11e64003d1 100644 --- a/webview-ui/src/components/prompts/PromptsView.tsx +++ b/webview-ui/src/components/prompts/PromptsView.tsx @@ -577,7 +577,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => { marginTop: "5px", color: "var(--vscode-descriptionForeground)", }}> - When enabled, Roo can help you create project-level custom modes. You can disable this to + When enabled, Roo can help you create custom modes. You can disable this to reduce Roo's token usage.

From 47294dec564fd2a698b150dc3f6d3bd3e1fc4d0a Mon Sep 17 00:00:00 2001 From: Hannes Rudolph <49103247+hannesrudolph@users.noreply.github.com> Date: Sat, 8 Mar 2025 22:32:40 -0700 Subject: [PATCH 20/42] Update webview-ui/src/components/prompts/PromptsView.tsx Co-authored-by: Matt Rubens --- webview-ui/src/components/prompts/PromptsView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webview-ui/src/components/prompts/PromptsView.tsx b/webview-ui/src/components/prompts/PromptsView.tsx index 11e64003d1..7ea8bfba62 100644 --- a/webview-ui/src/components/prompts/PromptsView.tsx +++ b/webview-ui/src/components/prompts/PromptsView.tsx @@ -563,7 +563,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => { */}
{ // Just update the local state through React context // The React context will update the global state From cc4f6dd6757d4b7daef0ddd3ea0ba5804a7e1547 Mon Sep 17 00:00:00 2001 From: hannesrudolph Date: Sat, 8 Mar 2025 22:40:43 -0700 Subject: [PATCH 21/42] style: Update margin styling for checkbox in PromptsView component --- webview-ui/src/components/prompts/PromptsView.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/webview-ui/src/components/prompts/PromptsView.tsx b/webview-ui/src/components/prompts/PromptsView.tsx index 7ea8bfba62..3561a80d10 100644 --- a/webview-ui/src/components/prompts/PromptsView.tsx +++ b/webview-ui/src/components/prompts/PromptsView.tsx @@ -561,7 +561,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => { which are managed in this component. This is an intentional deviation from the standard pattern described in cline_docs/settings.md. */} -
+
{ @@ -577,8 +577,8 @@ const PromptsView = ({ onDone }: PromptsViewProps) => { marginTop: "5px", color: "var(--vscode-descriptionForeground)", }}> - When enabled, Roo can help you create custom modes. You can disable this to - reduce Roo's token usage. + When enabled, Roo can help you create custom modes. You can disable this to reduce Roo's + token usage.

e.stopPropagation()} className="flex justify-between items-center mb-3"> From 68cfdc985e87bf55d1f3f04aee39469108fb76f1 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Sun, 9 Mar 2025 09:11:55 -0400 Subject: [PATCH 22/42] Fix the VSCode LM model picker --- .changeset/eleven-birds-doubt.md | 5 +++ .../src/components/settings/ApiOptions.tsx | 32 +++++++++++-------- 2 files changed, 23 insertions(+), 14 deletions(-) create mode 100644 .changeset/eleven-birds-doubt.md diff --git a/.changeset/eleven-birds-doubt.md b/.changeset/eleven-birds-doubt.md new file mode 100644 index 0000000000..977228c808 --- /dev/null +++ b/.changeset/eleven-birds-doubt.md @@ -0,0 +1,5 @@ +--- +"roo-cline": patch +--- + +Fix VS Code LM API model picker diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index c5a02dc117..f7982080c8 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -1076,27 +1076,31 @@ const ApiOptions = ({ Language Model {vsCodeLmModels.length > 0 ? ( - { - const valueStr = (e as DropdownOption)?.value + onValueChange={handleInputChange("vsCodeLmModelSelector", (valueStr) => { const [vendor, family] = valueStr.split("/") return { vendor, family } - })} - options={[ - { value: "", label: "Select a model..." }, - ...vsCodeLmModels.map((model) => ({ - value: `${model.vendor}/${model.family}`, - label: `${model.vendor} - ${model.family}`, - })), - ]} - className="w-full" - /> + })}> + + + + + + {vsCodeLmModels.map((model) => ( + + {`${model.vendor} - ${model.family}`} + + ))} + + + ) : (
The VS Code Language Model API allows you to run models provided by other VS Code From b57fcf2094c8a96a327caa271086f02259d36da3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 9 Mar 2025 13:46:40 +0000 Subject: [PATCH 23/42] changeset version bump --- .changeset/eleven-birds-doubt.md | 5 ----- CHANGELOG.md | 6 ++++++ package-lock.json | 4 ++-- package.json | 2 +- 4 files changed, 9 insertions(+), 8 deletions(-) delete mode 100644 .changeset/eleven-birds-doubt.md diff --git a/.changeset/eleven-birds-doubt.md b/.changeset/eleven-birds-doubt.md deleted file mode 100644 index 977228c808..0000000000 --- a/.changeset/eleven-birds-doubt.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"roo-cline": patch ---- - -Fix VS Code LM API model picker diff --git a/CHANGELOG.md b/CHANGELOG.md index ac03780cc8..248bdec9d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Roo Code Changelog +## 3.8.3 + +### Patch Changes + +- Fix VS Code LM API model picker + ## [3.8.2] - 2025-03-08 - Create an auto-approval toggle for subtask creation and completion (thanks @shaybc!) diff --git a/package-lock.json b/package-lock.json index 61b8eea298..6884f2e626 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "roo-cline", - "version": "3.8.2", + "version": "3.8.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "roo-cline", - "version": "3.8.2", + "version": "3.8.3", "dependencies": { "@anthropic-ai/bedrock-sdk": "^0.10.2", "@anthropic-ai/sdk": "^0.37.0", diff --git a/package.json b/package.json index c3adb234b3..e4adffd1d7 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "displayName": "Roo Code (prev. Roo Cline)", "description": "A whole dev team of AI agents in your editor.", "publisher": "RooVeterinaryInc", - "version": "3.8.2", + "version": "3.8.3", "icon": "assets/icons/rocket.png", "galleryBanner": { "color": "#617A91", From edec39b98ea05a1101e87b81619a6c7f7254f04f Mon Sep 17 00:00:00 2001 From: R00-B0T Date: Sun, 9 Mar 2025 13:47:05 +0000 Subject: [PATCH 24/42] Updating CHANGELOG.md format --- CHANGELOG.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 248bdec9d3..9c69784ac7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,6 @@ # Roo Code Changelog -## 3.8.3 - -### Patch Changes +## [3.8.3] - Fix VS Code LM API model picker From a05d7c1c8e65d9e0f086fb7101d33fb765da5a13 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Sun, 9 Mar 2025 09:51:44 -0400 Subject: [PATCH 25/42] Update CHANGELOG.md --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c69784ac7..7afcfae2d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ # Roo Code Changelog -## [3.8.3] +## [3.8.3] - 2025-03-09 -- Fix VS Code LM API model picker +- Fix VS Code LM API model picker truncation issue ## [3.8.2] - 2025-03-08 From 445066c430f52d5cb8f1da0cea833a8ec5f9961a Mon Sep 17 00:00:00 2001 From: hannesrudolph Date: Sun, 9 Mar 2025 15:56:23 -0600 Subject: [PATCH 26/42] refactor: Move custom mode creation setting to a more appropriate location in PromptsView component --- .../src/components/prompts/PromptsView.tsx | 55 ++++++++++--------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/webview-ui/src/components/prompts/PromptsView.tsx b/webview-ui/src/components/prompts/PromptsView.tsx index 3561a80d10..7303f7691d 100644 --- a/webview-ui/src/components/prompts/PromptsView.tsx +++ b/webview-ui/src/components/prompts/PromptsView.tsx @@ -555,32 +555,6 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
- {/* - NOTE: This setting is placed in PromptsView rather than SettingsView since it - directly affects the functionality related to modes and custom mode creation, - which are managed in this component. This is an intentional deviation from - the standard pattern described in cline_docs/settings.md. - */} -
- { - // Just update the local state through React context - // The React context will update the global state - setEnableCustomModeCreation(e.target.checked) - }}> - Enable Custom Mode Creation - -

- When enabled, Roo can help you create custom modes. You can disable this to reduce Roo's - token usage. -

-
e.stopPropagation()} className="flex justify-between items-center mb-3">

Modes

@@ -1048,6 +1022,35 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
)}
+ + {/* + NOTE: This setting is placed in PromptsView rather than SettingsView since it + directly affects the functionality related to modes and custom mode creation, + which are managed in this component. This is an intentional deviation from + the standard pattern described in cline_docs/settings.md. + */} +
+ { + // Just update the local state through React context + // The React context will update the global state + setEnableCustomModeCreation(e.target.checked) + }}> + Enable Custom Mode Creation + +

+ When enabled, Roo allows you to create custom modes using prompts like ‘Make me a custom + mode that…’. Disabling this reduces your system prompt by about 700 tokens when this feature + isn’t needed. When disabled you can still manually create custom prompts using the + button + above or by editing the related config JSON. +

+
Date: Sun, 9 Mar 2025 15:07:59 -0700 Subject: [PATCH 27/42] App tab layout fixes --- .env.sample | 3 +- webview-ui/src/App.tsx | 50 +++++------- .../src/components/chat/Announcement.tsx | 4 +- .../chat/checkpoints/CheckpointMenu.tsx | 14 +--- webview-ui/src/components/common/Alert.tsx | 15 ++++ webview-ui/src/components/common/Tab.tsx | 23 ++++++ .../src/components/history/HistoryView.tsx | 15 ++-- webview-ui/src/components/mcp/McpView.tsx | 26 +++--- .../src/components/prompts/PromptsView.tsx | 18 +++-- .../src/components/settings/SettingsView.tsx | 81 ++++++++----------- .../src/components/ui/dropdown-menu.tsx | 36 +++++---- webview-ui/src/components/ui/popover.tsx | 36 +++++---- .../src/components/ui/select-dropdown.tsx | 14 +--- webview-ui/src/components/ui/select.tsx | 4 +- .../src/components/welcome/WelcomeView.tsx | 38 ++++----- webview-ui/src/index.css | 4 + 16 files changed, 198 insertions(+), 183 deletions(-) create mode 100644 webview-ui/src/components/common/Alert.tsx create mode 100644 webview-ui/src/components/common/Tab.tsx diff --git a/.env.sample b/.env.sample index 6cdaa1b3b1..4d6c24ac72 100644 --- a/.env.sample +++ b/.env.sample @@ -1,2 +1 @@ -# PostHog API Keys for telemetry -POSTHOG_API_KEY=key-goes-here \ No newline at end of file +POSTHOG_API_KEY=key-goes-here diff --git a/webview-ui/src/App.tsx b/webview-ui/src/App.tsx index b537b9298e..99dda495d0 100644 --- a/webview-ui/src/App.tsx +++ b/webview-ui/src/App.tsx @@ -17,6 +17,12 @@ import { HumanRelayDialog } from "./components/human-relay/HumanRelayDialog" type Tab = "settings" | "history" | "mcp" | "prompts" | "chat" +type HumanRelayDialogState = { + isOpen: boolean + requestId: string + promptText: string +} + const tabsByMessageAction: Partial, Tab>> = { chatButtonClicked: "chat", settingsButtonClicked: "settings", @@ -24,24 +30,21 @@ const tabsByMessageAction: Partial { const { didHydrateState, showWelcome, shouldShowAnnouncement, telemetrySetting, telemetryKey, machineId } = useExtensionState() + const [showAnnouncement, setShowAnnouncement] = useState(false) const [tab, setTab] = useState("chat") - const settingsRef = useRef(null) - - // Human Relay Dialog Status - const [humanRelayDialogState, setHumanRelayDialogState] = useState<{ - isOpen: boolean - requestId: string - promptText: string - }>({ + const [humanRelayDialogState, setHumanRelayDialogState] = useState({ isOpen: false, requestId: "", promptText: "", }) + const settingsRef = useRef(null) + const switchTab = useCallback((newTab: Tab) => { if (settingsRef.current?.checkUnsaveChanges) { settingsRef.current.checkUnsaveChanges(() => setTab(newTab)) @@ -74,23 +77,6 @@ const App = () => { [switchTab], ) - // Processing Human Relay Dialog Submission - const handleHumanRelaySubmit = (requestId: string, text: string) => { - vscode.postMessage({ - type: "humanRelayResponse", - requestId, - text, - }) - } - - // Handle Human Relay dialog box cancel - const handleHumanRelayCancel = (requestId: string) => { - vscode.postMessage({ - type: "humanRelayCancel", - requestId, - }) - } - useEvent("message", onMessage) useEffect(() => { @@ -106,7 +92,7 @@ const App = () => { } }, [telemetrySetting, telemetryKey, machineId, didHydrateState]) - // Tell Extension that we are ready to receive messages + // Tell the extension that we are ready to receive messages. useEffect(() => { vscode.postMessage({ type: "webviewDidLaunch" }) }, []) @@ -121,24 +107,23 @@ const App = () => { ) : ( <> - {tab === "settings" && setTab("chat")} />} - {tab === "history" && switchTab("chat")} />} - {tab === "mcp" && switchTab("chat")} />} {tab === "prompts" && switchTab("chat")} />} + {tab === "mcp" && switchTab("chat")} />} + {tab === "history" && switchTab("chat")} />} + {tab === "settings" && setTab("chat")} />} setShowAnnouncement(false)} showHistoryView={() => switchTab("history")} /> - {/* Human Relay Dialog */} setHumanRelayDialogState((prev) => ({ ...prev, isOpen: false }))} - onSubmit={handleHumanRelaySubmit} - onCancel={handleHumanRelayCancel} + onSubmit={(requestId, text) => vscode.postMessage({ type: "humanRelayResponse", requestId, text })} + onCancel={(requestId) => vscode.postMessage({ type: "humanRelayCancel", requestId })} /> ) @@ -147,6 +132,7 @@ const App = () => { const AppWithProviders = () => ( +
) diff --git a/webview-ui/src/components/chat/Announcement.tsx b/webview-ui/src/components/chat/Announcement.tsx index 82fdb628eb..791ca26085 100644 --- a/webview-ui/src/components/chat/Announcement.tsx +++ b/webview-ui/src/components/chat/Announcement.tsx @@ -33,7 +33,7 @@ const Announcement = ({ version, hideAnnouncement }: AnnouncementProps) => {

What's New

-

+

  • • Faster asynchronous checkpoints
  • • Support for .rooignore files
  • @@ -44,7 +44,7 @@ const Announcement = ({ version, hideAnnouncement }: AnnouncementProps) => {
  • • Updated DeepSeek provider
  • • New "Human Relay" provider
-

+

Get more details and discuss in{" "} diff --git a/webview-ui/src/components/chat/checkpoints/CheckpointMenu.tsx b/webview-ui/src/components/chat/checkpoints/CheckpointMenu.tsx index 1910fc0bb6..63867c9858 100644 --- a/webview-ui/src/components/chat/checkpoints/CheckpointMenu.tsx +++ b/webview-ui/src/components/chat/checkpoints/CheckpointMenu.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useCallback } from "react" +import { useState, useCallback } from "react" import { CheckIcon, Cross2Icon } from "@radix-ui/react-icons" import { Button, Popover, PopoverContent, PopoverTrigger } from "@/components/ui" @@ -14,7 +14,6 @@ type CheckpointMenuProps = { } export const CheckpointMenu = ({ ts, commitHash, currentHash, checkpoint }: CheckpointMenuProps) => { - const [portalContainer, setPortalContainer] = useState() const [isOpen, setIsOpen] = useState(false) const [isConfirming, setIsConfirming] = useState(false) @@ -42,15 +41,6 @@ export const CheckpointMenu = ({ ts, commitHash, currentHash, checkpoint }: Chec setIsOpen(false) }, [ts, commitHash]) - useEffect(() => { - // The dropdown menu uses a portal from @shadcn/ui which by default renders - // at the document root. This causes the menu to remain visible even when - // the parent ChatView component is hidden (during settings/history view). - // By moving the portal inside ChatView, the menu will properly hide when - // its parent is hidden. - setPortalContainer(document.getElementById("chat-view-portal") || undefined) - }, []) - return (

{isDiffAvailable && ( @@ -70,7 +60,7 @@ export const CheckpointMenu = ({ ts, commitHash, currentHash, checkpoint }: Chec - +
{!isCurrent && (
diff --git a/webview-ui/src/components/common/Alert.tsx b/webview-ui/src/components/common/Alert.tsx new file mode 100644 index 0000000000..b16e799b91 --- /dev/null +++ b/webview-ui/src/components/common/Alert.tsx @@ -0,0 +1,15 @@ +import { cn } from "@/lib/utils" +import { HTMLAttributes } from "react" + +type AlertProps = HTMLAttributes + +export const Alert = ({ className, children, ...props }: AlertProps) => ( +
+ {children} +
+) diff --git a/webview-ui/src/components/common/Tab.tsx b/webview-ui/src/components/common/Tab.tsx new file mode 100644 index 0000000000..982fb7e103 --- /dev/null +++ b/webview-ui/src/components/common/Tab.tsx @@ -0,0 +1,23 @@ +import { HTMLAttributes } from "react" + +import { cn } from "@/lib/utils" + +type TabProps = HTMLAttributes + +export const Tab = ({ className, children, ...props }: TabProps) => ( +
+ {children} +
+) + +export const TabHeader = ({ className, children, ...props }: TabProps) => ( +
+ {children} +
+) + +export const TabContent = ({ className, children, ...props }: TabProps) => ( +
+ {children} +
+) diff --git a/webview-ui/src/components/history/HistoryView.tsx b/webview-ui/src/components/history/HistoryView.tsx index e65a11a3ec..ec44f8eaca 100644 --- a/webview-ui/src/components/history/HistoryView.tsx +++ b/webview-ui/src/components/history/HistoryView.tsx @@ -9,6 +9,7 @@ import { formatLargeNumber, formatDate } from "@/utils/format" import { cn } from "@/lib/utils" import { Button } from "@/components/ui" +import { Tab, TabContent, TabHeader } from "../common/Tab" import { useTaskSearch } from "./useTaskSearch" import { ExportButton } from "./ExportButton" import { CopyButton } from "./CopyButton" @@ -25,8 +26,8 @@ const HistoryView = ({ onDone }: HistoryViewProps) => { const [deleteTaskId, setDeleteTaskId] = useState(null) return ( -
-
+ +

History

Done @@ -81,8 +82,9 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
-
-
+ + + {
)} /> -
+ + {deleteTaskId && ( !open && setDeleteTaskId(null)} open /> )} -
+ ) } diff --git a/webview-ui/src/components/mcp/McpView.tsx b/webview-ui/src/components/mcp/McpView.tsx index 7a24922d88..ce37a4c09d 100644 --- a/webview-ui/src/components/mcp/McpView.tsx +++ b/webview-ui/src/components/mcp/McpView.tsx @@ -1,3 +1,4 @@ +import { useState } from "react" import { VSCodeButton, VSCodeCheckbox, @@ -6,14 +7,17 @@ import { VSCodePanelTab, VSCodePanelView, } from "@vscode/webview-ui-toolkit/react" -import { useState } from "react" -import { vscode } from "../../utils/vscode" -import { useExtensionState } from "../../context/ExtensionStateContext" + import { McpServer } from "../../../../src/shared/mcp" + +import { vscode } from "@/utils/vscode" +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from "@/components/ui" + +import { useExtensionState } from "../../context/ExtensionStateContext" +import { Tab, TabContent, TabHeader } from "../common/Tab" import McpToolRow from "./McpToolRow" import McpResourceRow from "./McpResourceRow" import McpEnabledToggle from "./McpEnabledToggle" -import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from "../ui/dialog" type McpViewProps = { onDone: () => void @@ -29,12 +33,13 @@ const McpView = ({ onDone }: McpViewProps) => { } = useExtensionState() return ( -
-
+ +

MCP Servers

Done -
-
+ + +
{
)} -
-
+ + ) } -// Server Row Component const ServerRow = ({ server, alwaysAllowMcp }: { server: McpServer; alwaysAllowMcp?: boolean }) => { const [isExpanded, setIsExpanded] = useState(false) const [showDeleteConfirm, setShowDeleteConfirm] = useState(false) diff --git a/webview-ui/src/components/prompts/PromptsView.tsx b/webview-ui/src/components/prompts/PromptsView.tsx index ccf1e6d700..d51d7d8909 100644 --- a/webview-ui/src/components/prompts/PromptsView.tsx +++ b/webview-ui/src/components/prompts/PromptsView.tsx @@ -42,6 +42,7 @@ import { import { TOOL_GROUPS, GROUP_DISPLAY_NAMES, ToolGroup } from "../../../../src/shared/tool-groups" import { vscode } from "../../utils/vscode" +import { Tab, TabContent, TabHeader } from "../common/Tab" // Get all available groups that should show in prompts view const availableGroups = (Object.keys(TOOL_GROUPS) as ToolGroup[]).filter((group) => !TOOL_GROUPS[group].alwaysAvailable) @@ -406,12 +407,13 @@ const PromptsView = ({ onDone }: PromptsViewProps) => { } return ( -
-
+ +

Prompts

Done -
-
+ + +
Preferred Language
@@ -934,6 +936,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
+
{ )}
-
+ + {isCreateModeDialogOpen && (
{
)} + {isDialogOpen && (
{
)} + {isCustomLanguage && (
@@ -1497,7 +1503,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
)} -
+ ) } diff --git a/webview-ui/src/components/settings/SettingsView.tsx b/webview-ui/src/components/settings/SettingsView.tsx index 7cafbd6663..df08a03971 100644 --- a/webview-ui/src/components/settings/SettingsView.tsx +++ b/webview-ui/src/components/settings/SettingsView.tsx @@ -22,6 +22,7 @@ import { Button, } from "@/components/ui" +import { Tab, TabContent, TabHeader } from "../common/Tab" import { SetCachedStateField, SetExperimentEnabled } from "./types" import { SectionHeader } from "./SectionHeader" import ApiConfigManager from "./ApiConfigManager" @@ -263,53 +264,41 @@ const SettingsView = forwardRef(({ onDone }, const scrollToSection = (ref: React.RefObject) => ref.current?.scrollIntoView() return ( -
-
-
-
-
-

Settings

-
- {sections.map(({ id, icon: Icon, ref }) => ( - - ))} -
-
-
- - Save - - checkUnsaveChanges(onDone)}> - Done - -
+ + +
+

Settings

+
+ {sections.map(({ id, icon: Icon, ref }) => ( + + ))}
-
+
+ + Save + + checkUnsaveChanges(onDone)}> + Done + +
+ -
+
@@ -425,7 +414,7 @@ const SettingsView = forwardRef(({ onDone }, telemetrySetting={telemetrySetting} setTelemetrySetting={setTelemetrySetting} /> -
+ @@ -442,7 +431,7 @@ const SettingsView = forwardRef(({ onDone }, -
+ ) }) diff --git a/webview-ui/src/components/ui/dropdown-menu.tsx b/webview-ui/src/components/ui/dropdown-menu.tsx index fc5ad5b2b8..3193f497ca 100644 --- a/webview-ui/src/components/ui/dropdown-menu.tsx +++ b/webview-ui/src/components/ui/dropdown-menu.tsx @@ -53,23 +53,25 @@ DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayNam const DropdownMenuContent = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef & { - container?: HTMLElement - } ->(({ className, sideOffset = 4, container, ...props }, ref) => ( - - - -)) + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => { + const container = React.useMemo(() => document.getElementById("roo-portal"), []) + + return ( + + + + ) +}) DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName const DropdownMenuItem = React.forwardRef< diff --git a/webview-ui/src/components/ui/popover.tsx b/webview-ui/src/components/ui/popover.tsx index 3ab3344689..b6235853ca 100644 --- a/webview-ui/src/components/ui/popover.tsx +++ b/webview-ui/src/components/ui/popover.tsx @@ -11,23 +11,25 @@ const PopoverAnchor = PopoverPrimitive.Anchor const PopoverContent = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef & { - container?: HTMLElement - } ->(({ className, align = "center", sideOffset = 4, container, ...props }, ref) => ( - - - -)) + React.ComponentPropsWithoutRef +>(({ className, align = "center", sideOffset = 4, ...props }, ref) => { + const container = React.useMemo(() => document.getElementById("roo-portal"), []) + + return ( + + + + ) +}) PopoverContent.displayName = PopoverPrimitive.Content.displayName export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor } diff --git a/webview-ui/src/components/ui/select-dropdown.tsx b/webview-ui/src/components/ui/select-dropdown.tsx index 775066732d..eef474cd02 100644 --- a/webview-ui/src/components/ui/select-dropdown.tsx +++ b/webview-ui/src/components/ui/select-dropdown.tsx @@ -7,7 +7,6 @@ import { DropdownMenuSeparator, } from "./dropdown-menu" import { cn } from "@/lib/utils" -import { useEffect, useState } from "react" // Constants for option types export enum DropdownOptionType { @@ -39,6 +38,8 @@ export interface SelectDropdownProps { shortcutText?: string } +// TODO: Get rid of this and use the native @shadcn/ui `Select` component. + export const SelectDropdown = React.forwardRef, SelectDropdownProps>( ( { @@ -60,16 +61,6 @@ export const SelectDropdown = React.forwardRef { // Track open state const [open, setOpen] = React.useState(false) - const [portalContainer, setPortalContainer] = useState() - - useEffect(() => { - // The dropdown menu uses a portal from @shadcn/ui which by default renders - // at the document root. This causes the menu to remain visible even when - // the parent ChatView component is hidden (during settings/history view). - // By moving the portal inside ChatView, the menu will properly hide when - // its parent is hidden. - setPortalContainer(document.getElementById("chat-view-portal") || undefined) - }, []) // Find the selected option label const selectedOption = options.find((option) => option.value === value) @@ -130,7 +121,6 @@ export const SelectDropdown = React.forwardRef setOpen(false)} onInteractOutside={() => setOpen(false)} - container={portalContainer} className={cn( "bg-vscode-dropdown-background text-vscode-dropdown-foreground border border-vscode-dropdown-border z-50", contentClassName, diff --git a/webview-ui/src/components/ui/select.tsx b/webview-ui/src/components/ui/select.tsx index 50f89b3760..5e673257cf 100644 --- a/webview-ui/src/components/ui/select.tsx +++ b/webview-ui/src/components/ui/select.tsx @@ -39,8 +39,10 @@ function SelectContent({ position = "popper", ...props }: React.ComponentProps) { + const container = React.useMemo(() => document.getElementById("roo-portal"), []) + return ( - + { const { apiConfiguration, currentApiConfigName, setApiConfiguration, uriScheme } = useExtensionState() @@ -23,18 +26,16 @@ const WelcomeView = () => { }, [apiConfiguration, currentApiConfigName]) return ( -
-

Hi, I'm Roo!

-

- I can do all kinds of tasks thanks to the latest breakthroughs in agentic coding capabilities and access - to tools that let me create & edit files, explore complex projects, use the browser, and execute - terminal commands (with your permission, of course). I can even use MCP to create new tools and extend - my own capabilities. -

- - To get started, this extension needs an API provider. - -
+ + +

Hi, I'm Roo!

+
+ I can do all kinds of tasks thanks to the latest breakthroughs in agentic coding capabilities and + access to tools that let me create & edit files, explore complex projects, use the browser, and + execute terminal commands (with your permission, of course). I can even use MCP to create new tools + and extend my own capabilities. +
+ To get started, this extension needs an API provider. { errorMessage={errorMessage} setErrorMessage={setErrorMessage} /> -
- -
-
+ +
+
Let's go! - {errorMessage && {errorMessage}} + {errorMessage &&
{errorMessage}
}
-
+ ) } diff --git a/webview-ui/src/index.css b/webview-ui/src/index.css index 6cd405b93f..2144557a98 100644 --- a/webview-ui/src/index.css +++ b/webview-ui/src/index.css @@ -111,6 +111,10 @@ --color-vscode-charts-green: var(--vscode-charts-green); --color-vscode-charts-yellow: var(--vscode-charts-yellow); + + --color-vscode-inputValidation-infoForeground: var(--vscode-inputValidation-infoForeground); + --color-vscode-inputValidation-infoBackground: var(--vscode-inputValidation-infoBackground); + --color-vscode-inputValidation-infoBorder: var(--vscode-inputValidation-infoBorder); } @layer base { From 5f20fbbf44126f097abb64cf06b52cc399a66086 Mon Sep 17 00:00:00 2001 From: cte Date: Sun, 9 Mar 2025 15:08:36 -0700 Subject: [PATCH 28/42] Add changeset --- .changeset/lemon-bulldogs-unite.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/lemon-bulldogs-unite.md diff --git a/.changeset/lemon-bulldogs-unite.md b/.changeset/lemon-bulldogs-unite.md new file mode 100644 index 0000000000..b87abc1bd2 --- /dev/null +++ b/.changeset/lemon-bulldogs-unite.md @@ -0,0 +1,5 @@ +--- +"roo-cline": patch +--- + +App tab layout fixes From 78be1b89775612e545828c811513287b93dc1da2 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Sun, 9 Mar 2025 18:35:48 -0400 Subject: [PATCH 29/42] Revert "Clean up the tool progress UX" This reverts commit 1c8071bbb371f019994c0bf7849ba37df2ef67e7. --- src/core/diff/strategies/multi-search-replace.ts | 12 +++++------- src/shared/ExtensionMessage.ts | 1 - webview-ui/src/components/common/CodeAccordian.tsx | 14 ++++++++------ 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/core/diff/strategies/multi-search-replace.ts b/src/core/diff/strategies/multi-search-replace.ts index bcf2f65430..0462629b9b 100644 --- a/src/core/diff/strategies/multi-search-replace.ts +++ b/src/core/diff/strategies/multi-search-replace.ts @@ -368,20 +368,18 @@ Only use a single line of '=======' between search and replacement content, beca getProgressStatus(toolUse: ToolUse, result?: DiffResult): ToolProgressStatus { const diffContent = toolUse.params.diff if (diffContent) { - const icon = "diff-multiple" - const searchBlockCount = (diffContent.match(/SEARCH/g) || []).length if (toolUse.partial) { if (diffContent.length < 1000 || (diffContent.length / 50) % 10 === 0) { - return { icon, text: `${searchBlockCount}` } + return { text: `progressing ${(diffContent.match(/SEARCH/g) || []).length} blocks...` } } } else if (result) { - if (result.failParts?.length) { + const searchBlockCount = (diffContent.match(/SEARCH/g) || []).length + if (result.failParts) { return { - icon, - text: `${searchBlockCount - result.failParts.length}/${searchBlockCount}`, + text: `progressed ${searchBlockCount - result.failParts.length}/${searchBlockCount} blocks.`, } } else { - return { icon, text: `${searchBlockCount}` } + return { text: `progressed ${searchBlockCount} blocks.` } } } } diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index b7e3d850cf..0c20669ed1 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -277,6 +277,5 @@ export interface HumanRelayCancelMessage { export type ClineApiReqCancelReason = "streaming_failed" | "user_cancelled" export type ToolProgressStatus = { - icon?: string text?: string } diff --git a/webview-ui/src/components/common/CodeAccordian.tsx b/webview-ui/src/components/common/CodeAccordian.tsx index 9d2f224ffb..defafdc869 100644 --- a/webview-ui/src/components/common/CodeAccordian.tsx +++ b/webview-ui/src/components/common/CodeAccordian.tsx @@ -99,12 +99,14 @@ const CodeAccordian = ({ )}
{progressStatus && progressStatus.text && ( - <> - {progressStatus.icon && } - - {progressStatus.text} - - + + {progressStatus.text} + )}
From 183e47b1bd14a0432a6a167fe1e1e651e27059c1 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Sun, 9 Mar 2025 18:35:56 -0400 Subject: [PATCH 30/42] Revert "support tool progress status" This reverts commit 80139d88d7d7ab3baee492955a7380ade17550cc. --- src/core/Cline.ts | 26 +------------------ .../diff/strategies/multi-search-replace.ts | 23 ---------------- src/core/diff/types.ts | 5 ---- src/shared/ExtensionMessage.ts | 5 ---- webview-ui/src/components/chat/ChatRow.tsx | 1 - .../src/components/common/CodeAccordian.tsx | 13 ---------- 6 files changed, 1 insertion(+), 72 deletions(-) diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 3d27a50255..3d1f980c7d 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -48,7 +48,6 @@ import { ClineSay, ClineSayBrowserAction, ClineSayTool, - ToolProgressStatus, } from "../shared/ExtensionMessage" import { getApiMetrics } from "../shared/getApiMetrics" import { HistoryItem } from "../shared/HistoryItem" @@ -409,7 +408,6 @@ export class Cline { type: ClineAsk, text?: string, partial?: boolean, - progressStatus?: ToolProgressStatus, ): Promise<{ response: ClineAskResponse; text?: string; images?: string[] }> { // If this Cline instance was aborted by the provider, then the only thing keeping us alive is a promise still running in the background, in which case we don't want to send its result to the webview as it is attached to a new instance of Cline now. So we can safely ignore the result of any active promises, and this class will be deallocated. (Although we set Cline = undefined in provider, that simply removes the reference to this instance, but the instance is still alive until this promise resolves or rejects.) if (this.abort) { @@ -425,7 +423,6 @@ export class Cline { // existing partial message, so update it lastMessage.text = text lastMessage.partial = partial - lastMessage.progressStatus = progressStatus // todo be more efficient about saving and posting only new data or one whole message at a time so ignore partial for saves, and only post parts of partial message instead of whole array in new listener // await this.saveClineMessages() // await this.providerRef.deref()?.postStateToWebview() @@ -463,8 +460,6 @@ export class Cline { // lastMessage.ts = askTs lastMessage.text = text lastMessage.partial = false - lastMessage.progressStatus = progressStatus - await this.saveClineMessages() // await this.providerRef.deref()?.postStateToWebview() await this.providerRef @@ -516,7 +511,6 @@ export class Cline { images?: string[], partial?: boolean, checkpoint?: Record, - progressStatus?: ToolProgressStatus, ): Promise { if (this.abort) { throw new Error(`Task: ${this.taskNumber} Roo Code instance aborted (#2)`) @@ -532,7 +526,6 @@ export class Cline { lastMessage.text = text lastMessage.images = images lastMessage.partial = partial - lastMessage.progressStatus = progressStatus await this.providerRef .deref() ?.postMessageToWebview({ type: "partialMessage", partialMessage: lastMessage }) @@ -552,7 +545,6 @@ export class Cline { lastMessage.text = text lastMessage.images = images lastMessage.partial = false - lastMessage.progressStatus = progressStatus // instead of streaming partialMessage events, we do a save and post like normal to persist to disk await this.saveClineMessages() @@ -1711,16 +1703,8 @@ export class Cline { try { if (block.partial) { // update gui message - let toolProgressStatus - if (this.diffStrategy && this.diffStrategy.getProgressStatus) { - toolProgressStatus = this.diffStrategy.getProgressStatus(block) - } - const partialMessage = JSON.stringify(sharedMessageProps) - - await this.ask("tool", partialMessage, block.partial, toolProgressStatus).catch( - () => {}, - ) + await this.ask("tool", partialMessage, block.partial).catch(() => {}) break } else { if (!relPath) { @@ -1815,14 +1799,6 @@ export class Cline { diff: diffContent, } satisfies ClineSayTool) - let toolProgressStatus - if (this.diffStrategy && this.diffStrategy.getProgressStatus) { - toolProgressStatus = this.diffStrategy.getProgressStatus(block, diffResult) - } - await this.ask("tool", completeMessage, block.partial, toolProgressStatus).catch( - () => {}, - ) - const didApprove = await askApproval("tool", completeMessage) if (!didApprove) { await this.diffViewProvider.revertChanges() // This likely handles closing the diff view diff --git a/src/core/diff/strategies/multi-search-replace.ts b/src/core/diff/strategies/multi-search-replace.ts index 0462629b9b..99c22a31df 100644 --- a/src/core/diff/strategies/multi-search-replace.ts +++ b/src/core/diff/strategies/multi-search-replace.ts @@ -1,8 +1,6 @@ import { DiffStrategy, DiffResult } from "../types" import { addLineNumbers, everyLineHasLineNumbers, stripLineNumbers } from "../../../integrations/misc/extract-text" import { distance } from "fastest-levenshtein" -import { ToolProgressStatus } from "../../../shared/ExtensionMessage" -import { ToolUse } from "../../assistant-message" const BUFFER_LINES = 40 // Number of extra context lines to show before and after matches @@ -364,25 +362,4 @@ Only use a single line of '=======' between search and replacement content, beca failParts: diffResults, } } - - getProgressStatus(toolUse: ToolUse, result?: DiffResult): ToolProgressStatus { - const diffContent = toolUse.params.diff - if (diffContent) { - if (toolUse.partial) { - if (diffContent.length < 1000 || (diffContent.length / 50) % 10 === 0) { - return { text: `progressing ${(diffContent.match(/SEARCH/g) || []).length} blocks...` } - } - } else if (result) { - const searchBlockCount = (diffContent.match(/SEARCH/g) || []).length - if (result.failParts) { - return { - text: `progressed ${searchBlockCount - result.failParts.length}/${searchBlockCount} blocks.`, - } - } else { - return { text: `progressed ${searchBlockCount} blocks.` } - } - } - } - return {} - } } diff --git a/src/core/diff/types.ts b/src/core/diff/types.ts index e12a47762d..be6d8cd311 100644 --- a/src/core/diff/types.ts +++ b/src/core/diff/types.ts @@ -2,9 +2,6 @@ * Interface for implementing different diff strategies */ -import { ToolProgressStatus } from "../../shared/ExtensionMessage" -import { ToolUse } from "../assistant-message" - export type DiffResult = | { success: true; content: string; failParts?: DiffResult[] } | ({ @@ -37,6 +34,4 @@ export interface DiffStrategy { * @returns A DiffResult object containing either the successful result or error details */ applyDiff(originalContent: string, diffContent: string, startLine?: number, endLine?: number): Promise - - getProgressStatus?(toolUse: ToolUse, result?: any): ToolProgressStatus } diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index 0c20669ed1..7487d338b1 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -155,7 +155,6 @@ export interface ClineMessage { reasoning?: string conversationHistoryIndex?: number checkpoint?: Record - progressStatus?: ToolProgressStatus } export type ClineAsk = @@ -275,7 +274,3 @@ export interface HumanRelayCancelMessage { } export type ClineApiReqCancelReason = "streaming_failed" | "user_cancelled" - -export type ToolProgressStatus = { - text?: string -} diff --git a/webview-ui/src/components/chat/ChatRow.tsx b/webview-ui/src/components/chat/ChatRow.tsx index b19d67dc05..259c03fa21 100644 --- a/webview-ui/src/components/chat/ChatRow.tsx +++ b/webview-ui/src/components/chat/ChatRow.tsx @@ -258,7 +258,6 @@ export const ChatRowContent = ({ Roo wants to edit this file:
void isLoading?: boolean - progressStatus?: ToolProgressStatus } /* @@ -34,7 +32,6 @@ const CodeAccordian = ({ isExpanded, onToggleExpand, isLoading, - progressStatus, }: CodeAccordianProps) => { const inferredLanguage = useMemo( () => code && (language ?? (path ? getLanguageFromPath(path) : undefined)), @@ -98,16 +95,6 @@ const CodeAccordian = ({ )}
- {progressStatus && progressStatus.text && ( - - {progressStatus.text} - - )}
)} From c9794d24525ebc1566f75f5e0b4bf6e757dba0d1 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Sun, 9 Mar 2025 18:36:42 -0400 Subject: [PATCH 31/42] Changeset --- .changeset/empty-bees-suffer.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/empty-bees-suffer.md diff --git a/.changeset/empty-bees-suffer.md b/.changeset/empty-bees-suffer.md new file mode 100644 index 0000000000..e3d87a51ad --- /dev/null +++ b/.changeset/empty-bees-suffer.md @@ -0,0 +1,5 @@ +--- +"roo-cline": patch +--- + +Revert tool progress for now From 6bcb39f0a1c1d066363edab18d77d6894f06b2e0 Mon Sep 17 00:00:00 2001 From: Hannes Rudolph <49103247+hannesrudolph@users.noreply.github.com> Date: Sun, 9 Mar 2025 16:45:47 -0600 Subject: [PATCH 32/42] Update webview-ui/src/components/prompts/PromptsView.tsx Co-authored-by: Matt Rubens --- webview-ui/src/components/prompts/PromptsView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webview-ui/src/components/prompts/PromptsView.tsx b/webview-ui/src/components/prompts/PromptsView.tsx index 7303f7691d..9307c0d7a8 100644 --- a/webview-ui/src/components/prompts/PromptsView.tsx +++ b/webview-ui/src/components/prompts/PromptsView.tsx @@ -1037,7 +1037,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => { // The React context will update the global state setEnableCustomModeCreation(e.target.checked) }}> - Enable Custom Mode Creation + Enable Custom Mode Creation Through Prompts

Date: Sun, 9 Mar 2025 16:45:53 -0600 Subject: [PATCH 33/42] Update webview-ui/src/components/prompts/PromptsView.tsx Co-authored-by: Matt Rubens --- webview-ui/src/components/prompts/PromptsView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webview-ui/src/components/prompts/PromptsView.tsx b/webview-ui/src/components/prompts/PromptsView.tsx index 9307c0d7a8..6c69bc4229 100644 --- a/webview-ui/src/components/prompts/PromptsView.tsx +++ b/webview-ui/src/components/prompts/PromptsView.tsx @@ -1047,7 +1047,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => { }}> When enabled, Roo allows you to create custom modes using prompts like ‘Make me a custom mode that…’. Disabling this reduces your system prompt by about 700 tokens when this feature - isn’t needed. When disabled you can still manually create custom prompts using the + button + isn’t needed. When disabled you can still manually create custom modes using the + button above or by editing the related config JSON.

From 6b0e377afc521e31de52d5f3b366ff0fe5269986 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Sun, 9 Mar 2025 19:42:33 -0400 Subject: [PATCH 34/42] v3.8.4 --- .changeset/sixty-ants-begin.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/sixty-ants-begin.md diff --git a/.changeset/sixty-ants-begin.md b/.changeset/sixty-ants-begin.md new file mode 100644 index 0000000000..5ef9e04868 --- /dev/null +++ b/.changeset/sixty-ants-begin.md @@ -0,0 +1,5 @@ +--- +"roo-cline": patch +--- + +v3.8.4 From 847d8f57f0022a39fad8edb8fca408f785dc0d22 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 9 Mar 2025 23:44:37 +0000 Subject: [PATCH 35/42] changeset version bump --- .changeset/empty-bees-suffer.md | 5 ----- .changeset/sixty-ants-begin.md | 5 ----- CHANGELOG.md | 7 +++++++ package-lock.json | 4 ++-- package.json | 2 +- 5 files changed, 10 insertions(+), 13 deletions(-) delete mode 100644 .changeset/empty-bees-suffer.md delete mode 100644 .changeset/sixty-ants-begin.md diff --git a/.changeset/empty-bees-suffer.md b/.changeset/empty-bees-suffer.md deleted file mode 100644 index e3d87a51ad..0000000000 --- a/.changeset/empty-bees-suffer.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"roo-cline": patch ---- - -Revert tool progress for now diff --git a/.changeset/sixty-ants-begin.md b/.changeset/sixty-ants-begin.md deleted file mode 100644 index 5ef9e04868..0000000000 --- a/.changeset/sixty-ants-begin.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"roo-cline": patch ---- - -v3.8.4 diff --git a/CHANGELOG.md b/CHANGELOG.md index 7afcfae2d9..ae74b346d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Roo Code Changelog +## 3.8.4 + +### Patch Changes + +- Revert tool progress for now +- v3.8.4 + ## [3.8.3] - 2025-03-09 - Fix VS Code LM API model picker truncation issue diff --git a/package-lock.json b/package-lock.json index 6884f2e626..cd961b0a21 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "roo-cline", - "version": "3.8.3", + "version": "3.8.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "roo-cline", - "version": "3.8.3", + "version": "3.8.4", "dependencies": { "@anthropic-ai/bedrock-sdk": "^0.10.2", "@anthropic-ai/sdk": "^0.37.0", diff --git a/package.json b/package.json index e4adffd1d7..b32a81071d 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "displayName": "Roo Code (prev. Roo Cline)", "description": "A whole dev team of AI agents in your editor.", "publisher": "RooVeterinaryInc", - "version": "3.8.3", + "version": "3.8.4", "icon": "assets/icons/rocket.png", "galleryBanner": { "color": "#617A91", From 831371cfa9d4354fe2d6f69c113bf3af78761d93 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Sun, 9 Mar 2025 19:47:22 -0400 Subject: [PATCH 36/42] Update CHANGELOG.md --- CHANGELOG.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ae74b346d0..fd6a574dc6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,9 @@ # Roo Code Changelog -## 3.8.4 +## [3.8.4] - 2025-03-09 -### Patch Changes - -- Revert tool progress for now -- v3.8.4 +- Roll back multi-diff progress indicator temporarily to fix a double-confirmation in saving edits +- Add an option in the prompts tab to save tokens by disabling the ability to ask Roo to create/edit custom modes for you (thanks @hannesrudolph!) ## [3.8.3] - 2025-03-09 From 9c08d044b39afb766e012d311ef984be2dba0fdf Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Sun, 9 Mar 2025 22:55:23 -0400 Subject: [PATCH 37/42] Add telemetry for checkpoint save/restore/diff --- src/core/Cline.ts | 6 ++++++ src/services/telemetry/TelemetryService.ts | 15 +++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 3d1f980c7d..65e053c3d1 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -3798,6 +3798,8 @@ export class Cline { return } + telemetryService.captureCheckpointDiffed(this.taskId) + if (!previousCommitHash && mode === "checkpoint") { const previousCheckpoint = this.clineMessages .filter(({ say }) => say === "checkpoint_saved") @@ -3849,6 +3851,8 @@ export class Cline { return } + telemetryService.captureCheckpointCreated(this.taskId) + // Start the checkpoint process in the background. service.saveCheckpoint(`Task: ${this.taskId}, Time: ${Date.now()}`).catch((err) => { console.error("[Cline#checkpointSave] caught unexpected error, disabling checkpoints", err) @@ -3880,6 +3884,8 @@ export class Cline { try { await service.restoreCheckpoint(commitHash) + telemetryService.captureCheckpointRestored(this.taskId) + await this.providerRef.deref()?.postMessageToWebview({ type: "currentCheckpointUpdated", text: commitHash }) if (mode === "restore") { diff --git a/src/services/telemetry/TelemetryService.ts b/src/services/telemetry/TelemetryService.ts index 45a34bda4e..d3ea8bfb5f 100644 --- a/src/services/telemetry/TelemetryService.ts +++ b/src/services/telemetry/TelemetryService.ts @@ -22,6 +22,9 @@ class PostHogClient { CONVERSATION_MESSAGE: "Conversation Message", MODE_SWITCH: "Mode Switched", TOOL_USED: "Tool Used", + CHECKPOINT_CREATED: "Checkpoint Created", + CHECKPOINT_RESTORED: "Checkpoint Restored", + CHECKPOINT_DIFFED: "Checkpoint Diffed", }, } @@ -246,6 +249,18 @@ class TelemetryService { }) } + public captureCheckpointCreated(taskId: string): void { + this.captureEvent(PostHogClient.EVENTS.TASK.CHECKPOINT_CREATED, { taskId }) + } + + public captureCheckpointDiffed(taskId: string): void { + this.captureEvent(PostHogClient.EVENTS.TASK.CHECKPOINT_DIFFED, { taskId }) + } + + public captureCheckpointRestored(taskId: string): void { + this.captureEvent(PostHogClient.EVENTS.TASK.CHECKPOINT_RESTORED, { taskId }) + } + /** * Checks if telemetry is currently enabled * @returns Whether telemetry is enabled From 732eeddc6c87b9135900bb6770ec4409381ba6b7 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Sun, 9 Mar 2025 23:18:44 -0400 Subject: [PATCH 38/42] Add model telemetry too --- src/core/webview/ClineProvider.ts | 9 +++ .../webview/__tests__/ClineProvider.test.ts | 59 +++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index b82a6a62e0..72cf56d35b 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -2567,6 +2567,15 @@ export class ClineProvider implements vscode.WebviewViewProvider { properties.apiProvider = apiConfiguration.apiProvider } + // Add model ID if available + const currentCline = this.getCurrentCline() + if (currentCline?.api) { + const { id: modelId } = currentCline.api.getModel() + if (modelId) { + properties.modelId = modelId + } + } + return properties } } diff --git a/src/core/webview/__tests__/ClineProvider.test.ts b/src/core/webview/__tests__/ClineProvider.test.ts index f9fc5d3ece..2e9fcdf336 100644 --- a/src/core/webview/__tests__/ClineProvider.test.ts +++ b/src/core/webview/__tests__/ClineProvider.test.ts @@ -1652,3 +1652,62 @@ describe("ContextProxy integration", () => { expect(mockContextProxy.setValues).toBeDefined() }) }) + +describe("getTelemetryProperties", () => { + let provider: ClineProvider + let mockContext: vscode.ExtensionContext + let mockOutputChannel: vscode.OutputChannel + let mockCline: any + + beforeEach(() => { + // Reset mocks + jest.clearAllMocks() + + // Setup basic mocks + mockContext = { + globalState: { + get: jest.fn().mockImplementation((key: string) => { + if (key === "mode") return "code" + if (key === "apiProvider") return "anthropic" + return undefined + }), + update: jest.fn(), + keys: jest.fn().mockReturnValue([]), + }, + secrets: { get: jest.fn(), store: jest.fn(), delete: jest.fn() }, + extensionUri: {} as vscode.Uri, + globalStorageUri: { fsPath: "/test/path" }, + extension: { packageJSON: { version: "1.0.0" } }, + } as unknown as vscode.ExtensionContext + + mockOutputChannel = { appendLine: jest.fn() } as unknown as vscode.OutputChannel + provider = new ClineProvider(mockContext, mockOutputChannel) + + // Setup Cline instance with mocked getModel method + const { Cline } = require("../../Cline") + mockCline = new Cline() + mockCline.api = { + getModel: jest.fn().mockReturnValue({ + id: "claude-3-7-sonnet-20250219", + info: { contextWindow: 200000 }, + }), + } + }) + + test("includes basic properties in telemetry", async () => { + const properties = await provider.getTelemetryProperties() + + expect(properties).toHaveProperty("vscodeVersion") + expect(properties).toHaveProperty("platform") + expect(properties).toHaveProperty("appVersion", "1.0.0") + }) + + test("includes model ID from current Cline instance if available", async () => { + // Add mock Cline to stack + await provider.addClineToStack(mockCline) + + const properties = await provider.getTelemetryProperties() + + expect(properties).toHaveProperty("modelId", "claude-3-7-sonnet-20250219") + }) +}) From 9fdc546f0189dbd21999b18227cea289cab9089f Mon Sep 17 00:00:00 2001 From: cte Date: Sun, 9 Mar 2025 21:49:29 -0700 Subject: [PATCH 39/42] Fix portal stuff --- webview-ui/src/App.tsx | 1 - .../src/components/chat/ChatTextArea.tsx | 19 +++++----- webview-ui/src/components/chat/ChatView.tsx | 2 +- .../chat/checkpoints/CheckpointMenu.tsx | 4 ++- .../src/components/ui/dropdown-menu.tsx | 35 +++++++++---------- webview-ui/src/components/ui/hooks/index.ts | 1 + .../src/components/ui/hooks/useRooPortal.ts | 10 ++++++ webview-ui/src/components/ui/popover.tsx | 35 +++++++++---------- .../src/components/ui/select-dropdown.tsx | 27 ++++++-------- webview-ui/src/components/ui/select.tsx | 6 ++-- 10 files changed, 71 insertions(+), 69 deletions(-) create mode 100644 webview-ui/src/components/ui/hooks/useRooPortal.ts diff --git a/webview-ui/src/App.tsx b/webview-ui/src/App.tsx index 99dda495d0..389f5709fc 100644 --- a/webview-ui/src/App.tsx +++ b/webview-ui/src/App.tsx @@ -132,7 +132,6 @@ const App = () => { const AppWithProviders = () => ( -
) diff --git a/webview-ui/src/components/chat/ChatTextArea.tsx b/webview-ui/src/components/chat/ChatTextArea.tsx index 3df3e87e9b..1002788dbc 100644 --- a/webview-ui/src/components/chat/ChatTextArea.tsx +++ b/webview-ui/src/components/chat/ChatTextArea.tsx @@ -1,22 +1,25 @@ import React, { forwardRef, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react" import DynamicTextArea from "react-textarea-autosize" + import { mentionRegex, mentionRegexGlobal } from "../../../../src/shared/context-mentions" -import { useExtensionState } from "../../context/ExtensionStateContext" +import { WebviewMessage } from "../../../../src/shared/WebviewMessage" +import { Mode, getAllModes } from "../../../../src/shared/modes" + +import { vscode } from "@/utils/vscode" import { ContextMenuOptionType, getContextMenuOptions, insertMention, removeMention, shouldShowContextMenu, -} from "../../utils/context-mentions" +} from "@/utils/context-mentions" +import { SelectDropdown, DropdownOptionType } from "@/components/ui" + +import { useExtensionState } from "../../context/ExtensionStateContext" +import Thumbnails from "../common/Thumbnails" +import { convertToMentionPath } from "../../utils/path-mentions" import { MAX_IMAGES_PER_MESSAGE } from "./ChatView" import ContextMenu from "./ContextMenu" -import Thumbnails from "../common/Thumbnails" -import { vscode } from "../../utils/vscode" -import { WebviewMessage } from "../../../../src/shared/WebviewMessage" -import { Mode, getAllModes } from "../../../../src/shared/modes" -import { convertToMentionPath } from "../../utils/path-mentions" -import { SelectDropdown, DropdownOptionType } from "../ui" interface ChatTextAreaProps { inputValue: string diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index 5ac7f50559..09dcdd3ca1 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -1275,7 +1275,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie modeShortcutText={modeShortcutText} /> -
+
) } diff --git a/webview-ui/src/components/chat/checkpoints/CheckpointMenu.tsx b/webview-ui/src/components/chat/checkpoints/CheckpointMenu.tsx index 63867c9858..b6aaebd518 100644 --- a/webview-ui/src/components/chat/checkpoints/CheckpointMenu.tsx +++ b/webview-ui/src/components/chat/checkpoints/CheckpointMenu.tsx @@ -2,6 +2,7 @@ import { useState, useCallback } from "react" import { CheckIcon, Cross2Icon } from "@radix-ui/react-icons" import { Button, Popover, PopoverContent, PopoverTrigger } from "@/components/ui" +import { useRooPortal } from "@/components/ui/hooks" import { vscode } from "../../../utils/vscode" import { Checkpoint } from "./schema" @@ -16,6 +17,7 @@ type CheckpointMenuProps = { export const CheckpointMenu = ({ ts, commitHash, currentHash, checkpoint }: CheckpointMenuProps) => { const [isOpen, setIsOpen] = useState(false) const [isConfirming, setIsConfirming] = useState(false) + const portalContainer = useRooPortal("roo-portal") const isCurrent = currentHash === commitHash const isFirst = checkpoint.isFirst @@ -60,7 +62,7 @@ export const CheckpointMenu = ({ ts, commitHash, currentHash, checkpoint }: Chec - +
{!isCurrent && (
diff --git a/webview-ui/src/components/ui/dropdown-menu.tsx b/webview-ui/src/components/ui/dropdown-menu.tsx index 3193f497ca..c65f18b1b5 100644 --- a/webview-ui/src/components/ui/dropdown-menu.tsx +++ b/webview-ui/src/components/ui/dropdown-menu.tsx @@ -1,5 +1,6 @@ import * as React from "react" import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" +import { PortalProps } from "@radix-ui/react-portal" import { CheckIcon, ChevronRightIcon, DotFilledIcon } from "@radix-ui/react-icons" import { cn } from "@/lib/utils" @@ -53,25 +54,21 @@ DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayNam const DropdownMenuContent = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, sideOffset = 4, ...props }, ref) => { - const container = React.useMemo(() => document.getElementById("roo-portal"), []) - - return ( - - - - ) -}) + React.ComponentPropsWithoutRef & Pick +>(({ className, sideOffset = 4, container, ...props }, ref) => ( + + + +)) DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName const DropdownMenuItem = React.forwardRef< diff --git a/webview-ui/src/components/ui/hooks/index.ts b/webview-ui/src/components/ui/hooks/index.ts index 0ca9075f59..46aff4f28d 100644 --- a/webview-ui/src/components/ui/hooks/index.ts +++ b/webview-ui/src/components/ui/hooks/index.ts @@ -1 +1,2 @@ export * from "./useClipboard" +export * from "./useRooPortal" diff --git a/webview-ui/src/components/ui/hooks/useRooPortal.ts b/webview-ui/src/components/ui/hooks/useRooPortal.ts new file mode 100644 index 0000000000..25ef139e64 --- /dev/null +++ b/webview-ui/src/components/ui/hooks/useRooPortal.ts @@ -0,0 +1,10 @@ +import { useState } from "react" +import { useMount } from "react-use" + +export const useRooPortal = (id: string) => { + const [container, setContainer] = useState() + + useMount(() => setContainer(document.getElementById(id) ?? undefined)) + + return container +} diff --git a/webview-ui/src/components/ui/popover.tsx b/webview-ui/src/components/ui/popover.tsx index b6235853ca..9fc035ad2a 100644 --- a/webview-ui/src/components/ui/popover.tsx +++ b/webview-ui/src/components/ui/popover.tsx @@ -1,4 +1,5 @@ import * as React from "react" +import { PortalProps } from "@radix-ui/react-portal" import * as PopoverPrimitive from "@radix-ui/react-popover" import { cn } from "@/lib/utils" @@ -11,25 +12,21 @@ const PopoverAnchor = PopoverPrimitive.Anchor const PopoverContent = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, align = "center", sideOffset = 4, ...props }, ref) => { - const container = React.useMemo(() => document.getElementById("roo-portal"), []) - - return ( - - - - ) -}) + React.ComponentPropsWithoutRef & Pick +>(({ className, align = "center", sideOffset = 4, container, ...props }, ref) => ( + + + +)) PopoverContent.displayName = PopoverPrimitive.Content.displayName export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor } diff --git a/webview-ui/src/components/ui/select-dropdown.tsx b/webview-ui/src/components/ui/select-dropdown.tsx index eef474cd02..bec496ed50 100644 --- a/webview-ui/src/components/ui/select-dropdown.tsx +++ b/webview-ui/src/components/ui/select-dropdown.tsx @@ -1,4 +1,8 @@ import * as React from "react" + +import { cn } from "@/lib/utils" + +import { useRooPortal } from "./hooks/useRooPortal" import { DropdownMenu, DropdownMenuContent, @@ -6,9 +10,7 @@ import { DropdownMenuTrigger, DropdownMenuSeparator, } from "./dropdown-menu" -import { cn } from "@/lib/utils" -// Constants for option types export enum DropdownOptionType { ITEM = "item", SEPARATOR = "separator", @@ -19,7 +21,7 @@ export interface DropdownOption { value: string label: string disabled?: boolean - type?: DropdownOptionType // Optional type to specify special behaviors + type?: DropdownOptionType } export interface SelectDropdownProps { @@ -38,8 +40,6 @@ export interface SelectDropdownProps { shortcutText?: string } -// TODO: Get rid of this and use the native @shadcn/ui `Select` component. - export const SelectDropdown = React.forwardRef, SelectDropdownProps>( ( { @@ -59,24 +59,19 @@ export const SelectDropdown = React.forwardRef { - // Track open state const [open, setOpen] = React.useState(false) + const portalContainer = useRooPortal("roo-portal") - // Find the selected option label const selectedOption = options.find((option) => option.value === value) const displayText = selectedOption?.label || placeholder || "" - // Handle menu item click const handleSelect = (option: DropdownOption) => { - // Check if this is an action option by its explicit type if (option.type === DropdownOptionType.ACTION) { - window.postMessage({ - type: "action", - action: option.value, - }) + window.postMessage({ type: "action", action: option.value }) setOpen(false) return } + onChange(option.value) setOpen(false) } @@ -94,7 +89,7 @@ export const SelectDropdown = React.forwardRef @@ -121,17 +116,16 @@ export const SelectDropdown = React.forwardRef setOpen(false)} onInteractOutside={() => setOpen(false)} + container={portalContainer} className={cn( "bg-vscode-dropdown-background text-vscode-dropdown-foreground border border-vscode-dropdown-border z-50", contentClassName, )}> {options.map((option, index) => { - // Handle separator type if (option.type === DropdownOptionType.SEPARATOR) { return } - // Handle shortcut text type (disabled label for keyboard shortcuts) if ( option.type === DropdownOptionType.SHORTCUT || (option.disabled && shortcutText && option.label.includes(shortcutText)) @@ -143,7 +137,6 @@ export const SelectDropdown = React.forwardRef) { - const container = React.useMemo(() => document.getElementById("roo-portal"), []) - +}: React.ComponentProps & Pick) { return ( Date: Mon, 10 Mar 2025 11:51:47 +0100 Subject: [PATCH 40/42] ci: publish git tags to Github (resolves #444) --- .changeset/automatic-tags-publish.md | 5 +++++ .github/workflows/marketplace-publish.yml | 9 +++++++++ 2 files changed, 14 insertions(+) create mode 100644 .changeset/automatic-tags-publish.md diff --git a/.changeset/automatic-tags-publish.md b/.changeset/automatic-tags-publish.md new file mode 100644 index 0000000000..a3ff07fb4c --- /dev/null +++ b/.changeset/automatic-tags-publish.md @@ -0,0 +1,5 @@ +--- +"roo-cline": patch +--- + +Update GitHub Actions workflow to automatically create and push git tags during release diff --git a/.github/workflows/marketplace-publish.yml b/.github/workflows/marketplace-publish.yml index c6fd66b1b3..7d71b2f86f 100644 --- a/.github/workflows/marketplace-publish.yml +++ b/.github/workflows/marketplace-publish.yml @@ -10,6 +10,8 @@ env: jobs: publish-extension: runs-on: ubuntu-latest + permissions: + contents: write # Required for pushing tags if: > ( github.event_name == 'pull_request' && github.event.pull_request.base.ref == 'main' && @@ -51,3 +53,10 @@ jobs: npm run publish:marketplace echo "Successfully published version $current_package_version to VS Code Marketplace" + + - name: Create and Push Git Tag + run: | + current_package_version=$(node -p "require('./package.json').version") + git tag -a "v${current_package_version}" -m "Release v${current_package_version}" + git push origin "v${current_package_version}" + echo "Successfully created and pushed git tag v${current_package_version}" From 03045d8e752c4e693f7ba652cc09d97c11c5aea5 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Mon, 10 Mar 2025 09:54:45 -0400 Subject: [PATCH 41/42] Update src/core/Cline.ts --- src/core/Cline.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 4f27a89cc0..7f2b77ec1e 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -875,7 +875,7 @@ export class Cline { // The way this agentic loop works is that cline will be given a task that he then calls tools to complete. unless there's an attempt_completion call, we keep responding back to him with his tool's responses until he either attempt_completion or does not use anymore tools. If he does not use anymore tools, we ask him to consider if he's completed the task and then call attempt_completion, otherwise proceed with completing the task. // There is a MAX_REQUESTS_PER_TASK limit to prevent infinite requests, but Cline is prompted to finish the task as efficiently as he can. - //const totalCost = this.calculateApiCostAntrhopic(totalInputTokens, totalOutputTokens) + //const totalCost = this.calculateApiCostAnthropic(totalInputTokens, totalOutputTokens) if (didEndLoop) { // For now a task never 'completes'. This will only happen if the user hits max requests and denies resetting the count. //this.say("task_completed", `Task completed. Total API usage cost: ${totalCost}`) From f99fc59460f1049f53ac82b809d9b11dc9d2663f Mon Sep 17 00:00:00 2001 From: Patrick Decat Date: Mon, 10 Mar 2025 15:29:14 +0100 Subject: [PATCH 42/42] ci: tag after packaging but before publishing --- .github/workflows/marketplace-publish.yml | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/.github/workflows/marketplace-publish.yml b/.github/workflows/marketplace-publish.yml index 7d71b2f86f..4ecd2af7a2 100644 --- a/.github/workflows/marketplace-publish.yml +++ b/.github/workflows/marketplace-publish.yml @@ -25,24 +25,24 @@ jobs: - uses: actions/setup-node@v4 with: node-version: 18 + - run: | git config user.name github-actions git config user.email github-actions@github.com + - name: Install Dependencies run: | npm install -g vsce ovsx npm run install:ci + - name: Create .env file run: | echo "# PostHog API Keys for telemetry" > .env echo "POSTHOG_API_KEY=${{ secrets.POSTHOG_API_KEY }}" >> .env - - name: Package and Publish Extension - env: - VSCE_PAT: ${{ secrets.VSCE_PAT }} - OVSX_PAT: ${{ secrets.OVSX_PAT }} + + - name: Package Extension run: | current_package_version=$(node -p "require('./package.json').version") - npm run vsix package=$(unzip -l bin/roo-cline-${current_package_version}.vsix) echo "$package" @@ -51,12 +51,18 @@ jobs: echo "$package" | grep -q "extension/node_modules/@vscode/codicons/dist/codicon.ttf" || exit 1 echo "$package" | grep -q ".env" || exit 1 - npm run publish:marketplace - echo "Successfully published version $current_package_version to VS Code Marketplace" - - name: Create and Push Git Tag run: | current_package_version=$(node -p "require('./package.json').version") git tag -a "v${current_package_version}" -m "Release v${current_package_version}" git push origin "v${current_package_version}" echo "Successfully created and pushed git tag v${current_package_version}" + + - name: Publish Extension + env: + VSCE_PAT: ${{ secrets.VSCE_PAT }} + OVSX_PAT: ${{ secrets.OVSX_PAT }} + run: | + current_package_version=$(node -p "require('./package.json').version") + npm run publish:marketplace + echo "Successfully published version $current_package_version to VS Code Marketplace"